Added scope to introspection rules.
Added 'scope' property to IntrospectionRule and logic to check if a node falls in the same scope.This allows introspection rules to be applied on selected nodes instead of every one of them. Story: 2006995 Task: 37763 Change-Id: I77034f032ea0ec16886afdd928546eb801f7a90a
This commit is contained in:
parent
e6ece052ec
commit
e2c8f9fd7b
|
@ -33,6 +33,7 @@ Request
|
||||||
- conditions: conditions
|
- conditions: conditions
|
||||||
- actions: actions
|
- actions: actions
|
||||||
- description: description
|
- description: description
|
||||||
|
- scope: scope
|
||||||
|
|
||||||
|
|
||||||
**Example creating rule request:**
|
**Example creating rule request:**
|
||||||
|
@ -54,6 +55,7 @@ section may contain additional default fields, like ``invert``,
|
||||||
- conditions: conditions
|
- conditions: conditions
|
||||||
- actions: actions
|
- actions: actions
|
||||||
- description: description
|
- description: description
|
||||||
|
- scope: scope
|
||||||
|
|
||||||
**Example JSON representation:**
|
**Example JSON representation:**
|
||||||
|
|
||||||
|
@ -77,6 +79,7 @@ Response
|
||||||
|
|
||||||
- uuid: uuid
|
- uuid: uuid
|
||||||
- description: description
|
- description: description
|
||||||
|
- scope: scope
|
||||||
- links: links
|
- links: links
|
||||||
|
|
||||||
**Example JSON representation:**
|
**Example JSON representation:**
|
||||||
|
@ -117,6 +120,7 @@ The response will contain full rule object:
|
||||||
- conditions: conditions
|
- conditions: conditions
|
||||||
- actions: actions
|
- actions: actions
|
||||||
- description: description
|
- description: description
|
||||||
|
- scope: scope
|
||||||
|
|
||||||
**Example JSON representation:**
|
**Example JSON representation:**
|
||||||
|
|
||||||
|
|
|
@ -214,6 +214,13 @@ root_disk:
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
scope:
|
||||||
|
description: |
|
||||||
|
Scope of an introspection rule. If set, the rule is only applied to nodes
|
||||||
|
that have matching ``inspection_scope`` property.
|
||||||
|
in: body
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
started_at:
|
started_at:
|
||||||
description: |
|
description: |
|
||||||
UTC ISO8601 timestamp of introspection start.
|
UTC ISO8601 timestamp of introspection start.
|
||||||
|
|
|
@ -22,5 +22,6 @@
|
||||||
"op":"is-empty",
|
"op":"is-empty",
|
||||||
"field":"node://driver_info.deploy_kernel"
|
"field":"node://driver_info.deploy_kernel"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"scope":"Delivery_1"
|
||||||
}
|
}
|
|
@ -32,5 +32,6 @@
|
||||||
"rel": "self"
|
"rel": "self"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"uuid": "7459bf7c-9ff9-43a8-ba9f-48542ecda66c"
|
"uuid": "7459bf7c-9ff9-43a8-ba9f-48542ecda66c",
|
||||||
|
"scope": ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -402,3 +402,4 @@ Version History
|
||||||
* **1.14** allows formatting to be applied to strings nested in dicts and lists
|
* **1.14** allows formatting to be applied to strings nested in dicts and lists
|
||||||
in the actions of introspection rules.
|
in the actions of introspection rules.
|
||||||
* **1.15** allows reapply with provided introspection data from request.
|
* **1.15** allows reapply with provided introspection data from request.
|
||||||
|
* **1.16** adds ``scope`` field to introspection rule.
|
||||||
|
|
|
@ -103,6 +103,32 @@ results (e.g. the field contains a list), available options are:
|
||||||
All other fields are passed to the condition plugin, e.g. numeric comparison
|
All other fields are passed to the condition plugin, e.g. numeric comparison
|
||||||
operations require a ``value`` field to compare against.
|
operations require a ``value`` field to compare against.
|
||||||
|
|
||||||
|
Scope
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
By default, introspection rules are applied to all nodes being inspected.
|
||||||
|
In order for the rule to be applied only to specific nodes, a matching scope
|
||||||
|
variable must be set to both the rule and the node. To set the scope for a
|
||||||
|
rule include field ``"scope"`` in JSON file before importing. For example::
|
||||||
|
|
||||||
|
cat <json file>
|
||||||
|
{
|
||||||
|
"description": "...",
|
||||||
|
"actions": [...],
|
||||||
|
"conditions": [...],
|
||||||
|
"scope": "SCOPE"
|
||||||
|
}
|
||||||
|
|
||||||
|
Set the property ``inspection_scope`` on the node you want the rule to be
|
||||||
|
applied to::
|
||||||
|
|
||||||
|
openstack baremetal node set --property inspection_scope="SCOPE" <node>
|
||||||
|
|
||||||
|
Now, when inspecting, the rule will be applied only to nodes with matching
|
||||||
|
scope value. It will also ignore nodes that do not have ``inspection_scope``
|
||||||
|
property set. Note that if a rule has no scope set, it will be applied to all
|
||||||
|
nodes, regardless if they have ``inspection_scope`` set or not.
|
||||||
|
|
||||||
Actions
|
Actions
|
||||||
^^^^^^^
|
^^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,7 @@ class Rule(Base):
|
||||||
description = Column(Text)
|
description = Column(Text)
|
||||||
# NOTE(dtantsur): in the future we might need to temporary disable a rule
|
# NOTE(dtantsur): in the future we might need to temporary disable a rule
|
||||||
disabled = Column(Boolean, default=False)
|
disabled = Column(Boolean, default=False)
|
||||||
|
scope = Column(String(255), nullable=True)
|
||||||
|
|
||||||
conditions = orm.relationship('RuleCondition', lazy='joined',
|
conditions = orm.relationship('RuleCondition', lazy='joined',
|
||||||
order_by='RuleCondition.id',
|
order_by='RuleCondition.id',
|
||||||
|
|
|
@ -41,7 +41,7 @@ _app = flask.Flask(__name__)
|
||||||
LOG = utils.getProcessingLogger(__name__)
|
LOG = utils.getProcessingLogger(__name__)
|
||||||
|
|
||||||
MINIMUM_API_VERSION = (1, 0)
|
MINIMUM_API_VERSION = (1, 0)
|
||||||
CURRENT_API_VERSION = (1, 15)
|
CURRENT_API_VERSION = (1, 16)
|
||||||
DEFAULT_API_VERSION = CURRENT_API_VERSION
|
DEFAULT_API_VERSION = CURRENT_API_VERSION
|
||||||
_LOGGING_EXCLUDED_KEYS = ('logs',)
|
_LOGGING_EXCLUDED_KEYS = ('logs',)
|
||||||
|
|
||||||
|
@ -413,11 +413,16 @@ def api_rules():
|
||||||
body = flask.request.get_json(force=True)
|
body = flask.request.get_json(force=True)
|
||||||
if body.get('uuid') and not uuidutils.is_uuid_like(body['uuid']):
|
if body.get('uuid') and not uuidutils.is_uuid_like(body['uuid']):
|
||||||
raise utils.Error(_('Invalid UUID value'), code=400)
|
raise utils.Error(_('Invalid UUID value'), code=400)
|
||||||
|
if body.get('scope') and len(body.get('scope')) > 255:
|
||||||
|
raise utils.Error(
|
||||||
|
_("Invalid scope: the length of the scope should be within "
|
||||||
|
"255 characters"), code=400)
|
||||||
|
|
||||||
rule = rules.create(conditions_json=body.get('conditions', []),
|
rule = rules.create(conditions_json=body.get('conditions', []),
|
||||||
actions_json=body.get('actions', []),
|
actions_json=body.get('actions', []),
|
||||||
uuid=body.get('uuid'),
|
uuid=body.get('uuid'),
|
||||||
description=body.get('description'))
|
description=body.get('description'),
|
||||||
|
scope=body.get('scope'))
|
||||||
|
|
||||||
response_code = (200 if _get_version() < (1, 6) else 201)
|
response_code = (200 if _get_version() < (1, 6) else 201)
|
||||||
return flask.make_response(
|
return flask.make_response(
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""Added 'scope' column to 'Rules' table
|
||||||
|
|
||||||
|
Revision ID: b55109d5063a
|
||||||
|
Revises: bf8dec16023c
|
||||||
|
Create Date: 2019-12-11 14:15:57.510289
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b55109d5063a'
|
||||||
|
down_revision = 'bf8dec16023c'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('rules', sa.Column('scope', sa.String(255),
|
||||||
|
nullable=True, default=None))
|
|
@ -101,17 +101,19 @@ def actions_schema():
|
||||||
class IntrospectionRule(object):
|
class IntrospectionRule(object):
|
||||||
"""High-level class representing an introspection rule."""
|
"""High-level class representing an introspection rule."""
|
||||||
|
|
||||||
def __init__(self, uuid, conditions, actions, description):
|
def __init__(self, uuid, conditions, actions, description, scope=None):
|
||||||
"""Create rule object from database data."""
|
"""Create rule object from database data."""
|
||||||
self._uuid = uuid
|
self._uuid = uuid
|
||||||
self._conditions = conditions
|
self._conditions = conditions
|
||||||
self._actions = actions
|
self._actions = actions
|
||||||
self._description = description
|
self._description = description
|
||||||
|
self._scope = scope
|
||||||
|
|
||||||
def as_dict(self, short=False):
|
def as_dict(self, short=False):
|
||||||
result = {
|
result = {
|
||||||
'uuid': self._uuid,
|
'uuid': self._uuid,
|
||||||
'description': self._description,
|
'description': self._description,
|
||||||
|
'scope': self._scope
|
||||||
}
|
}
|
||||||
|
|
||||||
if not short:
|
if not short:
|
||||||
|
@ -211,6 +213,17 @@ class IntrospectionRule(object):
|
||||||
LOG.debug('Successfully applied actions',
|
LOG.debug('Successfully applied actions',
|
||||||
node_info=node_info, data=data)
|
node_info=node_info, data=data)
|
||||||
|
|
||||||
|
def check_scope(self, node_info):
|
||||||
|
"""Check if node's scope falls under rule._scope and rule is applicable
|
||||||
|
|
||||||
|
:param node_info: a NodeInfo object
|
||||||
|
:returns: True if conditions match, otherwise False
|
||||||
|
"""
|
||||||
|
if not self._scope:
|
||||||
|
return True
|
||||||
|
return self._scope == \
|
||||||
|
node_info.node().properties.get('inspection_scope')
|
||||||
|
|
||||||
|
|
||||||
def _format_value(value, data):
|
def _format_value(value, data):
|
||||||
"""Apply parameter formatting to a value.
|
"""Apply parameter formatting to a value.
|
||||||
|
@ -338,7 +351,7 @@ def _validate_actions(actions_json):
|
||||||
|
|
||||||
|
|
||||||
def create(conditions_json, actions_json, uuid=None,
|
def create(conditions_json, actions_json, uuid=None,
|
||||||
description=None):
|
description=None, scope=None):
|
||||||
"""Create a new rule in database.
|
"""Create a new rule in database.
|
||||||
|
|
||||||
:param conditions_json: list of dicts with the following keys:
|
:param conditions_json: list of dicts with the following keys:
|
||||||
|
@ -350,13 +363,16 @@ def create(conditions_json, actions_json, uuid=None,
|
||||||
Other keys are stored as is.
|
Other keys are stored as is.
|
||||||
:param uuid: rule UUID, will be generated if empty
|
:param uuid: rule UUID, will be generated if empty
|
||||||
:param description: human-readable rule description
|
:param description: human-readable rule description
|
||||||
|
:param scope: if scope on node and rule matches, rule applies;
|
||||||
|
if its empty, rule applies to all nodes.
|
||||||
:returns: new IntrospectionRule object
|
:returns: new IntrospectionRule object
|
||||||
:raises: utils.Error on failure
|
:raises: utils.Error on failure
|
||||||
"""
|
"""
|
||||||
uuid = uuid or uuidutils.generate_uuid()
|
uuid = uuid or uuidutils.generate_uuid()
|
||||||
LOG.debug('Creating rule %(uuid)s with description "%(descr)s", '
|
LOG.debug('Creating rule %(uuid)s with description "%(descr)s", '
|
||||||
'conditions %(conditions)s and actions %(actions)s',
|
'conditions %(conditions)s, scope "%(scope)s"'
|
||||||
{'uuid': uuid, 'descr': description,
|
' and actions %(actions)s',
|
||||||
|
{'uuid': uuid, 'descr': description, 'scope': scope,
|
||||||
'conditions': conditions_json, 'actions': actions_json})
|
'conditions': conditions_json, 'actions': actions_json})
|
||||||
|
|
||||||
conditions = _validate_conditions(conditions_json)
|
conditions = _validate_conditions(conditions_json)
|
||||||
|
@ -364,8 +380,8 @@ def create(conditions_json, actions_json, uuid=None,
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with db.ensure_transaction() as session:
|
with db.ensure_transaction() as session:
|
||||||
rule = db.Rule(uuid=uuid, description=description,
|
rule = db.Rule(uuid=uuid, description=description, disabled=False,
|
||||||
disabled=False, created_at=timeutils.utcnow())
|
created_at=timeutils.utcnow(), scope=scope)
|
||||||
|
|
||||||
for field, op, multiple, invert, params in conditions:
|
for field, op, multiple, invert, params in conditions:
|
||||||
rule.conditions.append(db.RuleCondition(op=op,
|
rule.conditions.append(db.RuleCondition(op=op,
|
||||||
|
@ -385,12 +401,14 @@ def create(conditions_json, actions_json, uuid=None,
|
||||||
raise utils.Error(_('Rule with UUID %s already exists') % uuid,
|
raise utils.Error(_('Rule with UUID %s already exists') % uuid,
|
||||||
code=409)
|
code=409)
|
||||||
|
|
||||||
LOG.info('Created rule %(uuid)s with description "%(descr)s"',
|
LOG.info('Created rule %(uuid)s with description "%(descr)s" '
|
||||||
{'uuid': uuid, 'descr': description})
|
'and scope "%(scope)s"',
|
||||||
|
{'uuid': uuid, 'descr': description, 'scope': scope})
|
||||||
return IntrospectionRule(uuid=uuid,
|
return IntrospectionRule(uuid=uuid,
|
||||||
conditions=rule.conditions,
|
conditions=rule.conditions,
|
||||||
actions=rule.actions,
|
actions=rule.actions,
|
||||||
description=description)
|
description=description,
|
||||||
|
scope=rule.scope)
|
||||||
|
|
||||||
|
|
||||||
def get(uuid):
|
def get(uuid):
|
||||||
|
@ -402,7 +420,8 @@ def get(uuid):
|
||||||
|
|
||||||
return IntrospectionRule(uuid=rule.uuid, actions=rule.actions,
|
return IntrospectionRule(uuid=rule.uuid, actions=rule.actions,
|
||||||
conditions=rule.conditions,
|
conditions=rule.conditions,
|
||||||
description=rule.description)
|
description=rule.description,
|
||||||
|
scope=rule.scope)
|
||||||
|
|
||||||
|
|
||||||
def get_all():
|
def get_all():
|
||||||
|
@ -410,7 +429,8 @@ def get_all():
|
||||||
query = db.model_query(db.Rule).order_by(db.Rule.created_at)
|
query = db.model_query(db.Rule).order_by(db.Rule.created_at)
|
||||||
return [IntrospectionRule(uuid=rule.uuid, actions=rule.actions,
|
return [IntrospectionRule(uuid=rule.uuid, actions=rule.actions,
|
||||||
conditions=rule.conditions,
|
conditions=rule.conditions,
|
||||||
description=rule.description)
|
description=rule.description,
|
||||||
|
scope=rule.scope)
|
||||||
for rule in query]
|
for rule in query]
|
||||||
|
|
||||||
|
|
||||||
|
@ -452,7 +472,8 @@ def apply(node_info, data):
|
||||||
|
|
||||||
to_apply = []
|
to_apply = []
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
if rule.check_conditions(node_info, data):
|
if (rule.check_scope(node_info) and
|
||||||
|
rule.check_conditions(node_info, data)):
|
||||||
to_apply.append(rule)
|
to_apply.append(rule)
|
||||||
|
|
||||||
if to_apply:
|
if to_apply:
|
||||||
|
|
|
@ -393,7 +393,8 @@ class Test(Base):
|
||||||
{'op': 'eq', 'field': 'memory_mb', 'value': 1024},
|
{'op': 'eq', 'field': 'memory_mb', 'value': 1024},
|
||||||
],
|
],
|
||||||
'actions': [{'action': 'fail', 'message': 'boom'}],
|
'actions': [{'action': 'fail', 'message': 'boom'}],
|
||||||
'description': 'Cool actions'
|
'description': 'Cool actions',
|
||||||
|
'scope': "sniper's scope"
|
||||||
}
|
}
|
||||||
|
|
||||||
res = self.call_add_rule(rule)
|
res = self.call_add_rule(rule)
|
||||||
|
@ -411,7 +412,8 @@ class Test(Base):
|
||||||
res = self.call_list_rules()
|
res = self.call_list_rules()
|
||||||
self.assertEqual(rule['links'], res[0].pop('links'))
|
self.assertEqual(rule['links'], res[0].pop('links'))
|
||||||
self.assertEqual([{'uuid': rule['uuid'],
|
self.assertEqual([{'uuid': rule['uuid'],
|
||||||
'description': 'Cool actions'}],
|
'description': rule['description'],
|
||||||
|
'scope': rule['scope']}],
|
||||||
res)
|
res)
|
||||||
|
|
||||||
res = self.call_get_rule(rule['uuid'])
|
res = self.call_get_rule(rule['uuid'])
|
||||||
|
@ -466,7 +468,7 @@ class Test(Base):
|
||||||
'value': 'foo'},
|
'value': 'foo'},
|
||||||
{'action': 'fail', 'message': 'boom'}
|
{'action': 'fail', 'message': 'boom'}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
self.call_add_rule(rule)
|
self.call_add_rule(rule)
|
||||||
|
|
|
@ -465,7 +465,8 @@ class TestApiRules(BaseAPITest):
|
||||||
create_mock.assert_called_once_with(conditions_json='cond',
|
create_mock.assert_called_once_with(conditions_json='cond',
|
||||||
actions_json='act',
|
actions_json='act',
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
description=None)
|
description=None,
|
||||||
|
scope=None)
|
||||||
self.assertEqual(exp, json.loads(res.data.decode('utf-8')))
|
self.assertEqual(exp, json.loads(res.data.decode('utf-8')))
|
||||||
|
|
||||||
@mock.patch.object(rules, 'create', autospec=True)
|
@mock.patch.object(rules, 'create', autospec=True)
|
||||||
|
@ -487,7 +488,8 @@ class TestApiRules(BaseAPITest):
|
||||||
create_mock.assert_called_once_with(conditions_json='cond',
|
create_mock.assert_called_once_with(conditions_json='cond',
|
||||||
actions_json='act',
|
actions_json='act',
|
||||||
uuid=self.uuid,
|
uuid=self.uuid,
|
||||||
description=None)
|
description=None,
|
||||||
|
scope=None)
|
||||||
self.assertEqual(exp, json.loads(res.data.decode('utf-8')))
|
self.assertEqual(exp, json.loads(res.data.decode('utf-8')))
|
||||||
|
|
||||||
@mock.patch.object(rules, 'create', autospec=True)
|
@mock.patch.object(rules, 'create', autospec=True)
|
||||||
|
|
|
@ -40,6 +40,8 @@ class BaseTest(test_base.NodeTest):
|
||||||
'local_gb': 42,
|
'local_gb': 42,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.scope = "inner circle"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def condition_defaults(condition):
|
def condition_defaults(condition):
|
||||||
condition = condition.copy()
|
condition = condition.copy()
|
||||||
|
@ -56,7 +58,8 @@ class TestCreateRule(BaseTest):
|
||||||
self.assertTrue(rule_json.pop('uuid'))
|
self.assertTrue(rule_json.pop('uuid'))
|
||||||
self.assertEqual({'description': None,
|
self.assertEqual({'description': None,
|
||||||
'conditions': [],
|
'conditions': [],
|
||||||
'actions': self.actions_json},
|
'actions': self.actions_json,
|
||||||
|
'scope': None},
|
||||||
rule_json)
|
rule_json)
|
||||||
|
|
||||||
def test_create_action_none_value(self):
|
def test_create_action_none_value(self):
|
||||||
|
@ -68,7 +71,8 @@ class TestCreateRule(BaseTest):
|
||||||
self.assertTrue(rule_json.pop('uuid'))
|
self.assertTrue(rule_json.pop('uuid'))
|
||||||
self.assertEqual({'description': None,
|
self.assertEqual({'description': None,
|
||||||
'conditions': [],
|
'conditions': [],
|
||||||
'actions': self.actions_json},
|
'actions': self.actions_json,
|
||||||
|
'scope': None},
|
||||||
rule_json)
|
rule_json)
|
||||||
|
|
||||||
def test_duplicate_uuid(self):
|
def test_duplicate_uuid(self):
|
||||||
|
@ -94,7 +98,8 @@ class TestCreateRule(BaseTest):
|
||||||
self.assertEqual({'description': None,
|
self.assertEqual({'description': None,
|
||||||
'conditions': [BaseTest.condition_defaults(cond)
|
'conditions': [BaseTest.condition_defaults(cond)
|
||||||
for cond in self.conditions_json],
|
for cond in self.conditions_json],
|
||||||
'actions': self.actions_json},
|
'actions': self.actions_json,
|
||||||
|
'scope': None},
|
||||||
rule_json)
|
rule_json)
|
||||||
|
|
||||||
def test_invalid_condition(self):
|
def test_invalid_condition(self):
|
||||||
|
@ -157,6 +162,17 @@ class TestCreateRule(BaseTest):
|
||||||
rules.create,
|
rules.create,
|
||||||
self.conditions_json, self.actions_json)
|
self.conditions_json, self.actions_json)
|
||||||
|
|
||||||
|
def test_scope(self):
|
||||||
|
rule = rules.create([], self.actions_json, scope=self.scope)
|
||||||
|
rule_json = rule.as_dict()
|
||||||
|
|
||||||
|
self.assertTrue(rule_json.pop('uuid'))
|
||||||
|
self.assertEqual({'description': None,
|
||||||
|
'conditions': [],
|
||||||
|
'actions': self.actions_json,
|
||||||
|
'scope': self.scope},
|
||||||
|
rule_json)
|
||||||
|
|
||||||
|
|
||||||
class TestGetRule(BaseTest):
|
class TestGetRule(BaseTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -170,7 +186,8 @@ class TestGetRule(BaseTest):
|
||||||
self.assertEqual({'description': None,
|
self.assertEqual({'description': None,
|
||||||
'conditions': [BaseTest.condition_defaults(cond)
|
'conditions': [BaseTest.condition_defaults(cond)
|
||||||
for cond in self.conditions_json],
|
for cond in self.conditions_json],
|
||||||
'actions': self.actions_json},
|
'actions': self.actions_json,
|
||||||
|
'scope': None},
|
||||||
rule_json)
|
rule_json)
|
||||||
|
|
||||||
def test_not_found(self):
|
def test_not_found(self):
|
||||||
|
@ -558,3 +575,61 @@ class TestApply(BaseTest):
|
||||||
self.node_info, data=self.data)
|
self.node_info, data=self.data)
|
||||||
else:
|
else:
|
||||||
self.assertFalse(rule.apply_actions.called)
|
self.assertFalse(rule.apply_actions.called)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(rules, 'get_all', autospec=True)
|
||||||
|
class TestRuleScope(BaseTest):
|
||||||
|
"""Test that rules are only applied on the nodes that fall in their scope.
|
||||||
|
|
||||||
|
Check that:
|
||||||
|
- global rule is applied to all nodes
|
||||||
|
- different rules with scopes are applied to different nodes
|
||||||
|
- rule without matching scope is not applied
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestRuleScope, self).setUp()
|
||||||
|
|
||||||
|
"""
|
||||||
|
rule_global
|
||||||
|
rule_scope_1
|
||||||
|
rule_scope_2
|
||||||
|
rule_out_scope
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.rules = [rules.IntrospectionRule("", "", "", "", None),
|
||||||
|
rules.IntrospectionRule("", "", "", "", "scope_1"),
|
||||||
|
rules.IntrospectionRule("", "", "", "", "scope_2"),
|
||||||
|
rules.IntrospectionRule("", "", "", "", "scope_3")]
|
||||||
|
for r in self.rules:
|
||||||
|
r.check_conditions = mock.Mock()
|
||||||
|
r.check_conditions.return_value = True
|
||||||
|
r.apply_actions = mock.Mock()
|
||||||
|
r.apply_actions.return_value = True
|
||||||
|
|
||||||
|
def test_node_no_scope(self, mock_get_all):
|
||||||
|
mock_get_all.return_value = self.rules
|
||||||
|
self.node_info.node().properties['inspection_scope'] = None
|
||||||
|
rules.apply(self.node_info, self.data)
|
||||||
|
self.rules[0].apply_actions.assert_called_once() # global
|
||||||
|
self.rules[1].apply_actions.assert_not_called() # scope_1
|
||||||
|
self.rules[2].apply_actions.assert_not_called() # scope_2
|
||||||
|
self.rules[3].apply_actions.assert_not_called() # scope_3
|
||||||
|
|
||||||
|
def test_node_scope_1(self, mock_get_all):
|
||||||
|
mock_get_all.return_value = self.rules
|
||||||
|
self.node_info.node().properties['inspection_scope'] = "scope_1"
|
||||||
|
rules.apply(self.node_info, self.data)
|
||||||
|
self.rules[0].apply_actions.assert_called_once() # global
|
||||||
|
self.rules[1].apply_actions.assert_called_once() # scope_1
|
||||||
|
self.rules[2].apply_actions.assert_not_called() # scope_2
|
||||||
|
self.rules[3].apply_actions.assert_not_called() # scope_3
|
||||||
|
|
||||||
|
def test_node_scope_2(self, mock_get_all):
|
||||||
|
mock_get_all.return_value = self.rules
|
||||||
|
self.node_info.node().properties['inspection_scope'] = "scope_2"
|
||||||
|
rules.apply(self.node_info, self.data)
|
||||||
|
self.rules[0].apply_actions.assert_called_once() # global
|
||||||
|
self.rules[1].apply_actions.assert_not_called() # scope_1
|
||||||
|
self.rules[2].apply_actions.assert_called_once() # scope_2
|
||||||
|
self.rules[3].apply_actions.assert_not_called() # scope_3
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added the capability to define a scope for the inspection process.
|
||||||
|
Previously, all introspection rules were applied when inspecting
|
||||||
|
any node. There was no mechanism to apply only a selected set of
|
||||||
|
rules. This change introduces a ``scope`` field to introspection rules.
|
||||||
|
If a scope is set on an introspection rule, it will only apply to nodes
|
||||||
|
that have a matching ``inspection_scope`` property. If not set, it will
|
||||||
|
apply to all nodes.
|
Loading…
Reference in New Issue