barbican/barbican/tests/tasks/test_keystone_consumer.py

337 lines
13 KiB
Python

# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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
import sqlalchemy
from barbican.common import exception
from barbican.common import resources as c_resources
from barbican.model import models
from barbican.model import repositories as rep
from barbican.plugin.crypto import manager
from barbican.plugin import resources as plugin
from barbican.tasks import keystone_consumer as consumer
from barbican.tests import database_utils
from oslo_utils import uuidutils
class InitializeDatabaseMixin(object):
def _init_memory_db_setup(self):
# Force a refresh of the singleton plugin manager for each test.
manager._PLUGIN_MANAGER = None
manager.CONF.set_override('enabled_crypto_plugins',
['simple_crypto'],
group='crypto',
enforce_type=True)
self.project_id1 = uuidutils.generate_uuid()
self.project_id2 = uuidutils.generate_uuid(dashed=False)
self.project1_data = c_resources.get_or_create_project(
self.project_id1)
self.assertIsNotNone(self.project1_data)
self.project2_data = c_resources.get_or_create_project(
self.project_id2)
self.assertIsNotNone(self.project2_data)
def _create_secret_for_project(self, project_data):
secret_info = {"name": uuidutils.generate_uuid(dashed=False),
"algorithm": "aes", "bit_length": 256, "mode": "cbc",
"payload_content_type": "application/octet-stream"}
new_secret = plugin.generate_secret(
secret_info, secret_info.get('payload_content_type'), project_data)
return new_secret
class WhenUsingKeystoneEventConsumer(
database_utils.RepositoryTestCase,
InitializeDatabaseMixin):
"""Test all but the process() method on KeystoneEventConsumer class.
For unit testing the process() method, use the
WhenUsingKeystoneEventConsumerProcessMethod class.
"""
def setUp(self):
super(WhenUsingKeystoneEventConsumer, self).setUp()
self.kek_repo = rep.get_kek_datum_repository()
self.project_repo = rep.get_project_repository()
self.secret_meta_repo = rep.get_secret_meta_repository()
self.secret_repo = rep.get_secret_repository()
self.transport_key_repo = rep.get_transport_key_repository()
def test_get_project_entities_lookup_call(self):
self._init_memory_db_setup()
secret = self._create_secret_for_project(self.project2_data)
project2_id = self.project2_data.id
self.assertIsNotNone(secret)
db_secrets = self.secret_repo.get_project_entities(project2_id)
self.assertEqual(1, len(db_secrets))
self.assertEqual(secret.id, db_secrets[0].id)
db_kek = self.kek_repo.get_project_entities(project2_id)
self.assertEqual(1, len(db_kek))
# secret_meta_repo does not implement function
# _build_get_project_entities_query, so it should raise error
self.assertRaises(NotImplementedError,
self.secret_meta_repo.get_project_entities,
project2_id)
# transport_key_repo does not implement function
# _build_get_project_entities_query, so it should raise error
self.assertRaises(NotImplementedError,
self.transport_key_repo.get_project_entities,
project2_id)
@mock.patch.object(models.Project, 'delete',
side_effect=sqlalchemy.exc.SQLAlchemyError)
def test_delete_project_entities_alchemy_error_suppress_exception_true(
self, mock_entity_delete):
self._init_memory_db_setup()
secret = self._create_secret_for_project(self.project1_data)
self.assertIsNotNone(secret)
project1_id = self.project1_data.id
# sqlalchemy error is suppressed here
no_error = self.project_repo.delete_project_entities(
project1_id, suppress_exception=True)
self.assertIsNone(no_error)
@mock.patch.object(models.Project, 'delete',
side_effect=sqlalchemy.exc.SQLAlchemyError)
def test_delete_project_entities_alchemy_error_suppress_exception_false(
self, mock_entity_delete):
self._init_memory_db_setup()
secret = self._create_secret_for_project(self.project1_data)
self.assertIsNotNone(secret)
project1_id = self.project1_data.id
# sqlalchemy error is not suppressed here
self.assertRaises(exception.BarbicanException,
self.project_repo.delete_project_entities,
project1_id, suppress_exception=False)
def test_delete_project_entities_not_impl_error_suppress_exception_true(
self):
self._init_memory_db_setup()
secret = self._create_secret_for_project(self.project1_data)
self.assertIsNotNone(secret)
project1_id = self.project1_data.id
# NotImplementedError is not suppressed regardless of related flag
self.assertRaises(NotImplementedError,
self.secret_meta_repo.delete_project_entities,
project1_id, suppress_exception=True)
def test_delete_project_entities_not_impl_error_suppress_exception_false(
self):
self._init_memory_db_setup()
secret = self._create_secret_for_project(self.project1_data)
self.assertIsNotNone(secret)
project1_id = self.project1_data.id
# NotImplementedError is not suppressed regardless of related flag
self.assertRaises(NotImplementedError,
self.secret_meta_repo.delete_project_entities,
project1_id, suppress_exception=False)
def test_invoke_handle_error(self):
task = consumer.KeystoneEventConsumer()
project = mock.MagicMock()
project.project_id = 'project_id'
status = 'status'
message = 'message'
exception_test = ValueError('Abort!')
resource_type = 'type'
operation_type = 'operation'
task.handle_error(
project, status, message, exception_test, project_id=None,
resource_type=resource_type, operation_type=operation_type)
class WhenUsingKeystoneEventConsumerProcessMethod(
database_utils.RepositoryTestCase,
InitializeDatabaseMixin):
"""Test only the process() method on KeystoneEventConsumer class.
For unit testing all but the process() method, use the
WhenUsingKeystoneEventConsumer class.
"""
def setUp(self):
super(WhenUsingKeystoneEventConsumerProcessMethod, self).setUp()
# Override the database start function as repositories.start() is
# already invoked by the RepositoryTestCase base class setUp().
# Similarly, override the clear function.
self.task = consumer.KeystoneEventConsumer(
db_start=mock.MagicMock(),
db_clear=mock.MagicMock()
)
def test_project_entities_cleanup_for_no_matching_barbican_project(self):
self._init_memory_db_setup()
result = self.task.process(project_id=self.project_id1,
resource_type='project',
operation_type='deleted')
self.assertIsNone(result, 'No return is expected as result')
def test_project_entities_cleanup_for_missing_barbican_project(self):
self._init_memory_db_setup()
result = self.task.process(project_id=None,
resource_type='project',
operation_type='deleted')
self.assertIsNone(result, 'No return is expected as result')
@mock.patch.object(consumer.KeystoneEventConsumer, 'handle_success')
def test_existing_project_entities_cleanup_for_plain_secret(
self, mock_handle_success):
self._init_memory_db_setup()
secret = self._create_secret_for_project(self.project1_data)
self.assertIsNotNone(secret)
secret_id = secret.id
project1_id = self.project1_data.id
secret_repo = rep.get_secret_repository()
db_secrets = secret_repo.get_project_entities(project1_id)
self.assertEqual(1, len(db_secrets))
self.assertEqual(secret.id, db_secrets[0].id)
# Get secret_store_metadata for related secret
self.assertGreater(len(db_secrets[0].secret_store_metadata), 0)
secret_metadata_id = list(db_secrets[0].
secret_store_metadata.values())[0].id
self.assertIsNotNone(secret_metadata_id)
# Get db entry for secret_store_metadata by id to make sure its
# presence before removing via delete project task
secret_meta_repo = rep.get_secret_meta_repository()
db_secret_store_meta = secret_meta_repo.get(
entity_id=secret_metadata_id)
self.assertIsNotNone(db_secret_store_meta)
kek_repo = rep.get_kek_datum_repository()
db_kek = kek_repo.get_project_entities(project1_id)
self.assertEqual(1, len(db_kek))
# task = consumer.KeystoneEventConsumer()
result = self.task.process(project_id=self.project_id1,
resource_type='project',
operation_type='deleted')
self.assertIsNone(result, 'No return is expected as result')
mock_handle_success.assert_has_calls([])
_, kwargs = mock_handle_success.call_args
self.assertEqual(self.project_id1, kwargs['project_id'])
self.assertEqual('project', kwargs['resource_type'])
self.assertEqual('deleted', kwargs['operation_type'])
# After project entities delete, make sure secret is not found
ex = self.assertRaises(exception.NotFound, secret_repo.get,
entity_id=secret_id,
external_project_id=self.project_id1)
self.assertIn(secret_id, str(ex))
# After project entities delete, make sure kek data is not found
entities = kek_repo.get_project_entities(project1_id)
self.assertEqual(0, len(entities))
project_repo = rep.get_project_repository()
db_project = project_repo.get_project_entities(project1_id)
self.assertEqual(0, len(db_project))
# Should have deleted SecretStoreMetadatum via children delete
self.assertRaises(exception.NotFound,
secret_meta_repo.get,
entity_id=secret_metadata_id)
@mock.patch.object(consumer.KeystoneEventConsumer, 'handle_error')
@mock.patch.object(rep.ProjectRepo, 'delete_project_entities',
side_effect=exception.BarbicanException)
def test_rollback_with_error_during_project_cleanup(self, mock_delete,
mock_handle_error):
self._init_memory_db_setup()
secret = self._create_secret_for_project(self.project1_data)
self.assertIsNotNone(secret)
secret_id = secret.id
project1_id = self.project1_data.id
secret_repo = rep.get_secret_repository()
db_secrets = secret_repo.get_project_entities(project1_id)
self.assertEqual(1, len(db_secrets))
self.assertEqual(secret.id, db_secrets[0].id)
kek_repo = rep.get_kek_datum_repository()
db_kek = kek_repo.get_project_entities(project1_id)
self.assertEqual(1, len(db_kek))
# Commit changes made so far before creating rollback scenario
rep.commit()
handle_error_mock = mock.MagicMock()
self.task.handler_error = handle_error_mock
self.assertRaises(exception.BarbicanException,
self.task.process, project_id=self.project_id1,
resource_type='project', operation_type='deleted')
mock_handle_error.assert_called_once_with(
self.project1_data,
500,
mock.ANY,
mock.ANY,
operation_type='deleted',
project_id=mock.ANY,
resource_type='project',
)
args, kwargs = mock_handle_error.call_args
self.assertEqual(500, args[1])
self.assertEqual(self.project_id1, kwargs['project_id'])
self.assertEqual('project', kwargs['resource_type'])
self.assertEqual('deleted', kwargs['operation_type'])
# Make sure entities are still present after rollback
db_secrets = secret_repo.get_project_entities(project1_id)
self.assertEqual(1, len(db_secrets))
self.assertEqual(secret_id, db_secrets[0].id)
db_kek = kek_repo.get_project_entities(project1_id)
self.assertEqual(1, len(db_kek))
project_repo = rep.get_project_repository()
db_project = project_repo.get_project_entities(project1_id)
self.assertEqual(1, len(db_project))