Beispiel #1
0
async def _build_capabilities_cache():
    """Construct the list that will become the device capabilities cache.

    Returns:
        list: A list of dictionaries, where each dictionary corresponds to
            a registered plugin. The plugin dict will identify the plugin
            and enumerate the device kinds it supports and the output types
            supported by those device kinds.

    Raises:
        errors.InternalApiError: All plugins failed the capabilities request.
    """
    logger.debug(_('Building the device capabilities cache'))
    capabilities = []

    # First, we want to iterate through all of the known plugins and use
    # their clients to get the capability info for each plugin.
    plugin_count = len(Plugin.manager.plugins)
    if plugin_count == 0:
        logger.debug(_('Manager has no plugins - registering plugins'))
        register_plugins()
        plugin_count = len(Plugin.manager.plugins)

    logger.debug(_('Plugins to get capabilities for: {}').format(plugin_count))

    # Track which plugins failed to provide capability info for any reason.
    failures = {}

    # FIXME (etd): as of pylint 2.1.1, this gets marked with 'not-an-iterable'
    # It still appears to work just fine, so need to figure out why it is getting
    # marked as such and what should be done to fix it.
    async for plugin_id, plugin in get_plugins():  # pylint: disable=not-an-iterable
        logger.debug('{} - {}'.format(plugin_id, plugin))

        devices = []

        try:
            for capability in plugin.client.capabilities():
                devices.append({
                    'kind': capability.kind,
                    'outputs': capability.outputs
                })

        except grpc.RpcError as ex:
            failures[plugin_id] = ex
            logger.warning(
                _('Failed to get capability for plugin: {}').format(plugin_id))
            logger.warning(ex)
            continue

        capabilities.append({'plugin': plugin.tag, 'devices': devices})

    # If we fail to read from all plugins (assuming there were any), then we
    # can raise an error since it is likely something is mis-configured.
    if plugin_count != 0 and plugin_count == len(failures):
        raise errors.InternalApiError(
            _('Failed to get capabilities for all plugins: {}').format(
                failures))

    return capabilities
Beispiel #2
0
async def write_route(request, rack, board, device):
    """Write data to a known device.

    The data POSTed here should be JSON with an 'action' field  and 'raw'
    field, if applicable. If no data is posted, the write will fail.

    Args:
        request (sanic.request.Request): The incoming request.
        rack (str): The identifier of the rack which the device resides on.
        board (str): The identifier of the board which the device resides on.
        device (str): The identifier of the device to write to.

    Returns:
        sanic.response.HTTPResponse: The endpoint response.
    """
    try:
        data = request.json
    except Exception as e:
        raise errors.InvalidJsonError(
            _('Invalid JSON specified: {}').format(request.body)) from e

    logger.debug(_('Write route: POSTed JSON: {}').format(data))

    if not any([x in data for x in ['action', 'raw']]):
        raise errors.InvalidArgumentsError(
            _('Invalid data POSTed for write. Must contain "action" and/or "raw"'
              ))

    response = await commands.write(rack, board, device, data)
    return response.to_json()
Beispiel #3
0
    def add(self, plugin):
        """Add a new Plugin to the manager.

        All plugins should be added to the Manager, since this is how they
        are accessed later. An error will be raised if a Plugin with the same
        name is already tracked by the manager.

        Args:
            plugin (Plugin): The plugin instance to add to the manager.

        Raises:
            errors.PluginStateError: The manager was not able to add the
                plugin either because the given object was not a plugin
                or a plugin with the same name is already being tracked.
        """
        if not isinstance(plugin, Plugin):
            raise errors.PluginStateError(
                _('Only Plugin instances can be added to the manager')
            )

        plugin_id = plugin.id()
        if plugin_id in self.plugins:
            raise errors.PluginStateError(
                _('Plugin ("{}") already exists in the manager').format(plugin_id)
            )

        self.plugins[plugin_id] = plugin
