Merge ~newell-jensen/maas:lp1806969 into maas:master

Proposed by Newell Jensen
Status: Merged
Approved by: Newell Jensen
Approved revision: 16517d8dbfc64cc2546b1531ce63371d3547e087
Merge reported by: MAAS Lander
Merged at revision: not available
Proposed branch: ~newell-jensen/maas:lp1806969
Merge into: maas:master
Diff against target: 205 lines (+171/-4)
2 files modified
src/maasserver/forms/pods.py (+21/-4)
src/maasserver/forms/tests/test_pods.py (+150/-0)
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Björn Tillenius Needs Fixing
Review via email: mp+370083@code.launchpad.net

Commit message

LP: #1806969 -- Explicitly search for bridges, bonds, followed by the remaining interfaces when retrieving a requested machine via interface constraints.

Description of the change

The reason this bug was occurring is that the interface constraints specified were being mapped to a list of interface_ids and the max id of this list was being chosen but the the bridge interface was the one with a lower id. This branch changes how the interfaces are chosen if there is a list of interface_ids by searching for a bridge or bond and then taking the max id of this. If nothing is found or if there isn't a list of interface_ids to check, the max is taken as was done before.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Why combine bridges and bonds? Should the search order be bridges first, then bonds, then other interfaces.

review: Needs Information
Revision history for this message
Björn Tillenius (bjornt) wrote :

I agree with Blake, that you should order first by bridges, and then by bonds, and then other interfaces.

I also think you need some more tests. See inline comments.

review: Needs Fixing
~newell-jensen/maas:lp1806969 updated
beed205... by Newell Jensen

Update queries so that we first try to match on bridges, followed by bonds, and then finally interfaces. Add better unit test coverage.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Some optimizations need to be done, but the test coverage now looks better.

review: Needs Fixing
~newell-jensen/maas:lp1806969 updated
1c00626... by Newell Jensen

Re-write django queries to improve the performance.

Revision history for this message
Blake Rouse (blake-rouse) :
review: Needs Fixing
~newell-jensen/maas:lp1806969 updated
d547dd0... by Newell Jensen

Review fixes.

Revision history for this message
Blake Rouse (blake-rouse) :
review: Needs Fixing
~newell-jensen/maas:lp1806969 updated
16517d8... by Newell Jensen

