Пример #1
0
async def check_transaction(transaction_id):
    """The handler for the Synse Server "transaction" API command.

    Args:
        transaction_id (str|None): The id of the transaction to check. If
            the ID is None, a list of all transactions currently in the
            cache is returned.

    Returns:
        TransactionResponse: The "transaction" response scheme model.
        TransactionListResponse: The list of all transactions.
    """
    logger.debug(_('Transaction Command (args: {})').format(transaction_id))

    # If we are not given a transaction ID, then we want to return
    # the list of all actively tracked transactions.
    if transaction_id is None:
        all_keys = cache.transaction_cache._cache.keys()
        # Keys in the cache are prepended with 'transaction', so here we get all
        # keys with that prefix and strip the prefix.
        transaction_ids = [
            k[11:] for k in all_keys if k.startswith('transaction')
        ]
        return scheme.TransactionListResponse(transaction_ids)

    # Otherwise, get the specified transaction.
    transaction = await cache.get_transaction(transaction_id)
    if not transaction:
        raise errors.TransactionNotFoundError(
            _('Transaction with id "{}" not found').format(transaction_id))

    plugin_name = transaction.get('plugin')
    context = transaction.get('context')

    if not plugin_name:
        # TODO - in the future, what we could do is attempt sending the transaction
        #   request to *all* of the known plugins. this could be useful in the event
        #   that synse goes down. since everything is just stored in memory, a new
        #   synse instance will have lost the transaction cache.
        #
        #   alternatively, we could think about having an internal api command to
        #   essentially dump the active transactions so that we can rebuild the cache.
        raise errors.TransactionNotFoundError(
            _('Unable to determine managing plugin for transaction {}.').
            format(transaction_id))

    _plugin = plugin.get_plugin(plugin_name)
    if not _plugin:
        raise errors.PluginNotFoundError(
            _('Unable to find plugin "{}"').format(plugin_name))

    try:
        resp = _plugin.client.transaction(transaction_id)
    except grpc.RpcError as ex:
        raise errors.FailedTransactionCommandError(str(ex)) from ex

    return scheme.TransactionResponse(transaction_id, context, resp[0])
Пример #2
0
def test_synse_error_plugin_not_found():
    """Check for PLUGIN_NOT_FOUND error"""
    e = errors.PluginNotFoundError('message')

    assert isinstance(e, exceptions.NotFound)
    assert isinstance(e, errors.SynseError)
    assert isinstance(e, errors.SynseNotFoundError)

    assert e.status_code == 404
    assert e.error_id == errors.PLUGIN_NOT_FOUND
    assert e.args[0] == 'message'