Beispiel #4
0
def register_plugins():
    """Register all of the configured plugins.

    Plugins can either use a unix socket or TCP for communication. Unix
    socket based plugins will be detected from the presence of the socket
    file in a well-known directory, or via configuration. TCP based plugins
    will need to be made known to Synse Server via configuration.

    Upon initialization, the Plugin instances are automatically registered
    with the PluginManager.
    """
    # Register plugins from local config (file, env)
    unix = register_unix()
    tcp = register_tcp()

    # Get addresses of plugins to register via service discovery
    discovered = []
    addresses = kubernetes.discover()
    for address in addresses:
        plugin_id = register_plugin(address, 'tcp')
        if plugin_id is None:
            logger.error(_('Failed to register plugin with address: {}').format(address))
            continue
        discovered.append(plugin_id)

    diff = set(Plugin.manager.plugins) - set(unix + tcp + discovered)

    # Now that we have found all current plugins, we will want to clear out
    # any old plugins which may no longer be present.
    logger.debug(_('Plugins to purge from manager: {}').format(diff))
    Plugin.manager.purge(diff)

    logger.debug(_('Plugin registration complete'))
Beispiel #5
0
def register_tcp_plugins():
    """Register the plugins that use TCP for communication.

    Return:
        list[str]: The names of all plugins that were registered.
    """
    logger.debug(_('Registering plugins (tcp)'))

    configured = config.options.get('plugin.tcp', {})
    logger.debug(_('TCP plugins in configuration: {}').format(configured))
    if not configured:
        return []

    manager = Plugin.manager

    # Track the names of all plugins that are registered.
    registered = []

    for name, address in configured.items():
        if manager.get(name) is None:
            # A new plugin gets added to the manager on initialization.
            plugin = Plugin(name=name, address=address, mode='tcp')
            logger.debug(_('Created new plugin (tcp): {}').format(plugin))
        else:
            logger.debug(
                _('TCP Plugin "{}" already exists - will not re-register').
                format(name))

        registered.append(name)

    return list(set(registered))
Beispiel #6
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])
Beispiel #7
0
async def scan(rack=None, board=None, force=False):
    """The handler for the Synse Server "scan" API command.

    Args:
        rack (str): The rack to filter the scan results by.
        board (str): The board to filter the scan results by.
        force (bool): Force a re-scan of the meta-information.

    Returns:
        ScanResponse: The "scan" response scheme model.
    """
    logger.debug(_('Scan Command (args: {}, {}, force: {})').format(rack, board, force))

    if force:
        await cache.clear_all_meta_caches()

    # Plugins are registered on scan. If no plugins exist and a scan is
    # performed (e.g. on startup), we will find and register plugins.
    # Additionally, if we are forcing re-scan, we will re-register plugins.
    # This allows us to pick up any dynamically added plugins and clear out
    # any plugins that were removed.
    if len(plugin.Plugin.manager.plugins) == 0 or force:
        logger.debug(_('Re-registering plugins'))
        plugin.register_plugins()

    cache_data = await cache.get_scan_cache()

    # Filter the scan results by rack.
    if rack is not None:
        if not cache_data:
            raise errors.FailedScanCommandError(
                _('Unable to filter by resource - no scan results returned')
            )

        for r in cache_data['racks']:
            if r['id'] == rack:
                cache_data = r
                break
        else:
            raise errors.RackNotFoundError(
                _('Rack "{}" not found in scan results').format(rack)
            )

        # Filter the rack results by board.
        if board is not None:
            for b in cache_data['boards']:
                if b['id'] == board:
                    cache_data = b
                    break
            else:
                raise errors.BoardNotFoundError(
                    _('Board "{}" not found in scan results').format(board)
                )

    return ScanResponse(
        data=cache_data
    )
