Merge "policy library DB interface, DSE service, API"

This commit is contained in:
Jenkins 2017-06-29 04:34:01 +00:00 committed by Gerrit Code Review
commit a724dfb59c
22 changed files with 1259 additions and 16 deletions

View File

@ -20,6 +20,7 @@ from __future__ import absolute_import
from oslo_config import cfg
ENGINE_SERVICE_ID = '__engine'
LIBRARY_SERVICE_ID = '__library'
DS_MANAGER_SERVICE_ID = '_ds_manager'

View File

@ -0,0 +1,223 @@
# Copyright (c) 2017 VMware, Inc. All rights reserved.
#
# 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.
#
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import json
import jsonschema
from oslo_log import log as logging
from congress.api import base
from congress.api import error_codes
from congress.api import webservice
from congress import exception
LOG = logging.getLogger(__name__)
class LibraryPolicyModel(base.APIModel):
"""Model for handling API requests about Library Policies."""
# Note(thread-safety): blocking function
def get_items(self, params, context=None):
"""Get items in model.
Args:
params: A dict-like object containing parameters
from the request query string and body.
context: Key-values providing frame of reference of request
Returns: A dict containing at least a 'results' key whose value is
a list of items in the model. Additional keys set in the
dict will also be rendered for the user.
"""
try:
# Note(thread-safety): blocking call
return {"results": self.invoke_rpc(base.LIBRARY_SERVICE_ID,
'get_policies',
{})}
except exception.CongressException as e:
raise webservice.DataModelException.create(e)
# Note(thread-safety): blocking function
def get_item(self, id_, params, context=None):
"""Retrieve item with name name from model.
Args:
name: The unique name of the item to retrieve
params: A dict-like object containing parameters
from the request query string and body.
context: Key-values providing frame of reference of request
Returns:
The matching item or None if no item named name exists.
"""
try:
# Note(thread-safety): blocking call
return self.invoke_rpc(base.LIBRARY_SERVICE_ID,
'get_policy',
{'id_': id_, 'include_rules': True})
except exception.CongressException as e:
raise webservice.DataModelException.create(e)
# Note(thread-safety): blocking function
def add_item(self, item, params, id_=None, context=None):
"""Add item to model.
Args:
item: The item to add to the model
params: A dict-like object containing parameters
from the request query string and body.
id_: The unique name of the item
context: Key-values providing frame of reference of request
Returns:
Tuple of (ID, newly_created_item)
Raises:
KeyError: ID already exists.
DataModelException: Addition cannot be performed.
"""
if id_ is not None:
(num, desc) = error_codes.get('policy_id_must_not_be_provided')
raise webservice.DataModelException(num, desc)
self._validate_policy_item(item)
try:
# Note(thread-safety): blocking call
policy_metadata = self.invoke_rpc(
base.LIBRARY_SERVICE_ID, 'create_policy',
{'policy_dict': item})
except exception.CongressException as e:
raise webservice.DataModelException.create(e)
return (policy_metadata['id'], policy_metadata)
# Note(thread-safety): blocking function
def delete_item(self, id_, params, context=None):
"""Remove item from model.
Args:
id_: The unique name of the item to be removed
params:
context: Key-values providing frame of reference of request
Returns:
The removed item.
Raises:
KeyError: Item with specified id_ not present.
"""
# Note(thread-safety): blocking call
return self.invoke_rpc(base.LIBRARY_SERVICE_ID,
'delete_policy',
{'id_': id_})
def update_item(self, id_, item, params, context=None):
"""Update item with id_ with new data.
Args:
id_: The ID of the item to be updated
item: The new item
params: A dict-like object containing parameters
from the request query string and body.
context: Key-values providing frame of reference of request
Returns:
The updated item.
Raises:
KeyError: Item with specified id_ not present.
"""
self._validate_policy_item(item)
# Note(thread-safety): blocking call
try:
return self.invoke_rpc(base.LIBRARY_SERVICE_ID,
'replace_policy',
{'id_': id_,
'policy_dict': item})
except exception.CongressException as 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))

View File

