[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:
@@ -13,12 +13,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
|
||||
from neutron._i18n import _LI
|
||||
|
||||
LOG = None
|
||||
|
||||
from neutron.common import exceptions
|
||||
|
||||
NAME_TYPE_TENANT = 'tenant'
|
||||
NAME_TYPE_NETWORK = 'network'
|
||||
@@ -33,112 +28,33 @@ NAME_TYPE_EXTERNAL_SEGMENT = 'external_segment'
|
||||
NAME_TYPE_EXTERNAL_POLICY = 'external_policy'
|
||||
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,
|
||||
# and modified to pass in resource names rather than calling the core
|
||||
# plugin to get them, and to use the existing DB session. We need
|
||||
# decide whether to make these changes in apicapi (maybe on a branch),
|
||||
# move this some other repo, or keep it here. The changes are not
|
||||
# backwards compatible. The implementation should also be cleaned up
|
||||
# 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
|
||||
|
||||
# TODO(rkukura): This module and class are being phased out. Uses of
|
||||
# this class should be replaced with calls to a function, similar to
|
||||
# aim.utils.sanitize_display_name, that does any concatenation,
|
||||
# character or substring translation, or truncation is needed to
|
||||
# ensure the supplied string (and optional prefix) form a valid APIC
|
||||
# name. One this phase out is complete, this module will be deleted.
|
||||
|
||||
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):
|
||||
"""Wrapper to land all the common operations between mappers."""
|
||||
def wrap(func):
|
||||
def inner(inst, session, resource_id, resource_name=None,
|
||||
prefix=None):
|
||||
# REVISIT(Bob): Optional argument for reserving characters in
|
||||
# the prefix?
|
||||
saved_name = inst.db.get_apic_name(session,
|
||||
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:
|
||||
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 not resource_id:
|
||||
raise InvalidResourceId(type=name_type, id=resource_id)
|
||||
result = resource_id
|
||||
if prefix:
|
||||
result = prefix + result
|
||||
result = truncate(result, MAX_APIC_NAME_LENGTH)
|
||||
return result
|
||||
return inner
|
||||
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)
|
||||
def tenant(self, session, tenant_id, tenant_name=None):
|
||||
return tenant_name
|
||||
@@ -195,4 +111,6 @@ class APICNameMapper(object):
|
||||
return nat_pool['name']
|
||||
|
||||
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
|
||||
|
||||
@@ -52,6 +52,9 @@ class ProjectNameCache(object):
|
||||
cache.
|
||||
"""
|
||||
|
||||
if not project_id:
|
||||
return
|
||||
|
||||
# TODO(rkukura): It seems load_from_conf_options() and
|
||||
# keystoneclient auth plugins have been deprecated, and we
|
||||
# should use keystoneauth instead.
|
||||
|
||||
@@ -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 cache
|
||||
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
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@@ -99,8 +98,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
|
||||
def initialize(self):
|
||||
LOG.info(_LI("APIC AIM MD initializing"))
|
||||
self.project_name_cache = cache.ProjectNameCache()
|
||||
self.db = model.DbModel()
|
||||
self.name_mapper = apic_mapper.APICNameMapper(self.db, log)
|
||||
self.name_mapper = apic_mapper.APICNameMapper()
|
||||
self.aim = aim_manager.AimManager()
|
||||
self._core_plugin = None
|
||||
self._l3_plugin = None
|
||||
@@ -119,6 +117,15 @@ class ApicMechanismDriver(api_plus.MechanismDriver):
|
||||
def ensure_tenant(self, plugin_context, 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)
|
||||
|
||||
# TODO(rkukura): Move the following to calls made from
|
||||
|
||||
@@ -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()
|
||||
@@ -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_l3
|
||||
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 (
|
||||
constants as gp_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
|
||||
def initialize(self):
|
||||
LOG.info(_LI("APIC AIM Policy Driver initializing"))
|
||||
self.db = model.DbModel()
|
||||
super(AIMMappingDriver, self).initialize()
|
||||
self._apic_aim_mech_driver = None
|
||||
self._apic_segmentation_label_driver = None
|
||||
|
||||
@@ -173,8 +173,8 @@ class ApicAimTestCase(test_address_scope.AddressScopeTestCase,
|
||||
super(ApicAimTestCase, self).tearDown()
|
||||
|
||||
def _map_name(self, resource):
|
||||
# Assumes no conflicts and no substition needed.
|
||||
return resource['name'][:40] + '_' + resource['id'][:5]
|
||||
# TODO(rkukura): Eliminate.
|
||||
return resource['id']
|
||||
|
||||
def _find_by_dn(self, dn, cls):
|
||||
aim_ctx = aim_context.AimContext(self.db_session)
|
||||
@@ -1923,8 +1923,8 @@ class TestExternalConnectivityBase(object):
|
||||
contract = self._map_name(router)
|
||||
a_ext_net1 = aim_resource.ExternalNetwork(
|
||||
tenant_name='t1', l3out_name='l1', name='n1',
|
||||
provided_contract_names=['pr-1', contract],
|
||||
consumed_contract_names=['co-1', contract])
|
||||
provided_contract_names=sorted(['pr-1', contract]),
|
||||
consumed_contract_names=sorted(['co-1', contract]))
|
||||
a_vrf = aim_resource.VRF(tenant_name=self._tenant_name,
|
||||
name='DefaultVRF')
|
||||
if use_addr_scope:
|
||||
@@ -1938,8 +1938,8 @@ class TestExternalConnectivityBase(object):
|
||||
ext_net2['id']}}})
|
||||
a_ext_net2 = aim_resource.ExternalNetwork(
|
||||
tenant_name='t1', l3out_name='l2', name='n2',
|
||||
provided_contract_names=['pr-1', contract],
|
||||
consumed_contract_names=['co-1', contract])
|
||||
provided_contract_names=sorted(['pr-1', contract]),
|
||||
consumed_contract_names=sorted(['co-1', contract]))
|
||||
a_ext_net1.provided_contract_names = []
|
||||
a_ext_net1.consumed_contract_names = []
|
||||
dv.assert_called_once_with(mock.ANY, a_ext_net1, a_vrf)
|
||||
|
||||
@@ -35,7 +35,6 @@ from oslo_utils import uuidutils
|
||||
import webob.exc
|
||||
|
||||
from gbpservice.network.neutronv2 import local_api
|
||||
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import model
|
||||
from gbpservice.neutron.services.grouppolicy.common import (
|
||||
constants as gp_const)
|
||||
from gbpservice.neutron.services.grouppolicy import config
|
||||
@@ -148,7 +147,6 @@ class AIMBaseTestCase(test_nr_base.CommonNeutronBaseTestCase,
|
||||
vm.name = 'someid'
|
||||
nova_client.return_value = vm
|
||||
|
||||
self._db = model.DbModel()
|
||||
self.extension_attributes = ('router:external', DN,
|
||||
'apic:nat_type', 'apic:snat_host_pool',
|
||||
CIDR, PROV, CONS)
|
||||
|
||||
Reference in New Issue
Block a user