Beispiel #8
0
async def read_cached(start=None, end=None):
    """The handler for the Synse Server "readcached" API command.

    Args:
        start (str): An RFC3339 or RFC3339Nano formatted timestamp
            which defines a starting bound on the cache data to
            return. If no timestamp is specified, there will not
            be a starting bound. (default: None)
        end (str): An RFC3339 or RFC3339Nano formatted timestamp
            which defines an ending bound on the cache data to
            return. If no timestamp is specified, there will not
            be an ending bound. (default: None)

    Yields:
        ReadCachedResponse: The cached reading from the plugin.
    """
    start, end = start or '', end or ''
    logger.debug(
        _('Read Cached command (start: {}, end: {})').format(start, end))

    # If the plugins have not yet been registered, register them now.
    if len(plugin.Plugin.manager.plugins) == 0:
        logger.debug(_('Re-registering plugins'))
        plugin.register_plugins()

    # For each plugin, we'll want to request a dump of its readings cache.
    async for plugin_name, plugin_handler in plugin.get_plugins():  # pylint: disable=not-an-iterable
        logger.debug(
            _('Getting readings cache for plugin: {}').format(plugin_name))

        # Get the cached data from the plugin
        try:
            for reading in plugin_handler.client.read_cached(start, end):
                # If there is no reading, we're done iterating
                if reading is None:
                    return

                try:
                    __, device = await cache.get_device_info(  # pylint: disable=unused-variable
                        reading.rack, reading.board, reading.device)
                except errors.DeviceNotFoundError:
                    logger.info(
                        _('Did not find device {}-{}-{} locally. Skipping device; '
                          'server cache may be out of sync.'))
                    continue

                yield ReadCachedResponse(
                    device=device,
                    device_reading=reading,
                )

        except grpc.RpcError as ex:
            raise errors.FailedReadCachedCommandError(str(ex)) from ex
Beispiel #9
0
    def _channel(self):
        """Convenience method to create the client gRPC channel."""
        if self.mode == 'unix':
            target = 'unix:{}'.format(os.path.join(SOCKET_DIR, self.name + '.sock'))
        elif self.mode == 'tcp':
            target = self.addr
        else:
            raise errors.InvalidArgumentsError(
                _('Invalid gRPC client mode: {}').format(self.mode)
            )

        logger.debug(_('Client gRPC channel: {}').format(target))
        return grpc.insecure_channel(target)
Beispiel #10
0
async def boot_target_route(request, rack, board, device):
    """Endpoint to read/write boot target device data.

    This route is an alias for the core `read` functionality when there
    are no valid query parameters specified. It is an alias for the core
    `write` functionality when there are valid query parameters specified.

    Supported Query Parameters:
        target: The boot target to set (hdd|pxe)

    Args:
        request (sanic.request.Request): The incoming request.
        rack (str): The rack which the system resides on.
        board (str): The board which the system resides on.
        device (str): The system device.

    Returns:
        sanic.response.HTTPResponse: The endpoint response.
    """
    await validate.validate_device_type(const.BOOT_TARGET_TYPES, rack, board,
                                        device)

    # Get the valid query parameters. If unsupported query parameters
    # are specified, this will raise an error.
    qparams = validate.validate_query_params(request.raw_args, 'target')
    param_target = qparams.get('target')

    # If any of the parameters are specified, this will be a write request
    # using those parameters.
    if param_target is not None:
        logger.debug(
            _('Boot target alias route: writing (query parameters: {})').
            format(qparams))

        if param_target not in const.boot_targets:
            raise errors.InvalidArgumentsError(
                _('Invalid boot target "{}". Must be one of: {}').format(
                    param_target, const.boot_targets))

        data = {'action': 'target', 'raw': param_target}
        transaction = await commands.write(rack, board, device, data)
        return transaction.to_json()

    # Otherwise, we just read from the device.
    else:
        logger.debug(_('Boot target alias route: reading'))
        reading = await commands.read(rack, board, device)
        return reading.to_json()
