Explorar el Código

OSP-216 Add unicode display-name support, fix capability check, add/fix related unit tests

This commit adds an option to the plugin to enable/disable unicode based objects and store them on BCF.

When the config is enabled, the os_object id is used for bcf name, and os_object name is used for bcf display-name.

Default is unicode enabled (naming_scheme_unicode=True).

Unicode is enabled only when both of following are True:
1. BCF supports it (5.0.0 or above)
2. naming_scheme_unicode is set to True in config (or if it is empty)

In other situations, unicode will be disabled and sent in old format.

---
As of Queens:

Objects that always have names:
- tenant(project)
- security-group

Objects that might not have names(Empty Names for these are now supported when unicode is enabled):
- endpoint(port)
- nat-profile(router)
- segment(network)

We don't care about names of other objects like floating ip or subnets.

---
This PR also does some change to capability check functions:

Before:
  - [] (empty capabilities) = fine
  - result capabilities = intersaction of two servers' capabilities
  - cached only once during startup

After:
  - [] (empty capabilities) = failed request, always query BCF to try update it
  - result capabilities = union of two servers' capabilities
  - update capabilities every 5 minutes (does not clear existing cache if it fails)

Note: even though capabilities is checked every 5 minutes, it only logs unicode enabled/disabled during service startup and when it changes. This is to reduce uneccesary logs.

---
This PR does not touch test_path/reachability test, which means it would only work when display-name is disabled currently.

There will be a later PR for it.

Change-Id: I9ecb13df063e85038b2a622724c874dec01b4bbc
tags/14.0.0
Weifan Fu hace 1 año
padre
commit
d5d0267ba8

+ 15
- 3
etc/neutron/plugins/bigswitch/restproxy.ini Ver fichero

@@ -18,12 +18,19 @@
# add_meta_server_route : True | False (default: True)
# thread_pool_size : <int> (default: 4)
# sync_security_groups : True | False (default: False)
# naming_scheme_unicode : True | False (default: True)

# A comma separated list of BigSwitch or Floodlight servers and port numbers. The plugin proxies the requests to the BigSwitch/Floodlight server, which performs the networking configuration. Note that only one server is needed per deployment, but you may wish to deploy multiple servers to support failover.
# A comma separated list of BigSwitch or Floodlight servers and port numbers.
# The plugin proxies the requests to the BigSwitch/Floodlight server, which
# performs the networking configuration. Note that only one server is needed
# per deployment, but you may wish to deploy multiple servers to support
# failover.
servers=localhost:8080

# The username and password for authenticating against the BigSwitch or Floodlight controller.
# The authentication information for authenticating against the BigSwitch or
# Floodlight controller. (Can be username and password, or access-token)
# server_auth=username:password
# server_auth=access-token

# Use SSL when connecting to the BigSwitch or Floodlight controller.
# server_ssl=True
@@ -59,7 +66,7 @@ servers=localhost:8080
# User defined identifier for this Neutron deployment
# neutron_id =

# Flag to decide if a route to the metadata server should be injected into the VM
# Flag to decide if a route to the metadata server should be injected into VM
# add_meta_server_route = True

# Number of threads to use to handle large volumes of port creation requests
@@ -69,6 +76,11 @@ servers=localhost:8080
# visibility.
# sync_security_groups = False

# Whether or not to enable unicode support, if enabled, display-name are used
# to store object names on BCF, while uuid will be used for identification on
# BCF. (Require BCF 5.0 or above)
# naming_scheme_unicode = True

[nova]
# Specify the VIF_TYPE that will be controlled on the Nova compute instances
# options: ivs or ovs

+ 5
- 1
networking_bigswitch/plugins/bigswitch/config.py Ver fichero