@ -45,6 +45,21 @@ class APIRouterV1(object):
allow_replace=False)
resource_mgr.register_handler(policy_element_handler)
library_policies = process_dict['api-library-policy']
library_policy_collection_handler = webservice.CollectionHandler(
r'/v1/librarypolicies',
library_policies)
resource_mgr.register_handler(library_policy_collection_handler)
library_policy_path = r'/v1/librarypolicies/(?P<policy_id>[^/]+)'
library_policy_element_handler = webservice.ElementHandler(
library_policy_path,
library_policies,
library_policy_collection_handler,
allow_update=False,
allow_replace=True)
resource_mgr.register_handler(library_policy_element_handler)
policy_rules = process_dict['api-rule']
rule_collection_handler = webservice.CollectionHandler(
r'/v1/policies/(?P<policy_id>[^/]+)/rules',

View File

@ -26,6 +26,7 @@ import json
import re
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
from oslo_utils import uuidutils
import six
@ -249,17 +250,21 @@ class ElementHandler(AbstractApiHandler):
Returns:
A webob response object.
"""
if request.method == 'GET' and self.allow_read:
return self.read(request)
elif request.method == 'POST' and self.allow_actions:
return self.action(request)
elif request.method == 'PUT' and self.allow_replace:
return self.replace(request)
elif request.method == 'PATCH' and self.allow_update:
return self.update(request)
elif request.method == 'DELETE' and self.allow_delete:
return self.delete(request)
return NOT_SUPPORTED_RESPONSE
try:
if request.method == 'GET' and self.allow_read:
return self.read(request)
elif request.method == 'POST' and self.allow_actions:
return self.action(request)
elif request.method == 'PUT' and self.allow_replace:
return self.replace(request)
elif request.method == 'PATCH' and self.allow_update:
return self.update(request)
elif request.method == 'DELETE' and self.allow_delete:
return self.delete(request)
return NOT_SUPPORTED_RESPONSE
except db_exc.DBError:
LOG.exception('Database backend experienced an unknown error.')
raise exception.DatabaseError
def read(self, request):
if not hasattr(self.model, 'get_item'):

View File

@ -0,0 +1,115 @@
# Copyright (c) 2017 VMware, Inc. All rights reserved.
#
# 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.
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import json
import sqlalchemy as sa
from sqlalchemy.orm import exc as db_exc
from congress.db import api as db
from congress.db import model_base
class LibraryPolicy(model_base.BASE, model_base.HasId):
__tablename__ = 'librarypolicies'
name = sa.Column(sa.String(255), nullable=False)
abbreviation = sa.Column(sa.String(5), nullable=False)
description = sa.Column(sa.Text(), nullable=False)
kind = sa.Column(sa.Text(), nullable=False)
rules = sa.Column(sa.Text(), nullable=False)
def to_dict(self, include_rules=True, json_rules=False):
"""From a given library policy, return a policy dict.
Args:
include_rules (bool, optional): include policy rules in return
dictionary. Defaults to False.
"""
if not include_rules:
d = {'id': self.id,
'name': self.name,
'abbreviation': self.abbreviation,
'description': self.description,
'kind': self.kind}
else:
d = {'id': self.id,
'name': self.name,
'abbreviation': self.abbreviation,
'description': self.description,
'kind': self.kind,
'rules': (self.rules if json_rules
else json.loads(self.rules))}
return d
def add_policy(policy_dict, session=None):
session = session or db.get_session()
with session.begin(subtransactions=True):
new_row = LibraryPolicy(
name=policy_dict['name'],
abbreviation=policy_dict['abbreviation'],
description=policy_dict['description'],
kind=policy_dict['kind'],
rules=json.dumps(policy_dict['rules']))
session.add(new_row)
return new_row
def replace_policy(id_, policy_dict, session=None):
session = session or db.get_session()
try:
with session.begin(subtransactions=True):
new_row = LibraryPolicy(
id=id_,
name=policy_dict['name'],
abbreviation=policy_dict['abbreviation'],
description=policy_dict['description'],
kind=policy_dict['kind'],
rules=json.dumps(policy_dict['rules']))
session.query(LibraryPolicy).filter(
LibraryPolicy.id == id_).one().update(
new_row.to_dict(include_rules=True, json_rules=True))
return new_row
except db_exc.NoResultFound:
raise KeyError('No policy found with policy id %s' % id_)
def delete_policy(id_, session=None):
session = session or db.get_session()
return session.query(LibraryPolicy).filter(
LibraryPolicy.id == id_).delete()
def delete_policies(session=None):
session = session or db.get_session()
return session.query(LibraryPolicy).delete()
def get_policy(id_, session=None):
session = session or db.get_session()
try:
return session.query(LibraryPolicy).filter(
LibraryPolicy.id == id_).one()
except db_exc.NoResultFound:
raise KeyError('No policy found with policy id %s' % id_)
def get_policies(session=None):
session = session or db.get_session()
return (session.query(LibraryPolicy).all())

View File

@ -1 +1 @@
3cee191c4f84
c0125080d572

View File

@ -0,0 +1,47 @@
# Copyright 2017 OpenStack Foundation
#
# 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.
#
"""policy library
Revision ID: c0125080d572
Revises: aabe895bbd4d
Create Date: 2017-06-21 13:20:14.529313
"""
# revision identifiers, used by Alembic.
revision = 'c0125080d572'
down_revision = 'aabe895bbd4d'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'librarypolicies',
sa.Column('id', sa.String(length=36), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False),
sa.Column('abbreviation', sa.String(length=5), nullable=False),
sa.Column('description', sa.Text(), nullable=False),
sa.Column('kind', sa.Text(), nullable=False),
sa.Column('rules', sa.Text(), nullable=False),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)
def downgrade():
op.drop_table('librarypolicies')

View File

@ -21,6 +21,7 @@ import eventlet
eventlet.monkey_patch() # for using oslo.messaging w/ eventlet executor
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
import oslo_messaging as messaging
from oslo_messaging import exceptions as messaging_exceptions
@ -103,7 +104,8 @@ class DseNode(object):
self.context = self._message_context()
self.transport = messaging.get_transport(
self.messaging_config,
allowed_remote_exmods=[exception.__name__, dispatcher.__name__, ])
allowed_remote_exmods=[exception.__name__, dispatcher.__name__,
db_exc.__name__, ])
self._rpctarget = self.node_rpc_target(self.node_id, self.node_id)
self._rpc_server = messaging.get_rpc_server(
self.transport, self._rpctarget, self.node_rpc_endpoints,

View File

@ -180,6 +180,10 @@ class PolicyRuntimeException(CongressException):
pass
class DatabaseError(CongressException):
msg_fmt = _("Database backend experienced an unknown error.")
class IncompleteSchemaException(CongressException):
pass

View File

@ -26,6 +26,7 @@ from congress.api import action_model
from congress.api import application
from congress.api import base as api_base
from congress.api import datasource_model
from congress.api import library_policy_model
from congress.api import policy_model
from congress.api import router
from congress.api import row_model
@ -38,9 +39,9 @@ from congress.db import datasources as db_datasources
from congress.dse2 import datasource_manager as ds_manager
from congress.dse2 import dse_node
from congress import exception
from congress.library_service import library_service
from congress.policy_engines import agnostic
LOG = logging.getLogger(__name__)
@ -88,6 +89,14 @@ def create2(node_id=None, bus_id=None, existing_node=None,
node.register_service(engine)
initialize_policy_engine(engine)
# NOTE(ekcs): library service does not depend on policy engine,
# it is placed on the same nodes as policy engine for convenience only
LOG.info("Registering congress policy library service on node %s",
node.node_id)
library = create_policy_library_service()
services[api_base.LIBRARY_SERVICE_ID] = library
node.register_service(library)
if api:
LOG.info("Registering congress API service on node %s", node.node_id)
services['api'], services['api_service'] = create_api()
@ -108,6 +117,8 @@ def create_api():
def create_api_models(bus):
"""Create all the API models and return as a dictionary for DSE2."""
res = {}
res['api-library-policy'] = library_policy_model.LibraryPolicyModel(
'api-library-policy', bus=bus)
res['api-policy'] = policy_model.PolicyModel('api-policy', bus=bus)
res['api-rule'] = rule_model.RuleModel('api-rule', bus=bus)
res['api-row'] = row_model.RowModel('api-row', bus=bus)
@ -137,6 +148,12 @@ def initialize_policy_engine(engine):
engine.persistent_load_rules()
def create_policy_library_service():
"""Create policy library service."""
library = library_service.LibraryService(api_base.LIBRARY_SERVICE_ID)
return library
def create_datasources(bus):
"""Create and register datasource services ."""
if cfg.CONF.delete_missing_driver_datasources:

View File

View File

@ -0,0 +1,129 @@
# Copyright (c) 2017 VMware, Inc. All rights reserved.
#
# 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.
#
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import copy
from oslo_db import exception as db_exc
from oslo_log import log as logging
from congress.datalog import compile
from congress.db import db_library_policies
from congress.dse2 import data_service
from congress import exception
LOG = logging.getLogger(__name__)
class LibraryService (data_service.DataService):
def __init__(self, name):
data_service.DataService.__init__(self, name)
self.name = name
self.add_rpc_endpoint(DseLibraryServiceEndpoints(self))
def create_policy(self, policy_dict):
policy_dict = copy.deepcopy(policy_dict)
policy_name = policy_dict['name']
# check name is valid
if not compile.string_is_servicename(policy_name):
raise exception.PolicyException(
'name `%s` is not a valid policy name' % policy_name)
# make defaults
if 'kind' not in policy_dict:
policy_dict['kind'] = 'nonrecursive'
if 'abbreviation' not in policy_dict:
policy_dict['abbreviation'] = policy_name[:5]
if 'description' not in policy_dict:
policy_dict['description'] = ''
try:
# Note(thread-safety): blocking call
policy = db_library_policies.add_policy(policy_dict=policy_dict)
return policy.to_dict()
except db_exc.DBError:
LOG.exception('Creating a new library policy failed.')
raise
def get_policies(self, include_rules=True):
return [p.to_dict(include_rules)
for p in db_library_policies.get_policies()]
def get_policy(self, id_, include_rules=True):
# Note(thread-safety): blocking call
policy = db_library_policies.get_policy(id_)
return policy.to_dict(include_rules)
def delete_all_policies(self):
# Note(thread-safety): blocking call
db_library_policies.delete_policies()
def delete_policy(self, id_):
# Note(thread-safety): blocking call
db_object = db_library_policies.get_policy(id_)
db_library_policies.delete_policy(id_)
return db_object.to_dict(include_rules=True)
def replace_policy(self, id_, policy_dict):
policy_name = policy_dict['name']
# check name is valid
if not compile.string_is_servicename(policy_name):
raise exception.PolicyException(
"Policy name %s is not a valid service name" % policy_name)
# make defaults
if 'kind' not in policy_dict:
policy_dict['kind'] = 'nonrecursive'
if 'abbreviation' not in policy_dict:
policy_dict['abbreviation'] = policy_name[:5]
if 'description' not in policy_dict:
policy_dict['description'] = ''
# Note(thread-safety): blocking call
policy = db_library_policies.replace_policy(
id_, policy_dict=policy_dict)
return policy.to_dict()
class DseLibraryServiceEndpoints(object):
"""RPC endpoints exposed by LibraryService."""
def __init__(self, data_service):
self.data_service = data_service
def create_policy(
self, context, policy_dict):
return self.data_service.create_policy(
policy_dict)
def get_policies(self, context, include_rules=True):
return self.data_service.get_policies(include_rules)
def get_policy(self, context, id_, include_rules=True):
return self.data_service.get_policy(id_, include_rules)
def delete_all_policies(self, context):
return self.data_service.delete_all_policies()
def delete_policy(self, context, id_):
return self.data_service.delete_policy(id_)
def replace_policy(self, context, id_, policy_dict):
return self.data_service.replace_policy(id_, policy_dict)

View File

@ -60,13 +60,15 @@ def setup_config(with_fake_datasource=True, node_id='testnode',
node.register_service(data)
engine_service = None
library_service = None
api_service = None
if policy:
engine_service = services[api_base.ENGINE_SERVICE_ID]
library_service = services[api_base.LIBRARY_SERVICE_ID]
if api:
api_service = services['api']
if datasources:
ds_manager = services['ds_manager']
return {'node': node, 'engine': engine_service, 'data': data,
'api': api_service, 'ds_manager': ds_manager}
return {'node': node, 'engine': engine_service, 'library': library_service,
'data': data, 'api': api_service, 'ds_manager': ds_manager}

View File

@ -0,0 +1,250 @@
# Copyright (c) 2017 VMware Inc
#
# 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.
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import copy
from congress.api import webservice
from congress.tests.api import base as api_base
from congress.tests import base
class TestLibraryPolicyModel(base.SqlTestCase):
def setUp(self):
super(TestLibraryPolicyModel, self).setUp()
services = api_base.setup_config()
self.library_policy_model = services['api']['api-library-policy']
self.node = services['node']
self.engine = services['engine']
self._add_test_policy()
def _add_test_policy(self):
test_policy = {
"name": "test_policy",
"description": "test policy description",
"kind": "nonrecursive",
"abbreviation": "abbr",
"rules": [{"rule": "p(x) :- q(x)", "comment": "test comment",
"name": "test name"},
{"rule": "p(x) :- q2(x)", "comment": "test comment2",
"name": "test name2"}]
}
test_policy_id, obj = self.library_policy_model.add_item(
test_policy, {})
test_policy["id"] = test_policy_id
test_policy2 = {
"name": "test_policy2",
"description": "test policy2 description",
"kind": "nonrecursive",
"abbreviation": "abbr2",
"rules": []
}
test_policy_id, obj = self.library_policy_model.add_item(
test_policy2, {})
test_policy2["id"] = test_policy_id
self.policy = test_policy
self.policy2 = test_policy2
self.policy_metadata = copy.deepcopy(test_policy)
self.policy2_metadata = copy.deepcopy(test_policy2)
del self.policy_metadata['rules']
del self.policy2_metadata['rules']
def test_get_items(self):
ret = self.library_policy_model.get_items({})
self.assertTrue(all(p in ret['results']
for p in [self.policy,
self.policy2]))
def test_get_item(self):
expected_ret = self.policy
ret = self.library_policy_model.get_item(self.policy["id"], {})
self.assertEqual(expected_ret, ret)
def test_get_invalid_item(self):
self.assertRaises(KeyError,
self.library_policy_model.get_item,
'invalid-id', {})
def test_add_item(self):
test = {
"name": "test",
"description": "test description",
"kind": "nonrecursive",
"abbreviation": "abbr",
"rules": []
}
expected_ret = copy.deepcopy(test)
del expected_ret['rules']
policy_id, policy_obj = self.library_policy_model.add_item(test, {})
# self.assertEqual(test['id'], policy_id)
test['id'] = policy_id
self.assertEqual(test, policy_obj)
def test_add_item_duplicate_name(self):
test = {
"name": "test_policy",
"description": "test description",
"kind": "nonrecursive",
"abbreviation": "abbr",
"rules": []
}
self.library_policy_model.add_item(test, {})
ret = self.library_policy_model.get_items({})
self.assertEqual(len(ret['results']), 3)
def test_add_item_with_id(self):
test = {
"name": "test",
"description": "test description",
"kind": "nonrecursive",
"abbreviation": "abbr",
"rules": []
}
self.assertRaises(webservice.DataModelException,
self.library_policy_model.add_item, test, {}, 'id')
def test_add_item_without_name(self):
test = {
"description": "test description",
"kind": "nonrecursive",
"abbreviation": "abbr"
}
self.assertRaises(webservice.DataModelException,
self.library_policy_model.add_item, test, {})
def test_add_item_with_long_abbreviation(self):
test = {
"name": "test",
"description": "test description",
"kind": "nonrecursive",
"abbreviation": "123456",
"rules": []
}
self.assertRaises(webservice.DataModelException,
self.library_policy_model.add_item, test, {})
def test_update_item_without_name(self):
test = {
"description": "test description",
"kind": "nonrecursive",
"abbreviation": "abbr"
}
self.assertRaises(webservice.DataModelException,
self.library_policy_model.update_item,
self.policy['id'], test, {})
def test_update_item_with_long_abbreviation(self):
test = {
"name": "test",
"description": "test description",
"kind": "nonrecursive",
"abbreviation": "123456",
"rules": []
}
self.assertRaises(webservice.DataModelException,
self.library_policy_model.update_item,
self.policy['id'], test, {})
def test_delete_item(self):
# delete non-existent policy
self.assertRaises(KeyError, self.library_policy_model.delete_item,
'no_such_policy', {})
# delete existing policy
expected_ret = self.policy
policy_id = self.policy['id']
ret = self.library_policy_model.delete_item(policy_id, {})
self.assertEqual(expected_ret, ret)
self.assertRaises(KeyError,
self.library_policy_model.get_item,
self.policy['id'], {})
def test_policy_api_model_error(self):
"""Test the policy api model."""
# policy without name
self.assertRaises(webservice.DataModelException,
self.library_policy_model.add_item,
{'rules': []}, {})
self.assertRaises(webservice.DataModelException,
self.library_policy_model.update_item,
self.policy['id'], {'rules': []}, {})
# policy with bad name
self.assertRaises(webservice.DataModelException,
self.library_policy_model.add_item,
{'name': '7*7', 'rules': []}, {})
self.assertRaises(webservice.DataModelException,
self.library_policy_model.update_item,
self.policy['id'], {'name': '7*7', 'rules': []}, {})
self.assertRaises(webservice.DataModelException,
self.library_policy_model.add_item,
{'name': 'p(x) :- q(x)'}, {})
self.assertRaises(webservice.DataModelException,
self.library_policy_model.update_item,
self.policy['id'], {'name': 'p(x) :- q(x)'}, {})
# policy with invalid 'kind'
self.assertRaises(webservice.DataModelException,
self.library_policy_model.add_item,
{'kind': 'nonexistent', 'name': 'alice',
'rules': []}, {})
self.assertRaises(webservice.DataModelException,
self.library_policy_model.update_item,
self.policy['id'],
{'kind': 'nonexistent', 'name': 'alice',
'rules': []}, {})
def test_update_item(self):
replacement_policy = {
"name": "new_name",
"description": "new test policy2 description",
"kind": "nonrecursive",
"abbreviation": "newab",
"rules": [{"rule": "r(x) :- c(x)", "comment": "test comment",
"name": "test name"}]
}
# update non-existent item
self.assertRaises(KeyError,
self.library_policy_model.update_item, 'no_such_id',
replacement_policy, {}, {})
# update existing item
self.library_policy_model.update_item(
self.policy2['id'], replacement_policy, {}, {})
replacement_policy_w_id = copy.deepcopy(replacement_policy)
replacement_policy_w_id['id'] = self.policy2['id']
ret = self.library_policy_model.get_items({})
self.assertEqual(len(ret['results']), 2)
self.assertTrue(all(p in ret['results']
for p in [self.policy,
replacement_policy_w_id]))

View File

@ -0,0 +1,155 @@
# Copyright 2017 VMware Inc. All rights reserved.
# All Rights Reserved.
#
# 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.
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
from congress.db import db_library_policies
from congress.tests import base
class TestDbLibraryPolicies(base.SqlTestCase):
def setUp(self):
super(TestDbLibraryPolicies, self).setUp()
def test_add_policy_no_name(self):
self.assertRaises(
KeyError, db_library_policies.add_policy, {'rules': []})
def test_add_policy_no_rules(self):
self.assertRaises(KeyError, db_library_policies.add_policy,
{'name': 'policy1'})
def test_add_policy(self):
res = db_library_policies.add_policy(
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
self.assertEqual(res.to_dict(include_rules=True),
{'id': res['id'],
'abbreviation': 'abbr',
'kind': 'database',
'name': 'policy1',
'description': 'descrip',
'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
def test_add_policy_duplicate(self):
db_library_policies.add_policy(
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': []})
self.assertRaises(
KeyError, db_library_policies.add_policy,
{'name': 'policy1', 'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
def test_get_policy_empty(self):
res = db_library_policies.get_policies()
self.assertEqual(res, [])
self.assertRaises(KeyError, db_library_policies.get_policy,
'nosuchpolicy')
def test_create_get_policy(self):
policy1 = db_library_policies.add_policy(
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
res = db_library_policies.get_policies()
res = db_library_policies.get_policies()
self.assertEqual([p.to_dict(include_rules=True) for p in res],
[{'id': policy1['id'],
'abbreviation': 'abbr',
'kind': 'database',
'name': 'policy1',
'description': 'descrip',
'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]}])
res = db_library_policies.get_policy(policy1['id'])
self.assertEqual(res.to_dict(include_rules=True),
{'id': policy1['id'],
'abbreviation': 'abbr',
'kind': 'database',
'name': 'policy1',
'description': 'descrip',
'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
self.assertRaises(KeyError, db_library_policies.get_policy,
'no_such_policy')
def test_delete_policy(self):
db_library_policies.delete_policy('policy1')
policy1 = db_library_policies.add_policy(
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
policy2 = db_library_policies.add_policy(
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
res = db_library_policies.get_policies()
self.assertEqual(len(res), 2)
db_library_policies.delete_policy('no_such_policy')
res = db_library_policies.get_policies()
self.assertEqual(len(res), 2)
db_library_policies.delete_policy(policy1['id'])
res = db_library_policies.get_policies()
self.assertEqual(len(res), 1)
db_library_policies.delete_policy(policy2['id'])
res = db_library_policies.get_policies()
self.assertEqual(len(res), 0)
def test_delete_policies(self):
db_library_policies.delete_policies()
res = db_library_policies.get_policies()
self.assertEqual(len(res), 0)
db_library_policies.add_policy(
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
db_library_policies.add_policy(
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]})
db_library_policies.delete_policies()
res = db_library_policies.get_policies()
self.assertEqual(len(res), 0)

Binary file not shown.

View File

@ -0,0 +1,194 @@
# Copyright 2017 VMware Inc. All rights reserved.
# All Rights Reserved.
#
# 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.
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import copy
from congress import exception
from congress.library_service import library_service
from congress.tests import base
class TestLibraryService(base.SqlTestCase):
def setUp(self):
super(TestLibraryService, self).setUp()
self.library = library_service.LibraryService('lib-test')
self.policy1 = {'name': 'policy1', 'abbreviation': 'abbr',
'kind': 'database', 'description': 'descrip',
'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]}
self.policy2 = {'name': 'policy2', 'abbreviation': 'abbr',
'kind': 'database', 'description': 'descrip',
'rules': [{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]}
self.policy1_meta = copy.deepcopy(self.policy1)
self.policy2_meta = copy.deepcopy(self.policy2)
del self.policy1_meta['rules']
del self.policy2_meta['rules']
def test_create_policy_no_name(self):
self.assertRaises(
KeyError, self.library.create_policy, {'rules': []})
def test_create_policy_no_rules(self):
self.assertRaises(KeyError, self.library.create_policy,
{'name': 'policy1'})
def test_create_policy_bad_name(self):
self.assertRaises(exception.PolicyException,
self.library.create_policy,
{'name': 'disallowed-hyphen', 'rules': []})
def test_create_policy_default(self):
res = self.library.create_policy({'name': 'policy1', 'rules': []})
self.assertEqual(res, {'id': res['id'], 'abbreviation': 'polic',
'kind': 'nonrecursive', 'name': 'policy1',
'description': '', 'rules': []})
def test_create_policy(self):
policy_obj = self.library.create_policy(self.policy1)
self.policy1['id'] = policy_obj['id']
self.assertEqual(policy_obj, self.policy1)
def test_create_policy_duplicate(self):
self.library.create_policy({'name': 'policy1', 'rules': []})
self.library.create_policy({'name': 'policy1', 'rules': []})
res = self.library.get_policies()
self.assertEqual(len(res), 2)
def test_get_policy_empty(self):
res = self.library.get_policies()
self.assertEqual(res, [])
self.assertRaises(KeyError, self.library.get_policy,
'nosuchpolicy')
def test_create_get_policy(self):
policy_obj = self.library.create_policy(self.policy1)
self.policy1['id'] = policy_obj['id']
self.policy1_meta['id'] = policy_obj['id']
res = self.library.get_policies()
self.assertEqual(res, [self.policy1])
res = self.library.get_policy(policy_obj['id'])
self.assertEqual(res, self.policy1)
res = self.library.get_policies(include_rules=True)
self.assertEqual(res, [self.policy1])
res = self.library.get_policy(policy_obj['id'], include_rules=False)
self.assertEqual(res, self.policy1_meta)
self.assertRaises(KeyError, self.library.get_policy, 'no_such_policy')
def test_delete_policy(self):
self.assertRaises(KeyError, self.library.delete_policy,
'policy1')
policy_obj = self.library.create_policy(self.policy1)
self.policy1['id'] = policy_obj['id']
policy_obj = self.library.create_policy(self.policy2)
self.policy2['id'] = policy_obj['id']
res = self.library.get_policies()
self.assertEqual(len(res), 2)
self.assertTrue(all(p in res
for p in [self.policy1, self.policy2]))
self.assertRaises(KeyError, self.library.delete_policy,
'no_such_policy')
res = self.library.delete_policy(self.policy1['id'])
self.assertEqual(res, self.policy1)
res = self.library.get_policies()
self.assertEqual(len(res), 1)
self.assertEqual(res[0], self.policy2)
res = self.library.delete_policy(self.policy2['id'])
self.assertEqual(res, self.policy2)
res = self.library.get_policies()
self.assertEqual(len(res), 0)
def test_delete_policies(self):
self.library.delete_all_policies()
res = self.library.get_policies()
self.assertEqual(len(res), 0)
self.library.create_policy(
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]]})
self.library.create_policy(
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]]})
self.library.delete_all_policies()
res = self.library.get_policies()
self.assertEqual(len(res), 0)
def test_replace_policy(self):
policy1 = self.library.create_policy(
{'name': 'policy1', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]]})
policy2 = self.library.create_policy(
{'name': 'policy2', 'abbreviation': 'abbr', 'kind': 'database',
'description': 'descrip', 'rules': [[{'rule': 'p(x) :- q(x)',
'comment': 'test comment',
'name': 'testname'}]]})
replacement_policy = {
"name": "new_name",
"description": "new test policy2 description",
"kind": "nonrecursive",
"abbreviation": "newab",
"rules": [{"rule": "r(x) :- c(x)", "comment": "test comment",
"name": "test name"}]
}
# update non-existent item
self.assertRaises(KeyError,
self.library.replace_policy, 'no_such_id',
replacement_policy)
# update existing item
self.library.replace_policy(policy2['id'], replacement_policy)
replacement_policy_w_id = copy.deepcopy(replacement_policy)
replacement_policy_w_id['id'] = policy2['id']
ret = self.library.get_policies()
self.assertEqual(len(ret), 2)
self.assertTrue(all(p in ret
for p in [policy1,
replacement_policy_w_id]))

View File

@ -48,6 +48,7 @@ class BaseTestPolicyCongress(base.SqlTestCase):
self.api = self.services['api']
self.node = self.services['node']
self.engine = self.services['engine']
self.library = self.services['library']
self.neutronv2 = self._create_neutron_mock('neutron')
@ -186,6 +187,12 @@ class TestCongress(BaseTestPolicyCongress):
f = lambda: len(neutron.state['ports'])
helper.retry_check_function_return_value_not_eq(f, 0)
def test_library_service(self):
# NOTE(ekcs): only the most basic test right now, more detailed testing
# done in test_library_service.py
res = self.library.get_policies()
self.assertEqual(res, [])
class APILocalRouting(BaseTestPolicyCongress):

View File

@ -31,6 +31,9 @@ class PolicyClient(rest_client.RestClient):
policies = '/v1/policies'
policies_status = '/v1/policies/%s/status'
policy_action = '/v1/policies/%s?%s'
library_policy = '/v1/librarypolicies'
library_policy_path = '/v1/librarypolicies/%s'
library_policies = '/v1/librarypolicies'
datasources = '/v1/data-sources'
datasource_path = '/v1/data-sources/%s'
datasource_tables = '/v1/data-sources/%s/tables'
@ -63,6 +66,22 @@ class PolicyClient(rest_client.RestClient):
self.policy_path % policy)
return self._resp_helper(resp, body)
def create_library_policy(self, body):
body = json.dumps(body)
resp, body = self.post(
self.library_policy, body=body)
return self._resp_helper(resp, body)
def delete_library_policy(self, policy):
resp, body = self.delete(
self.library_policy_path % policy)
return self._resp_helper(resp, body)
def show_library_policy(self, policy):
resp, body = self.get(
self.library_policy_path % policy)
return self._resp_helper(resp, body)
def create_policy_rule(self, policy_name, body=None):
body = json.dumps(body)
resp, body = self.post(
@ -95,6 +114,10 @@ class PolicyClient(rest_client.RestClient):
resp, body = self.get(self.policies)
return self._resp_helper(resp, body)
def list_library_policy(self):
resp, body = self.get(self.library_policies)
return self._resp_helper(resp, body)
def list_policy_tables(self, policy_name):
resp, body = self.get(self.policy_tables % (policy_name))
return self._resp_helper(resp, body)

View File

@ -190,6 +190,54 @@ class TestPolicyBasicOps(manager_congress.ScenarioPolicyBase):
self.assertEqual(f(), meta_data)
class TestPolicyLibraryBasicOps(manager_congress.ScenarioPolicyBase):
@decorators.attr(type='smoke')
def test_policy_library_basic_op(self):
response = self.admin_manager.congress_client.list_library_policy()
initial_state = response['results']
test_policy = {
"name": "test_policy",
"description": "test policy description",
"kind": "nonrecursive",
"abbreviation": "abbr",
"rules": [{"rule": "p(x) :- q(x)", "comment": "test comment",
"name": "test name"},
{"rule": "p(x) :- q2(x)", "comment": "test comment2",
"name": "test name2"}]
}
response = self.admin_manager.congress_client.create_library_policy(
test_policy)
policy_id = response['id']
test_policy['id'] = policy_id
def delete_if_found(id_):
try:
self.admin_manager.congress_client.delete_library_policy(id_)
except exceptions.NotFound:
pass
self.addCleanup(delete_if_found, policy_id)
response = self.admin_manager.congress_client.list_library_policy()
new_state = response['results']
self.assertEqual(len(initial_state) + 1, len(new_state),
'new library policy not reflected in list results')
self.assertIn(test_policy, new_state,
'new library policy not reflected in list results')
self.admin_manager.congress_client.delete_library_policy(policy_id)
response = self.admin_manager.congress_client.list_library_policy()
new_state = response['results']
self.assertEqual(len(initial_state), len(new_state),
'library policy delete not reflected in list results')
self.assertNotIn(test_policy, new_state,
'library policy delete not reflected in list results')
class TestCongressDataSources(manager_congress.ScenarioPolicyBase):
@classmethod

View File

@ -0,0 +1,5 @@
---
prelude: >
upgrade:
- A new database table `librarypolicies` is added;
alembic migration scripts included.

View File

@ -4,6 +4,7 @@
Babel!=2.4.0,>=2.3.4 # BSD
eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT
jsonschema>=2.0.0,<3.0.0,!=2.5.0 # MIT
PuLP>=1.4.1 # MIT
keystoneauth1>=2.21.0 # Apache-2.0
keystonemiddleware>=4.12.0 # Apache-2.0