[AIM] Eliminate name mapping DB

AIM resource names are now Neutron and GBP resource UUIDs, with an
optional prefix.

This is the first step of phasing out the name mapper. Once an
appropriate function, similar to aim.utils.sanitize_display_name, is
available from AIM, calls to the name mapper should be replaced with
calls to that function. Once this is complete, the name mapper module
and class will be removed.

Change-Id: I6b4b180f8722e4378e3afee7d62a71292eea233d
(cherry picked from commit 9e06d9d506)
This commit is contained in:
Robert Kukura
2016-11-18 03:52:05 -05:00
parent 3cc269092f
commit ad45fd04f8
7 changed files with 35 additions and 174 deletions

View File

@@ -13,12 +13,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import re from neutron.common import exceptions
from neutron._i18n import _LI
LOG = None
NAME_TYPE_TENANT = 'tenant' NAME_TYPE_TENANT = 'tenant'
NAME_TYPE_NETWORK = 'network' NAME_TYPE_NETWORK = 'network'
@@ -33,112 +28,33 @@ NAME_TYPE_EXTERNAL_SEGMENT = 'external_segment'
NAME_TYPE_EXTERNAL_POLICY = 'external_policy' NAME_TYPE_EXTERNAL_POLICY = 'external_policy'
NAME_TYPE_NAT_POOL = 'nat_pool' NAME_TYPE_NAT_POOL = 'nat_pool'
MAX_APIC_NAME_LENGTH = 46
class InvalidResourceId(exceptions.BadRequest):
message = _("The %(type)s ID '%(id)s' is invalid.")
# TODO(rkukura): This is name mapper is copied from the apicapi repo, # TODO(rkukura): This module and class are being phased out. Uses of
# and modified to pass in resource names rather than calling the core # this class should be replaced with calls to a function, similar to
# plugin to get them, and to use the existing DB session. We need # aim.utils.sanitize_display_name, that does any concatenation,
# decide whether to make these changes in apicapi (maybe on a branch), # character or substring translation, or truncation is needed to
# move this some other repo, or keep it here. The changes are not # ensure the supplied string (and optional prefix) form a valid APIC
# backwards compatible. The implementation should also be cleaned up # name. One this phase out is complete, this module will be deleted.
# and simplified. For example, sessions should be passed in place of
# contexts, and the core plugin calls eliminated.
def truncate(string, max_length):
if max_length < 0:
return ''
return string[:max_length] if len(string) > max_length else string
class APICNameMapper(object): class APICNameMapper(object):
def __init__(self, db, log):
self.db = db
self.min_suffix = 5
global LOG
LOG = log.getLogger(__name__)
def mapper(name_type): def mapper(name_type):
"""Wrapper to land all the common operations between mappers.""" """Wrapper to land all the common operations between mappers."""
def wrap(func): def wrap(func):
def inner(inst, session, resource_id, resource_name=None, def inner(inst, session, resource_id, resource_name=None,
prefix=None): prefix=None):
# REVISIT(Bob): Optional argument for reserving characters in if not resource_id:
# the prefix? raise InvalidResourceId(type=name_type, id=resource_id)
saved_name = inst.db.get_apic_name(session, result = resource_id
resource_id,
name_type)
if saved_name:
result = saved_name[0]
# REVISIT(Sumit): Should this name mapper be aware of
# this prefixing logic, or should we instead prepend
# the prefix at the point from where this is being
# invoked. The latter approach has the disadvantage
# of having to replicate the logic in many places.
if prefix: if prefix:
result = prefix + result result = prefix + result
result = truncate(result, MAX_APIC_NAME_LENGTH)
return result
name = ''
try:
name = func(inst, session, resource_id, resource_name)
except Exception as e:
LOG.warn(("Exception in looking up name %s"), name_type)
LOG.error(e.message)
purged_id = re.sub(r"-+", "-", resource_id)
result = purged_id[:inst.min_suffix]
if name:
name = re.sub(r"-+", "-", name)
# Keep as many uuid chars as possible
id_suffix = "_" + result
max_name_length = MAX_APIC_NAME_LENGTH - len(id_suffix)
result = truncate(name, max_name_length) + id_suffix
result = truncate(result, MAX_APIC_NAME_LENGTH)
# Remove forbidden whitespaces
result = result.replace(' ', '')
result = inst._grow_id_if_needed(
session, purged_id, name_type, result,
start=inst.min_suffix)
else:
result = purged_id
inst.db.add_apic_name(session, resource_id,
name_type, result)
if prefix:
result = prefix + result
result = truncate(result, MAX_APIC_NAME_LENGTH)
return result return result
return inner return inner
return wrap return wrap
def _grow_id_if_needed(self, session, resource_id, name_type,
current_result, start=0):
result = current_result
if result.endswith('_'):
result = result[:-1]
try:
x = 0
while True:
if self.db.get_filtered_apic_names(session,
neutron_type=name_type,
apic_name=result):
if x == 0 and start == 0:
result += '_'
# This name overlaps, add more ID characters
result += resource_id[start + x]
x += 1
else:
break
except AttributeError:
LOG.info(_LI("Current DB API doesn't support "
"get_filtered_apic_names."))
except IndexError:
LOG.debug("Ran out of ID characters.")
return result
@mapper(NAME_TYPE_TENANT) @mapper(NAME_TYPE_TENANT)
def tenant(self, session, tenant_id, tenant_name=None): def tenant(self, session, tenant_id, tenant_name=None):
return tenant_name return tenant_name
@@ -195,4 +111,6 @@ class APICNameMapper(object):
return nat_pool['name'] return nat_pool['name']
def delete_apic_name(self, session, object_id): def delete_apic_name(self, session, object_id):
self.db.delete_apic_name(session, object_id) # This method is kept for compatibility with existing code
# until the name mapper is phased out entirely.
pass

