Exemple #1
0
async def patch_device(request: core_api.APIRequest,
                       params: Attributes) -> None:
    def unexpected_field_code(field: str) -> str:
        if field in core_device_attrs.get_attrdefs():
            return 'attribute-not-modifiable'

        else:
            return 'no-such-attribute'

    core_api_schema.validate(params,
                             core_device_attrs.get_schema(),
                             unexpected_field_code=unexpected_field_code,
                             unexpected_field_name='attribute')

    try:
        reboot_required = core_device_attrs.set_attrs(params)

    except core_device_attrs.DeviceAttributeError as e:
        raise core_api.APIError(400, e.error, attribute=e.attribute)

    except Exception as e:
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e

    await core_device.save()
    await core_device_events.trigger_update()

    if reboot_required:
        main.loop.call_later(2, system.reboot)
Exemple #2
0
async def put_device(request: core_api.APIRequest, params: Attributes) -> None:
    core_api_schema.validate(params, core_device_attrs.get_schema(loose=True))

    # Password fields must explicitly be ignored, so we pop them from supplied data
    for f in ('admin', 'normal', 'viewonly'):
        params.pop(f'{f}_password', None)

    # Ignore the date attribute
    params.pop('date', None)

    # Reset device attributes
    await core_device.reset(preserve_attrs=[
        'admin_password_hash', 'normal_password_hash', 'viewonly_password_hash'
    ])
    await core_device.load()

    try:
        core_device_attrs.set_attrs(params, ignore_extra=True)

    except core_device_attrs.DeviceAttributeError as e:
        raise core_api.APIError(400, e.error, attribute=e.attribute)

    except Exception as e:
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e

    await core_device.save()
    await core_device_events.trigger_update()
Exemple #3
0
async def patch_port(request: core_api.APIRequest, port_id: str, params: Attributes) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no such port')

    def unexpected_field_msg(field: str) -> str:
        if field in port.get_non_modifiable_attrs():
            return 'attribute not modifiable: {field}'

        else:
            return 'no such attribute: {field}'

    core_api_schema.validate(params, await port.get_schema(), unexpected_field_msg=unexpected_field_msg)

    # Step validation
    for name, value in params.items():
        attrdef = port.ATTRDEFS[name]
        step = attrdef.get('step')
        _min = attrdef.get('min')
        if None not in (step, _min) and step != 0 and (value - _min) % step:
            raise core_api.APIError(400, f'invalid field: {name}')

    errors_by_name = {}

    async def set_attr(attr_name: str, attr_value: Attribute) -> None:
        core_api.logger.debug('setting attribute %s = %s on %s', attr_name, json_utils.dumps(attr_value), port)

        try:
            await port.set_attr(attr_name, attr_value)

        except Exception as e:
            errors_by_name[attr_name] = e

    if params:
        await asyncio.wait([set_attr(name, value) for name, value in params.items()])

    if errors_by_name:
        name, error = next(iter(errors_by_name.items()))

        if isinstance(error, core_api.APIError):
            raise error

        elif isinstance(error, core_ports.InvalidAttributeValue):
            raise core_api.APIError(400, f'invalid field: {name}')

        elif isinstance(error, core_ports.PortTimeout):
            raise core_api.APIError(504, 'port timeout')

        elif isinstance(error, core_ports.PortError):
            raise core_api.APIError(502, f'port error: {error}')

        else:
            # Transform any unhandled exception into APIError(500)
            raise core_api.APIError(500, str(error))

    await port.save()
async def patch_webhooks(request: core_api.APIRequest,
                         params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_WEBHOOKS)

    try:
        core_webhooks.setup(**params)

    except core_webhooks.InvalidParamError as e:
        raise core_api.APIError(400, str(e)) from e

    core_webhooks.save()
Exemple #5
0
async def patch_reverse(request: core_api.APIRequest,
                        params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_REVERSE)

    try:
        core_reverse.setup(**params)

    except core_reverse.InvalidParamError as e:
        raise core_api.APIError(400, str(e)) from e

    core_reverse.save()
