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)
Пример #2
0
    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)
Пример #6
0
 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