View File

@@ -52,6 +52,9 @@ class ProjectNameCache(object):
cache. cache.
""" """
if not project_id:
return
# TODO(rkukura): It seems load_from_conf_options() and # TODO(rkukura): It seems load_from_conf_options() and
# keystoneclient auth plugins have been deprecated, and we # keystoneclient auth plugins have been deprecated, and we
# should use keystoneauth instead. # should use keystoneauth instead.

View File

@@ -46,7 +46,6 @@ from gbpservice.neutron.plugins.ml2plus import driver_api as api_plus
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import apic_mapper
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import cache from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import cache
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import extension_db from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import extension_db
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
from oslo_serialization.jsonutils import netaddr from oslo_serialization.jsonutils import netaddr
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@@ -99,8 +98,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_name_cache = cache.ProjectNameCache()
self.db = model.DbModel() self.name_mapper = apic_mapper.APICNameMapper()
self.name_mapper = apic_mapper.APICNameMapper(self.db, log)
self.aim = aim_manager.AimManager() self.aim = aim_manager.AimManager()
self._core_plugin = None self._core_plugin = None
self._l3_plugin = None self._l3_plugin = None
@@ -119,6 +117,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
def ensure_tenant(self, plugin_context, tenant_id): def ensure_tenant(self, plugin_context, tenant_id):
LOG.debug("APIC AIM MD ensuring tenant_id: %s", tenant_id) LOG.debug("APIC AIM MD ensuring tenant_id: %s", tenant_id)
if not tenant_id:
# The l3_db module creates gateway ports with empty string
# project IDs in order to hide those ports from
# users. Since we are not currently mapping ports to
# anything in AIM, we can ignore these. Any other cases
# where empty string project IDs are used may require
# mapping AIM resources under some actual Tenant.
return
self.project_name_cache.ensure_project(tenant_id) self.project_name_cache.ensure_project(tenant_id)
# TODO(rkukura): Move the following to calls made from # TODO(rkukura): Move the following to calls made from

View File

@@ -1,63 +0,0 @@
# Copyright (c) 2016 Cisco 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.
from apic_ml2.neutron.plugins.ml2.drivers.cisco.apic import (
apic_model as old_model)
from neutron._i18n import _LI
from oslo_log import log
from sqlalchemy import orm
LOG = log.getLogger(__name__)
# REVISIT(rkukura): Temporarily using ApicName model defined in old
# apic-ml2 driver with migration in neutron. We should define our
# own, and may want to switch to per-resource name mapping tables with
# foriegn keys.
class DbModel(object):
def __init__(self):
LOG.info(_LI("APIC AIM DbModel __init__"))
def add_apic_name(self, session, neutron_id, neutron_type, apic_name):
name = old_model.ApicName(neutron_id=neutron_id,
neutron_type=neutron_type,
apic_name=apic_name)
with session.begin(subtransactions=True):
session.add(name)
def get_apic_name(self, session, neutron_id, neutron_type):
return session.query(old_model.ApicName.apic_name).filter_by(
neutron_id=neutron_id, neutron_type=neutron_type).first()
def delete_apic_name(self, session, neutron_id):
with session.begin(subtransactions=True):
try:
session.query(old_model.ApicName).filter_by(
neutron_id=neutron_id).delete()
except orm.exc.NoResultFound:
return
def get_filtered_apic_names(self, session, neutron_id=None,
neutron_type=None, apic_name=None):
query = session.query(old_model.ApicName.apic_name)
if neutron_id:
query = query.filter_by(neutron_id=neutron_id)
if neutron_type:
query = query.filter_by(neutron_type=neutron_type)
if apic_name:
query = query.filter_by(apic_name=apic_name)
return query.all()

View File

