def test_ignores_whitespace_lines(self): testdata = dedent( """ 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \ mode DEFAULT group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff """ ) ip_link = parse_ip_addr(testdata) # Sanity check to ensure some data exists self.assertIsNotNone(ip_link.get("lo")) self.assertIsNotNone(ip_link.get("eth0")) self.assertIsNotNone(ip_link["eth0"].get("mac"))
def test_parses_xenial_interfaces(self): testdata = dedent( """ 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN \ group default link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP \ group default qlen 1000 link/ether 52:54:00:2d:39:49 brd ff:ff:ff:ff:ff:ff inet 172.16.100.108/24 brd 172.16.100.255 scope global ens3 valid_lft forever preferred_lft forever inet6 fe80::5054:ff:fe2d:3949/64 scope link valid_lft forever preferred_lft forever 3: ens10: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN \ group default qlen 1000 link/ether 52:54:00:e5:c6:6b brd ff:ff:ff:ff:ff:ff 4: ens11: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN \ group default qlen 1000 link/ether 52:54:00:ed:9f:9d brd ff:ff:ff:ff:ff:ff """ ) ip_link = parse_ip_addr(testdata) self.assertEqual(2, ip_link["ens3"]["index"]) self.assertEqual("52:54:00:2d:39:49", ip_link["ens3"]["mac"]) self.assertEqual(["172.16.100.108/24"], ip_link["ens3"]["inet"]) self.assertEqual(3, ip_link["ens10"]["index"]) self.assertEqual("52:54:00:e5:c6:6b", ip_link["ens10"]["mac"]) self.assertThat(ip_link["ens10"], Not(Contains("inet"))) self.assertEqual(4, ip_link["ens11"]["index"]) self.assertEqual("52:54:00:ed:9f:9d", ip_link["ens11"]["mac"]) self.assertThat(ip_link["ens11"], Not(Contains("inet")))
def test_parses_mac(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff """) ip_link = parse_ip_addr(testdata) self.assertEqual('80:fa:5c:0d:43:5e', ip_link['eth0']['mac'])
def test_parses_enabled_state(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff """) ip_link = parse_ip_addr(testdata) self.assertTrue(ip_link["eth0"]["enabled"])
def _parse_interfaces(node, data): """Return a dict of interfaces keyed by MAC address.""" interfaces = {} # Retrieve informaton from IPADDR_OUTPUT_NAME script script_set = node.current_commissioning_script_set script_result = script_set.find_script_result( script_name=IPADDR_OUTPUT_NAME ) # Only the IP addresses come from ip addr output. While this information # is useful for automatically associating a device with a VLAN and subnet # it is not necessary for use as users can manually do it. if not script_result or script_result.status != SCRIPT_STATUS.PASSED: # Pods don't currently have ip addr information, don't warn about it. if not node.is_pod: logger.error( "%s: Unable to discover NIC IP addresses due to missing " "passed output from %s" % (node.hostname, IPADDR_OUTPUT_NAME) ) ip_addr_info = {} else: ip_addr_info = parse_ip_addr(script_result.output) network_cards = data.get("network", {}).get("cards", {}) for card in network_cards: for port in card.get("ports", {}): mac = port.get("address") if mac in (None, SWITCH_OPENBMC_MAC): # Ignore loopback (with no MAC) and OpenBMC interfaces on # switches which all share the same, hard-coded OpenBMC MAC # address. continue interface = { "name": port.get("id"), "link_connected": port.get("link_detected"), "interface_speed": _parse_interface_speed(port), "link_speed": port.get("link_speed", 0), "numa_node": card.get("numa_node", 0), "vendor": card.get("vendor"), "product": card.get("product"), "firmware_version": card.get("firmware_version"), "sriov_max_vf": card.get("sriov", {}).get("maximum_vfs", 0), } # Assign the IP addresses to this interface link = ip_addr_info.get(interface["name"], {}) interface["ips"] = link.get("inet", []) + link.get("inet6", []) if mac in interfaces: raise DuplicateMACs( f"Duplicated MAC address({mac}) on " "{interfaces[mac]['name']} and {interface['name']}" ) else: interfaces[mac] = interface return interfaces
def test_skips_ipv4_link_local(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff inet 169.254.1.4/16 brd 192.168.0.255 scope global eth0 valid_lft forever preferred_lft forever """) ip_link = parse_ip_addr(testdata) self.assertIsNone(ip_link['eth0'].get('inet'))
def test_skips_ipv6_link_local(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff inet6 fe80::3e97:eff:fe0e:56dc/64 scope link valid_lft forever preferred_lft forever """) ip_link = parse_ip_addr(testdata) self.assertIsNone(ip_link['eth0'].get('inet6'))
def test_parses_flags(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff """) ip_link = parse_ip_addr(testdata) flags = ip_link['eth0'].get('flags') self.assertIsNotNone(flags) self.assertThat(set(flags), Equals({'BROADCAST', 'MULTICAST', 'UP', 'LOWER_UP'}))
def test_handles_wlan_flags(self): testdata = dedent(""" 2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff inet6 fe80::3e97:eff:fe0e:56dc/64 scope link valid_lft forever preferred_lft forever inet 192.168.2.112/24 brd 192.168.2.255 scope global dynamic wlan0 valid_lft forever preferred_lft forever """) ip_link = parse_ip_addr(testdata) inet = ip_link['wlan0'].get('inet') self.assertEqual(['192.168.2.112/24'], inet)
def test_parses_inet6(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff inet 192.168.0.3/24 brd 192.168.0.255 scope global eth0 valid_lft forever preferred_lft forever inet6 2001:db8:85a3:8d3:1319:8a2e:370:7348/64 scope link valid_lft forever preferred_lft forever """) ip_link = parse_ip_addr(testdata) inet = ip_link['eth0'].get('inet6') self.assertEqual(['2001:db8:85a3:8d3:1319:8a2e:370:7348/64'], inet)
def test_parses_inet(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff inet 192.168.0.3/24 brd 192.168.0.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::3e97:eff:fe0e:56dc/64 scope link valid_lft forever preferred_lft forever """) ip_link = parse_ip_addr(testdata) inet = ip_link["eth0"].get("inet") self.assertEqual(["192.168.0.3/24"], inet)
def test__populates_interface_type_for_each_interface(self): # Note: this is more of an end-to-end test, since we call # "/sbin/ip addr" on the host running the tests. ip_addr_output = check_output(['ip', 'addr']) interfaces = parse_ip_addr(ip_addr_output) interfaces_with_types = annotate_with_driver_information(interfaces) for name in interfaces: iface = interfaces_with_types[name] self.assertThat(iface, Contains('type')) if iface['type'] == 'ethernet.vlan': self.expectThat(iface, Contains('vid')) elif iface['type'] == 'ethernet.bond': self.expectThat(iface, Contains('bonded_interfaces')) elif iface['type'] == 'ethernet.bridge': self.expectThat(iface, Contains('bridged_interfaces'))
def test__populates_interface_type_for_each_interface(self): # Note: this is more of an end-to-end test, since we call # "/sbin/ip addr" on the host running the tests. ip_addr_output = check_output(["ip", "addr"]) interfaces = parse_ip_addr(ip_addr_output) interfaces_with_types = annotate_with_driver_information(interfaces) for name in interfaces: iface = interfaces_with_types[name] self.assertThat(iface, Contains("type")) if iface["type"] == "ethernet.vlan": self.expectThat(iface, Contains("vid")) elif iface["type"] == "ethernet.bond": self.expectThat(iface, Contains("bonded_interfaces")) elif iface["type"] == "ethernet.bridge": self.expectThat(iface, Contains("bridged_interfaces"))
def test_parses_settings(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff """) ip_link = parse_ip_addr(testdata) settings = ip_link['eth0'].get('settings') self.assertIsNotNone(settings) self.assertThat(settings, Equals({ 'mtu': '1500', 'qdisc': 'pfifo_fast', 'state': 'UP', 'mode': 'DEFAULT', 'group': 'default', 'qlen': '1000', }))
def test_parses_multiple_interfaces(self): testdata = dedent( """ 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff inet 192.168.0.3/24 brd 192.168.0.255 scope global eth0 valid_lft forever preferred_lft forever inet6 2001:db8:85a3:8d3:1319:8a2e:370:7350/64 scope link valid_lft forever preferred_lft forever 3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP \ mode DORMANT group default qlen 1000 link/ether 48:51:bb:7a:d5:e2 brd ff:ff:ff:ff:ff:ff inet 192.168.0.5/24 brd 192.168.0.255 scope global eth1 valid_lft forever preferred_lft forever inet6 2001:db8:85a3:8d3:1319:8a2e:370:7348/64 scope link valid_lft forever preferred_lft forever inet6 2001:db8:85a3:8d3:1319:8a2e:370:3645/64 scope global dynamic valid_lft forever preferred_lft forever inet6 2001:db8:85a3:8d3::1111/64 scope global tentative dynamic valid_lft forever preferred_lft forever inet6 2620:1:260::1/64 scope global valid_lft forever preferred_lft forever """ ) ip_link = parse_ip_addr(testdata) self.assertEqual(2, ip_link["eth0"]["index"]) self.assertEqual("80:fa:5c:0d:43:5e", ip_link["eth0"]["mac"]) self.assertEqual(["192.168.0.3/24"], ip_link["eth0"]["inet"]) self.assertEqual( ["2001:db8:85a3:8d3:1319:8a2e:370:7350/64"], ip_link["eth0"]["inet6"], ) self.assertEqual(3, ip_link["eth1"]["index"]) self.assertEqual("48:51:bb:7a:d5:e2", ip_link["eth1"]["mac"]) self.assertEqual(["192.168.0.5/24"], ip_link["eth1"]["inet"]) self.assertEqual( [ "2001:db8:85a3:8d3:1319:8a2e:370:7348/64", "2001:db8:85a3:8d3:1319:8a2e:370:3645/64", "2001:db8:85a3:8d3::1111/64", "2620:1:260::1/64", ], ip_link["eth1"]["inet6"], )
def _parse_interfaces(node, data): """Return a dict of interfaces keyed by MAC address.""" interfaces = {} # Retrieve informaton from IPADDR_SCRIPT script_set = node.current_commissioning_script_set script_result = script_set.find_script_result( script_name=IPADDR_OUTPUT_NAME ) if not script_result or script_result.status != SCRIPT_STATUS.PASSED: logger.error( "%s: Unable to discover NIC IP addresses due to missing " "passed output from %s" % (node.hostname, IPADDR_OUTPUT_NAME) ) assert isinstance(script_result.output, bytes) ip_addr_info = parse_ip_addr(script_result.output) network_cards = data.get("network", {}).get("cards", {}) for card in network_cards: for port in card.get("ports", {}): mac = port.get("address") if mac in (None, SWITCH_OPENBMC_MAC): # Ignore loopback (with no MAC) and OpenBMC interfaces on # switches which all share the same, hard-coded OpenBMC MAC # address. continue interface = { "name": port.get("id"), "link_connected": port.get("link_detected"), "interface_speed": _parse_interface_speed(port), "link_speed": port.get("link_speed", 0), "numa_node": card.get("numa_node", 0), "vendor": card.get("vendor"), "product": card.get("product"), "firmware_version": card.get("firmware_version"), "sriov_max_vf": card.get("sriov", {}).get("maximum_vfs", 0), } # Assign the IP addresses to this interface link = ip_addr_info[interface["name"]] interface["ips"] = link.get("inet", []) + link.get("inet6", []) interfaces[mac] = interface return interfaces
def _parse_interfaces(node, data): """Return a dict of interfaces keyed by MAC address.""" interfaces = {} # Retrieve informaton from IPADDR_SCRIPT script_set = node.current_commissioning_script_set script_result = script_set.find_script_result( script_name=IPADDR_OUTPUT_NAME) if not script_result or script_result.status != SCRIPT_STATUS.PASSED: logger.error('%s: Unable to discover NIC IP addresses due to missing ' 'passed output from %s' % (node.hostname, IPADDR_OUTPUT_NAME)) assert isinstance(script_result.output, bytes) ip_addr_info = parse_ip_addr(script_result.output) network_cards = data.get('network', {}).get('cards', {}) for card in network_cards: for port in card.get('ports', {}): mac = port.get('address') if mac in (None, SWITCH_OPENBMC_MAC): # Ignore loopback (with no MAC) and OpenBMC interfaces on # switches which all share the same, hard-coded OpenBMC MAC # address. continue interface = { 'name': port.get('id'), 'link_connected': port.get('link_detected'), 'interface_speed': _parse_interface_speed(port), 'link_speed': port.get('link_speed'), 'numa_node': card.get('numa_node', 0), 'vendor': card.get('vendor'), 'product': card.get('product'), 'firmware_version': card.get('firmware_version'), 'sriov_max_vf': card.get('sriov', {}).get('maximum_vfs', 0), } # Assign the IP addresses to this interface link = ip_addr_info[interface['name']] interface['ips'] = link.get('inet', []) + link.get('inet6', []) interfaces[mac] = interface return interfaces
def test_parses_settings(self): testdata = dedent(""" 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast \ state UP mode DEFAULT group default qlen 1000 link/ether 80:fa:5c:0d:43:5e brd ff:ff:ff:ff:ff:ff """) ip_link = parse_ip_addr(testdata) settings = ip_link["eth0"].get("settings") self.assertIsNotNone(settings) self.assertThat( settings, Equals({ "mtu": "1500", "qdisc": "pfifo_fast", "state": "UP", "mode": "DEFAULT", "group": "default", "qlen": "1000", }), )
def update_node_network_information(node, output, exit_status): """Updates the network interfaces from the results of `IPADDR_SCRIPT`. Creates and deletes an Interface according to what we currently know about this node's hardware. If `exit_status` is non-zero, this function returns without doing anything. """ if exit_status != 0: logger.error( "%s: node network information script failed with status: %s." % (node.hostname, exit_status)) return assert isinstance(output, bytes) # Skip network configuration if set by the user. if node.skip_networking: # Turn off skip_networking now that the hook has been called. node.skip_networking = False node.save(update_fields=['skip_networking']) return # Get the MAC addresses of all connected interfaces. ip_addr_info = parse_ip_addr(output) current_interfaces = set() extended_nic_info = parse_lshw_nic_info(node) for link in ip_addr_info.values(): link_mac = link.get('mac') # Ignore loopback interfaces. if link_mac is None: continue elif link_mac == SWITCH_OPENBMC_MAC: # Ignore OpenBMC interfaces on switches which all share the same, # hard-coded OpenBMC MAC address. continue else: ifname = link['name'] extra_info = extended_nic_info.get(link_mac, {}) try: interface = PhysicalInterface.objects.get(mac_address=link_mac) update_fields = [] if interface.node is not None and interface.node != node: logger.warning( "Interface with MAC %s moved from node %s to %s. " "(The existing interface will be deleted.)" % (interface.mac_address, interface.node.fqdn, node.fqdn)) interface.delete() interface = _create_default_physical_interface( node, ifname, link_mac, **extra_info) else: # Interface already exists on this Node, so just update # the name and NIC info. update_fields = [] if interface.name != ifname: interface.name = ifname update_fields.append('name') for k, v in extra_info.items(): if getattr(interface, k, v) != v: setattr(interface, k, v) update_fields.append(k) if update_fields: interface.save( update_fields=['updated', *update_fields]) except PhysicalInterface.DoesNotExist: interface = _create_default_physical_interface( node, ifname, link_mac, **extra_info) current_interfaces.add(interface) ips = link.get('inet', []) + link.get('inet6', []) interface.update_ip_addresses(ips) if 'NO-CARRIER' in link.get('flags', []): # This interface is now disconnected. if interface.vlan is not None: interface.vlan = None interface.save(update_fields=['vlan', 'updated']) # If a machine boots by UUID before commissioning(s390x) no boot_interface # will be set as interfaces existed during boot. Set it using the # boot_cluster_ip now that the interfaces have been created. if node.boot_interface is None and node.boot_cluster_ip is not None: subnet = Subnet.objects.get_best_subnet_for_ip(node.boot_cluster_ip) if subnet: node.boot_interface = node.interface_set.filter( id__in=[interface.id for interface in current_interfaces], vlan=subnet.vlan).first() node.save(update_fields=['boot_interface']) # Only configured Interfaces are tested so configuration must be done # before regeneration. node.set_initial_networking_configuration() # XXX ltrager 11-16-2017 - Don't regenerate ScriptResults on controllers. # Currently this is not needed saving us 1 database query. However, if # commissioning is ever enabled for controllers regeneration will need # to be allowed on controllers otherwise network testing may break. if node.current_testing_script_set is not None and not node.is_controller: # LP: #1731353 - Regenerate ScriptResults before deleting Interfaces. # This creates a ScriptResult with proper parameters for each interface # on the system. Interfaces no long available will be deleted which # causes a casade delete on their assoicated ScriptResults. node.current_testing_script_set.regenerate(storage=False, network=True) Interface.objects.filter(node=node).exclude( id__in=[iface.id for iface in current_interfaces]).delete()
def update_node_network_information(node, output, exit_status): """Updates the network interfaces from the results of `IPADDR_SCRIPT`. Creates and deletes an Interface according to what we currently know about this node's hardware. If `exit_status` is non-zero, this function returns without doing anything. """ if exit_status != 0: logger.error( "%s: node network information script failed with status: %s." % ( node.hostname, exit_status)) return assert isinstance(output, bytes) # Skip network configuration if set by the user. if node.skip_networking: # Turn off skip_networking now that the hook has been called. node.skip_networking = False node.save(update_fields=['skip_networking']) return # Get the MAC addresses of all connected interfaces. ip_addr_info = parse_ip_addr(output) current_interfaces = set() extended_nic_info = parse_lshw_nic_info(node) for link in ip_addr_info.values(): link_mac = link.get('mac') # Ignore loopback interfaces. if link_mac is None: continue elif link_mac == SWITCH_OPENBMC_MAC: # Ignore OpenBMC interfaces on switches which all share the same, # hard-coded OpenBMC MAC address. continue else: ifname = link['name'] extra_info = extended_nic_info.get(link_mac, {}) try: interface = PhysicalInterface.objects.get( mac_address=link_mac) update_fields = [] if interface.node is not None and interface.node != node: logger.warning( "Interface with MAC %s moved from node %s to %s. " "(The existing interface will be deleted.)" % (interface.mac_address, interface.node.fqdn, node.fqdn)) interface.delete() interface = _create_default_physical_interface( node, ifname, link_mac, **extra_info) else: # Interface already exists on this Node, so just update # the name and NIC info. update_fields = [] if interface.name != ifname: interface.name = ifname update_fields.append('name') for k, v in extra_info.items(): if getattr(interface, k, v) != v: setattr(interface, k, v) update_fields.append(k) if update_fields: interface.save( update_fields=['updated', *update_fields]) except PhysicalInterface.DoesNotExist: interface = _create_default_physical_interface( node, ifname, link_mac, **extra_info) current_interfaces.add(interface) ips = link.get('inet', []) + link.get('inet6', []) interface.update_ip_addresses(ips) if 'NO-CARRIER' in link.get('flags', []): # This interface is now disconnected. if interface.vlan is not None: interface.vlan = None interface.save(update_fields=['vlan', 'updated']) for iface in Interface.objects.filter(node=node): if iface not in current_interfaces: iface.delete() # If a machine boots by UUID before commissioning(s390x) no boot_interface # will be set as interfaces existed during boot. Set it using the # boot_cluster_ip now that the interfaces have been created. if node.boot_interface is None and node.boot_cluster_ip is not None: subnet = Subnet.objects.get_best_subnet_for_ip(node.boot_cluster_ip) if subnet: node.boot_interface = node.interface_set.filter( vlan=subnet.vlan).first() node.save(update_fields=['boot_interface'])
def update_node_network_information(node, output, exit_status): """Updates the network interfaces from the results of `IPADDR_SCRIPT`. Creates and deletes an Interface according to what we currently know about this node's hardware. If `exit_status` is non-zero, this function returns without doing anything. """ if exit_status != 0: logger.error( "%s: node network information script failed with status: %s." % (node.hostname, exit_status)) return assert isinstance(output, bytes) # Skip network configuration if set by the user. if node.skip_networking: return # Get the MAC addresses of all connected interfaces. ip_addr_info = parse_ip_addr(output) current_interfaces = set() for link in ip_addr_info.values(): link_mac = link.get('mac') # Ignore loopback interfaces. if link_mac is None: continue elif link_mac == SWITCH_OPENBMC_MAC: # Ignore OpenBMC interfaces on switches which all share the same, # hard-coded OpenBMC MAC address. continue else: ifname = link['name'] try: interface = PhysicalInterface.objects.get(mac_address=link_mac) if interface.node is not None and interface.node != node: logger.warning( "Interface with MAC %s moved from node %s to %s. " "(The existing interface will be deleted.)" % (interface.mac_address, interface.node.fqdn, node.fqdn)) interface.delete() interface = _create_default_physical_interface( node, ifname, link_mac) else: # Interface already exists on this Node, so just update # the name. if interface.name != ifname: interface.name = ifname interface.save(update_fields=['name', 'updated']) except PhysicalInterface.DoesNotExist: interface = _create_default_physical_interface( node, ifname, link_mac) current_interfaces.add(interface) ips = link.get('inet', []) + link.get('inet6', []) interface.update_ip_addresses(ips) if 'NO-CARRIER' in link.get('flags', []): # This interface is now disconnected. if interface.vlan is not None: interface.vlan = None interface.save(update_fields=['vlan', 'updated']) for iface in Interface.objects.filter(node=node): if iface not in current_interfaces: iface.delete()