Beispiel #11
0
    def format_readings(self):
        """Format the instance's readings to the read response scheme.

        Returns:
            dict: A properly formatted Read response.
        """
        formatted = []

        dev_output = self.device.output
        for reading in self.readings:
            rt = reading.type

            # These fields may not be specified, e.g. in cases where it wouldn't
            # make sense for a reading unit, e.g. LED state (on/off)
            unit = None
            precision = None

            found = False
            for out in dev_output:
                if out.type == rt:
                    symbol = out.unit.symbol
                    name = out.unit.name
                    precision = out.precision

                    if symbol or name:
                        unit = {'symbol': symbol, 'name': name}

                    found = True
                    break

            # If the reading type does not match the supported types, we will not
            # return it, and instead will just just skip over it.
            if not found:
                logger.warning(
                    _('Found unexpected reading type "{}" for device {}').
                    format(rt, self.device))
                continue

            # The value is stored in a protobuf oneof block, so we need to figure out
            # which field it is in, and extract it. If no field is set, take the reading
            # value to be None.
            value = None

            field = reading.WhichOneof('value')
            if field is not None:
                value = getattr(reading, field)

            # Set the specified precision, if specified
            if precision and isinstance(value, float):
                value = round(value, precision)

            formatted.append({
                'value': value,
                'timestamp': reading.timestamp,
                'unit': unit,
                'type': rt,
                'info': reading.info,
            })

        return formatted
Beispiel #12
0
async def validate_device_type(device_type, rack, board, device):
    """Validate that the device associated with the given routing info
    (rack, board device) matches the given device type.

    This checks that the lower-cased device type matches the lower-cased
    expected type, so casing on device type(s) should not matter.

    Args:
        device_type (list[str]): The device types that are permissible,
            e.g. "led", "fan", etc.
        rack (str): The rack which the device belongs to.
        board (str): The board which the device belongs to.
        device (str): The ID of the device.

    Raises:
        errors.InvalidDeviceType: The device does not match the given type.
        errors.DeviceNotFoundError: The specified device is not found.
    """
    __, device = await cache.get_device_info(rack, board, device)  # pylint: disable=unused-variable

    # The type of a device should be the last element in it's kind namespace.
    _type = device.kind.split('.')[-1].lower()
    if _type not in [t.lower() for t in device_type]:
        raise errors.InvalidDeviceType(
            _('Device ({}) is not a supported type {}').format(
                _type, device_type))
Beispiel #13
0
    def write(self, rack, board, device, data):
        """Write data to the specified device.

        Args:
            rack (str): The rack which the device resides on.
            board (str): The board which the device resides on.
            device (str): The identifier for the device to write to.
            data (list[WriteData]): The data to write to the device.

        Returns:
            synse_grpc.api.Transactions: The transactions that can be used
                to track the given write request(s).
        """
        logger.debug(_('Issuing gRPC write request'))

        req = synse_grpc.api.WriteInfo(
            deviceFilter=synse_grpc.api.DeviceFilter(
                device=device,
                board=board,
                rack=rack,
            ),
            data=[d.to_grpc() for d in data])

        timeout = config.options.get('grpc.timeout', None)
        resp = self.grpc.Write(req, timeout=timeout)
        return resp
Beispiel #14
0
    def read_cached(self, start=None, end=None):
        """Get the cached readings from a plugin. If caching readings
        is disabled for the plugin, this will get a dump of the current
        reading state.

        Args:
            start (str): An RFC3339 or RFC3339Nano formatted timestamp
                which defines a starting bound on the cache data to
                return. If no timestamp is specified, there will not
                be a starting bound. (default: None)
            end (str): An RFC3339 or RFC3339Nano formatted timestamp
                which defines an ending bound on the cache data to
                return. If no timestamp is specified, there will not
                be an ending bound. (default: None)

        Yields:
            synse_grpc.api.DeviceReading: A cached reading value
                with its associated device routing info.
        """
        logger.debug(_('Issuing gRPC read cached request'))

        bounds = synse_grpc.api.Bounds(
            start=start or '',
            end=end or '',
        )

        timeout = config.options.get('grpc.timeout', None)
        for reading in self.grpc.ReadCached(bounds, timeout=timeout):
            yield reading
