def test_not_found_in_cache_active_introspection(self, mock_record): CONF.set_override('permit_active_introspection', True, 'processing') self.find_mock.side_effect = utils.NotFoundInCacheError('not found') self.cli.node.get.side_effect = exceptions.NotFound('boom') self.cache_fixture.mock.acquire_lock = mock.Mock() self.cache_fixture.mock.uuid = '1111' self.cache_fixture.mock.finished_at = None self.cache_fixture.mock.node = mock.Mock() mock_record.return_value = self.cache_fixture.mock res = process.process(self.data) self.assertEqual(self.fake_result_json, res) self.find_mock.assert_called_once_with( bmc_address=[self.bmc_address, self.bmc_v6address], mac=mock.ANY) actual_macs = self.find_mock.call_args[1]['mac'] self.assertEqual(sorted(self.all_macs), sorted(actual_macs)) mock_record.assert_called_once_with( bmc_addresses=['1.2.3.4', '2001:1234:1234:1234:1234:1234:1234:1234/64'], macs=mock.ANY) actual_macs = mock_record.call_args[1]['macs'] self.assertEqual(sorted(self.all_macs), sorted(actual_macs)) self.cli.node.get.assert_not_called() self.process_mock.assert_called_once_with( mock.ANY, mock.ANY, self.data)
def find_node(**attributes): """Find node in cache. :param attributes: attributes known about this node (like macs, BMC etc) also ironic client instance may be passed under 'ironic' :returns: structure NodeInfo with attributes ``uuid`` and ``created_at`` :raises: Error if node is not found """ ironic = attributes.pop('ironic', None) # NOTE(dtantsur): sorting is not required, but gives us predictability found = set() for (name, value) in sorted(attributes.items()): if not value: LOG.debug('Empty value for attribute %s', name) continue if not isinstance(value, list): value = [value] LOG.debug('Trying to use %s of value %s for node look up' % (name, value)) value_list = [] for v in value: value_list.append('name="%s" AND value="%s"' % (name, v)) stmt = ('select distinct uuid from attributes where ' + ' OR '.join(value_list)) rows = (db.model_query(db.Attribute.uuid).from_statement( text(stmt)).all()) if rows: found.update(item.uuid for item in rows) if not found: raise utils.NotFoundInCacheError( _('Could not find a node for attributes %s') % attributes) elif len(found) > 1: raise utils.Error(_('Multiple matching nodes found for attributes ' '%(attr)s: %(found)s') % { 'attr': attributes, 'found': list(found) }, code=404) uuid = found.pop() row = (db.model_query(db.Node.started_at, db.Node.finished_at).filter_by(uuid=uuid).first()) if not row: raise utils.Error(_('Could not find node %s in introspection cache, ' 'probably it\'s not on introspection now') % uuid, code=404) if row.finished_at: raise utils.Error( _('Introspection for node %(node)s already finished on ' '%(finish)s') % { 'node': uuid, 'finish': row.finished_at }) return NodeInfo(uuid=uuid, started_at=row.started_at, ironic=ironic)
def test_node_not_found_hook_exception(self, hook_mock): CONF.set_override('node_not_found_hook', 'example', 'processing') self.find_mock.side_effect = utils.NotFoundInCacheError('BOOM') hook_mock.side_effect = Exception('Hook Error') self.assertRaisesRegex(utils.Error, 'Node not found hook failed: Hook Error', process.process, self.data) hook_mock.assert_called_once_with(self.data)
def test_node_not_found_hook_run_none(self, hook_mock): CONF.set_override('node_not_found_hook', 'example', 'processing') self.find_mock.side_effect = utils.NotFoundInCacheError('BOOM') hook_mock.return_value = None self.assertRaisesRegex(utils.Error, 'Node not found hook returned nothing', process.process, self.data) hook_mock.assert_called_once_with(self.data)
def test_node_not_found_hook_run_ok(self, hook_mock): CONF.set_override('node_not_found_hook', 'example', 'processing') self.find_mock.side_effect = utils.NotFoundInCacheError('BOOM') hook_mock.return_value = node_cache.NodeInfo( uuid=self.node.uuid, started_at=self.started_at) res = process.process(self.data) self.assertEqual(self.fake_result_json, res) hook_mock.assert_called_once_with(self.data)
def version_id(self): """Get the version id""" if self._version_id is None: row = db.model_query(db.Node).get(self.uuid) if row is None: raise utils.NotFoundInCacheError(_('Node not found in the ' 'cache'), node_info=self) self._version_id = row.version_id return self._version_id
def record_node(ironic=None, bmc_addresses=None, macs=None): """Create a cache record for a known active node. :param ironic: ironic client instance. :param bmc_addresses: list of BMC addresses. :param macs: list of MAC addresses. :return: NodeInfo """ if not bmc_addresses and not macs: raise utils.NotFoundInCacheError( _("Existing node cannot be found since neither MAC addresses " "nor BMC addresses are present in the inventory")) if ironic is None: ironic = ir_utils.get_client() node = ir_utils.lookup_node(macs=macs, bmc_addresses=bmc_addresses, ironic=ironic) if not node: bmc_addresses = ', '.join(bmc_addresses) if bmc_addresses else None macs = ', '.join(macs) if macs else None raise utils.NotFoundInCacheError( _("Existing node was not found by MAC address(es) %(macs)s " "and BMC address(es) %(addr)s") % { 'macs': macs, 'addr': bmc_addresses }) node = ironic.node.get(node, fields=['uuid', 'provision_state']) # TODO(dtantsur): do we want to allow updates in all states? if node.provision_state not in ir_utils.VALID_ACTIVE_STATES: raise utils.Error( _("Node %(node)s is not active, its provision " "state is %(state)s") % { 'node': node.uuid, 'state': node.provision_state }) return add_node(node.uuid, istate.States.waiting, manage_boot=False, mac=macs, bmc_address=bmc_addresses)
def test_node_not_found_hook_exception(self, cli, pop_mock, process_mock): CONF.set_override('node_not_found_hook', 'example', 'processing') plugins_base._NOT_FOUND_HOOK_MGR = None pop_mock.side_effect = iter([utils.NotFoundInCacheError('BOOM')]) with mock.patch.object(example_plugin, 'example_not_found_hook') as hook_mock: hook_mock.side_effect = Exception('Hook Error') self.assertRaisesRegexp(utils.Error, 'Node not found hook failed: Hook Error', process.process, self.data) hook_mock.assert_called_once_with(self.data)
def test_node_not_found_hook_run_ok(self, cli, pop_mock, process_mock): CONF.set_override('node_not_found_hook', 'example', 'processing') plugins_base._NOT_FOUND_HOOK_MGR = None pop_mock.side_effect = iter([utils.NotFoundInCacheError('BOOM')]) with mock.patch.object(example_plugin, 'example_not_found_hook') as hook_mock: hook_mock.return_value = node_cache.NodeInfo( uuid=self.node.uuid, started_at=self.started_at) res = process.process(self.data) self.assertEqual(self.fake_result_json, res) hook_mock.assert_called_once_with(self.data)
def find_node(**attributes): """Find node in cache. Looks up a node based on attributes in a best-match fashion. This function acquires a lock on a node. :param attributes: attributes known about this node (like macs, BMC etc) also ironic client instance may be passed under 'ironic' :returns: structure NodeInfo with attributes ``uuid`` and ``created_at`` :raises: Error if node is not found or multiple nodes match the attributes """ ironic = attributes.pop('ironic', None) # NOTE(dtantsur): sorting is not required, but gives us predictability found = collections.Counter() for (name, value) in sorted(attributes.items()): if not value: LOG.debug('Empty value for attribute %s', name) continue if not isinstance(value, list): value = [value] LOG.debug('Trying to use %s of value %s for node look up', name, value) value_list = [] for v in value: value_list.append("name='%s' AND value='%s'" % (name, v)) stmt = ('select distinct node_uuid from attributes where ' + ' OR '.join(value_list)) rows = (db.model_query(db.Attribute.node_uuid).from_statement( text(stmt)).all()) found.update(row.node_uuid for row in rows) if not found: raise utils.NotFoundInCacheError( _('Could not find a node for attributes %s') % attributes) most_common = found.most_common() LOG.debug( 'The following nodes match the attributes: %(attributes)s, ' 'scoring: %(most_common)s', { 'most_common': ', '.join('%s: %d' % tpl for tpl in most_common), 'attributes': ', '.join('%s=%s' % tpl for tpl in attributes.items()) }) # NOTE(milan) most_common is sorted, higher scores first highest_score = most_common[0][1] found = [item[0] for item in most_common if highest_score == item[1]] if len(found) > 1: raise utils.Error( _('Multiple nodes match the same number of attributes ' '%(attr)s: %(found)s') % { 'attr': attributes, 'found': found }, code=404) uuid = found.pop() node_info = NodeInfo(uuid=uuid, ironic=ironic) node_info.acquire_lock() try: row = (db.model_query( db.Node.started_at, db.Node.finished_at).filter_by(uuid=uuid).first()) if not row: raise utils.Error( _('Could not find node %s in introspection cache, ' 'probably it\'s not on introspection now') % uuid, code=404) if row.finished_at: raise utils.Error( _('Introspection for node %(node)s already finished on ' '%(finish)s') % { 'node': uuid, 'finish': row.finished_at }) node_info.started_at = row.started_at return node_info except Exception: with excutils.save_and_reraise_exception(): node_info.release_lock()
def test_error_if_node_not_found_hook(self): plugins_base._NOT_FOUND_HOOK_MGR = None self.find_mock.side_effect = utils.NotFoundInCacheError('BOOM') self.assertRaisesRegex(utils.Error, 'Look up error: BOOM', process.process, self.data)
def test_error_if_node_not_found_hook(self): self.find_mock.side_effect = utils.NotFoundInCacheError('BOOM') self.assertRaisesRegex(utils.Error, 'Look up error: BOOM', process.process, self.data)
def test_node_not_in_db(self, fsm_event_mock, add_node_mock): fsm_event_mock.side_effect = utils.NotFoundInCacheError('Oops!') node_cache.start_introspection(self.node_info.uuid) add_node_mock.assert_called_once_with(self.node_info.uuid, istate.States.starting)
def test_error_if_node_not_found_hook(self, cli, pop_mock, process_mock): plugins_base._NOT_FOUND_HOOK_MGR = None pop_mock.side_effect = iter([utils.NotFoundInCacheError('BOOM')]) self.assertRaisesRegexp(utils.Error, 'Look up error: BOOM', process.process, self.data)