Esempio n. 1
0
def conditions_schema():
    global _CONDITIONS_SCHEMA
    if _CONDITIONS_SCHEMA is None:
        condition_plugins = [x.name for x in
                             plugins_base.rule_conditions_manager()]
        _CONDITIONS_SCHEMA = {
            "title": "Inspector rule conditions schema",
            "type": "array",
            # we can have rules that always apply
            "minItems": 0,
            "items": {
                "type": "object",
                # field might become optional in the future, but not right now
                "required": ["op", "field"],
                "properties": {
                    "op": {
                        "description": "condition operator",
                        "enum": condition_plugins
                    },
                    "field": {
                        "description": "JSON path to field for matching",
                        "type": "string"
                    },
                    "multiple": {
                        "description": "how to treat multiple values",
                        "enum": ["all", "any", "first"]
                    },
                },
                # other properties are validated by plugins
                "additionalProperties": True
            }
        }

    return _CONDITIONS_SCHEMA
Esempio n. 2
0
    def check_conditions(self, node_info, data):
        """Check if conditions are true for a given node.

        :param node_info: a NodeInfo object
        :param data: introspection data
        :returns: True if conditions match, otherwise False
        """
        LOG.debug('Checking rule "%s"', self.description,
                  node_info=node_info, data=data)
        ext_mgr = plugins_base.rule_conditions_manager()
        for cond in self._conditions:
            scheme, path = _parse_path(cond.field)

            if scheme == 'node':
                source_data = node_info.node().to_dict()
            elif scheme == 'data':
                source_data = data

            field_values = jsonpath.parse(path).find(source_data)
            field_values = [x.value for x in field_values]
            cond_ext = ext_mgr[cond.op].obj

            if not field_values:
                if cond_ext.ALLOW_NONE:
                    LOG.debug('Field with JSON path %s was not found in data',
                              cond.field, node_info=node_info, data=data)
                    field_values = [None]
                else:
                    LOG.info('Field with JSON path %(path)s was not found '
                             'in data, rule "%(rule)s" will not '
                             'be applied',
                             {'path': cond.field, 'rule': self.description},
                             node_info=node_info, data=data)
                    return False

            for value in field_values:
                result = cond_ext.check(node_info, value, cond.params)
                if cond.invert:
                    result = not result

                if (cond.multiple == 'first'
                        or (cond.multiple == 'all' and not result)
                        or (cond.multiple == 'any' and result)):
                    break

            if not result:
                LOG.info('Rule "%(rule)s" will not be applied: condition '
                         '%(field)s %(op)s %(params)s failed',
                         {'rule': self.description, 'field': cond.field,
                          'op': cond.op, 'params': cond.params},
                         node_info=node_info, data=data)
                return False

        LOG.info('Rule "%s" will be applied', self.description,
                 node_info=node_info, data=data)
        return True
Esempio n. 3
0
    def check_conditions(self, node_info, data):
        """Check if conditions are true for a given node.

        :param node_info: a NodeInfo object
        :param data: introspection data
        :returns: True if conditions match, otherwise False
        """
        LOG.debug('Checking rule "%s"', self.description,
                  node_info=node_info, data=data)
        ext_mgr = plugins_base.rule_conditions_manager()
        for cond in self._conditions:
            scheme, path = _parse_path(cond.field)

            if scheme == 'node':
                source_data = node_info.node().to_dict()
            elif scheme == 'data':
                source_data = data

            field_values = jsonpath.parse(path).find(source_data)
            field_values = [x.value for x in field_values]
            cond_ext = ext_mgr[cond.op].obj

            if not field_values:
                if cond_ext.ALLOW_NONE:
                    LOG.debug('Field with JSON path %s was not found in data',
                              cond.field, node_info=node_info, data=data)
                    field_values = [None]
                else:
                    LOG.info('Field with JSON path %(path)s was not found '
                             'in data, rule "%(rule)s" will not '
                             'be applied',
                             {'path': cond.field, 'rule': self.description},
                             node_info=node_info, data=data)
                    return False

            for value in field_values:
                result = cond_ext.check(node_info, value, cond.params)
                if cond.invert:
                    result = not result

                if (cond.multiple == 'first'
                        or (cond.multiple == 'all' and not result)
                        or (cond.multiple == 'any' and result)):
                    break

            if not result:
                LOG.info('Rule "%(rule)s" will not be applied: condition '
                         '%(field)s %(op)s %(params)s failed',
                         {'rule': self.description, 'field': cond.field,
                          'op': cond.op, 'params': cond.params},
                         node_info=node_info, data=data)
                return False

        LOG.info('Rule "%s" will be applied', self.description,
                 node_info=node_info, data=data)
        return True