Exemple #6
0
async def patch_port_value(request: core_api.APIRequest, port_id: str,
                           params: PortValue) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no-such-port')

    try:
        core_api_schema.validate(params, await port.get_value_schema())

    except core_api.APIError:
        # Transform any validation error into an invalid-field APIError for value
        raise core_api.APIError(400, 'invalid-value') from None

    value = params

    # Step validation
    step = await port.get_attr('step')
    min_ = await port.get_attr('min')
    if None not in (step, min_) and step != 0 and (value - min_) % step:
        raise core_api.APIError(400, 'invalid-value')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port-disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only-port')

    old_value = port.get_last_read_value()

    try:
        await port.write_transformed_value(value,
                                           reason=core_ports.CHANGE_REASON_API)

    except core_ports.PortTimeout as e:
        raise core_api.APIError(504, 'port-timeout') from e

    except core_ports.PortError as e:
        raise core_api.APIError(502, 'port-error', code=str(e)) from e

    except core_api.APIError:
        raise

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e

    await core_main.update()

    # If port value hasn't really changed, use 202 Accepted to inform consumer that new value hasn't been applied yet
    current_value = port.get_last_read_value()
    if (old_value == current_value) and (old_value != value):
        port.debug("API supplied value hasn't been applied right away")
        raise core_api.APIAccepted()
async def patch_firmware(request: core_api.APIRequest,
                         params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_FIRMWARE)

    status = await fwupdate.get_status()
    if status not in (fwupdate.STATUS_IDLE, fwupdate.STATUS_ERROR):
        raise core_api.APIError(503, 'busy')

    if params.get('url'):
        await fwupdate.update_to_url(params['url'])

    else:  # Assuming params['version']
        await fwupdate.update_to_version(params['version'])
Exemple #8
0
async def post_slave_device_events(request: core_api.APIRequest, name: str, params: GenericJSONDict) -> None:
    slave = slaves_devices.get(name)
    if not slave:
        raise core_api.APIError(404, 'no-such-device')

    # Slave events endpoint has special privilege requirements: its token signature must be validated using slave admin
    # password

    auth = request.headers.get('Authorization')
    if not auth:
        slave.warning('missing authorization header')
        raise core_api.APIError(401, 'authentication-required')

    try:
        core_api_auth.parse_auth_header(
            auth,
            core_api_auth.ORIGIN_DEVICE,
            lambda u: slave.get_admin_password_hash(),
            require_usr=False
        )

    except core_api_auth.AuthError as e:
        slave.warning(str(e))
        raise core_api.APIError(401, 'authentication-required') from e

    core_api_schema.validate(params, api_schema.POST_SLAVE_DEVICE_EVENTS)

    if slave.get_poll_interval() > 0:
        raise core_api.APIError(400, 'polling-enabled')

    if slave.is_listen_enabled():
        raise core_api.APIError(400, 'listening-enabled')

    # At this point we can be sure the slave is permanently offline

    try:
        await slave.handle_event(params)

    except Exception as e:
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e

    slave.update_last_sync()
    await slave.save()

    # As soon as we receive event requests (normally generated by a webhook), we can consider the device is temporarily
    # reachable, so we apply provisioning values and update data locally
    slave.schedule_provisioning_and_update(1)
Exemple #9
0
async def patch_port_value(request: core_api.APIRequest, port_id: str, params: PortValue) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no such port')

    core_api_schema.validate(params, await port.get_value_schema(), invalid_request_msg='invalid value')

    value = params

    # Step validation
    step = await port.get_attr('step')
    _min = await port.get_attr('min')
    if None not in (step, _min) and step != 0 and (value - _min) % step:
        raise core_api.APIError(400, 'invalid field: value')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only port')

    old_value = port.get_value()

    try:
        await port.write_transformed_value(value, reason=core_ports.CHANGE_REASON_API)

    except core_ports.PortTimeout as e:
        raise core_api.APIError(504, 'port timeout') from e

    except core_ports.PortError as e:
        raise core_api.APIError(502, f'port error: {e}') from e

    except core_api.APIError:
        raise

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, str(e)) from e

    await main.update()

    # If port value hasn't really changed, trigger a value-change to inform consumer that new value has been ignored
    current_value = port.get_value()
    if old_value == current_value:
        port.debug('API supplied value was ignored')
        port.trigger_value_change()
Exemple #10
0
async def patch_port_sequence(request: core_api.APIRequest, port_id: str,
                              params: GenericJSONDict) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no-such-port')

    core_api_schema.validate(params, core_api_schema.PATCH_PORT_SEQUENCE)

    values = params['values']
    delays = params['delays']
    repeat = params['repeat']

    if len(values) != len(delays):
        raise core_api.APIError(400, 'invalid-field', field='delays')

    value_schema = await port.get_value_schema()
    step = await port.get_attr('step')
    min_ = await port.get_attr('min')
    for value in values:
        # Translate any APIError generated when validating value schema into an invalid-field APIError on value
        try:
            core_api_schema.validate(value, value_schema)

        except core_api.APIError:
            raise core_api.APIError(400, 'invalid-field',
                                    field='values') from None

        # Step validation
        if None not in (step, min_) and step != 0 and (value - min_) % step:
            raise core_api.APIError(400, 'invalid-field', field='values')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port-disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only-port')

    if await port.get_attr('expression'):
        raise core_api.APIError(400, 'port-with-expression')

    try:
        await port.set_sequence(values, delays, repeat)

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, 'unexpected-error', message=str(e)) from e
Exemple #11
0
async def patch_discovered_device(request: core_api.APIRequest, name: str,
                                  params: GenericJSONDict) -> GenericJSONDict:
    core_api_schema.validate(params, api_schema.PATCH_DISCOVERED_DEVICE)

    discovered_devices = slaves_discover.get_discovered_devices() or {}
    discovered_device = discovered_devices.get(name)
    if not discovered_device:
        raise core_api.APIError(404, 'no-such-device')

    attrs = params['attrs']
    try:
        discovered_device = await slaves_discover.configure(
            discovered_device, attrs)

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

    return discovered_device.to_json()
