Merge lp:~blake-rouse/maas/pick-best-gateway-ip into lp:~maas-committers/maas/trunk
- pick-best-gateway-ip
- Merge into trunk
Proposed by
Blake Rouse
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 4250 |
Proposed branch: | lp:~blake-rouse/maas/pick-best-gateway-ip |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
328 lines (+301/-0) 2 files modified
src/maasserver/models/node.py (+79/-0) src/maasserver/models/tests/test_node.py (+222/-0) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/pick-best-gateway-ip |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Review via email: mp+270832@code.launchpad.net |
Commit message
Add method on node to return the best guess for the gateway_ip.
Description of the change
This method is not being used yet, but a following branch will use it.
To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote : | # |
Thanks for the review.
Revision history for this message
Mike Pontillo (mpontillo) wrote : | # |
Just one reply below.
I suspect our user story for disabling a nodegroup is not fully understood.
Revision history for this message
Blake Rouse (blake-rouse) wrote : | # |
It means the its no longer managed, it doesnt mean that the subnets that are available on that subnet are dead.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/models/node.py' |
2 | --- src/maasserver/models/node.py 2015-09-11 03:31:23 +0000 |
3 | +++ src/maasserver/models/node.py 2015-09-11 19:14:12 +0000 |
4 | @@ -33,6 +33,7 @@ |
5 | PermissionDenied, |
6 | ValidationError, |
7 | ) |
8 | +from django.db import connection |
9 | from django.db.models import ( |
10 | BigIntegerField, |
11 | BooleanField, |
12 | @@ -1844,6 +1845,84 @@ |
13 | if interface.enabled: |
14 | interface.ensure_link_up() |
15 | |
16 | + def get_best_guess_for_default_gateway_ips(self): |
17 | + """Return the best guess for the default gateway IP addresses. This is |
18 | + either one IPv4 address, one IPv6 address, or both. |
19 | + |
20 | + This is determined by looking at all interfaces on the node and |
21 | + selecting the best possible default gateway IP. The criteria below |
22 | + is used to select the best possible gateway: |
23 | + 1. Managed subnets over unmanaged subnets. |
24 | + 2. Bond interfaces over physical interfaces. |
25 | + 3. Node's boot interface over all other interfaces except bonds. |
26 | + 4. Physical interfaces over VLAN interfaces. |
27 | + 5. Sticky IP links over user reserved IP links. |
28 | + 6. User reserved IP links over auto IP links. |
29 | + |
30 | + :return: List of tuples with (interface ID, subnet ID, and gateway IP) |
31 | + :rtype: list |
32 | + """ |
33 | + cursor = connection.cursor() |
34 | + |
35 | + # DISTINCT ON returns the first matching row for any given |
36 | + # IP family. Using the query's ordering. |
37 | + # |
38 | + # For nodes that have disable_ipv4 set, leave out any IPv4 address. |
39 | + cursor.execute(""" |
40 | + SELECT DISTINCT ON (family(subnet.gateway_ip)) |
41 | + interface.id, subnet.id, subnet.gateway_ip |
42 | + FROM maasserver_node AS node |
43 | + JOIN maasserver_interface AS interface ON |
44 | + interface.node_id = node.id |
45 | + JOIN maasserver_interface_ip_addresses AS link ON |
46 | + link.interface_id = interface.id |
47 | + JOIN maasserver_staticipaddress AS staticip ON |
48 | + staticip.id = link.staticipaddress_id |
49 | + JOIN maasserver_subnet AS subnet ON |
50 | + subnet.id = staticip.subnet_id |
51 | + LEFT JOIN maasserver_nodegroupinterface AS ngi ON |
52 | + ngi.subnet_id = subnet.id |
53 | + LEFT JOIN maasserver_nodegroup AS nodegroup ON |
54 | + nodegroup.id = ngi.nodegroup_id |
55 | + WHERE |
56 | + node.id = %s AND |
57 | + subnet.gateway_ip IS NOT NULL AND |
58 | + host(subnet.gateway_ip) != '' AND |
59 | + staticip.alloc_type != 5 AND /* Ignore DHCP */ |
60 | + staticip.alloc_type != 6 AND /* Ignore DISCOVERED */ |
61 | + ( |
62 | + node.disable_ipv4 IS FALSE OR |
63 | + family(subnet.gateway_ip) <> 4 |
64 | + ) |
65 | + ORDER BY |
66 | + family(subnet.gateway_ip), |
67 | + nodegroup.status, |
68 | + ngi.management DESC, |
69 | + CASE |
70 | + WHEN interface.type = 'bond' THEN 1 |
71 | + WHEN interface.type = 'physical' AND |
72 | + interface.id = node.boot_interface_id THEN 2 |
73 | + WHEN interface.type = 'physical' THEN 3 |
74 | + WHEN interface.type = 'vlan' THEN 4 |
75 | + WHEN interface.type = 'alias' THEN 5 |
76 | + ELSE 6 |
77 | + END, |
78 | + CASE |
79 | + WHEN staticip.alloc_type = 1 /* STICKY */ |
80 | + THEN 1 |
81 | + WHEN staticip.alloc_type = 4 /* USER_RESERVED */ |
82 | + THEN 2 |
83 | + WHEN staticip.alloc_type = 0 /* AUTO */ |
84 | + THEN 3 |
85 | + ELSE staticip.alloc_type |
86 | + END, |
87 | + interface.id |
88 | + """, (self.id,)) |
89 | + return [ |
90 | + (found[0], found[1], found[2]) |
91 | + for found in cursor.fetchall() |
92 | + ] |
93 | + |
94 | def get_boot_purpose(self): |
95 | """ |
96 | Return a suitable "purpose" for this boot, e.g. "install". |
97 | |
98 | === modified file 'src/maasserver/models/tests/test_node.py' |
99 | --- src/maasserver/models/tests/test_node.py 2015-09-11 03:31:23 +0000 |
100 | +++ src/maasserver/models/tests/test_node.py 2015-09-11 19:14:12 +0000 |
101 | @@ -2614,6 +2614,228 @@ |
102 | self.assertItemsEqual(enabled_interfaces, observed_interfaces) |
103 | |
104 | |
105 | +class TestGetBestGuessForDefaultGatewayIPs(MAASServerTestCase): |
106 | + |
107 | + def test__simple(self): |
108 | + node = factory.make_Node_with_Interface_on_Subnet( |
109 | + status=NODE_STATUS.READY, disable_ipv4=False) |
110 | + boot_interface = node.get_boot_interface() |
111 | + managed_subnet = boot_interface.ip_addresses.filter( |
112 | + alloc_type=IPADDRESS_TYPE.AUTO).first().subnet |
113 | + gateway_ip = managed_subnet.gateway_ip |
114 | + self.assertEquals( |
115 | + [(boot_interface.id, managed_subnet.id, gateway_ip)], |
116 | + node.get_best_guess_for_default_gateway_ips()) |
117 | + |
118 | + def test__ipv4_and_ipv6(self): |
119 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
120 | + node = factory.make_Node( |
121 | + status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False) |
122 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
123 | + network_v4 = factory.make_ipv4_network() |
124 | + subnet_v4 = factory.make_Subnet(cidr=unicode(network_v4.cidr)) |
125 | + factory.make_NodeGroupInterface( |
126 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
127 | + subnet=subnet_v4) |
128 | + network_v6 = factory.make_ipv6_network() |
129 | + subnet_v6 = factory.make_Subnet(cidr=unicode(network_v6.cidr)) |
130 | + factory.make_NodeGroupInterface( |
131 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
132 | + subnet=subnet_v6) |
133 | + factory.make_StaticIPAddress( |
134 | + alloc_type=IPADDRESS_TYPE.STICKY, |
135 | + ip=factory.pick_ip_in_network(network_v4), |
136 | + subnet=subnet_v4, interface=interface) |
137 | + factory.make_StaticIPAddress( |
138 | + alloc_type=IPADDRESS_TYPE.STICKY, |
139 | + ip=factory.pick_ip_in_network(network_v6), |
140 | + subnet=subnet_v6, interface=interface) |
141 | + self.assertItemsEqual([ |
142 | + (interface.id, subnet_v4.id, subnet_v4.gateway_ip), |
143 | + (interface.id, subnet_v6.id, subnet_v6.gateway_ip), |
144 | + ], node.get_best_guess_for_default_gateway_ips()) |
145 | + |
146 | + def test__only_one(self): |
147 | + node = factory.make_Node_with_Interface_on_Subnet( |
148 | + status=NODE_STATUS.READY, disable_ipv4=False) |
149 | + boot_interface = node.get_boot_interface() |
150 | + managed_subnet = boot_interface.ip_addresses.filter( |
151 | + alloc_type=IPADDRESS_TYPE.AUTO).first().subnet |
152 | + # Give it two IP addresses on the same subnet. |
153 | + factory.make_StaticIPAddress( |
154 | + alloc_type=IPADDRESS_TYPE.STICKY, |
155 | + ip=factory.pick_ip_in_network(managed_subnet.get_ipnetwork()), |
156 | + subnet=managed_subnet, interface=boot_interface) |
157 | + gateway_ip = managed_subnet.gateway_ip |
158 | + self.assertEquals( |
159 | + [(boot_interface.id, managed_subnet.id, gateway_ip)], |
160 | + node.get_best_guess_for_default_gateway_ips()) |
161 | + |
162 | + def test__managed_subnet_over_unmanaged(self): |
163 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
164 | + node = factory.make_Node( |
165 | + status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False) |
166 | + interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
167 | + unmanaged_network = factory.make_ipv4_network() |
168 | + unmanaged_subnet = factory.make_Subnet( |
169 | + cidr=unicode(unmanaged_network.cidr)) |
170 | + factory.make_StaticIPAddress( |
171 | + alloc_type=IPADDRESS_TYPE.STICKY, |
172 | + ip=factory.pick_ip_in_network(unmanaged_network), |
173 | + subnet=unmanaged_subnet, interface=interface) |
174 | + managed_network = factory.make_ipv4_network() |
175 | + managed_subnet = factory.make_Subnet( |
176 | + cidr=unicode(managed_network.cidr)) |
177 | + factory.make_NodeGroupInterface( |
178 | + nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP, |
179 | + subnet=managed_subnet) |
180 | + factory.make_StaticIPAddress( |
181 | + alloc_type=IPADDRESS_TYPE.STICKY, |
182 | + ip=factory.pick_ip_in_network(managed_network), |
183 | + subnet=managed_subnet, interface=interface) |
184 | + gateway_ip = managed_subnet.gateway_ip |
185 | + self.assertEquals( |
186 | + [(interface.id, managed_subnet.id, gateway_ip)], |
187 | + node.get_best_guess_for_default_gateway_ips()) |
188 | + |
189 | + def test__bond_over_physical_interface(self): |
190 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
191 | + node = factory.make_Node( |
192 | + status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False) |
193 | + physical_interface = factory.make_Interface( |
194 | + INTERFACE_TYPE.PHYSICAL, node=node) |
195 | + physical_network = factory.make_ipv4_network() |
196 | + physical_subnet = factory.make_Subnet( |
197 | + cidr=unicode(physical_network.cidr)) |
198 | + factory.make_StaticIPAddress( |
199 | + alloc_type=IPADDRESS_TYPE.STICKY, |
200 | + ip=factory.pick_ip_in_network(physical_network), |
201 | + subnet=physical_subnet, interface=physical_interface) |
202 | + parent_interfaces = [ |
203 | + factory.make_Interface(INTERFACE_TYPE.PHYSICAL, node=node) |
204 | + for _ in range(2) |
205 | + ] |
206 | + bond_interface = factory.make_Interface( |
207 | + INTERFACE_TYPE.BOND, node=node, parents=parent_interfaces) |
208 | + bond_network = factory.make_ipv4_network() |
209 | + bond_subnet = factory.make_Subnet( |
210 | + cidr=unicode(bond_network.cidr)) |
211 | + factory.make_StaticIPAddress( |
212 | + alloc_type=IPADDRESS_TYPE.STICKY, |
213 | + ip=factory.pick_ip_in_network(bond_network), |
214 | + subnet=bond_subnet, interface=bond_interface) |
215 | + gateway_ip = bond_subnet.gateway_ip |
216 | + self.assertEquals( |
217 | + [(bond_interface.id, bond_subnet.id, gateway_ip)], |
218 | + node.get_best_guess_for_default_gateway_ips()) |
219 | + |
220 | + def test__physical_over_vlan_interface(self): |
221 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
222 | + node = factory.make_Node( |
223 | + status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False) |
224 | + physical_interface = factory.make_Interface( |
225 | + INTERFACE_TYPE.PHYSICAL, node=node) |
226 | + physical_network = factory.make_ipv4_network() |
227 | + physical_subnet = factory.make_Subnet( |
228 | + cidr=unicode(physical_network.cidr)) |
229 | + factory.make_StaticIPAddress( |
230 | + alloc_type=IPADDRESS_TYPE.STICKY, |
231 | + ip=factory.pick_ip_in_network(physical_network), |
232 | + subnet=physical_subnet, interface=physical_interface) |
233 | + vlan_interface = factory.make_Interface( |
234 | + INTERFACE_TYPE.VLAN, node=node, parents=[physical_interface]) |
235 | + vlan_network = factory.make_ipv4_network() |
236 | + vlan_subnet = factory.make_Subnet( |
237 | + cidr=unicode(vlan_network.cidr)) |
238 | + factory.make_StaticIPAddress( |
239 | + alloc_type=IPADDRESS_TYPE.STICKY, |
240 | + ip=factory.pick_ip_in_network(vlan_network), |
241 | + subnet=vlan_subnet, interface=vlan_interface) |
242 | + gateway_ip = physical_subnet.gateway_ip |
243 | + self.assertEquals( |
244 | + [(physical_interface.id, physical_subnet.id, gateway_ip)], |
245 | + node.get_best_guess_for_default_gateway_ips()) |
246 | + |
247 | + def test__boot_interface_over_other_interfaces(self): |
248 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
249 | + node = factory.make_Node( |
250 | + status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False) |
251 | + physical_interface = factory.make_Interface( |
252 | + INTERFACE_TYPE.PHYSICAL, node=node) |
253 | + physical_network = factory.make_ipv4_network() |
254 | + physical_subnet = factory.make_Subnet( |
255 | + cidr=unicode(physical_network.cidr)) |
256 | + factory.make_StaticIPAddress( |
257 | + alloc_type=IPADDRESS_TYPE.STICKY, |
258 | + ip=factory.pick_ip_in_network(physical_network), |
259 | + subnet=physical_subnet, interface=physical_interface) |
260 | + boot_interface = factory.make_Interface( |
261 | + INTERFACE_TYPE.PHYSICAL, node=node) |
262 | + boot_network = factory.make_ipv4_network() |
263 | + boot_subnet = factory.make_Subnet( |
264 | + cidr=unicode(boot_network.cidr)) |
265 | + factory.make_StaticIPAddress( |
266 | + alloc_type=IPADDRESS_TYPE.STICKY, |
267 | + ip=factory.pick_ip_in_network(boot_network), |
268 | + subnet=boot_subnet, interface=boot_interface) |
269 | + node.boot_interface = boot_interface |
270 | + node.save() |
271 | + gateway_ip = boot_subnet.gateway_ip |
272 | + self.assertEquals( |
273 | + [(boot_interface.id, boot_subnet.id, gateway_ip)], |
274 | + node.get_best_guess_for_default_gateway_ips()) |
275 | + |
276 | + def test__sticky_ip_over_user_reserved(self): |
277 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
278 | + node = factory.make_Node( |
279 | + status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False) |
280 | + interface = factory.make_Interface( |
281 | + INTERFACE_TYPE.PHYSICAL, node=node) |
282 | + sticky_network = factory.make_ipv4_network() |
283 | + sticky_subnet = factory.make_Subnet( |
284 | + cidr=unicode(sticky_network.cidr)) |
285 | + factory.make_StaticIPAddress( |
286 | + alloc_type=IPADDRESS_TYPE.STICKY, |
287 | + ip=factory.pick_ip_in_network(sticky_network), |
288 | + subnet=sticky_subnet, interface=interface) |
289 | + user_reserved_network = factory.make_ipv4_network() |
290 | + user_reserved_subnet = factory.make_Subnet( |
291 | + cidr=unicode(user_reserved_network.cidr)) |
292 | + factory.make_StaticIPAddress( |
293 | + alloc_type=IPADDRESS_TYPE.USER_RESERVED, user=factory.make_User(), |
294 | + ip=factory.pick_ip_in_network(user_reserved_network), |
295 | + subnet=user_reserved_subnet, interface=interface) |
296 | + gateway_ip = sticky_subnet.gateway_ip |
297 | + self.assertEquals( |
298 | + [(interface.id, sticky_subnet.id, gateway_ip)], |
299 | + node.get_best_guess_for_default_gateway_ips()) |
300 | + |
301 | + def test__user_reserved_ip_over_auto(self): |
302 | + nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED) |
303 | + node = factory.make_Node( |
304 | + status=NODE_STATUS.READY, nodegroup=nodegroup, disable_ipv4=False) |
305 | + interface = factory.make_Interface( |
306 | + INTERFACE_TYPE.PHYSICAL, node=node) |
307 | + user_reserved_network = factory.make_ipv4_network() |
308 | + user_reserved_subnet = factory.make_Subnet( |
309 | + cidr=unicode(user_reserved_network.cidr)) |
310 | + factory.make_StaticIPAddress( |
311 | + alloc_type=IPADDRESS_TYPE.USER_RESERVED, user=factory.make_User(), |
312 | + ip=factory.pick_ip_in_network(user_reserved_network), |
313 | + subnet=user_reserved_subnet, interface=interface) |
314 | + auto_network = factory.make_ipv4_network() |
315 | + auto_subnet = factory.make_Subnet( |
316 | + cidr=unicode(auto_network.cidr)) |
317 | + factory.make_StaticIPAddress( |
318 | + alloc_type=IPADDRESS_TYPE.AUTO, |
319 | + ip=factory.pick_ip_in_network(auto_network), |
320 | + subnet=auto_subnet, interface=interface) |
321 | + gateway_ip = user_reserved_subnet.gateway_ip |
322 | + self.assertEquals( |
323 | + [(interface.id, user_reserved_subnet.id, gateway_ip)], |
324 | + node.get_best_guess_for_default_gateway_ips()) |
325 | + |
326 | + |
327 | class TestDeploymentStatus(MAASServerTestCase): |
328 | """Tests for node.get_deployment_status.""" |
329 |
Looks nice. I've got a few minor comments.