Adds common services plugin for audit support

This commit enables the event model/db, common services plugin
and extenstions for audit support of Tacker resources.

Implements: blueprint: audit-support

Change-Id: I8fe824d335917c07d4b51dd63effae5f33bf0c32
Co-Authored-By: Kanagaraj Manickam <mkr1481@gmail.com>
This commit is contained in:
vish 2016-06-06 19:03:38 +00:00 committed by vishwanath jayaraman
parent 30a1d2464b
commit e94357abac
11 changed files with 502 additions and 4 deletions

View File

@ -42,6 +42,7 @@ tacker.service_plugins =
dummy = tacker.tests.unit.dummy_plugin:DummyServicePlugin
vnfm = tacker.vm.plugin:VNFMPlugin
nfvo = tacker.nfvo.nfvo_plugin:NfvoPlugin
commonservices = tacker.plugins.common_services.common_services_plugin:CommonServicesPlugin
tacker.nfvo.vim.drivers =
openstack = tacker.nfvo.drivers.vim.openstack_driver:OpenStack_Driver
tacker.openstack.common.cache.backends =

View File

@ -40,7 +40,7 @@ core_opts = [
help=_("The API paste config file to use")),
cfg.StrOpt('api_extensions_path', default="",
help=_("The path for API extensions")),
cfg.ListOpt('service_plugins', default=['nfvo', 'vnfm'],
cfg.ListOpt('service_plugins', default=['nfvo', 'vnfm', 'commonservices'],
help=_("The service plugins Tacker will use")),
cfg.StrOpt('policy_file', default="policy.json",
help=_("The policy file to use")),

View File

View File

@ -0,0 +1,99 @@
# Copyright 2016 Brocade Communications System, 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.
import sqlalchemy as sa
from sqlalchemy.orm import exc as orm_exc
from oslo_log import log as logging
from tacker.common import log
from tacker.db import db_base
from tacker.db import model_base
from tacker.db import types
from tacker.extensions import common_services
from tacker import manager
LOG = logging.getLogger(__name__)
EVENT_ATTRIBUTES = ('id', 'resource_id', 'resource_type', 'resource_state',
'timestamp', 'event_type', 'event_details')
class Event(model_base.BASE):
id = sa.Column(sa.Integer, primary_key=True, nullable=False,
autoincrement=True)
resource_id = sa.Column(types.Uuid, nullable=False)
resource_state = sa.Column(sa.String(64), nullable=False)
resource_type = sa.Column(sa.String(64), nullable=False)
timestamp = sa.Column(sa.DateTime, nullable=False)
event_type = sa.Column(sa.String(64), nullable=False)
event_details = sa.Column(types.Json)
class CommonServicesPluginDb(common_services.CommonServicesPluginBase,
db_base.CommonDbMixin):
def __init__(self):
super(CommonServicesPluginDb, self).__init__()
@property
def _core_plugin(self):
return manager.TackerManager.get_plugin()
def _make_event_dict(self, event_db, fields=None):
res = dict((key, event_db[key]) for key in EVENT_ATTRIBUTES)
return self._fields(res, fields)
def _fields(self, resource, fields):
if fields:
return dict(((key, item) for key, item in resource.items()
if key in fields))
return resource
@log.log
def create_event(self, context, res_id, res_type, res_state, evt_type,
tstamp, details=""):
try:
with context.session.begin(subtransactions=True):
event_db = Event(
resource_id=res_id,
resource_type=res_type,
resource_state=res_state,
event_details=details,
event_type=evt_type,
timestamp=tstamp)
context.session.add(event_db)
except Exception as e:
LOG.exception(_("create event error: %s"), str(e))
raise common_services.EventCreationFailureException(
error_str=str(e))
return self._make_event_dict(event_db)
@log.log
def get_event(self, context, event_id, fields=None):
try:
events_db = self._get_by_id(context, Event, event_id)
except orm_exc.NoResultFound:
raise common_services.EventNotFoundException(evt_id=event_id)
return self._make_event_dict(events_db, fields)
@log.log
def get_events(self, context, filters=None, fields=None, sorts=None,
limit=None, marker_obj=None, page_reverse=False):
return self._get_collection(context, Event, self._make_event_dict,
filters, fields, sorts, limit,
marker_obj, page_reverse)

View File

@ -0,0 +1,45 @@
# Copyright 2016 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.
#
"""audit_support_events
Revision ID: 4ee19c8a6d0a
Revises: acf941e54075
Create Date: 2016-06-07 03:16:53.513392
"""
# revision identifiers, used by Alembic.
revision = '4ee19c8a6d0a'
down_revision = '941b5a6fff9e'
from alembic import op
import sqlalchemy as sa
from tacker.db import types
def upgrade(active_plugins=None, options=None):
op.create_table('events',
sa.Column('id', sa.Integer, nullable=False, autoincrement=True),
sa.Column('resource_id', types.Uuid, nullable=False),
sa.Column('resource_state', sa.String(64), nullable=False),
sa.Column('resource_type', sa.String(64), nullable=False),
sa.Column('event_type', sa.String(64), nullable=False),
sa.Column('timestamp', sa.DateTime, nullable=False),
sa.Column('event_details', types.Json),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)

View File

@ -1,2 +1 @@
941b5a6fff9e
4ee19c8a6d0a

View File

@ -0,0 +1,145 @@
# Copyright 2016 Brocade Communications Systems 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.
import abc
import six
from tacker.api import extensions
from tacker.api.v1 import attributes as attr
from tacker.api.v1 import resource_helper
from tacker.common import exceptions
from tacker.plugins.common import constants
from tacker.services import service_base
class EventCreationFailureException(exceptions.TackerException):
message = _("Failed to create an event: %(error_str)s")
class EventNotFoundException(exceptions.TackerException):
message = _("Specified Event id %(evt_id)s is invalid. Please verify and "
"pass a valid Event id")
class InvalidModelException(exceptions.TackerException):
message = _("Specified model is invalid, only Event model supported")
RESOURCE_ATTRIBUTE_MAP = {
'events': {
'id': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'resource_id': {
'allow_post': False,
'allow_put': False,
'is_visible': True
},
'resource_type': {
'allow_post': False,
'allow_put': False,
'is_visible': True
},
'resource_state': {
'allow_post': False,
'allow_put': False,
'is_visible': True
},
'timestamp': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'event_details': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
'event_type': {
'allow_post': False,
'allow_put': False,
'is_visible': True,
},
}
}
class Common_services(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return 'COMMONSERVICES'
@classmethod
def get_alias(cls):
return 'Commonservices'
@classmethod
def get_description(cls):
return "Extension for CommonServices"
@classmethod
def get_namespace(cls):
return 'http://wiki.openstack.org/Tacker'
@classmethod
def get_updated(cls):
return "2016-06-06T13:00:00-00:00"
@classmethod
def get_resources(cls):
special_mappings = {}
plural_mappings = resource_helper.build_plural_mappings(
special_mappings, RESOURCE_ATTRIBUTE_MAP)
attr.PLURALS.update(plural_mappings)
return resource_helper.build_resource_info(
plural_mappings, RESOURCE_ATTRIBUTE_MAP, constants.COMMONSERVICES,
translate_name=True)
@classmethod
def get_plugin_interface(cls):
return CommonServicesPluginBase
def update_attributes_map(self, attributes):
super(Common_services, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
version_map = {'1.0': RESOURCE_ATTRIBUTE_MAP}
return version_map.get(version, {})
@six.add_metaclass(abc.ABCMeta)
class CommonServicesPluginBase(service_base.NFVPluginBase):
def get_plugin_name(self):
return constants.COMMONSERVICES
def get_plugin_type(self):
return constants.COMMONSERVICES
def get_plugin_description(self):
return 'Tacker CommonServices plugin'
@abc.abstractmethod
def get_event(self, context, event_id, fields=None):
pass
@abc.abstractmethod
def get_events(self, context, filters=None, fields=None, sorts=None,
limit=None, marker_obj=None, page_reverse=False):
pass

View File

@ -18,12 +18,14 @@ CORE = "CORE"
DUMMY = "DUMMY"
VNFM = "VNFM"
NFVO = "NFVO"
COMMONSERVICES = "COMMONSERVICES"
COMMON_PREFIXES = {
CORE: "",
DUMMY: "/dummy_svc",
VNFM: "",
NFVO: ""
NFVO: "",
COMMONSERVICES: ""
}
# Service operation status constants

View File

@ -0,0 +1,48 @@
# Copyright 2016 Brocade Communications System, 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 oslo_log import log as logging
from tacker.common import log
from tacker.db.common_services import common_services_db
LOG = logging.getLogger(__name__)
class CommonServicesPlugin(common_services_db.CommonServicesPluginDb):
"""Reference plugin for COMMONSERVICES extension
Implements the COMMONSERVICES extension and defines public facing APIs for
common utility operations.
"""
supported_extension_aliases = ['CommonServices']
def __init__(self):
super(CommonServicesPlugin, self).__init__()
@log.log
def get_event(self, context, event_id, fields=None):
return super(CommonServicesPlugin, self).get_event(context, event_id,
fields)
@log.log
def get_events(self, context, filters=None, fields=None, sorts=None,
limit=None, marker_obj=None, page_reverse=False):
return super(CommonServicesPlugin, self).get_events(context, filters,
fields, sorts, limit,
marker_obj,
page_reverse)

View File

@ -0,0 +1,159 @@
# Copyright 2016 Brocade Communications System, 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.
import mock
from oslo_utils import timeutils
from tacker import context
from tacker.db.common_services import common_services_db
from tacker.extensions import common_services
from tacker.plugins.common_services import common_services_plugin
from tacker.tests.unit.db import base as db_base
class TestCommonServicesPlugin(db_base.SqlTestCase):
def setUp(self):
super(TestCommonServicesPlugin, self).setUp()
self.addCleanup(mock.patch.stopall)
self.context = context.get_admin_context()
self.event_db_plugin = common_services_db.CommonServicesPluginDb()
self.coreutil_plugin = common_services_plugin.CommonServicesPlugin()
def _get_dummy_event_obj(self):
return {
'resource_id': '6261579e-d6f3-49ad-8bc3-a9cb974778ff',
'resource_state': 'ACTIVE',
'resource_type': 'VNF',
'event_details': '',
'event_type': 'scale_up',
'timestamp': timeutils.parse_strtime('2016-07-20T05:43:52.765172')
}
def test_create_event(self):
evt_obj = self._get_dummy_event_obj()
result = self.event_db_plugin.create_event(self.context,
evt_obj['resource_id'],
evt_obj['resource_type'],
evt_obj['resource_state'],
evt_obj['event_type'],
evt_obj['timestamp'],
evt_obj['event_details'])
self.assertIsNotNone(result)
self.assertIn('id', result)
self.assertIn('resource_id', result)
self.assertIn('resource_state', result)
self.assertIn('resource_type', result)
self.assertIn('event_type', result)
self.assertIn('event_details', result)
self.assertIn('timestamp', result)
def test_event_not_found(self):
self.assertRaises(common_services.EventNotFoundException,
self.coreutil_plugin.get_event, self.context, '99')
def test_InvalidModelInputExceptionNotThrown(self):
evt_obj = self._get_dummy_event_obj()
result = self.event_db_plugin.create_event(self.context,
evt_obj['resource_id'],
evt_obj['resource_type'],
evt_obj['resource_state'],
evt_obj['event_type'],
evt_obj['timestamp'],
evt_obj['event_details'])
try:
self.coreutil_plugin.get_event(self, context, str(result['id']))
except common_services.InvalidModelException:
self.assertTrue(False)
except Exception:
self.assertTrue(True)
def test_get_event_by_id(self):
evt_obj = self._get_dummy_event_obj()
evt_created = self.event_db_plugin.create_event(
self.context, evt_obj['resource_id'],
evt_obj['resource_type'],
evt_obj['resource_state'],
evt_obj['event_type'],
evt_obj['timestamp'],
evt_obj['event_details'])
self.assertIsNotNone(evt_created)
evt_get = self.coreutil_plugin.get_event(self.context,
evt_created['id'])
self.assertEqual(evt_created['resource_id'], evt_get['resource_id'])
self.assertEqual(evt_created['resource_state'],
evt_get['resource_state'])
self.assertEqual(evt_created['resource_type'],
evt_get['resource_type'])
self.assertEqual(evt_created['event_type'], evt_get['event_type'])
self.assertEqual(evt_created['event_details'],
evt_get['event_details'])
self.assertEqual(evt_created['timestamp'], evt_get['timestamp'])
def test_get_events(self):
evt_obj = self._get_dummy_event_obj()
self.event_db_plugin.create_event(self.context,
evt_obj['resource_id'],
evt_obj['resource_type'],
evt_obj['resource_state'],
evt_obj['event_type'],
evt_obj['timestamp'],
evt_obj['event_details'])
result = self.coreutil_plugin.get_events(self.context)
self.assertTrue(len(result))
def test_get_events_filtered_invalid_id(self):
evt_obj = self._get_dummy_event_obj()
self.event_db_plugin.create_event(self.context,
evt_obj['resource_id'],
evt_obj['resource_type'],
evt_obj['resource_state'],
evt_obj['event_type'],
evt_obj['timestamp'],
evt_obj['event_details'])
result = self.coreutil_plugin.get_events(self.context, {'id': 'xyz'})
self.assertFalse(len(result))
def test_get_events_filtered_valid_id(self):
evt_obj = self._get_dummy_event_obj()
self.event_db_plugin.create_event(self.context,
evt_obj['resource_id'],
evt_obj['resource_type'],
evt_obj['resource_state'],
evt_obj['event_type'],
evt_obj['timestamp'],
evt_obj['event_details'])
result = self.coreutil_plugin.get_events(self.context, {'id': '1'})
self.assertTrue(len(result))
def test_get_events_valid_fields(self):
evt_obj = self._get_dummy_event_obj()
self.event_db_plugin.create_event(self.context,
evt_obj['resource_id'],
evt_obj['resource_type'],
evt_obj['resource_state'],
evt_obj['event_type'],
evt_obj['timestamp'],
evt_obj['event_details'])
result = self.coreutil_plugin.get_events(self.context, {'id': '1'},
['id', 'event_type'])
self.assertTrue(len(result))
self.assertIn('id', result[0])
self.assertNotIn('resource_id', result[0])
self.assertNotIn('resource_state', result[0])
self.assertNotIn('resource_type', result[0])
self.assertIn('event_type', result[0])
self.assertNotIn('event_details', result[0])
self.assertNotIn('timestamp', result[0])