Beispiel #15
0
def discover():
    """Discover plugins for kubernetes based on the kubernetes service
    discovery configuration(s).

    Returns:
        list[str]: A list of host:port addresses for plugins discovered
            via kubernetes.
    """
    addresses = []

    cfg = config.options.get('plugin.discover.kubernetes')
    if not cfg:
        return addresses

    # Currently, everything we want to be able to discover (namely, endpoints)
    # should all be in the same namespace, so we define it globally. If other
    # methods of lookup are added later, we could also have a namespace per
    # resource, e.g. so we can look for endpoints in namespace X and pods in
    # namespace Y, etc.
    #
    # If no namespace is provided via user configuration, if will default to
    # the 'default' namespace.
    ns = config.options.get('plugin.discover.kubernetes.namespace', 'default')
    logger.debug(_('Using namespace "{}" for k8s discovery').format(ns))

    # Currently, we only support plugin discovery via kubernetes service
    # endpoints, under the `plugin.discover.kubernetes.endpoints` config
    # field.
    #
    # We can support other means later.
    endpoints_cfg = cfg.get('endpoints')
    if endpoints_cfg:
        addresses.extend(_register_from_endpoints(ns=ns, cfg=endpoints_cfg))

    return addresses
Beispiel #16
0
def validate_query_params(raw_args, *valid_params):
    """Validate that the incoming request's query parameters are valid.

    Any unsupported query parameter will cause an error to be raised.
    Absence of a supported query parameter will not cause an error. If
    a supported query parameter is found, it is added to the response
    dictionary.

    Args:
        raw_args: An incoming Sanic request's `raw_args`, which contains the
            query params that came in as part of the request.
        *valid_params: The query parameter keys that are valid for the request.

    Returns:
        dict: A dictionary that maps the supported query parameters found in
            the request with their values.

    Raises:
        errors.InvalidArgumentsError: An invalid query parameter was detected.
    """
    params = {}
    for k, v in raw_args.items():
        if k not in valid_params:
            raise errors.InvalidArgumentsError(
                _('Invalid query param: {} (valid params: {})').format(
                    k, valid_params))
        params[k] = v
    return params
Beispiel #17
0
    def metainfo(self, rack=None, board=None):
        """Get all meta-information from a plugin.

        Args:
            rack (str): The rack to filter by.
            board (str): The board to filter by.

        Returns:
            list[synse_plugin.api.MetainfoResponse]: All device meta-information
                provided by the plugin.
        """
        logger.debug(_('Issuing gRPC metainfo request'))

        # If the rack or board is not specified, pass it through as an
        # empty string.
        rack = rack if rack is not None else ''
        board = board if board is not None else ''

        req = synse_api.MetainfoRequest(
            rack=rack,
            board=board
        )

        timeout = config.options.get('grpc.timeout', None)
        resp = [r for r in self.stub.Metainfo(req, timeout=timeout)]

        return resp
Beispiel #18
0
async def get_device_info(rack, board, device):
    """Get the device information for a device.

    Args:
        rack (str): The rack which the device resides on.
        board (str): The board which the device resides on.
        device (str): The ID of the device to get meta-info for.

    Returns:
        tuple(str, Device): A tuple where the first item is
            the name of the plugin that the device is associated with and
            the second item is the device information for that device.

    Raises:
        errors.DeviceNotFoundError: The given rack-board-device combination
            does not correspond to a known device.
    """
    cid = utils.composite(rack, board, device)

    # This also builds the plugins cache
    _cache = await get_device_info_cache()
    dev = _cache.get(cid)

    if dev is None:
        raise errors.DeviceNotFoundError(
            _('{} does not correspond with a known device').format('/'.join(
                [rack, board, device])))

    # If the device exists, it will have come from a plugin, so we should
    # always have the plugin name here.
    pcache = await _plugins_cache.get(PLUGINS_CACHE_KEY)
    return pcache.get(cid), dev
Beispiel #19
0
async def add_transaction(transaction_id, context, plugin_name):
    """Add a new transaction to the transaction cache.

    This cache tracks transactions and maps them to the plugin from which they
    originated, as well as the context of the transaction.

    Args:
        transaction_id (str): The ID of the transaction.
        context (dict): The action/raw data of the write transaction that
            can be used to help identify the transaction.
        plugin_name (str): The name of the plugin to associate with the
            transaction.

    Returns:
        bool: True if successful; False otherwise.
    """
    ttl = config.options.get('cache.transaction.ttl', None)
    logger.debug(
        _('Caching transaction {} from plugin {} ({})').format(
            transaction_id, plugin_name, context))
    return await transaction_cache.set(transaction_id, {
        'plugin': plugin_name,
        'context': context
    },
                                       ttl=ttl)