@@ -80,7 +80,11 @@ restproxy_opts = [
"Openstack tenants. (0 to disable)")),
cfg.BoolOpt('sync_security_groups', default=False,
help=_("Sync security group info to Big Cloud Fabric for "
"enhanced Testpath visibility."))
"enhanced Testpath visibility.")),
cfg.BoolOpt('naming_scheme_unicode', default=True,
help=_("Configure whether or not to configure BCF "
"with unicode display-name. Applicable to BCF 5.0 "
"onwards."))
]
router_opts = [
cfg.MultiStrOpt('tenant_default_router_rule', default=['*:any:any:permit'],

+ 3
- 3
networking_bigswitch/plugins/bigswitch/l3_router_plugin.py Ver fichero

@@ -166,7 +166,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
self.txn_cache.add_transaction(router[BSN_TRANSACTION_ID],
router['id'])
with db_api.CONTEXT_READER.using(context):
mapped_router = self._map_tenant_name(router)
mapped_router = self._map_display_name_or_tenant(router)
mapped_router = self._map_state_and_status(mapped_router)

# Does not handle external gateway and some other information
@@ -190,7 +190,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
default_policy_dict = self._get_tenant_default_router_policy(tenant_id)

with db_api.CONTEXT_WRITER.using(context):
mapped_router = self._map_tenant_name(router)
mapped_router = self._map_display_name_or_tenant(router)
mapped_router = self._map_state_and_status(mapped_router)
# populate external tenant_id if it is absent for external network,
# This is a new work flow in kilo that user can specify external
@@ -503,7 +503,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
if ext_tenant_id:
updated_router[l3_apidef.EXTERNAL_GW_INFO]['tenant_id'] = (
ext_tenant_id)
router = self._map_tenant_name(updated_router)
router = self._map_display_name_or_tenant(updated_router)
router = self._map_state_and_status(router)
# look up the network on this side to save an expensive query on
# the backend controller.

+ 108
- 44
networking_bigswitch/plugins/bigswitch/plugin.py Ver fichero

@@ -203,6 +203,10 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
True, if obj name, obj's tenant name and name have supported chars
False, otherwise
"""

if self.servers.is_unicode_enabled():
return True

if name and not servermanager.is_valid_bcf_name(name):
LOG.warning('Unsupported characters in Name: %(name)s. ',
{'name': name})
@@ -281,7 +285,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
[const.DEVICE_OWNER_ROUTER_GW,
const.DEVICE_OWNER_ROUTER_HA_INTF]):
continue
mapped_port = self._map_tenant_name(port)
mapped_port = self._map_display_name_or_tenant(port)
if self.servers.is_unicode_enabled():
# remove port name so that it won't be stored in
# description
mapped_port['name'] = None
mapped_port = self._map_state_and_status(mapped_port)
mapped_port = self._map_port_hostid(mapped_port, net)
if not mapped_port:
@@ -327,7 +335,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
ext_tenant_id)

interfaces = []
mapped_router = self._map_tenant_name(router)
mapped_router = self._map_display_name_or_tenant(router)
mapped_router = self._map_state_and_status(mapped_router)
if not self._validate_names(mapped_router):
continue
@@ -368,13 +376,16 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
new_sgs = []
for sg in sgs:
try:
mapped_sg = self._map_tenant_name(sg)
mapped_sg = self._map_display_name_or_tenant(sg)
if not self._validate_names(mapped_sg):
continue
if 'description' in mapped_sg:
mapped_sg['description'] = ''
mapped_sg['name'] = Util.format_resource_name(
mapped_sg['name'])
if self.servers.is_unicode_enabled():
mapped_sg['name'] = None
else:
mapped_sg['name'] = Util.format_resource_name(
mapped_sg['name'])
new_sgs.append(mapped_sg)
except servermanager.TenantIDNotFound:
# if tenant name is not known to keystone, skip the sg
@@ -383,13 +394,27 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
data.update({'security-groups': new_sgs})

all_tenants_map = self.servers.keystone_tenants
tenants = {}
for tenant in all_tenants_map:
if not self._validate_names(None, name=all_tenants_map[tenant]):
continue
tenants[tenant] = all_tenants_map[tenant]

data.update({'tenants': tenants})
if self.servers.is_unicode_enabled():
# display-name is only supported as list for topology in NSAPI
tenants = []
for tenant_id, tenant_name in all_tenants_map.items():
tenants.append({
'name': tenant_id,
'id': tenant_id,
'display-name': tenant_name
})
else:
# dict for tenant works in topology sync only if display-name is
# not enabled
tenants = {}
for tenant in all_tenants_map:
if not self._validate_names(None,
name=all_tenants_map[tenant]):
continue
tenants[tenant] = all_tenants_map[tenant]

data['tenants'] = tenants
return data

def _send_all_data_auto(self, timeout=None, triggered_by_tenant=None):
@@ -416,9 +441,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
def _assign_resource_to_service_tenant(self, resource):
resource['tenant_id'] = (resource['tenant_id'] or
servermanager.SERVICE_TENANT)
if resource.get('name'):
# resource name may contain space. Replace space with -
resource['name'] = Util.format_resource_name(resource['name'])

if not self.servers.is_unicode_enabled():
if resource.get('name'):
# resource name may contain space. Replace space with -
resource['name'] = Util.format_resource_name(resource['name'])

def _get_network_with_floatingips(self, network, context=None):
if context is None:
@@ -433,9 +460,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
for flip in fl_ips:
try:
# BVS-7525: the 'tenant_id' in a floating-ip represents the
# tenant to which it is allocated. Validate that the
# tenant exists
mapped_flip = self._map_tenant_name(flip)
# tenant to which it is allocated.
# Validate that the tenant exists
# name/display-name of floating ip is not actually
# used on bcf
mapped_flip = self._map_display_name_or_tenant(flip)
if mapped_flip.get('floating_port_id'):
fport = self.get_port(context,
mapped_flip['floating_port_id'])
@@ -461,7 +490,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
if subnets:
for subnet in subnets:
subnet_dict = self._make_subnet_dict(subnet, context=context)
mapped_subnet = self._map_tenant_name(subnet_dict)
mapped_subnet = self._map_display_name_or_tenant(subnet_dict)
mapped_subnet = self._map_state_and_status(mapped_subnet)
subnets_details.append(mapped_subnet)

@@ -473,11 +502,15 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
This network is not associated with any tenant
"""
sg['tenant_id'] = sg['tenant_id'] or servermanager.SERVICE_TENANT
sg['tenant_name'] = self.servers.keystone_tenants.get(sg['tenant_id'])
if not sg['tenant_name']:
tenant_name = self.servers.keystone_tenants.get(sg['tenant_id'])

if not tenant_name:
self.servers._update_tenant_cache(reconcile=True)
tenant_name = self.servers.keystone_tenants.get(sg['tenant_id'])

if not self.servers.is_unicode_enabled():
sg['tenant_name'] = tenant_name
return tenant_name

def bsn_create_security_group(self, sg_id=None, sg=None, context=None):
if sg_id:
@@ -487,14 +520,19 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
sg = self.get_security_group(context, sg_id)

if sg:
sg['name'] = Util.format_resource_name(sg['name'])
if self.servers.is_unicode_enabled():
sg['display-name'] = sg['name']
sg['name'] = None
else:
sg['name'] = Util.format_resource_name(sg['name'])
# remove description as its not used
if 'description' in sg:
sg['description'] = ''
self._tenant_check_for_security_group(sg)
if sg.get('description'):
del(sg['description'])
# check and map tenant_name for sg
tenant_name = self._tenant_check_for_security_group(sg)
# skip the security group if its tenant is unknown
if sg['tenant_name']:
if sg['tenant_name'] == servermanager.SERVICE_TENANT:
if tenant_name:
if tenant_name == servermanager.SERVICE_TENANT:
self.bsn_create_tenant(servermanager.SERVICE_TENANT,
context=context)
self.servers.rest_create_securitygroup(sg)
@@ -511,14 +549,15 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
self.servers.rest_delete_tenant(tenant_id)

def _verify_network_precommit(self, context):
if context.current['name'] != context.original['name']:
raise servermanager.NetworkNameChangeError()
if not self.servers.is_unicode_enabled():
if context.current['name'] != context.original['name']:
raise servermanager.NetworkNameChangeError()

def _get_mapped_network_with_subnets(self, network, context=None):
# if context is not provided, admin context is used
if context is None:
context = qcontext.get_admin_context()
network = self._map_tenant_name(network)
network = self._map_display_name_or_tenant(network)
network = self._map_state_and_status(network)
subnets = self._get_all_subnets_json_for_network(network['id'],
context)
@@ -537,6 +576,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
# OSP-45: remove name to avoid NSAPI error in convertToAscii
for subnet in (subnets or []):
subnet.pop('name', None)

return network

def _skip_bcf_network_event(self, network):
@@ -568,14 +608,16 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
if default_group:
# VRRP tenant doesn't have tenant_id
self.bsn_create_security_group(sg=default_group[0])
# display-name is also mapped here
mapped_network = self._get_mapped_network_with_subnets(network,
context)

if not tenant_id:
tenant_id = servermanager.SERVICE_TENANT
mapped_network['tenant_id'] = servermanager.SERVICE_TENANT
mapped_network['name'] = Util.format_resource_name(
mapped_network['name'])
if not self.servers.is_unicode_enabled():
mapped_network['name'] = Util.format_resource_name(
mapped_network['name'])
self.bsn_create_tenant(servermanager.SERVICE_TENANT,
context=context)
self.servers.rest_create_network(tenant_id, mapped_network)
@@ -588,6 +630,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
{'name': network.get('name')})
return

# display-name is also mapped here
mapped_network = self._get_mapped_network_with_subnets(network,
context)
net_fl_ips = self._get_network_with_floatingips(mapped_network,
@@ -595,8 +638,9 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
if not tenant_id:
tenant_id = servermanager.SERVICE_TENANT
net_fl_ips['tenant_id'] = servermanager.SERVICE_TENANT
net_fl_ips['name'] = Util.format_resource_name(
net_fl_ips['name'])
if not self.servers.is_unicode_enabled():
net_fl_ips['name'] = Util.format_resource_name(
net_fl_ips['name'])
self.servers.rest_update_network(tenant_id, net_id, net_fl_ips)

def _send_delete_network(self, network, context=None):
@@ -604,22 +648,35 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
tenant_id = network['tenant_id'] or servermanager.SERVICE_TENANT
self.servers.rest_delete_network(tenant_id, net_id)

def _map_tenant_name(self, resource):
resource = copy.copy(resource)
def _map_display_name_or_tenant(self, resource):
"""This maps tenant_name or display-name for an object

None-unicode mode uses tenant_name
Unicode mode uses tenant_id and display-name

:param resource: object to be mapped
:return: mapped object copy
"""
resource = copy.deepcopy(resource)
self._assign_resource_to_service_tenant(resource)

tenant_name = self.servers.keystone_tenants.get(resource['tenant_id'])
if tenant_name:
resource['tenant_name'] = tenant_name
else:
if not tenant_name:
self.servers._update_tenant_cache()
tenant_name = self.servers.keystone_tenants.get(
resource['tenant_id'])
if tenant_name:
resource['tenant_name'] = tenant_name
else:
if not tenant_name:
raise servermanager.TenantIDNotFound(
tenant=resource['tenant_id'])

if self.servers.is_unicode_enabled():
if resource.get('name'):
resource['display-name'] = resource['name']
# cases like network needs the name on bcf side
resource['name'] = resource['id']
else:
resource['tenant_name'] = tenant_name

return resource

def _map_state_and_status(self, resource):
@@ -795,7 +852,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
net_id = subnet['network_id']
network = self.get_network(context, net_id)
mapped_network = self._get_mapped_network_with_subnets(network)
mapped_subnet = self._map_tenant_name(subnet)
mapped_subnet = self._map_display_name_or_tenant(subnet)
mapped_subnet = self._map_state_and_status(mapped_subnet)

data = {
@@ -1141,7 +1198,10 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
self._add_host_route(context, destination, new_port)

# create on network ctrl
mapped_port = self._map_tenant_name(new_port)
mapped_port = self._map_display_name_or_tenant(new_port)
if self.servers.is_unicode_enabled():
# remove port name so that it won't be stored in description
mapped_port['name'] = None
mapped_port = self._map_state_and_status(mapped_port)
# ports have to be created synchronously when creating a router
# port since adding router interfaces is a multi-call process
@@ -1230,7 +1290,11 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
# tenant_id must come from network in case network is shared
net_tenant_id = self._get_port_net_tenantid(context, new_port)
new_port = self._extend_port_dict_binding(context, new_port)
mapped_port = self._map_tenant_name(new_port)
mapped_port = self._map_display_name_or_tenant(new_port)
if self.servers.is_unicode_enabled():
# remove port name so that it won't be stored in
# description
mapped_port['name'] = None
mapped_port = self._map_state_and_status(mapped_port)
self.servers.rest_update_port(net_tenant_id,
new_port["network_id"],

+ 128
- 23
networking_bigswitch/plugins/bigswitch/servermanager.py Ver fichero

@@ -158,7 +158,7 @@ class NetworkNameChangeError(exceptions.NeutronException):


class RemoteRestError(exceptions.NeutronException):
message = _("Error in REST call to remote network "
message = _("Error in REST call to BCF "
"controller: %(reason)s")
status = None

@@ -285,8 +285,8 @@ class ServerProxy(object):
if body:
self.capabilities = jsonutils.loads(body)
except Exception:
LOG.exception("Couldn't retrieve capabilities. "
"Newer API calls won't be supported.")
LOG.exception("Couldn't retrieve capabilities on server "
"%(server)s. ", {'server': self.server})
LOG.info("The following capabilities were received "
"for %(server)s: %(cap)s",
{'server': self.server, 'cap': self.capabilities})
@@ -417,6 +417,9 @@ class ServerPool(object):
self.auth = cfg.CONF.RESTPROXY.server_auth
self.ssl = cfg.CONF.RESTPROXY.server_ssl
self.neutron_id = cfg.CONF.RESTPROXY.neutron_id
# unicode config
self.cfg_unicode_enabled = cfg.CONF.RESTPROXY.naming_scheme_unicode

if 'keystone_authtoken' in cfg.CONF:
self.auth_user = get_keystoneauth_cfg(cfg.CONF, 'username')
self.auth_password = get_keystoneauth_cfg(cfg.CONF, 'password')
@@ -453,6 +456,7 @@ class ServerPool(object):
self._update_tenant_cache(reconcile=False)
self.timeout = cfg.CONF.RESTPROXY.server_timeout
self.always_reconnect = not cfg.CONF.RESTPROXY.cache_connections
self.capabilities = []
default_port = 8000
if timeout is not False:
self.timeout = timeout
@@ -480,12 +484,20 @@ class ServerPool(object):
server = server[1:-1]
self.servers.append(self.server_proxy_for(server, int(port)))
self.start_background_tasks()

ServerPool._instance = self

LOG.debug("ServerPool: initialization done")

def start_background_tasks(self):
# update capabilities, starts immediately
# updates every 5 minutes, mostly for bcf upgrade/downgrade cases
eventlet.spawn(self._capability_watchdog, 300)

# consistency check, starts after 1* consistency_interval
eventlet.spawn(self._consistency_watchdog,
cfg.CONF.RESTPROXY.consistency_interval)

# Start keystone sync thread after 5 consistency sync
# to give enough time for topology to sync over when
# neutron-server starts.
@@ -495,19 +507,39 @@ class ServerPool(object):
cfg.CONF.RESTPROXY.keystone_sync_interval)

def get_capabilities(self):
"""Get capabilities

If cache has the value, use it
If Not, do REST calls to BCF controllers to check it

:return: supported capability list
"""
# lookup on first try
try:
# if capabilities is empty, the check is either not done, or failed
if self.capabilities:
return self.capabilities
except AttributeError:
# this exception is hit when the capabilities haven't been
# looked up yet
pass
# each server should return a list of capabilities it supports
# e.g. ['floatingip']
capabilities = [set(server.get_capabilities())
for server in self.servers]
# Pool only supports what all of the servers support
self.capabilities = set.intersection(*capabilities)
else:
return self.get_capabilities_force_update()

def get_capabilities_force_update(self):
"""Do REST calls to update capabilities

Logs a unicode change message when:
1. the first time that plugin gets capabilities from BCF
2. plugin notices the unicode mode is changed

:return: combined capability list from all servers
"""
# Servers should be the same version
# If one server is down, use online server's capabilities
capability_list = [set(server.get_capabilities())
for server in self.servers]

new_capabilities = set.union(*capability_list)

self.log_unicode_status_change(new_capabilities)
self.capabilities = new_capabilities

# With multiple workers enabled, the fork may occur after the
# connections to the DB have been established. We need to clear the
# connections after the first attempt to call the backend to ensure
@@ -522,8 +554,60 @@ class ServerPool(object):
# ec716b9e68b8b66a88218913ae4c9aa3a26b025a/neutron/wsgi.py#L104
if cdb.HashHandler._FACADE:
cdb.HashHandler._FACADE.get_engine().pool.dispose()

if not new_capabilities:
LOG.error('Failed to get capabilities on any controller. ')
return self.capabilities

def log_unicode_status_change(self, new_capabilities):
"""Log unicode status, if capabilities is initialized or if changed

Compares old capabilities with new capabilities
:param new_capabilities: new capabilities
:return:
"""
if new_capabilities and self.capabilities != new_capabilities:
# unicode disabled by user
if not self.cfg_unicode_enabled:
# Log only during Initialization
if not self.capabilities:
LOG.info('naming_scheme_unicode is set to False,'
' Unicode names Disabled')
# unicode enabled and supported by controller
elif 'display-name' in new_capabilities:
# Log for 2 situations:
# 1. Initialization
# 2. BCF is upgraded to support unicode
if 'display-name' not in self.capabilities:
LOG.info('naming_scheme_unicode is set to True,'
' Unicode names Enabled')
# unicode enabled, but not supported by controller
else:
# Log for 2 situations:
# 1. Initialization
# 2. BCF is downgraded, no longer supports unicode
if not self.capabilities or 'display-name' in \
self.capabilities:
LOG.warning('naming_scheme_unicode is set to True,'
' but BCF does not support it.'
' Unicode names Disabled')

def is_unicode_enabled(self):
"""Check unicode running status

True: enabled
False: disabled
"""
if not self.get_capabilities():
msg = 'Capabilities unknown! Please check BCF controller status.'
raise RemoteRestError(reason=msg)

if self.cfg_unicode_enabled and 'display-name' in \
self.get_capabilities():
return True
else:
return False

def server_proxy_for(self, server, port):
combined_cert = self._get_combined_cert_for_server(server, port)
return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id,
@@ -760,13 +844,16 @@ class ServerPool(object):
if not tenant_name:
raise TenantIDNotFound(tenant=tenant_id)

if not is_valid_bcf_name(tenant_name):
raise UnsupportedNameException(obj_type=ObjTypeEnum.tenant,
obj_id=tenant_id,
obj_name=tenant_name)

if self.is_unicode_enabled():
data = {"tenant_id": tenant_id, 'tenant_name': tenant_id,
'display-name': tenant_name}
else:
if not is_valid_bcf_name(tenant_name):
raise UnsupportedNameException(obj_type=ObjTypeEnum.tenant,
obj_id=tenant_id,
obj_name=tenant_name)
data = {"tenant_id": tenant_id, 'tenant_name': tenant_name}
resource = TENANT_RESOURCE_PATH
data = {"tenant_id": tenant_id, 'tenant_name': tenant_name}
errstr = _("Unable to create tenant: %s")
self.rest_action('POST', resource, data, errstr)

@@ -939,7 +1026,7 @@ class ServerPool(object):
def _consistency_watchdog(self, polling_interval=60):
if 'consistency' not in self.get_capabilities():
LOG.warning("Backend server(s) do not support automated "
"consitency checks.")
"consistency checks.")
return
if not polling_interval:
LOG.warning("Consistency watchdog disabled by polling "
@@ -957,6 +1044,19 @@ class ServerPool(object):
LOG.exception("Encountered an error checking controller "
"health.")

def _capability_watchdog(self, polling_interval=300):
"""Check capabilities based on polling_interval

:param polling_interval: interval in seconds
"""
while True:
try:
self.get_capabilities_force_update()
except Exception:
LOG.exception("Encountered an error checking capabilities.")
finally:
eventlet.sleep(polling_interval)

def force_topo_sync(self, check_ts=True):
"""Execute a topology_sync between OSP and BCF.

@@ -1050,8 +1150,13 @@ class ServerPool(object):
sess = session.Session(auth=auth)
keystone_client = ksclient.Client(session=sess)
tenants = keystone_client.projects.list()
new_cached_tenants = {tn.id: Util.format_resource_name(tn.name)
for tn in tenants}

if self.is_unicode_enabled():
new_cached_tenants = {tn.id: tn.name
for tn in tenants}
else:
new_cached_tenants = {tn.id: Util.format_resource_name(tn.name)
for tn in tenants}
# Add SERVICE_TENANT to handle hidden network for VRRP
new_cached_tenants[SERVICE_TENANT] = SERVICE_TENANT


+ 18
- 10
networking_bigswitch/plugins/ml2/drivers/mech_bigswitch/driver.py Ver fichero

@@ -240,24 +240,28 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
# we retain this section for security groups, because it handles
# other events as well. Ignore security group events if disabled in
# config
if (event_type == 'security_group.create.end' and
cfg.CONF.RESTPROXY.sync_security_groups):
if event_type == 'security_group.create.end':
LOG.debug("Security group created: %s", payload)
self.bsn_create_security_group(sg=payload['security_group'])
elif (event_type == 'security_group.delete.end' and
cfg.CONF.RESTPROXY.sync_security_groups):
if cfg.CONF.RESTPROXY.sync_security_groups:
self.bsn_create_security_group(sg=payload['security_group'])
elif event_type == 'security_group.delete.end':
LOG.debug("Security group deleted: %s", payload)
self.bsn_delete_security_group(payload['security_group_id'])
elif (event_type == 'security_group_rule.delete.end' and
cfg.CONF.RESTPROXY.sync_security_groups):
if cfg.CONF.RESTPROXY.sync_security_groups:
self.bsn_delete_security_group(payload['security_group_id'])
elif event_type == 'security_group_rule.delete.end':
LOG.debug("Security group rule deleted: %s", payload)
self.bsn_delete_sg_rule(payload['security_group_rule'], ctxt)
if cfg.CONF.RESTPROXY.sync_security_groups:
self.bsn_delete_sg_rule(payload['security_group_rule'], ctxt)
elif event_type == 'identity.project.deleted':
LOG.debug("Project deleted: %s", payload)
self.bsn_delete_tenant(payload['resource_info'])
elif event_type == 'identity.project.created':
LOG.debug("Project created: %s", payload)
self.bsn_create_tenant(payload['resource_info'])
elif event_type == 'identity.project.updated':
LOG.debug("Project updated: %s", payload)
# update is the same as create, nsapi will handle it
self.bsn_create_tenant(payload['resource_info'])
else:
LOG.debug("Else events: %s payload: %s", (event_type, payload))

@@ -419,7 +423,11 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
net = context.network.current
port['network'] = net
port['bound_segment'] = context.top_bound_segment
prepped_port = self._map_tenant_name(port)
prepped_port = self._map_display_name_or_tenant(port)
if prepped_port.get('description'):
del (prepped_port['description'])
if self.servers.is_unicode_enabled():
prepped_port['name'] = None
prepped_port = self._map_state_and_status(prepped_port)
prepped_port = self._map_port_hostid(prepped_port, net)
return prepped_port

+ 18
- 5
networking_bigswitch/tests/unit/bigswitch/test_base.py Ver fichero

@@ -49,8 +49,11 @@ SPAWN = ('networking_bigswitch.plugins.bigswitch.plugin.eventlet.GreenPool'
'.spawn_n')
KSCLIENT = 'keystoneclient.v3.client.Client'
BACKGROUND = SERVER_MANAGER + '.ServerPool.start_background_tasks'
MAP_TENANT_NAME = ('networking_bigswitch.plugins.bigswitch.plugin.'
'NeutronRestProxyV2Base._map_tenant_name')
MAP_DISPLAY_NAME_OR_TENANT = ('networking_bigswitch.plugins.bigswitch.plugin.'
'NeutronRestProxyV2Base.'
'_map_display_name_or_tenant')
IS_UNICODE_ENABLED = ('networking_bigswitch.plugins.bigswitch.servermanager.'
'ServerPool.is_unicode_enabled')
LIB_RPC_TRANSPORT = ('neutron_lib.rpc.TRANSPORT')


@@ -80,9 +83,14 @@ class BigSwitchTestBase(object):
cfg.CONF.set_override('api_extensions_path', False)

def map_tenant_name_side_effect(self, value):
# for old tests, always map tenant name
value['tenant_name'] = 'tenant_name'
return value

def is_unicode_enabled_side_effect(self):
# for old tests, always return False
return False

def setup_patches(self):
self.plugin_notifier_p = mock.patch(NOTIFIER)
self.dhcp_notifier_p = mock.patch(DHCP_NOTIFIER)
@@ -94,8 +102,12 @@ class BigSwitchTestBase(object):
self.log_exc_p = mock.patch(SERVER_MANAGER + ".LOG.exception",
new=lambda *args, **kwargs: None)
self.ksclient_p = mock.patch(KSCLIENT)
self.map_tenant_name_p = mock.patch(
MAP_TENANT_NAME, side_effect=self.map_tenant_name_side_effect)
self.map_display_name_or_tenant_p = mock.patch(
MAP_DISPLAY_NAME_OR_TENANT,
side_effect=self.map_tenant_name_side_effect)
self.is_unicode_enabled_p = mock.patch(
IS_UNICODE_ENABLED,
side_effect=self.is_unicode_enabled_side_effect)
self.lib_rpc_transport_p = mock.patch(LIB_RPC_TRANSPORT)
# start all mock patches
self.log_exc_p.start()
@@ -104,7 +116,8 @@ class BigSwitchTestBase(object):
self.watch_p.start()
self.dhcp_notifier_p.start()
self.ksclient_p.start()
self.map_tenant_name_p.start()
self.map_display_name_or_tenant_p.start()
self.is_unicode_enabled_p.start()
self.lib_rpc_transport_p.start()

def startHttpPatch(self):

+ 107
- 0
networking_bigswitch/tests/unit/bigswitch/test_restproxy_plugin.py Ver fichero

@@ -29,6 +29,8 @@ from neutron_lib.plugins import directory

from networking_bigswitch.plugins.bigswitch import config as pl_config
from networking_bigswitch.plugins.bigswitch import constants as bsn_constants
from networking_bigswitch.plugins.bigswitch.servermanager import\
TenantIDNotFound
from networking_bigswitch.tests.unit.bigswitch import fake_server
from networking_bigswitch.tests.unit.bigswitch \
import test_base as bsn_test_base
@@ -36,6 +38,8 @@ from networking_bigswitch.tests.unit.bigswitch \
patch = mock.patch
HTTPCON = ('networking_bigswitch.plugins.bigswitch.servermanager.httplib'
'.HTTPConnection')
IS_UNICODE_ENABLED = ('networking_bigswitch.plugins.bigswitch.servermanager.'
'ServerPool.is_unicode_enabled')


class BigSwitchProxyPluginV2TestCase(bsn_test_base.BigSwitchTestBase,
@@ -340,6 +344,109 @@ class TestBigSwitchProxySync(BigSwitchProxyPluginV2TestCase):
self.assertEqual(result[0], 200)


class TestDisplayName(BigSwitchProxyPluginV2TestCase):
def get_true(self):
"""Used for side_effect replacement

:return:
"""
return True

def test_map_display_name_or_tenant_unicode_disabled(self):
"""Test _map_display_name_or_tenant behaviors when unicode is disabled

:return:
"""
self.map_display_name_or_tenant_p.stop()
plugin_obj = directory.get_plugin()

self.assertFalse(plugin_obj.servers.is_unicode_enabled())

# object with non-existing tenant_id
no_tenant_obj = {'id': 'test_id',
'name': 'test_name',
'tenant_id': 'non_exist_tenant_id'}

self.assertRaises(TenantIDNotFound,
plugin_obj._map_display_name_or_tenant,
no_tenant_obj)

# add a tenant to cache
plugin_obj.servers.keystone_tenants = {'tenant_id': 'tenant_name'}

# object with name, '_' in name will be replaced with '__'
test_obj = {'id': 'test_id',
'name': 'test_name',
'tenant_id': 'tenant_id'}

expected_obj = {'id': 'test_id',
'name': 'test__name',
'tenant_id': 'tenant_id',
'tenant_name': 'tenant_name'}

self.assertEqual(expected_obj,
plugin_obj._map_display_name_or_tenant(test_obj))

# object without name
test_obj = {'id': 'test_id',
'tenant_id': 'tenant_id'}

expected_obj = {'id': 'test_id',
'tenant_id': 'tenant_id',
'tenant_name': 'tenant_name'}

self.assertEqual(expected_obj,
plugin_obj._map_display_name_or_tenant(test_obj))

def test_map_display_name_or_tenant_unicode_enabled(self):
"""Test _map_display_name_or_tenant behaviors when unicode is enabled

:return:
"""
self.map_display_name_or_tenant_p.stop()
self.is_unicode_enabled_p.stop()
mock.patch(IS_UNICODE_ENABLED, side_effect=self.get_true).start()
plugin_obj = directory.get_plugin()

self.assertTrue(plugin_obj.servers.is_unicode_enabled())

# object with non-existing tenant_id, unicode enabled
no_tenant_obj = {'id': 'test_id',
'name': 'test_name',
'tenant_id': 'non_exist_tenant_id'}

self.assertRaises(TenantIDNotFound,
plugin_obj._map_display_name_or_tenant,
no_tenant_obj)

# add a tenant to cache
plugin_obj.servers.keystone_tenants = {'tenant_id': 'tenant_name'}

# object with name, unicode enabled
test_obj = {'id': 'test_id',
'name': 'test_name',
'tenant_id': 'tenant_id'}

expected_obj = {'id': 'test_id',
'name': 'test_id',
'display-name': 'test_name',
'tenant_id': 'tenant_id'}

self.assertEqual(expected_obj,
plugin_obj._map_display_name_or_tenant(test_obj))

# object without name, unicode enabled
test_obj = {'id': 'test_id',
'tenant_id': 'tenant_id'}

expected_obj = {'id': 'test_id',
'name': 'test_id',
'tenant_id': 'tenant_id'}

self.assertEqual(expected_obj,
plugin_obj._map_display_name_or_tenant(test_obj))


class TestBigSwitchAddressPairs(test_addr_pair.TestAllowedAddressPairs,
BigSwitchProxyPluginV2TestCase):
def test_create_missing_mac_field(self):

+ 68
- 8
networking_bigswitch/tests/unit/bigswitch/test_servermanager.py Ver fichero

@@ -31,6 +31,7 @@ SERVERMANAGER = 'networking_bigswitch.plugins.bigswitch.servermanager'
CONSISTENCYDB = 'networking_bigswitch.plugins.bigswitch.db.consistency_db'
HTTPCON = SERVERMANAGER + '.httplib.HTTPConnection'
HTTPSCON = SERVERMANAGER + '.HTTPSConnectionWithValidation'
SERVER_GET_CAPABILITIES = SERVERMANAGER + '.ServerPool.get_capabilities'


class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
@@ -87,8 +88,9 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):

def test_consistency_watchdog(self):
pl = directory.get_plugin()
pl.servers.capabilities = []
pl.servers.capabilities = ['dummy']
self.watch_p.stop()

with mock.patch('eventlet.sleep') as smock,\
mock.patch(
SERVERMANAGER + '.ServerPool.rest_call',
@@ -102,6 +104,7 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
# should return immediately without consistency capability
pl.servers._consistency_watchdog()
self.assertFalse(smock.called)

pl.servers.capabilities = ['consistency']
self.assertRaises(KeyError,
pl.servers._consistency_watchdog)
@@ -187,14 +190,18 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):

# each server will get different capabilities
rv.read.side_effect = ['["a","b","c"]', '["b","c","d"]']
# pool capabilities is intersection between both
self.assertEqual(set(['b', 'c']), sp.get_capabilities())
# pool capabilities is union of both
# normally capabilities should be the same across all servers
# this only happens in two situations:
# 1. a server is down
# 2. during upgrade/downgrade
self.assertEqual(set(['a', 'b', 'c', 'd']), sp.get_capabilities())
self.assertEqual(2, rv.read.call_count)

# the pool should cache after the first call so no more
# HTTP calls should be made
# the pool should cache after the first call during a short period
# so no more HTTP calls should be made
rv.read.side_effect = ['["w","x","y"]', '["x","y","z"]']
self.assertEqual(set(['b', 'c']), sp.get_capabilities())
self.assertEqual(set(['a', 'b', 'c', 'd']), sp.get_capabilities())
self.assertEqual(2, rv.read.call_count)

def test_capabilities_retrieval_failure(self):
@@ -206,9 +213,9 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
rv.read.return_value = 'XXXXX'
self.assertEqual([], sp.servers[0].get_capabilities())

# One broken server should affect all capabilities
# as capabilities is empty, it should try to update capabilities
rv.read.side_effect = ['{"a": "b"}', '["b","c","d"]']
self.assertEqual(set(), sp.get_capabilities())
self.assertEqual(set(['a', 'b', 'c', 'd']), sp.get_capabilities())

def test_reconnect_on_timeout_change(self):
sp = servermanager.ServerPool()
@@ -533,6 +540,59 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
self.assertEqual(con._tunnel_port, 3128)
self.assertEqual(con.sock, self.wrap_mock())

def test_is_unicode_enabled(self):
"""Verify that unicode is enabled only when both conditions are True:

1. naming_scheme_unicode is True or empty
2. BCF capabilities include display-name

:return:
"""
self.is_unicode_enabled_p.stop()

def capability_unicode_supported():
return ['dummy', 'display-name']

def capability_unicode_unsupported():
return ['dummy']

patch_supported = mock.patch(
SERVER_GET_CAPABILITIES,
side_effect=capability_unicode_supported)

patch_unsupported = mock.patch(
SERVER_GET_CAPABILITIES,
side_effect=capability_unicode_unsupported)

# Create a server pool with default naming_scheme_unicode
# verify default value is true
sp = servermanager.ServerPool()
self.assertTrue(cfg.CONF.RESTPROXY.naming_scheme_unicode)

# config enabled, and unicode is supported on bcf
patch_supported.start()
self.assertTrue(sp.is_unicode_enabled())
patch_supported.stop()

# config enabled, but unicode is not supported on bcf
patch_unsupported.start()
self.assertFalse(sp.is_unicode_enabled())
patch_unsupported.stop()

# Recreate the server pool, as the config is read during initialization
cfg.CONF.set_override('naming_scheme_unicode', False, 'RESTPROXY')
sp = servermanager.ServerPool()

# config disabled, though unicode is supported on bcf
patch_supported.start()
self.assertFalse(sp.is_unicode_enabled())
patch_supported.stop()

# config disabled, and unicode is not supported on bcf
patch_unsupported.start()
self.assertFalse(sp.is_unicode_enabled())
patch_unsupported.stop()


class TestSockets(test_rp.BigSwitchProxyPluginV2TestCase):


Cargando…
Cancelar
Guardar