Esempio n. 4
0
def _validate_conditions(conditions_json):
    """Validates conditions from jsonschema.

    :returns: a list of conditions.
    """
    try:
        jsonschema.validate(conditions_json, conditions_schema())
    except jsonschema.ValidationError as exc:
        raise utils.Error(_('Validation failed for conditions: %s') % exc)

    cond_mgr = plugins_base.rule_conditions_manager()
    conditions = []
    reserved_params = {'op', 'field', 'multiple', 'invert'}
    for cond_json in conditions_json:
        field = cond_json['field']

        scheme, path = _parse_path(field)

        if scheme not in ('node', 'data'):
            raise utils.Error(
                _('Unsupported scheme for field: %s, valid '
                  'values are node:// or data://') % scheme)
        # verify field as JSON path
        try:
            jsonpath.parse(path)
        except Exception as exc:
            raise utils.Error(
                _('Unable to parse field JSON path %(field)s: '
                  '%(error)s') % {
                      'field': field,
                      'error': exc
                  })

        plugin = cond_mgr[cond_json['op']].obj
        params = {
            k: v
            for k, v in cond_json.items() if k not in reserved_params
        }
        try:
            plugin.validate(params)
        except ValueError as exc:
            raise utils.Error(
                _('Invalid parameters for operator %(op)s: '
                  '%(error)s') % {
                      'op': cond_json['op'],
                      'error': exc
                  })

        conditions.append(
            (cond_json['field'], cond_json['op'],
             cond_json.get('multiple', 'any'), cond_json.get('invert',
                                                             False), params))
    return conditions
Esempio n. 5
0
    def check_conditions(self, node_info, data):
        """Check if conditions are true for a given node.

        :param node_info: a NodeInfo object
        :param data: introspection data
        :returns: True if conditions match, otherwise False
        """
        LOG.debug('Checking rule "%(descr)s" on node %(uuid)s',
                  {'descr': self.description, 'uuid': node_info.uuid})
        ext_mgr = plugins_base.rule_conditions_manager()
        for cond in self._conditions:
            field_values = jsonpath.parse(cond.field).find(data)
            field_values = [x.value for x in field_values]
            cond_ext = ext_mgr[cond.op].obj

            if not field_values:
                if cond_ext.ALLOW_NONE:
                    LOG.debug('Field with JSON path %(path)s was not found in '
                              'data for node %(uuid)s',
                              {'path': cond.field, 'uuid': node_info.uuid})
                    field_values = [None]
                else:
                    LOG.info(_LI('Field with JSON path %(path)s was not found '
                                 'in data for node %(uuid)s, rule "%(rule)s" '
                                 'will not be applied'),
                             {'path': cond.field, 'uuid': node_info.uuid,
                              'rule': self.description})
                    return False

            for value in field_values:
                result = cond_ext.check(node_info, value, cond.params)
                if (cond.multiple == 'first'
                        or (cond.multiple == 'all' and not result)
                        or (cond.multiple == 'any' and result)):
                    break

            if not result:
                LOG.info(_LI('Rule "%(rule)s" will not be applied to node '
                             '%(uuid)s: condition %(field)s %(op)s %(params)s '
                             'failed'),
                         {'rule': self.description, 'uuid': node_info.uuid,
                          'field': cond.field, 'op': cond.op,
                          'params': cond.params})
                return False

        LOG.info(_LI('Rule "%(rule)s" will be applied to node %(uuid)s'),
                 {'rule': self.description, 'uuid': node_info.uuid})
        return True
