Exemplo n.º 1
0
def process(node_info):
    """Process data from the discovery ramdisk.

    This function heavily relies on the hooks to do the actual data processing.
    """
    hooks = plugins_base.processing_hooks_manager()
    for hook_ext in hooks:
        hook_ext.obj.before_processing(node_info)

    cached_node = node_cache.find_node(
        bmc_address=node_info.get('ipmi_address'), mac=node_info.get('macs'))

    ironic = utils.get_client()
    try:
        node = ironic.node.get(cached_node.uuid)
    except exceptions.NotFound:
        msg = ('Node UUID %s was found in cache, but is not found in Ironic' %
               cached_node.uuid)
        cached_node.finished(error=msg)
        raise utils.Error(msg, code=404)

    try:
        return _process_node(ironic, node, node_info, cached_node)
    except utils.Error as exc:
        cached_node.finished(error=str(exc))
        raise
    except Exception as exc:
        msg = 'Unexpected exception during processing'
        LOG.exception(msg)
        cached_node.finished(error=msg)
        raise utils.Error(msg)
Exemplo n.º 2
0
def check_auth():
    """Check whether request is properly authenticated."""
    if not conf.getboolean('discoverd', 'authenticate'):
        return

    if not flask.request.headers.get('X-Auth-Token'):
        LOG.error("No X-Auth-Token header, rejecting request")
        raise utils.Error('Authentication required', code=401)
    try:
        utils.check_is_admin(token=flask.request.headers['X-Auth-Token'])
    except exceptions.Unauthorized as exc:
        LOG.error("Keystone denied access: %s, rejecting request", exc)
        raise utils.Error('Access denied', code=403)
Exemplo n.º 3
0
def find_node(**attributes):
    """Find node in cache.

    :param attributes: attributes known about this node (like macs, BMC etc)
    :returns: structure NodeInfo with attributes ``uuid`` and ``created_at``
    :raises: Error if node is not found
    """
    # NOTE(dtantsur): sorting is not required, but gives us predictability
    found = set()
    db = _db()
    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))
        rows = db.execute(
            'select distinct uuid from attributes where ' +
            ' OR '.join('name=? AND value=?' for _ in value),
            sum(([name, v] for v in value), [])).fetchall()
        if rows:
            found.update(item[0] for item in rows)

    if not found:
        raise utils.Error('Could not find a node for attributes %s' %
                          attributes,
                          code=404)
    elif len(found) > 1:
        raise utils.Error(
            'Multiple matching nodes found for attributes %s: %s' %
            (attributes, list(found)),
            code=404)

    uuid = found.pop()
    row = db.execute('select started_at, finished_at from nodes where uuid=?',
                     (uuid, )).fetchone()
    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 %s already finished on %s' %
                          (uuid, row['finished_at']))

    return NodeInfo(uuid=uuid, started_at=row['started_at'])
Exemplo n.º 4
0
def _prepare_for_pxe(ironic, cached_node, macs, setup_ipmi_credentials):
    if macs:
        LOG.info('Whitelisting MAC\'s %s for node %s on the firewall',
                 macs, cached_node.uuid)
        firewall.update_filters(ironic)

    if not setup_ipmi_credentials:
        try:
            utils.retry_on_conflict(ironic.node.set_boot_device,
                                    cached_node.uuid, 'pxe', persistent=False)
        except Exception as exc:
            LOG.warning('Failed to set boot device to PXE for node %s: %s',
                        cached_node.uuid, exc)

        try:
            utils.retry_on_conflict(ironic.node.set_power_state,
                                    cached_node.uuid, 'reboot')
        except Exception as exc:
            raise utils.Error('Failed to power on node %s, check it\'s power '
                              'management configuration:\n%s'
                              % (cached_node.uuid, exc))
    else:
        LOG.info('Introspection environment is ready for node %s, '
                 'manual power on is required within %d seconds',
                 cached_node.uuid, conf.getint('discoverd', 'timeout'))
Exemplo n.º 5
0
    def test_not_found_in_cache(self, cli, pop_mock, process_mock):
        pop_mock.side_effect = utils.Error('not found')

        self.assertRaisesRegexp(utils.Error, 'not found', process.process,
                                self.data)
        self.assertFalse(cli.node.get.called)
        self.assertFalse(process_mock.called)
Exemplo n.º 6
0
 def test_intospect_failed(self, introspect_mock):
     introspect_mock.side_effect = utils.Error("boom")
     res = self.app.post('/v1/introspection/uuid1')
     self.assertEqual(400, res.status_code)
     self.assertEqual(b"boom", res.data)
     introspect_mock.assert_called_once_with("uuid1",
                                             setup_ipmi_credentials=False)
Exemplo n.º 7
0
    def test_expected_exception(self, finished_mock, client_mock, pop_mock,
                                process_mock):
        pop_mock.return_value = node_cache.NodeInfo(uuid=self.node.uuid,
                                                    started_at=self.started_at)
        process_mock.side_effect = utils.Error('boom')

        self.assertRaisesRegexp(utils.Error, 'boom', process.process,
                                self.data)

        finished_mock.assert_called_once_with(mock.ANY, error='boom')
Exemplo n.º 8
0
def get_node(uuid):
    """Get node from cache by it's UUID.

    :param uuid: node UUID.
    :returns: structure NodeInfo.
    """
    row = _db().execute('select * from nodes where uuid=?',
                        (uuid, )).fetchone()
    if row is None:
        raise utils.Error('Could not find node %s in cache' % uuid, code=404)
    return NodeInfo.from_row(row)
