Merge lp:~mpontillo/maas/relax-bridge-restrictions--bug-1661203 into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 5937
Proposed branch: lp:~mpontillo/maas/relax-bridge-restrictions--bug-1661203
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 784 lines (+169/-92)
2 files modified
src/maasserver/forms/interface.py (+28/-11)
src/maasserver/forms/tests/test_interface.py (+141/-81)
To merge this branch: bzr merge lp:~mpontillo/maas/relax-bridge-restrictions--bug-1661203
Reviewer Review Type Date Requested Status
LaMont Jones (community) Approve
Review via email: mp+322066@code.launchpad.net

Commit message

Allow creating bridges whose parents already have VLANs.

Drive-by fix to use dict(form.errors) so that test failures produce nicer errors.

To post a comment you must log in.
Revision history for this message
LaMont Jones (lamont) wrote :

LGTM.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/forms/interface.py'
--- src/maasserver/forms/interface.py 2017-02-28 11:37:12 +0000
+++ src/maasserver/forms/interface.py 2017-04-05 23:39:39 +0000
@@ -397,21 +397,25 @@
397 vlan = parents[0].vlan397 vlan = parents[0].vlan
398 self.cleaned_data['vlan'] = vlan398 self.cleaned_data['vlan'] = vlan
399399
400 def _validate_parental_fidelity(self, parents):400 def get_delinquent_children(self, parents):
401 """Returns either an empty set, or a set of children whose presence
402 would deter the parent from adopting this new child."""
403 return {
404 parent.name for parent in parents
405 for rel in parent.children_relationships.all()
406 if rel.child.id != self.instance.id
407 }
408
409 def validate_parental_fidelity(self, parents):
401 """Check that all of the parent interfaces are not already in a410 """Check that all of the parent interfaces are not already in a
402 relationship before committing them to this child.411 relationship before committing them to this child.
403 """412 """
404 parents_with_other_children = {413 dilinquents = self.get_delinquent_children(parents)
405 parent.name414 if len(dilinquents) != 0:
406 for parent in parents
407 for rel in parent.children_relationships.all()
408 if rel.child.id != self.instance.id
409 }
410 if parents_with_other_children:
411 set_form_error(415 set_form_error(
412 self, 'parents',416 self, 'parents',
413 "Interfaces already in-use: %s." % (417 "Interfaces already in-use: %s." % (
414 ', '.join(sorted(parents_with_other_children))))418 ', '.join(sorted(dilinquents))))
415419
416420
417class BondInterfaceForm(ChildInterfaceForm):421class BondInterfaceForm(ChildInterfaceForm):
@@ -466,7 +470,7 @@
466 # created.470 # created.
467 if parents:471 if parents:
468 self._set_default_child_mac(parents)472 self._set_default_child_mac(parents)
469 self._validate_parental_fidelity(parents)473 self.validate_parental_fidelity(parents)
470 self._set_default_vlan(parents)474 self._set_default_vlan(parents)
471 self._validate_parent_vlans_match(parents)475 self._validate_parent_vlans_match(parents)
472 return cleaned_data476 return cleaned_data
@@ -544,6 +548,19 @@
544 "in a bond or a bridge.")548 "in a bond or a bridge.")
545 return parents549 return parents
546550
551 def get_delinquent_children(self, parents):
552 """Returns a set of children who would prevent the creation of this
553 bridge interface. The only difference between this method and the
554 method it overrides is that it allows VLAN interface children, whom
555 bridges may get along with.
556 """
557 return {
558 parent.name for parent in parents
559 for rel in parent.children_relationships.all()
560 if (rel.child.id != self.instance.id and
561 rel.child.type != INTERFACE_TYPE.VLAN)
562 }
563
547 def clean(self):564 def clean(self):
548 cleaned_data = super().clean()565 cleaned_data = super().clean()
549 if self.fields_ok(['vlan', 'parents']):566 if self.fields_ok(['vlan', 'parents']):
@@ -552,7 +569,7 @@
552 # created.569 # created.
553 if parents:570 if parents:
554 self._set_default_child_mac(parents)571 self._set_default_child_mac(parents)
555 self._validate_parental_fidelity(parents)572 self.validate_parental_fidelity(parents)
556 self._set_default_vlan(parents)573 self._set_default_vlan(parents)
557 return cleaned_data574 return cleaned_data
558575
559576
=== modified file 'src/maasserver/forms/tests/test_interface.py'
--- src/maasserver/forms/tests/test_interface.py 2017-03-29 11:11:35 +0000
+++ src/maasserver/forms/tests/test_interface.py 2017-04-05 23:39:39 +0000
@@ -82,7 +82,7 @@
82 data={82 data={
83 'vlan': new_vlan.id,83 'vlan': new_vlan.id,
84 })84 })
85 self.assertTrue(form.is_valid(), form.errors)85 self.assertTrue(form.is_valid(), dict(form.errors))
86 interface = form.save()86 interface = form.save()
87 self.assertThat(87 self.assertThat(
88 interface,88 interface,
@@ -98,7 +98,7 @@
98 data={98 data={
99 'vlan': None,99 'vlan': None,
100 })100 })
101 self.assertTrue(form.is_valid(), form.errors)101 self.assertTrue(form.is_valid(), dict(form.errors))
102 interface = form.save()102 interface = form.save()
103 self.assertThat(103 self.assertThat(
104 interface,104 interface,
@@ -119,7 +119,7 @@
119 'name': new_name,119 'name': new_name,
120 'mac_address': new_mac,120 'mac_address': new_mac,
121 })121 })
122 self.assertTrue(form.is_valid(), form.errors)122 self.assertTrue(form.is_valid(), dict(form.errors))
123 interface = form.save()123 interface = form.save()
124 self.assertThat(124 self.assertThat(
125 interface,125 interface,
@@ -147,7 +147,7 @@
147 'vlan': vlan.id,147 'vlan': vlan.id,
148 'tags': ",".join(tags),148 'tags': ",".join(tags),
149 })149 })
150 self.assertTrue(form.is_valid(), form.errors)150 self.assertTrue(form.is_valid(), dict(form.errors))
151 interface = form.save()151 interface = form.save()
152 self.assertThat(152 self.assertThat(
153 interface,153 interface,
@@ -174,7 +174,7 @@
174 'vlan': vlan.id,174 'vlan': vlan.id,
175 'tags': ",".join(tags),175 'tags': ",".join(tags),
176 })176 })
177 self.assertTrue(form.is_valid(), form.errors)177 self.assertTrue(form.is_valid(), dict(form.errors))
178 interface = form.save()178 interface = form.save()
179 self.assertThat(179 self.assertThat(
180 interface,180 interface,
@@ -198,7 +198,7 @@
198 'mac_address': mac_address,198 'mac_address': mac_address,
199 'tags': ",".join(tags),199 'tags': ",".join(tags),
200 })200 })
201 self.assertTrue(form.is_valid(), form.errors)201 self.assertTrue(form.is_valid(), dict(form.errors))
202 interface = form.save()202 interface = form.save()
203 self.assertThat(203 self.assertThat(
204 interface,204 interface,
@@ -225,7 +225,7 @@
225 'vlan': vlan.id,225 'vlan': vlan.id,
226 'tags': ",".join(tags),226 'tags': ",".join(tags),
227 })227 })
228 self.assertTrue(form.is_valid(), form.errors)228 self.assertTrue(form.is_valid(), dict(form.errors))
229 interface = form.save()229 interface = form.save()
230 self.assertIsNotNone(230 self.assertIsNotNone(
231 interface.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.STICKY))231 interface.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.STICKY))
@@ -240,7 +240,7 @@
240 'name': interface_name,240 'name': interface_name,
241 'vlan': vlan.id,241 'vlan': vlan.id,
242 })242 })
243 self.assertFalse(form.is_valid(), form.errors)243 self.assertFalse(form.is_valid(), dict(form.errors))
244 self.assertItemsEqual(244 self.assertItemsEqual(
245 ['mac_address'], form.errors.keys(), form.errors)245 ['mac_address'], form.errors.keys(), form.errors)
246 self.assertIn(246 self.assertIn(
@@ -259,7 +259,7 @@
259 'mac_address': mac_address,259 'mac_address': mac_address,
260 'vlan': interface.vlan.id,260 'vlan': interface.vlan.id,
261 })261 })
262 self.assertFalse(form.is_valid(), form.errors)262 self.assertFalse(form.is_valid(), dict(form.errors))
263 self.assertItemsEqual(263 self.assertItemsEqual(
264 ['name'], form.errors.keys(), form.errors)264 ['name'], form.errors.keys(), form.errors)
265 self.assertIn(265 self.assertIn(
@@ -279,7 +279,7 @@
279 'mac_address': mac_address,279 'mac_address': mac_address,
280 'vlan': vlan.id,280 'vlan': vlan.id,
281 })281 })
282 self.assertFalse(form.is_valid(), form.errors)282 self.assertFalse(form.is_valid(), dict(form.errors))
283 self.assertItemsEqual(283 self.assertItemsEqual(
284 ['vlan'], form.errors.keys(), form.errors)284 ['vlan'], form.errors.keys(), form.errors)
285 self.assertIn(285 self.assertIn(
@@ -301,7 +301,7 @@
301 'mac_address': mac_address,301 'mac_address': mac_address,
302 'vlan': vlan.id,302 'vlan': vlan.id,
303 })303 })
304 self.assertTrue(form.is_valid(), form.errors)304 self.assertTrue(form.is_valid(), dict(form.errors))
305 interface = form.save()305 interface = form.save()
306 self.assertEquals(vlan, interface.vlan)306 self.assertEquals(vlan, interface.vlan)
307307
@@ -317,7 +317,7 @@
317 'vlan': vlan.id,317 'vlan': vlan.id,
318 'parents': [parent.id],318 'parents': [parent.id],
319 })319 })
320 self.assertFalse(form.is_valid(), form.errors)320 self.assertFalse(form.is_valid(), dict(form.errors))
321 self.assertItemsEqual(321 self.assertItemsEqual(
322 ['parents'], form.errors.keys(), form.errors)322 ['parents'], form.errors.keys(), form.errors)
323 self.assertIn(323 self.assertIn(
@@ -338,7 +338,7 @@
338 'enabled': False,338 'enabled': False,
339 'tags': "",339 'tags': "",
340 })340 })
341 self.assertTrue(form.is_valid(), form.errors)341 self.assertTrue(form.is_valid(), dict(form.errors))
342 interface = form.save()342 interface = form.save()
343 self.assertThat(343 self.assertThat(
344 interface,344 interface,
@@ -358,7 +358,7 @@
358 'enabled': False,358 'enabled': False,
359 'tags': "",359 'tags': "",
360 })360 })
361 self.assertTrue(form.is_valid(), form.errors)361 self.assertTrue(form.is_valid(), dict(form.errors))
362 interface = form.save()362 interface = form.save()
363 self.assertThat(363 self.assertThat(
364 interface,364 interface,
@@ -378,7 +378,7 @@
378 'enabled': False,378 'enabled': False,
379 'tags': "",379 'tags': "",
380 })380 })
381 self.assertTrue(form.is_valid(), form.errors)381 self.assertTrue(form.is_valid(), dict(form.errors))
382 interface = form.save()382 interface = form.save()
383 self.assertThat(383 self.assertThat(
384 interface,384 interface,
@@ -410,7 +410,7 @@
410 'accept_ra': accept_ra,410 'accept_ra': accept_ra,
411 'autoconf': autoconf,411 'autoconf': autoconf,
412 })412 })
413 self.assertTrue(form.is_valid(), form.errors)413 self.assertTrue(form.is_valid(), dict(form.errors))
414 interface = form.save()414 interface = form.save()
415 self.assertEqual({415 self.assertEqual({
416 "mtu": mtu,416 "mtu": mtu,
@@ -440,7 +440,7 @@
440 'enabled': False,440 'enabled': False,
441 'tags': "",441 'tags': "",
442 })442 })
443 self.assertTrue(form.is_valid(), form.errors)443 self.assertTrue(form.is_valid(), dict(form.errors))
444 interface = form.save()444 interface = form.save()
445 self.assertEqual({445 self.assertEqual({
446 "mtu": mtu,446 "mtu": mtu,
@@ -469,7 +469,7 @@
469 "accept_ra": new_accept_ra,469 "accept_ra": new_accept_ra,
470 "autoconf": new_autoconf,470 "autoconf": new_autoconf,
471 })471 })
472 self.assertTrue(form.is_valid(), form.errors)472 self.assertTrue(form.is_valid(), dict(form.errors))
473 interface = form.save()473 interface = form.save()
474 self.assertEqual({474 self.assertEqual({
475 "mtu": new_mtu,475 "mtu": new_mtu,
@@ -495,7 +495,7 @@
495 "accept_ra": "",495 "accept_ra": "",
496 "autoconf": "",496 "autoconf": "",
497 })497 })
498 self.assertTrue(form.is_valid(), form.errors)498 self.assertTrue(form.is_valid(), dict(form.errors))
499 interface = form.save()499 interface = form.save()
500 self.assertEqual({}, interface.params)500 self.assertEqual({}, interface.params)
501501
@@ -511,7 +511,7 @@
511 'vlan': vlan.id,511 'vlan': vlan.id,
512 'parents': [parent.id],512 'parents': [parent.id],
513 })513 })
514 self.assertTrue(form.is_valid(), form.errors)514 self.assertTrue(form.is_valid(), dict(form.errors))
515 interface = form.save()515 interface = form.save()
516 interface_name = build_vlan_interface_name(parent, vlan)516 interface_name = build_vlan_interface_name(parent, vlan)
517 self.assertThat(517 self.assertThat(
@@ -529,7 +529,7 @@
529 'vlan': vlan.id,529 'vlan': vlan.id,
530 'parents': [parent.id],530 'parents': [parent.id],
531 })531 })
532 self.assertTrue(form.is_valid(), form.errors)532 self.assertTrue(form.is_valid(), dict(form.errors))
533 interface = form.save()533 interface = form.save()
534 self.assertIsNotNone(534 self.assertIsNotNone(
535 interface.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.STICKY))535 interface.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.STICKY))
@@ -541,7 +541,7 @@
541 data={541 data={
542 'parents': [parent.id],542 'parents': [parent.id],
543 })543 })
544 self.assertFalse(form.is_valid(), form.errors)544 self.assertFalse(form.is_valid(), dict(form.errors))
545 self.assertItemsEqual(545 self.assertItemsEqual(
546 ['vlan'], form.errors.keys(), form.errors)546 ['vlan'], form.errors.keys(), form.errors)
547 self.assertIn(547 self.assertIn(
@@ -559,7 +559,7 @@
559 'vlan': vlan.id,559 'vlan': vlan.id,
560 'parents': [parent.id],560 'parents': [parent.id],
561 })561 })
562 self.assertFalse(form.is_valid(), form.errors)562 self.assertFalse(form.is_valid(), dict(form.errors))
563 self.assertItemsEqual(563 self.assertItemsEqual(
564 ['name'], form.errors.keys(), form.errors)564 ['name'], form.errors.keys(), form.errors)
565 self.assertIn(565 self.assertIn(
@@ -575,7 +575,7 @@
575 'vlan': vlan.id,575 'vlan': vlan.id,
576 'parents': [parent.id],576 'parents': [parent.id],
577 })577 })
578 self.assertFalse(form.is_valid(), form.errors)578 self.assertFalse(form.is_valid(), dict(form.errors))
579 self.assertItemsEqual(579 self.assertItemsEqual(
580 ['vlan'], form.errors.keys(), form.errors)580 ['vlan'], form.errors.keys(), form.errors)
581 self.assertIn(581 self.assertIn(
@@ -589,7 +589,7 @@
589 data={589 data={
590 'vlan': vlan.id,590 'vlan': vlan.id,
591 })591 })
592 self.assertFalse(form.is_valid(), form.errors)592 self.assertFalse(form.is_valid(), dict(form.errors))
593 self.assertItemsEqual(['parents'], form.errors.keys())593 self.assertItemsEqual(['parents'], form.errors.keys())
594 self.assertIn(594 self.assertIn(
595 "A VLAN interface must have exactly one parent.",595 "A VLAN interface must have exactly one parent.",
@@ -607,7 +607,7 @@
607 'vlan': other_vlan.id,607 'vlan': other_vlan.id,
608 'parents': [vlan_parent.id],608 'parents': [vlan_parent.id],
609 })609 })
610 self.assertFalse(form.is_valid(), form.errors)610 self.assertFalse(form.is_valid(), dict(form.errors))
611 self.assertItemsEqual(['parents'], form.errors.keys())611 self.assertItemsEqual(['parents'], form.errors.keys())
612 self.assertIn(612 self.assertIn(
613 "VLAN interface can't have another VLAN interface as parent.",613 "VLAN interface can't have another VLAN interface as parent.",
@@ -621,7 +621,7 @@
621 'vlan': None,621 'vlan': None,
622 'parents': [parent.id],622 'parents': [parent.id],
623 })623 })
624 self.assertFalse(form.is_valid(), form.errors)624 self.assertFalse(form.is_valid(), dict(form.errors))
625 self.assertItemsEqual(['vlan'], form.errors.keys())625 self.assertItemsEqual(['vlan'], form.errors.keys())
626 self.assertIn(626 self.assertIn(
627 "A VLAN interface must be connected to a tagged VLAN.",627 "A VLAN interface must be connected to a tagged VLAN.",
@@ -637,7 +637,7 @@
637 'vlan': other_vlan.id,637 'vlan': other_vlan.id,
638 'parents': [parent.id],638 'parents': [parent.id],
639 })639 })
640 self.assertFalse(form.is_valid(), form.errors)640 self.assertFalse(form.is_valid(), dict(form.errors))
641 self.assertItemsEqual(['vlan'], form.errors.keys())641 self.assertItemsEqual(['vlan'], form.errors.keys())
642 self.assertIn(642 self.assertIn(
643 "A VLAN interface can only belong to a tagged VLAN on "643 "A VLAN interface can only belong to a tagged VLAN on "
@@ -654,7 +654,7 @@
654 'vlan': vlan.id,654 'vlan': vlan.id,
655 'parents': [parent.id],655 'parents': [parent.id],
656 })656 })
657 self.assertFalse(form.is_valid(), form.errors)657 self.assertFalse(form.is_valid(), dict(form.errors))
658 self.assertItemsEqual(['parents'], form.errors.keys())658 self.assertItemsEqual(['parents'], form.errors.keys())
659 self.assertIn(659 self.assertIn(
660 "A VLAN interface can't have a parent that is already in a bond.",660 "A VLAN interface can't have a parent that is already in a bond.",
@@ -671,7 +671,7 @@
671 'vlan': vlan.id,671 'vlan': vlan.id,
672 'parents': [parent1.id, parent2.id],672 'parents': [parent1.id, parent2.id],
673 })673 })
674 self.assertFalse(form.is_valid(), form.errors)674 self.assertFalse(form.is_valid(), dict(form.errors))
675 self.assertItemsEqual(['parents'], form.errors.keys())675 self.assertItemsEqual(['parents'], form.errors.keys())
676 self.assertIn(676 self.assertIn(
677 "A VLAN interface must have exactly one parent.",677 "A VLAN interface must have exactly one parent.",
@@ -688,7 +688,7 @@
688 data={688 data={
689 'vlan': new_vlan.id689 'vlan': new_vlan.id
690 })690 })
691 self.assertTrue(form.is_valid(), form.errors)691 self.assertTrue(form.is_valid(), dict(form.errors))
692 interface = form.save()692 interface = form.save()
693 self.assertThat(693 self.assertThat(
694 interface,694 interface,
@@ -713,7 +713,7 @@
713 'parents': [parent1.id, parent2.id],713 'parents': [parent1.id, parent2.id],
714 'bond_mode': bond_mode,714 'bond_mode': bond_mode,
715 })715 })
716 self.assertFalse(form.is_valid(), form.errors)716 self.assertFalse(form.is_valid(), dict(form.errors))
717 self.assertEqual({717 self.assertEqual({
718 "bond_mode": [718 "bond_mode": [
719 compose_invalid_choice_text(719 compose_invalid_choice_text(
@@ -731,7 +731,7 @@
731 'name': interface_name,731 'name': interface_name,
732 'parents': [parent1.id, parent2.id],732 'parents': [parent1.id, parent2.id],
733 })733 })
734 self.assertTrue(form.is_valid(), form.errors)734 self.assertTrue(form.is_valid(), dict(form.errors))
735 interface = form.save()735 interface = form.save()
736 self.assertThat(736 self.assertThat(
737 interface,737 interface,
@@ -755,7 +755,7 @@
755 'name': interface_name,755 'name': interface_name,
756 'parents': [parent1.id, parent2.id],756 'parents': [parent1.id, parent2.id],
757 })757 })
758 self.assertTrue(form.is_valid(), form.errors)758 self.assertTrue(form.is_valid(), dict(form.errors))
759 interface = form.save()759 interface = form.save()
760 self.assertEqual(760 self.assertEqual(
761 0,761 0,
@@ -780,7 +780,7 @@
780 'parents': [parent1.id, parent2.id],780 'parents': [parent1.id, parent2.id],
781 'mac_address': parent1.mac_address,781 'mac_address': parent1.mac_address,
782 })782 })
783 self.assertTrue(form.is_valid(), form.errors)783 self.assertTrue(form.is_valid(), dict(form.errors))
784 interface = form.save()784 interface = form.save()
785 self.assertThat(785 self.assertThat(
786 interface,786 interface,
@@ -800,7 +800,7 @@
800 'name': interface_name,800 'name': interface_name,
801 'parents': [parent1.id, parent2.id],801 'parents': [parent1.id, parent2.id],
802 })802 })
803 self.assertTrue(form.is_valid(), form.errors)803 self.assertTrue(form.is_valid(), dict(form.errors))
804 interface = form.save()804 interface = form.save()
805 self.assertEqual({805 self.assertEqual({
806 "bond_mode": "balance-rr",806 "bond_mode": "balance-rr",
@@ -835,7 +835,7 @@
835 'bond_lacp_rate': bond_lacp_rate,835 'bond_lacp_rate': bond_lacp_rate,
836 'bond_xmit_hash_policy': bond_xmit_hash_policy,836 'bond_xmit_hash_policy': bond_xmit_hash_policy,
837 })837 })
838 self.assertTrue(form.is_valid(), form.errors)838 self.assertTrue(form.is_valid(), dict(form.errors))
839 interface = form.save()839 interface = form.save()
840 self.assertEqual({840 self.assertEqual({
841 "bond_mode": bond_mode,841 "bond_mode": bond_mode,
@@ -853,7 +853,7 @@
853 data={853 data={
854 'name': interface_name,854 'name': interface_name,
855 })855 })
856 self.assertFalse(form.is_valid(), form.errors)856 self.assertFalse(form.is_valid(), dict(form.errors))
857 self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys())857 self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys())
858 self.assertIn(858 self.assertIn(
859 "A bond interface must have one or more parents.",859 "A bond interface must have one or more parents.",
@@ -871,7 +871,7 @@
871 'mac_address': parent.mac_address,871 'mac_address': parent.mac_address,
872 'vlan': vlan.id,872 'vlan': vlan.id,
873 })873 })
874 self.assertFalse(form.is_valid(), form.errors)874 self.assertFalse(form.is_valid(), dict(form.errors))
875 self.assertItemsEqual(['vlan'], form.errors.keys())875 self.assertItemsEqual(['vlan'], form.errors.keys())
876 self.assertIn(876 self.assertIn(
877 "A bond interface can only belong to an untagged VLAN.",877 "A bond interface can only belong to an untagged VLAN.",
@@ -892,7 +892,7 @@
892 'name': interface_name,892 'name': interface_name,
893 'parents': [parent1.id, parent2.id]893 'parents': [parent1.id, parent2.id]
894 })894 })
895 self.assertFalse(form.is_valid(), form.errors)895 self.assertFalse(form.is_valid(), dict(form.errors))
896 self.assertIn(896 self.assertIn(
897 "Interfaces already in-use: eth0, eth1.",897 "Interfaces already in-use: eth0, eth1.",
898 form.errors['parents'][0])898 form.errors['parents'][0])
@@ -910,7 +910,7 @@
910 'name': interface_name,910 'name': interface_name,
911 'parents': [parent1.id, parent2.id]911 'parents': [parent1.id, parent2.id]
912 })912 })
913 self.assertFalse(form.is_valid(), form.errors)913 self.assertFalse(form.is_valid(), dict(form.errors))
914 self.assertEquals(914 self.assertEquals(
915 "All parents must belong to the same VLAN.",915 "All parents must belong to the same VLAN.",
916 form.errors['parents'][0])916 form.errors['parents'][0])
@@ -934,7 +934,7 @@
934 'name': new_name,934 'name': new_name,
935 'parents': [parent1.id, parent2.id, new_parent.id],935 'parents': [parent1.id, parent2.id, new_parent.id],
936 })936 })
937 self.assertTrue(form.is_valid(), form.errors)937 self.assertTrue(form.is_valid(), dict(form.errors))
938 interface = form.save()938 interface = form.save()
939 self.assertThat(939 self.assertThat(
940 interface,940 interface,
@@ -959,7 +959,7 @@
959 data={959 data={
960 'vlan': None,960 'vlan': None,
961 })961 })
962 self.assertTrue(form.is_valid(), form.errors)962 self.assertTrue(form.is_valid(), dict(form.errors))
963 interface = form.save()963 interface = form.save()
964 self.assertThat(964 self.assertThat(
965 interface,965 interface,
@@ -983,7 +983,7 @@
983 'name': new_name,983 'name': new_name,
984 'parents': [parent1.id, parent2.id],984 'parents': [parent1.id, parent2.id],
985 })985 })
986 self.assertTrue(form.is_valid(), form.errors)986 self.assertTrue(form.is_valid(), dict(form.errors))
987 interface = form.save()987 interface = form.save()
988 self.assertThat(988 self.assertThat(
989 interface,989 interface,
@@ -1009,7 +1009,7 @@
1009 'name': new_name,1009 'name': new_name,
1010 'parents': [parent1.id, parent2.id],1010 'parents': [parent1.id, parent2.id],
1011 })1011 })
1012 self.assertTrue(form.is_valid(), form.errors)1012 self.assertTrue(form.is_valid(), dict(form.errors))
1013 interface = form.save()1013 interface = form.save()
1014 self.assertThat(1014 self.assertThat(
1015 interface,1015 interface,
@@ -1049,7 +1049,7 @@
1049 data={1049 data={
1050 'name': new_name,1050 'name': new_name,
1051 })1051 })
1052 self.assertTrue(form.is_valid(), form.errors)1052 self.assertTrue(form.is_valid(), dict(form.errors))
1053 interface = form.save()1053 interface = form.save()
1054 self.assertEqual({1054 self.assertEqual({
1055 "bond_mode": bond_mode,1055 "bond_mode": bond_mode,
@@ -1102,7 +1102,7 @@
1102 'bond_lacp_rate': new_bond_lacp_rate,1102 'bond_lacp_rate': new_bond_lacp_rate,
1103 'bond_xmit_hash_policy': new_bond_xmit_hash_policy,1103 'bond_xmit_hash_policy': new_bond_xmit_hash_policy,
1104 })1104 })
1105 self.assertTrue(form.is_valid(), form.errors)1105 self.assertTrue(form.is_valid(), dict(form.errors))
1106 interface = form.save()1106 interface = form.save()
1107 self.assertEqual({1107 self.assertEqual({
1108 "bond_mode": new_bond_mode,1108 "bond_mode": new_bond_mode,
@@ -1155,7 +1155,7 @@
1155 'bond_lacp_rate': new_bond_lacp_rate,1155 'bond_lacp_rate': new_bond_lacp_rate,
1156 'bond_xmit_hash_policy': new_bond_xmit_hash_policy,1156 'bond_xmit_hash_policy': new_bond_xmit_hash_policy,
1157 })1157 })
1158 self.assertTrue(form.is_valid(), form.errors)1158 self.assertTrue(form.is_valid(), dict(form.errors))
1159 interface = form.save()1159 interface = form.save()
1160 self.assertEqual({1160 self.assertEqual({
1161 "bond_mode": new_bond_mode,1161 "bond_mode": new_bond_mode,
@@ -1178,14 +1178,62 @@
1178 'name': interface_name,1178 'name': interface_name,
1179 'parents': [parent.id],1179 'parents': [parent.id],
1180 })1180 })
1181 self.assertTrue(form.is_valid(), form.errors)1181 self.assertTrue(form.is_valid(), dict(form.errors))
1182 interface = form.save()1182 interface = form.save()
1183 self.assertThat(1183 self.assertThat(
1184 interface,1184 interface,
1185 MatchesStructure.byEquality(1185 MatchesStructure.byEquality(
1186 name=interface_name, type=INTERFACE_TYPE.BRIDGE))1186 name=interface_name, type=INTERFACE_TYPE.BRIDGE))
1187 self.assertEqual(interface.mac_address, parent.mac_address)1187 self.assertEqual(interface.mac_address, parent.mac_address)
1188 self.assertItemsEqual([parent], interface.parents.all())1188 self.assertItemsEqual([parent], interface.parents.all())
1189
1190 def test__allows_bridge_on_parent_with_vlan_bridges(self):
1191 parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
1192 vlan1 = factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent])
1193 factory.make_Interface(INTERFACE_TYPE.BRIDGE, parents=[vlan1])
1194 vlan2 = factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent])
1195 factory.make_Interface(INTERFACE_TYPE.BRIDGE, parents=[vlan2])
1196 interface_name = factory.make_name()
1197 form = BridgeInterfaceForm(
1198 node=parent.node,
1199 data={
1200 'name': interface_name,
1201 'parents': [parent.id],
1202 })
1203 self.assertTrue(form.is_valid(), dict(form.errors))
1204 interface = form.save()
1205 self.assertThat(
1206 interface,
1207 MatchesStructure.byEquality(
1208 name=interface_name, type=INTERFACE_TYPE.BRIDGE))
1209 self.assertEqual(interface.mac_address, parent.mac_address)
1210 self.assertItemsEqual([parent], interface.parents.all())
1211
1212 def test__allows_bridge_on_bond_with_vlan_bridges(self):
1213 node = factory.make_Node()
1214 eth0 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
1215 eth1 = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node)
1216 bond0 = factory.make_Interface(
1217 INTERFACE_TYPE.BOND, parents=[eth0, eth1])
1218 vlan1 = factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[bond0])
1219 factory.make_Interface(INTERFACE_TYPE.BRIDGE, parents=[vlan1])
1220 vlan2 = factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[bond0])
1221 factory.make_Interface(INTERFACE_TYPE.BRIDGE, parents=[vlan2])
1222 interface_name = factory.make_name()
1223 form = BridgeInterfaceForm(
1224 node=node,
1225 data={
1226 'name': interface_name,
1227 'parents': [bond0.id],
1228 })
1229 self.assertTrue(form.is_valid(), dict(form.errors))
1230 interface = form.save()
1231 self.assertThat(
1232 interface,
1233 MatchesStructure.byEquality(
1234 name=interface_name, type=INTERFACE_TYPE.BRIDGE))
1235 self.assertEqual(interface.mac_address, bond0.mac_address)
1236 self.assertItemsEqual([bond0], interface.parents.all())
11891237
1190 def test__create_removes_parent_links_and_sets_link_up_on_bridge(self):1238 def test__create_removes_parent_links_and_sets_link_up_on_bridge(self):
1191 parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)1239 parent = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
@@ -1197,7 +1245,7 @@
1197 'name': interface_name,1245 'name': interface_name,
1198 'parents': [parent.id],1246 'parents': [parent.id],
1199 })1247 })
1200 self.assertTrue(form.is_valid(), form.errors)1248 self.assertTrue(form.is_valid(), dict(form.errors))
1201 interface = form.save()1249 interface = form.save()
1202 self.assertEqual(1250 self.assertEqual(
1203 0,1251 0,
@@ -1216,7 +1264,7 @@
1216 'parents': [parent.id],1264 'parents': [parent.id],
1217 'mac_address': parent.mac_address,1265 'mac_address': parent.mac_address,
1218 })1266 })
1219 self.assertTrue(form.is_valid(), form.errors)1267 self.assertTrue(form.is_valid(), dict(form.errors))
1220 interface = form.save()1268 interface = form.save()
1221 self.assertThat(1269 self.assertThat(
1222 interface,1270 interface,
@@ -1232,7 +1280,7 @@
1232 data={1280 data={
1233 'name': interface_name,1281 'name': interface_name,
1234 })1282 })
1235 self.assertFalse(form.is_valid(), form.errors)1283 self.assertFalse(form.is_valid(), dict(form.errors))
1236 self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys())1284 self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys())
1237 self.assertIn(1285 self.assertIn(
1238 "A bridge interface must have exactly one parent.",1286 "A bridge interface must have exactly one parent.",
@@ -1240,17 +1288,24 @@
12401288
1241 def test__rejects_when_parent_already_have_children(self):1289 def test__rejects_when_parent_already_have_children(self):
1242 node = factory.make_Node()1290 node = factory.make_Node()
1243 parent = factory.make_Interface(1291 eth0 = factory.make_Interface(
1244 INTERFACE_TYPE.PHYSICAL, node=node, name="eth0")1292 INTERFACE_TYPE.PHYSICAL, node=node, name="eth0")
1245 factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent])1293 eth1 = factory.make_Interface(
1294 INTERFACE_TYPE.PHYSICAL, node=node, name="eth1")
1295 invalid0 = factory.make_Interface(
1296 INTERFACE_TYPE.BOND, parents=[eth0, eth1])
1297 # This should never happen, but in order to validate the case we're
1298 # trying to validate, we need a child that isn't a bond or bridge.
1299 invalid0.type = INTERFACE_TYPE.UNKNOWN
1300 invalid0.save()
1246 interface_name = factory.make_name()1301 interface_name = factory.make_name()
1247 form = BridgeInterfaceForm(1302 form = BridgeInterfaceForm(
1248 node=node,1303 node=node,
1249 data={1304 data={
1250 'name': interface_name,1305 'name': interface_name,
1251 'parents': [parent.id]1306 'parents': [eth0.id]
1252 })1307 })
1253 self.assertFalse(form.is_valid(), form.errors)1308 self.assertFalse(form.is_valid(), dict(form.errors))
1254 self.assertIn(1309 self.assertIn(
1255 "Interfaces already in-use: eth0.",1310 "Interfaces already in-use: eth0.",
1256 form.errors['parents'][0])1311 form.errors['parents'][0])
@@ -1265,7 +1320,7 @@
1265 'name': interface_name,1320 'name': interface_name,
1266 'parents': [bridge.id]1321 'parents': [bridge.id]
1267 })1322 })
1268 self.assertFalse(form.is_valid(), form.errors)1323 self.assertFalse(form.is_valid(), dict(form.errors))
1269 self.assertIn(1324 self.assertIn(
1270 "A bridge interface can't have another bridge interface as "1325 "A bridge interface can't have another bridge interface as "
1271 "parent.",1326 "parent.",
@@ -1283,7 +1338,7 @@
1283 'name': interface_name,1338 'name': interface_name,
1284 'parents': [parent.id]1339 'parents': [parent.id]
1285 })1340 })
1286 self.assertFalse(form.is_valid(), form.errors)1341 self.assertFalse(form.is_valid(), dict(form.errors))
1287 self.assertIn(1342 self.assertIn(
1288 "A bridge interface can't have a parent that is already "1343 "A bridge interface can't have a parent that is already "
1289 "in a bond or a bridge.",1344 "in a bond or a bridge.",
@@ -1302,7 +1357,7 @@
1302 'name': interface_name,1357 'name': interface_name,
1303 'parents': [parent1.id]1358 'parents': [parent1.id]
1304 })1359 })
1305 self.assertFalse(form.is_valid(), form.errors)1360 self.assertFalse(form.is_valid(), dict(form.errors))
1306 self.assertIn(1361 self.assertIn(
1307 "A bridge interface can't have a parent that is already "1362 "A bridge interface can't have a parent that is already "
1308 "in a bond or a bridge.",1363 "in a bond or a bridge.",
@@ -1325,7 +1380,7 @@
1325 'name': new_name,1380 'name': new_name,
1326 'parents': [new_parent.id],1381 'parents': [new_parent.id],
1327 })1382 })
1328 self.assertTrue(form.is_valid(), form.errors)1383 self.assertTrue(form.is_valid(), dict(form.errors))
1329 interface = form.save()1384 interface = form.save()
1330 self.assertThat(1385 self.assertThat(
1331 interface,1386 interface,
@@ -1345,7 +1400,7 @@
1345 data={1400 data={
1346 'vlan': None,1401 'vlan': None,
1347 })1402 })
1348 self.assertTrue(form.is_valid(), form.errors)1403 self.assertTrue(form.is_valid(), dict(form.errors))
1349 interface = form.save()1404 interface = form.save()
1350 self.assertThat(1405 self.assertThat(
1351 interface,1406 interface,
@@ -1372,7 +1427,7 @@
1372 data={1427 data={
1373 'name': new_name,1428 'name': new_name,
1374 })1429 })
1375 self.assertTrue(form.is_valid(), form.errors)1430 self.assertTrue(form.is_valid(), dict(form.errors))
1376 interface = form.save()1431 interface = form.save()
1377 self.assertEqual({1432 self.assertEqual({
1378 "bridge_stp": bridge_stp,1433 "bridge_stp": bridge_stp,
@@ -1400,7 +1455,7 @@
1400 'bridge_stp': new_bridge_stp,1455 'bridge_stp': new_bridge_stp,
1401 'bridge_fd': new_bridge_fd,1456 'bridge_fd': new_bridge_fd,
1402 })1457 })
1403 self.assertTrue(form.is_valid(), form.errors)1458 self.assertTrue(form.is_valid(), dict(form.errors))
1404 interface = form.save()1459 interface = form.save()
1405 self.assertEqual({1460 self.assertEqual({
1406 "bridge_stp": new_bridge_stp,1461 "bridge_stp": new_bridge_stp,
@@ -1426,7 +1481,7 @@
1426 'bridge_stp': new_bridge_stp,1481 'bridge_stp': new_bridge_stp,
1427 'bridge_fd': new_bridge_fd,1482 'bridge_fd': new_bridge_fd,
1428 })1483 })
1429 self.assertTrue(form.is_valid(), form.errors)1484 self.assertTrue(form.is_valid(), dict(form.errors))
1430 interface = form.save()1485 interface = form.save()
1431 self.assertEqual({1486 self.assertEqual({
1432 'bridge_stp': new_bridge_stp,1487 'bridge_stp': new_bridge_stp,
@@ -1459,7 +1514,7 @@
1459 'parents': [parent.id],1514 'parents': [parent.id],
1460 'tags': tags,1515 'tags': tags,
1461 })1516 })
1462 self.assertTrue(form.is_valid(), form.errors)1517 self.assertTrue(form.is_valid(), dict(form.errors))
1463 interface = form.save()1518 interface = form.save()
1464 self.assertThat(1519 self.assertThat(
1465 interface,1520 interface,
@@ -1478,25 +1533,30 @@
1478 data={1533 data={
1479 'name': interface_name,1534 'name': interface_name,
1480 })1535 })
1481 self.assertFalse(form.is_valid(), form.errors)1536 self.assertFalse(form.is_valid(), dict(form.errors))
1482 self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys())1537 self.assertItemsEqual(['parents', 'mac_address'], form.errors.keys())
1483 self.assertIn(1538 self.assertIn(
1484 "A bridge interface must have exactly one parent.",1539 "A bridge interface must have exactly one parent.",
1485 form.errors['parents'][0])1540 form.errors['parents'][0])
14861541
1487 def test__rejects_when_parent_already_have_children(self):1542 def test__rejects_when_parent_already_have_non_vlan_children(self):
1488 node = factory.make_Node()1543 node = factory.make_Node()
1489 parent = factory.make_Interface(1544 eth0 = factory.make_Interface(
1490 INTERFACE_TYPE.PHYSICAL, node=node, name="eth0")1545 INTERFACE_TYPE.PHYSICAL, node=node, name="eth0")
1491 factory.make_Interface(INTERFACE_TYPE.VLAN, parents=[parent])1546 eth1 = factory.make_Interface(
1547 INTERFACE_TYPE.PHYSICAL, node=node, name="eth1")
1548 bond0 = factory.make_Interface(
1549 INTERFACE_TYPE.BOND, parents=[eth0, eth1])
1550 bond0.type = INTERFACE_TYPE.UNKNOWN
1551 bond0.save()
1492 interface_name = factory.make_name()1552 interface_name = factory.make_name()
1493 form = AcquiredBridgeInterfaceForm(1553 form = AcquiredBridgeInterfaceForm(
1494 node=node,1554 node=node,
1495 data={1555 data={
1496 'name': interface_name,1556 'name': interface_name,
1497 'parents': [parent.id]1557 'parents': [eth0.id]
1498 })1558 })
1499 self.assertFalse(form.is_valid(), form.errors)1559 self.assertFalse(form.is_valid(), dict(form.errors))
1500 self.assertIn(1560 self.assertIn(
1501 "Interfaces already in-use: eth0.",1561 "Interfaces already in-use: eth0.",
1502 form.errors['parents'][0])1562 form.errors['parents'][0])
@@ -1511,7 +1571,7 @@
1511 'name': interface_name,1571 'name': interface_name,
1512 'parents': [bridge.id]1572 'parents': [bridge.id]
1513 })1573 })
1514 self.assertFalse(form.is_valid(), form.errors)1574 self.assertFalse(form.is_valid(), dict(form.errors))
1515 self.assertIn(1575 self.assertIn(
1516 "A bridge interface can't have another bridge interface as "1576 "A bridge interface can't have another bridge interface as "
1517 "parent.",1577 "parent.",
@@ -1529,7 +1589,7 @@
1529 'name': interface_name,1589 'name': interface_name,
1530 'parents': [parent.id]1590 'parents': [parent.id]
1531 })1591 })
1532 self.assertFalse(form.is_valid(), form.errors)1592 self.assertFalse(form.is_valid(), dict(form.errors))
1533 self.assertIn(1593 self.assertIn(
1534 "A bridge interface can't have a parent that is already "1594 "A bridge interface can't have a parent that is already "
1535 "in a bond or a bridge.",1595 "in a bond or a bridge.",
@@ -1548,7 +1608,7 @@
1548 'name': interface_name,1608 'name': interface_name,
1549 'parents': [parent1.id]1609 'parents': [parent1.id]
1550 })1610 })
1551 self.assertFalse(form.is_valid(), form.errors)1611 self.assertFalse(form.is_valid(), dict(form.errors))
1552 self.assertIn(1612 self.assertIn(
1553 "A bridge interface can't have a parent that is already "1613 "A bridge interface can't have a parent that is already "
1554 "in a bond or a bridge.",1614 "in a bond or a bridge.",