Beispiel #20
0
async def power_route(request, rack, board, device):
    """Endpoint to read/write power device data.

    This route is an alias for the core `read` functionality when there
    are no valid query parameters specified. It is an alias for the core
    `write` functionality when there are valid query parameters specified.

    Supported Query Parameters:
        state: The power state to set (on|off|cycle)

    Args:
        request (sanic.request.Request): The incoming request.
        rack (str): The rack which the power device resides on.
        board (str): The board which the power device resides on.
        device (str): The power device.

    Returns:
        sanic.response.HTTPResponse: The endpoint response.
    """
    await validate.validate_device_type(const.TYPE_POWER, rack, board, device)

    # Get the valid query parameters. If unsupported query parameters
    # are specified, this will raise an error.
    qparams = validate.validate_query_params(request.raw_args, 'state')
    param_state = qparams.get('state')

    # If any of the parameters are specified, this will be a write request
    # using those parameters.
    if param_state is not None:
        logger.debug(
            _('Power alias route: writing (query parameters: {})').format(
                qparams))

        if param_state not in const.power_actions:
            raise errors.InvalidArgumentsError(
                _('Invalid power state "{}". Must be one of: {}').format(
                    param_state, const.power_actions))

        data = {'action': 'state', 'raw': param_state}
        transaction = await commands.write(rack, board, device, data)
        return transaction.to_json()

    # Otherwise, we just read from the device.
    else:
        logger.debug(_('Power alias route: reading'))
        reading = await commands.read(rack, board, device)
        return reading.to_json()
Beispiel #21
0
    def _validate_mode(self):
        """Validate the plugin mode.

        Raises:
            errors.PluginStateError: The plugin mode is unsupported. This will
                also be raised if the mode is 'unix', but the socket does not
                exist.
        """
        if self.mode not in ['tcp', 'unix']:
            raise errors.PluginStateError(
                _('The given mode ({}) must be one of: tcp, unix').format(
                    self.mode))

        if self.mode == 'unix':
            if not os.path.exists(self.addr):
                raise errors.PluginStateError(
                    _('Unix socket ({}) does not exist').format(self.addr))
Beispiel #22
0
    def make_channel(self):
        """Make the channel for the grpc client stub."""
        # If Synse Server is configured to communicate with the plugin using
        # TLS, set up a secure channel, otherwise use an insecure channel.
        # FIXME (etd) - we'll probably want to support using a CA here?
        if config.options.get('grpc.tls'):
            logger.info(_('TLS enabled for gRPC'))

            cert = config.options.get('grpc.tls.cert')
            logger.info(_('Using cert file: {}').format(cert))
            with open(cert, 'rb') as f:
                plugin_cert = f.read()

            creds = grpc.ssl_channel_credentials(root_certificates=plugin_cert)
            self.channel = grpc.secure_channel(self._fmt_address(), creds)
        else:
            self.channel = grpc.insecure_channel(self._fmt_address())
Beispiel #23
0
async def lock_route(request, rack, board, device):  # pylint: disable=unused-argument
    """Endpoint to read/write lock device data.

    This route is an alias for the core `read` functionality when there
    are no valid query parameters specified. It is an alias for the core
    `write` functionality when there are valid query parameters specified.

    Args:
        request (sanic.request.Request): The incoming request.
        rack (str): The rack which the lock resides on.
        board (str): The board which the lock resides on.
        device (str): The lock device.

    Returns:
        sanic.response.HTTPResponse: The endpoint response.
    """
    await validate.validate_device_type(const.LOCK_TYPES, rack, board, device)

    # Get the valid query parameters. If unsupported query parameters
    # are specified, this will raise an error.
    qparams = validate.validate_query_params(request.raw_args, 'action')
    param_action = qparams.get('action')

    # If any of the parameters are specified, this will be a write request
    # using those parameters.
    if param_action is not None:
        logger.debug(
            _('Lock alias route: writing (query parameters: {})').format(
                qparams))

        if param_action not in const.lock_actions:
            raise errors.InvalidArgumentsError(
                _('Invalid boot target "{}". Must be one of: {}').format(
                    param_action, const.lock_actions))

        data = {
            'action': param_action,
        }
        transaction = await commands.write(rack, board, device, data)
        return transaction.to_json()

    # Otherwise, we just read from the device.
    else:
        logger.debug(_('Lock alias route: reading'))
        reading = await commands.read(rack, board, device)
        return reading.to_json()
