Merge lp:~mpontillo/maas/allow-multiple-macs-on-the-same-node into lp:~maas-committers/maas/trunk

Proposed by Mike Pontillo
Status: Rejected
Rejected by: MAAS Lander
Proposed branch: lp:~mpontillo/maas/allow-multiple-macs-on-the-same-node
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 222 lines (+101/-51)
4 files modified
src/maasserver/models/interface.py (+0/-13)
src/maasserver/models/node.py (+2/-1)
src/maasserver/rpc/events.py (+4/-5)
src/metadataserver/builtin_scripts/hooks.py (+95/-32)
To merge this branch: bzr merge lp:~mpontillo/maas/allow-multiple-macs-on-the-same-node
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+319073@code.launchpad.net

Commit message

Allow multiple physical interfaces to have the same MAC if they are on the same node.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

Transitioned to Git.

lp:maas has now moved from Bzr to Git.
Please propose your branches with Launchpad using Git.

git clone https://git.launchpad.net/maas

Unmerged revisions

5780. By Mike Pontillo

Merge trunk.

5779. By Mike Pontillo

Allow multiple physical interfaces to have the same MAC if they are on the same node.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/models/interface.py'
--- src/maasserver/models/interface.py 2017-02-17 14:23:04 +0000
+++ src/maasserver/models/interface.py 2017-03-06 14:57:51 +0000
@@ -1403,19 +1403,6 @@
1403 if len(validation_errors) > 0:1403 if len(validation_errors) > 0:
1404 raise ValidationError(validation_errors)1404 raise ValidationError(validation_errors)
14051405
1406 # MAC address must be unique amongst every PhysicalInterface.
1407 other_interfaces = PhysicalInterface.objects.filter(
1408 mac_address=self.mac_address)
1409 if self.id is not None:
1410 other_interfaces = other_interfaces.exclude(id=self.id)
1411 other_interfaces = other_interfaces.all()
1412 if len(other_interfaces) > 0:
1413 raise ValidationError({
1414 "mac_address": [
1415 "This MAC address is already in use by %s." % (
1416 other_interfaces[0].get_log_string())]
1417 })
1418
1419 # No parents are allow for a physical interface.1406 # No parents are allow for a physical interface.
1420 if self.id is not None:1407 if self.id is not None:
1421 # Use the precache so less queries are made.1408 # Use the precache so less queries are made.
14221409
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2017-03-02 00:22:23 +0000
+++ src/maasserver/models/node.py 2017-03-06 14:57:51 +0000
@@ -1625,7 +1625,8 @@
1625 UnknownInterface.objects.filter(mac_address=mac).delete()1625 UnknownInterface.objects.filter(mac_address=mac).delete()
1626 try:1626 try:
1627 iface = PhysicalInterface.objects.get(mac_address=mac)1627 iface = PhysicalInterface.objects.get(mac_address=mac)
1628 except PhysicalInterface.DoesNotExist:1628 except (PhysicalInterface.MultipleObjectsReturned,
1629 PhysicalInterface.DoesNotExist):
1629 return PhysicalInterface.objects.create(1630 return PhysicalInterface.objects.create(
1630 node=self, mac_address=mac, name=name)1631 node=self, mac_address=mac, name=name)
1631 if iface.node != self:1632 if iface.node != self:
16321633
=== modified file 'src/maasserver/rpc/events.py'
--- src/maasserver/rpc/events.py 2016-10-25 13:57:02 +0000
+++ src/maasserver/rpc/events.py 2017-03-06 14:57:51 +0000
@@ -75,12 +75,11 @@
75 except EventType.DoesNotExist:75 except EventType.DoesNotExist:
76 raise NoSuchEventType.from_name(type_name)76 raise NoSuchEventType.from_name(type_name)
7777
78 try:78 interface = Interface.objects.filter(
79 interface = Interface.objects.get(79 type=INTERFACE_TYPE.PHYSICAL, mac_address=mac_address).first()
80 type=INTERFACE_TYPE.PHYSICAL, mac_address=mac_address)80 if interface is None:
81 except Interface.DoesNotExist:
82 # The node doesn't exist, but we don't raise an exception - it's81 # The node doesn't exist, but we don't raise an exception - it's
83 # entirely possible the cluster has started sending events for a node82 # entirely possible the rack has started sending events for a node
84 # that we don't know about yet. This is most likely to happen when a83 # that we don't know about yet. This is most likely to happen when a
85 # new node is trying to enlist.84 # new node is trying to enlist.
86 maaslog.debug(85 maaslog.debug(
8786
=== modified file 'src/metadataserver/builtin_scripts/hooks.py'
--- src/metadataserver/builtin_scripts/hooks.py 2017-03-06 13:32:48 +0000
+++ src/metadataserver/builtin_scripts/hooks.py 2017-03-06 14:57:51 +0000
@@ -7,6 +7,7 @@
7 'NODE_INFO_SCRIPTS',7 'NODE_INFO_SCRIPTS',
8 ]8 ]
99
10from collections import defaultdict
10import fnmatch11import fnmatch
11import json12import json
12import logging13import logging
@@ -91,6 +92,31 @@
91 return interface92 return interface
9293
9394
95def get_mac_to_ifnames_dict(ip_addr_info):
96 mac_to_ifnames = defaultdict(list)
97 for link in ip_addr_info.values():
98 mac = link.get('mac')
99 if mac is None:
100 # Ignore loopback interfaces.
101 continue
102 else:
103 ifname = link['name']
104 mac_to_ifnames[mac].append(ifname)
105 return mac_to_ifnames
106
107
108def clean_up_moved_nics(node, mac):
109 interfaces = PhysicalInterface.objects.filter(
110 mac_address=mac).exclude(node=node)
111 for interface in interfaces:
112 logger.warning(
113 "Interface with MAC %s moved from node %s to %s. "
114 "(The existing interface will be deleted.)" %
115 (interface.mac_address, interface.node.fqdn,
116 node.fqdn))
117 interface.delete()
118
119
94def update_node_network_information(node, output, exit_status):120def update_node_network_information(node, output, exit_status):
95 """Updates the network interfaces from the results of `IPADDR_SCRIPT`.121 """Updates the network interfaces from the results of `IPADDR_SCRIPT`.
96122
@@ -107,50 +133,87 @@
107 node.hostname, exit_status))133 node.hostname, exit_status))
108 return134 return
109 assert isinstance(output, bytes)135 assert isinstance(output, bytes)
110
111 # Skip network configuration if set by the user.136 # Skip network configuration if set by the user.
112 if node.skip_networking:137 if node.skip_networking:
113 return138 return
114
115 # Get the MAC addresses of all connected interfaces.139 # Get the MAC addresses of all connected interfaces.
116 ip_addr_info = parse_ip_addr(output)140 ip_addr_info = parse_ip_addr(output)
141 # Prepare a set of interfaces found during commissioning, so that any
142 # physical interfaces no longer present on the machine can be removed.
117 current_interfaces = set()143 current_interfaces = set()
118144 # Pre-process the list to check if multiple physical interfaces share the
119 for link in ip_addr_info.values():145 # same MAC.
120 link_mac = link.get('mac')146 mac_to_ifnames = get_mac_to_ifnames_dict(ip_addr_info)
121 # Ignore loopback interfaces.147 # Go through the map of { ifname: [mac, ...] } to determine the best course
122 if link_mac is None:148 # of action. If interface names correspond 1:1 with MACs (the normal case)
123 continue149 # we can avoid losing data (such as child interfaces created by a MAAS
124 else:150 # admin by simply renaming interfaces).
125 ifname = link['name']151 for mac, ifnames in mac_to_ifnames.items():
126 try:152 clean_up_moved_nics(node, mac)
153 existing_iface_count = PhysicalInterface.objects.filter(
154 mac_address=mac, node=node).count()
155 if len(ifnames) == 1:
156 ifname = ifnames[0]
157 if existing_iface_count == 0:
158 # Found a single interface with this MAC, and no interface
159 # was already on the node. Just go ahead and create it.
160 interface = _create_default_physical_interface(
161 node, ifname, mac)
162 ip_addr_info[ifname]['model'] = interface
163 elif existing_iface_count == 1:
164 # Found an interface that was created by a previous
165 # commissioning, so don't throw away the data. Just change the
166 # name if necessary.
127 interface = PhysicalInterface.objects.get(167 interface = PhysicalInterface.objects.get(
128 mac_address=link_mac)168 mac_address=mac, node=node)
129 if interface.node is not None and interface.node != node:169 if interface.name != ifname:
130 logger.warning(170 interface.name = ifname
131 "Interface with MAC %s moved from node %s to %s. "171 interface.save()
132 "(The existing interface will be deleted.)" %172 ip_addr_info[ifname]['model'] = interface
133 (interface.mac_address, interface.node.fqdn,173 else:
134 node.fqdn))174 # Previously there were multiple physical interfaces with the
175 # same MAC, but now there is only one. Might as well start
176 # fresh.
177 for iface in PhysicalInterface.objects.filter(
178 mac_address=mac, node=node):
179 iface.delete()
180 interface = _create_default_physical_interface(
181 node, ifname, mac)
182 ip_addr_info[ifname]['model'] = interface
183 else:
184 # Multiple interfaces found with the same MAC.
185 # This means we shouldn't try to preserve renamed interfaces.
186 existing = set()
187 for interface in PhysicalInterface.objects.filter(
188 mac_address=mac, node=node):
189 ifname = interface.name
190 if interface.name not in ifnames:
191 # Interface has been removed.
135 interface.delete()192 interface.delete()
193 else:
194 # Existing interface with the same name as an interface
195 # we just found during commissioning. Make note of it.
196 existing.add(interface.name)
197 ip_addr_info[ifname]['model'] = interface
198 for ifname in ifnames:
199 # Go through the list of interfaces with a shared MAC whose
200 # names don't match any existing interface names and create
201 # them.
202 if ifname not in existing:
136 interface = _create_default_physical_interface(203 interface = _create_default_physical_interface(
137 node, ifname, link_mac)204 node, ifname, mac)
138 else:205 ip_addr_info[ifname]['model'] = interface
139 # Interface already exists on this Node, so just update206 # Record any observed IP addresses on the interfaces we created.
140 # the name.207 for link in ip_addr_info.values():
141 interface.name = ifname208 interface = link.get('model')
142 interface.save()209 if interface is not None:
143 except PhysicalInterface.DoesNotExist:
144 interface = _create_default_physical_interface(
145 node, ifname, link_mac)
146
147 current_interfaces.add(interface)210 current_interfaces.add(interface)
148 ips = link.get('inet', []) + link.get('inet6', [])211 ips = link.get('inet', []) + link.get('inet6', [])
149 interface.update_ip_addresses(ips)212 interface.update_ip_addresses(ips)
150213 # Delete any obsolete interfaces on this node.
151 for iface in PhysicalInterface.objects.filter(node=node):214 for interface in PhysicalInterface.objects.filter(node=node):
152 if iface not in current_interfaces:215 if interface not in current_interfaces:
153 iface.delete()216 interface.delete()
154217
155218
156def update_node_network_interface_tags(node, output, exit_status):219def update_node_network_interface_tags(node, output, exit_status):