Merge lp:~blake-rouse/maas/node-networking-link into lp:~maas-committers/maas/trunk
- node-networking-link
- Merge into trunk
Proposed by
Blake Rouse
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Blake Rouse | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 4337 | ||||
Proposed branch: | lp:~blake-rouse/maas/node-networking-link | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
2142 lines (+1529/-130) 10 files modified
src/maasserver/models/interface.py (+134/-10) src/maasserver/models/tests/test_interface.py (+503/-1) src/maasserver/static/js/angular/controllers/node_details_networking.js (+175/-24) src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js (+530/-58) src/maasserver/static/js/angular/factories/nodes.js (+14/-2) src/maasserver/static/js/angular/factories/tests/test_nodes.js (+45/-1) src/maasserver/static/partials/node-details.html (+21/-22) src/maasserver/websockets/handlers/node.py (+33/-3) src/maasserver/websockets/handlers/tests/test_node.py (+54/-5) src/maastesting/factory.py (+20/-4) |
||||
To merge this branch: | bzr merge lp:~blake-rouse/maas/node-networking-link | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andres Rodriguez (community) | Approve | ||
Review via email: mp+273002@code.launchpad.net |
Commit message
Add the ability to change the link mode of an interface or alias in the UI. Provide the backend work to allow making this changed without changing the link_id. Order the links for each interface by ID so the order never changes. Clean up the unmountFilesystem websocket handler name and fix the naming of subnets and vlans in the dropdowns.
Description of the change
This is a rather large change just because the backend could not handle the interaction that the UI wanted. Also includes some small fixes and HTML and JS.
I have fully tested this from packaging and it fully works.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/models/interface.py' |
2 | --- src/maasserver/models/interface.py 2015-09-24 16:22:12 +0000 |
3 | +++ src/maasserver/models/interface.py 2015-10-01 14:33:47 +0000 |
4 | @@ -601,7 +601,8 @@ |
5 | return ip_address |
6 | |
7 | def _link_subnet_static( |
8 | - self, subnet, ip_address=None, alloc_type=None, user=None): |
9 | + self, subnet, ip_address=None, alloc_type=None, user=None, |
10 | + swap_static_ip=None): |
11 | """Link interface to subnet using STATIC.""" |
12 | valid_alloc_types = [ |
13 | IPADDRESS_TYPE.STICKY, |
14 | @@ -617,12 +618,8 @@ |
15 | user = None |
16 | |
17 | ngi = None |
18 | - has_allocations = False |
19 | if subnet is not None: |
20 | ngi = subnet.get_managed_cluster_interface() |
21 | - if ngi is not None: |
22 | - has_allocations = self._has_static_allocation_on_cluster( |
23 | - ngi.nodegroup, get_subnet_family(subnet)) |
24 | |
25 | if ip_address: |
26 | ip_address = IPAddress(ip_address) |
27 | @@ -664,10 +661,20 @@ |
28 | None, None, alloc_type=alloc_type, subnet=subnet, user=user) |
29 | self.ip_addresses.add(static_ip) |
30 | |
31 | + # Swap the ID's that way it keeps the same ID as the swap object. |
32 | + if swap_static_ip is not None: |
33 | + static_ip.id, swap_static_ip.id = swap_static_ip.id, static_ip.id |
34 | + swap_static_ip.delete() |
35 | + static_ip.save() |
36 | + |
37 | # Need to update the hostmaps on the cluster, if this subnet |
38 | # has a managed interface. |
39 | - if ngi is not None and not has_allocations: |
40 | - self._update_host_maps(ngi.nodegroup, static_ip) |
41 | + if ngi is not None: |
42 | + allocated_ip = ( |
43 | + self._get_first_static_allocation_for_cluster( |
44 | + ngi.nodegroup, get_subnet_family(subnet))) |
45 | + if allocated_ip is None or allocated_ip.id == static_ip.id: |
46 | + self._update_host_maps(ngi.nodegroup, static_ip) |
47 | |
48 | # Was successful at creating the STATIC link. Remove the DHCP and |
49 | # LINK_UP link if it exists. |
50 | @@ -756,7 +763,8 @@ |
51 | subnet = None |
52 | self.link_subnet(INTERFACE_LINK_TYPE.LINK_UP, subnet) |
53 | |
54 | - def _unlink_static_ip(self, static_ip, update_cluster=True): |
55 | + def _unlink_static_ip( |
56 | + self, static_ip, update_cluster=True, swap_alloc_type=None): |
57 | """Unlink the STATIC IP address from the interface.""" |
58 | registered_on_cluster = False |
59 | ngi = None |
60 | @@ -771,14 +779,23 @@ |
61 | # Need to remove the hostmap on the cluster before it can |
62 | # be deleted. |
63 | self._remove_host_maps(ngi.nodegroup, static_ip) |
64 | - static_ip.delete() |
65 | + |
66 | + # If the allocation type is only changing then we don't need to delete |
67 | + # the IP address it needs to be updated. |
68 | + ip_version = IPAddress(static_ip.ip).version |
69 | + if swap_alloc_type is not None: |
70 | + static_ip.alloc_type = swap_alloc_type |
71 | + static_ip.ip = None |
72 | + static_ip.save() |
73 | + else: |
74 | + static_ip.delete() |
75 | |
76 | # If this IP address was registered on the cluster and now has been |
77 | # deleted we need to register the next assigned IP address to the |
78 | # cluster hostmap. |
79 | if registered_on_cluster and ngi is not None and update_cluster: |
80 | new_hostmap_ip = self._get_first_static_allocation_for_cluster( |
81 | - ngi.nodegroup, IPAddress(static_ip.ip).version) |
82 | + ngi.nodegroup, ip_version) |
83 | if new_hostmap_ip is not None: |
84 | self._update_host_maps(ngi.nodegroup, new_hostmap_ip) |
85 | |
86 | @@ -787,6 +804,7 @@ |
87 | self._update_dns_zones([ngi.nodegroup]) |
88 | else: |
89 | self._update_dns_zones() |
90 | + return static_ip |
91 | |
92 | def unlink_ip_address( |
93 | self, ip_address, update_cluster=True, clearing_config=False): |
94 | @@ -815,6 +833,112 @@ |
95 | ip_address = self.ip_addresses.get(id=link_id) |
96 | self.unlink_ip_address(ip_address) |
97 | |
98 | + def _swap_subnet(self, static_ip, subnet, ip_address=None): |
99 | + """Swap the subnet for the `static_ip`.""" |
100 | + # Check that requested `ip_address` is available. |
101 | + if ip_address is not None: |
102 | + already_used = get_one( |
103 | + StaticIPAddress.objects.filter(ip=ip_address)) |
104 | + if already_used is not None: |
105 | + raise StaticIPAddressUnavailable( |
106 | + "IP address is already in use.") |
107 | + |
108 | + # Remove the hostmap on the new subnet. |
109 | + new_subnet_ngi = subnet.get_managed_cluster_interface() |
110 | + if new_subnet_ngi is not None: |
111 | + static_ip_on_new_subnet = ( |
112 | + self._get_first_static_allocation_for_cluster( |
113 | + new_subnet_ngi.nodegroup, get_subnet_family(subnet))) |
114 | + if (static_ip_on_new_subnet is not None and |
115 | + static_ip_on_new_subnet.id > static_ip.id): |
116 | + # The updated static_id should be registered over the other |
117 | + # IP address registered on the new subnet. |
118 | + self._remove_host_maps( |
119 | + new_subnet_ngi.nodegroup, static_ip_on_new_subnet) |
120 | + |
121 | + # If the subnets are different then remove the hostmap from the old |
122 | + # subnet as well. |
123 | + if static_ip.subnet is not None and static_ip.subnet != subnet: |
124 | + old_subnet_ngi = static_ip.subnet.get_managed_cluster_interface() |
125 | + registered_on_cluster = False |
126 | + if old_subnet_ngi is not None: |
127 | + registered_on_cluster = ( |
128 | + self._is_first_static_allocation_on_cluster( |
129 | + static_ip, old_subnet_ngi.nodegroup)) |
130 | + if registered_on_cluster: |
131 | + self._remove_host_maps(old_subnet_ngi.nodegroup, static_ip) |
132 | + |
133 | + # Clear the subnet before checking which is the next hostmap. |
134 | + static_ip.subnet = None |
135 | + static_ip.save() |
136 | + |
137 | + # Register the new STATIC IP address for the old subnet. |
138 | + if registered_on_cluster and old_subnet_ngi is not None: |
139 | + new_hostmap_ip = self._get_first_static_allocation_for_cluster( |
140 | + old_subnet_ngi.nodegroup, IPAddress(static_ip.ip).version) |
141 | + if new_hostmap_ip is not None: |
142 | + self._update_host_maps( |
143 | + old_subnet_ngi.nodegroup, new_hostmap_ip) |
144 | + |
145 | + # Update the DNS configuration for the old subnet if needed. |
146 | + if old_subnet_ngi is not None: |
147 | + self._update_dns_zones([old_subnet_ngi.nodegroup]) |
148 | + |
149 | + # If the IP addresses are on the same subnet but the IP's are |
150 | + # different then we need to remove the hostmap. |
151 | + if (static_ip.subnet == subnet and |
152 | + new_subnet_ngi is not None and |
153 | + static_ip.ip != ip_address): |
154 | + self._remove_host_maps( |
155 | + new_subnet_ngi.nodegroup, static_ip) |
156 | + |
157 | + # Link to the new subnet, which will also update the hostmap. |
158 | + return self._link_subnet_static( |
159 | + subnet, ip_address=ip_address, swap_static_ip=static_ip) |
160 | + |
161 | + def update_ip_address( |
162 | + self, static_ip, mode, subnet, ip_address=None): |
163 | + """Update an already existing link on interface to be the new data.""" |
164 | + if mode == INTERFACE_LINK_TYPE.AUTO: |
165 | + new_alloc_type = IPADDRESS_TYPE.AUTO |
166 | + elif mode == INTERFACE_LINK_TYPE.DHCP: |
167 | + new_alloc_type = IPADDRESS_TYPE.DHCP |
168 | + elif mode in [INTERFACE_LINK_TYPE.LINK_UP, INTERFACE_LINK_TYPE.STATIC]: |
169 | + new_alloc_type = IPADDRESS_TYPE.STICKY |
170 | + |
171 | + current_mode = static_ip.get_interface_link_type() |
172 | + if current_mode == INTERFACE_LINK_TYPE.STATIC: |
173 | + if mode == INTERFACE_LINK_TYPE.STATIC: |
174 | + if (static_ip.subnet == subnet and ( |
175 | + ip_address is None or static_ip.ip == ip_address)): |
176 | + # Same subnet and IP address nothing to do. |
177 | + return static_ip |
178 | + # Update the subent and IP address for the static assignment. |
179 | + return self._swap_subnet( |
180 | + static_ip, subnet, ip_address=ip_address) |
181 | + else: |
182 | + # Not staying in the same mode so we can just remove the |
183 | + # static IP and change its alloc_type from STICKY. |
184 | + static_ip = self._unlink_static_ip( |
185 | + static_ip, swap_alloc_type=new_alloc_type) |
186 | + elif mode == INTERFACE_LINK_TYPE.STATIC: |
187 | + # Linking to the subnet statically were the original was not a |
188 | + # static link. Swap the objects so the object keeps the same ID. |
189 | + return self._link_subnet_static( |
190 | + subnet, ip_address=ip_address, |
191 | + swap_static_ip=static_ip) |
192 | + static_ip.alloc_type = new_alloc_type |
193 | + static_ip.ip = None |
194 | + static_ip.subnet = subnet |
195 | + static_ip.save() |
196 | + return static_ip |
197 | + |
198 | + def update_link_by_id(self, link_id, mode, subnet, ip_address=None): |
199 | + """Update the `IPAddress` link on interface by its ID.""" |
200 | + static_ip = self.ip_addresses.get(id=link_id) |
201 | + return self.update_ip_address( |
202 | + static_ip, mode, subnet, ip_address=ip_address) |
203 | + |
204 | def clear_all_links(self, clearing_config=False): |
205 | """Remove all the `IPAddress` link on the interface.""" |
206 | for ip_address in self.ip_addresses.exclude( |
207 | |
208 | === modified file 'src/maasserver/models/tests/test_interface.py' |
209 | --- src/maasserver/models/tests/test_interface.py 2015-09-24 16:22:12 +0000 |
210 | +++ src/maasserver/models/tests/test_interface.py 2015-10-01 14:33:47 +0000 |
211 | @@ -29,7 +29,10 @@ |
212 | NODEGROUP_STATUS, |
213 | NODEGROUPINTERFACE_MANAGEMENT, |
214 | ) |
215 | -from maasserver.exceptions import StaticIPAddressOutOfRange |
216 | +from maasserver.exceptions import ( |
217 | + StaticIPAddressOutOfRange, |
218 | + StaticIPAddressUnavailable, |
219 | +) |
220 | from maasserver.models import ( |
221 | Fabric, |
222 | interface as interface_module, |
223 | @@ -67,6 +70,7 @@ |
224 | IPNetwork, |
225 | IPRange, |
226 | ) |
227 | +from testtools import ExpectedException |
228 | from testtools.matchers import ( |
229 | Equals, |
230 | MatchesDict, |
231 | @@ -1318,6 +1322,504 @@ |
232 | alloc_type=IPADDRESS_TYPE.STICKY, ip=None).first()) |
233 | |
234 | |
235 | +class TestUpdateIPAddress(MAASServerTestCase): |
236 | + """Tests for `Interface.update_ip_address`.""" |
237 | + |
238 | + def test__switch_dhcp_to_auto(self): |
239 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
240 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
241 | + static_ip = factory.make_StaticIPAddress( |
242 | + alloc_type=IPADDRESS_TYPE.DHCP, ip="", |
243 | + subnet=subnet, interface=interface) |
244 | + static_id = static_ip.id |
245 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
246 | + static_ip = interface.update_ip_address( |
247 | + static_ip, INTERFACE_LINK_TYPE.AUTO, new_subnet) |
248 | + self.assertEquals(static_id, static_ip.id) |
249 | + self.assertEquals(IPADDRESS_TYPE.AUTO, static_ip.alloc_type) |
250 | + self.assertEquals(new_subnet, static_ip.subnet) |
251 | + self.assertIsNone(static_ip.ip) |
252 | + |
253 | + def test__switch_dhcp_to_link_up(self): |
254 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
255 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
256 | + static_ip = factory.make_StaticIPAddress( |
257 | + alloc_type=IPADDRESS_TYPE.DHCP, ip="", |
258 | + subnet=subnet, interface=interface) |
259 | + static_id = static_ip.id |
260 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
261 | + static_ip = interface.update_ip_address( |
262 | + static_ip, INTERFACE_LINK_TYPE.LINK_UP, new_subnet) |
263 | + self.assertEquals(static_id, static_ip.id) |
264 | + self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type) |
265 | + self.assertEquals(new_subnet, static_ip.subnet) |
266 | + self.assertIsNone(static_ip.ip) |
267 | + |
268 | + def test__switch_dhcp_to_static(self): |
269 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
270 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
271 | + network_v4 = factory.make_ipv4_network(slash=24) |
272 | + subnet = factory.make_Subnet( |
273 | + vlan=interface.vlan, cidr=unicode(network_v4.cidr)) |
274 | + factory.make_NodeGroupInterface( |
275 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
276 | + subnet=subnet) |
277 | + static_ip = factory.make_StaticIPAddress( |
278 | + alloc_type=IPADDRESS_TYPE.DHCP, ip="", |
279 | + subnet=subnet, interface=interface) |
280 | + static_id = static_ip.id |
281 | + network_v6 = factory.make_ipv6_network(slash=24) |
282 | + new_subnet = factory.make_Subnet( |
283 | + vlan=interface.vlan, cidr=unicode(network_v6.cidr)) |
284 | + factory.make_NodeGroupInterface( |
285 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
286 | + subnet=new_subnet) |
287 | + mock_update_host_maps = self.patch_autospec( |
288 | + interface, "_update_host_maps") |
289 | + mock_update_dns_zones = self.patch_autospec( |
290 | + interface, "_update_dns_zones") |
291 | + static_ip = interface.update_ip_address( |
292 | + static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet) |
293 | + self.assertEquals(static_id, static_ip.id) |
294 | + self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type) |
295 | + self.assertEquals(new_subnet, static_ip.subnet) |
296 | + self.assertIsNotNone(static_ip.ip) |
297 | + self.assertThat( |
298 | + mock_update_host_maps, MockCalledOnceWith(nodegroup, static_ip)) |
299 | + self.assertThat( |
300 | + mock_update_dns_zones, MockCalledOnceWith([nodegroup])) |
301 | + |
302 | + def test__switch_auto_to_dhcp(self): |
303 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
304 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
305 | + static_ip = factory.make_StaticIPAddress( |
306 | + alloc_type=IPADDRESS_TYPE.AUTO, ip="", |
307 | + subnet=subnet, interface=interface) |
308 | + static_id = static_ip.id |
309 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
310 | + static_ip = interface.update_ip_address( |
311 | + static_ip, INTERFACE_LINK_TYPE.DHCP, new_subnet) |
312 | + self.assertEquals(static_id, static_ip.id) |
313 | + self.assertEquals(IPADDRESS_TYPE.DHCP, static_ip.alloc_type) |
314 | + self.assertEquals(new_subnet, static_ip.subnet) |
315 | + self.assertIsNone(static_ip.ip) |
316 | + |
317 | + def test__switch_auto_to_link_up(self): |
318 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
319 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
320 | + static_ip = factory.make_StaticIPAddress( |
321 | + alloc_type=IPADDRESS_TYPE.AUTO, ip="", |
322 | + subnet=subnet, interface=interface) |
323 | + static_id = static_ip.id |
324 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
325 | + static_ip = interface.update_ip_address( |
326 | + static_ip, INTERFACE_LINK_TYPE.LINK_UP, new_subnet) |
327 | + self.assertEquals(static_id, static_ip.id) |
328 | + self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type) |
329 | + self.assertEquals(new_subnet, static_ip.subnet) |
330 | + self.assertIsNone(static_ip.ip) |
331 | + |
332 | + def test__switch_auto_to_static(self): |
333 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
334 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
335 | + network_v4 = factory.make_ipv4_network(slash=24) |
336 | + subnet = factory.make_Subnet( |
337 | + vlan=interface.vlan, cidr=unicode(network_v4.cidr)) |
338 | + factory.make_NodeGroupInterface( |
339 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
340 | + subnet=subnet) |
341 | + static_ip = factory.make_StaticIPAddress( |
342 | + alloc_type=IPADDRESS_TYPE.AUTO, ip="", |
343 | + subnet=subnet, interface=interface) |
344 | + static_id = static_ip.id |
345 | + network_v6 = factory.make_ipv6_network(slash=24) |
346 | + new_subnet = factory.make_Subnet( |
347 | + vlan=interface.vlan, cidr=unicode(network_v6.cidr)) |
348 | + factory.make_NodeGroupInterface( |
349 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
350 | + subnet=new_subnet) |
351 | + mock_update_host_maps = self.patch_autospec( |
352 | + interface, "_update_host_maps") |
353 | + mock_update_dns_zones = self.patch_autospec( |
354 | + interface, "_update_dns_zones") |
355 | + static_ip = interface.update_ip_address( |
356 | + static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet) |
357 | + self.assertEquals(static_id, static_ip.id) |
358 | + self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type) |
359 | + self.assertEquals(new_subnet, static_ip.subnet) |
360 | + self.assertIsNotNone(static_ip.ip) |
361 | + self.assertThat( |
362 | + mock_update_host_maps, MockCalledOnceWith(nodegroup, static_ip)) |
363 | + self.assertThat( |
364 | + mock_update_dns_zones, MockCalledOnceWith([nodegroup])) |
365 | + |
366 | + def test__switch_link_up_to_auto(self): |
367 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
368 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
369 | + static_ip = factory.make_StaticIPAddress( |
370 | + alloc_type=IPADDRESS_TYPE.STICKY, ip="", |
371 | + subnet=subnet, interface=interface) |
372 | + static_id = static_ip.id |
373 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
374 | + static_ip = interface.update_ip_address( |
375 | + static_ip, INTERFACE_LINK_TYPE.AUTO, new_subnet) |
376 | + self.assertEquals(static_id, static_ip.id) |
377 | + self.assertEquals(IPADDRESS_TYPE.AUTO, static_ip.alloc_type) |
378 | + self.assertEquals(new_subnet, static_ip.subnet) |
379 | + self.assertIsNone(static_ip.ip) |
380 | + |
381 | + def test__switch_link_up_to_dhcp(self): |
382 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
383 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
384 | + static_ip = factory.make_StaticIPAddress( |
385 | + alloc_type=IPADDRESS_TYPE.STICKY, ip="", |
386 | + subnet=subnet, interface=interface) |
387 | + static_id = static_ip.id |
388 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
389 | + static_ip = interface.update_ip_address( |
390 | + static_ip, INTERFACE_LINK_TYPE.DHCP, new_subnet) |
391 | + self.assertEquals(static_id, static_ip.id) |
392 | + self.assertEquals(IPADDRESS_TYPE.DHCP, static_ip.alloc_type) |
393 | + self.assertEquals(new_subnet, static_ip.subnet) |
394 | + self.assertIsNone(static_ip.ip) |
395 | + |
396 | + def test__switch_link_up_to_static(self): |
397 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
398 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
399 | + network_v4 = factory.make_ipv4_network(slash=24) |
400 | + subnet = factory.make_Subnet( |
401 | + vlan=interface.vlan, cidr=unicode(network_v4.cidr)) |
402 | + factory.make_NodeGroupInterface( |
403 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
404 | + subnet=subnet) |
405 | + static_ip = factory.make_StaticIPAddress( |
406 | + alloc_type=IPADDRESS_TYPE.STICKY, ip="", |
407 | + subnet=subnet, interface=interface) |
408 | + static_id = static_ip.id |
409 | + network_v6 = factory.make_ipv6_network(slash=24) |
410 | + new_subnet = factory.make_Subnet( |
411 | + vlan=interface.vlan, cidr=unicode(network_v6.cidr)) |
412 | + factory.make_NodeGroupInterface( |
413 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
414 | + subnet=new_subnet) |
415 | + mock_update_host_maps = self.patch_autospec( |
416 | + interface, "_update_host_maps") |
417 | + mock_update_dns_zones = self.patch_autospec( |
418 | + interface, "_update_dns_zones") |
419 | + static_ip = interface.update_ip_address( |
420 | + static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet) |
421 | + self.assertEquals(static_id, static_ip.id) |
422 | + self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type) |
423 | + self.assertEquals(new_subnet, static_ip.subnet) |
424 | + self.assertIsNotNone(static_ip.ip) |
425 | + self.assertThat( |
426 | + mock_update_host_maps, MockCalledOnceWith(nodegroup, static_ip)) |
427 | + self.assertThat( |
428 | + mock_update_dns_zones, MockCalledOnceWith([nodegroup])) |
429 | + |
430 | + def test__switch_static_to_dhcp(self): |
431 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
432 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
433 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
434 | + ngi = factory.make_NodeGroupInterface( |
435 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
436 | + subnet=subnet) |
437 | + static_ip = factory.make_StaticIPAddress( |
438 | + alloc_type=IPADDRESS_TYPE.STICKY, |
439 | + ip=factory.pick_ip_in_static_range(ngi), |
440 | + subnet=subnet, interface=interface) |
441 | + static_id = static_ip.id |
442 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
443 | + mock_remove_host_maps = self.patch_autospec( |
444 | + interface, "_remove_host_maps") |
445 | + mock_update_dns_zones = self.patch_autospec( |
446 | + interface, "_update_dns_zones") |
447 | + static_ip = interface.update_ip_address( |
448 | + static_ip, INTERFACE_LINK_TYPE.DHCP, new_subnet) |
449 | + self.assertEquals(static_id, static_ip.id) |
450 | + self.assertEquals(IPADDRESS_TYPE.DHCP, static_ip.alloc_type) |
451 | + self.assertEquals(new_subnet, static_ip.subnet) |
452 | + self.assertIsNone(static_ip.ip) |
453 | + self.assertThat( |
454 | + mock_remove_host_maps, MockCalledOnceWith(nodegroup, static_ip)) |
455 | + self.assertThat( |
456 | + mock_update_dns_zones, MockCalledOnceWith([nodegroup])) |
457 | + |
458 | + def test__switch_static_to_auto(self): |
459 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
460 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
461 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
462 | + ngi = factory.make_NodeGroupInterface( |
463 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
464 | + subnet=subnet) |
465 | + static_ip = factory.make_StaticIPAddress( |
466 | + alloc_type=IPADDRESS_TYPE.STICKY, |
467 | + ip=factory.pick_ip_in_static_range(ngi), |
468 | + subnet=subnet, interface=interface) |
469 | + static_id = static_ip.id |
470 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
471 | + mock_remove_host_maps = self.patch_autospec( |
472 | + interface, "_remove_host_maps") |
473 | + mock_update_dns_zones = self.patch_autospec( |
474 | + interface, "_update_dns_zones") |
475 | + static_ip = interface.update_ip_address( |
476 | + static_ip, INTERFACE_LINK_TYPE.AUTO, new_subnet) |
477 | + self.assertEquals(static_id, static_ip.id) |
478 | + self.assertEquals(IPADDRESS_TYPE.AUTO, static_ip.alloc_type) |
479 | + self.assertEquals(new_subnet, static_ip.subnet) |
480 | + self.assertIsNone(static_ip.ip) |
481 | + self.assertThat( |
482 | + mock_remove_host_maps, MockCalledOnceWith(nodegroup, static_ip)) |
483 | + self.assertThat( |
484 | + mock_update_dns_zones, MockCalledOnceWith([nodegroup])) |
485 | + |
486 | + def test__switch_static_to_link_up(self): |
487 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
488 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
489 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
490 | + ngi = factory.make_NodeGroupInterface( |
491 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
492 | + subnet=subnet) |
493 | + static_ip = factory.make_StaticIPAddress( |
494 | + alloc_type=IPADDRESS_TYPE.STICKY, |
495 | + ip=factory.pick_ip_in_static_range(ngi), |
496 | + subnet=subnet, interface=interface) |
497 | + static_id = static_ip.id |
498 | + new_subnet = factory.make_Subnet(vlan=interface.vlan) |
499 | + mock_remove_host_maps = self.patch_autospec( |
500 | + interface, "_remove_host_maps") |
501 | + mock_update_dns_zones = self.patch_autospec( |
502 | + interface, "_update_dns_zones") |
503 | + static_ip = interface.update_ip_address( |
504 | + static_ip, INTERFACE_LINK_TYPE.LINK_UP, new_subnet) |
505 | + self.assertEquals(static_id, static_ip.id) |
506 | + self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type) |
507 | + self.assertEquals(new_subnet, static_ip.subnet) |
508 | + self.assertIsNone(static_ip.ip) |
509 | + self.assertThat( |
510 | + mock_remove_host_maps, MockCalledOnceWith(nodegroup, static_ip)) |
511 | + self.assertThat( |
512 | + mock_update_dns_zones, MockCalledOnceWith([nodegroup])) |
513 | + |
514 | + def test__switch_static_to_same_subnet_does_nothing(self): |
515 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
516 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
517 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
518 | + ngi = factory.make_NodeGroupInterface( |
519 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
520 | + subnet=subnet) |
521 | + static_ip = factory.make_StaticIPAddress( |
522 | + alloc_type=IPADDRESS_TYPE.STICKY, |
523 | + ip=factory.pick_ip_in_static_range(ngi), |
524 | + subnet=subnet, interface=interface) |
525 | + static_id = static_ip.id |
526 | + static_ip_address = static_ip.ip |
527 | + mock_remove_host_maps = self.patch_autospec( |
528 | + interface, "_remove_host_maps") |
529 | + mock_update_host_maps = self.patch_autospec( |
530 | + interface, "_update_host_maps") |
531 | + mock_update_dns_zones = self.patch_autospec( |
532 | + interface, "_update_dns_zones") |
533 | + static_ip = interface.update_ip_address( |
534 | + static_ip, INTERFACE_LINK_TYPE.STATIC, subnet) |
535 | + self.assertEquals(static_id, static_ip.id) |
536 | + self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type) |
537 | + self.assertEquals(subnet, static_ip.subnet) |
538 | + self.assertEquals(static_ip_address, static_ip.ip) |
539 | + self.assertThat(mock_remove_host_maps, MockNotCalled()) |
540 | + self.assertThat(mock_update_host_maps, MockNotCalled()) |
541 | + self.assertThat(mock_update_dns_zones, MockNotCalled()) |
542 | + |
543 | + def test__switch_static_to_already_used_ip_address(self): |
544 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
545 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
546 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
547 | + ngi = factory.make_NodeGroupInterface( |
548 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
549 | + subnet=subnet) |
550 | + static_ip = factory.make_StaticIPAddress( |
551 | + alloc_type=IPADDRESS_TYPE.STICKY, |
552 | + ip=factory.pick_ip_in_static_range(ngi), |
553 | + subnet=subnet, interface=interface) |
554 | + other_interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
555 | + used_ip_address = factory.pick_ip_in_static_range( |
556 | + ngi, but_not=[static_ip.ip]) |
557 | + factory.make_StaticIPAddress( |
558 | + alloc_type=IPADDRESS_TYPE.STICKY, |
559 | + ip=used_ip_address, |
560 | + subnet=subnet, interface=other_interface) |
561 | + with ExpectedException(StaticIPAddressUnavailable): |
562 | + interface.update_ip_address( |
563 | + static_ip, INTERFACE_LINK_TYPE.STATIC, subnet, |
564 | + ip_address=used_ip_address) |
565 | + |
566 | + def test__switch_static_to_same_subnet_with_different_ip(self): |
567 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
568 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
569 | + network = factory.make_ipv4_network(slash=24) |
570 | + subnet = factory.make_Subnet( |
571 | + vlan=interface.vlan, cidr=unicode(network.cidr)) |
572 | + ngi = factory.make_NodeGroupInterface( |
573 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
574 | + subnet=subnet) |
575 | + static_ip = factory.make_StaticIPAddress( |
576 | + alloc_type=IPADDRESS_TYPE.STICKY, |
577 | + ip=factory.pick_ip_in_static_range(ngi), |
578 | + subnet=subnet, interface=interface) |
579 | + static_id = static_ip.id |
580 | + static_ip_address = static_ip.ip |
581 | + new_ip_address = factory.pick_ip_in_static_range( |
582 | + ngi, but_not=[static_ip_address]) |
583 | + mock_remove_host_maps = self.patch_autospec( |
584 | + interface, "_remove_host_maps") |
585 | + mock_update_host_maps = self.patch_autospec( |
586 | + interface, "_update_host_maps") |
587 | + mock_update_dns_zones = self.patch_autospec( |
588 | + interface, "_update_dns_zones") |
589 | + new_static_ip = interface.update_ip_address( |
590 | + static_ip, INTERFACE_LINK_TYPE.STATIC, subnet, |
591 | + ip_address=new_ip_address) |
592 | + self.assertEquals(static_id, new_static_ip.id) |
593 | + self.assertEquals(IPADDRESS_TYPE.STICKY, new_static_ip.alloc_type) |
594 | + self.assertEquals(subnet, new_static_ip.subnet) |
595 | + self.assertEquals(new_ip_address, new_static_ip.ip) |
596 | + # The remove actual loads the IP address from the database so it |
597 | + # is not the same object. We just need to check that the IP's match. |
598 | + self.assertEquals(nodegroup, mock_remove_host_maps.call_args[0][0]) |
599 | + self.assertEquals( |
600 | + static_ip_address, mock_remove_host_maps.call_args[0][1].ip) |
601 | + self.assertThat( |
602 | + mock_update_host_maps, |
603 | + MockCalledOnceWith(nodegroup, new_static_ip)) |
604 | + self.assertThat( |
605 | + mock_update_dns_zones, MockCalledOnceWith([nodegroup])) |
606 | + |
607 | + def test__switch_static_to_another_subnet(self): |
608 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
609 | + nodegroup_v4 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
610 | + network_v4 = factory.make_ipv4_network(slash=24) |
611 | + subnet = factory.make_Subnet( |
612 | + vlan=interface.vlan, cidr=unicode(network_v4.cidr)) |
613 | + ngi = factory.make_NodeGroupInterface( |
614 | + nodegroup_v4, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
615 | + subnet=subnet) |
616 | + static_ip = factory.make_StaticIPAddress( |
617 | + alloc_type=IPADDRESS_TYPE.STICKY, |
618 | + ip=factory.pick_ip_in_static_range(ngi), |
619 | + subnet=subnet, interface=interface) |
620 | + other_static_ip = factory.make_StaticIPAddress( |
621 | + alloc_type=IPADDRESS_TYPE.STICKY, |
622 | + ip=factory.pick_ip_in_static_range(ngi, but_not=[static_ip.id]), |
623 | + subnet=subnet, interface=interface) |
624 | + static_id = static_ip.id |
625 | + nodegroup_v6 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
626 | + network_v6 = factory.make_ipv6_network(slash=24) |
627 | + new_subnet = factory.make_Subnet( |
628 | + vlan=interface.vlan, cidr=unicode(network_v6.cidr)) |
629 | + new_ngi = factory.make_NodeGroupInterface( |
630 | + nodegroup_v6, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
631 | + subnet=new_subnet) |
632 | + new_subnet_static_ip = factory.make_StaticIPAddress( |
633 | + alloc_type=IPADDRESS_TYPE.STICKY, |
634 | + ip=factory.pick_ip_in_static_range(new_ngi), |
635 | + subnet=new_subnet, interface=interface) |
636 | + mock_remove_host_maps = self.patch_autospec( |
637 | + interface, "_remove_host_maps") |
638 | + mock_update_host_maps = self.patch_autospec( |
639 | + interface, "_update_host_maps") |
640 | + mock_update_dns_zones = self.patch_autospec( |
641 | + interface, "_update_dns_zones") |
642 | + new_static_ip = interface.update_ip_address( |
643 | + static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet) |
644 | + self.assertEquals(static_id, new_static_ip.id) |
645 | + self.assertEquals(IPADDRESS_TYPE.STICKY, new_static_ip.alloc_type) |
646 | + self.assertEquals(new_subnet, new_static_ip.subnet) |
647 | + self.assertIsNotNone(new_static_ip.ip) |
648 | + self.assertThat( |
649 | + mock_remove_host_maps, |
650 | + MockCallsMatch( |
651 | + call(nodegroup_v6, new_subnet_static_ip), |
652 | + call(nodegroup_v4, static_ip), |
653 | + )) |
654 | + self.assertThat( |
655 | + mock_update_host_maps, |
656 | + MockCallsMatch( |
657 | + call(nodegroup_v4, other_static_ip), |
658 | + call(nodegroup_v6, new_static_ip), |
659 | + )) |
660 | + self.assertThat( |
661 | + mock_update_dns_zones, |
662 | + MockCallsMatch( |
663 | + call([nodegroup_v4]), |
664 | + call([nodegroup_v6]), |
665 | + )) |
666 | + |
667 | + def test__switch_static_to_another_subnet_with_ip_address(self): |
668 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
669 | + nodegroup_v4 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
670 | + network_v4 = factory.make_ipv4_network(slash=24) |
671 | + subnet = factory.make_Subnet( |
672 | + vlan=interface.vlan, cidr=unicode(network_v4.cidr)) |
673 | + ngi = factory.make_NodeGroupInterface( |
674 | + nodegroup_v4, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
675 | + subnet=subnet) |
676 | + static_ip = factory.make_StaticIPAddress( |
677 | + alloc_type=IPADDRESS_TYPE.STICKY, |
678 | + ip=factory.pick_ip_in_static_range(ngi), |
679 | + subnet=subnet, interface=interface) |
680 | + static_id = static_ip.id |
681 | + nodegroup_v6 = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
682 | + network_v6 = factory.make_ipv6_network(slash=24) |
683 | + new_subnet = factory.make_Subnet( |
684 | + vlan=interface.vlan, cidr=unicode(network_v6.cidr)) |
685 | + new_ngi = factory.make_NodeGroupInterface( |
686 | + nodegroup_v6, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
687 | + subnet=new_subnet) |
688 | + new_ip_address = factory.pick_ip_in_static_range(new_ngi) |
689 | + mock_remove_host_maps = self.patch_autospec( |
690 | + interface, "_remove_host_maps") |
691 | + mock_update_host_maps = self.patch_autospec( |
692 | + interface, "_update_host_maps") |
693 | + mock_update_dns_zones = self.patch_autospec( |
694 | + interface, "_update_dns_zones") |
695 | + new_static_ip = interface.update_ip_address( |
696 | + static_ip, INTERFACE_LINK_TYPE.STATIC, new_subnet, |
697 | + ip_address=new_ip_address) |
698 | + self.assertEquals(static_id, new_static_ip.id) |
699 | + self.assertEquals(IPADDRESS_TYPE.STICKY, new_static_ip.alloc_type) |
700 | + self.assertEquals(new_subnet, new_static_ip.subnet) |
701 | + self.assertEquals(new_ip_address, new_static_ip.ip) |
702 | + self.assertThat( |
703 | + mock_remove_host_maps, |
704 | + MockCalledOnceWith(nodegroup_v4, static_ip)) |
705 | + self.assertThat( |
706 | + mock_update_host_maps, |
707 | + MockCalledOnceWith(nodegroup_v6, new_static_ip)) |
708 | + self.assertThat( |
709 | + mock_update_dns_zones, |
710 | + MockCallsMatch( |
711 | + call([nodegroup_v4]), |
712 | + call([nodegroup_v6]), |
713 | + )) |
714 | + |
715 | + |
716 | +class TestUpdateLinkById(MAASServerTestCase): |
717 | + """Tests for `Interface.update_link_by_id`.""" |
718 | + |
719 | + def test__calls_update_ip_address_with_ip_address(self): |
720 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
721 | + subnet = factory.make_Subnet(vlan=interface.vlan) |
722 | + static_ip = factory.make_StaticIPAddress( |
723 | + alloc_type=IPADDRESS_TYPE.DHCP, ip="", |
724 | + subnet=subnet, interface=interface) |
725 | + mock_update_ip_address = self.patch_autospec( |
726 | + interface, "update_ip_address") |
727 | + interface.update_link_by_id( |
728 | + static_ip.id, INTERFACE_LINK_TYPE.AUTO, subnet) |
729 | + self.expectThat(mock_update_ip_address, MockCalledOnceWith( |
730 | + static_ip, INTERFACE_LINK_TYPE.AUTO, subnet, ip_address=None)) |
731 | + |
732 | + |
733 | class TestClaimAutoIPs(MAASServerTestCase): |
734 | """Tests for `Interface.claim_auto_ips`.""" |
735 | |
736 | |
737 | === modified file 'src/maasserver/static/js/angular/controllers/node_details_networking.js' |
738 | --- src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-09-29 15:19:25 +0000 |
739 | +++ src/maasserver/static/js/angular/controllers/node_details_networking.js 2015-10-01 14:33:47 +0000 |
740 | @@ -6,10 +6,10 @@ |
741 | |
742 | angular.module('MAAS').controller('NodeNetworkingController', [ |
743 | '$scope', 'FabricsManager', 'VLANsManager', 'SubnetsManager', |
744 | - 'NodesManager', 'ManagerHelperService', |
745 | + 'NodesManager', 'ManagerHelperService', 'ValidationService', |
746 | function( |
747 | $scope, FabricsManager, VLANsManager, SubnetsManager, NodesManager, |
748 | - ManagerHelperService) { |
749 | + ManagerHelperService, ValidationService) { |
750 | |
751 | // Different interface types. |
752 | var INTERFACE_TYPE = { |
753 | @@ -36,7 +36,7 @@ |
754 | "auto": "Auto assign", |
755 | "static": "Static assign", |
756 | "dhcp": "DHCP", |
757 | - "link_up": "Unconfigured" |
758 | + "link_up": "No IP" |
759 | }; |
760 | |
761 | // Set the initial values for this scope. |
762 | @@ -46,6 +46,7 @@ |
763 | $scope.column = 'name'; |
764 | $scope.fabrics = FabricsManager.getItems(); |
765 | $scope.vlans = VLANsManager.getItems(); |
766 | + $scope.subnets = SubnetsManager.getItems(); |
767 | $scope.interfaces = []; |
768 | $scope.interfaceLinksMap = {}; |
769 | $scope.originalInterfaces = {}; |
770 | @@ -119,7 +120,7 @@ |
771 | // disabled or has no links (which means the interface |
772 | // is in LINK_UP mode). |
773 | nic.link_id = -1; |
774 | - nic.subnet_id = null; |
775 | + nic.subnet = null; |
776 | nic.mode = LINK_MODE.LINK_UP; |
777 | nic.ip_address = ""; |
778 | interfaces.push(nic); |
779 | @@ -128,9 +129,13 @@ |
780 | angular.forEach(nic.links, function(link) { |
781 | var nic_copy = angular.copy(nic); |
782 | nic_copy.link_id = link.id; |
783 | - nic_copy.subnet_id = link.subnet_id; |
784 | + nic_copy.subnet = SubnetsManager.getItemFromList( |
785 | + link.subnet_id); |
786 | nic_copy.mode = link.mode; |
787 | nic_copy.ip_address = link.ip_address; |
788 | + if(angular.isUndefined(nic_copy.ip_address)) { |
789 | + nic_copy.ip_address = ""; |
790 | + } |
791 | // We don't want to deep copy the VLAN and fabric |
792 | // object so we set those back to the original. |
793 | nic_copy.vlan = nic.vlan; |
794 | @@ -176,6 +181,23 @@ |
795 | } |
796 | } |
797 | |
798 | + // Return the original link object for the given interface. |
799 | + function mapNICToOriginalLink(nic) { |
800 | + var originalInteface = $scope.originalInterfaces[nic.id]; |
801 | + if(angular.isObject(originalInteface)) { |
802 | + var i, link = null; |
803 | + for(i = 0; i < originalInteface.links.length; i++) { |
804 | + link = originalInteface.links[i]; |
805 | + if(link.id === nic.link_id) { |
806 | + break; |
807 | + } |
808 | + } |
809 | + return link; |
810 | + } else { |
811 | + return null; |
812 | + } |
813 | + } |
814 | + |
815 | // Called by $parent when the node has been loaded. |
816 | $scope.nodeLoaded = function() { |
817 | $scope.$watch("node.interfaces", updateInterfaces); |
818 | @@ -203,22 +225,25 @@ |
819 | } |
820 | }; |
821 | |
822 | - // Get the subnet for the interface. |
823 | - $scope.getSubnet = function(nic) { |
824 | - return SubnetsManager.getItemFromList(nic.subnet_id); |
825 | - }; |
826 | - |
827 | - // Get the name of the subnet for this interface. |
828 | - $scope.getSubnetName = function(nic) { |
829 | - if(angular.isNumber(nic.subnet_id)) { |
830 | - var subnet = $scope.getSubnet(nic); |
831 | - if(angular.isObject(subnet)) { |
832 | - return subnet.name; |
833 | - } else { |
834 | - return "Unknown"; |
835 | - } |
836 | + // Get the text to display in the VLAN dropdown. |
837 | + $scope.getVLANText = function(vlan) { |
838 | + if(angular.isString(vlan.name) && vlan.name.length > 0) { |
839 | + return vlan.vid + " (" + vlan.name + ")"; |
840 | } else { |
841 | + return vlan.vid; |
842 | + } |
843 | + }; |
844 | + |
845 | + // Get the text to display in the subnet dropdown. |
846 | + $scope.getSubnetText = function(subnet) { |
847 | + if(!angular.isObject(subnet)) { |
848 | return "Unconfigured"; |
849 | + } else if(angular.isString(subnet.name) && |
850 | + subnet.name.length > 0 && |
851 | + subnet.cidr !== subnet.name) { |
852 | + return subnet.cidr + " (" + subnet.name + ")"; |
853 | + } else { |
854 | + return subnet.cidr; |
855 | } |
856 | }; |
857 | |
858 | @@ -240,10 +265,10 @@ |
859 | // Save the following interface on the node. This will only save if |
860 | // the interface has changed. |
861 | $scope.saveInterface = function(nic) { |
862 | - // If the name or vlan has changed then we need to update |
863 | - // the interface. |
864 | var originalInteface = $scope.originalInterfaces[nic.id]; |
865 | - if(originalInteface.name !== nic.name || |
866 | + if($scope.isInterfaceNameInvalid(nic)) { |
867 | + nic.name = originalInteface.name; |
868 | + } else if(originalInteface.name !== nic.name || |
869 | originalInteface.vlan_id !== nic.vlan.id) { |
870 | var params = { |
871 | "name": nic.name, |
872 | @@ -255,6 +280,10 @@ |
873 | // we need to expose this as a better message to the |
874 | // user. |
875 | console.log(error); |
876 | + |
877 | + // Update the interfaces so it is back to the way it |
878 | + // was before the user changed it. |
879 | + updateInterfaces(); |
880 | }); |
881 | } |
882 | }; |
883 | @@ -268,10 +297,16 @@ |
884 | // if it has changed. |
885 | $scope.clearFocusInterface = function(nic) { |
886 | if(angular.isUndefined(nic)) { |
887 | - $scope.saveInterface($scope.focusInterface); |
888 | + if($scope.focusInterface.type !== INTERFACE_TYPE.ALIAS) { |
889 | + $scope.saveInterface($scope.focusInterface); |
890 | + } |
891 | + $scope.saveInterfaceIPAddress($scope.focusInterface); |
892 | $scope.focusInterface = null; |
893 | } else if($scope.focusInterface === nic) { |
894 | - $scope.saveInterface($scope.focusInterface); |
895 | + if($scope.focusInterface.type !== INTERFACE_TYPE.ALIAS) { |
896 | + $scope.saveInterface($scope.focusInterface); |
897 | + } |
898 | + $scope.saveInterfaceIPAddress($scope.focusInterface); |
899 | $scope.focusInterface = null; |
900 | } |
901 | }; |
902 | @@ -300,6 +335,122 @@ |
903 | $scope.saveInterface(nic); |
904 | }; |
905 | |
906 | + // Return True if the link mode select should be disabled. |
907 | + $scope.isLinkModeDisabled = function(nic) { |
908 | + // This is only disabled when a subnet has not been selected. |
909 | + return !angular.isObject(nic.subnet); |
910 | + }; |
911 | + |
912 | + // Get the available link modes for an interface. |
913 | + $scope.getLinkModes = function(nic) { |
914 | + modes = []; |
915 | + if(!angular.isObject(nic.subnet)) { |
916 | + // No subnet is configure so the only allowed mode |
917 | + // is 'link_up'. |
918 | + modes.push({ |
919 | + "mode": LINK_MODE.LINK_UP, |
920 | + "text": LINK_MODE_TEXTS[LINK_MODE.LINK_UP] |
921 | + }); |
922 | + } else { |
923 | + angular.forEach(LINK_MODE_TEXTS, function(text, mode) { |
924 | + // Don't add LINK_UP or DHCP if more than one link exists. |
925 | + if(nic.links.length > 1 && ( |
926 | + mode === LINK_MODE.LINK_UP || |
927 | + mode === LINK_MODE.DHCP)) { |
928 | + return; |
929 | + } |
930 | + modes.push({ |
931 | + "mode": mode, |
932 | + "text": text |
933 | + }); |
934 | + }); |
935 | + } |
936 | + return modes; |
937 | + }; |
938 | + |
939 | + // Called when the link mode for this interface and link has been |
940 | + // changed. |
941 | + $scope.saveInterfaceLink = function(nic) { |
942 | + var params = { |
943 | + "mode": nic.mode |
944 | + }; |
945 | + if(angular.isObject(nic.subnet)) { |
946 | + params.subnet = nic.subnet.id; |
947 | + } |
948 | + if(nic.link_id >= 0) { |
949 | + params.link_id = nic.link_id; |
950 | + } |
951 | + if(nic.mode === LINK_MODE.STATIC && nic.ip_address.length > 0) { |
952 | + params.ip_address = nic.ip_address; |
953 | + } |
954 | + NodesManager.linkSubnet($scope.node, nic.id, params).then( |
955 | + null, function(error) { |
956 | + // XXX blake_r: Just log the error in the console, but |
957 | + // we need to expose this as a better message to the |
958 | + // user. |
959 | + console.log(error); |
960 | + |
961 | + // Update the interfaces so it is back to the way it |
962 | + // was before the user changed it. |
963 | + updateInterfaces(); |
964 | + }); |
965 | + }; |
966 | + |
967 | + // Called when the user changes the subnet. |
968 | + $scope.subnetChanged = function(nic) { |
969 | + if(!angular.isObject(nic.subnet)) { |
970 | + // Set to 'Unconfigured' so the link mode should be set to |
971 | + // 'link_up'. |
972 | + nic.mode = LINK_MODE.LINK_UP; |
973 | + } |
974 | + // Clear the IP address so a new one on the subnet is assigned. |
975 | + nic.ip_address = ""; |
976 | + $scope.saveInterfaceLink(nic); |
977 | + }; |
978 | + |
979 | + // Return True when the IP address input field should be shown. |
980 | + $scope.shouldShowIPAddress = function(nic) { |
981 | + if(nic.mode === LINK_MODE.STATIC) { |
982 | + // Check that the original has an IP address if it doesn't then |
983 | + // it should not be shown as the IP address still has not been |
984 | + // loaded over the websocket. If the subnets have been switched |
985 | + // then the IP address has been clear, don't show the IP |
986 | + // address until the original subnet and nic subnet match. |
987 | + var originalLink = mapNICToOriginalLink(nic); |
988 | + return ( |
989 | + angular.isObject(originalLink) && |
990 | + angular.isString(originalLink.ip_address) && |
991 | + originalLink.ip_address.length > 0 && |
992 | + angular.isObject(nic.subnet) && |
993 | + nic.subnet.id === originalLink.subnet_id); |
994 | + } else if(angular.isString(nic.ip_address) && |
995 | + nic.ip_address.length > 0) { |
996 | + return true; |
997 | + } else { |
998 | + return false; |
999 | + } |
1000 | + }; |
1001 | + |
1002 | + // Return True if the interface IP address that the user typed is |
1003 | + // invalid. |
1004 | + $scope.isIPAddressInvalid = function(nic) { |
1005 | + return (nic.ip_address.length === 0 || |
1006 | + !ValidationService.validateIP(nic.ip_address) || |
1007 | + !ValidationService.validateIPInNetwork( |
1008 | + nic.ip_address, nic.subnet.cidr)); |
1009 | + }; |
1010 | + |
1011 | + // Save the interface IP address. |
1012 | + $scope.saveInterfaceIPAddress = function(nic) { |
1013 | + var originalLink = mapNICToOriginalLink(nic); |
1014 | + var prevIPAddress = originalLink.ip_address; |
1015 | + if($scope.isIPAddressInvalid(nic)) { |
1016 | + nic.ip_address = prevIPAddress; |
1017 | + } else if(nic.ip_address !== prevIPAddress) { |
1018 | + $scope.saveInterfaceLink(nic); |
1019 | + } |
1020 | + }; |
1021 | + |
1022 | // Load all the required managers. |
1023 | ManagerHelperService.loadManagers([ |
1024 | FabricsManager, |
1025 | |
1026 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js' |
1027 | --- src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-09-29 15:19:25 +0000 |
1028 | +++ src/maasserver/static/js/angular/controllers/tests/test_node_details_networking.js 2015-10-01 14:33:47 +0000 |
1029 | @@ -65,6 +65,9 @@ |
1030 | expect($scope.nodeHasLoaded).toBe(false); |
1031 | expect($scope.managersHaveLoaded).toBe(false); |
1032 | expect($scope.column).toBe('name'); |
1033 | + expect($scope.fabrics).toBe(FabricsManager.getItems()); |
1034 | + expect($scope.vlans).toBe(VLANsManager.getItems()); |
1035 | + expect($scope.subnets).toBe(SubnetsManager.getItems()); |
1036 | expect($scope.interfaces).toEqual([]); |
1037 | expect($scope.interfaceLinksMap).toEqual({}); |
1038 | expect($scope.originalInterfaces).toEqual({}); |
1039 | @@ -212,7 +215,7 @@ |
1040 | members: [parent1, parent2], |
1041 | vlan: null, |
1042 | link_id: -1, |
1043 | - subnet_id: null, |
1044 | + subnet: null, |
1045 | mode: "link_up", |
1046 | ip_address: "" |
1047 | }]); |
1048 | @@ -297,13 +300,15 @@ |
1049 | links: [], |
1050 | vlan: null, |
1051 | link_id: -1, |
1052 | - subnet_id: null, |
1053 | + subnet: null, |
1054 | mode: "link_up", |
1055 | ip_address: "" |
1056 | }]); |
1057 | }); |
1058 | |
1059 | it("duplicates links as alias interfaces", function() { |
1060 | + var subnet0 = { id: 0 }, subnet1 = { id: 1 }, subnet2 = { id: 2 }; |
1061 | + SubnetsManager._items = [subnet0, subnet1, subnet2]; |
1062 | var links = [ |
1063 | { |
1064 | id: 0, |
1065 | @@ -345,7 +350,7 @@ |
1066 | vlan: null, |
1067 | fabric: undefined, |
1068 | link_id: 0, |
1069 | - subnet_id: 0, |
1070 | + subnet: subnet0, |
1071 | mode: "dhcp", |
1072 | ip_address: "" |
1073 | }, |
1074 | @@ -359,7 +364,7 @@ |
1075 | vlan: null, |
1076 | fabric: undefined, |
1077 | link_id: 1, |
1078 | - subnet_id: 1, |
1079 | + subnet: subnet1, |
1080 | mode: "auto", |
1081 | ip_address: "" |
1082 | }, |
1083 | @@ -373,7 +378,7 @@ |
1084 | vlan: null, |
1085 | fabric: undefined, |
1086 | link_id: 2, |
1087 | - subnet_id: 2, |
1088 | + subnet: subnet2, |
1089 | mode: "static", |
1090 | ip_address: "192.168.122.10" |
1091 | } |
1092 | @@ -470,7 +475,7 @@ |
1093 | "auto": "Auto assign", |
1094 | "static": "Static assign", |
1095 | "dhcp": "DHCP", |
1096 | - "link_up": "Unconfigured", |
1097 | + "link_up": "No IP", |
1098 | "missing_type": "missing_type" |
1099 | }; |
1100 | |
1101 | @@ -485,64 +490,63 @@ |
1102 | }); |
1103 | }); |
1104 | |
1105 | - describe("getSubnet", function() { |
1106 | + describe("getVLANText", function() { |
1107 | |
1108 | - it("returns item from SubnetsManager", function() { |
1109 | + it("returns just vid", function() { |
1110 | var controller = makeController(); |
1111 | - var subnet_id = makeInteger(0, 100); |
1112 | - var subnet = { |
1113 | - id: subnet_id |
1114 | - }; |
1115 | - SubnetsManager._items = [subnet]; |
1116 | - |
1117 | - var nic = { |
1118 | - subnet_id: subnet_id |
1119 | - }; |
1120 | - expect($scope.getSubnet(nic)).toBe(subnet); |
1121 | + var vlan = { |
1122 | + vid: 5 |
1123 | + }; |
1124 | + expect($scope.getVLANText(vlan)).toBe(5); |
1125 | }); |
1126 | |
1127 | - it("returns null for missing subnet", function() { |
1128 | + it("returns vid + name", function() { |
1129 | var controller = makeController(); |
1130 | - var subnet_id = makeInteger(0, 100); |
1131 | - var nic = { |
1132 | - subnet_id: subnet_id |
1133 | + var name = makeName("vlan"); |
1134 | + var vlan = { |
1135 | + vid: 5, |
1136 | + name: name |
1137 | }; |
1138 | - expect($scope.getSubnet(nic)).toBeNull(); |
1139 | + expect($scope.getVLANText(vlan)).toBe("5 (" + name + ")"); |
1140 | }); |
1141 | }); |
1142 | |
1143 | - describe("getSubnetName", function() { |
1144 | - |
1145 | - it("returns name from item in SubnetsManager", function() { |
1146 | - var controller = makeController(); |
1147 | - var subnet_id = makeInteger(0, 100); |
1148 | - var subnet_name = makeName("subnet"); |
1149 | - var subnet = { |
1150 | - id: subnet_id, |
1151 | - name: subnet_name |
1152 | - }; |
1153 | - SubnetsManager._items = [subnet]; |
1154 | - |
1155 | - var nic = { |
1156 | - subnet_id: subnet_id |
1157 | - }; |
1158 | - expect($scope.getSubnetName(nic)).toBe(subnet_name); |
1159 | - }); |
1160 | - |
1161 | - it("returns 'Unknown' if item not in SubnetsManager", function() { |
1162 | - var controller = makeController(); |
1163 | - var nic = { |
1164 | - subnet_id: makeInteger(0, 100) |
1165 | - }; |
1166 | - expect($scope.getSubnetName(nic)).toBe("Unknown"); |
1167 | - }); |
1168 | - |
1169 | - it("returns 'Unconfigured' if no subnet_id", function() { |
1170 | - var controller = makeController(); |
1171 | - var nic = { |
1172 | - subnet_id: null |
1173 | - }; |
1174 | - expect($scope.getSubnetName(nic)).toBe("Unconfigured"); |
1175 | + describe("getSubnetText", function() { |
1176 | + |
1177 | + it("returns 'Unconfigured' for null", function() { |
1178 | + var controller = makeController(); |
1179 | + expect($scope.getSubnetText(null)).toBe("Unconfigured"); |
1180 | + }); |
1181 | + |
1182 | + it("returns just cidr if no name", function() { |
1183 | + var controller = makeController(); |
1184 | + var cidr = makeName("cidr"); |
1185 | + var subnet = { |
1186 | + cidr: cidr |
1187 | + }; |
1188 | + expect($scope.getSubnetText(subnet)).toBe(cidr); |
1189 | + }); |
1190 | + |
1191 | + it("returns just cidr if name same as cidr", function() { |
1192 | + var controller = makeController(); |
1193 | + var cidr = makeName("cidr"); |
1194 | + var subnet = { |
1195 | + cidr: cidr, |
1196 | + name: cidr |
1197 | + }; |
1198 | + expect($scope.getSubnetText(subnet)).toBe(cidr); |
1199 | + }); |
1200 | + |
1201 | + it("returns cidr + name", function() { |
1202 | + var controller = makeController(); |
1203 | + var cidr = makeName("cidr"); |
1204 | + var name = makeName("name"); |
1205 | + var subnet = { |
1206 | + cidr: cidr, |
1207 | + name: name |
1208 | + }; |
1209 | + expect($scope.getSubnetText(subnet)).toBe( |
1210 | + cidr + " (" + name + ")"); |
1211 | }); |
1212 | }); |
1213 | |
1214 | @@ -615,6 +619,31 @@ |
1215 | expect(NodesManager.updateInterface).not.toHaveBeenCalled(); |
1216 | }); |
1217 | |
1218 | + it("resets name if its invalid and doesn't call update", function() { |
1219 | + var controller = makeController(); |
1220 | + var id = makeInteger(0, 100); |
1221 | + var name = makeName("nic"); |
1222 | + var vlan = { id: makeInteger(0, 100) }; |
1223 | + var original_nic = { |
1224 | + id: id, |
1225 | + name: name, |
1226 | + vlan_id: vlan.id |
1227 | + }; |
1228 | + var nic = { |
1229 | + id: id, |
1230 | + name: "", |
1231 | + vlan: vlan |
1232 | + }; |
1233 | + $scope.originalInterfaces[id] = original_nic; |
1234 | + $scope.interfaces = [nic]; |
1235 | + |
1236 | + spyOn(NodesManager, "updateInterface").and.returnValue( |
1237 | + $q.defer().promise); |
1238 | + $scope.saveInterface(nic); |
1239 | + expect(nic.name).toBe(name); |
1240 | + expect(NodesManager.updateInterface).not.toHaveBeenCalled(); |
1241 | + }); |
1242 | + |
1243 | it("calls NodesManager.updateInterface if name changed", function() { |
1244 | var controller = makeController(); |
1245 | var id = makeInteger(0, 100); |
1246 | @@ -686,32 +715,72 @@ |
1247 | |
1248 | it("clears focusInterface no arguments", function() { |
1249 | var controller = makeController(); |
1250 | - var nic = {}; |
1251 | + var nic = { |
1252 | + type: "physical" |
1253 | + }; |
1254 | $scope.focusInterface = nic; |
1255 | spyOn($scope, "saveInterface"); |
1256 | + spyOn($scope, "saveInterfaceIPAddress"); |
1257 | $scope.clearFocusInterface(); |
1258 | expect($scope.focusInterface).toBeNull(); |
1259 | expect($scope.saveInterface).toHaveBeenCalledWith(nic); |
1260 | + expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic); |
1261 | }); |
1262 | |
1263 | it("clears focusInterface if same interface", function() { |
1264 | var controller = makeController(); |
1265 | - var nic = {}; |
1266 | + var nic = { |
1267 | + type: "physical" |
1268 | + }; |
1269 | $scope.focusInterface = nic; |
1270 | spyOn($scope, "saveInterface"); |
1271 | + spyOn($scope, "saveInterfaceIPAddress"); |
1272 | $scope.clearFocusInterface(nic); |
1273 | expect($scope.focusInterface).toBeNull(); |
1274 | expect($scope.saveInterface).toHaveBeenCalledWith(nic); |
1275 | + expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic); |
1276 | }); |
1277 | |
1278 | it("doesnt clear focusInterface if different interface", function() { |
1279 | var controller = makeController(); |
1280 | - var nic = {}; |
1281 | + var nic = { |
1282 | + type: "physical" |
1283 | + }; |
1284 | $scope.focusInterface = nic; |
1285 | spyOn($scope, "saveInterface"); |
1286 | + spyOn($scope, "saveInterfaceIPAddress"); |
1287 | $scope.clearFocusInterface({}); |
1288 | expect($scope.focusInterface).toBe(nic); |
1289 | expect($scope.saveInterface).not.toHaveBeenCalled(); |
1290 | + expect($scope.saveInterfaceIPAddress).not.toHaveBeenCalled(); |
1291 | + }); |
1292 | + |
1293 | + it("doesnt call save with focusInterface no arguments", function() { |
1294 | + var controller = makeController(); |
1295 | + var nic = { |
1296 | + type: "alias" |
1297 | + }; |
1298 | + $scope.focusInterface = nic; |
1299 | + spyOn($scope, "saveInterface"); |
1300 | + spyOn($scope, "saveInterfaceIPAddress"); |
1301 | + $scope.clearFocusInterface(); |
1302 | + expect($scope.focusInterface).toBeNull(); |
1303 | + expect($scope.saveInterface).not.toHaveBeenCalled(); |
1304 | + expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic); |
1305 | + }); |
1306 | + |
1307 | + it("doesnt call save with focusInterface if same nic", function() { |
1308 | + var controller = makeController(); |
1309 | + var nic = { |
1310 | + type: "alias" |
1311 | + }; |
1312 | + $scope.focusInterface = nic; |
1313 | + spyOn($scope, "saveInterface"); |
1314 | + spyOn($scope, "saveInterfaceIPAddress"); |
1315 | + $scope.clearFocusInterface(nic); |
1316 | + expect($scope.focusInterface).toBeNull(); |
1317 | + expect($scope.saveInterface).not.toHaveBeenCalled(); |
1318 | + expect($scope.saveInterfaceIPAddress).toHaveBeenCalledWith(nic); |
1319 | }); |
1320 | }); |
1321 | |
1322 | @@ -812,4 +881,407 @@ |
1323 | expect($scope.saveInterface).toHaveBeenCalledWith(nic); |
1324 | }); |
1325 | }); |
1326 | + |
1327 | + describe("isLinkModeDisabled", function() { |
1328 | + |
1329 | + it("enabled when subnet", function() { |
1330 | + var controller = makeController(); |
1331 | + var nic = { |
1332 | + subnet : {} |
1333 | + }; |
1334 | + expect($scope.isLinkModeDisabled(nic)).toBe(false); |
1335 | + }); |
1336 | + |
1337 | + it("disabled when not subnet", function() { |
1338 | + var controller = makeController(); |
1339 | + var nic = { |
1340 | + subnet : null |
1341 | + }; |
1342 | + expect($scope.isLinkModeDisabled(nic)).toBe(true); |
1343 | + }); |
1344 | + }); |
1345 | + |
1346 | + describe("getLinkModes", function() { |
1347 | + |
1348 | + it("only link_up when no subnet", function() { |
1349 | + var controller = makeController(); |
1350 | + var nic = { |
1351 | + subnet : null |
1352 | + }; |
1353 | + expect($scope.getLinkModes(nic)).toEqual([ |
1354 | + { |
1355 | + "mode": "link_up", |
1356 | + "text": "No IP" |
1357 | + } |
1358 | + ]); |
1359 | + }); |
1360 | + |
1361 | + it("all modes if only one link", function() { |
1362 | + var controller = makeController(); |
1363 | + var nic = { |
1364 | + subnet : {}, |
1365 | + links: [{}] |
1366 | + }; |
1367 | + expect($scope.getLinkModes(nic)).toEqual([ |
1368 | + { |
1369 | + "mode": "auto", |
1370 | + "text": "Auto assign" |
1371 | + }, |
1372 | + { |
1373 | + "mode": "static", |
1374 | + "text": "Static assign" |
1375 | + }, |
1376 | + { |
1377 | + "mode": "dhcp", |
1378 | + "text": "DHCP" |
1379 | + }, |
1380 | + { |
1381 | + "mode": "link_up", |
1382 | + "text": "No IP" |
1383 | + } |
1384 | + ]); |
1385 | + }); |
1386 | + |
1387 | + it("auto and static modes if more than one link", function() { |
1388 | + var controller = makeController(); |
1389 | + var nic = { |
1390 | + subnet : {}, |
1391 | + links: [{}, {}] |
1392 | + }; |
1393 | + expect($scope.getLinkModes(nic)).toEqual([ |
1394 | + { |
1395 | + "mode": "auto", |
1396 | + "text": "Auto assign" |
1397 | + }, |
1398 | + { |
1399 | + "mode": "static", |
1400 | + "text": "Static assign" |
1401 | + } |
1402 | + ]); |
1403 | + }); |
1404 | + }); |
1405 | + |
1406 | + describe("saveInterfaceLink", function() { |
1407 | + |
1408 | + it("calls NodesManager.linkSubnet with params", function() { |
1409 | + var controller = makeController(); |
1410 | + var nic = { |
1411 | + id: makeInteger(0, 100), |
1412 | + mode: "static", |
1413 | + subnet: { id: makeInteger(0, 100) }, |
1414 | + link_id: makeInteger(0, 100), |
1415 | + ip_address: "192.168.122.1" |
1416 | + }; |
1417 | + spyOn(NodesManager, "linkSubnet").and.returnValue( |
1418 | + $q.defer().promise); |
1419 | + $scope.saveInterfaceLink(nic); |
1420 | + expect(NodesManager.linkSubnet).toHaveBeenCalledWith( |
1421 | + node, nic.id, { |
1422 | + "mode": "static", |
1423 | + "subnet": nic.subnet.id, |
1424 | + "link_id": nic.link_id, |
1425 | + "ip_address": nic.ip_address |
1426 | + }); |
1427 | + }); |
1428 | + }); |
1429 | + |
1430 | + describe("subnetChanged", function() { |
1431 | + |
1432 | + it("sets mode to link_up if set to no subnet", function() { |
1433 | + var controller = makeController(); |
1434 | + var nic = { |
1435 | + subnet: null |
1436 | + }; |
1437 | + spyOn($scope, "saveInterfaceLink"); |
1438 | + $scope.subnetChanged(nic); |
1439 | + expect(nic.mode).toBe("link_up"); |
1440 | + expect($scope.saveInterfaceLink).toHaveBeenCalledWith(nic); |
1441 | + }); |
1442 | + |
1443 | + it("doesnt set mode to link_up if set if subnet", function() { |
1444 | + var controller = makeController(); |
1445 | + var nic = { |
1446 | + mode: "static", |
1447 | + subnet: {} |
1448 | + }; |
1449 | + spyOn($scope, "saveInterfaceLink"); |
1450 | + $scope.subnetChanged(nic); |
1451 | + expect(nic.mode).toBe("static"); |
1452 | + expect($scope.saveInterfaceLink).toHaveBeenCalledWith(nic); |
1453 | + }); |
1454 | + |
1455 | + it("clears ip_address", function() { |
1456 | + var controller = makeController(); |
1457 | + var nic = { |
1458 | + subnet: null, |
1459 | + ip_address: makeName("ip") |
1460 | + }; |
1461 | + spyOn($scope, "saveInterfaceLink"); |
1462 | + $scope.subnetChanged(nic); |
1463 | + expect(nic.ip_address).toBe(""); |
1464 | + }); |
1465 | + }); |
1466 | + |
1467 | + describe("shouldShowIPAddress", function() { |
1468 | + |
1469 | + it("true if not static and has ip address", function() { |
1470 | + var controller = makeController(); |
1471 | + var nic = { |
1472 | + mode: "auto", |
1473 | + ip_address: "192.168.122.1" |
1474 | + }; |
1475 | + expect($scope.shouldShowIPAddress(nic)).toBe(true); |
1476 | + }); |
1477 | + |
1478 | + it("false if not static and doesn't have ip address", function() { |
1479 | + var controller = makeController(); |
1480 | + var nic = { |
1481 | + mode: "dhcp", |
1482 | + ip_address: "" |
1483 | + }; |
1484 | + expect($scope.shouldShowIPAddress(nic)).toBe(false); |
1485 | + }); |
1486 | + |
1487 | + describe("static", function() { |
1488 | + |
1489 | + it("false if no orginial link", function() { |
1490 | + var controller = makeController(); |
1491 | + var nic = { |
1492 | + id: 0, |
1493 | + mode: "static", |
1494 | + link_id: -1, |
1495 | + ip_address: "" |
1496 | + }; |
1497 | + expect($scope.shouldShowIPAddress(nic)).toBe(false); |
1498 | + }); |
1499 | + |
1500 | + it("false if orginial link has no IP address", function() { |
1501 | + var controller = makeController(); |
1502 | + var originalInterface = { |
1503 | + id: 0, |
1504 | + links: [ |
1505 | + { |
1506 | + id: 0, |
1507 | + mode: "static" |
1508 | + } |
1509 | + ] |
1510 | + }; |
1511 | + $scope.originalInterfaces = [originalInterface]; |
1512 | + |
1513 | + var nic = { |
1514 | + id: 0, |
1515 | + mode: "static", |
1516 | + link_id: 0, |
1517 | + ip_address: "" |
1518 | + }; |
1519 | + expect($scope.shouldShowIPAddress(nic)).toBe(false); |
1520 | + }); |
1521 | + |
1522 | + it("false if orginial link has empty IP address", function() { |
1523 | + var controller = makeController(); |
1524 | + var originalInterface = { |
1525 | + id: 0, |
1526 | + links: [ |
1527 | + { |
1528 | + id: 0, |
1529 | + mode: "static", |
1530 | + ip_address: "" |
1531 | + } |
1532 | + ] |
1533 | + }; |
1534 | + $scope.originalInterfaces = [originalInterface]; |
1535 | + |
1536 | + var nic = { |
1537 | + id: 0, |
1538 | + mode: "static", |
1539 | + link_id: 0, |
1540 | + ip_address: "" |
1541 | + }; |
1542 | + expect($scope.shouldShowIPAddress(nic)).toBe(false); |
1543 | + }); |
1544 | + |
1545 | + it("false if no subnet on nic", function() { |
1546 | + var controller = makeController(); |
1547 | + var originalInterface = { |
1548 | + id: 0, |
1549 | + links: [ |
1550 | + { |
1551 | + id: 0, |
1552 | + mode: "static", |
1553 | + ip_address: "192.168.122.2" |
1554 | + } |
1555 | + ] |
1556 | + }; |
1557 | + $scope.originalInterfaces = [originalInterface]; |
1558 | + |
1559 | + var nic = { |
1560 | + id: 0, |
1561 | + mode: "static", |
1562 | + link_id: 0, |
1563 | + ip_address: "" |
1564 | + }; |
1565 | + expect($scope.shouldShowIPAddress(nic)).toBe(false); |
1566 | + }); |
1567 | + |
1568 | + it("false if the subnets don't match", function() { |
1569 | + var controller = makeController(); |
1570 | + var originalInterface = { |
1571 | + id: 0, |
1572 | + links: [ |
1573 | + { |
1574 | + id: 0, |
1575 | + mode: "static", |
1576 | + ip_address: "192.168.122.2", |
1577 | + subnet_id: 0 |
1578 | + } |
1579 | + ] |
1580 | + }; |
1581 | + $scope.originalInterfaces = [originalInterface]; |
1582 | + |
1583 | + var nic = { |
1584 | + id: 0, |
1585 | + mode: "static", |
1586 | + link_id: 0, |
1587 | + ip_address: "", |
1588 | + subnet: { |
1589 | + id: 1 |
1590 | + } |
1591 | + }; |
1592 | + expect($scope.shouldShowIPAddress(nic)).toBe(false); |
1593 | + }); |
1594 | + |
1595 | + it("true if all condititions match", function() { |
1596 | + var controller = makeController(); |
1597 | + var originalInterface = { |
1598 | + id: 0, |
1599 | + links: [ |
1600 | + { |
1601 | + id: 0, |
1602 | + mode: "static", |
1603 | + ip_address: "192.168.122.2", |
1604 | + subnet_id: 0 |
1605 | + } |
1606 | + ] |
1607 | + }; |
1608 | + $scope.originalInterfaces = [originalInterface]; |
1609 | + |
1610 | + var nic = { |
1611 | + id: 0, |
1612 | + mode: "static", |
1613 | + link_id: 0, |
1614 | + ip_address: "", |
1615 | + subnet: { |
1616 | + id: 0 |
1617 | + } |
1618 | + }; |
1619 | + expect($scope.shouldShowIPAddress(nic)).toBe(true); |
1620 | + }); |
1621 | + }); |
1622 | + }); |
1623 | + |
1624 | + describe("isIPAddressInvalid", function() { |
1625 | + |
1626 | + it("true if empty IP address", function() { |
1627 | + var controller = makeController(); |
1628 | + var nic = { |
1629 | + ip_address: "" |
1630 | + }; |
1631 | + expect($scope.isIPAddressInvalid(nic)).toBe(true); |
1632 | + }); |
1633 | + |
1634 | + it("true if not valid IP address", function() { |
1635 | + var controller = makeController(); |
1636 | + var nic = { |
1637 | + ip_address: "192.168.260.5" |
1638 | + }; |
1639 | + expect($scope.isIPAddressInvalid(nic)).toBe(true); |
1640 | + }); |
1641 | + |
1642 | + it("true if IP address not in subnet", function() { |
1643 | + var controller = makeController(); |
1644 | + var nic = { |
1645 | + ip_address: "192.168.123.10", |
1646 | + subnet: { |
1647 | + cidr: "192.168.122.0/24" |
1648 | + } |
1649 | + }; |
1650 | + expect($scope.isIPAddressInvalid(nic)).toBe(true); |
1651 | + }); |
1652 | + |
1653 | + it("false if IP address in subnet", function() { |
1654 | + var controller = makeController(); |
1655 | + var nic = { |
1656 | + ip_address: "192.168.122.10", |
1657 | + subnet: { |
1658 | + cidr: "192.168.122.0/24" |
1659 | + } |
1660 | + }; |
1661 | + expect($scope.isIPAddressInvalid(nic)).toBe(false); |
1662 | + }); |
1663 | + }); |
1664 | + |
1665 | + describe("saveInterfaceIPAddress", function() { |
1666 | + |
1667 | + it("resets IP address if invalid doesn't save", function() { |
1668 | + var controller = makeController(); |
1669 | + var originalInterface = { |
1670 | + id: 0, |
1671 | + links: [ |
1672 | + { |
1673 | + id: 0, |
1674 | + mode: "static", |
1675 | + ip_address: "192.168.122.10", |
1676 | + subnet_id: 0 |
1677 | + } |
1678 | + ] |
1679 | + }; |
1680 | + $scope.originalInterfaces = [originalInterface]; |
1681 | + |
1682 | + var nic = { |
1683 | + id: 0, |
1684 | + mode: "static", |
1685 | + link_id: 0, |
1686 | + ip_address: "192.168.123.10", |
1687 | + subnet: { |
1688 | + id: 0, |
1689 | + cidr: "192.168.122.0/24" |
1690 | + } |
1691 | + }; |
1692 | + spyOn($scope, "saveInterfaceLink"); |
1693 | + $scope.saveInterfaceIPAddress(nic); |
1694 | + expect(nic.ip_address).toBe("192.168.122.10"); |
1695 | + expect($scope.saveInterfaceLink).not.toHaveBeenCalled(); |
1696 | + }); |
1697 | + |
1698 | + it("saves the link if valid", function() { |
1699 | + var controller = makeController(); |
1700 | + var originalInterface = { |
1701 | + id: 0, |
1702 | + links: [ |
1703 | + { |
1704 | + id: 0, |
1705 | + mode: "static", |
1706 | + ip_address: "192.168.122.10", |
1707 | + subnet_id: 0 |
1708 | + } |
1709 | + ] |
1710 | + }; |
1711 | + $scope.originalInterfaces = [originalInterface]; |
1712 | + |
1713 | + var nic = { |
1714 | + id: 0, |
1715 | + mode: "static", |
1716 | + link_id: 0, |
1717 | + ip_address: "192.168.122.11", |
1718 | + subnet: { |
1719 | + id: 0, |
1720 | + cidr: "192.168.122.0/24" |
1721 | + } |
1722 | + }; |
1723 | + spyOn($scope, "saveInterfaceLink"); |
1724 | + $scope.saveInterfaceIPAddress(nic); |
1725 | + expect(nic.ip_address).toBe("192.168.122.11"); |
1726 | + expect($scope.saveInterfaceLink).toHaveBeenCalledWith(nic); |
1727 | + }); |
1728 | + }); |
1729 | }); |
1730 | |
1731 | === modified file 'src/maasserver/static/js/angular/factories/nodes.js' |
1732 | --- src/maasserver/static/js/angular/factories/nodes.js 2015-09-30 22:40:08 +0000 |
1733 | +++ src/maasserver/static/js/angular/factories/nodes.js 2015-10-01 14:33:47 +0000 |
1734 | @@ -90,11 +90,23 @@ |
1735 | "node.update_interface", params); |
1736 | }; |
1737 | |
1738 | - // Send the update information to the region. |
1739 | + // Create or update the link to the subnet for the interface. |
1740 | + NodesManager.prototype.linkSubnet = function( |
1741 | + node, interface_id, params) { |
1742 | + if(!angular.isObject(params)) { |
1743 | + params = {}; |
1744 | + } |
1745 | + params.system_id = node.system_id; |
1746 | + params.interface_id = interface_id; |
1747 | + return RegionConnection.callMethod( |
1748 | + "node.link_subnet", params); |
1749 | + }; |
1750 | + |
1751 | + // Unmount the filesystem on the block device or partition. |
1752 | NodesManager.prototype.unmountFilesystem = function( |
1753 | system_id, block_id, partition_id) { |
1754 | var self = this; |
1755 | - var method = this._handler + ".unmountFilesystem"; |
1756 | + var method = this._handler + ".unmount_filesystem"; |
1757 | var params = { |
1758 | system_id: system_id, |
1759 | block_id: block_id, |
1760 | |
1761 | === modified file 'src/maasserver/static/js/angular/factories/tests/test_nodes.js' |
1762 | --- src/maasserver/static/js/angular/factories/tests/test_nodes.js 2015-09-30 22:40:08 +0000 |
1763 | +++ src/maasserver/static/js/angular/factories/tests/test_nodes.js 2015-10-01 14:33:47 +0000 |
1764 | @@ -178,6 +178,49 @@ |
1765 | }); |
1766 | }); |
1767 | |
1768 | + describe("linkSubnet", function() { |
1769 | + |
1770 | + it("calls node.link_subnet with system_id and interface_id", |
1771 | + function(done) { |
1772 | + var node = makeNode(), interface_id = makeInteger(0, 100); |
1773 | + webSocket.returnData.push(makeFakeResponse("updated")); |
1774 | + NodesManager.linkSubnet(node, interface_id).then( |
1775 | + function() { |
1776 | + var sentObject = angular.fromJson( |
1777 | + webSocket.sentData[0]); |
1778 | + expect(sentObject.method).toBe( |
1779 | + "node.link_subnet"); |
1780 | + expect(sentObject.params.system_id).toBe( |
1781 | + node.system_id); |
1782 | + expect(sentObject.params.interface_id).toBe( |
1783 | + interface_id); |
1784 | + done(); |
1785 | + }); |
1786 | + }); |
1787 | + |
1788 | + it("calls node.link_subnet with params", |
1789 | + function(done) { |
1790 | + var node = makeNode(), interface_id = makeInteger(0, 100); |
1791 | + var params = { |
1792 | + name: makeName("eth0") |
1793 | + }; |
1794 | + webSocket.returnData.push(makeFakeResponse("updated")); |
1795 | + NodesManager.linkSubnet(node, interface_id, params).then( |
1796 | + function() { |
1797 | + var sentObject = angular.fromJson( |
1798 | + webSocket.sentData[0]); |
1799 | + expect(sentObject.method).toBe( |
1800 | + "node.link_subnet"); |
1801 | + expect(sentObject.params.system_id).toBe( |
1802 | + node.system_id); |
1803 | + expect(sentObject.params.interface_id).toBe( |
1804 | + interface_id); |
1805 | + expect(sentObject.params.name).toBe(params.name); |
1806 | + done(); |
1807 | + }); |
1808 | + }); |
1809 | + }); |
1810 | + |
1811 | describe("unmountFilesystem", function() { |
1812 | |
1813 | it("calls node.unmountFilesystem", function(done) { |
1814 | @@ -186,7 +229,7 @@ |
1815 | NodesManager.unmountFilesystem( |
1816 | makeName("block_id"), null).then(function() { |
1817 | var sentObject = angular.fromJson(webSocket.sentData[0]); |
1818 | - expect(sentObject.method).toBe("node.unmountFilesystem"); |
1819 | + expect(sentObject.method).toBe("node.unmount_filesystem"); |
1820 | done(); |
1821 | }); |
1822 | }); |
1823 | @@ -200,6 +243,7 @@ |
1824 | fakeNode.system_id, block_id, partition_id).then( |
1825 | function() { |
1826 | var sentObject = angular.fromJson(webSocket.sentData[0]); |
1827 | + expect(sentObject.method).toBe("node.unmount_filesystem"); |
1828 | expect(sentObject.params.system_id).toBe(fakeNode.system_id); |
1829 | expect(sentObject.params.block_id).toBe(block_id); |
1830 | expect(sentObject.params.partition_id).toBe(partition_id); |
1831 | |
1832 | === modified file 'src/maasserver/static/partials/node-details.html' |
1833 | --- src/maasserver/static/partials/node-details.html 2015-10-01 11:15:36 +0000 |
1834 | +++ src/maasserver/static/partials/node-details.html 2015-10-01 14:33:47 +0000 |
1835 | @@ -358,6 +358,7 @@ |
1836 | <div class="table__data table__column--14"> |
1837 | <select class="table__input" name="fabric" id="fabric" |
1838 | data-ng-model="interface.fabric" |
1839 | + data-ng-disabled="interface.type == 'alias'" |
1840 | data-ng-change="fabricChanged(interface)" |
1841 | data-ng-options="fabric as fabric.name for fabric in fabrics"> |
1842 | </select> |
1843 | @@ -365,39 +366,37 @@ |
1844 | <div class="table__data table__column--14"> |
1845 | <select class="table__input" name="vlan" id="vlan" |
1846 | data-ng-model="interface.vlan" |
1847 | + data-ng-disabled="interface.type == 'alias'" |
1848 | data-ng-change="saveInterface(interface)" |
1849 | - data-ng-options="vlan as vlan.name for vlan in vlans | filterByFabric:interface.fabric"> |
1850 | + data-ng-options="vlan as getVLANText(vlan) for vlan in vlans | filterByFabric:interface.fabric"> |
1851 | </select> |
1852 | </div> |
1853 | <div class="table__data table__column--18"> |
1854 | - {$ getSubnetName(interface) $} |
1855 | - <!-- |
1856 | - TODO: Use when editing is done. |
1857 | - <select class="table__input" name="range" id="range"> |
1858 | - <option value="auto">10.10.10.1/6 (managed)</option> |
1859 | + <select class="table__input" name="subnet" id="subnet" |
1860 | + data-ng-model="interface.subnet" |
1861 | + data-ng-change="subnetChanged(interface)" |
1862 | + data-ng-options="subnet as getSubnetText(subnet) for subnet in subnets | filterByVLAN:interface.vlan"> |
1863 | + <option value="" data-ng-hide="interface.links.length > 1">Unconfigured</option> |
1864 | </select> |
1865 | - --> |
1866 | </div> |
1867 | <div class="table__data table__column--14"> |
1868 | <ul class="no-bullets"> |
1869 | <li> |
1870 | - {$ getLinkModeText(interface) $} |
1871 | - <!-- |
1872 | - TODO: Use when editing is done. |
1873 | - <select class="table__input" name="ip-address" id="ip-address"> |
1874 | - <option value="auto">Auto assign</option> |
1875 | - <option value="manual" selected>Manual</option> |
1876 | - <option value="DHCP">DHCP</option> |
1877 | - <option value="unconfigured">Unconfigured</option> |
1878 | + <select class="table__input" name="link-mode" id="link-mode" |
1879 | + data-ng-model="interface.mode" |
1880 | + data-ng-change="saveInterfaceLink(interface)" |
1881 | + data-ng-disabled="isLinkModeDisabled(interface)" |
1882 | + data-ng-options="mode.mode as mode.text for mode in getLinkModes(interface)"> |
1883 | </select> |
1884 | - --> |
1885 | </li> |
1886 | - <li class="margin-top--ten" data-ng-show="interface.ip_address"> |
1887 | - {$ interface.ip_address $} |
1888 | - <!-- |
1889 | - TODO: Use when editing is done. |
1890 | - <input type="text" class="table__input" value="127.0.0.1"> |
1891 | - --> |
1892 | + <li class="margin-top--ten" data-ng-show="shouldShowIPAddress(interface)"> |
1893 | + <input type="text" class="table__input" |
1894 | + data-ng-model="interface.ip_address" |
1895 | + data-ng-class="{ invalid: isIPAddressInvalid(interface) }" |
1896 | + data-maas-enter-blur |
1897 | + data-ng-focus="setFocusInterface(interface)" |
1898 | + data-ng-blur="clearFocusInterface(interface)" |
1899 | + data-ng-disabled="interface.mode != 'static'"> |
1900 | </li> |
1901 | </ul> |
1902 | </div> |
1903 | |
1904 | === modified file 'src/maasserver/websockets/handlers/node.py' |
1905 | --- src/maasserver/websockets/handlers/node.py 2015-09-30 22:40:08 +0000 |
1906 | +++ src/maasserver/websockets/handlers/node.py 2015-10-01 14:33:47 +0000 |
1907 | @@ -37,6 +37,7 @@ |
1908 | from maasserver.models.nodeprobeddetails import get_single_probed_details |
1909 | from maasserver.models.partition import Partition |
1910 | from maasserver.models.physicalblockdevice import PhysicalBlockDevice |
1911 | +from maasserver.models.subnet import Subnet |
1912 | from maasserver.models.tag import Tag |
1913 | from maasserver.node_action import compile_node_actions |
1914 | from maasserver.rpc import getClientFor |
1915 | @@ -105,7 +106,8 @@ |
1916 | 'set_active', |
1917 | 'check_power', |
1918 | 'update_interface', |
1919 | - 'unmountFilesystem', |
1920 | + 'link_subnet', |
1921 | + 'unmount_filesystem', |
1922 | ] |
1923 | form = AdminNodeWithMACAddressesForm |
1924 | exclude = [ |
1925 | @@ -348,7 +350,9 @@ |
1926 | |
1927 | def dehydrate_interface(self, interface, obj): |
1928 | """Dehydrate a `interface` into a interface definition.""" |
1929 | - links = interface.get_links() |
1930 | + # Sort the links by ID that way they show up in the same order in |
1931 | + # the UI. |
1932 | + links = sorted(interface.get_links(), key=itemgetter("id")) |
1933 | for link in links: |
1934 | # Replace the subnet object with the subnet_id. The client will |
1935 | # use this information to pull the subnet information from the |
1936 | @@ -545,7 +549,7 @@ |
1937 | node_obj.save() |
1938 | return self.full_dehydrate(node_obj) |
1939 | |
1940 | - def unmountFilesystem(self, params): |
1941 | + def unmount_filesystem(self, params): |
1942 | node = self.get_object(params) |
1943 | if params.get('partition_id') is not None: |
1944 | obj = Partition.objects.get( |
1945 | @@ -601,6 +605,10 @@ |
1946 | |
1947 | def update_interface(self, params): |
1948 | """Update the interface.""" |
1949 | + # Only admin users can perform update. |
1950 | + if not self.user.is_superuser: |
1951 | + raise HandlerPermissionError() |
1952 | + |
1953 | node = self.get_object(params) |
1954 | interface = Interface.objects.get(node=node, id=params["interface_id"]) |
1955 | interface_form = InterfaceForm.get_interface_form(interface.type) |
1956 | @@ -610,6 +618,28 @@ |
1957 | else: |
1958 | raise ValidationError(form.errors) |
1959 | |
1960 | + def link_subnet(self, params): |
1961 | + """Create or update the link.""" |
1962 | + # Only admin users can perform update. |
1963 | + if not self.user.is_superuser: |
1964 | + raise HandlerPermissionError() |
1965 | + |
1966 | + node = self.get_object(params) |
1967 | + interface = Interface.objects.get(node=node, id=params["interface_id"]) |
1968 | + subnet = None |
1969 | + if "subnet" in params: |
1970 | + subnet = Subnet.objects.get(id=params["subnet"]) |
1971 | + if "link_id" in params: |
1972 | + # We are updating an already existing link. |
1973 | + interface.update_link_by_id( |
1974 | + params["link_id"], params["mode"], subnet, |
1975 | + ip_address=params.get("ip_address", None)) |
1976 | + else: |
1977 | + # We are creating a new link. |
1978 | + interface.link_subnet( |
1979 | + params["mode"], subnet, |
1980 | + ip_address=params.get("ip_address", None)) |
1981 | + |
1982 | @asynchronous |
1983 | @inlineCallbacks |
1984 | def check_power(self, params): |
1985 | |
1986 | === modified file 'src/maasserver/websockets/handlers/tests/test_node.py' |
1987 | --- src/maasserver/websockets/handlers/tests/test_node.py 2015-10-01 13:29:51 +0000 |
1988 | +++ src/maasserver/websockets/handlers/tests/test_node.py 2015-10-01 14:33:47 +0000 |
1989 | @@ -25,6 +25,7 @@ |
1990 | from lxml import etree |
1991 | from maasserver.enum import ( |
1992 | FILESYSTEM_FORMAT_TYPE_CHOICES_DICT, |
1993 | + INTERFACE_LINK_TYPE, |
1994 | INTERFACE_TYPE, |
1995 | IPADDRESS_TYPE, |
1996 | NODE_STATUS, |
1997 | @@ -33,6 +34,7 @@ |
1998 | from maasserver.forms import AdminNodeWithMACAddressesForm |
1999 | from maasserver.models import interface as interface_module |
2000 | from maasserver.models.config import Config |
2001 | +from maasserver.models.interface import Interface |
2002 | from maasserver.models.nodeprobeddetails import get_single_probed_details |
2003 | from maasserver.node_action import compile_node_actions |
2004 | from maasserver.rpc.testing.fixtures import MockLiveRegionToClusterRPCFixture |
2005 | @@ -79,7 +81,10 @@ |
2006 | LIST_MODALIASES_OUTPUT_NAME, |
2007 | LLDP_OUTPUT_NAME, |
2008 | ) |
2009 | -from mock import sentinel |
2010 | +from mock import ( |
2011 | + ANY, |
2012 | + sentinel, |
2013 | +) |
2014 | from netaddr import IPAddress |
2015 | from provisioningserver.power.poweraction import PowerActionFail |
2016 | from provisioningserver.rpc.cluster import PowerQuery |
2017 | @@ -915,14 +920,14 @@ |
2018 | updated_node = handler.update(node_data) |
2019 | self.assertItemsEqual([tag_name], updated_node["tags"]) |
2020 | |
2021 | - def test_unmountFilesystem(self): |
2022 | + def test_unmount_filesystem(self): |
2023 | user = factory.make_admin() |
2024 | handler = NodeHandler(user, {}) |
2025 | architecture = make_usable_architecture(self) |
2026 | node = factory.make_Node(interface=True, architecture=architecture) |
2027 | block_device = factory.make_PhysicalBlockDevice(node=node) |
2028 | factory.make_Filesystem(block_device=block_device) |
2029 | - handler.unmountFilesystem({ |
2030 | + handler.unmount_filesystem({ |
2031 | 'system_id': node.system_id, |
2032 | 'block_id': block_device.id |
2033 | }) |
2034 | @@ -1006,7 +1011,7 @@ |
2035 | node.distro_series, Equals(osystem["releases"][0]["name"])) |
2036 | |
2037 | def test_update_interface(self): |
2038 | - user = factory.make_User() |
2039 | + user = factory.make_admin() |
2040 | node = factory.make_Node() |
2041 | handler = NodeHandler(user, {}) |
2042 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
2043 | @@ -1023,7 +1028,7 @@ |
2044 | self.assertEquals(new_vlan, interface.vlan) |
2045 | |
2046 | def test_update_interface_raises_ValidationError(self): |
2047 | - user = factory.make_User() |
2048 | + user = factory.make_admin() |
2049 | node = factory.make_Node() |
2050 | handler = NodeHandler(user, {}) |
2051 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
2052 | @@ -1036,6 +1041,50 @@ |
2053 | "vlan": random.randint(1000, 5000), |
2054 | }) |
2055 | |
2056 | + def test_link_subnet_calls_update_link_by_id_if_link_id(self): |
2057 | + user = factory.make_admin() |
2058 | + node = factory.make_Node() |
2059 | + handler = NodeHandler(user, {}) |
2060 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
2061 | + subnet = factory.make_Subnet() |
2062 | + link_id = random.randint(0, 100) |
2063 | + mode = factory.pick_enum(INTERFACE_LINK_TYPE) |
2064 | + ip_address = factory.make_ip_address() |
2065 | + self.patch_autospec(Interface, "update_link_by_id") |
2066 | + handler.link_subnet({ |
2067 | + "system_id": node.system_id, |
2068 | + "interface_id": interface.id, |
2069 | + "link_id": link_id, |
2070 | + "subnet": subnet.id, |
2071 | + "mode": mode, |
2072 | + "ip_address": ip_address, |
2073 | + }) |
2074 | + self.assertThat( |
2075 | + Interface.update_link_by_id, |
2076 | + MockCalledOnceWith( |
2077 | + ANY, link_id, mode, subnet, ip_address=ip_address)) |
2078 | + |
2079 | + def test_link_subnet_calls_link_subnet_if_not_link_id(self): |
2080 | + user = factory.make_admin() |
2081 | + node = factory.make_Node() |
2082 | + handler = NodeHandler(user, {}) |
2083 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
2084 | + subnet = factory.make_Subnet() |
2085 | + mode = factory.pick_enum(INTERFACE_LINK_TYPE) |
2086 | + ip_address = factory.make_ip_address() |
2087 | + self.patch_autospec(Interface, "link_subnet") |
2088 | + handler.link_subnet({ |
2089 | + "system_id": node.system_id, |
2090 | + "interface_id": interface.id, |
2091 | + "subnet": subnet.id, |
2092 | + "mode": mode, |
2093 | + "ip_address": ip_address, |
2094 | + }) |
2095 | + self.assertThat( |
2096 | + Interface.link_subnet, |
2097 | + MockCalledOnceWith( |
2098 | + ANY, mode, subnet, ip_address=ip_address)) |
2099 | + |
2100 | |
2101 | class TestNodeHandlerCheckPower(MAASTransactionServerTestCase): |
2102 | |
2103 | |
2104 | === modified file 'src/maastesting/factory.py' |
2105 | --- src/maastesting/factory.py 2015-09-24 16:22:12 +0000 |
2106 | +++ src/maastesting/factory.py 2015-10-01 14:33:47 +0000 |
2107 | @@ -276,15 +276,31 @@ |
2108 | slash = 128 - host_bits |
2109 | return self.make_ipv6_network(slash=slash) |
2110 | |
2111 | - def pick_ip_in_dynamic_range(self, ngi): |
2112 | + def pick_ip_in_dynamic_range(self, ngi, but_not=None): |
2113 | + if but_not is None: |
2114 | + but_not = [] |
2115 | first = ngi.get_dynamic_ip_range().first |
2116 | last = ngi.get_dynamic_ip_range().last |
2117 | - return unicode(IPAddress(random.randrange(first, last))) |
2118 | + but_not = [IPAddress(but) for but in but_not if but is not None] |
2119 | + for _ in range(100): |
2120 | + address = IPAddress(random.randint(first, last)) |
2121 | + if address not in but_not: |
2122 | + return bytes(address) |
2123 | + raise TooManyRandomRetries( |
2124 | + "Could not find available IP in static range") |
2125 | |
2126 | - def pick_ip_in_static_range(self, ngi): |
2127 | + def pick_ip_in_static_range(self, ngi, but_not=None): |
2128 | + if but_not is None: |
2129 | + but_not = [] |
2130 | first = ngi.get_static_ip_range().first |
2131 | last = ngi.get_static_ip_range().last |
2132 | - return unicode(IPAddress(random.randrange(first, last))) |
2133 | + but_not = [IPAddress(but) for but in but_not if but is not None] |
2134 | + for _ in range(100): |
2135 | + address = IPAddress(random.randint(first, last)) |
2136 | + if address not in but_not: |
2137 | + return bytes(address) |
2138 | + raise TooManyRandomRetries( |
2139 | + "Could not find available IP in static range") |
2140 | |
2141 | def pick_ip_in_network(self, network, but_not=None): |
2142 | if but_not is None: |
lgtm! I have not done an indepth review but looks good so far!