Beispiel #24
0
    def purge(self, ids):
        """Remove all of the specified Plugins from the manager.

        Args:
            ids (list[str]): The ids of the Plugins to remove.
        """
        for plugin_id in ids:
            if plugin_id in self.plugins:
                del self.plugins[plugin_id]
        logger.debug(_('PluginManager purged plugins: {}').format(ids))
Beispiel #25
0
async def capabilities():
    """The handler for the Synse Server "capabilities" API command.

    Returns:
        CapabilitiesResponse: The "capabilities" response scheme model.
    """
    logger.debug(_('Capabilities Command'))

    cache_data = await cache.get_capabilities_cache()
    return CapabilitiesResponse(data=cache_data)
Beispiel #26
0
    def purge(self, names):
        """Remove all of the specified Plugins from the manager.

        Args:
            names (list[str]): The names of the Plugins to remove.
        """
        for name in names:
            if name in self.plugins:
                del self.plugins[name]
        logger.debug(_('PluginManager purged plugins: {}').format(names))
Beispiel #27
0
async def info(rack, board=None, device=None):
    """The handler for the Synse Server "info" API command.

    Args:
        rack (str): The rack to get information for.
        board (str): The board to get information for.
        device (str): The device to get information for.

    Returns:
        InfoResponse: The "info" response scheme model.
    """
    logger.debug(
        _('Info Command (args: {}, {}, {})').format(rack, board, device))

    if rack is None:
        raise errors.InvalidArgumentsError(
            _('No rack specified when issuing info command'))

    _cache = await cache.get_resource_info_cache()
    r, b, d = get_resources(_cache, rack, board, device)

    if board is not None:
        # We have: rack, board, device
        if device is not None:
            response = d

        # We have: rack, board
        else:
            response = {
                'board': b['board'],
                'location': {
                    'rack': r['rack']
                },
                'devices': list(b['devices'].keys())
            }

    else:
        # We have: rack
        response = {'rack': r['rack'], 'boards': list(r['boards'].keys())}

    return InfoResponse(response)
Beispiel #28
0
    def metainfo(self):
        """Get the plugin metainfo.

        Returns:
            synse_grpc.api.Metadata: The plugin's metadata.
        """
        logger.debug(_('Issuing gRPC metainfo request'))

        req = synse_grpc.api.Empty()
        timeout = config.options.get('grpc.timeout', None)
        resp = self.grpc.Metainfo(req, timeout=timeout)
        return resp
Beispiel #29
0
async def clear_cache(namespace):
    """Clear the cache with the given namespace.

    Cache namespaces are defined in the cache module as variables with
    a "NS_" prefix.

    Args:
        namespace (str): The namespace of the cache to clear.
    """
    logger.debug(_('Invalidating cache: {}').format(namespace))
    _cache = aiocache.caches.get('default')
    return await _cache.clear(namespace=namespace)
Beispiel #30
0
def register_plugins():
    """Register all of the configured plugins.

    Plugins can either use a unix socket or TCP for communication. Unix
    socket based plugins will be detected from the presence of the socket
    file in a well-known directory, or via configuration. TCP based plugins
    will need to be made known to Synse Server via configuration.

    Upon initialization, the Plugin instances are automatically registered
    with the PluginManager.
    """
    unix = register_unix_plugins()
    tcp = register_tcp_plugins()

    diff = set(Plugin.manager.plugins) - set(unix + tcp)

    # Now that we have found all current plugins, we will want to clear out
    # any old plugins which may no longer be present.
    logger.debug(_('Plugins to purge from manager: {}').format(diff))
    Plugin.manager.purge(diff)

    logger.debug(_('Plugin registration complete'))