Update_proj_descr in apic when project description is updated in os

update mechanism driver to save project description on project creation
and project update into aim database.

Change-Id: Ibecd5bbad286f8f77b381171ca5a64ff7f4fc7ad
This commit is contained in:
ajgoyalnoiro 2019-10-13 03:09:13 -07:00 committed by ajay goyal
parent 03cf64eeb4
commit 6b2cd8d967
4 changed files with 135 additions and 48 deletions

View File

@ -34,11 +34,11 @@ ksc_session.Session.register_conf_options(cfg.CONF, AUTH_GROUP)
ksc_auth.register_conf_options(cfg.CONF, AUTH_GROUP) ksc_auth.register_conf_options(cfg.CONF, AUTH_GROUP)
class ProjectNameCache(object): class ProjectDetailsCache(object):
"""Cache of Keystone project ID to project name mappings.""" """Cache of Keystone project ID to project details mappings."""
def __init__(self): def __init__(self):
self.project_names = {} self.project_details = {}
self.keystone = None self.keystone = None
self.gbp = None self.gbp = None
self.enable_neutronclient_internal_ep_interface = ( self.enable_neutronclient_internal_ep_interface = (
@ -78,7 +78,7 @@ class ProjectNameCache(object):
inside a transaction with a project_id not already in the inside a transaction with a project_id not already in the
cache. cache.
""" """
if project_id and project_id not in self.project_names: if project_id and project_id not in self.project_details:
self.load_projects() self.load_projects()
def load_projects(self): def load_projects(self):
@ -88,29 +88,45 @@ class ProjectNameCache(object):
projects = self.keystone.projects.list() projects = self.keystone.projects.list()
LOG.debug("Received projects: %s", projects) LOG.debug("Received projects: %s", projects)
for project in projects: for project in projects:
self.project_names[project.id] = project.name self.project_details[project.id] = (project.name,
project.description)
def get_project_name(self, project_id): def get_project_details(self, project_id):
"""Get name of project from cache. """Get name and descr of project from cache.
:param project_id: ID of the project :param project_id: ID of the project
Get the name of the project identified by project_id from the If the cache contains project_id, a tuple with
cache. If the cache contains project_id, the project's name is project name and description is returned
returned. If not, None is returned. else a tuple (None,None) is returned
""" """
return self.project_names.get(project_id) if self.project_details.get(project_id):
return self.project_details[project_id]
return ('', '')
def update_project_name(self, project_id): def update_project_details(self, project_id):
"""Update project name and description from keystone.
:param project_id: ID of the project
Get the name and description of the project identified by
project_id from the keystone. If the value in cache doesn't
match values in keystone, update the cache and return 1,
to indicate that cache has been updated
"""
if self.keystone is None: if self.keystone is None:
self._get_keystone_client() self._get_keystone_client()
if self.keystone: if self.keystone:
LOG.debug("Calling project API")
project = self.keystone.projects.get(project_id) project = self.keystone.projects.get(project_id)
# only return project name when there is a change if project:
if project and self.project_names.get(project_id) != project.name: prj_details = self.get_project_details(project_id)
self.project_names[project.id] = project.name if (prj_details[0] != project.name or
return project.name prj_details[1] != project.description):
self.project_details[project_id] = (
project.name, project.description)
LOG.debug("Project updated %s " % (
str(self.project_details[project_id])))
return self.project_details[project_id]
return None return None
def purge_gbp(self, project_id): def purge_gbp(self, project_id):

View File

@ -156,10 +156,11 @@ class KeystoneNotificationEndpoint(object):
"tenant %(tenant_id)s", "tenant %(tenant_id)s",
{'event_type': event_type, {'event_type': event_type,
'tenant_id': tenant_id}) 'tenant_id': tenant_id})
if event_type == 'identity.project.updated': if event_type == 'identity.project.updated':
new_project_name = (self._driver.project_name_cache. prj_details = (self._driver.project_details_cache.
update_project_name(tenant_id)) update_project_details(tenant_id))
if not new_project_name: if not prj_details:
return None return None
# we only update tenants which have been created in APIC. For other # we only update tenants which have been created in APIC. For other
@ -172,8 +173,9 @@ class KeystoneNotificationEndpoint(object):
if not self._driver.aim.get(aim_ctx, tenant): if not self._driver.aim.get(aim_ctx, tenant):
return None return None
self._driver.aim.update(aim_ctx, tenant, disp_name = aim_utils.sanitize_display_name(prj_details[0])
display_name=aim_utils.sanitize_display_name(new_project_name)) self._driver.aim.update(aim_ctx, tenant, display_name
= disp_name, descr=prj_details[1])
return oslo_messaging.NotificationResult.HANDLED return oslo_messaging.NotificationResult.HANDLED
if event_type == 'identity.project.deleted': if event_type == 'identity.project.deleted':
@ -184,7 +186,7 @@ class KeystoneNotificationEndpoint(object):
# of the gbp/neutron purge on the server side instead of # of the gbp/neutron purge on the server side instead of
# using gbp/neutron client API to do it which is not so # using gbp/neutron client API to do it which is not so
# efficient. # efficient.
self._driver.project_name_cache.purge_gbp(tenant_id) self._driver.project_details_cache.purge_gbp(tenant_id)
# delete the tenant and AP in AIM also # delete the tenant and AP in AIM also
session = db_api.get_session() session = db_api.get_session()
@ -207,7 +209,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
def initialize(self): def initialize(self):
LOG.info(_LI("APIC AIM MD initializing")) LOG.info(_LI("APIC AIM MD initializing"))
self.project_name_cache = cache.ProjectNameCache() self.project_details_cache = cache.ProjectDetailsCache()
self.name_mapper = apic_mapper.APICNameMapper() self.name_mapper = apic_mapper.APICNameMapper()
self.aim = aim_manager.AimManager() self.aim = aim_manager.AimManager()
self._core_plugin = None self._core_plugin = None
@ -535,7 +537,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
# mapping AIM resources under some actual Tenant. # mapping AIM resources under some actual Tenant.
return return
self.project_name_cache.ensure_project(project_id) self.project_details_cache.ensure_project(project_id)
# TODO(rkukura): Move the following to calls made from # TODO(rkukura): Move the following to calls made from
# precommit methods so AIM Tenants, ApplicationProfiles, and # precommit methods so AIM Tenants, ApplicationProfiles, and
@ -543,13 +545,12 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
session = plugin_context.session session = plugin_context.session
with session.begin(subtransactions=True): with session.begin(subtransactions=True):
tenant_aname = self.name_mapper.project(session, project_id) tenant_aname = self.name_mapper.project(session, project_id)
project_name = self.project_name_cache.get_project_name(project_id) project_details = (self.project_details_cache.
if project_name is None: get_project_details(project_id))
project_name = ''
aim_ctx = aim_context.AimContext(session) aim_ctx = aim_context.AimContext(session)
tenant = aim_resource.Tenant( tenant = aim_resource.Tenant(
name=tenant_aname, descr=self.apic_system_id, name=tenant_aname, descr=project_details[1], display_name=
display_name=aim_utils.sanitize_display_name(project_name)) aim_utils.sanitize_display_name(project_details[0]))
# NOTE(ivar): by overwriting the existing tenant, we make sure # NOTE(ivar): by overwriting the existing tenant, we make sure
# existing deployments will update their description value. This # existing deployments will update their description value. This
# however negates any change to the Tenant object done by direct # however negates any change to the Tenant object done by direct
@ -5475,10 +5476,11 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
mgr.expected_session, project_id) mgr.expected_session, project_id)
tenant = aim_resource.Tenant(name=tenant_name) tenant = aim_resource.Tenant(name=tenant_name)
project_name = ( project_details = self.project_details_cache.get_project_details(
self.project_name_cache.get_project_name(project_id) or '') project_id)
tenant.display_name = aim_utils.sanitize_display_name(project_name) tenant.display_name = aim_utils.sanitize_display_name(
tenant.descr = self.apic_system_id project_details[0])
tenant.descr = project_details[1]
tenant.monitored = False tenant.monitored = False
mgr.expect_aim_resource(tenant) mgr.expect_aim_resource(tenant)