Exemple #12
0
async def post_reset(request: core_api.APIRequest,
                     params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.POST_RESET)

    factory = params.get('factory')

    if factory:
        core_api.logger.info('resetting to factory defaults')

        core_ports.reset()
        core_vports.reset()
        core_device.reset()
        if settings.webhooks.enabled:
            core_webhooks.reset()
        if settings.reverse.enabled:
            core_reverse.reset()
        if settings.slaves.enabled:
            slaves.reset()

    main.loop.call_later(2, system.reboot)
Exemple #13
0
async def patch_slave_device(request: core_api.APIRequest, name: str,
                             params: GenericJSONDict) -> None:
    core_api_schema.validate(params, api_schema.PATCH_SLAVE_DEVICE)

    slave = slaves_devices.get(name)
    if not slave:
        raise core_api.APIError(404, 'no such device')

    if params.get('enabled') is True and not slave.is_enabled():
        await slave.enable()

    elif params.get('enabled') is False and slave.is_enabled():
        await slave.disable()

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

    if params.get('poll_interval') is not None:
        slave.set_poll_interval(params['poll_interval'])

    if params.get('listen_enabled') is not None:
        if params['listen_enabled']:
            # We need to know if device supports listening; we therefore call GET /device before enabling it

            if slave.is_enabled():
                try:
                    attrs = await slave.api_call('GET', '/device')

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

                if 'listen' not in attrs['flags']:
                    raise core_api.APIError(400, 'no listen support')

            slave.enable_listen()

        else:
            slave.disable_listen()

    slave.save()
    slave.trigger_update()
Exemple #14
0
async def put_webhooks(request: core_api.APIRequest,
                       params: GenericJSONDict) -> None:
    core_api_schema.validate(params, core_api_schema.PATCH_WEBHOOKS)

    # Also ensure that needed fields are not empty when webhooks are enabled
    if params['enabled']:
        if not params['host']:
            raise core_api.APIError(400, 'invalid-field', field='host')
        if not params['path']:
            raise core_api.APIError(400, 'invalid-field', field='path')

    if 'password' not in params and 'password_hash' not in params:
        raise core_api.APIError(400, 'missing-field', field='password')

    try:
        core_webhooks.setup(**params)

    except core_webhooks.InvalidParamError as e:
        raise core_api.APIError(400, 'invalid-field', field=e.param) from e

    await core_webhooks.save()
Exemple #15
0
async def post_port_sequence(request: core_api.APIRequest, port_id: str, params: GenericJSONDict) -> None:
    port = core_ports.get(port_id)
    if port is None:
        raise core_api.APIError(404, 'no such port')

    core_api_schema.validate(params, core_api_schema.POST_PORT_SEQUENCE)

    values = params['values']
    delays = params['delays']
    repeat = params['repeat']

    if len(values) != len(delays):
        raise core_api.APIError(400, 'invalid field: delays')

    value_schema = await port.get_value_schema()
    step = await port.get_attr('step')
    _min = await port.get_attr('min')
    for value in values:
        core_api_schema.validate(value, value_schema, invalid_request_msg='invalid field: values')

        # Step validation
        if None not in (step, _min) and step != 0 and (value - _min) % step:
            raise core_api.APIError(400, 'invalid field: values')

    if not port.is_enabled():
        raise core_api.APIError(400, 'port disabled')

    if not await port.is_writable():
        raise core_api.APIError(400, 'read-only port')

    if await port.get_attr('expression'):
        raise core_api.APIError(400, 'port with expression')

    try:
        await port.set_sequence(values, delays, repeat)

    except Exception as e:
        # Transform any unhandled exception into APIError(500)
        raise core_api.APIError(500, str(e)) from e
