def test_get_lldp_info_empty(self, sock_mock, select_mock, fcntl_mock): expected_lldp = { 'eth1': [], 'eth0': [] } interface_names = ['eth0', 'eth1'] sock1 = mock.Mock() sock1.fileno.return_value = 4 sock2 = mock.Mock() sock2.fileno.return_value = 5 sock_mock.side_effect = [sock1, sock2] select_mock.side_effect = [ ([], [], []), ([], [], []) ] lldp_info = netutils.get_lldp_info(interface_names) self.assertEqual(expected_lldp, lldp_info) sock1.bind.assert_called_with(('eth0', netutils.LLDP_ETHERTYPE)) sock2.bind.assert_called_with(('eth1', netutils.LLDP_ETHERTYPE)) sock1.recv.not_called() sock2.recv.not_called() self.assertEqual(1, sock1.close.call_count) self.assertEqual(1, sock2.close.call_count) # 2 interfaces * (2 calls to enter promiscuous mode + 1 to leave) = 6 self.assertEqual(6, fcntl_mock.call_count)
def collect_lldp_data(self, interface_names): """Collect and convert LLDP info from the node. In order to process the LLDP information later, the raw data needs to be converted for serialization purposes. :param interface_names: list of names of node's interfaces. :return: a dict, containing the lldp data from every interface. """ interface_names = [name for name in interface_names if name != 'lo'] lldp_data = {} try: raw_lldp_data = netutils.get_lldp_info(interface_names) except Exception: # NOTE(sambetts) The get_lldp_info function will log this exception # and we don't invalidate any existing data in the cache if we fail # to get data to replace it so just return. return lldp_data for ifname, tlvs in raw_lldp_data.items(): # NOTE(sambetts) Convert each type-length-value (TLV) value to hex # so that it can be serialised safely processed_tlvs = [] for typ, data in tlvs: try: processed_tlvs.append((typ, binascii.hexlify(data).decode())) except (binascii.Error, binascii.Incomplete) as e: LOG.warning('An error occurred while processing TLV type ' '%s for interface %s: %s', (typ, ifname, e)) lldp_data[ifname] = processed_tlvs return lldp_data
def test_get_lldp_info_multiple(self, sock_mock, select_mock, fcntl_mock): expected_lldp = { 'eth1': [ (0, b''), (1, b'\x04\x88Z\x92\xecTY'), (2, b'\x05Ethernet1/18'), (3, b'\x00x')], 'eth0': [ (0, b''), (1, b'\x04\x88Z\x92\xecTY'), (2, b'\x05Ethernet1/18'), (3, b'\x00x')] } interface_names = ['eth0', 'eth1'] sock1 = mock.Mock() sock1.recv.return_value = FAKE_LLDP_PACKET sock1.fileno.return_value = 4 sock2 = mock.Mock() sock2.recv.return_value = FAKE_LLDP_PACKET sock2.fileno.return_value = 5 sock_mock.side_effect = [sock1, sock2] select_mock.side_effect = [ ([sock1], [], []), ([sock2], [], []), ] lldp_info = netutils.get_lldp_info(interface_names) self.assertEqual(expected_lldp, lldp_info) sock1.bind.assert_called_with(('eth0', netutils.LLDP_ETHERTYPE)) sock2.bind.assert_called_with(('eth1', netutils.LLDP_ETHERTYPE)) sock1.recv.assert_called_with(1600) sock2.recv.assert_called_with(1600) self.assertEqual(1, sock1.close.call_count) self.assertEqual(1, sock2.close.call_count) # 2 interfaces, 2 calls to enter promiscuous mode, 1 to leave self.assertEqual(6, fcntl_mock.call_count) expected_calls = [ mock.call([sock1, sock2], [], [], cfg.CONF.lldp_timeout), mock.call([sock2], [], [], cfg.CONF.lldp_timeout), ] self.assertEqual(expected_calls, select_mock.call_args_list)
def verify_ports(self, node, ports): """Given Port dicts, verify they match LLDP information :param node: a dict representation of a Node object :param ports: a dict representation of Ports connected to the node :raises CleaningError: if any of the steps determine the node does not match the given data """ node_switchports = self._get_node_switchports(node, ports) if not node_switchports: # Fail gracefully if we cannot find node ports. If call is made # with only driver_info, don't fail. return interface_names = [x.name for x in self.list_network_interfaces()] lldp_info = netutils.get_lldp_info(interface_names) # Both should be a set of tuples: (chassis, port) lldp_ports = set() for lldp in lldp_info.values(): lldp_ports.add(self._get_port_from_lldp(lldp)) LOG.info('LLDP ports: %s', lldp_ports) LOG.info('Node ports: %s', node_switchports) # TODO(JoshNang) add check that ports, chassis *and* interface match # when port/chassis are stored on Port objects # Compare the ports if node_switchports != lldp_ports: LOG.error('Ports did not match, LLDP: %(lldp)s, Node: %(node)s', {'lldp': lldp_ports, 'node': node_switchports}) # TODO(supermari0) The old error here - VerificationError - seems # not to exist or have existed. It would be good if we had more # specific errors that subclass CleaningError. raise errors.CleaningError( 'Detected port mismatches. LLDP detected_ports: %(lldp)s, ' 'Node ports: %(node)s.' % {'lldp': lldp_ports, 'node': node_switchports}) # Return the LLDP info LOG.debug('Ports match, returning LLDP info: %s', lldp_info) # Ensure the return value is properly encode or JSON throws errors return unicode(lldp_info)
def test_get_lldp_info_malformed(self, sock_mock, select_mock, fcntl_mock): expected_lldp = { 'eth1': [], 'eth0': [], } interface_names = ['eth0', 'eth1'] sock1 = mock.Mock() sock1.recv.return_value = b'123456789012345' # odd size sock1.fileno.return_value = 4 sock2 = mock.Mock() sock2.recv.return_value = b'1234' # too small sock2.fileno.return_value = 5 sock_mock.side_effect = [sock1, sock2] select_mock.side_effect = [ ([sock1], [], []), ([sock2], [], []), ] lldp_info = netutils.get_lldp_info(interface_names) self.assertEqual(expected_lldp, lldp_info) sock1.bind.assert_called_with(('eth0', netutils.LLDP_ETHERTYPE)) sock2.bind.assert_called_with(('eth1', netutils.LLDP_ETHERTYPE)) sock1.recv.assert_called_with(1600) sock2.recv.assert_called_with(1600) self.assertEqual(1, sock1.close.call_count) self.assertEqual(1, sock2.close.call_count) # 2 interfaces, 2 calls to enter promiscuous mode, 1 to leave self.assertEqual(6, fcntl_mock.call_count) expected_calls = [ mock.call([sock1, sock2], [], [], cfg.CONF.lldp_timeout), mock.call([sock2], [], [], cfg.CONF.lldp_timeout), ] self.assertEqual(expected_calls, select_mock.call_args_list)
def _cache_lldp_data(self, interface_names): interface_names = [name for name in interface_names if name != 'lo'] try: raw_lldp_data = netutils.get_lldp_info(interface_names) except Exception: # NOTE(sambetts) The get_lldp_info function will log this exception # and we don't invalidate any existing data in the cache if we fail # to get data to replace it so just return. return for ifname, tlvs in raw_lldp_data.items(): # NOTE(sambetts) Convert each type-length-value (TLV) value to hex # so that it can be serialised safely processed_tlvs = [] for typ, data in tlvs: try: processed_tlvs.append((typ, binascii.hexlify(data).decode())) except (binascii.Error, binascii.Incomplete) as e: LOG.warning('An error occurred while processing TLV type ' '%s for interface %s: %s', (typ, ifname, e)) self.lldp_data[ifname] = processed_tlvs