Exemple #1
0
async def add_slave_device(properties: GenericJSONDict) -> slaves_devices.Slave:
    properties = dict(properties)  # Work on copy, don't mess up incoming argument

    scheme = properties.pop('scheme')
    host = properties.pop('host')
    port = properties.pop('port')
    path = properties.pop('path')
    admin_password = properties.pop('admin_password', None)
    admin_password_hash = properties.pop('admin_password_hash', None)
    poll_interval = properties.pop('poll_interval', 0)
    listen_enabled = properties.pop('listen_enabled', None)

    # Look for slave duplicate
    for slave in slaves_devices.get_all():
        if (slave.get_scheme() == scheme and
            slave.get_host() == host and
            slave.get_port() == port and
            slave.get_path() == path):

            raise core_api.APIError(400, 'duplicate-device')

    if poll_interval and listen_enabled:
        raise core_api.APIError(400, 'listening-and-polling')

    # Ensure admin password is supplied, in a way or another
    if admin_password is None and admin_password_hash is None:
        raise core_api.APIError(400, 'missing-field', field='admin_password')

    try:
        slave = await slaves_devices.add(
            scheme,
            host,
            port,
            path,
            poll_interval,
            listen_enabled,
            admin_password=admin_password,
            admin_password_hash=admin_password_hash,
            **properties
        )

    except (core_responses.HostUnreachable,
            core_responses.NetworkUnreachable,
            core_responses.UnresolvableHostname) as e:

        raise core_api.APIError(502, 'unreachable') from e

    except core_responses.ConnectionRefused as e:
        raise core_api.APIError(502, 'connection-refused') from e

    except core_responses.InvalidJson as e:
        raise core_api.APIError(502, 'invalid-device') from e

    except core_responses.Timeout as e:
        raise core_api.APIError(504, 'device-timeout') from e

    except slaves_exceptions.InvalidDevice as e:
        raise core_api.APIError(502, 'invalid-device') from e

    except slaves_exceptions.NoListenSupport as e:
        raise core_api.APIError(400, 'no-listen-support') from e

    except slaves_exceptions.DeviceAlreadyExists as e:
        raise core_api.APIError(400, 'duplicate-device') from e

    except core_api.APIError:
        raise

    except core_responses.HTTPError as e:
        # We need to treat the 401/403 slave responses as a 400
        if e.code in (401, 403):
            raise core_api.APIError(400, 'forbidden') from e

        raise core_api.APIError.from_http_error(e) from e

    except Exception as e:
        raise slaves_exceptions.adapt_api_error(e) from e

    return slave
Exemple #2
0
async def put_slave_devices(request: core_api.APIRequest, params: GenericJSONList) -> None:
    if not settings.core.backup_support:
        raise core_api.APIError(404, 'no-such-function')

    core_api_schema.validate(
        params,
        api_schema.PUT_SLAVE_DEVICES
    )

    core_api.logger.debug('restoring slave devices')

    # Disable event handling during the processing of this request, as we're going to trigger a full-update at the end
    core_events.disable()

    # Temporarily disable core updating (port polling, expression evaluating and value-change handling)
    core_main.disable_updating()

    try:
        # Remove all slave devices
        for slave in slaves_devices.get_all():
            await slaves_devices.remove(slave)

        add_device_schema = dict(api_schema.POST_SLAVE_DEVICES)
        add_device_schema['additionalProperties'] = True

        # Validate supplied slave properties
        for index, properties in enumerate(params):
            await wrap_error_with_index(
                index,
                core_api_schema.validate,
                properties,
                add_device_schema
            )

        # Add slave devices
        add_slave_futures = []
        for index, properties in enumerate(params):
            add_slave_future = wrap_error_with_index(
                index,
                add_slave_device_retry_disabled,
                properties
            )
            add_slave_futures.append(add_slave_future)

        added_slaves = await asyncio.gather(*add_slave_futures)

        # Wait for devices to come online
        wait_online_futures = []
        for slave in added_slaves:
            if slave.is_enabled():
                wait_online_futures.append(slave.wait_online(timeout=settings.slaves.long_timeout))

        try:
            await asyncio.gather(*wait_online_futures)

        except asyncio.TimeoutError:
            # Ignore timeouts; this is a best-effort API call
            pass

    finally:
        core_main.enable_updating()
        core_events.enable()

    await core_events.trigger_full_update()

    core_api.logger.debug('slave devices restore done')