Exemple #16
0
async def post_introspect(request: core_api.APIRequest,
                          params: GenericJSONDict) -> GenericJSONDict:
    core_api_schema.validate(params, core_api_schema.POST_INTROSPECT)

    exc_str = None
    res_str = None

    try:
        imports = params.get('imports', [])
        extra_locals = {}
        for imp in imports:
            extra_locals[imp.split('.')[0]] = importlib.__import__(imp)

        result = eval(params['code'], globals(), dict(locals(),
                                                      **extra_locals))
        if inspect.isawaitable(result):
            result = await result

        res_str = str(result)

    except Exception:
        exc_str = traceback.format_exc()

    return {'result': res_str, 'exception': exc_str}
Exemple #17
0
async def post_ports(request: core_api.APIRequest, params: GenericJSONDict) -> Attributes:
    core_api_schema.validate(params, core_api_schema.POST_PORTS)

    port_id = params['id']
    port_type = params['type']
    _min = params.get('min')
    _max = params.get('max')
    integer = params.get('integer')
    step = params.get('step')
    choices = params.get('choices')

    if core_ports.get(port_id):
        raise core_api.APIError(400, 'duplicate port')

    if len(core_vports.all_settings()) >= settings.core.virtual_ports:
        raise core_api.APIError(400, 'too many ports')

    core_vports.add(port_id, port_type, _min, _max, integer, step, choices)
    port = await core_ports.load_one(
        'qtoggleserver.core.vports.VirtualPort',
        {
            'port_id': port_id,
            '_type': port_type,
            '_min': _min,
            '_max': _max,
            'integer': integer,
            'step': step,
            'choices': choices
        }
    )

    # A virtual port is enabled by default
    await port.enable()
    await port.save()

    return await port.to_json()
Exemple #18
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 #19
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()
Exemple #20
0
async def post_slave_devices(request: core_api.APIRequest, params: GenericJSONDict) -> GenericJSONDict:
    core_api_schema.validate(params, api_schema.POST_SLAVE_DEVICES)
    slave = await add_slave_device(params)

    return slave.to_json()
Exemple #21
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 #22
0
async def set_port_attrs(port: core_ports.BasePort, attrs: GenericJSONDict, ignore_extra_attrs: bool) -> None:
    non_modifiable_attrs = await port.get_non_modifiable_attrs()

    def unexpected_field_code(field: str) -> str:
        if field in non_modifiable_attrs:
            return 'attribute-not-modifiable'

        else:
            return 'no-such-attribute'

    schema = await port.get_schema()
    if ignore_extra_attrs:
        schema = dict(schema)
        schema['additionalProperties'] = True  # Ignore non-existent and non-modifiable attributes

    core_api_schema.validate(
        attrs,
        schema,
        unexpected_field_code=unexpected_field_code,
        unexpected_field_name='attribute'
    )

    # Step validation
    attrdefs = await port.get_attrdefs()
    for name, value in attrs.items():
        attrdef = attrdefs.get(name)
        if attrdef is None:
            continue

        step = attrdef.get('step')
        min_ = attrdef.get('min')
        if None not in (step, min_) and step != 0 and (value - min_) % step:
            raise core_api.APIError(400, 'invalid-field', field=name)

    errors_by_name = {}

    async def set_attr(attr_name: str, attr_value: Attribute) -> None:
        core_api.logger.debug('setting attribute %s = %s on %s', attr_name, json_utils.dumps(attr_value), port)

        try:
            await port.set_attr(attr_name, attr_value)

        except Exception as e1:
            errors_by_name[attr_name] = e1

    value = attrs.pop('value', None)

    if attrs:
        await asyncio.wait([set_attr(n, v) for n, v in attrs.items()])

    if errors_by_name:
        name, error = next(iter(errors_by_name.items()))

        if isinstance(error, core_api.APIError):
            raise error

        elif isinstance(error, core_ports.InvalidAttributeValue):
            raise core_api.APIError(400, 'invalid-field', field=name, details=error.details)

        elif isinstance(error, core_ports.PortTimeout):
            raise core_api.APIError(504, 'port-timeout')

        elif isinstance(error, core_ports.PortError):
            raise core_api.APIError(502, 'port-error', code=str(error))

        else:
            # Transform any unhandled exception into APIError(500)
            raise core_api.APIError(500, 'unexpected-error', message=str(error)) from error

    # If value is supplied among attrs, use it to update port value, but in background and ignoring any errors
    if value is not None and port.is_enabled():
        asyncio.create_task(port.write_transformed_value(value, reason=core_ports.CHANGE_REASON_API))

    await port.save()
Exemple #23
0
async def post_ports(request: core_api.APIRequest, params: GenericJSONDict) -> Attributes:
    core_api_schema.validate(params, core_api_schema.POST_PORTS)
    port = await add_virtual_port(params)

    return await port.to_json()