@@ -34,7 +34,6 @@ from gbpservice.neutron.extensions import cisco_apic
from gbpservice.neutron.extensions import cisco_apic_gbp as aim_ext from gbpservice.neutron.extensions import cisco_apic_gbp as aim_ext
from gbpservice.neutron.extensions import cisco_apic_l3 from gbpservice.neutron.extensions import cisco_apic_l3
from gbpservice.neutron.extensions import group_policy as gpolicy from gbpservice.neutron.extensions import group_policy as gpolicy
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
from gbpservice.neutron.services.grouppolicy.common import ( from gbpservice.neutron.services.grouppolicy.common import (
constants as gp_const) constants as gp_const)
from gbpservice.neutron.services.grouppolicy.common import constants as g_const from gbpservice.neutron.services.grouppolicy.common import constants as g_const
@@ -127,7 +126,6 @@ class AIMMappingDriver(nrd.CommonNeutronBase, aim_rpc.AIMMappingRPCMixin):
@log.log_method_call @log.log_method_call
def initialize(self): def initialize(self):
LOG.info(_LI("APIC AIM Policy Driver initializing")) LOG.info(_LI("APIC AIM Policy Driver initializing"))
self.db = model.DbModel()
super(AIMMappingDriver, self).initialize() super(AIMMappingDriver, self).initialize()
self._apic_aim_mech_driver = None self._apic_aim_mech_driver = None
self._apic_segmentation_label_driver = None self._apic_segmentation_label_driver = None

View File

@@ -173,8 +173,8 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
super(ApicAimTestCase, self).tearDown() super(ApicAimTestCase, self).tearDown()
def _map_name(self, resource): def _map_name(self, resource):
# Assumes no conflicts and no substition needed. # TODO(rkukura): Eliminate.
return resource['name'][:40] + '_' + resource['id'][:5] return resource['id']
def _find_by_dn(self, dn, cls): def _find_by_dn(self, dn, cls):
aim_ctx = aim_context.AimContext(self.db_session) aim_ctx = aim_context.AimContext(self.db_session)
@@ -1923,8 +1923,8 @@ class TestExternalConnectivityBase(object):
contract = self._map_name(router) contract = self._map_name(router)
a_ext_net1 = aim_resource.ExternalNetwork( a_ext_net1 = aim_resource.ExternalNetwork(
tenant_name='t1', l3out_name='l1', name='n1', tenant_name='t1', l3out_name='l1', name='n1',
provided_contract_names=['pr-1', contract], provided_contract_names=sorted(['pr-1', contract]),
consumed_contract_names=['co-1', contract]) consumed_contract_names=sorted(['co-1', contract]))
a_vrf = aim_resource.VRF(tenant_name=self._tenant_name, a_vrf = aim_resource.VRF(tenant_name=self._tenant_name,
name='DefaultVRF') name='DefaultVRF')
if use_addr_scope: if use_addr_scope:
@@ -1938,8 +1938,8 @@ class TestExternalConnectivityBase(object):
ext_net2['id']}}}) ext_net2['id']}}})
a_ext_net2 = aim_resource.ExternalNetwork( a_ext_net2 = aim_resource.ExternalNetwork(
tenant_name='t1', l3out_name='l2', name='n2', tenant_name='t1', l3out_name='l2', name='n2',
provided_contract_names=['pr-1', contract], provided_contract_names=sorted(['pr-1', contract]),
consumed_contract_names=['co-1', contract]) consumed_contract_names=sorted(['co-1', contract]))
a_ext_net1.provided_contract_names = [] a_ext_net1.provided_contract_names = []
a_ext_net1.consumed_contract_names = [] a_ext_net1.consumed_contract_names = []
dv.assert_called_once_with(mock.ANY, a_ext_net1, a_vrf) dv.assert_called_once_with(mock.ANY, a_ext_net1, a_vrf)

View File

@@ -35,7 +35,6 @@ from oslo_utils import uuidutils
import webob.exc import webob.exc
from gbpservice.network.neutronv2 import local_api from gbpservice.network.neutronv2 import local_api
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
from gbpservice.neutron.services.grouppolicy.common import ( from gbpservice.neutron.services.grouppolicy.common import (
constants as gp_const) constants as gp_const)
from gbpservice.neutron.services.grouppolicy import config from gbpservice.neutron.services.grouppolicy import config
@@ -148,7 +147,6 @@ class AIMBaseTestCase(test_nr_base.CommonNeutronBaseTestCase,
vm.name = 'someid' vm.name = 'someid'
nova_client.return_value = vm nova_client.return_value = vm
self._db = model.DbModel()
self.extension_attributes = ('router:external', DN, self.extension_attributes = ('router:external', DN,
'apic:nat_type', 'apic:snat_host_pool', 'apic:nat_type', 'apic:snat_host_pool',
CIDR, PROV, CONS) CIDR, PROV, CONS)