Esempio n. 6
0
def _validate_conditions(conditions_json):
    """Validates conditions from jsonschema.

    :returns: a list of conditions.
    """
    try:
        jsonschema.validate(conditions_json, conditions_schema())
    except jsonschema.ValidationError as exc:
        raise utils.Error(_('Validation failed for conditions: %s') % exc)

    cond_mgr = plugins_base.rule_conditions_manager()
    conditions = []
    reserved_params = {'op', 'field', 'multiple', 'invert'}
    for cond_json in conditions_json:
        field = cond_json['field']

        scheme, path = _parse_path(field)

        if scheme not in ('node', 'data'):
            raise utils.Error(_('Unsupported scheme for field: %s, valid '
                                'values are node:// or data://') % scheme)
        # verify field as JSON path
        try:
            jsonpath.parse(path)
        except Exception as exc:
            raise utils.Error(_('Unable to parse field JSON path %(field)s: '
                                '%(error)s') % {'field': field, 'error': exc})

        plugin = cond_mgr[cond_json['op']].obj
        params = {k: v for k, v in cond_json.items()
                  if k not in reserved_params}
        try:
            plugin.validate(params)
        except ValueError as exc:
            raise utils.Error(_('Invalid parameters for operator %(op)s: '
                                '%(error)s') %
                              {'op': cond_json['op'], 'error': exc})

        conditions.append((cond_json['field'],
                           cond_json['op'],
                           cond_json.get('multiple', 'any'),
                           cond_json.get('invert', False),
                           params))
    return conditions
Esempio n. 7
0
def create(conditions_json, actions_json, uuid=None,
           description=None):
    """Create a new rule in database.

    :param conditions_json: list of dicts with the following keys:
                            * op - operator
                            * field - JSON path to field to compare
                            Other keys are stored as is.
    :param actions_json: list of dicts with the following keys:
                         * action - action type
                         Other keys are stored as is.
    :param uuid: rule UUID, will be generated if empty
    :param description: human-readable rule description
    :returns: new IntrospectionRule object
    :raises: utils.Error on failure
    """
    uuid = uuid or uuidutils.generate_uuid()
    LOG.debug('Creating rule %(uuid)s with description "%(descr)s", '
              'conditions %(conditions)s and actions %(actions)s',
              {'uuid': uuid, 'descr': description,
               'conditions': conditions_json, 'actions': actions_json})

    try:
        jsonschema.validate(conditions_json, conditions_schema())
    except jsonschema.ValidationError as exc:
        raise utils.Error(_('Validation failed for conditions: %s') % exc)

    try:
        jsonschema.validate(actions_json, actions_schema())
    except jsonschema.ValidationError as exc:
        raise utils.Error(_('Validation failed for actions: %s') % exc)

    cond_mgr = plugins_base.rule_conditions_manager()
    act_mgr = plugins_base.rule_actions_manager()

    conditions = []
    for cond_json in conditions_json:
        field = cond_json['field']
        try:
            jsonpath.parse(field)
        except Exception as exc:
            raise utils.Error(_('Unable to parse field JSON path %(field)s: '
                                '%(error)s') % {'field': field, 'error': exc})

        plugin = cond_mgr[cond_json['op']].obj
        params = {k: v for k, v in cond_json.items()
                  if k not in ('op', 'field', 'multiple')}
        try:
            plugin.validate(params)
        except ValueError as exc:
            raise utils.Error(_('Invalid parameters for operator %(op)s: '
                                '%(error)s') %
                              {'op': cond_json['op'], 'error': exc})

        conditions.append((cond_json['field'], cond_json['op'],
                           cond_json.get('multiple', 'any'), params))

    actions = []
    for action_json in actions_json:
        plugin = act_mgr[action_json['action']].obj
        params = {k: v for k, v in action_json.items() if k != 'action'}
        try:
            plugin.validate(params)
        except ValueError as exc:
            raise utils.Error(_('Invalid parameters for action %(act)s: '
                                '%(error)s') %
                              {'act': action_json['action'], 'error': exc})

        actions.append((action_json['action'], params))

    try:
        with db.ensure_transaction() as session:
            rule = db.Rule(uuid=uuid, description=description,
                           disabled=False, created_at=timeutils.utcnow())

            for field, op, multiple, params in conditions:
                rule.conditions.append(db.RuleCondition(op=op, field=field,
                                                        multiple=multiple,
                                                        params=params))

            for action, params in actions:
                rule.actions.append(db.RuleAction(action=action,
                                                  params=params))

            rule.save(session)
    except db_exc.DBDuplicateEntry as exc:
        LOG.error(_LE('Database integrity error %s when '
                      'creating a rule'), exc)
        raise utils.Error(_('Rule with UUID %s already exists') % uuid,
                          code=409)

    LOG.info(_LI('Created rule %(uuid)s with description "%(descr)s"'),
             {'uuid': uuid, 'descr': description})
    return IntrospectionRule(uuid=uuid,
                             conditions=rule.conditions,
                             actions=rule.actions,
                             description=description)