View File

@ -63,7 +63,7 @@ class ValidationManager(object):
# REVISIT: Validate configuration. # REVISIT: Validate configuration.
# Load project names from Keystone. # Load project names from Keystone.
self.md.project_name_cache.load_projects() self.md.project_details_cache.load_projects()
# Start transaction. # Start transaction.
# #

View File

@ -157,23 +157,45 @@ TEST_TENANT_NAMES = {
# REVISIT(rkukura): Use mock for this instead? # REVISIT(rkukura): Use mock for this instead?
class FakeTenant(object): class FakeProject(object):
def __init__(self, id, name): def __init__(self, id, name, description=''):
self.id = id self.id = id
self.name = name self.name = name
self.description = description
class FakeProjectManager(object): class FakeProjectManager(object):
_instance = None
def __init__(self):
self._projects = {k: FakeProject(k, v)
for k, v in TEST_TENANT_NAMES.items()}
def list(self): def list(self):
return [FakeTenant(k, v) for k, v in six.iteritems(TEST_TENANT_NAMES)] return self._projects.values()
def get(self, project_id): def get(self, project_id):
return FakeTenant('test-tenant', 'new_name') return self._projects.get(project_id)
@classmethod
def reset(cls):
cls._instance = None
@classmethod
def get_instance(cls):
if not cls._instance:
cls._instance = FakeProjectManager()
return cls._instance
@classmethod
def set(cls, project_id, name, description=''):
cls.get_instance()._projects[project_id] = FakeProject(
project_id, name, description)
class FakeKeystoneClient(object): class FakeKeystoneClient(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.projects = FakeProjectManager() self.projects = FakeProjectManager.get_instance()
class AimSqlFixture(fixtures.Fixture): class AimSqlFixture(fixtures.Fixture):
@ -284,6 +306,7 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
self.validation_mgr = av.ValidationManager() self.validation_mgr = av.ValidationManager()
FakeProjectManager.reset()
self.saved_keystone_client = ksc_client.Client self.saved_keystone_client = ksc_client.Client
ksc_client.Client = FakeKeystoneClient ksc_client.Client = FakeKeystoneClient
self.plugin = manager.NeutronManager.get_plugin() self.plugin = manager.NeutronManager.get_plugin()
@ -1978,22 +2001,68 @@ class TestAimMapping(ApicAimTestCase):
self.driver.aim.get = mock.Mock(return_value=True) self.driver.aim.get = mock.Mock(return_value=True)
self.driver.aim.update = mock.Mock() self.driver.aim.update = mock.Mock()
self.driver.aim.delete = mock.Mock() self.driver.aim.delete = mock.Mock()
self.driver.project_name_cache.purge_gbp = mock.Mock() self.driver.project_details_cache.purge_gbp = mock.Mock()
payload = {} payload = {}
payload['resource_info'] = 'test-tenant' payload['resource_info'] = 'test-tenant-update'
keystone_ep = md.KeystoneNotificationEndpoint(self.driver) keystone_ep = md.KeystoneNotificationEndpoint(self.driver)
# first test with project.updated event tenant_name = self.name_mapper.project(None, 'test-tenant-update')
tenant = aim_resource.Tenant(name=tenant_name)
# Test project.updated event. Update both name and description.
FakeProjectManager.set('test-tenant-update',
'new-tenant', 'new-descr')
keystone_ep.info(None, None, 'identity.project.updated', payload, None) keystone_ep.info(None, None, 'identity.project.updated', payload, None)
assert(self.driver.aim.update.call_args_list[0] == mock.call(
mock.ANY, tenant, display_name='new-tenant', descr = 'new-descr'))
# Test project.updated event. Update only the project name.
FakeProjectManager.set('test-tenant-update', 'name123', 'new-descr')
keystone_ep.info(None, None, 'identity.project.updated', payload, None)
assert(self.driver.aim.update.call_args_list[1] == mock.call(
mock.ANY, tenant, display_name='name123', descr = 'new-descr'))
# Test project.updated event. Update only the project description.
FakeProjectManager.set('test-tenant-update', 'name123', 'descr123')
keystone_ep.info(None, None, 'identity.project.updated', payload, None)
assert(self.driver.aim.update.call_args_list[2] == mock.call(
mock.ANY, tenant, display_name='name123', descr='descr123'))
# Test project.updated event. Clear the project description.
FakeProjectManager.set('test-tenant-update', 'name123', '')
keystone_ep.info(None, None, 'identity.project.updated', payload, None)
assert(self.driver.aim.update.call_args_list[3] == mock.call(
mock.ANY, tenant, display_name='name123', descr=''))
# Test project.updated event. Update project name and description.
FakeProjectManager.set('test-tenant-update', 'prj1', 'prj2')
keystone_ep.info(None, None, 'identity.project.updated', payload, None)
assert(self.driver.aim.update.call_args_list[4] == mock.call(
mock.ANY, tenant, display_name='prj1', descr='prj2'))
# Test project.updated event. Add new tenant.
FakeProjectManager.set('test-tenant-new', 'add-tenant', 'add-descr')
self.driver.project_details_cache.project_details[
'test-tenant-new'] = ['new-tenant', 'new-descr']
tenant_name = self.name_mapper.project(None, 'test-tenant-new')
tenant = aim_resource.Tenant(name=tenant_name)
payload['resource_info'] = 'test-tenant-new'
keystone_ep.info(None, None, 'identity.project.updated', payload, None)
assert(self.driver.aim.update.call_args_list[5] == mock.call(
mock.ANY, tenant, display_name='add-tenant', descr='add-descr'))
# Test project.updated event. No change in name or description.
payload['resource_info'] = 'test-tenant-new'
keystone_ep.info(None, None, 'identity.project.updated', payload, None)
assert(len(self.driver.aim.update.call_args_list) == 6)
# Test with project.deleted event.
payload['resource_info'] = 'test-tenant'
tenant_name = self.name_mapper.project(None, 'test-tenant') tenant_name = self.name_mapper.project(None, 'test-tenant')
tenant = aim_resource.Tenant(name=tenant_name) tenant = aim_resource.Tenant(name=tenant_name)
self.driver.aim.update.assert_called_once_with(
mock.ANY, tenant, display_name='new_name')
# test again with project.deleted event
self.driver.enable_keystone_notification_purge = True self.driver.enable_keystone_notification_purge = True
keystone_ep.info(None, None, 'identity.project.deleted', payload, None) keystone_ep.info(None, None, 'identity.project.deleted', payload, None)
self.driver.project_name_cache.purge_gbp.assert_called_once_with( self.driver.project_details_cache.purge_gbp.assert_called_once_with(
'test-tenant') 'test-tenant')
tenant = aim_resource.Tenant(name=tenant_name) tenant = aim_resource.Tenant(name=tenant_name)
exp_calls = [ exp_calls = [