Merge "Create unit tests for the identity sync services in dcorch"
This commit is contained in:
commit
7a61ae8b5d
@ -1029,7 +1029,7 @@ class IdentitySyncThread(SyncThread):
|
|||||||
update_role(sc_role_id, role_records)
|
update_role(sc_role_id, role_records)
|
||||||
if not role_ref:
|
if not role_ref:
|
||||||
LOG.error("No role data returned when updating role {} in"
|
LOG.error("No role data returned when updating role {} in"
|
||||||
" subcloud.".format(role_id), extra=self.log_extra)
|
" subcloud.".format(sc_role_id), extra=self.log_extra)
|
||||||
raise exceptions.SyncRequestFailed
|
raise exceptions.SyncRequestFailed
|
||||||
|
|
||||||
# Persist the subcloud resource.
|
# Persist the subcloud resource.
|
||||||
@ -1349,7 +1349,7 @@ class IdentitySyncThread(SyncThread):
|
|||||||
get('revocation_event').get('audit_id')
|
get('revocation_event').get('audit_id')
|
||||||
subcloud_rsrc_id = self.\
|
subcloud_rsrc_id = self.\
|
||||||
persist_db_subcloud_resource(rsrc.id, revoke_event_ref_id)
|
persist_db_subcloud_resource(rsrc.id, revoke_event_ref_id)
|
||||||
LOG.info("Created Keystone token revoke event {}:{}"
|
LOG.info("Created Keystone token revocation event {}:{}"
|
||||||
.format(rsrc.id, subcloud_rsrc_id),
|
.format(rsrc.id, subcloud_rsrc_id),
|
||||||
extra=self.log_extra)
|
extra=self.log_extra)
|
||||||
|
|
||||||
@ -1424,7 +1424,7 @@ class IdentitySyncThread(SyncThread):
|
|||||||
|
|
||||||
subcloud_rsrc_id = self.persist_db_subcloud_resource(rsrc.id,
|
subcloud_rsrc_id = self.persist_db_subcloud_resource(rsrc.id,
|
||||||
event_id)
|
event_id)
|
||||||
LOG.info("Created Keystone token revoke event {}:{}"
|
LOG.info("Created Keystone token revocation event {}:{}"
|
||||||
.format(rsrc.id, subcloud_rsrc_id),
|
.format(rsrc.id, subcloud_rsrc_id),
|
||||||
extra=self.log_extra)
|
extra=self.log_extra)
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ from oslo_db import options
|
|||||||
from oslotest import base
|
from oslotest import base
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
|
from dcmanager.rpc import client as dcmanager_rpc_client
|
||||||
from dcorch.db import api
|
from dcorch.db import api
|
||||||
from dcorch.db.sqlalchemy import api as db_api
|
from dcorch.db.sqlalchemy import api as db_api
|
||||||
from dcorch.rpc import client as rpc_client
|
from dcorch.rpc import client as rpc_client
|
||||||
@ -84,8 +85,24 @@ class OrchestratorTestCase(base.BaseTestCase):
|
|||||||
self.mock_rpc_client = mock_patch.start()
|
self.mock_rpc_client = mock_patch.start()
|
||||||
self.addCleanup(mock_patch.stop)
|
self.addCleanup(mock_patch.stop)
|
||||||
|
|
||||||
def _mock_openstack_driver(self, target):
|
def _mock_rpc_client_subcloud_state_client(self):
|
||||||
mock_patch = mock.patch.object(target, 'OpenStackDriver')
|
mock_patch = mock.patch.object(dcmanager_rpc_client, 'SubcloudStateClient')
|
||||||
|
self.rpc_client_subcloud_state_client = mock_patch.start()
|
||||||
|
self.addCleanup(mock_patch.stop)
|
||||||
|
|
||||||
|
def _mock_rpc_client_manager(self):
|
||||||
|
mock_patch = mock.patch.object(dcmanager_rpc_client, 'ManagerClient')
|
||||||
|
self.rpc_client_manager = mock_patch.start()
|
||||||
|
self.addCleanup(mock_patch.stop)
|
||||||
|
|
||||||
|
def _mock_log(self, target):
|
||||||
|
mock_patch = mock.patch.object(target, 'LOG')
|
||||||
|
self.log = mock_patch.start()
|
||||||
|
self.addCleanup(mock_patch.stop)
|
||||||
|
|
||||||
|
def _mock_openstack_driver(self):
|
||||||
|
mock_patch = \
|
||||||
|
mock.patch('dccommon.drivers.openstack.sdk_platform.OpenStackDriver')
|
||||||
self.mock_openstack_driver = mock_patch.start()
|
self.mock_openstack_driver = mock_patch.start()
|
||||||
self.addCleanup(mock_patch.stop)
|
self.addCleanup(mock_patch.stop)
|
||||||
|
|
||||||
|
@ -0,0 +1,366 @@
|
|||||||
|
# Copyright (c) 2024 Wind River Systems, Inc.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from keystoneauth1 import exceptions as keystone_exceptions
|
||||||
|
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
import dcorch.common.exceptions as exceptions
|
||||||
|
|
||||||
|
from dcdbsync.dbsyncclient import exceptions as dbsync_exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMixin(object):
|
||||||
|
"""Base mixin class to declare common methods for generic resource requests"""
|
||||||
|
|
||||||
|
def _get_request(self):
|
||||||
|
"""Returns the request object"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_rsrc(self):
|
||||||
|
"""Returns the rsrc object"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_log(self):
|
||||||
|
"""Returns the log object"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_subcloud(self):
|
||||||
|
"""Returns the subcloud object"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_subcloud_resource(self):
|
||||||
|
"""Returns the subcloud resouce object"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_resource_name(self):
|
||||||
|
"""Returns the resource name"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_resource_ref(self):
|
||||||
|
"""Returns the resource ref mock"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _get_resource_ref_name(self):
|
||||||
|
"""Returns the resource ref name path"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _resource_add(self):
|
||||||
|
"""Returns the resource's add method"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _resource_detail(self):
|
||||||
|
"""Returns the resource's detail method"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _resource_update(self):
|
||||||
|
"""Returns the resource's update method"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _resource_keystone_update(self):
|
||||||
|
"""Returns the resource's update method from Keystone"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _resource_keystone_delete(self):
|
||||||
|
"""Returns the resource's delete method from Keystone"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _execute(self):
|
||||||
|
"""Executes the method"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _execute_and_assert_exception(self, exception):
|
||||||
|
"""Executes the method"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _assert_log(self, level, message, extra=mock.ANY):
|
||||||
|
"""Asserts the log's call"""
|
||||||
|
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class PostResourceMixin(BaseMixin):
|
||||||
|
"""Base mixin class for post requests to a resource"""
|
||||||
|
|
||||||
|
def test_post_succeeds(self):
|
||||||
|
"""Test post succeeds"""
|
||||||
|
|
||||||
|
self._resource_add().return_value = self._get_resource_ref()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_detail().assert_called_once()
|
||||||
|
self._resource_add().assert_called_once()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Created Keystone {self._get_resource_name()} "
|
||||||
|
f"{self._get_rsrc().id}:"
|
||||||
|
f"{self._get_resource_ref().get(self._get_resource_name()).get('id')} "
|
||||||
|
f"[{self._get_resource_ref_name()}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_source_resource_id(self):
|
||||||
|
"""Test post fails without source resource id"""
|
||||||
|
|
||||||
|
self._get_request().orch_job.source_resource_id = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Received {self._get_resource_name()} create request "
|
||||||
|
"without required 'source_resource_id' field"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_with_dbsync_unauthorized_exception(self):
|
||||||
|
"""Test post fails with dbsync unauthorized exception"""
|
||||||
|
|
||||||
|
self._resource_detail().side_effect = dbsync_exceptions.Unauthorized()
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(dbsync_exceptions.UnauthorizedMaster)
|
||||||
|
|
||||||
|
self._resource_detail().assert_called_once()
|
||||||
|
self._resource_add().assert_not_called()
|
||||||
|
|
||||||
|
def test_post_fails_with_empty_resource_ref(self):
|
||||||
|
"""Test post fails with empty resource ref"""
|
||||||
|
|
||||||
|
self._resource_add().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"No {self._get_resource_name()} data returned when creating "
|
||||||
|
f"{self._get_resource_name()} "
|
||||||
|
f"{self._get_request().orch_job.source_resource_id} in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_resource_records(self):
|
||||||
|
"""Test post fails without resource records"""
|
||||||
|
|
||||||
|
self._resource_detail().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "No data retrieved from master cloud for "
|
||||||
|
f"{self._get_resource_name()} "
|
||||||
|
f"{self._get_request().orch_job.source_resource_id} to create its "
|
||||||
|
"equivalent in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PutResourceMixin(BaseMixin):
|
||||||
|
"""Base mixin class for put requests to a resource"""
|
||||||
|
|
||||||
|
def test_put_succeeds(self):
|
||||||
|
"""Test put succeeds"""
|
||||||
|
|
||||||
|
self._resource_update().return_value = self._get_resource_ref()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_detail().assert_called_once()
|
||||||
|
self._resource_update().assert_called_once()
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Updated Keystone {self._get_resource_name()} {self.rsrc.id}:"
|
||||||
|
f"{self._get_resource_ref().get(self._get_resource_name()).get('id')} "
|
||||||
|
f"[{self._get_resource_ref_name()}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_put_fails_without_source_resource_id(self):
|
||||||
|
"""Test put fails without source resource id"""
|
||||||
|
|
||||||
|
self._get_request().orch_job.source_resource_id = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Received {self._get_resource_name()} update request "
|
||||||
|
"without required source resource id"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_put_fails_without_id_in_resource_info(self):
|
||||||
|
"""Test put fails without id in resource info"""
|
||||||
|
|
||||||
|
print(f"{{{self._get_resource_name()}: {{}}}}")
|
||||||
|
self._get_request().orch_job.resource_info = \
|
||||||
|
f'{{"{self._get_resource_name()}": {{}}}}'
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Received {self._get_resource_name()} update request "
|
||||||
|
"without required subcloud resource id"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_put_fails_with_dbsync_unauthorized_exception(self):
|
||||||
|
"""Test put fails with dbsync unauthorized exception"""
|
||||||
|
|
||||||
|
self._resource_detail().side_effect = dbsync_exceptions.Unauthorized
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(dbsync_exceptions.UnauthorizedMaster)
|
||||||
|
|
||||||
|
self._resource_detail().assert_called_once()
|
||||||
|
self._resource_update().assert_not_called()
|
||||||
|
|
||||||
|
def test_put_fails_without_resource_records(self):
|
||||||
|
"""Test put fails without resource records"""
|
||||||
|
|
||||||
|
self._resource_detail().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "No data retrieved from master cloud for "
|
||||||
|
f"{self._get_resource_name()} "
|
||||||
|
f"{self._get_request().orch_job.source_resource_id} "
|
||||||
|
"to update its equivalent in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_put_fails_without_resource_ref(self):
|
||||||
|
"""Test put fails without resource ref"""
|
||||||
|
|
||||||
|
self._resource_update().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"No {self._get_resource_name()} data returned when updating "
|
||||||
|
f"{self._get_resource_name()} "
|
||||||
|
f"{self._get_resource_ref().get(self._get_resource_name()).get('id')} "
|
||||||
|
"in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PatchResourceMixin(BaseMixin):
|
||||||
|
"""Base mixin class for patch requests to a resource"""
|
||||||
|
|
||||||
|
def test_patch_succeeds(self):
|
||||||
|
"""Test patch succeeds"""
|
||||||
|
|
||||||
|
mock_update = mock.Mock()
|
||||||
|
mock_update.id = self._get_subcloud_resource().subcloud_resource_id
|
||||||
|
self._resource_keystone_update().return_value = mock_update
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_keystone_update().assert_called_once()
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Updated Keystone {self._get_resource_name()}: "
|
||||||
|
f"{self._get_rsrc().id}:{mock_update.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_patch_fails_with_empty_resource_update_dict(self):
|
||||||
|
"""Test patch fails with empty resource update dict"""
|
||||||
|
|
||||||
|
self._get_request().orch_job.resource_info = "{}"
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Received {self._get_resource_name()} update request "
|
||||||
|
"without any update fields"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_patch_fails_without_resource_subcloud_rsrc(self):
|
||||||
|
"""Test patch fails with empty resource update dict
|
||||||
|
|
||||||
|
When the resource id and subcloud id does not match to a subcloud resource,
|
||||||
|
the resource subcloud rsrc is not found
|
||||||
|
"""
|
||||||
|
|
||||||
|
loaded_resource_info = \
|
||||||
|
jsonutils.loads(self._get_request().orch_job.resource_info)
|
||||||
|
|
||||||
|
self._get_rsrc().id = 9999
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Unable to update {self._get_resource_name()} reference "
|
||||||
|
f"{self.rsrc}:{loaded_resource_info[self._get_resource_name()]}, cannot "
|
||||||
|
f"find equivalent Keystone {self._get_resource_name()} in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_patch_fails_with_resource_ref_id_not_equal_resource_id(self):
|
||||||
|
"""Test patch fails with resource ref id not equal resource id"""
|
||||||
|
|
||||||
|
mock_update = mock.Mock()
|
||||||
|
mock_update.id = 9999
|
||||||
|
self._resource_keystone_update().return_value = mock_update
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Unable to update Keystone {self._get_resource_name()} "
|
||||||
|
f"{self._get_rsrc().id}:"
|
||||||
|
f"{self._get_subcloud_resource().subcloud_resource_id} for subcloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteResourceMixin(BaseMixin):
|
||||||
|
"""Base mixin class for delete requests to a resource"""
|
||||||
|
|
||||||
|
def test_delete_succeeds(self):
|
||||||
|
"""Test delete succeeds"""
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_keystone_delete().assert_called_once()
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Keystone {self._get_resource_name()} {self._get_rsrc().id}:"
|
||||||
|
f"{self._get_subcloud_resource().id} "
|
||||||
|
f"[{self._get_subcloud_resource().subcloud_resource_id}] deleted"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_succeeds_with_keystone_not_found_exception(self):
|
||||||
|
"""Test delete succeeds with keystone's not found exception"""
|
||||||
|
|
||||||
|
self._resource_keystone_delete().side_effect = keystone_exceptions.NotFound()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_keystone_delete().assert_called_once()
|
||||||
|
self._get_log().assert_has_calls([
|
||||||
|
mock.call.info(
|
||||||
|
f"Delete {self._get_resource_name()}: {self._get_resource_name()} "
|
||||||
|
f"{self._get_subcloud_resource().subcloud_resource_id} "
|
||||||
|
f"not found in {self._get_subcloud().region_name}, "
|
||||||
|
"considered as deleted.", extra=mock.ANY
|
||||||
|
),
|
||||||
|
mock.call.info(
|
||||||
|
f"Keystone {self._get_resource_name()} {self._get_rsrc().id}:"
|
||||||
|
f"{self._get_subcloud_resource().id} "
|
||||||
|
f"[{self._get_subcloud_resource().subcloud_resource_id}] deleted",
|
||||||
|
extra=mock.ANY
|
||||||
|
)],
|
||||||
|
any_order=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_fails_without_resource_subcloud_rsrc(self):
|
||||||
|
"""Test delete fails without resource subcloud rsrc
|
||||||
|
|
||||||
|
When the resource id and subcloud id does not match to a subcloud resource,
|
||||||
|
the user subcloud rsrc is not found
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._get_rsrc().id = 9999
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Unable to delete {self._get_resource_name()} reference "
|
||||||
|
f"{self._get_rsrc()}, cannot find equivalent Keystone "
|
||||||
|
f"{self._get_resource_name()} in subcloud."
|
||||||
|
)
|
@ -0,0 +1,921 @@
|
|||||||
|
# Copyright (c) 2024 Wind River Systems, Inc.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from keystoneauth1 import exceptions as keystone_exceptions
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
|
|
||||||
|
from dccommon import consts as dccommon_consts
|
||||||
|
from dcdbsync.dbsyncclient import exceptions as dbsync_exceptions
|
||||||
|
import dcorch.common.exceptions as exceptions
|
||||||
|
import dcorch.db.api as db_api
|
||||||
|
import dcorch.engine.sync_services.identity as identity_service
|
||||||
|
import dcorch.objects.subcloud_resource as subcloud_resource
|
||||||
|
from dcorch.tests.base import OrchestratorTestCase
|
||||||
|
import dcorch.tests.unit.engine.sync_services.mixins as mixins
|
||||||
|
|
||||||
|
SOURCE_RESOURCE_ID = 2
|
||||||
|
RESOURCE_ID = 3
|
||||||
|
MASTER_ID = 4
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThread(OrchestratorTestCase, mixins.BaseMixin):
|
||||||
|
"""Base test class for IdentitySyncThread"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self._mock_openstack_driver()
|
||||||
|
self._mock_rpc_client_subcloud_state_client()
|
||||||
|
self._mock_rpc_client_manager()
|
||||||
|
self._mock_log(identity_service)
|
||||||
|
|
||||||
|
self._create_request_and_resource_mocks()
|
||||||
|
self._create_subcloud_and_subcloud_resource()
|
||||||
|
|
||||||
|
self.identity_sync_thread = identity_service.IdentitySyncThread(
|
||||||
|
self.subcloud.region_name
|
||||||
|
)
|
||||||
|
|
||||||
|
self.method = lambda *args: None
|
||||||
|
self.resource_name = ''
|
||||||
|
self.resource_ref = None
|
||||||
|
self.resource_ref_name = None
|
||||||
|
self.resource_add = lambda: None
|
||||||
|
self.resource_detail = lambda: None
|
||||||
|
self.resource_update = lambda: None
|
||||||
|
self.resource_keystone_update = lambda: None
|
||||||
|
self.resource_keystone_delete = lambda: None
|
||||||
|
|
||||||
|
def _create_request_and_resource_mocks(self):
|
||||||
|
self.request = mock.MagicMock()
|
||||||
|
self.request.orch_job.resource_info = f'{{\"id\": {RESOURCE_ID}}}'
|
||||||
|
self.request.orch_job.source_resource_id = SOURCE_RESOURCE_ID
|
||||||
|
|
||||||
|
self.rsrc = mock.MagicMock
|
||||||
|
self.rsrc.id = RESOURCE_ID
|
||||||
|
self.rsrc.master_id = MASTER_ID
|
||||||
|
|
||||||
|
def _create_subcloud_and_subcloud_resource(self):
|
||||||
|
values = {
|
||||||
|
'software_version': '10.04',
|
||||||
|
'management_state': dccommon_consts.MANAGEMENT_MANAGED,
|
||||||
|
'availability_status': dccommon_consts.AVAILABILITY_ONLINE,
|
||||||
|
'initial_sync_state': '',
|
||||||
|
'capabilities': {}
|
||||||
|
}
|
||||||
|
self.subcloud = db_api.subcloud_create(self.ctx, 'subcloud', values)
|
||||||
|
self.subcloud_resource = subcloud_resource.SubcloudResource(
|
||||||
|
self.ctx, subcloud_resource_id=self.rsrc.master_id,
|
||||||
|
resource_id=self.rsrc.id, subcloud_id=self.subcloud.id
|
||||||
|
)
|
||||||
|
self.subcloud_resource.create()
|
||||||
|
|
||||||
|
def _get_request(self):
|
||||||
|
return self.request
|
||||||
|
|
||||||
|
def _get_rsrc(self):
|
||||||
|
return self.rsrc
|
||||||
|
|
||||||
|
def _get_log(self):
|
||||||
|
return self.log
|
||||||
|
|
||||||
|
def _get_subcloud(self):
|
||||||
|
return self.subcloud
|
||||||
|
|
||||||
|
def _get_subcloud_resource(self):
|
||||||
|
return self.subcloud_resource
|
||||||
|
|
||||||
|
def _get_resource_name(self):
|
||||||
|
return self.resource_name
|
||||||
|
|
||||||
|
def _get_resource_ref(self):
|
||||||
|
return self.resource_ref
|
||||||
|
|
||||||
|
def _get_resource_ref_name(self):
|
||||||
|
return self.resource_ref_name
|
||||||
|
|
||||||
|
def _resource_add(self):
|
||||||
|
return self.resource_add
|
||||||
|
|
||||||
|
def _resource_detail(self):
|
||||||
|
return self.resource_detail
|
||||||
|
|
||||||
|
def _resource_update(self):
|
||||||
|
return self.resource_update
|
||||||
|
|
||||||
|
def _resource_keystone_update(self):
|
||||||
|
return self.resource_keystone_update
|
||||||
|
|
||||||
|
def _resource_keystone_delete(self):
|
||||||
|
return self.resource_keystone_delete
|
||||||
|
|
||||||
|
def _execute(self):
|
||||||
|
self.method(self.request, self.rsrc)
|
||||||
|
|
||||||
|
def _execute_and_assert_exception(self, exception):
|
||||||
|
self.assertRaises(
|
||||||
|
exception,
|
||||||
|
self.method,
|
||||||
|
self.request,
|
||||||
|
self.rsrc
|
||||||
|
)
|
||||||
|
|
||||||
|
def _assert_log(self, level, message, extra=mock.ANY):
|
||||||
|
if level == 'info':
|
||||||
|
self.log.info.assert_called_with(message, extra=extra)
|
||||||
|
elif level == 'error':
|
||||||
|
self.log.error.assert_called_with(message, extra=extra)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadUsers(BaseTestIdentitySyncThread):
|
||||||
|
"""Base test class for users' requests"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.resource_name = 'user'
|
||||||
|
self.resource_ref = {
|
||||||
|
self.resource_name: {'id': RESOURCE_ID},
|
||||||
|
'local_user': {'name': 'fake value'}
|
||||||
|
}
|
||||||
|
self.resource_ref_name = self.resource_ref.get('local_user').get('name')
|
||||||
|
self.resource_detail = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
identity_user_manager.user_detail
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadUsersPost(
|
||||||
|
BaseTestIdentitySyncThreadUsers, mixins.PostResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for users' post method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.post_users
|
||||||
|
self.resource_add = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
identity_user_manager.add_user
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadUsersPut(
|
||||||
|
BaseTestIdentitySyncThreadUsers, mixins.PutResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for users' put method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.put_users
|
||||||
|
self.resource_update = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
identity_user_manager.update_user
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadUsersPatch(
|
||||||
|
BaseTestIdentitySyncThreadUsers, mixins.PatchResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for users' patch method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.patch_users
|
||||||
|
self.request.orch_job.resource_info = f'{{"{self.resource_name}": {{}}}}'
|
||||||
|
self.resource_keystone_update = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.users.update
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadUsersDelete(
|
||||||
|
BaseTestIdentitySyncThreadUsers, mixins.DeleteResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for users' delete method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.delete_users
|
||||||
|
self.resource_keystone_delete = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.users.delete
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadGroups(BaseTestIdentitySyncThread):
|
||||||
|
"""Base test class for groups' methods"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.resource_name = 'group'
|
||||||
|
self.resource_ref = \
|
||||||
|
{self.resource_name: {'id': RESOURCE_ID, 'name': 'fake value'}}
|
||||||
|
self.resource_ref_name = \
|
||||||
|
self.resource_ref.get(self.resource_name).get('name')
|
||||||
|
self.resource_detail = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
identity_group_manager.group_detail
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadGroupsPost(
|
||||||
|
BaseTestIdentitySyncThreadGroups, mixins.PostResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for groups' post method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.post_groups
|
||||||
|
self.resource_add = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
identity_group_manager.add_group
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadGroupsPut(
|
||||||
|
BaseTestIdentitySyncThreadGroups, mixins.PutResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for groups' put method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.put_groups
|
||||||
|
self.resource_update = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
identity_group_manager.update_group
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadGroupsPatch(
|
||||||
|
BaseTestIdentitySyncThreadGroups, mixins.PatchResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for groups' patch method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.patch_groups
|
||||||
|
self.request.orch_job.resource_info = f'{{"{self.resource_name}": {{}}}}'
|
||||||
|
self.resource_keystone_update = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.groups.update
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadGroupsDelete(
|
||||||
|
BaseTestIdentitySyncThreadGroups, mixins.DeleteResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for groups' delete method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.delete_groups
|
||||||
|
self.resource_keystone_delete = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.groups.delete
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadProjects(BaseTestIdentitySyncThread):
|
||||||
|
"""Base test class for projects' methods"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.resource_name = 'project'
|
||||||
|
self.resource_ref = {
|
||||||
|
self.resource_name: {'id': RESOURCE_ID, 'name': 'fake value'}
|
||||||
|
}
|
||||||
|
self.resource_ref_name = \
|
||||||
|
self.resource_ref.get(self.resource_name).get('name')
|
||||||
|
self.resource_detail = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
project_manager.project_detail
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadProjectsPost(
|
||||||
|
BaseTestIdentitySyncThreadProjects, mixins.PostResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for projects' post method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.post_projects
|
||||||
|
self.resource_add = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
project_manager.add_project
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadProjectsPut(
|
||||||
|
BaseTestIdentitySyncThreadProjects, mixins.PutResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for projects' put method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.put_projects
|
||||||
|
self.resource_update = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
project_manager.update_project
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadProjectsPatch(
|
||||||
|
BaseTestIdentitySyncThreadProjects, mixins.PatchResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for projects' patch method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.patch_projects
|
||||||
|
self.request.orch_job.resource_info = f'{{"{self.resource_name}": {{}}}}'
|
||||||
|
self.resource_keystone_update = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.projects.update
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadProjectsDelete(
|
||||||
|
BaseTestIdentitySyncThreadProjects, mixins.DeleteResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for projects' delete method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.delete_projects
|
||||||
|
self.resource_keystone_delete = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.projects.delete
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadRoles(BaseTestIdentitySyncThread):
|
||||||
|
"""Base test class for roles' methods"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.resource_name = 'role'
|
||||||
|
self.resource_ref = {
|
||||||
|
self.resource_name: {'id': RESOURCE_ID, 'name': 'fake value'}
|
||||||
|
}
|
||||||
|
self.resource_ref_name = \
|
||||||
|
self.resource_ref.get(self.resource_name).get('name')
|
||||||
|
self.resource_detail = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
role_manager.role_detail
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadRolesPost(
|
||||||
|
BaseTestIdentitySyncThreadRoles, mixins.PostResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for roles' post method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.post_roles
|
||||||
|
self.resource_add = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
role_manager.add_role
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadRolesPut(
|
||||||
|
BaseTestIdentitySyncThreadRoles, mixins.PutResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for roles' put method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.put_roles
|
||||||
|
self.resource_update = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
role_manager.update_role
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadRolesPatch(
|
||||||
|
BaseTestIdentitySyncThreadRoles, mixins.PatchResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for roles' patch method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.patch_roles
|
||||||
|
self.request.orch_job.resource_info = f'{{"{self.resource_name}": {{}}}}'
|
||||||
|
self.resource_keystone_update = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.roles.update
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadRolesDelete(
|
||||||
|
BaseTestIdentitySyncThreadRoles, mixins.DeleteResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for roles' delete method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.delete_roles
|
||||||
|
self.resource_keystone_delete = self.mock_openstack_driver().\
|
||||||
|
keystone_client.keystone_client.roles.delete
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadProjectRoleAssignments(BaseTestIdentitySyncThread):
|
||||||
|
"""Base test class for project role assignments' methods"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.project_id = 10
|
||||||
|
self.actor_id = 11
|
||||||
|
self.role_id = 12
|
||||||
|
self.domain = 13
|
||||||
|
|
||||||
|
self.resource_tags = f'{self.project_id}_{self.actor_id}_{self.role_id}'
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadProjectRoleAssignmentsPost(
|
||||||
|
BaseTestIdentitySyncThreadProjectRoleAssignments
|
||||||
|
):
|
||||||
|
"""Test class for project role assignments' post method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.post_project_role_assignments
|
||||||
|
self.rsrc.master_id = self.resource_tags
|
||||||
|
|
||||||
|
self.mock_sc_role = self._create_mock_object(self.role_id)
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
roles.list.return_value = [self.mock_sc_role]
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
projects.list.return_value = [self._create_mock_object(self.project_id)]
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
domains.list.return_value = [self._create_mock_object(self.project_id)]
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
users.list.return_value = [self._create_mock_object(self.actor_id)]
|
||||||
|
|
||||||
|
def _create_mock_object(self, id):
|
||||||
|
mock_object = mock.MagicMock()
|
||||||
|
mock_object.id = str(id)
|
||||||
|
|
||||||
|
return mock_object
|
||||||
|
|
||||||
|
def test_post_succeeds_with_sc_user(self):
|
||||||
|
"""Test post succeeds with sc user"""
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Created Keystone role assignment {self.rsrc.id}:"
|
||||||
|
f"{self.rsrc.master_id} [{self.rsrc.master_id}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_succeeds_with_sc_group(self):
|
||||||
|
"""Test post succeeds with sc group"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
users.list.return_value = []
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
groups.list.return_value = [self._create_mock_object(self.actor_id)]
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Created Keystone role assignment {self.rsrc.id}:"
|
||||||
|
f"{self.rsrc.master_id} [{self.rsrc.master_id}]"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_with_invalid_resource_tags(self):
|
||||||
|
"""Test post fails with invalid resource tags"""
|
||||||
|
|
||||||
|
self.rsrc.master_id = f'{self.project_id}_{self.actor_id}'
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Malformed resource tag {self.rsrc.id} expected to be in "
|
||||||
|
"format: ProjectID_UserID_RoleID."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_sc_role(self):
|
||||||
|
"""Test post fails without sc role"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
roles.list.return_value = []
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "Unable to assign role to user on project reference "
|
||||||
|
f"{self.rsrc}:{self.role_id}, cannot "
|
||||||
|
"find equivalent Keystone Role in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_sc_proj(self):
|
||||||
|
"""Test post fails without sc proj"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
projects.list.return_value = []
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "Unable to assign role to user on project reference "
|
||||||
|
f"{self.rsrc}:{self.project_id}, cannot "
|
||||||
|
"find equivalent Keystone Project in subcloud"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_wihtout_sc_user_and_sc_group(self):
|
||||||
|
"""Test post fails without sc user and sc group"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
users.list.return_value = []
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "Unable to assign role to user/group on project "
|
||||||
|
f"reference {self.rsrc}:{self.actor_id}, cannot find "
|
||||||
|
"equivalent Keystone User/Group in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_role_ref(self):
|
||||||
|
"""Test post fails without role ref"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
role_assignments.list.return_value = []
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "Unable to update Keystone role assignment "
|
||||||
|
f"{self.rsrc.id}:{self.mock_sc_role} "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadProjectRoleAssignmentsPut(
|
||||||
|
BaseTestIdentitySyncThreadProjectRoleAssignments
|
||||||
|
):
|
||||||
|
"""Test class for project role assignments' put method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.put_project_role_assignments
|
||||||
|
self.subcloud_resource.subcloud_resource_id = self.resource_tags
|
||||||
|
|
||||||
|
def test_put_succeeds(self):
|
||||||
|
"""Test put succeeds
|
||||||
|
|
||||||
|
Currently, there isn't an implementation for the put method. Because of
|
||||||
|
that, it only returns an empty response.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._assert_log('info', 'IdentitySyncThread initialized')
|
||||||
|
self.log.error.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadProjectRoleAssignmentsDelete(
|
||||||
|
BaseTestIdentitySyncThreadProjectRoleAssignments
|
||||||
|
):
|
||||||
|
"""Test class for project role assignments' delete method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.delete_project_role_assignments
|
||||||
|
|
||||||
|
self.subcloud_resource.subcloud_resource_id = self.resource_tags
|
||||||
|
self.subcloud_resource.save()
|
||||||
|
|
||||||
|
def test_delete_succeeds(self):
|
||||||
|
"""Test delete succeeds"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
role_assignments.list.return_value = []
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'info', "Deleted Keystone role assignment: "
|
||||||
|
f"{self.rsrc.id}:{self.subcloud_resource}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_succeeds_without_assignment_subcloud_rsrc(self):
|
||||||
|
"""Test delete succeeds without assignment subcloud rsrc"""
|
||||||
|
|
||||||
|
self.rsrc.id = 999
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Unable to delete assignment {self.rsrc}, "
|
||||||
|
"cannot find Keystone Role Assignment in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_succeeds_with_invalid_resource_tags(self):
|
||||||
|
"""Test delete succeeds with invalid resource tags"""
|
||||||
|
|
||||||
|
self.subcloud_resource.subcloud_resource_id = MASTER_ID
|
||||||
|
self.subcloud_resource.save()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Malformed subcloud resource tag {self.subcloud_resource}, "
|
||||||
|
"expected to be in format: ProjectID_UserID_RoleID or "
|
||||||
|
"ProjectID_GroupID_RoleID."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_for_user_succeeds_with_keystone_not_found_exception(self):
|
||||||
|
"""Test delete fails for user with keystone not found exception"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
roles.revoke.side_effect = [keystone_exceptions.NotFound, None]
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
role_assignments.list.return_value = []
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self.log.assert_has_calls([
|
||||||
|
mock.call.info(
|
||||||
|
f"Revoke role assignment: (role {self.role_id}, "
|
||||||
|
f"user {self.actor_id}, project {self.project_id}) "
|
||||||
|
f"not found in {self.subcloud.region_name}, "
|
||||||
|
"considered as deleted.", extra=mock.ANY
|
||||||
|
),
|
||||||
|
mock.call.info(
|
||||||
|
f"Deleted Keystone role assignment: {self.rsrc.id}:"
|
||||||
|
f"{self.subcloud_resource}", extra=mock.ANY
|
||||||
|
)],
|
||||||
|
any_order=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_for_group_succeeds_with_keystone_not_found_exception(self):
|
||||||
|
"""Test delete fails for group with keystone not found exception"""
|
||||||
|
|
||||||
|
self.mock_openstack_driver().keystone_client.keystone_client.\
|
||||||
|
roles.revoke.side_effect = keystone_exceptions.NotFound
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self.log.assert_has_calls([
|
||||||
|
mock.call.info(
|
||||||
|
f"Revoke role assignment: (role {self.role_id}, "
|
||||||
|
f"group {self.actor_id}, project {self.project_id}) "
|
||||||
|
f"not found in {self.subcloud.region_name}, "
|
||||||
|
"considered as deleted.", extra=mock.ANY
|
||||||
|
),
|
||||||
|
mock.call.info(
|
||||||
|
f"Deleted Keystone role assignment: {self.rsrc.id}:"
|
||||||
|
f"{self.subcloud_resource}", extra=mock.ANY
|
||||||
|
)],
|
||||||
|
any_order=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_fails_without_role_ref(self):
|
||||||
|
"""Test delete fails without role ref"""
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'error', "Unable to delete Keystone role assignment "
|
||||||
|
f"{self.rsrc.id}:{self.role_id} "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadRevokeEvents(BaseTestIdentitySyncThread):
|
||||||
|
"""Base test class for revoke events' methods"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.resource_name = 'token revocation event'
|
||||||
|
self.resource_ref = {
|
||||||
|
'revocation_event': {'audit_id': RESOURCE_ID, 'name': 'fake value'}
|
||||||
|
}
|
||||||
|
self.resource_ref_name = \
|
||||||
|
self.resource_ref.get('revocation_event').get('name')
|
||||||
|
self.resource_detail = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
revoke_event_manager.revoke_event_detail
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadRevokeEventsPost(
|
||||||
|
BaseTestIdentitySyncThreadRevokeEvents, mixins.PostResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for revoke events' post method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.resource_info = {"token_revoke_event": {"audit_id": RESOURCE_ID}}
|
||||||
|
self.request.orch_job.resource_info = jsonutils.dumps(self.resource_info)
|
||||||
|
self.method = self.identity_sync_thread.post_revoke_events
|
||||||
|
self.resource_add = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
revoke_event_manager.add_revoke_event
|
||||||
|
|
||||||
|
def test_post_succeeds(self):
|
||||||
|
"""Test post succeeds"""
|
||||||
|
|
||||||
|
self._resource_add().return_value = self._get_resource_ref()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_detail().assert_called_once()
|
||||||
|
self._resource_add().assert_called_once()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Created Keystone {self._get_resource_name()} "
|
||||||
|
f"{self._get_rsrc().id}:"
|
||||||
|
f"{self.resource_info.get('token_revoke_event').get('audit_id')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_source_resource_id(self):
|
||||||
|
"""Test post fails without source resource id"""
|
||||||
|
|
||||||
|
self._get_request().orch_job.resource_info = "{}"
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Received {self._get_resource_name()} create request "
|
||||||
|
"without required subcloud resource id"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_with_empty_resource_ref(self):
|
||||||
|
"""Test post fails with empty resource ref"""
|
||||||
|
|
||||||
|
self._resource_add().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"No {self._get_resource_name()} data returned when creating "
|
||||||
|
f"{self._get_resource_name()} with audit_id "
|
||||||
|
f"{self.resource_info.get('token_revoke_event').get('audit_id')} "
|
||||||
|
"in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_resource_records(self):
|
||||||
|
"""Test post fails without resource records"""
|
||||||
|
|
||||||
|
self._resource_detail().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "No data retrieved from master cloud for "
|
||||||
|
f"{self._get_resource_name()} with audit_id "
|
||||||
|
f"{self.resource_info.get('token_revoke_event').get('audit_id')} "
|
||||||
|
"to create its equivalent in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadRevokeEventsDelete(
|
||||||
|
BaseTestIdentitySyncThreadRevokeEvents, mixins.DeleteResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for revoke events' delete method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.delete_revoke_events
|
||||||
|
self.resource_keystone_delete = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
revoke_event_manager.delete_revoke_event
|
||||||
|
|
||||||
|
def test_delete_succeeds_with_keystone_not_found_exception(self):
|
||||||
|
"""Test delete succeeds with keystone's not found exception
|
||||||
|
|
||||||
|
The revoke events doesn't use the keystone client
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_delete_succeeds_with_dbsync_not_found_exception(self):
|
||||||
|
"""Test delete succeeds with dbsync's not found exception"""
|
||||||
|
|
||||||
|
self._resource_keystone_delete().side_effect = dbsync_exceptions.NotFound()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_keystone_delete().assert_called_once()
|
||||||
|
self._get_log().assert_has_calls([
|
||||||
|
mock.call.info(
|
||||||
|
f"Delete {self._get_resource_name()}: event "
|
||||||
|
f"{self._get_subcloud_resource().subcloud_resource_id} "
|
||||||
|
f"not found in {self._get_subcloud().region_name}, "
|
||||||
|
"considered as deleted.", extra=mock.ANY
|
||||||
|
),
|
||||||
|
mock.call.info(
|
||||||
|
f"Keystone {self._get_resource_name()} {self._get_rsrc().id}:"
|
||||||
|
f"{self._get_subcloud().id} "
|
||||||
|
f"[{self._get_subcloud_resource().subcloud_resource_id}] deleted",
|
||||||
|
extra=mock.ANY
|
||||||
|
)],
|
||||||
|
any_order=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTestIdentitySyncThreadRevokeEventsForUser(BaseTestIdentitySyncThread):
|
||||||
|
"""Base test class for revoke events for user' methods"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.resource_name = 'token revocation event'
|
||||||
|
self.resource_ref = {
|
||||||
|
'revocation_event': {'audit_id': RESOURCE_ID, 'name': 'fake value'}
|
||||||
|
}
|
||||||
|
self.resource_ref_name = \
|
||||||
|
self.resource_ref.get('revocation_event').get('name')
|
||||||
|
self.resource_detail = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
revoke_event_manager.revoke_event_detail
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadRevokeEventsForUserPost(
|
||||||
|
BaseTestIdentitySyncThreadRevokeEventsForUser, mixins.PostResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for revoke events for user' post method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.post_revoke_events_for_user
|
||||||
|
self.resource_add = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
revoke_event_manager.add_revoke_event
|
||||||
|
|
||||||
|
def test_post_succeeds(self):
|
||||||
|
"""Test post succeeds"""
|
||||||
|
|
||||||
|
self._resource_add().return_value = self._get_resource_ref()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_detail().assert_called_once()
|
||||||
|
self._resource_add().assert_called_once()
|
||||||
|
|
||||||
|
self._assert_log(
|
||||||
|
'info', f"Created Keystone {self._get_resource_name()} "
|
||||||
|
f"{self._get_rsrc().id}:"
|
||||||
|
f"{self._get_request().orch_job.source_resource_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_source_resource_id(self):
|
||||||
|
"""Test post fails without source resource id"""
|
||||||
|
|
||||||
|
self._get_request().orch_job.source_resource_id = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"Received {self._get_resource_name()} create request "
|
||||||
|
"without required subcloud resource id"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_with_empty_resource_ref(self):
|
||||||
|
"""Test post fails with empty resource ref"""
|
||||||
|
|
||||||
|
self._resource_add().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', f"No {self._get_resource_name()} data returned when creating "
|
||||||
|
f"{self._get_resource_name()} with event_id "
|
||||||
|
f"{self._get_request().orch_job.source_resource_id} in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_fails_without_resource_records(self):
|
||||||
|
"""Test post fails without resource records"""
|
||||||
|
|
||||||
|
self._resource_detail().return_value = None
|
||||||
|
|
||||||
|
self._execute_and_assert_exception(exceptions.SyncRequestFailed)
|
||||||
|
self._assert_log(
|
||||||
|
'error', "No data retrieved from master cloud for "
|
||||||
|
f"{self._get_resource_name()} with event_id "
|
||||||
|
f"{self._get_request().orch_job.source_resource_id} to create its "
|
||||||
|
"equivalent in subcloud."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestIdentitySyncThreadRevokeEventsForUserDelete(
|
||||||
|
BaseTestIdentitySyncThreadRevokeEventsForUser, mixins.DeleteResourceMixin
|
||||||
|
):
|
||||||
|
"""Test class for revoke events for user' delete method"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.method = self.identity_sync_thread.delete_revoke_events_for_user
|
||||||
|
self.resource_keystone_delete = self.mock_openstack_driver().dbsync_client.\
|
||||||
|
revoke_event_manager.delete_revoke_event
|
||||||
|
|
||||||
|
def test_delete_succeeds_with_keystone_not_found_exception(self):
|
||||||
|
"""Test delete succeeds with keystone's not found exception
|
||||||
|
|
||||||
|
The revoke events for users doesn't use the keystone client
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_delete_succeeds_with_dbsync_not_found_exception(self):
|
||||||
|
"""Test delete succeeds with dbsync's not found exception"""
|
||||||
|
|
||||||
|
self._resource_keystone_delete().side_effect = dbsync_exceptions.NotFound()
|
||||||
|
|
||||||
|
self._execute()
|
||||||
|
|
||||||
|
self._resource_keystone_delete().assert_called_once()
|
||||||
|
self._get_log().assert_has_calls([
|
||||||
|
mock.call.info(
|
||||||
|
f"Delete {self._get_resource_name()}: event "
|
||||||
|
f"{self._get_subcloud_resource().subcloud_resource_id} "
|
||||||
|
f"not found in {self._get_subcloud().region_name}, "
|
||||||
|
"considered as deleted.", extra=mock.ANY
|
||||||
|
),
|
||||||
|
mock.call.info(
|
||||||
|
f"Keystone {self._get_resource_name()} {self._get_rsrc().id}:"
|
||||||
|
f"{self._get_subcloud().id} "
|
||||||
|
f"[{self._get_subcloud_resource().subcloud_resource_id}] deleted",
|
||||||
|
extra=mock.ANY
|
||||||
|
)],
|
||||||
|
any_order=False
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user