Review fixes +2.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good! Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/maasserver/forms/pods.py b/src/maasserver/forms/pods.py
2index f54d875..4072a84 100644
3--- a/src/maasserver/forms/pods.py
4+++ b/src/maasserver/forms/pods.py
5@@ -495,6 +495,19 @@ class ComposeMachineForm(forms.Form):
6 known_host_interfaces=known_host_interfaces,
7 )
8
9+ def _pick_interface(self, interfaces):
10+ bridge_interfaces = interfaces.filter(
11+ type=INTERFACE_TYPE.BRIDGE).order_by('-id')
12+ bond_interfaces = interfaces.filter(
13+ type=INTERFACE_TYPE.BOND).order_by('-id')
14+ bridge_interface = list(bridge_interfaces[:1])
15+ if bridge_interface:
16+ return bridge_interface[0]
17+ bond_interface = list(bond_interfaces[:1])
18+ if bond_interface:
19+ return bond_interface[0]
20+ return interfaces[0]
21+
22 def _get_requested_machine_interfaces_via_constraints(
23 self, interfaces_label_map):
24 requested_machine_interfaces = []
25@@ -512,11 +525,15 @@ class ComposeMachineForm(forms.Form):
26 for label in result.label_map.keys():
27 # XXX: We might want to use the "deepest" interface in the
28 # heirarchy, to ensure we get a bridge or bond (if configured)
29- # rather than its parent. max() is a good approximation, since
30- # child interfaces will be created after their parents.
31+ # rather than its parent. Since child interfaces will be created
32+ # after their parents, this is a good approximation.
33+ # Search for bridges, followed by bonds, and finally with the
34+ # remaining interfaces.
35 interface_ids = result.label_map[label][pod_node_id]
36- interface_id = max(interface_ids)
37- interface = Interface.objects.get(id=interface_id)
38+ interfaces = Interface.objects.filter(
39+ id__in=interface_ids).order_by('-id')
40+ interface = self._pick_interface(interfaces)
41+
42 # Check to see if we have a bootable VLAN,
43 # we need at least one.
44 if interface.has_bootable_vlan():
45diff --git a/src/maasserver/forms/tests/test_pods.py b/src/maasserver/forms/tests/test_pods.py
46index 03d332a..a218d84 100644
47--- a/src/maasserver/forms/tests/test_pods.py
48+++ b/src/maasserver/forms/tests/test_pods.py
49@@ -1241,6 +1241,156 @@ class TestComposeMachineForm(MAASTransactionServerTestCase):
50 attach_options=Equals(MACVLAN_MODE.BRIDGE))),
51 ]))))
52
53+ def test__get_machine_with_interfaces_by_subnets_bridge(self):
54+ request = MagicMock()
55+ cidr1 = "10.0.0.0/24"
56+ cidr2 = "192.168.100.0/24"
57+ vlan = factory.make_VLAN(dhcp_on=True)
58+ subnet = factory.make_Subnet(cidr=cidr2, vlan=vlan)
59+ pod_host = factory.make_Machine_with_Interface_on_Subnet(
60+ cidr=cidr1)
61+ space = factory.make_Space('dmz')
62+ pod_host.boot_interface.vlan.space = space
63+ pod_host.boot_interface.vlan.save()
64+
65+ # Create a bridge and non-bridge on the pod_host
66+ bridge = factory.make_Interface(
67+ iftype=INTERFACE_TYPE.BRIDGE, node=pod_host,
68+ subnet=pod_host.boot_interface.vlan.subnet_set.first())
69+ non_bridge = factory.make_Interface(node=pod_host, subnet=subnet)
70+
71+ pod = make_pod_with_hints()
72+ pod.ip_address = pod_host.boot_interface.ip_addresses.first()
73+ pod.save()
74+ interfaces = "eth0:subnet=%s;eth1:subnet=%s" % (cidr1, cidr2)
75+ form = ComposeMachineForm(data={
76+ 'interfaces': interfaces,
77+ }, request=request, pod=pod)
78+ self.assertTrue(form.is_valid(), form.errors)
79+ request_machine = form.get_requested_machine(
80+ make_known_host_interfaces(pod))
81+ self.assertThat(request_machine, MatchesAll(
82+ IsInstance(RequestedMachine),
83+ MatchesStructure(
84+ interfaces=MatchesListwise([
85+ MatchesAll(
86+ IsInstance(RequestedMachineInterface),
87+ MatchesStructure(
88+ attach_name=Equals(bridge.name),
89+ attach_type=Equals(InterfaceAttachType.BRIDGE),
90+ attach_options=Equals(None))),
91+ MatchesAll(
92+ IsInstance(RequestedMachineInterface),
93+ MatchesStructure(
94+ attach_name=Equals(non_bridge.name),
95+ attach_type=Equals(InterfaceAttachType.MACVLAN),
96+ attach_options=Equals(MACVLAN_MODE.BRIDGE))),
97+ ]))))
98+
99+ def test__get_machine_with_interfaces_by_subnets_bond(self):
100+ request = MagicMock()
101+ cidr1 = "10.0.0.0/24"
102+ cidr2 = "192.168.100.0/24"
103+ vlan = factory.make_VLAN(dhcp_on=True)
104+ subnet = factory.make_Subnet(cidr=cidr2, vlan=vlan)
105+ pod_host = factory.make_Machine_with_Interface_on_Subnet(
106+ cidr=cidr1)
107+ space = factory.make_Space('dmz')
108+ pod_host.boot_interface.vlan.space = space
109+ pod_host.boot_interface.vlan.save()
110+
111+ # Create a bond and non-bond on the pod_host
112+ bond_if = factory.make_Interface(
113+ node=pod_host,
114+ subnet=pod_host.boot_interface.vlan.subnet_set.first())
115+ bond = factory.make_Interface(
116+ iftype=INTERFACE_TYPE.BOND, node=pod_host, parents=[
117+ pod_host.boot_interface, bond_if],
118+ subnet=pod_host.boot_interface.vlan.subnet_set.first())
119+ non_bond = factory.make_Interface(node=pod_host, subnet=subnet)
120+
121+ pod = make_pod_with_hints()
122+ pod.ip_address = pod_host.boot_interface.ip_addresses.first()
123+ pod.save()
124+ interfaces = "eth0:subnet=%s;eth1:subnet=%s" % (cidr1, cidr2)
125+ form = ComposeMachineForm(data={
126+ 'interfaces': interfaces,
127+ }, request=request, pod=pod)
128+ self.assertTrue(form.is_valid(), form.errors)
129+ request_machine = form.get_requested_machine(
130+ make_known_host_interfaces(pod))
131+ self.assertThat(request_machine, MatchesAll(
132+ IsInstance(RequestedMachine),
133+ MatchesStructure(
134+ interfaces=MatchesListwise([
135+ MatchesAll(
136+ IsInstance(RequestedMachineInterface),
137+ MatchesStructure(
138+ attach_name=Equals(bond.name),
139+ attach_type=Equals(InterfaceAttachType.MACVLAN),
140+ attach_options=Equals(MACVLAN_MODE.BRIDGE))),
141+ MatchesAll(
142+ IsInstance(RequestedMachineInterface),
143+ MatchesStructure(
144+ attach_name=Equals(non_bond.name),
145+ attach_type=Equals(InterfaceAttachType.MACVLAN),
146+ attach_options=Equals(MACVLAN_MODE.BRIDGE))),
147+ ]))))
148+
149+ def test__get_machine_with_interfaces_by_subnets_bond_inside_bridge(self):
150+ request = MagicMock()
151+ cidr1 = "10.0.0.0/24"
152+ cidr2 = "192.168.100.0/24"
153+ vlan = factory.make_VLAN(dhcp_on=True)
154+ subnet = factory.make_Subnet(cidr=cidr2, vlan=vlan)
155+ pod_host = factory.make_Machine_with_Interface_on_Subnet(
156+ cidr=cidr1)
157+ space = factory.make_Space('dmz')
158+ pod_host.boot_interface.vlan.space = space
159+ pod_host.boot_interface.vlan.save()
160+
161+ # Create a bond and non-bond on the pod_host
162+ bond_if = factory.make_Interface(
163+ node=pod_host,
164+ subnet=pod_host.boot_interface.vlan.subnet_set.first())
165+ bond = factory.make_Interface(
166+ iftype=INTERFACE_TYPE.BOND, node=pod_host, parents=[
167+ pod_host.boot_interface, bond_if],
168+ subnet=pod_host.boot_interface.vlan.subnet_set.first())
169+ non_bond = factory.make_Interface(node=pod_host, subnet=subnet)
170+ # Create bridge using the bond
171+ bridge = factory.make_Interface(
172+ iftype=INTERFACE_TYPE.BRIDGE, node=pod_host, parents=[bond],
173+ subnet=pod_host.boot_interface.vlan.subnet_set.first())
174+
175+ pod = make_pod_with_hints()
176+ pod.ip_address = pod_host.boot_interface.ip_addresses.first()
177+ pod.save()
178+ interfaces = "eth0:subnet=%s;eth1:subnet=%s" % (cidr1, cidr2)
179+ form = ComposeMachineForm(data={
180+ 'interfaces': interfaces,
181+ }, request=request, pod=pod)
182+ self.assertTrue(form.is_valid(), form.errors)
183+ request_machine = form.get_requested_machine(
184+ make_known_host_interfaces(pod))
185+ self.assertThat(request_machine, MatchesAll(
186+ IsInstance(RequestedMachine),
187+ MatchesStructure(
188+ interfaces=MatchesListwise([
189+ MatchesAll(
190+ IsInstance(RequestedMachineInterface),
191+ MatchesStructure(
192+ attach_name=Equals(bridge.name),
193+ attach_type=Equals(InterfaceAttachType.BRIDGE),
194+ attach_options=Equals(None))),
195+ MatchesAll(
196+ IsInstance(RequestedMachineInterface),
197+ MatchesStructure(
198+ attach_name=Equals(non_bond.name),
199+ attach_type=Equals(InterfaceAttachType.MACVLAN),
200+ attach_options=Equals(MACVLAN_MODE.BRIDGE))),
201+ ]))))
202+
203 def test__get_machine_with_interfaces_by_space_as_bridge(self):
204 request = MagicMock()
205 pod_host = factory.make_Machine_with_Interface_on_Subnet(

Subscribers

People subscribed via source and target branches