Exemple #3
0
async def put_ports(request: core_api.APIRequest, params: GenericJSONList) -> None:
    if not settings.core.backup_support:
        raise core_api.APIError(404, 'no-such-function')

    core_api_schema.validate(
        params,
        core_api_schema.PUT_PORTS
    )

    core_api.logger.debug('restoring ports')

    # Disable event handling during the processing of this request, as we're going to trigger a full-update at the end
    core_events.disable()

    # Temporarily disable core updating (port polling, expression evaluating and value-change handling)
    core_main.disable_updating()

    try:
        # Remove all (local) virtual ports
        for port in core_ports.get_all():
            if not isinstance(port, core_vports.VirtualPort):
                continue

            await port.remove()
            await core_vports.remove(port.get_id())

        # Reset ports
        await core_ports.reset()
        if settings.slaves.enabled:
            await slaves.reset_ports()
        for port in core_ports.get_all():
            await port.reset()

        add_port_schema = dict(core_api_schema.POST_PORTS)
        add_port_schema['additionalProperties'] = True

        # Restore supplied attributes
        for attrs in params:
            id_ = attrs.get('id')
            if id_ is None:
                core_api.logger.warning('ignoring entry without id')
                continue

            port = core_ports.get(id_)

            # Virtual ports must be added first (unless they belong to a slave)
            virtual = attrs.get('virtual')
            if port is not None:  # Port already exists so it probably belongs to a slave
                virtual = False
            for slave in slaves_devices.get_all():
                if id_.startswith(f'{slave.get_name()}.'):  # id indicates that port belongs to a slave
                    virtual = False
                    break
            if 'provisioning' in attrs:  # A clear indication that port belongs to a slave
                virtual = False

            if virtual:
                await wrap_error_with_port_id(
                    id_,
                    core_api_schema.validate,
                    attrs,
                    add_port_schema
                )
                port = await wrap_error_with_port_id(
                    id_,
                    add_virtual_port,
                    attrs
                )

            if port is None:
                core_api.logger.warning('ignoring unknown port id "%s"', id_)
                continue

            if isinstance(port, slaves_ports.SlavePort):
                core_api.logger.debug('restoring slave port "%s"', id_)

                # For slave ports, ignore any attributes that are not kept on master
                attrs = {n: v for n, v in attrs.items() if n in ('tag', 'expression', 'expires')}

            else:
                core_api.logger.debug('restoring local port "%s"', id_)

            await wrap_error_with_port_id(
                id_,
                set_port_attrs,
                port,
                attrs,
                ignore_extra_attrs=True
            )

    finally:
        core_main.enable_updating()
        core_events.enable()

    await core_events.trigger_full_update()

    core_api.logger.debug('ports restore done')
Exemple #4
0
async def get_slave_devices(request: core_api.APIRequest) -> GenericJSONList:
    return [slave.to_json() for slave in slaves_devices.get_all()]
Exemple #5
0
async def post_slave_devices(request: core_api.APIRequest,
                             params: GenericJSONDict) -> GenericJSONDict:
    core_api_schema.validate(params, api_schema.POST_SLAVE_DEVICES)

    scheme = params['scheme']
    host = params['host']
    port = params['port']
    path = params['path']
    admin_password = params['admin_password']
    poll_interval = params['poll_interval']
    listen_enabled = params['listen_enabled']

    # Look for slave duplicate
    for slave in slaves_devices.get_all():
        if (slave.get_scheme() == scheme and slave.get_host() == host
                and slave.get_port() == port and slave.get_path() == path):

            raise core_api.APIError(400, 'duplicate device')

    if poll_interval and listen_enabled:
        raise core_api.APIError(400, 'listening and polling')

    try:
        slave = await slaves_devices.add(scheme,
                                         host,
                                         port,
                                         path,
                                         poll_interval,
                                         listen_enabled,
                                         admin_password=admin_password)

    except (core_responses.HostUnreachable, core_responses.NetworkUnreachable,
            core_responses.UnresolvableHostname) as e:

        raise core_api.APIError(502, 'unreachable') from e

    except core_responses.ConnectionRefused as e:
        raise core_api.APIError(502, 'connection refused') from e

    except core_responses.InvalidJson as e:
        raise core_api.APIError(502, 'invalid device') from e

    except core_responses.Timeout as e:
        raise core_api.APIError(504, 'device timeout') from e

    except exceptions.InvalidDevice as e:
        raise core_api.APIError(502, 'invalid device') from e

    except exceptions.NoListenSupport as e:
        raise core_api.APIError(400, 'no listen support') from e

    except exceptions.DeviceAlreadyExists as e:
        raise core_api.APIError(400, 'duplicate device') from e

    except core_api.APIError:
        raise

    except core_responses.HTTPError as e:
        # We need to treat the 401/403 slave responses as a 400
        if e.code in (401, 403):
            raise core_api.APIError(400, 'forbidden') from e

        raise core_api.APIError.from_http_error(e) from e

    except Exception as e:
        raise exceptions.adapt_api_error(e) from e

    return slave.to_json()