Ejemplo n.º 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
Ejemplo n.º 2
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()