Пример #3
0
async def read(rack, board, device):
    """The handler for the Synse Server "read" API command.

    Args:
        rack (str): The rack which the device resides on.
        board (str): The board which the device resides on.
        device (str): The device to read.

    Returns:
        ReadResponse: The "read" response scheme model.
    """
    logger.debug(
        _('Read Command (args: {}, {}, {})').format(rack, board, device))

    # Lookup the known info for the specified device.
    plugin_name, dev = await cache.get_device_info(rack, board, device)
    logger.debug(
        _('Device {} is managed by plugin {}').format(device, plugin_name))

    # Get the plugin context for the device's specified protocol.
    _plugin = plugin.get_plugin(plugin_name)
    logger.debug(_('Got plugin: {}').format(_plugin))
    if not _plugin:
        raise errors.PluginNotFoundError(
            _('Unable to find plugin named "{}" to read').format(plugin_name))

    try:
        # Perform a gRPC read on the device's managing plugin
        read_data = _plugin.client.read(rack, board, device)
    except grpc.RpcError as ex:

        # FIXME (etd) - this isn't the nicest way of doing this check.
        # this string is returned from the SDK, and its not likely to change
        # anytime soon, so this is "safe" for now, but we should see if there
        # is a better way to check this other than comparing strings..
        if hasattr(ex, 'code') and hasattr(ex, 'details'):
            if grpc.StatusCode.NOT_FOUND == ex.code(
            ) and 'no readings found' in ex.details().lower():

                # FIXME (etd) - with SDK v1.0, is the below correct? We should not longer
                # have to pass the "null" string. I think an empty string should also not
                # indicate no readings.. it should be the NOT_FOUND error (or, at least
                # some kind of error).

                # Currently, in the SDK, there are three different behaviors for
                # devices that do not have readings. Either (a). "null" is returned,
                # (b). an empty string ("") is returned, or (c). a gRPC error is
                # returned with the NOT_FOUND status code. Cases (a) and (b) are
                # handled in the ReadResponse initialization (below). This block
                # handles case (c).
                #
                # The reason for the difference between (a) and (b) is just one
                # of implementation. The empty string is the default value for the
                # gRPC read response, but sometimes it is useful to have an explict
                # value set to make things easier to read.
                #
                # The difference between those and (c) is more distinct. (c) should
                # only happen when a configured device is not being read from at all.
                # Essentially, (c) is the fallback for when device-specific handlers
                # fail to read a configured device.
                #
                # To summarize:
                #   (a), (b)
                #       A device is configured and the plugin's device handlers
                #       can operate on the device. This indicates that the plugin
                #       is working, but the device could be failing or disconnected.
                #
                #   (c)
                #       A device is configured, but the plugin's device handler
                #       can not (or is not) able to operate on the device. This
                #       could indicate either a plugin configuration error or
                #       an error with the plugin logic itself.

                # Create empty readings for each of the device's readings.
                logger.warning(
                    _('Read for {}/{}/{} returned gRPC "no readings found". Will '
                      'apply None as reading value. Note that this response might '
                      'indicate plugin error/misconfiguration.').format(
                          rack, board, device))
                read_data = []
                for output in dev.output:
                    read_data.append(
                        api.Reading(
                            timestamp=utils.rfc3339now(),
                            type=output.type,
                        ))
            else:
                raise errors.FailedReadCommandError(str(ex)) from ex
        else:
            raise errors.FailedReadCommandError(str(ex)) from ex

    return ReadResponse(device=dev, readings=read_data)
Пример #4
0
async def write(rack, board, device, data):
    """The handler for the Synse Server "write" API command.

    Args:
        rack (str): The rack which the device resides on.
        board (str): The board which the device resides on.
        device (str): The device to write to.
        data (dict): The data to write to the device.

    Returns:
        WriteResponse: The "write" response scheme model.
    """
    logger.debug(
        _('Write Command (args: {}, {}, {}, data: {})').format(
            rack, board, device, data))

    # Lookup the known info for the specified device
    plugin_name, __ = await cache.get_device_info(rack, board, device)  # pylint: disable=unused-variable

    # Get the plugin context for the device's specified protocol
    _plugin = plugin.get_plugin(plugin_name)
    if not _plugin:
        raise errors.PluginNotFoundError(
            _('Unable to find plugin named "{}"').format(plugin_name))

    # The data comes in as the POSTed dictionary which includes an 'action'
    # and/or 'raw'/'data' field. Here, we convert it to the appropriate modeling for
    # transport to the plugin.
    write_action = data.get('action')
    if not isinstance(write_action, str):
        raise errors.InvalidArgumentsError(
            _('"action" value must be a string, but was {}').format(
                type(write_action)))

    # Get the data out. If the 'data' field is present, we will use it. Otherwise, we will
    # look for a 'raw' field, for backwards compatibility. If 'data' exists, 'raw' is ignored.
    write_data = data.get('data')
    if write_data is None:
        write_data = data.get('raw')

    if write_data is not None:
        # The data should be an instance of bytes, which in python is a string
        if not isinstance(write_data, str):
            raise errors.InvalidArgumentsError(
                _('"raw"/"data" value must be a string, but was {}').format(
                    type(write_data)))
        write_data = str.encode(write_data)

    wd = WriteData(action=write_action, data=write_data)
    logger.info(
        _('Writing to {}: {}').format('/'.join((rack, board, device)), wd))

    # Perform a gRPC write on the device's managing plugin
    try:
        t = _plugin.client.write(rack, board, device, [wd])
    except grpc.RpcError as ex:
        raise errors.FailedWriteCommandError(str(ex)) from ex

    # Now that we have the transaction info, we want to map it to the corresponding
    # process so any subsequent transaction check will know where to look.
    for _id, ctx in t.transactions.items():
        context = {'action': ctx.action, 'data': ctx.data}
        ok = await cache.add_transaction(_id, context, _plugin.id())
        if not ok:
            logger.error(
                _('Failed to add transaction {} to the cache').format(_id))

    return WriteResponse(transactions=t.transactions)