Merge lp:~blake-rouse/maas/fix-1566336-1.9 into lp:maas/1.9
- fix-1566336-1.9
- Merge into 1.9
Proposed by
Blake Rouse
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4552 |
Proposed branch: | lp:~blake-rouse/maas/fix-1566336-1.9 |
Merge into: | lp:maas/1.9 |
Diff against target: |
1205 lines (+409/-138) 10 files modified
src/maasserver/api/tests/test_interfaces.py (+21/-13) src/maasserver/forms_interface.py (+55/-12) src/maasserver/models/interface.py (+3/-2) src/maasserver/models/signals/interfaces.py (+58/-18) src/maasserver/models/signals/tests/test_interfaces.py (+46/-0) src/maasserver/models/tests/test_interface.py (+55/-23) src/maasserver/models/tests/test_staticipaddress.py (+3/-0) src/maasserver/testing/factory.py (+17/-4) src/maasserver/tests/test_forms_interface.py (+137/-57) src/maasserver/websockets/handlers/tests/test_node.py (+14/-9) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/fix-1566336-1.9 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+291009@code.launchpad.net |
Commit message
Remove links on parents when added to a bond. Only allow physical interface and bond interface to be attached to the untagged VLAN on a fabric.
Description of the change
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/api/tests/test_interfaces.py' |
2 | --- src/maasserver/api/tests/test_interfaces.py 2015-12-04 21:23:28 +0000 |
3 | +++ src/maasserver/api/tests/test_interfaces.py 2016-04-05 16:02:24 +0000 |
4 | @@ -56,14 +56,14 @@ |
5 | def make_complex_interface(node, name=None): |
6 | """Makes interface with parents and children.""" |
7 | fabric = factory.make_Fabric() |
8 | - vlan_5 = factory.make_VLAN(vid=5, fabric=fabric) |
9 | + untagged = fabric.get_default_vlan() |
10 | nic_0 = factory.make_Interface( |
11 | - INTERFACE_TYPE.PHYSICAL, vlan=vlan_5, node=node) |
12 | + INTERFACE_TYPE.PHYSICAL, vlan=untagged, node=node) |
13 | nic_1 = factory.make_Interface( |
14 | - INTERFACE_TYPE.PHYSICAL, vlan=vlan_5, node=node) |
15 | + INTERFACE_TYPE.PHYSICAL, vlan=untagged, node=node) |
16 | parents = [nic_0, nic_1] |
17 | bond_interface = factory.make_Interface( |
18 | - INTERFACE_TYPE.BOND, mac_address=nic_0.mac_address, vlan=vlan_5, |
19 | + INTERFACE_TYPE.BOND, mac_address=nic_0.mac_address, vlan=untagged, |
20 | parents=parents, name=name) |
21 | vlan_10 = factory.make_VLAN(vid=10, fabric=fabric) |
22 | vlan_nic_10 = factory.make_Interface( |
23 | @@ -117,7 +117,8 @@ |
24 | node = factory.make_Node(status=status) |
25 | mac = factory.make_mac_address() |
26 | name = factory.make_name("eth") |
27 | - vlan = factory.make_VLAN() |
28 | + fabric = factory.make_Fabric() |
29 | + vlan = fabric.get_default_vlan() |
30 | tags = [ |
31 | factory.make_name("tag") |
32 | for _ in range(3) |
33 | @@ -150,7 +151,8 @@ |
34 | owner=self.logged_in_user, installable=False, parent=parent) |
35 | mac = factory.make_mac_address() |
36 | name = factory.make_name("eth") |
37 | - vlan = factory.make_VLAN() |
38 | + fabric = factory.make_Fabric() |
39 | + vlan = fabric.get_default_vlan() |
40 | tags = [ |
41 | factory.make_name("tag") |
42 | for _ in range(3) |
43 | @@ -183,7 +185,8 @@ |
44 | node = factory.make_Node(status=status) |
45 | mac = factory.make_mac_address() |
46 | name = factory.make_name("eth") |
47 | - vlan = factory.make_VLAN() |
48 | + fabric = factory.make_Fabric() |
49 | + vlan = fabric.get_default_vlan() |
50 | tags = [ |
51 | factory.make_name("tag") |
52 | for _ in range(3) |
53 | @@ -279,7 +282,8 @@ |
54 | interface_on_other_node = factory.make_Interface( |
55 | INTERFACE_TYPE.PHYSICAL) |
56 | name = factory.make_name("eth") |
57 | - vlan = factory.make_VLAN() |
58 | + fabric = factory.make_Fabric() |
59 | + vlan = fabric.get_default_vlan() |
60 | uri = get_interfaces_uri(node) |
61 | response = self.client.post(uri, { |
62 | "op": "create_physical", |
63 | @@ -299,7 +303,8 @@ |
64 | self.become_admin() |
65 | for status in (NODE_STATUS.READY, NODE_STATUS.BROKEN): |
66 | node = factory.make_Node(status=status) |
67 | - vlan = factory.make_VLAN() |
68 | + fabric = factory.make_Fabric() |
69 | + vlan = fabric.get_default_vlan() |
70 | parent_1_iface = factory.make_Interface( |
71 | INTERFACE_TYPE.PHYSICAL, vlan=vlan, node=node) |
72 | parent_2_iface = factory.make_Interface( |
73 | @@ -402,7 +407,7 @@ |
74 | self.assertEqual( |
75 | httplib.CONFLICT, response.status_code, response.content) |
76 | |
77 | - def test_create_bond_requires_name_vlan_and_parents(self): |
78 | + def test_create_bond_requires_name_and_parents(self): |
79 | self.become_admin() |
80 | node = factory.make_Node(status=NODE_STATUS.READY) |
81 | uri = get_interfaces_uri(node) |
82 | @@ -415,7 +420,6 @@ |
83 | self.assertEquals({ |
84 | "mac_address": ["This field cannot be blank."], |
85 | "name": ["This field is required."], |
86 | - "vlan": ["This field is required."], |
87 | "parents": ["A Bond interface must have one or more parents."], |
88 | }, json.loads(response.content)) |
89 | |
90 | @@ -634,7 +638,8 @@ |
91 | interface = factory.make_Interface( |
92 | INTERFACE_TYPE.PHYSICAL, node=node) |
93 | new_name = factory.make_name("name") |
94 | - new_vlan = factory.make_VLAN() |
95 | + new_fabric = factory.make_Fabric() |
96 | + new_vlan = new_fabric.get_default_vlan() |
97 | uri = get_interface_uri(interface) |
98 | response = self.client.put(uri, { |
99 | "name": new_name, |
100 | @@ -653,7 +658,8 @@ |
101 | interface = factory.make_Interface( |
102 | INTERFACE_TYPE.PHYSICAL, node=device) |
103 | new_name = factory.make_name("name") |
104 | - new_vlan = factory.make_VLAN() |
105 | + new_fabric = factory.make_Fabric() |
106 | + new_vlan = new_fabric.get_default_vlan() |
107 | uri = get_interface_uri(interface) |
108 | response = self.client.put(uri, { |
109 | "name": new_name, |
110 | @@ -688,9 +694,11 @@ |
111 | node) |
112 | physical_interface = factory.make_Interface( |
113 | INTERFACE_TYPE.PHYSICAL, node=node) |
114 | + new_vlan = factory.make_VLAN(fabric=physical_interface.vlan.fabric) |
115 | uri = get_interface_uri(vlan_10) |
116 | response = self.client.put(uri, { |
117 | "parent": physical_interface.id, |
118 | + "vlan": new_vlan.id, |
119 | }) |
120 | self.assertEqual( |
121 | httplib.OK, response.status_code, response.content) |
122 | |
123 | === modified file 'src/maasserver/forms_interface.py' |
124 | --- src/maasserver/forms_interface.py 2015-10-31 23:55:19 +0000 |
125 | +++ src/maasserver/forms_interface.py 2016-04-05 16:02:24 +0000 |
126 | @@ -196,6 +196,13 @@ |
127 | msg = "A physical interface cannot have parents." |
128 | raise ValidationError({'parents': [msg]}) |
129 | |
130 | + def clean_vlan(self): |
131 | + new_vlan = self.cleaned_data.get('vlan') |
132 | + if new_vlan and new_vlan.fabric.get_default_vlan() != new_vlan: |
133 | + raise ValidationError( |
134 | + "A physical interface can only belong to an untagged VLAN.") |
135 | + return new_vlan |
136 | + |
137 | def clean(self): |
138 | cleaned_data = super(PhysicalInterfaceForm, self).clean() |
139 | new_name = cleaned_data.get('name') |
140 | @@ -233,11 +240,25 @@ |
141 | "in a bond.") |
142 | return parents |
143 | |
144 | + def clean_vlan(self): |
145 | + new_vlan = self.cleaned_data.get('vlan') |
146 | + if new_vlan and new_vlan.fabric.get_default_vlan() == new_vlan: |
147 | + raise ValidationError( |
148 | + "A VLAN interface can only belong to a tagged VLAN.") |
149 | + return new_vlan |
150 | + |
151 | def clean(self): |
152 | cleaned_data = super(VLANInterfaceForm, self).clean() |
153 | if self.fields_ok(['vlan', 'parents']): |
154 | new_vlan = self.cleaned_data.get('vlan') |
155 | if new_vlan: |
156 | + # VLAN needs to be the in the same fabric as the parent. |
157 | + parent = self.cleaned_data.get('parents')[0] |
158 | + if parent.vlan.fabric_id != new_vlan.fabric_id: |
159 | + set_form_error( |
160 | + self, "vlan", |
161 | + "A VLAN interface can only belong to a tagged VLAN on " |
162 | + "the same fabric as its parent interface.") |
163 | name = build_vlan_interface_name( |
164 | self.cleaned_data.get('parents').first(), new_vlan) |
165 | self.clean_interface_name_uniqueness(name) |
166 | @@ -281,6 +302,15 @@ |
167 | 'name', |
168 | ) |
169 | |
170 | + def __init__(self, *args, **kwargs): |
171 | + super(BondInterfaceForm, self).__init__(*args, **kwargs) |
172 | + # Allow VLAN to be blank when creating. |
173 | + instance = kwargs.get("instance", None) |
174 | + if instance is not None and instance.id is not None: |
175 | + self.fields['vlan'].required = True |
176 | + else: |
177 | + self.fields['vlan'].required = False |
178 | + |
179 | def clean_parents(self): |
180 | parents = self.get_clean_parents() |
181 | if parents is None: |
182 | @@ -290,9 +320,16 @@ |
183 | raise ValidationError({'parents': [msg]}) |
184 | return parents |
185 | |
186 | + def clean_vlan(self): |
187 | + new_vlan = self.cleaned_data.get('vlan') |
188 | + if new_vlan and new_vlan.fabric.get_default_vlan() != new_vlan: |
189 | + raise ValidationError( |
190 | + "A bond interface can only belong to an untagged VLAN.") |
191 | + return new_vlan |
192 | + |
193 | def clean(self): |
194 | cleaned_data = super(BondInterfaceForm, self).clean() |
195 | - if self.fields_ok(['parents']): |
196 | + if self.fields_ok(['vlan', 'parents']): |
197 | parents = self.cleaned_data.get('parents') |
198 | # Set the mac_address if its missing and the interface is being |
199 | # created. |
200 | @@ -333,6 +370,23 @@ |
201 | self, 'parents', |
202 | "%s is already in-use by another interface." % ( |
203 | ', '.join(sorted(parents_with_other_children)))) |
204 | + |
205 | + # When creating the bond set VLAN to the same as the parents |
206 | + # and check that the parents all belong to the same VLAN. |
207 | + if self.instance.id is None: |
208 | + vlan = self.cleaned_data.get('vlan') |
209 | + if vlan is None: |
210 | + vlan = parents[0].vlan |
211 | + self.cleaned_data['vlan'] = vlan |
212 | + parent_vlans = { |
213 | + parent.vlan |
214 | + for parent in parents |
215 | + } |
216 | + if parent_vlans != set([vlan]): |
217 | + set_form_error( |
218 | + self, 'parents', |
219 | + "All parents must belong to the same VLAN.") |
220 | + |
221 | return cleaned_data |
222 | |
223 | def set_extra_parameters(self, interface, created): |
224 | @@ -356,17 +410,6 @@ |
225 | elif created: |
226 | interface.params[bond_field] = self.fields[bond_field].initial |
227 | |
228 | - def save(self, *args, **kwargs): |
229 | - """Persist the interface into the database.""" |
230 | - created = self.instance.id is None |
231 | - interface = super(BondInterfaceForm, self).save() |
232 | - if created: |
233 | - # Bond was created we remove all the links on the parent interfaces |
234 | - # and ensure that the bond has atleast a LINK_UP. |
235 | - for parent in interface.parents.all(): |
236 | - parent.clear_all_links(clearing_config=True) |
237 | - return interface |
238 | - |
239 | |
240 | INTERFACE_FORM_MAPPING = { |
241 | INTERFACE_TYPE.PHYSICAL: PhysicalInterfaceForm, |
242 | |
243 | === modified file 'src/maasserver/models/interface.py' |
244 | --- src/maasserver/models/interface.py 2015-12-11 00:24:18 +0000 |
245 | +++ src/maasserver/models/interface.py 2016-04-05 16:02:24 +0000 |
246 | @@ -922,11 +922,12 @@ |
247 | # Nothing to do, already has links. |
248 | return |
249 | else: |
250 | - # Use an associated subnet if it exists, else it will just be a |
251 | + # Use an associated subnet if it exists and its on the same VLAN |
252 | + # the interface is currently connected, else it will just be a |
253 | # LINK_UP without a subnet. |
254 | discovered_address = self.ip_addresses.filter( |
255 | alloc_type=IPADDRESS_TYPE.DISCOVERED, |
256 | - subnet__isnull=False).first() |
257 | + subnet__vlan=self.vlan).first() |
258 | if discovered_address is not None: |
259 | subnet = discovered_address.subnet |
260 | else: |
261 | |
262 | === modified file 'src/maasserver/models/signals/interfaces.py' |
263 | --- src/maasserver/models/signals/interfaces.py 2015-11-03 12:38:17 +0000 |
264 | +++ src/maasserver/models/signals/interfaces.py 2016-04-05 16:02:24 +0000 |
265 | @@ -14,6 +14,7 @@ |
266 | __metaclass__ = type |
267 | __all__ = [] |
268 | |
269 | +from django.db.models.signals import post_save |
270 | from maasserver.enum import ( |
271 | INTERFACE_TYPE, |
272 | IPADDRESS_TYPE, |
273 | @@ -27,6 +28,14 @@ |
274 | from maasserver.utils.signals import connect_to_field_change |
275 | |
276 | |
277 | +INTERFACE_CLASSES = [ |
278 | + Interface, |
279 | + PhysicalInterface, |
280 | + BondInterface, |
281 | + VLANInterface, |
282 | +] |
283 | + |
284 | + |
285 | def interface_enabled_or_disabled(instance, old_values, **kwargs): |
286 | """When an interface is enabled be sure at minimum a LINK_UP is created. |
287 | When an interface is disabled make sure that all its links are removed, |
288 | @@ -52,12 +61,10 @@ |
289 | ip_address, clearing_config=True) |
290 | |
291 | |
292 | -connect_to_field_change( |
293 | - interface_enabled_or_disabled, |
294 | - Interface, ['enabled'], delete=False) |
295 | -connect_to_field_change( |
296 | - interface_enabled_or_disabled, |
297 | - PhysicalInterface, ['enabled'], delete=False) |
298 | +for klass in INTERFACE_CLASSES: |
299 | + connect_to_field_change( |
300 | + interface_enabled_or_disabled, |
301 | + klass, ['enabled'], delete=False) |
302 | |
303 | |
304 | def interface_mtu_params_update(instance, old_values, **kwargs): |
305 | @@ -118,15 +125,48 @@ |
306 | parent.save() |
307 | |
308 | |
309 | -connect_to_field_change( |
310 | - interface_mtu_params_update, |
311 | - Interface, ['params'], delete=False) |
312 | -connect_to_field_change( |
313 | - interface_mtu_params_update, |
314 | - PhysicalInterface, ['params'], delete=False) |
315 | -connect_to_field_change( |
316 | - interface_mtu_params_update, |
317 | - BondInterface, ['params'], delete=False) |
318 | -connect_to_field_change( |
319 | - interface_mtu_params_update, |
320 | - VLANInterface, ['params'], delete=False) |
321 | +for klass in INTERFACE_CLASSES: |
322 | + connect_to_field_change( |
323 | + interface_mtu_params_update, |
324 | + klass, ['params'], delete=False) |
325 | + |
326 | + |
327 | +def update_bond_parents(sender, instance, created, **kwargs): |
328 | + """Update bond parents when interface created.""" |
329 | + if instance.type == INTERFACE_TYPE.BOND: |
330 | + for parent in instance.parents.all(): |
331 | + # Make sure the parent has not links as well, just to be sure. |
332 | + parent.clear_all_links(clearing_config=True) |
333 | + if parent.vlan != instance.vlan: |
334 | + parent.vlan = instance.vlan |
335 | + parent.save() |
336 | + |
337 | + |
338 | +for klass in INTERFACE_CLASSES: |
339 | + post_save.connect( |
340 | + update_bond_parents, sender=klass) |
341 | + |
342 | + |
343 | +def interface_vlan_update(instance, old_values, **kwargs): |
344 | + """When an interfaces VLAN is changed we need to remove all |
345 | + links if the VLAN is different. |
346 | + """ |
347 | + new_vlan_id = instance.vlan_id |
348 | + [old_vlan_id] = old_values |
349 | + if new_vlan_id == old_vlan_id: |
350 | + # Nothing changed do nothing. |
351 | + return |
352 | + if instance.node is None: |
353 | + # Not assigned to a node. Nothing to do. |
354 | + return |
355 | + |
356 | + # Interface VLAN was changed on a machine or device. Remove all its |
357 | + # links except the DISCOVERED ones. |
358 | + instance.ip_addresses.exclude( |
359 | + alloc_type=IPADDRESS_TYPE.DISCOVERED).delete() |
360 | + |
361 | + |
362 | +for klass in INTERFACE_CLASSES: |
363 | + connect_to_field_change( |
364 | + interface_vlan_update, |
365 | + klass, ['vlan_id'], delete=False) |
366 | |
367 | === modified file 'src/maasserver/models/signals/tests/test_interfaces.py' |
368 | --- src/maasserver/models/signals/tests/test_interfaces.py 2015-11-03 12:38:17 +0000 |
369 | +++ src/maasserver/models/signals/tests/test_interfaces.py 2016-04-05 16:02:24 +0000 |
370 | @@ -132,3 +132,49 @@ |
371 | self.assertEquals({ |
372 | 'mtu': bond_mtu, |
373 | }, reload_object(physical3_interface).params) |
374 | + |
375 | + |
376 | +class TestUpdateBondParents(MAASServerTestCase): |
377 | + |
378 | + def test__updates_bond_parents(self): |
379 | + parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
380 | + parent2 = factory.make_Interface( |
381 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
382 | + bond = factory.make_Interface( |
383 | + INTERFACE_TYPE.BOND, parents=[parent1, parent2]) |
384 | + self.assertEqual(bond.vlan, reload_object(parent1).vlan) |
385 | + self.assertEqual(bond.vlan, reload_object(parent2).vlan) |
386 | + |
387 | + def test__update_bond_clears_parent_links(self): |
388 | + parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
389 | + parent2 = factory.make_Interface( |
390 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
391 | + static_ip = factory.make_StaticIPAddress(interface=parent1) |
392 | + factory.make_Interface( |
393 | + INTERFACE_TYPE.BOND, parents=[parent1, parent2]) |
394 | + self.assertIsNone(reload_object(static_ip)) |
395 | + |
396 | + |
397 | +class TestInterfaceVLANUpdate(MAASServerTestCase): |
398 | + |
399 | + scenarios = ( |
400 | + ("node", { |
401 | + "maker": factory.make_Node, |
402 | + }), |
403 | + ("device", { |
404 | + "maker": factory.make_Device, |
405 | + }), |
406 | + ) |
407 | + |
408 | + def test__removes_links(self): |
409 | + node = self.maker() |
410 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
411 | + static_ip = factory.make_StaticIPAddress(interface=interface) |
412 | + discovered_ip = factory.make_StaticIPAddress( |
413 | + alloc_type=IPADDRESS_TYPE.DISCOVERED, interface=interface) |
414 | + new_fabric = factory.make_Fabric() |
415 | + new_vlan = new_fabric.get_default_vlan() |
416 | + interface.vlan = new_vlan |
417 | + interface.save() |
418 | + self.assertIsNone(reload_object(static_ip)) |
419 | + self.assertIsNotNone(reload_object(discovered_ip)) |
420 | |
421 | === modified file 'src/maasserver/models/tests/test_interface.py' |
422 | --- src/maasserver/models/tests/test_interface.py 2015-12-11 00:24:23 +0000 |
423 | +++ src/maasserver/models/tests/test_interface.py 2016-04-05 16:02:24 +0000 |
424 | @@ -230,31 +230,51 @@ |
425 | ["id:%s" % iface1.id, "id:%s" % iface2.id]), [iface1, iface2]) |
426 | |
427 | def test__filter_by_specifiers_matches_vid(self): |
428 | - iface1 = factory.make_Interface() |
429 | - iface2 = factory.make_Interface() |
430 | - self.assertItemsEqual( |
431 | - Interface.objects.filter_by_specifiers( |
432 | - "vid:%s" % iface1.vlan.vid), [iface1]) |
433 | - self.assertItemsEqual( |
434 | - Interface.objects.filter_by_specifiers( |
435 | - "vid:%s" % iface2.vlan.vid), [iface2]) |
436 | - self.assertItemsEqual( |
437 | - Interface.objects.filter_by_specifiers( |
438 | - ["vid:%s" % iface1.vlan.vid, "vid:%s" % iface2.vlan.vid]), |
439 | + fabric1 = factory.make_Fabric() |
440 | + parent1 = factory.make_Interface( |
441 | + INTERFACE_TYPE.PHYSICAL, vlan=fabric1.get_default_vlan()) |
442 | + vlan1 = factory.make_VLAN(fabric=fabric1) |
443 | + iface1 = factory.make_Interface( |
444 | + INTERFACE_TYPE.VLAN, vlan=vlan1, parents=[parent1]) |
445 | + fabric2 = factory.make_Fabric() |
446 | + parent2 = factory.make_Interface( |
447 | + INTERFACE_TYPE.PHYSICAL, vlan=fabric2.get_default_vlan()) |
448 | + vlan2 = factory.make_VLAN(fabric=fabric2) |
449 | + iface2 = factory.make_Interface( |
450 | + INTERFACE_TYPE.VLAN, vlan=vlan2, parents=[parent2]) |
451 | + self.assertItemsEqual( |
452 | + Interface.objects.filter_by_specifiers( |
453 | + "vid:%s" % vlan1.vid), [iface1]) |
454 | + self.assertItemsEqual( |
455 | + Interface.objects.filter_by_specifiers( |
456 | + "vid:%s" % vlan2.vid), [iface2]) |
457 | + self.assertItemsEqual( |
458 | + Interface.objects.filter_by_specifiers( |
459 | + ["vid:%s" % vlan1.vid, "vid:%s" % vlan2.vid]), |
460 | [iface1, iface2]) |
461 | |
462 | def test__filter_by_specifiers_matches_vlan(self): |
463 | - iface1 = factory.make_Interface() |
464 | - iface2 = factory.make_Interface() |
465 | - self.assertItemsEqual( |
466 | - Interface.objects.filter_by_specifiers( |
467 | - "vlan:%s" % iface1.vlan.vid), [iface1]) |
468 | - self.assertItemsEqual( |
469 | - Interface.objects.filter_by_specifiers( |
470 | - "vlan:%s" % iface2.vlan.vid), [iface2]) |
471 | - self.assertItemsEqual( |
472 | - Interface.objects.filter_by_specifiers( |
473 | - ["vlan:%s" % iface1.vlan.vid, "vlan:%s" % iface2.vlan.vid]), |
474 | + fabric1 = factory.make_Fabric() |
475 | + parent1 = factory.make_Interface( |
476 | + INTERFACE_TYPE.PHYSICAL, vlan=fabric1.get_default_vlan()) |
477 | + vlan1 = factory.make_VLAN(fabric=fabric1) |
478 | + iface1 = factory.make_Interface( |
479 | + INTERFACE_TYPE.VLAN, vlan=vlan1, parents=[parent1]) |
480 | + fabric2 = factory.make_Fabric() |
481 | + parent2 = factory.make_Interface( |
482 | + INTERFACE_TYPE.PHYSICAL, vlan=fabric2.get_default_vlan()) |
483 | + vlan2 = factory.make_VLAN(fabric=fabric2) |
484 | + iface2 = factory.make_Interface( |
485 | + INTERFACE_TYPE.VLAN, vlan=vlan2, parents=[parent2]) |
486 | + self.assertItemsEqual( |
487 | + Interface.objects.filter_by_specifiers( |
488 | + "vlan:%s" % vlan1.vid), [iface1]) |
489 | + self.assertItemsEqual( |
490 | + Interface.objects.filter_by_specifiers( |
491 | + "vlan:%s" % vlan2.vid), [iface2]) |
492 | + self.assertItemsEqual( |
493 | + Interface.objects.filter_by_specifiers( |
494 | + ["vlan:%s" % vlan1.vid, "vlan:%s" % vlan2.vid]), |
495 | [iface1, iface2]) |
496 | |
497 | def test__filter_by_specifiers_matches_subnet_specifier(self): |
498 | @@ -1515,7 +1535,7 @@ |
499 | 1, interface.ip_addresses.count(), |
500 | "Should only have one IP address assigned.") |
501 | |
502 | - def test__creates_link_up_to_discovered_subnet(self): |
503 | + def test__creates_link_up_to_discovered_subnet_on_same_vlan(self): |
504 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
505 | subnet = factory.make_Subnet(vlan=interface.vlan) |
506 | factory.make_StaticIPAddress( |
507 | @@ -1527,6 +1547,18 @@ |
508 | self.assertIsNone(link_ip.ip) |
509 | self.assertEquals(subnet, link_ip.subnet) |
510 | |
511 | + def test__creates_link_up_to_no_subnet_when_on_different_vlan(self): |
512 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
513 | + subnet = factory.make_Subnet() |
514 | + factory.make_StaticIPAddress( |
515 | + alloc_type=IPADDRESS_TYPE.DISCOVERED, ip="", |
516 | + subnet=subnet, interface=interface) |
517 | + interface.ensure_link_up() |
518 | + link_ip = interface.ip_addresses.filter( |
519 | + alloc_type=IPADDRESS_TYPE.STICKY).first() |
520 | + self.assertIsNone(link_ip.ip) |
521 | + self.assertIsNone(link_ip.subnet) |
522 | + |
523 | def test__creates_link_up_to_no_subnet(self): |
524 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
525 | interface.ensure_link_up() |
526 | |
527 | === modified file 'src/maasserver/models/tests/test_staticipaddress.py' |
528 | --- src/maasserver/models/tests/test_staticipaddress.py 2015-11-17 00:33:35 +0000 |
529 | +++ src/maasserver/models/tests/test_staticipaddress.py 2016-04-05 16:02:24 +0000 |
530 | @@ -805,6 +805,7 @@ |
531 | |
532 | def test_get_hostname_ip_mapping_prefers_bond_with_no_boot_interface(self): |
533 | self.patch_autospec(interface_module, "update_host_maps") |
534 | + self.patch_autospec(interface_module, "remove_host_maps") |
535 | subnet = factory.make_Subnet( |
536 | cidr=unicode(factory.make_ipv4_network().cidr)) |
537 | node = factory.make_Node_with_Interface_on_Subnet( |
538 | @@ -835,6 +836,7 @@ |
539 | |
540 | def test_get_hostname_ip_mapping_prefers_bond_with_boot_interface(self): |
541 | self.patch_autospec(interface_module, "update_host_maps") |
542 | + self.patch_autospec(interface_module, "remove_host_maps") |
543 | subnet = factory.make_Subnet( |
544 | cidr=unicode(factory.make_ipv4_network().cidr)) |
545 | node = factory.make_Node_with_Interface_on_Subnet( |
546 | @@ -859,6 +861,7 @@ |
547 | |
548 | def test_get_hostname_ip_mapping_ignores_bond_without_boot_interface(self): |
549 | self.patch_autospec(interface_module, "update_host_maps") |
550 | + self.patch_autospec(interface_module, "remove_host_maps") |
551 | subnet = factory.make_Subnet( |
552 | cidr=unicode(factory.make_ipv4_network().cidr)) |
553 | node = factory.make_Node_with_Interface_on_Subnet( |
554 | |
555 | === modified file 'src/maasserver/testing/factory.py' |
556 | --- src/maasserver/testing/factory.py 2015-12-11 00:24:23 +0000 |
557 | +++ src/maasserver/testing/factory.py 2016-04-05 16:02:24 +0000 |
558 | @@ -335,7 +335,6 @@ |
559 | acquired = node.status in ALLOCATED_NODE_STATUSES |
560 | self.make_Filesystem( |
561 | partition=root_partition, mount_point='/', acquired=acquired) |
562 | - |
563 | # Update the 'updated'/'created' fields with a call to 'update' |
564 | # preventing a call to save() from overriding the values. |
565 | if updated is not None: |
566 | @@ -591,7 +590,9 @@ |
567 | node = self.make_Node( |
568 | nodegroup=nodegroup, fabric=fabric, **kwargs) |
569 | if vlan is None: |
570 | - vlan = self.make_VLAN(fabric=fabric) |
571 | + if fabric is None: |
572 | + fabric = factory.make_Fabric() |
573 | + vlan = fabric.get_default_vlan() |
574 | if subnet is None: |
575 | subnet = self.make_Subnet(vlan=vlan, cidr=cidr) |
576 | # Check if the subnet already has a managed interface. |
577 | @@ -630,7 +631,7 @@ |
578 | self.make_StaticIPAddress( |
579 | alloc_type=IPADDRESS_TYPE.STICKY, ip="", |
580 | subnet=subnet, interface=interface) |
581 | - return node |
582 | + return reload_object(node) |
583 | |
584 | UNDEFINED = float('NaN') |
585 | |
586 | @@ -786,7 +787,19 @@ |
587 | if iftype is None: |
588 | iftype = INTERFACE_TYPE.PHYSICAL |
589 | if vlan is None: |
590 | - vlan = self.make_VLAN(fabric=fabric) |
591 | + if fabric is not None: |
592 | + if iftype == INTERFACE_TYPE.VLAN: |
593 | + vlan = self.make_VLAN(fabric=fabric) |
594 | + else: |
595 | + vlan = fabric.get_default_vlan() |
596 | + else: |
597 | + if iftype == INTERFACE_TYPE.VLAN and parents: |
598 | + vlan = self.make_VLAN(fabric=parents[0].vlan.fabric) |
599 | + elif iftype == INTERFACE_TYPE.BOND and parents: |
600 | + vlan = parents[0].vlan |
601 | + else: |
602 | + fabric = self.make_Fabric() |
603 | + vlan = fabric.get_default_vlan() |
604 | if (mac_address is None and |
605 | iftype in [ |
606 | INTERFACE_TYPE.PHYSICAL, |
607 | |
608 | === modified file 'src/maasserver/tests/test_forms_interface.py' |
609 | --- src/maasserver/tests/test_forms_interface.py 2015-10-31 23:55:19 +0000 |
610 | +++ src/maasserver/tests/test_forms_interface.py 2016-04-05 16:02:24 +0000 |
611 | @@ -32,6 +32,7 @@ |
612 | ) |
613 | from maasserver.models.interface import build_vlan_interface_name |
614 | from maasserver.testing.factory import factory |
615 | +from maasserver.testing.orm import reload_object |
616 | from maasserver.testing.testcase import MAASServerTestCase |
617 | from maasserver.utils.forms import compose_invalid_choice_text |
618 | from testtools import ExpectedException |
619 | @@ -67,7 +68,8 @@ |
620 | node = factory.make_Node() |
621 | mac_address = factory.make_mac_address() |
622 | interface_name = 'eth0' |
623 | - vlan = factory.make_VLAN() |
624 | + fabric = factory.make_Fabric() |
625 | + vlan = fabric.get_default_vlan() |
626 | tags = [ |
627 | factory.make_name("tag") |
628 | for _ in range(3) |
629 | @@ -93,7 +95,8 @@ |
630 | node = factory.make_Node() |
631 | mac_address = factory.make_mac_address() |
632 | interface_name = 'eth0' |
633 | - vlan = factory.make_VLAN() |
634 | + fabric = factory.make_Fabric() |
635 | + vlan = fabric.get_default_vlan() |
636 | tags = [ |
637 | factory.make_name("tag") |
638 | for _ in range(3) |
639 | @@ -113,7 +116,8 @@ |
640 | |
641 | def test__requires_mac_address(self): |
642 | interface_name = 'eth0' |
643 | - vlan = factory.make_VLAN() |
644 | + fabric = factory.make_Fabric() |
645 | + vlan = fabric.get_default_vlan() |
646 | form = PhysicalInterfaceForm( |
647 | node=factory.make_Node(), |
648 | data={ |
649 | @@ -128,7 +132,9 @@ |
650 | form.errors['mac_address'][0]) |
651 | |
652 | def test_rejects_interface_with_duplicate_name(self): |
653 | - interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
654 | + fabric = factory.make_Fabric() |
655 | + vlan = fabric.get_default_vlan() |
656 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, vlan=vlan) |
657 | mac_address = factory.make_mac_address() |
658 | form = PhysicalInterfaceForm( |
659 | node=interface.node, |
660 | @@ -144,9 +150,30 @@ |
661 | "already has an interface named '%s'." % interface.name, |
662 | form.errors['name'][0]) |
663 | |
664 | + def test_rejects_interface_on_tagged_vlan(self): |
665 | + fabric = factory.make_Fabric() |
666 | + interface = factory.make_Interface( |
667 | + INTERFACE_TYPE.PHYSICAL, vlan=fabric.get_default_vlan()) |
668 | + vlan = factory.make_VLAN(fabric=fabric) |
669 | + mac_address = factory.make_mac_address() |
670 | + form = PhysicalInterfaceForm( |
671 | + node=interface.node, |
672 | + data={ |
673 | + 'name': factory.make_name("eth"), |
674 | + 'mac_address': mac_address, |
675 | + 'vlan': vlan.id, |
676 | + }) |
677 | + self.assertFalse(form.is_valid(), form.errors) |
678 | + self.assertItemsEqual( |
679 | + ['vlan'], form.errors.keys(), form.errors) |
680 | + self.assertIn( |
681 | + "A physical interface can only belong to an untagged VLAN.", |
682 | + form.errors['vlan'][0]) |
683 | + |
684 | def test__rejects_parents(self): |
685 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
686 | - vlan = factory.make_VLAN() |
687 | + fabric = factory.make_Fabric() |
688 | + vlan = fabric.get_default_vlan() |
689 | form = PhysicalInterfaceForm( |
690 | node=parent.node, |
691 | data={ |
692 | @@ -166,7 +193,8 @@ |
693 | interface = factory.make_Interface( |
694 | INTERFACE_TYPE.PHYSICAL, name='eth0') |
695 | new_name = 'eth1' |
696 | - new_vlan = factory.make_VLAN(vid=33) |
697 | + new_fabric = factory.make_Fabric() |
698 | + new_vlan = new_fabric.get_default_vlan() |
699 | form = PhysicalInterfaceForm( |
700 | instance=interface, |
701 | data={ |
702 | @@ -187,7 +215,8 @@ |
703 | node = factory.make_Node() |
704 | mac_address = factory.make_mac_address() |
705 | interface_name = 'eth0' |
706 | - vlan = factory.make_VLAN() |
707 | + fabric = factory.make_Fabric() |
708 | + vlan = fabric.get_default_vlan() |
709 | tags = [ |
710 | factory.make_name("tag") |
711 | for _ in range(3) |
712 | @@ -226,7 +255,8 @@ |
713 | "autoconf": autoconf, |
714 | } |
715 | new_name = 'eth1' |
716 | - new_vlan = factory.make_VLAN(vid=33) |
717 | + new_fabric = factory.make_Fabric() |
718 | + new_vlan = new_fabric.get_default_vlan() |
719 | form = PhysicalInterfaceForm( |
720 | instance=interface, |
721 | data={ |
722 | @@ -299,7 +329,7 @@ |
723 | |
724 | def test__creates_vlan_interface(self): |
725 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
726 | - vlan = factory.make_VLAN(vid=10) |
727 | + vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
728 | form = VLANInterfaceForm( |
729 | node=parent.node, |
730 | data={ |
731 | @@ -317,7 +347,7 @@ |
732 | |
733 | def test__create_ensures_link_up(self): |
734 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
735 | - vlan = factory.make_VLAN(vid=10) |
736 | + vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
737 | form = VLANInterfaceForm( |
738 | node=parent.node, |
739 | data={ |
740 | @@ -330,13 +360,10 @@ |
741 | interface.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.STICKY)) |
742 | |
743 | def test_rejects_interface_with_duplicate_name(self): |
744 | - vlan = factory.make_VLAN(vid=10) |
745 | - parent = factory.make_Interface( |
746 | - INTERFACE_TYPE.PHYSICAL, |
747 | - vlan=vlan) |
748 | + parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
749 | + vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
750 | interface = factory.make_Interface( |
751 | - INTERFACE_TYPE.VLAN, |
752 | - vlan=vlan, parents=[parent]) |
753 | + INTERFACE_TYPE.VLAN, vlan=vlan, parents=[parent]) |
754 | form = VLANInterfaceForm( |
755 | node=parent.node, |
756 | data={ |
757 | @@ -350,6 +377,22 @@ |
758 | "already has an interface named '%s'." % interface.name, |
759 | form.errors['name'][0]) |
760 | |
761 | + def test_rejects_interface_on_default_fabric(self): |
762 | + parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
763 | + vlan = parent.vlan.fabric.get_default_vlan() |
764 | + form = VLANInterfaceForm( |
765 | + node=parent.node, |
766 | + data={ |
767 | + 'vlan': vlan.id, |
768 | + 'parents': [parent.id], |
769 | + }) |
770 | + self.assertFalse(form.is_valid(), form.errors) |
771 | + self.assertItemsEqual( |
772 | + ['vlan'], form.errors.keys(), form.errors) |
773 | + self.assertIn( |
774 | + "A VLAN interface can only belong to a tagged VLAN.", |
775 | + form.errors['vlan'][0]) |
776 | + |
777 | def test__rejects_no_parents(self): |
778 | vlan = factory.make_VLAN(vid=10) |
779 | form = VLANInterfaceForm( |
780 | @@ -365,13 +408,14 @@ |
781 | |
782 | def test__rejects_vlan_parent(self): |
783 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
784 | + vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
785 | vlan_parent = factory.make_Interface( |
786 | - INTERFACE_TYPE.VLAN, parents=[parent]) |
787 | - vlan = factory.make_VLAN(vid=10) |
788 | + INTERFACE_TYPE.VLAN, vlan=vlan, parents=[parent]) |
789 | + other_vlan = factory.make_VLAN(fabric=parent.vlan.fabric, vid=11) |
790 | form = VLANInterfaceForm( |
791 | node=parent.node, |
792 | data={ |
793 | - 'vlan': vlan.id, |
794 | + 'vlan': other_vlan.id, |
795 | 'parents': [vlan_parent.id], |
796 | }) |
797 | self.assertFalse(form.is_valid(), form.errors) |
798 | @@ -380,10 +424,27 @@ |
799 | "VLAN interface can't have another VLAN interface as parent.", |
800 | form.errors['parents'][0]) |
801 | |
802 | + def test__rejects_vlan_not_on_same_fabric(self): |
803 | + parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
804 | + factory.make_VLAN(fabric=parent.vlan.fabric, vid=10) |
805 | + other_vlan = factory.make_VLAN() |
806 | + form = VLANInterfaceForm( |
807 | + node=parent.node, |
808 | + data={ |
809 | + 'vlan': other_vlan.id, |
810 | + 'parents': [parent.id], |
811 | + }) |
812 | + self.assertFalse(form.is_valid(), form.errors) |
813 | + self.assertItemsEqual(['vlan'], form.errors.keys()) |
814 | + self.assertIn( |
815 | + "A VLAN interface can only belong to a tagged VLAN on " |
816 | + "the same fabric as its parent interface.", |
817 | + form.errors['vlan'][0]) |
818 | + |
819 | def test__rejects_parent_on_bond(self): |
820 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
821 | - factory.make_Interface(INTERFACE_TYPE.BOND, parents=[parent]) |
822 | - vlan = factory.make_VLAN(vid=10) |
823 | + bond = factory.make_Interface(INTERFACE_TYPE.BOND, parents=[parent]) |
824 | + vlan = factory.make_VLAN(fabric=bond.vlan.fabric, vid=10) |
825 | form = VLANInterfaceForm( |
826 | node=parent.node, |
827 | data={ |
828 | @@ -417,7 +478,7 @@ |
829 | parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
830 | interface = factory.make_Interface( |
831 | INTERFACE_TYPE.VLAN, parents=[parent]) |
832 | - new_vlan = factory.make_VLAN(vid=33) |
833 | + new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric, vid=33) |
834 | form = VLANInterfaceForm( |
835 | instance=interface, |
836 | data={ |
837 | @@ -438,15 +499,13 @@ |
838 | def test__error_with_invalid_bond_mode(self): |
839 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
840 | parent2 = factory.make_Interface( |
841 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
842 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
843 | interface_name = factory.make_name() |
844 | - vlan = factory.make_VLAN(vid=10) |
845 | bond_mode = factory.make_name("bond_mode") |
846 | form = BondInterfaceForm( |
847 | node=parent1.node, |
848 | data={ |
849 | 'name': interface_name, |
850 | - 'vlan': vlan.id, |
851 | 'parents': [parent1.id, parent2.id], |
852 | 'bond_mode': bond_mode, |
853 | }) |
854 | @@ -460,14 +519,12 @@ |
855 | def test__creates_bond_interface(self): |
856 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
857 | parent2 = factory.make_Interface( |
858 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
859 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
860 | interface_name = factory.make_name() |
861 | - vlan = factory.make_VLAN(vid=10) |
862 | form = BondInterfaceForm( |
863 | node=parent1.node, |
864 | data={ |
865 | 'name': interface_name, |
866 | - 'vlan': vlan.id, |
867 | 'parents': [parent1.id, parent2.id], |
868 | }) |
869 | self.assertTrue(form.is_valid(), form.errors) |
870 | @@ -484,15 +541,13 @@ |
871 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
872 | parent1.ensure_link_up() |
873 | parent2 = factory.make_Interface( |
874 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
875 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
876 | parent2.ensure_link_up() |
877 | interface_name = factory.make_name() |
878 | - vlan = factory.make_VLAN(vid=10) |
879 | form = BondInterfaceForm( |
880 | node=parent1.node, |
881 | data={ |
882 | 'name': interface_name, |
883 | - 'vlan': vlan.id, |
884 | 'parents': [parent1.id, parent2.id], |
885 | }) |
886 | self.assertTrue(form.is_valid(), form.errors) |
887 | @@ -511,14 +566,12 @@ |
888 | def test__creates_bond_interface_with_parent_mac_address(self): |
889 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
890 | parent2 = factory.make_Interface( |
891 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
892 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
893 | interface_name = factory.make_name() |
894 | - vlan = factory.make_VLAN(vid=10) |
895 | form = BondInterfaceForm( |
896 | node=parent1.node, |
897 | data={ |
898 | 'name': interface_name, |
899 | - 'vlan': vlan.id, |
900 | 'parents': [parent1.id, parent2.id], |
901 | 'mac_address': parent1.mac_address, |
902 | }) |
903 | @@ -534,14 +587,12 @@ |
904 | def test__creates_bond_interface_with_default_bond_params(self): |
905 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
906 | parent2 = factory.make_Interface( |
907 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
908 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
909 | interface_name = factory.make_name() |
910 | - vlan = factory.make_VLAN(vid=10) |
911 | form = BondInterfaceForm( |
912 | node=parent1.node, |
913 | data={ |
914 | 'name': interface_name, |
915 | - 'vlan': vlan.id, |
916 | 'parents': [parent1.id, parent2.id], |
917 | }) |
918 | self.assertTrue(form.is_valid(), form.errors) |
919 | @@ -558,9 +609,8 @@ |
920 | def test__creates_bond_interface_with_bond_params(self): |
921 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
922 | parent2 = factory.make_Interface( |
923 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
924 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
925 | interface_name = factory.make_name() |
926 | - vlan = factory.make_VLAN(vid=10) |
927 | bond_mode = factory.pick_choice(BOND_MODE_CHOICES) |
928 | bond_miimon = random.randint(0, 1000) |
929 | bond_downdelay = random.randint(0, 1000) |
930 | @@ -572,7 +622,6 @@ |
931 | node=parent1.node, |
932 | data={ |
933 | 'name': interface_name, |
934 | - 'vlan': vlan.id, |
935 | 'parents': [parent1.id, parent2.id], |
936 | 'bond_mode': bond_mode, |
937 | 'bond_miimon': bond_miimon, |
938 | @@ -593,13 +642,11 @@ |
939 | }, interface.params) |
940 | |
941 | def test__rejects_no_parents(self): |
942 | - vlan = factory.make_VLAN(vid=10) |
943 | interface_name = factory.make_name() |
944 | form = BondInterfaceForm( |
945 | node=factory.make_Node(), |
946 | data={ |
947 | 'name': interface_name, |
948 | - 'vlan': vlan.id, |
949 | }) |
950 | self.assertFalse(form.is_valid(), form.errors) |
951 | self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys()) |
952 | @@ -607,21 +654,37 @@ |
953 | "A Bond interface must have one or more parents.", |
954 | form.errors['parents'][0]) |
955 | |
956 | + def test__rejects_when_vlan_not_untagged(self): |
957 | + interface_name = factory.make_name() |
958 | + parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
959 | + vlan = factory.make_VLAN(fabric=parent.vlan.fabric) |
960 | + form = BondInterfaceForm( |
961 | + node=parent.node, |
962 | + data={ |
963 | + 'name': interface_name, |
964 | + 'parents': [parent.id], |
965 | + 'mac_address': parent.mac_address, |
966 | + 'vlan': vlan.id, |
967 | + }) |
968 | + self.assertFalse(form.is_valid(), form.errors) |
969 | + self.assertItemsEqual(['vlan'], form.errors.keys()) |
970 | + self.assertIn( |
971 | + "A bond interface can only belong to an untagged VLAN.", |
972 | + form.errors['vlan'][0]) |
973 | + |
974 | def test__rejects_when_parents_already_have_children(self): |
975 | node = factory.make_Node() |
976 | parent1 = factory.make_Interface( |
977 | INTERFACE_TYPE.PHYSICAL, node=node, name="eth0") |
978 | factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent1]) |
979 | parent2 = factory.make_Interface( |
980 | - INTERFACE_TYPE.PHYSICAL, node=node, name="eth1") |
981 | + INTERFACE_TYPE.PHYSICAL, node=node, name="eth1", vlan=parent1.vlan) |
982 | factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent2]) |
983 | - vlan = factory.make_VLAN(vid=10) |
984 | interface_name = factory.make_name() |
985 | form = BondInterfaceForm( |
986 | node=node, |
987 | data={ |
988 | 'name': interface_name, |
989 | - 'vlan': vlan.id, |
990 | 'parents': [parent1.id, parent2.id] |
991 | }) |
992 | self.assertFalse(form.is_valid(), form.errors) |
993 | @@ -629,17 +692,36 @@ |
994 | "eth0, eth1 is already in-use by another interface.", |
995 | form.errors['parents'][0]) |
996 | |
997 | + def test__rejects_when_parents_not_in_same_vlan(self): |
998 | + node = factory.make_Node() |
999 | + parent1 = factory.make_Interface( |
1000 | + INTERFACE_TYPE.PHYSICAL, node=node, name="eth0") |
1001 | + parent2 = factory.make_Interface( |
1002 | + INTERFACE_TYPE.PHYSICAL, node=node, name="eth1") |
1003 | + interface_name = factory.make_name() |
1004 | + form = BondInterfaceForm( |
1005 | + node=node, |
1006 | + data={ |
1007 | + 'name': interface_name, |
1008 | + 'parents': [parent1.id, parent2.id] |
1009 | + }) |
1010 | + self.assertFalse(form.is_valid(), form.errors) |
1011 | + self.assertEquals( |
1012 | + "All parents must belong to the same VLAN.", |
1013 | + form.errors['parents'][0]) |
1014 | + |
1015 | def test__edits_interface(self): |
1016 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1017 | parent2 = factory.make_Interface( |
1018 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
1019 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1020 | interface = factory.make_Interface( |
1021 | INTERFACE_TYPE.BOND, |
1022 | parents=[parent1, parent2]) |
1023 | - new_vlan = factory.make_VLAN(vid=33) |
1024 | + new_fabric = factory.make_Fabric() |
1025 | + new_vlan = new_fabric.get_default_vlan() |
1026 | new_name = factory.make_name() |
1027 | new_parent = factory.make_Interface( |
1028 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
1029 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1030 | form = BondInterfaceForm( |
1031 | instance=interface, |
1032 | data={ |
1033 | @@ -656,6 +738,10 @@ |
1034 | vlan=new_vlan, type=INTERFACE_TYPE.BOND)) |
1035 | self.assertItemsEqual( |
1036 | [parent1, parent2, new_parent], interface.parents.all()) |
1037 | + self.assertItemsEqual([new_vlan], set( |
1038 | + reload_object(parent).vlan |
1039 | + for parent in [parent1, parent2, new_parent] |
1040 | + )) |
1041 | |
1042 | def test__edits_interface_removes_parents(self): |
1043 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1044 | @@ -713,7 +799,7 @@ |
1045 | def test__edit_doesnt_overwrite_params(self): |
1046 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1047 | parent2 = factory.make_Interface( |
1048 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
1049 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1050 | interface = factory.make_Interface( |
1051 | INTERFACE_TYPE.BOND, |
1052 | parents=[parent1, parent2]) |
1053 | @@ -733,12 +819,10 @@ |
1054 | "bond_xmit_hash_policy": bond_xmit_hash_policy, |
1055 | } |
1056 | interface.save() |
1057 | - new_vlan = factory.make_VLAN(vid=33) |
1058 | new_name = factory.make_name() |
1059 | form = BondInterfaceForm( |
1060 | instance=interface, |
1061 | data={ |
1062 | - 'vlan': new_vlan.id, |
1063 | 'name': new_name, |
1064 | }) |
1065 | self.assertTrue(form.is_valid(), form.errors) |
1066 | @@ -755,7 +839,7 @@ |
1067 | def test__edit_does_overwrite_params(self): |
1068 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1069 | parent2 = factory.make_Interface( |
1070 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
1071 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1072 | interface = factory.make_Interface( |
1073 | INTERFACE_TYPE.BOND, |
1074 | parents=[parent1, parent2]) |
1075 | @@ -775,7 +859,6 @@ |
1076 | "bond_xmit_hash_policy": bond_xmit_hash_policy, |
1077 | } |
1078 | interface.save() |
1079 | - new_vlan = factory.make_VLAN(vid=33) |
1080 | new_name = factory.make_name() |
1081 | new_bond_mode = factory.pick_choice(BOND_MODE_CHOICES) |
1082 | new_bond_miimon = random.randint(0, 1000) |
1083 | @@ -787,7 +870,6 @@ |
1084 | form = BondInterfaceForm( |
1085 | instance=interface, |
1086 | data={ |
1087 | - 'vlan': new_vlan.id, |
1088 | 'name': new_name, |
1089 | 'bond_mode': new_bond_mode, |
1090 | 'bond_miimon': new_bond_miimon, |
1091 | @@ -810,7 +892,7 @@ |
1092 | def test__edit_allows_zero_params(self): |
1093 | parent1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) |
1094 | parent2 = factory.make_Interface( |
1095 | - INTERFACE_TYPE.PHYSICAL, node=parent1.node) |
1096 | + INTERFACE_TYPE.PHYSICAL, node=parent1.node, vlan=parent1.vlan) |
1097 | interface = factory.make_Interface( |
1098 | INTERFACE_TYPE.BOND, |
1099 | parents=[parent1, parent2]) |
1100 | @@ -830,7 +912,6 @@ |
1101 | "bond_xmit_hash_policy": bond_xmit_hash_policy, |
1102 | } |
1103 | interface.save() |
1104 | - new_vlan = factory.make_VLAN(vid=33) |
1105 | new_name = factory.make_name() |
1106 | new_bond_mode = factory.pick_choice(BOND_MODE_CHOICES) |
1107 | new_bond_miimon = 0 |
1108 | @@ -842,7 +923,6 @@ |
1109 | form = BondInterfaceForm( |
1110 | instance=interface, |
1111 | data={ |
1112 | - 'vlan': new_vlan.id, |
1113 | 'name': new_name, |
1114 | 'bond_mode': new_bond_mode, |
1115 | 'bond_miimon': new_bond_miimon, |
1116 | |
1117 | === modified file 'src/maasserver/websockets/handlers/tests/test_node.py' |
1118 | --- src/maasserver/websockets/handlers/tests/test_node.py 2015-11-18 18:14:30 +0000 |
1119 | +++ src/maasserver/websockets/handlers/tests/test_node.py 2016-04-05 16:02:24 +0000 |
1120 | @@ -1912,7 +1912,8 @@ |
1121 | handler = NodeHandler(user, {}) |
1122 | name = factory.make_name("eth") |
1123 | mac_address = factory.make_mac_address() |
1124 | - vlan = factory.make_VLAN() |
1125 | + fabric = factory.make_Fabric() |
1126 | + vlan = fabric.get_default_vlan() |
1127 | handler.create_physical({ |
1128 | "system_id": node.system_id, |
1129 | "name": name, |
1130 | @@ -1929,7 +1930,8 @@ |
1131 | handler = NodeHandler(user, {}) |
1132 | name = factory.make_name("eth") |
1133 | mac_address = factory.make_mac_address() |
1134 | - vlan = factory.make_VLAN() |
1135 | + fabric = factory.make_Fabric() |
1136 | + vlan = fabric.get_default_vlan() |
1137 | subnet = factory.make_Subnet(vlan=vlan) |
1138 | handler.create_physical({ |
1139 | "system_id": node.system_id, |
1140 | @@ -1951,7 +1953,8 @@ |
1141 | handler = NodeHandler(user, {}) |
1142 | name = factory.make_name("eth") |
1143 | mac_address = factory.make_mac_address() |
1144 | - vlan = factory.make_VLAN() |
1145 | + fabric = factory.make_Fabric() |
1146 | + vlan = fabric.get_default_vlan() |
1147 | handler.create_physical({ |
1148 | "system_id": node.system_id, |
1149 | "name": name, |
1150 | @@ -1971,7 +1974,8 @@ |
1151 | handler = NodeHandler(user, {}) |
1152 | name = factory.make_name("eth") |
1153 | mac_address = factory.make_mac_address() |
1154 | - vlan = factory.make_VLAN() |
1155 | + fabric = factory.make_Fabric() |
1156 | + vlan = fabric.get_default_vlan() |
1157 | subnet = factory.make_Subnet(vlan=vlan) |
1158 | handler.create_physical({ |
1159 | "system_id": node.system_id, |
1160 | @@ -1992,7 +1996,7 @@ |
1161 | node = factory.make_Node() |
1162 | handler = NodeHandler(user, {}) |
1163 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1164 | - new_vlan = factory.make_VLAN() |
1165 | + new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1166 | handler.create_vlan({ |
1167 | "system_id": node.system_id, |
1168 | "parent": interface.id, |
1169 | @@ -2008,7 +2012,7 @@ |
1170 | node = factory.make_Node() |
1171 | handler = NodeHandler(user, {}) |
1172 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1173 | - new_vlan = factory.make_VLAN() |
1174 | + new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1175 | new_subnet = factory.make_Subnet(vlan=new_vlan) |
1176 | handler.create_vlan({ |
1177 | "system_id": node.system_id, |
1178 | @@ -2030,7 +2034,7 @@ |
1179 | node = factory.make_Node() |
1180 | handler = NodeHandler(user, {}) |
1181 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1182 | - new_vlan = factory.make_VLAN() |
1183 | + new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1184 | handler.create_vlan({ |
1185 | "system_id": node.system_id, |
1186 | "parent": interface.id, |
1187 | @@ -2050,7 +2054,7 @@ |
1188 | node = factory.make_Node() |
1189 | handler = NodeHandler(user, {}) |
1190 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1191 | - new_vlan = factory.make_VLAN() |
1192 | + new_vlan = factory.make_VLAN(fabric=interface.vlan.fabric) |
1193 | new_subnet = factory.make_Subnet(vlan=new_vlan) |
1194 | handler.create_vlan({ |
1195 | "system_id": node.system_id, |
1196 | @@ -2110,7 +2114,8 @@ |
1197 | handler = NodeHandler(user, {}) |
1198 | interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
1199 | new_name = factory.make_name("name") |
1200 | - new_vlan = factory.make_VLAN() |
1201 | + new_fabric = factory.make_Fabric() |
1202 | + new_vlan = new_fabric.get_default_vlan() |
1203 | handler.update_interface({ |
1204 | "system_id": node.system_id, |
1205 | "interface_id": interface.id, |
Self-approving backport.