Merge lp:~blake-rouse/maas/pick-best-gateway-ip into lp:~maas-committers/maas/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
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
Mike Pontillo (mpontillo) wrote :

Looks nice. I've got a few minor comments.

review: Approve
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