Merge "Automatically load library policy files at start"
This commit is contained in:
commit
8509657bb9
@ -185,7 +185,16 @@ Configure Congress (Assume you put config files in /etc/congress)
|
|||||||
$ sudo cp etc/api-paste.ini /etc/congress
|
$ sudo cp etc/api-paste.ini /etc/congress
|
||||||
$ sudo cp etc/policy.json /etc/congress
|
$ sudo cp etc/policy.json /etc/congress
|
||||||
|
|
||||||
|
Set-up Policy Library [optional]
|
||||||
|
This step copies the bundled collection Congress policies into the Congress
|
||||||
|
policy library for easy activation by an administrator. The policies in the
|
||||||
|
library do not become active until explicitly activated by an administrator.
|
||||||
|
The step may be skipped if you do not want to load the bundled policies into
|
||||||
|
the policy library.
|
||||||
|
|
||||||
|
.. code-block:: console
|
||||||
|
|
||||||
|
$ sudo cp -r library /etc/congress/.
|
||||||
|
|
||||||
Generate a configuration file as outlined in the Configuration Options section
|
Generate a configuration file as outlined in the Configuration Options section
|
||||||
of the :ref:`Deployment <deployment>` document. Note: you may have to run the command with sudo.
|
of the :ref:`Deployment <deployment>` document. Note: you may have to run the command with sudo.
|
||||||
|
@ -17,9 +17,6 @@ from __future__ import print_function
|
|||||||
from __future__ import division
|
from __future__ import division
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import json
|
|
||||||
import jsonschema
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from congress.api import base
|
from congress.api import base
|
||||||
@ -97,7 +94,6 @@ class LibraryPolicyModel(base.APIModel):
|
|||||||
(num, desc) = error_codes.get('policy_id_must_not_be_provided')
|
(num, desc) = error_codes.get('policy_id_must_not_be_provided')
|
||||||
raise webservice.DataModelException(num, desc)
|
raise webservice.DataModelException(num, desc)
|
||||||
|
|
||||||
self._validate_policy_item(item)
|
|
||||||
try:
|
try:
|
||||||
# Note(thread-safety): blocking call
|
# Note(thread-safety): blocking call
|
||||||
policy_metadata = self.invoke_rpc(
|
policy_metadata = self.invoke_rpc(
|
||||||
@ -144,8 +140,6 @@ class LibraryPolicyModel(base.APIModel):
|
|||||||
Raises:
|
Raises:
|
||||||
KeyError: Item with specified id_ not present.
|
KeyError: Item with specified id_ not present.
|
||||||
"""
|
"""
|
||||||
self._validate_policy_item(item)
|
|
||||||
|
|
||||||
# Note(thread-safety): blocking call
|
# Note(thread-safety): blocking call
|
||||||
try:
|
try:
|
||||||
return self.invoke_rpc(base.LIBRARY_SERVICE_ID,
|
return self.invoke_rpc(base.LIBRARY_SERVICE_ID,
|
||||||
@ -154,70 +148,3 @@ class LibraryPolicyModel(base.APIModel):
|
|||||||
'policy_dict': item})
|
'policy_dict': item})
|
||||||
except exception.CongressException as e:
|
except exception.CongressException as e:
|
||||||
raise webservice.DataModelException.create(e)
|
raise webservice.DataModelException.create(e)
|
||||||
|
|
||||||
def _validate_policy_item(self, item):
|
|
||||||
schema_json = '''
|
|
||||||
{
|
|
||||||
"id": "PolicyProperties",
|
|
||||||
"title": "Policy Properties",
|
|
||||||
"type": "object",
|
|
||||||
"required": ["name", "rules"],
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"title": "Policy unique name",
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1,
|
|
||||||
"maxLength": 255
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"title": "Policy description",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"kind": {
|
|
||||||
"title": "Policy kind",
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["database", "nonrecursive", "action", "materialized",
|
|
||||||
"delta", "datasource"]
|
|
||||||
},
|
|
||||||
"abbreviation": {
|
|
||||||
"title": "Policy name abbreviation",
|
|
||||||
"type": "string",
|
|
||||||
"minLength": 1,
|
|
||||||
"maxLength": 5
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"title": "collection of rules",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"PolicyRule": {
|
|
||||||
"title": "Policy rule",
|
|
||||||
"type": "object",
|
|
||||||
"required": ["rule"],
|
|
||||||
"properties": {
|
|
||||||
"rule": {
|
|
||||||
"title": "Rule definition following policy grammar",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"title": "User-friendly name",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"comment": {
|
|
||||||
"title": "User-friendly comment",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
try:
|
|
||||||
jsonschema.validate(item, json.loads(schema_json))
|
|
||||||
except jsonschema.exceptions.ValidationError as ve:
|
|
||||||
raise webservice.DataModelException(
|
|
||||||
1000, 'Input item violates JSON Schema', data=str(ve))
|
|
||||||
|
@ -75,6 +75,8 @@ core_opts = [
|
|||||||
cfg.BoolOpt('replicated_policy_engine', default=False,
|
cfg.BoolOpt('replicated_policy_engine', default=False,
|
||||||
help='Set the flag to use congress with replicated policy '
|
help='Set the flag to use congress with replicated policy '
|
||||||
'engines.'),
|
'engines.'),
|
||||||
|
cfg.StrOpt('policy_library_path', default='/etc/congress/library',
|
||||||
|
help=_('The directory containing library policy files.')),
|
||||||
cfg.BoolOpt('distributed_architecture',
|
cfg.BoolOpt('distributed_architecture',
|
||||||
deprecated_for_removal=True,
|
deprecated_for_removal=True,
|
||||||
deprecated_reason='distributed architecture is now the only '
|
deprecated_reason='distributed architecture is now the only '
|
||||||
|
@ -18,6 +18,7 @@ from __future__ import absolute_import
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from oslo_db import exception as oslo_db_exc
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from sqlalchemy.orm import exc as db_exc
|
from sqlalchemy.orm import exc as db_exc
|
||||||
|
|
||||||
@ -26,9 +27,9 @@ from congress.db import model_base
|
|||||||
|
|
||||||
|
|
||||||
class LibraryPolicy(model_base.BASE, model_base.HasId):
|
class LibraryPolicy(model_base.BASE, model_base.HasId):
|
||||||
__tablename__ = 'librarypolicies'
|
__tablename__ = 'library_policies'
|
||||||
|
|
||||||
name = sa.Column(sa.String(255), nullable=False)
|
name = sa.Column(sa.String(255), nullable=False, unique=True)
|
||||||
abbreviation = sa.Column(sa.String(5), nullable=False)
|
abbreviation = sa.Column(sa.String(5), nullable=False)
|
||||||
description = sa.Column(sa.Text(), nullable=False)
|
description = sa.Column(sa.Text(), nullable=False)
|
||||||
kind = sa.Column(sa.Text(), nullable=False)
|
kind = sa.Column(sa.Text(), nullable=False)
|
||||||
@ -60,15 +61,19 @@ class LibraryPolicy(model_base.BASE, model_base.HasId):
|
|||||||
|
|
||||||
def add_policy(policy_dict, session=None):
|
def add_policy(policy_dict, session=None):
|
||||||
session = session or db.get_session()
|
session = session or db.get_session()
|
||||||
with session.begin(subtransactions=True):
|
try:
|
||||||
new_row = LibraryPolicy(
|
with session.begin(subtransactions=True):
|
||||||
name=policy_dict['name'],
|
new_row = LibraryPolicy(
|
||||||
abbreviation=policy_dict['abbreviation'],
|
name=policy_dict['name'],
|
||||||
description=policy_dict['description'],
|
abbreviation=policy_dict['abbreviation'],
|
||||||
kind=policy_dict['kind'],
|
description=policy_dict['description'],
|
||||||
rules=json.dumps(policy_dict['rules']))
|
kind=policy_dict['kind'],
|
||||||
session.add(new_row)
|
rules=json.dumps(policy_dict['rules']))
|
||||||
return new_row
|
session.add(new_row)
|
||||||
|
return new_row
|
||||||
|
except oslo_db_exc.DBDuplicateEntry:
|
||||||
|
raise KeyError(
|
||||||
|
"Policy with name %s already exists" % policy_dict['name'])
|
||||||
|
|
||||||
|
|
||||||
def replace_policy(id_, policy_dict, session=None):
|
def replace_policy(id_, policy_dict, session=None):
|
||||||
|
@ -31,9 +31,9 @@ import sqlalchemy as sa
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'librarypolicies',
|
'library_policies',
|
||||||
sa.Column('id', sa.String(length=36), nullable=False),
|
sa.Column('id', sa.String(length=36), nullable=False),
|
||||||
sa.Column('name', sa.String(length=255), nullable=False),
|
sa.Column('name', sa.String(length=255), nullable=False, unique=True),
|
||||||
sa.Column('abbreviation', sa.String(length=5), nullable=False),
|
sa.Column('abbreviation', sa.String(length=5), nullable=False),
|
||||||
sa.Column('description', sa.Text(), nullable=False),
|
sa.Column('description', sa.Text(), nullable=False),
|
||||||
sa.Column('kind', sa.Text(), nullable=False),
|
sa.Column('kind', sa.Text(), nullable=False),
|
||||||
@ -44,4 +44,4 @@ def upgrade():
|
|||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
op.drop_table('librarypolicies')
|
op.drop_table('library_policies')
|
||||||
|
@ -160,6 +160,10 @@ class LazyTable(BadRequest):
|
|||||||
msg_fmt = _("table %(lazy_table)s is a lazy table and is not subscribed.")
|
msg_fmt = _("table %(lazy_table)s is a lazy table and is not subscribed.")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidPolicyInput(BadRequest):
|
||||||
|
msg_fmt = _('Input policy item violates schema.')
|
||||||
|
|
||||||
|
|
||||||
# NOTE(thinrichs): The following represent different kinds of
|
# NOTE(thinrichs): The following represent different kinds of
|
||||||
# exceptions: the policy compiler and the policy runtime, respectively.
|
# exceptions: the policy compiler and the policy runtime, respectively.
|
||||||
class PolicyException(CongressException):
|
class PolicyException(CongressException):
|
||||||
|
@ -151,6 +151,9 @@ def initialize_policy_engine(engine):
|
|||||||
def create_policy_library_service():
|
def create_policy_library_service():
|
||||||
"""Create policy library service."""
|
"""Create policy library service."""
|
||||||
library = library_service.LibraryService(api_base.LIBRARY_SERVICE_ID)
|
library = library_service.LibraryService(api_base.LIBRARY_SERVICE_ID)
|
||||||
|
# load library policies from file if none present in DB
|
||||||
|
if len(library.get_policies(include_rules=False)) == 0:
|
||||||
|
library.load_policies_from_files()
|
||||||
return library
|
return library
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +18,12 @@ from __future__ import division
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import json
|
||||||
|
import jsonschema
|
||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from oslo_config import cfg
|
||||||
from oslo_db import exception as db_exc
|
from oslo_db import exception as db_exc
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
|
|
||||||
@ -38,6 +43,7 @@ class LibraryService (data_service.DataService):
|
|||||||
|
|
||||||
def create_policy(self, policy_dict):
|
def create_policy(self, policy_dict):
|
||||||
policy_dict = copy.deepcopy(policy_dict)
|
policy_dict = copy.deepcopy(policy_dict)
|
||||||
|
self._validate_policy_item(policy_dict)
|
||||||
policy_name = policy_dict['name']
|
policy_name = policy_dict['name']
|
||||||
|
|
||||||
# check name is valid
|
# check name is valid
|
||||||
@ -81,6 +87,7 @@ class LibraryService (data_service.DataService):
|
|||||||
return db_object.to_dict(include_rules=True)
|
return db_object.to_dict(include_rules=True)
|
||||||
|
|
||||||
def replace_policy(self, id_, policy_dict):
|
def replace_policy(self, id_, policy_dict):
|
||||||
|
self._validate_policy_item(policy_dict)
|
||||||
policy_name = policy_dict['name']
|
policy_name = policy_dict['name']
|
||||||
|
|
||||||
# check name is valid
|
# check name is valid
|
||||||
@ -101,6 +108,111 @@ class LibraryService (data_service.DataService):
|
|||||||
id_, policy_dict=policy_dict)
|
id_, policy_dict=policy_dict)
|
||||||
return policy.to_dict()
|
return policy.to_dict()
|
||||||
|
|
||||||
|
def _validate_policy_item(self, item):
|
||||||
|
schema_json = '''
|
||||||
|
{
|
||||||
|
"id": "PolicyProperties",
|
||||||
|
"title": "Policy Properties",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name", "rules"],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"title": "Policy unique name",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 255
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"title": "Policy description",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"title": "Policy kind",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["database", "nonrecursive", "action", "materialized",
|
||||||
|
"delta", "datasource"]
|
||||||
|
},
|
||||||
|
"abbreviation": {
|
||||||
|
"title": "Policy name abbreviation",
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 5
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"title": "collection of rules",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"PolicyRule": {
|
||||||
|
"title": "Policy rule",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["rule"],
|
||||||
|
"properties": {
|
||||||
|
"rule": {
|
||||||
|
"title": "Rule definition following policy grammar",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"title": "User-friendly name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"title": "User-friendly comment",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
jsonschema.validate(item, json.loads(schema_json))
|
||||||
|
except jsonschema.exceptions.ValidationError as ve:
|
||||||
|
raise exception.InvalidPolicyInput(data=str(ve))
|
||||||
|
|
||||||
|
def load_policies_from_files(self):
|
||||||
|
def _load_library_policy_file(full_path):
|
||||||
|
with open(full_path, "r") as stream:
|
||||||
|
policies = yaml.load_all(stream)
|
||||||
|
count = 0
|
||||||
|
doc_num_in_file = 0
|
||||||
|
for policy in policies:
|
||||||
|
try:
|
||||||
|
doc_num_in_file += 1
|
||||||
|
self.create_policy(policy)
|
||||||
|
count += 1
|
||||||
|
except db_exc.DBDuplicateEntry:
|
||||||
|
LOG.debug(
|
||||||
|
'Library policy %s (number %s in file %s) already '
|
||||||
|
'exists (likely loaded by another Congress '
|
||||||
|
'instance). Skipping.',
|
||||||
|
policy.get('name', '[no name]'),
|
||||||
|
doc_num_in_file, full_path)
|
||||||
|
except exception.CongressException:
|
||||||
|
LOG.exception(
|
||||||
|
'Library policy %s could not be loaded. Skipped. '
|
||||||
|
'YAML reproduced here %s',
|
||||||
|
policy.get('name', '[no name]'),
|
||||||
|
yaml.dumps(policy))
|
||||||
|
return count
|
||||||
|
file_count = 0
|
||||||
|
policy_count = 0
|
||||||
|
for (dirpath, dirnames, filenames) in os.walk(
|
||||||
|
cfg.CONF.policy_library_path):
|
||||||
|
for filename in filenames:
|
||||||
|
count = _load_library_policy_file(
|
||||||
|
os.path.join(dirpath, filename))
|
||||||
|
if count > 0:
|
||||||
|
file_count += 1
|
||||||
|
policy_count += count
|
||||||
|
LOG.debug(
|
||||||
|
'%s library policies from %s files successfully loaded',
|
||||||
|
policy_count, file_count)
|
||||||
|
|
||||||
|
|
||||||
class DseLibraryServiceEndpoints(object):
|
class DseLibraryServiceEndpoints(object):
|
||||||
"""RPC endpoints exposed by LibraryService."""
|
"""RPC endpoints exposed by LibraryService."""
|
||||||
@ -108,10 +220,8 @@ class DseLibraryServiceEndpoints(object):
|
|||||||
def __init__(self, data_service):
|
def __init__(self, data_service):
|
||||||
self.data_service = data_service
|
self.data_service = data_service
|
||||||
|
|
||||||
def create_policy(
|
def create_policy(self, context, policy_dict):
|
||||||
self, context, policy_dict):
|
return self.data_service.create_policy(policy_dict)
|
||||||
return self.data_service.create_policy(
|
|
||||||
policy_dict)
|
|
||||||
|
|
||||||
def get_policies(self, context, include_rules=True):
|
def get_policies(self, context, include_rules=True):
|
||||||
return self.data_service.get_policies(include_rules)
|
return self.data_service.get_policies(include_rules)
|
||||||
|
@ -20,6 +20,7 @@ from __future__ import absolute_import
|
|||||||
import copy
|
import copy
|
||||||
|
|
||||||
from congress.api import webservice
|
from congress.api import webservice
|
||||||
|
from congress.db import db_library_policies
|
||||||
from congress.tests.api import base as api_base
|
from congress.tests.api import base as api_base
|
||||||
from congress.tests import base
|
from congress.tests import base
|
||||||
|
|
||||||
@ -32,6 +33,10 @@ class TestLibraryPolicyModel(base.SqlTestCase):
|
|||||||
self.library_policy_model = services['api']['api-library-policy']
|
self.library_policy_model = services['api']['api-library-policy']
|
||||||
self.node = services['node']
|
self.node = services['node']
|
||||||
self.engine = services['engine']
|
self.engine = services['engine']
|
||||||
|
|
||||||
|
# clear the library policies loaded on startup
|
||||||
|
db_library_policies.delete_policies()
|
||||||
|
|
||||||
self._add_test_policy()
|
self._add_test_policy()
|
||||||
|
|
||||||
def _add_test_policy(self):
|
def _add_test_policy(self):
|
||||||
@ -96,7 +101,6 @@ class TestLibraryPolicyModel(base.SqlTestCase):
|
|||||||
del expected_ret['rules']
|
del expected_ret['rules']
|
||||||
|
|
||||||
policy_id, policy_obj = self.library_policy_model.add_item(test, {})
|
policy_id, policy_obj = self.library_policy_model.add_item(test, {})
|
||||||
# self.assertEqual(test['id'], policy_id)
|
|
||||||
test['id'] = policy_id
|
test['id'] = policy_id
|
||||||
self.assertEqual(test, policy_obj)
|
self.assertEqual(test, policy_obj)
|
||||||
|
|
||||||
@ -108,9 +112,11 @@ class TestLibraryPolicyModel(base.SqlTestCase):
|
|||||||
"abbreviation": "abbr",
|
"abbreviation": "abbr",
|
||||||
"rules": []
|
"rules": []
|
||||||
}
|
}
|
||||||
self.library_policy_model.add_item(test, {})
|
# duplicate name allowed
|
||||||
|
self.assertRaises(KeyError,
|
||||||
|
self.library_policy_model.add_item, test, {})
|
||||||
ret = self.library_policy_model.get_items({})
|
ret = self.library_policy_model.get_items({})
|
||||||
self.assertEqual(len(ret['results']), 3)
|
self.assertEqual(len(ret['results']), 2)
|
||||||
|
|
||||||
def test_add_item_with_id(self):
|
def test_add_item_with_id(self):
|
||||||
test = {
|
test = {
|
||||||
|
@ -25,6 +25,7 @@ class TestDbLibraryPolicies(base.SqlTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestDbLibraryPolicies, self).setUp()
|
super(TestDbLibraryPolicies, self).setUp()
|
||||||
|
db_library_policies.delete_policies() # delete preloaded policies
|
||||||
|
|
||||||
def test_add_policy_no_name(self):
|
def test_add_policy_no_name(self):
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
|
Binary file not shown.
@ -29,6 +29,7 @@ class TestLibraryService(base.SqlTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestLibraryService, self).setUp()
|
super(TestLibraryService, self).setUp()
|
||||||
self.library = library_service.LibraryService('lib-test')
|
self.library = library_service.LibraryService('lib-test')
|
||||||
|
self.library.delete_all_policies() # clear pre-loaded library policies
|
||||||
|
|
||||||
self.policy1 = {'name': 'policy1', 'abbreviation': 'abbr',
|
self.policy1 = {'name': 'policy1', 'abbreviation': 'abbr',
|
||||||
'kind': 'database', 'description': 'descrip',
|
'kind': 'database', 'description': 'descrip',
|
||||||
@ -48,12 +49,12 @@ class TestLibraryService(base.SqlTestCase):
|
|||||||
del self.policy2_meta['rules']
|
del self.policy2_meta['rules']
|
||||||
|
|
||||||
def test_create_policy_no_name(self):
|
def test_create_policy_no_name(self):
|
||||||
self.assertRaises(
|
self.assertRaises(exception.InvalidPolicyInput,
|
||||||
KeyError, self.library.create_policy, {'rules': []})
|
self.library.create_policy, {'rules': []})
|
||||||
|
|
||||||
def test_create_policy_no_rules(self):
|
def test_create_policy_no_rules(self):
|
||||||
self.assertRaises(KeyError, self.library.create_policy,
|
self.assertRaises(exception.InvalidPolicyInput,
|
||||||
{'name': 'policy1'})
|
self.library.create_policy, {'name': 'policy1'})
|
||||||
|
|
||||||
def test_create_policy_bad_name(self):
|
def test_create_policy_bad_name(self):
|
||||||
self.assertRaises(exception.PolicyException,
|
self.assertRaises(exception.PolicyException,
|
||||||
@ -73,9 +74,10 @@ class TestLibraryService(base.SqlTestCase):
|
|||||||
|
|
||||||
def test_create_policy_duplicate(self):
|
def test_create_policy_duplicate(self):
|
||||||
self.library.create_policy({'name': 'policy1', 'rules': []})
|
self.library.create_policy({'name': 'policy1', 'rules': []})
|
||||||
self.library.create_policy({'name': 'policy1', 'rules': []})
|
self.assertRaises(KeyError, self.library.create_policy,
|
||||||
|
{'name': 'policy1', 'rules': []})
|
||||||
res = self.library.get_policies()
|
res = self.library.get_policies()
|
||||||
self.assertEqual(len(res), 2)
|
self.assertEqual(len(res), 1)
|
||||||
|
|
||||||
def test_get_policy_empty(self):
|
def test_get_policy_empty(self):
|
||||||
res = self.library.get_policies()
|
res = self.library.get_policies()
|
||||||
@ -140,15 +142,15 @@ class TestLibraryService(base.SqlTestCase):
|
|||||||
|
|
||||||
self.library.create_policy(
|
self.library.create_policy(
|
||||||
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
|
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
|
||||||
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
|
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
|
||||||
'comment': 'test comment',
|
'comment': 'test comment',
|
||||||
'name': 'testname'}]]})
|
'name': 'testname'}]})
|
||||||
|
|
||||||
self.library.create_policy(
|
self.library.create_policy(
|
||||||
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
|
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
|
||||||
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
|
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
|
||||||
'comment': 'test comment',
|
'comment': 'test comment',
|
||||||
'name': 'testname'}]]})
|
'name': 'testname'}]})
|
||||||
|
|
||||||
self.library.delete_all_policies()
|
self.library.delete_all_policies()
|
||||||
res = self.library.get_policies()
|
res = self.library.get_policies()
|
||||||
@ -157,15 +159,15 @@ class TestLibraryService(base.SqlTestCase):
|
|||||||
def test_replace_policy(self):
|
def test_replace_policy(self):
|
||||||
policy1 = self.library.create_policy(
|
policy1 = self.library.create_policy(
|
||||||
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
|
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
|
||||||
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
|
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
|
||||||
'comment': 'test comment',
|
'comment': 'test comment',
|
||||||
'name': 'testname'}]]})
|
'name': 'testname'}]})
|
||||||
|
|
||||||
policy2 = self.library.create_policy(
|
policy2 = self.library.create_policy(
|
||||||
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
|
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
|
||||||
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
|
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
|
||||||
'comment': 'test comment',
|
'comment': 'test comment',
|
||||||
'name': 'testname'}]]})
|
'name': 'testname'}]})
|
||||||
|
|
||||||
replacement_policy = {
|
replacement_policy = {
|
||||||
"name": "new_name",
|
"name": "new_name",
|
||||||
|
@ -31,6 +31,7 @@ from congress.api import base as api_base
|
|||||||
from congress.common import config
|
from congress.common import config
|
||||||
from congress.datasources import neutronv2_driver
|
from congress.datasources import neutronv2_driver
|
||||||
from congress.datasources import nova_driver
|
from congress.datasources import nova_driver
|
||||||
|
from congress.db import db_library_policies
|
||||||
from congress.tests.api import base as tests_api_base
|
from congress.tests.api import base as tests_api_base
|
||||||
from congress.tests import base
|
from congress.tests import base
|
||||||
from congress.tests.datasources import test_neutron_driver as test_neutron
|
from congress.tests.datasources import test_neutron_driver as test_neutron
|
||||||
@ -85,6 +86,9 @@ class TestCongress(BaseTestPolicyCongress):
|
|||||||
"""Setup tests that use multiple mock neutron instances."""
|
"""Setup tests that use multiple mock neutron instances."""
|
||||||
super(TestCongress, self).setUp()
|
super(TestCongress, self).setUp()
|
||||||
|
|
||||||
|
# clear the library policies loaded on startup
|
||||||
|
db_library_policies.delete_policies()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super(TestCongress, self).tearDown()
|
super(TestCongress, self).tearDown()
|
||||||
|
|
||||||
|
@ -196,6 +196,10 @@ class TestPolicyLibraryBasicOps(manager_congress.ScenarioPolicyBase):
|
|||||||
response = self.admin_manager.congress_client.list_library_policy()
|
response = self.admin_manager.congress_client.list_library_policy()
|
||||||
initial_state = response['results']
|
initial_state = response['results']
|
||||||
|
|
||||||
|
self.assertGreater(
|
||||||
|
len(initial_state), 0, 'library policy shows no policies, '
|
||||||
|
'indicating failed load-on-startup.')
|
||||||
|
|
||||||
test_policy = {
|
test_policy = {
|
||||||
"name": "test_policy",
|
"name": "test_policy",
|
||||||
"description": "test policy description",
|
"description": "test policy description",
|
||||||
|
@ -51,6 +51,8 @@ function configure_congress {
|
|||||||
|
|
||||||
cp $CONGRESS_DIR/etc/api-paste.ini $CONGRESS_API_PASTE_FILE
|
cp $CONGRESS_DIR/etc/api-paste.ini $CONGRESS_API_PASTE_FILE
|
||||||
cp $CONGRESS_DIR/etc/policy.json $CONGRESS_POLICY_FILE
|
cp $CONGRESS_DIR/etc/policy.json $CONGRESS_POLICY_FILE
|
||||||
|
mkdir $CONGRESS_LIBRARY_DIR
|
||||||
|
cp $CONGRESS_DIR/library/* $CONGRESS_LIBRARY_DIR
|
||||||
|
|
||||||
# Update either configuration file
|
# Update either configuration file
|
||||||
iniset $CONGRESS_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
|
iniset $CONGRESS_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
|
||||||
|
@ -36,6 +36,8 @@ CONGRESS_REPLICATED=${CONGRESS_REPLICATED:-False}
|
|||||||
CONGRESS_TRANSPORT_URL=${CONGRESS_TRANSPORT_URL:-kombu+memory:////}
|
CONGRESS_TRANSPORT_URL=${CONGRESS_TRANSPORT_URL:-kombu+memory:////}
|
||||||
# Mutli process deployment
|
# Mutli process deployment
|
||||||
CONGRESS_MULTIPROCESS_DEPLOYMENT=${CONGRESS_MULTIPROCESS_DEPLOYMENT:-False}
|
CONGRESS_MULTIPROCESS_DEPLOYMENT=${CONGRESS_MULTIPROCESS_DEPLOYMENT:-False}
|
||||||
|
# Directory path to library policy files
|
||||||
|
CONGRESS_LIBRARY_DIR=$CONGRESS_CONF_DIR/library
|
||||||
# File path to predefined policy and rules
|
# File path to predefined policy and rules
|
||||||
CONGRESS_PREDEFINED_POLICY_FILE=${CONGRESS_PREDEFINED_POLICY_FILE:-""}
|
CONGRESS_PREDEFINED_POLICY_FILE=${CONGRESS_PREDEFINED_POLICY_FILE:-""}
|
||||||
|
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: PauseBadFlavors
|
||||||
|
description: "Pause any server using a flavor that is not permitted"
|
||||||
id: PauseBadFlavors
|
|
||||||
description: Pause any server using a flavor that is not permitted
|
|
||||||
rules:
|
rules:
|
||||||
- comment: "User should customize this. Permitted flavors."
|
-
|
||||||
rule: permitted_flavor('m1.tiny')
|
comment: "User should customize this. Permitted flavors."
|
||||||
- comment: "User should customize this. Permitted flavors."
|
rule: permitted_flavor('m1.tiny')
|
||||||
rule: permitted_flavor('m1.large')
|
-
|
||||||
- rule: >
|
comment: "User should customize this. Permitted flavors."
|
||||||
server_with_bad_flavor(id) :- nova:servers(id=id,flavor_id=flavor_id), nova:flavors(id=flavor_id, name=flavor),
|
rule: permitted_flavor('m1.large')
|
||||||
not permitted_flavor(flavor)
|
-
|
||||||
- comment: "Remediation: Pause any VM that shows up in the server_with_bad_flavor table"
|
rule: >
|
||||||
rule: "execute[nova:servers.pause(id)] :- server_with_bad_flavor(id), nova:servers(id,status='ACTIVE')"
|
"server_with_bad_flavor(id) :- nova:servers(id=id,flavor_id=flavor_id),
|
||||||
|
nova:flavors(id=flavor_id, name=flavor), not permitted_flavor(flavor)"
|
||||||
|
-
|
||||||
|
comment: "Remediation: Pause any VM that shows up in the server_with_bad_flavor table"
|
||||||
|
rule: "execute[nova:servers.pause(id)] :- server_with_bad_flavor(id), nova:servers(id,status='ACTIVE')"
|
10
releasenotes/notes/load-lib-policies-a5cca19f58f9030c.yaml
Normal file
10
releasenotes/notes/load-lib-policies-a5cca19f58f9030c.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
prelude: >
|
||||||
|
upgrade:
|
||||||
|
- A new config option `policy_library_path` is added to the [DEFAULT]
|
||||||
|
section. The string option specifies the directory from which
|
||||||
|
Congress will load pre-written policies for easy activation later
|
||||||
|
by an administrator.
|
||||||
|
This option can be ignored if you do not want
|
||||||
|
Congress to load pre-written policies from files. Due to MySQL limitations,
|
||||||
|
the full path to each policy file cannot exceed 760 characters.
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
prelude: >
|
prelude: >
|
||||||
upgrade:
|
upgrade:
|
||||||
- A new database table `librarypolicies` is added;
|
- A new database table `library_policies` is added;
|
||||||
alembic migration scripts included.
|
alembic migration scripts included.
|
||||||
|
Loading…
Reference in New Issue
Block a user