Exemplo n.º 9
0
    def before_processing(self, node_info):
        """Validate that required properties are provided by the ramdisk."""
        missing = [key for key in self.KEYS if not node_info.get(key)]
        if missing:
            raise utils.Error(
                'The following required parameters are missing: %s' % missing)

        LOG.info(
            'Discovered data: CPUs: %(cpus)s %(cpu_arch)s, '
            'memory %(memory_mb)s MiB, disk %(local_gb)s GiB',
            {key: node_info.get(key)
             for key in self.KEYS})
Exemplo n.º 10
0
def _force_power_off(ironic, cached_node):
    LOG.debug('Forcing power off of node %s', cached_node.uuid)
    try:
        utils.retry_on_conflict(ironic.node.set_power_state, cached_node.uuid,
                                'off')
    except Exception as exc:
        msg = ('Failed to power off node %s, check it\'s power '
               'management configuration: %s' % (cached_node.uuid, exc))
        cached_node.finished(error=msg)
        raise utils.Error(msg)

    deadline = cached_node.started_at + conf.getint('discoverd', 'timeout')
    while time.time() < deadline:
        node = ironic.node.get(cached_node.uuid)
        if (node.power_state or '').lower() == 'power off':
            return
        LOG.info('Waiting for node %s to power off, current state is %s',
                 cached_node.uuid, node.power_state)
        eventlet.greenthread.sleep(_POWER_OFF_CHECK_PERIOD)

    msg = ('Timeout waiting for node %s to power off after introspection' %
           cached_node.uuid)
    cached_node.finished(error=msg)
    raise utils.Error(msg)
Exemplo n.º 11
0
def add_node(uuid, **attributes):
    """Store information about a node under introspection.

    All existing information about this node is dropped.
    Empty values are skipped.

    :param uuid: Ironic node UUID
    :param attributes: attributes known about this node (like macs, BMC etc)
    :returns: NodeInfo
    """
    started_at = time.time()
    with _db() as db:
        db.execute("delete from nodes where uuid=?", (uuid, ))
        db.execute("delete from attributes where uuid=?", (uuid, ))
        db.execute("delete from options where uuid=?", (uuid, ))

        db.execute("insert into nodes(uuid, started_at) "
                   "values(?, ?)", (uuid, started_at))
        for (name, value) in attributes.items():
            if not value:
                continue
            if not isinstance(value, list):
                value = [value]

            try:
                db.executemany(
                    "insert into attributes(name, value, uuid) "
                    "values(?, ?, ?)", [(name, v, uuid) for v in value])
            except sqlite3.IntegrityError as exc:
                LOG.error(
                    'Database integrity error %s during '
                    'adding attributes', exc)
                raise utils.Error(
                    'Some or all of %(name)s\'s %(value)s are already '
                    'on introspection' % {
                        'name': name,
                        'value': value
                    })

    return NodeInfo(uuid=uuid, started_at=started_at)
Exemplo n.º 12
0
def introspect(uuid, setup_ipmi_credentials=False):
    """Initiate hardware properties introspection for a given node.

    :param uuid: node uuid
    :raises: Error
    """
    ironic = utils.get_client()

    try:
        node = ironic.node.get(uuid)
    except exceptions.NotFound:
        raise utils.Error("Cannot find node %s" % uuid, code=404)
    except exceptions.HttpError as exc:
        raise utils.Error("Cannot get node %s: %s" % (uuid, exc))

    if (setup_ipmi_credentials and not
            conf.getboolean('discoverd', 'enable_setting_ipmi_credentials')):
        raise utils.Error(
            'IPMI credentials setup is disabled in configuration')

    if not node.maintenance:
        provision_state = node.provision_state
        if provision_state and provision_state.lower() not in VALID_STATES:
            msg = ('Refusing to introspect node %s with provision state "%s" '
                   'and maintenance mode off')
            raise utils.Error(msg % (node.uuid, provision_state))

        power_state = node.power_state
        if power_state and power_state.lower() not in VALID_POWER_STATES:
            msg = ('Refusing to introspect node %s with power state "%s" '
                   'and maintenance mode off')
            raise utils.Error(msg % (node.uuid, power_state))
    else:
        LOG.info('Node %s is in maintenance mode, skipping power and provision'
                 ' states check')

    if not setup_ipmi_credentials:
        validation = utils.retry_on_conflict(ironic.node.validate, node.uuid)
        if not validation.power['result']:
            msg = ('Failed validation of power interface for node %s, '
                   'reason: %s')
            raise utils.Error(msg % (node.uuid, validation.power['reason']))

    eventlet.greenthread.spawn_n(_background_start_discover, ironic, node,
                                 setup_ipmi_credentials=setup_ipmi_credentials)
Exemplo n.º 13
0
 def test_continue_failed(self, process_mock):
     process_mock.side_effect = utils.Error("boom")
     res = self.app.post('/v1/continue', data='"JSON"')
     self.assertEqual(400, res.status_code)
     process_mock.assert_called_once_with("JSON")
     self.assertEqual(b'boom', res.data)
Exemplo n.º 14
0
    def before_processing(self, node_info):
        if not node_info.get('error'):
            return

        raise utils.Error('Ramdisk reported error: %s' % node_info['error'])