summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWeifan Fu <weifan.fu@bigswitch.com>2018-11-14 19:49:50 -0800
committerWeifan Fu <weifan.fu@bigswitch.com>2018-11-14 19:49:50 -0800
commitd5d0267ba8872c8b9df17412087cace41fae2d6a (patch)
tree98b9403e465b65dcbd30f7d6372dde87ad6475ae
parent2552fcbe0a69609a5ad0002baa2a969038053cc9 (diff)
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
Notes
Notes (review): Code-Review+2: Weifan Fu <weifan.fu@bigswitch.com> Workflow+1: Weifan Fu <weifan.fu@bigswitch.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Thu, 15 Nov 2018 04:23:34 +0000 Reviewed-on: https://review.openstack.org/616714 Project: openstack/networking-bigswitch Branch: refs/heads/master
-rw-r--r--etc/neutron/plugins/bigswitch/restproxy.ini18
-rw-r--r--networking_bigswitch/plugins/bigswitch/config.py6
-rw-r--r--networking_bigswitch/plugins/bigswitch/l3_router_plugin.py6
-rw-r--r--networking_bigswitch/plugins/bigswitch/plugin.py152
-rw-r--r--networking_bigswitch/plugins/bigswitch/servermanager.py151
-rw-r--r--networking_bigswitch/plugins/ml2/drivers/mech_bigswitch/driver.py28
-rw-r--r--networking_bigswitch/tests/unit/bigswitch/test_base.py23
-rw-r--r--networking_bigswitch/tests/unit/bigswitch/test_restproxy_plugin.py107
-rw-r--r--networking_bigswitch/tests/unit/bigswitch/test_servermanager.py76
9 files changed, 470 insertions, 97 deletions
diff --git a/etc/neutron/plugins/bigswitch/restproxy.ini b/etc/neutron/plugins/bigswitch/restproxy.ini
index 79a108d..22d1882 100644
--- a/etc/neutron/plugins/bigswitch/restproxy.ini
+++ b/etc/neutron/plugins/bigswitch/restproxy.ini
@@ -18,12 +18,19 @@
18# add_meta_server_route : True | False (default: True) 18# add_meta_server_route : True | False (default: True)
19# thread_pool_size : <int> (default: 4) 19# thread_pool_size : <int> (default: 4)
20# sync_security_groups : True | False (default: False) 20# sync_security_groups : True | False (default: False)
21# naming_scheme_unicode : True | False (default: True)
21 22
22# 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. 23# A comma separated list of BigSwitch or Floodlight servers and port numbers.
24# The plugin proxies the requests to the BigSwitch/Floodlight server, which
25# performs the networking configuration. Note that only one server is needed
26# per deployment, but you may wish to deploy multiple servers to support
27# failover.
23servers=localhost:8080 28servers=localhost:8080
24 29
25# The username and password for authenticating against the BigSwitch or Floodlight controller. 30# The authentication information for authenticating against the BigSwitch or
31# Floodlight controller. (Can be username and password, or access-token)
26# server_auth=username:password 32# server_auth=username:password
33# server_auth=access-token
27 34
28# Use SSL when connecting to the BigSwitch or Floodlight controller. 35# Use SSL when connecting to the BigSwitch or Floodlight controller.
29# server_ssl=True 36# server_ssl=True
@@ -59,7 +66,7 @@ servers=localhost:8080
59# User defined identifier for this Neutron deployment 66# User defined identifier for this Neutron deployment
60# neutron_id = 67# neutron_id =
61 68
62# Flag to decide if a route to the metadata server should be injected into the VM 69# Flag to decide if a route to the metadata server should be injected into VM
63# add_meta_server_route = True 70# add_meta_server_route = True
64 71
65# Number of threads to use to handle large volumes of port creation requests 72# Number of threads to use to handle large volumes of port creation requests
@@ -69,6 +76,11 @@ servers=localhost:8080
69# visibility. 76# visibility.
70# sync_security_groups = False 77# sync_security_groups = False
71 78
79# Whether or not to enable unicode support, if enabled, display-name are used
80# to store object names on BCF, while uuid will be used for identification on
81# BCF. (Require BCF 5.0 or above)
82# naming_scheme_unicode = True
83
72[nova] 84[nova]
73# Specify the VIF_TYPE that will be controlled on the Nova compute instances 85# Specify the VIF_TYPE that will be controlled on the Nova compute instances
74# options: ivs or ovs 86# options: ivs or ovs
diff --git a/networking_bigswitch/plugins/bigswitch/config.py b/networking_bigswitch/plugins/bigswitch/config.py
index 6ae2598..0e284ec 100644
--- a/networking_bigswitch/plugins/bigswitch/config.py
+++ b/networking_bigswitch/plugins/bigswitch/config.py
@@ -80,7 +80,11 @@ restproxy_opts = [
80 "Openstack tenants. (0 to disable)")), 80 "Openstack tenants. (0 to disable)")),
81 cfg.BoolOpt('sync_security_groups', default=False, 81 cfg.BoolOpt('sync_security_groups', default=False,
82 help=_("Sync security group info to Big Cloud Fabric for " 82 help=_("Sync security group info to Big Cloud Fabric for "
83 "enhanced Testpath visibility.")) 83 "enhanced Testpath visibility.")),
84 cfg.BoolOpt('naming_scheme_unicode', default=True,
85 help=_("Configure whether or not to configure BCF "
86 "with unicode display-name. Applicable to BCF 5.0 "
87 "onwards."))
84] 88]
85router_opts = [ 89router_opts = [
86 cfg.MultiStrOpt('tenant_default_router_rule', default=['*:any:any:permit'], 90 cfg.MultiStrOpt('tenant_default_router_rule', default=['*:any:any:permit'],
diff --git a/networking_bigswitch/plugins/bigswitch/l3_router_plugin.py b/networking_bigswitch/plugins/bigswitch/l3_router_plugin.py
index 2749505..dac265b 100644
--- a/networking_bigswitch/plugins/bigswitch/l3_router_plugin.py
+++ b/networking_bigswitch/plugins/bigswitch/l3_router_plugin.py
@@ -166,7 +166,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
166 self.txn_cache.add_transaction(router[BSN_TRANSACTION_ID], 166 self.txn_cache.add_transaction(router[BSN_TRANSACTION_ID],
167 router['id']) 167 router['id'])
168 with db_api.CONTEXT_READER.using(context): 168 with db_api.CONTEXT_READER.using(context):
169 mapped_router = self._map_tenant_name(router) 169 mapped_router = self._map_display_name_or_tenant(router)
170 mapped_router = self._map_state_and_status(mapped_router) 170 mapped_router = self._map_state_and_status(mapped_router)
171 171
172 # Does not handle external gateway and some other information 172 # Does not handle external gateway and some other information
@@ -190,7 +190,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
190 default_policy_dict = self._get_tenant_default_router_policy(tenant_id) 190 default_policy_dict = self._get_tenant_default_router_policy(tenant_id)
191 191
192 with db_api.CONTEXT_WRITER.using(context): 192 with db_api.CONTEXT_WRITER.using(context):
193 mapped_router = self._map_tenant_name(router) 193 mapped_router = self._map_display_name_or_tenant(router)
194 mapped_router = self._map_state_and_status(mapped_router) 194 mapped_router = self._map_state_and_status(mapped_router)
195 # populate external tenant_id if it is absent for external network, 195 # populate external tenant_id if it is absent for external network,
196 # This is a new work flow in kilo that user can specify external 196 # This is a new work flow in kilo that user can specify external
@@ -503,7 +503,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
503 if ext_tenant_id: 503 if ext_tenant_id:
504 updated_router[l3_apidef.EXTERNAL_GW_INFO]['tenant_id'] = ( 504 updated_router[l3_apidef.EXTERNAL_GW_INFO]['tenant_id'] = (
505 ext_tenant_id) 505 ext_tenant_id)
506 router = self._map_tenant_name(updated_router) 506 router = self._map_display_name_or_tenant(updated_router)
507 router = self._map_state_and_status(router) 507 router = self._map_state_and_status(router)
508 # look up the network on this side to save an expensive query on 508 # look up the network on this side to save an expensive query on
509 # the backend controller. 509 # the backend controller.
diff --git a/networking_bigswitch/plugins/bigswitch/plugin.py b/networking_bigswitch/plugins/bigswitch/plugin.py
index 540b845..8466253 100644
--- a/networking_bigswitch/plugins/bigswitch/plugin.py
+++ b/networking_bigswitch/plugins/bigswitch/plugin.py
@@ -203,6 +203,10 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
203 True, if obj name, obj's tenant name and name have supported chars 203 True, if obj name, obj's tenant name and name have supported chars
204 False, otherwise 204 False, otherwise
205 """ 205 """
206
207 if self.servers.is_unicode_enabled():
208 return True
209
206 if name and not servermanager.is_valid_bcf_name(name): 210 if name and not servermanager.is_valid_bcf_name(name):
207 LOG.warning('Unsupported characters in Name: %(name)s. ', 211 LOG.warning('Unsupported characters in Name: %(name)s. ',
208 {'name': name}) 212 {'name': name})
@@ -281,7 +285,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
281 [const.DEVICE_OWNER_ROUTER_GW, 285 [const.DEVICE_OWNER_ROUTER_GW,
282 const.DEVICE_OWNER_ROUTER_HA_INTF]): 286 const.DEVICE_OWNER_ROUTER_HA_INTF]):
283 continue 287 continue
284 mapped_port = self._map_tenant_name(port) 288 mapped_port = self._map_display_name_or_tenant(port)
289 if self.servers.is_unicode_enabled():
290 # remove port name so that it won't be stored in
291 # description
292 mapped_port['name'] = None
285 mapped_port = self._map_state_and_status(mapped_port) 293 mapped_port = self._map_state_and_status(mapped_port)
286 mapped_port = self._map_port_hostid(mapped_port, net) 294 mapped_port = self._map_port_hostid(mapped_port, net)
287 if not mapped_port: 295 if not mapped_port:
@@ -327,7 +335,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
327 ext_tenant_id) 335 ext_tenant_id)
328 336
329 interfaces = [] 337 interfaces = []
330 mapped_router = self._map_tenant_name(router) 338 mapped_router = self._map_display_name_or_tenant(router)
331 mapped_router = self._map_state_and_status(mapped_router) 339 mapped_router = self._map_state_and_status(mapped_router)
332 if not self._validate_names(mapped_router): 340 if not self._validate_names(mapped_router):
333 continue 341 continue
@@ -368,13 +376,16 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
368 new_sgs = [] 376 new_sgs = []
369 for sg in sgs: 377 for sg in sgs:
370 try: 378 try:
371 mapped_sg = self._map_tenant_name(sg) 379 mapped_sg = self._map_display_name_or_tenant(sg)
372 if not self._validate_names(mapped_sg): 380 if not self._validate_names(mapped_sg):
373 continue 381 continue
374 if 'description' in mapped_sg: 382 if 'description' in mapped_sg:
375 mapped_sg['description'] = '' 383 mapped_sg['description'] = ''
376 mapped_sg['name'] = Util.format_resource_name( 384 if self.servers.is_unicode_enabled():
377 mapped_sg['name']) 385 mapped_sg['name'] = None
386 else:
387 mapped_sg['name'] = Util.format_resource_name(
388 mapped_sg['name'])
378 new_sgs.append(mapped_sg) 389 new_sgs.append(mapped_sg)
379 except servermanager.TenantIDNotFound: 390 except servermanager.TenantIDNotFound:
380 # if tenant name is not known to keystone, skip the sg 391 # if tenant name is not known to keystone, skip the sg
@@ -383,13 +394,27 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
383 data.update({'security-groups': new_sgs}) 394 data.update({'security-groups': new_sgs})
384 395
385 all_tenants_map = self.servers.keystone_tenants 396 all_tenants_map = self.servers.keystone_tenants
386 tenants = {}
387 for tenant in all_tenants_map:
388 if not self._validate_names(None, name=all_tenants_map[tenant]):
389 continue
390 tenants[tenant] = all_tenants_map[tenant]
391 397
392 data.update({'tenants': tenants}) 398 if self.servers.is_unicode_enabled():
399 # display-name is only supported as list for topology in NSAPI
400 tenants = []
401 for tenant_id, tenant_name in all_tenants_map.items():
402 tenants.append({
403 'name': tenant_id,
404 'id': tenant_id,
405 'display-name': tenant_name
406 })
407 else:
408 # dict for tenant works in topology sync only if display-name is
409 # not enabled
410 tenants = {}
411 for tenant in all_tenants_map:
412 if not self._validate_names(None,
413 name=all_tenants_map[tenant]):
414 continue
415 tenants[tenant] = all_tenants_map[tenant]
416
417 data['tenants'] = tenants
393 return data 418 return data
394 419
395 def _send_all_data_auto(self, timeout=None, triggered_by_tenant=None): 420 def _send_all_data_auto(self, timeout=None, triggered_by_tenant=None):
@@ -416,9 +441,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
416 def _assign_resource_to_service_tenant(self, resource): 441 def _assign_resource_to_service_tenant(self, resource):
417 resource['tenant_id'] = (resource['tenant_id'] or 442 resource['tenant_id'] = (resource['tenant_id'] or
418 servermanager.SERVICE_TENANT) 443 servermanager.SERVICE_TENANT)
419 if resource.get('name'): 444
420 # resource name may contain space. Replace space with - 445 if not self.servers.is_unicode_enabled():
421 resource['name'] = Util.format_resource_name(resource['name']) 446 if resource.get('name'):
447 # resource name may contain space. Replace space with -
448 resource['name'] = Util.format_resource_name(resource['name'])
422 449
423 def _get_network_with_floatingips(self, network, context=None): 450 def _get_network_with_floatingips(self, network, context=None):
424 if context is None: 451 if context is None:
@@ -433,9 +460,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
433 for flip in fl_ips: 460 for flip in fl_ips:
434 try: 461 try:
435 # BVS-7525: the 'tenant_id' in a floating-ip represents the 462 # BVS-7525: the 'tenant_id' in a floating-ip represents the
436 # tenant to which it is allocated. Validate that the 463 # tenant to which it is allocated.
437 # tenant exists 464 # Validate that the tenant exists
438 mapped_flip = self._map_tenant_name(flip) 465 # name/display-name of floating ip is not actually
466 # used on bcf
467 mapped_flip = self._map_display_name_or_tenant(flip)
439 if mapped_flip.get('floating_port_id'): 468 if mapped_flip.get('floating_port_id'):
440 fport = self.get_port(context, 469 fport = self.get_port(context,
441 mapped_flip['floating_port_id']) 470 mapped_flip['floating_port_id'])
@@ -461,7 +490,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
461 if subnets: 490 if subnets:
462 for subnet in subnets: 491 for subnet in subnets:
463 subnet_dict = self._make_subnet_dict(subnet, context=context) 492 subnet_dict = self._make_subnet_dict(subnet, context=context)
464 mapped_subnet = self._map_tenant_name(subnet_dict) 493 mapped_subnet = self._map_display_name_or_tenant(subnet_dict)
465 mapped_subnet = self._map_state_and_status(mapped_subnet) 494 mapped_subnet = self._map_state_and_status(mapped_subnet)
466 subnets_details.append(mapped_subnet) 495 subnets_details.append(mapped_subnet)
467 496
@@ -473,11 +502,15 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
473 This network is not associated with any tenant 502 This network is not associated with any tenant
474 """ 503 """
475 sg['tenant_id'] = sg['tenant_id'] or servermanager.SERVICE_TENANT 504 sg['tenant_id'] = sg['tenant_id'] or servermanager.SERVICE_TENANT
476 sg['tenant_name'] = self.servers.keystone_tenants.get(sg['tenant_id']) 505 tenant_name = self.servers.keystone_tenants.get(sg['tenant_id'])
477 if not sg['tenant_name']: 506
507 if not tenant_name:
478 self.servers._update_tenant_cache(reconcile=True) 508 self.servers._update_tenant_cache(reconcile=True)
479 tenant_name = self.servers.keystone_tenants.get(sg['tenant_id']) 509 tenant_name = self.servers.keystone_tenants.get(sg['tenant_id'])
510
511 if not self.servers.is_unicode_enabled():
480 sg['tenant_name'] = tenant_name 512 sg['tenant_name'] = tenant_name
513 return tenant_name
481 514
482 def bsn_create_security_group(self, sg_id=None, sg=None, context=None): 515 def bsn_create_security_group(self, sg_id=None, sg=None, context=None):
483 if sg_id: 516 if sg_id:
@@ -487,14 +520,19 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
487 sg = self.get_security_group(context, sg_id) 520 sg = self.get_security_group(context, sg_id)
488 521
489 if sg: 522 if sg:
490 sg['name'] = Util.format_resource_name(sg['name']) 523 if self.servers.is_unicode_enabled():
524 sg['display-name'] = sg['name']
525 sg['name'] = None
526 else:
527 sg['name'] = Util.format_resource_name(sg['name'])
491 # remove description as its not used 528 # remove description as its not used
492 if 'description' in sg: 529 if sg.get('description'):
493 sg['description'] = '' 530 del(sg['description'])
494 self._tenant_check_for_security_group(sg) 531 # check and map tenant_name for sg
532 tenant_name = self._tenant_check_for_security_group(sg)
495 # skip the security group if its tenant is unknown 533 # skip the security group if its tenant is unknown
496 if sg['tenant_name']: 534 if tenant_name:
497 if sg['tenant_name'] == servermanager.SERVICE_TENANT: 535 if tenant_name == servermanager.SERVICE_TENANT:
498 self.bsn_create_tenant(servermanager.SERVICE_TENANT, 536 self.bsn_create_tenant(servermanager.SERVICE_TENANT,
499 context=context) 537 context=context)
500 self.servers.rest_create_securitygroup(sg) 538 self.servers.rest_create_securitygroup(sg)
@@ -511,14 +549,15 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
511 self.servers.rest_delete_tenant(tenant_id) 549 self.servers.rest_delete_tenant(tenant_id)
512 550
513 def _verify_network_precommit(self, context): 551 def _verify_network_precommit(self, context):
514 if context.current['name'] != context.original['name']: 552 if not self.servers.is_unicode_enabled():
515 raise servermanager.NetworkNameChangeError() 553 if context.current['name'] != context.original['name']:
554 raise servermanager.NetworkNameChangeError()
516 555
517 def _get_mapped_network_with_subnets(self, network, context=None): 556 def _get_mapped_network_with_subnets(self, network, context=None):
518 # if context is not provided, admin context is used 557 # if context is not provided, admin context is used
519 if context is None: 558 if context is None:
520 context = qcontext.get_admin_context() 559 context = qcontext.get_admin_context()
521 network = self._map_tenant_name(network) 560 network = self._map_display_name_or_tenant(network)
522 network = self._map_state_and_status(network) 561 network = self._map_state_and_status(network)
523 subnets = self._get_all_subnets_json_for_network(network['id'], 562 subnets = self._get_all_subnets_json_for_network(network['id'],
524 context) 563 context)
@@ -537,6 +576,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
537 # OSP-45: remove name to avoid NSAPI error in convertToAscii 576 # OSP-45: remove name to avoid NSAPI error in convertToAscii
538 for subnet in (subnets or []): 577 for subnet in (subnets or []):
539 subnet.pop('name', None) 578 subnet.pop('name', None)
579
540 return network 580 return network
541 581
542 def _skip_bcf_network_event(self, network): 582 def _skip_bcf_network_event(self, network):
@@ -568,14 +608,16 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
568 if default_group: 608 if default_group:
569 # VRRP tenant doesn't have tenant_id 609 # VRRP tenant doesn't have tenant_id
570 self.bsn_create_security_group(sg=default_group[0]) 610 self.bsn_create_security_group(sg=default_group[0])
611 # display-name is also mapped here
571 mapped_network = self._get_mapped_network_with_subnets(network, 612 mapped_network = self._get_mapped_network_with_subnets(network,
572 context) 613 context)
573 614
574 if not tenant_id: 615 if not tenant_id:
575 tenant_id = servermanager.SERVICE_TENANT 616 tenant_id = servermanager.SERVICE_TENANT
576 mapped_network['tenant_id'] = servermanager.SERVICE_TENANT 617 mapped_network['tenant_id'] = servermanager.SERVICE_TENANT
577 mapped_network['name'] = Util.format_resource_name( 618 if not self.servers.is_unicode_enabled():
578 mapped_network['name']) 619 mapped_network['name'] = Util.format_resource_name(
620 mapped_network['name'])
579 self.bsn_create_tenant(servermanager.SERVICE_TENANT, 621 self.bsn_create_tenant(servermanager.SERVICE_TENANT,
580 context=context) 622 context=context)
581 self.servers.rest_create_network(tenant_id, mapped_network) 623 self.servers.rest_create_network(tenant_id, mapped_network)
@@ -588,6 +630,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
588 {'name': network.get('name')}) 630 {'name': network.get('name')})
589 return 631 return
590 632
633 # display-name is also mapped here
591 mapped_network = self._get_mapped_network_with_subnets(network, 634 mapped_network = self._get_mapped_network_with_subnets(network,
592 context) 635 context)
593 net_fl_ips = self._get_network_with_floatingips(mapped_network, 636 net_fl_ips = self._get_network_with_floatingips(mapped_network,
@@ -595,8 +638,9 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
595 if not tenant_id: 638 if not tenant_id:
596 tenant_id = servermanager.SERVICE_TENANT 639 tenant_id = servermanager.SERVICE_TENANT
597 net_fl_ips['tenant_id'] = servermanager.SERVICE_TENANT 640 net_fl_ips['tenant_id'] = servermanager.SERVICE_TENANT
598 net_fl_ips['name'] = Util.format_resource_name( 641 if not self.servers.is_unicode_enabled():
599 net_fl_ips['name']) 642 net_fl_ips['name'] = Util.format_resource_name(
643 net_fl_ips['name'])
600 self.servers.rest_update_network(tenant_id, net_id, net_fl_ips) 644 self.servers.rest_update_network(tenant_id, net_id, net_fl_ips)
601 645
602 def _send_delete_network(self, network, context=None): 646 def _send_delete_network(self, network, context=None):
@@ -604,22 +648,35 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
604 tenant_id = network['tenant_id'] or servermanager.SERVICE_TENANT 648 tenant_id = network['tenant_id'] or servermanager.SERVICE_TENANT
605 self.servers.rest_delete_network(tenant_id, net_id) 649 self.servers.rest_delete_network(tenant_id, net_id)
606 650
607 def _map_tenant_name(self, resource): 651 def _map_display_name_or_tenant(self, resource):
608 resource = copy.copy(resource) 652 """This maps tenant_name or display-name for an object
653
654 None-unicode mode uses tenant_name
655 Unicode mode uses tenant_id and display-name
656
657 :param resource: object to be mapped
658 :return: mapped object copy
659 """
660 resource = copy.deepcopy(resource)
609 self._assign_resource_to_service_tenant(resource) 661 self._assign_resource_to_service_tenant(resource)
662
610 tenant_name = self.servers.keystone_tenants.get(resource['tenant_id']) 663 tenant_name = self.servers.keystone_tenants.get(resource['tenant_id'])
611 if tenant_name: 664 if not tenant_name:
612 resource['tenant_name'] = tenant_name
613 else:
614 self.servers._update_tenant_cache() 665 self.servers._update_tenant_cache()
615 tenant_name = self.servers.keystone_tenants.get( 666 tenant_name = self.servers.keystone_tenants.get(
616 resource['tenant_id']) 667 resource['tenant_id'])
617 if tenant_name: 668 if not tenant_name:
618 resource['tenant_name'] = tenant_name
619 else:
620 raise servermanager.TenantIDNotFound( 669 raise servermanager.TenantIDNotFound(
621 tenant=resource['tenant_id']) 670 tenant=resource['tenant_id'])
622 671
672 if self.servers.is_unicode_enabled():
673 if resource.get('name'):
674 resource['display-name'] = resource['name']
675 # cases like network needs the name on bcf side
676 resource['name'] = resource['id']
677 else:
678 resource['tenant_name'] = tenant_name
679
623 return resource 680 return resource
624 681
625 def _map_state_and_status(self, resource): 682 def _map_state_and_status(self, resource):
@@ -795,7 +852,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
795 net_id = subnet['network_id'] 852 net_id = subnet['network_id']
796 network = self.get_network(context, net_id) 853 network = self.get_network(context, net_id)
797 mapped_network = self._get_mapped_network_with_subnets(network) 854 mapped_network = self._get_mapped_network_with_subnets(network)
798 mapped_subnet = self._map_tenant_name(subnet) 855 mapped_subnet = self._map_display_name_or_tenant(subnet)
799 mapped_subnet = self._map_state_and_status(mapped_subnet) 856 mapped_subnet = self._map_state_and_status(mapped_subnet)
800 857
801 data = { 858 data = {
@@ -1141,7 +1198,10 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
1141 self._add_host_route(context, destination, new_port) 1198 self._add_host_route(context, destination, new_port)
1142 1199
1143 # create on network ctrl 1200 # create on network ctrl
1144 mapped_port = self._map_tenant_name(new_port) 1201 mapped_port = self._map_display_name_or_tenant(new_port)
1202 if self.servers.is_unicode_enabled():
1203 # remove port name so that it won't be stored in description
1204 mapped_port['name'] = None
1145 mapped_port = self._map_state_and_status(mapped_port) 1205 mapped_port = self._map_state_and_status(mapped_port)
1146 # ports have to be created synchronously when creating a router 1206 # ports have to be created synchronously when creating a router
1147 # port since adding router interfaces is a multi-call process 1207 # port since adding router interfaces is a multi-call process
@@ -1230,7 +1290,11 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
1230 # tenant_id must come from network in case network is shared 1290 # tenant_id must come from network in case network is shared
1231 net_tenant_id = self._get_port_net_tenantid(context, new_port) 1291 net_tenant_id = self._get_port_net_tenantid(context, new_port)
1232 new_port = self._extend_port_dict_binding(context, new_port) 1292 new_port = self._extend_port_dict_binding(context, new_port)
1233 mapped_port = self._map_tenant_name(new_port) 1293 mapped_port = self._map_display_name_or_tenant(new_port)
1294 if self.servers.is_unicode_enabled():
1295 # remove port name so that it won't be stored in
1296 # description
1297 mapped_port['name'] = None
1234 mapped_port = self._map_state_and_status(mapped_port) 1298 mapped_port = self._map_state_and_status(mapped_port)
1235 self.servers.rest_update_port(net_tenant_id, 1299 self.servers.rest_update_port(net_tenant_id,
1236 new_port["network_id"], 1300 new_port["network_id"],
diff --git a/networking_bigswitch/plugins/bigswitch/servermanager.py b/networking_bigswitch/plugins/bigswitch/servermanager.py
index 622d85c..c6b54c4 100644
--- a/networking_bigswitch/plugins/bigswitch/servermanager.py
+++ b/networking_bigswitch/plugins/bigswitch/servermanager.py
@@ -158,7 +158,7 @@ class NetworkNameChangeError(exceptions.NeutronException):
158 158
159 159
160class RemoteRestError(exceptions.NeutronException): 160class RemoteRestError(exceptions.NeutronException):
161 message = _("Error in REST call to remote network " 161 message = _("Error in REST call to BCF "
162 "controller: %(reason)s") 162 "controller: %(reason)s")
163 status = None 163 status = None
164 164
@@ -285,8 +285,8 @@ class ServerProxy(object):
285 if body: 285 if body:
286 self.capabilities = jsonutils.loads(body) 286 self.capabilities = jsonutils.loads(body)
287 except Exception: 287 except Exception:
288 LOG.exception("Couldn't retrieve capabilities. " 288 LOG.exception("Couldn't retrieve capabilities on server "
289 "Newer API calls won't be supported.") 289 "%(server)s. ", {'server': self.server})
290 LOG.info("The following capabilities were received " 290 LOG.info("The following capabilities were received "
291 "for %(server)s: %(cap)s", 291 "for %(server)s: %(cap)s",
292 {'server': self.server, 'cap': self.capabilities}) 292 {'server': self.server, 'cap': self.capabilities})
@@ -417,6 +417,9 @@ class ServerPool(object):
417 self.auth = cfg.CONF.RESTPROXY.server_auth 417 self.auth = cfg.CONF.RESTPROXY.server_auth
418 self.ssl = cfg.CONF.RESTPROXY.server_ssl 418 self.ssl = cfg.CONF.RESTPROXY.server_ssl
419 self.neutron_id = cfg.CONF.RESTPROXY.neutron_id 419 self.neutron_id = cfg.CONF.RESTPROXY.neutron_id
420 # unicode config
421 self.cfg_unicode_enabled = cfg.CONF.RESTPROXY.naming_scheme_unicode
422
420 if 'keystone_authtoken' in cfg.CONF: 423 if 'keystone_authtoken' in cfg.CONF:
421 self.auth_user = get_keystoneauth_cfg(cfg.CONF, 'username') 424 self.auth_user = get_keystoneauth_cfg(cfg.CONF, 'username')
422 self.auth_password = get_keystoneauth_cfg(cfg.CONF, 'password') 425 self.auth_password = get_keystoneauth_cfg(cfg.CONF, 'password')
@@ -453,6 +456,7 @@ class ServerPool(object):
453 self._update_tenant_cache(reconcile=False) 456 self._update_tenant_cache(reconcile=False)
454 self.timeout = cfg.CONF.RESTPROXY.server_timeout 457 self.timeout = cfg.CONF.RESTPROXY.server_timeout
455 self.always_reconnect = not cfg.CONF.RESTPROXY.cache_connections 458 self.always_reconnect = not cfg.CONF.RESTPROXY.cache_connections
459 self.capabilities = []
456 default_port = 8000 460 default_port = 8000
457 if timeout is not False: 461 if timeout is not False:
458 self.timeout = timeout 462 self.timeout = timeout
@@ -480,12 +484,20 @@ class ServerPool(object):
480 server = server[1:-1] 484 server = server[1:-1]
481 self.servers.append(self.server_proxy_for(server, int(port))) 485 self.servers.append(self.server_proxy_for(server, int(port)))
482 self.start_background_tasks() 486 self.start_background_tasks()
487
483 ServerPool._instance = self 488 ServerPool._instance = self
489
484 LOG.debug("ServerPool: initialization done") 490 LOG.debug("ServerPool: initialization done")
485 491
486 def start_background_tasks(self): 492 def start_background_tasks(self):
493 # update capabilities, starts immediately
494 # updates every 5 minutes, mostly for bcf upgrade/downgrade cases
495 eventlet.spawn(self._capability_watchdog, 300)
496
497 # consistency check, starts after 1* consistency_interval
487 eventlet.spawn(self._consistency_watchdog, 498 eventlet.spawn(self._consistency_watchdog,
488 cfg.CONF.RESTPROXY.consistency_interval) 499 cfg.CONF.RESTPROXY.consistency_interval)
500
489 # Start keystone sync thread after 5 consistency sync 501 # Start keystone sync thread after 5 consistency sync
490 # to give enough time for topology to sync over when 502 # to give enough time for topology to sync over when
491 # neutron-server starts. 503 # neutron-server starts.
@@ -495,19 +507,39 @@ class ServerPool(object):
495 cfg.CONF.RESTPROXY.keystone_sync_interval) 507 cfg.CONF.RESTPROXY.keystone_sync_interval)
496 508
497 def get_capabilities(self): 509 def get_capabilities(self):
510 """Get capabilities
511
512 If cache has the value, use it
513 If Not, do REST calls to BCF controllers to check it
514
515 :return: supported capability list
516 """
498 # lookup on first try 517 # lookup on first try
499 try: 518 # if capabilities is empty, the check is either not done, or failed
519 if self.capabilities:
500 return self.capabilities 520 return self.capabilities
501 except AttributeError: 521 else:
502 # this exception is hit when the capabilities haven't been 522 return self.get_capabilities_force_update()
503 # looked up yet 523
504 pass 524 def get_capabilities_force_update(self):
505 # each server should return a list of capabilities it supports 525 """Do REST calls to update capabilities
506 # e.g. ['floatingip'] 526
507 capabilities = [set(server.get_capabilities()) 527 Logs a unicode change message when:
508 for server in self.servers] 528 1. the first time that plugin gets capabilities from BCF
509 # Pool only supports what all of the servers support 529 2. plugin notices the unicode mode is changed
510 self.capabilities = set.intersection(*capabilities) 530
531 :return: combined capability list from all servers
532 """
533 # Servers should be the same version
534 # If one server is down, use online server's capabilities
535 capability_list = [set(server.get_capabilities())
536 for server in self.servers]
537
538 new_capabilities = set.union(*capability_list)
539
540 self.log_unicode_status_change(new_capabilities)
541 self.capabilities = new_capabilities
542
511 # With multiple workers enabled, the fork may occur after the 543 # With multiple workers enabled, the fork may occur after the
512 # connections to the DB have been established. We need to clear the 544 # connections to the DB have been established. We need to clear the
513 # connections after the first attempt to call the backend to ensure 545 # connections after the first attempt to call the backend to ensure
@@ -522,8 +554,60 @@ class ServerPool(object):
522 # ec716b9e68b8b66a88218913ae4c9aa3a26b025a/neutron/wsgi.py#L104 554 # ec716b9e68b8b66a88218913ae4c9aa3a26b025a/neutron/wsgi.py#L104
523 if cdb.HashHandler._FACADE: 555 if cdb.HashHandler._FACADE:
524 cdb.HashHandler._FACADE.get_engine().pool.dispose() 556 cdb.HashHandler._FACADE.get_engine().pool.dispose()
557
558 if not new_capabilities:
559 LOG.error('Failed to get capabilities on any controller. ')
525 return self.capabilities 560 return self.capabilities
526 561
562 def log_unicode_status_change(self, new_capabilities):
563 """Log unicode status, if capabilities is initialized or if changed
564
565 Compares old capabilities with new capabilities
566 :param new_capabilities: new capabilities
567 :return:
568 """
569 if new_capabilities and self.capabilities != new_capabilities:
570 # unicode disabled by user
571 if not self.cfg_unicode_enabled:
572 # Log only during Initialization
573 if not self.capabilities:
574 LOG.info('naming_scheme_unicode is set to False,'
575 ' Unicode names Disabled')
576 # unicode enabled and supported by controller
577 elif 'display-name' in new_capabilities:
578 # Log for 2 situations:
579 # 1. Initialization
580 # 2. BCF is upgraded to support unicode
581 if 'display-name' not in self.capabilities:
582 LOG.info('naming_scheme_unicode is set to True,'
583 ' Unicode names Enabled')
584 # unicode enabled, but not supported by controller
585 else:
586 # Log for 2 situations:
587 # 1. Initialization
588 # 2. BCF is downgraded, no longer supports unicode
589 if not self.capabilities or 'display-name' in \
590 self.capabilities:
591 LOG.warning('naming_scheme_unicode is set to True,'
592 ' but BCF does not support it.'
593 ' Unicode names Disabled')
594
595 def is_unicode_enabled(self):
596 """Check unicode running status
597
598 True: enabled
599 False: disabled
600 """
601 if not self.get_capabilities():
602 msg = 'Capabilities unknown! Please check BCF controller status.'
603 raise RemoteRestError(reason=msg)
604
605 if self.cfg_unicode_enabled and 'display-name' in \
606 self.get_capabilities():
607 return True
608 else:
609 return False
610
527 def server_proxy_for(self, server, port): 611 def server_proxy_for(self, server, port):
528 combined_cert = self._get_combined_cert_for_server(server, port) 612 combined_cert = self._get_combined_cert_for_server(server, port)
529 return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id, 613 return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id,
@@ -760,13 +844,16 @@ class ServerPool(object):
760 if not tenant_name: 844 if not tenant_name:
761 raise TenantIDNotFound(tenant=tenant_id) 845 raise TenantIDNotFound(tenant=tenant_id)
762 846
763 if not is_valid_bcf_name(tenant_name): 847 if self.is_unicode_enabled():
764 raise UnsupportedNameException(obj_type=ObjTypeEnum.tenant, 848 data = {"tenant_id": tenant_id, 'tenant_name': tenant_id,
765 obj_id=tenant_id, 849 'display-name': tenant_name}
766 obj_name=tenant_name) 850 else:
767 851 if not is_valid_bcf_name(tenant_name):
852 raise UnsupportedNameException(obj_type=ObjTypeEnum.tenant,
853 obj_id=tenant_id,
854 obj_name=tenant_name)
855 data = {"tenant_id": tenant_id, 'tenant_name': tenant_name}
768 resource = TENANT_RESOURCE_PATH 856 resource = TENANT_RESOURCE_PATH
769 data = {"tenant_id": tenant_id, 'tenant_name': tenant_name}
770 errstr = _("Unable to create tenant: %s") 857 errstr = _("Unable to create tenant: %s")
771 self.rest_action('POST', resource, data, errstr) 858 self.rest_action('POST', resource, data, errstr)
772 859
@@ -939,7 +1026,7 @@ class ServerPool(object):
939 def _consistency_watchdog(self, polling_interval=60): 1026 def _consistency_watchdog(self, polling_interval=60):
940 if 'consistency' not in self.get_capabilities(): 1027 if 'consistency' not in self.get_capabilities():
941 LOG.warning("Backend server(s) do not support automated " 1028 LOG.warning("Backend server(s) do not support automated "
942 "consitency checks.") 1029 "consistency checks.")
943 return 1030 return
944 if not polling_interval: 1031 if not polling_interval:
945 LOG.warning("Consistency watchdog disabled by polling " 1032 LOG.warning("Consistency watchdog disabled by polling "
@@ -957,6 +1044,19 @@ class ServerPool(object):
957 LOG.exception("Encountered an error checking controller " 1044 LOG.exception("Encountered an error checking controller "
958 "health.") 1045 "health.")
959 1046
1047 def _capability_watchdog(self, polling_interval=300):
1048 """Check capabilities based on polling_interval
1049
1050 :param polling_interval: interval in seconds
1051 """
1052 while True:
1053 try:
1054 self.get_capabilities_force_update()
1055 except Exception:
1056 LOG.exception("Encountered an error checking capabilities.")
1057 finally:
1058 eventlet.sleep(polling_interval)
1059
960 def force_topo_sync(self, check_ts=True): 1060 def force_topo_sync(self, check_ts=True):
961 """Execute a topology_sync between OSP and BCF. 1061 """Execute a topology_sync between OSP and BCF.
962 1062
@@ -1050,8 +1150,13 @@ class ServerPool(object):
1050 sess = session.Session(auth=auth) 1150 sess = session.Session(auth=auth)
1051 keystone_client = ksclient.Client(session=sess) 1151 keystone_client = ksclient.Client(session=sess)
1052 tenants = keystone_client.projects.list() 1152 tenants = keystone_client.projects.list()
1053 new_cached_tenants = {tn.id: Util.format_resource_name(tn.name) 1153
1054 for tn in tenants} 1154 if self.is_unicode_enabled():
1155 new_cached_tenants = {tn.id: tn.name
1156 for tn in tenants}
1157 else:
1158 new_cached_tenants = {tn.id: Util.format_resource_name(tn.name)
1159 for tn in tenants}
1055 # Add SERVICE_TENANT to handle hidden network for VRRP 1160 # Add SERVICE_TENANT to handle hidden network for VRRP
1056 new_cached_tenants[SERVICE_TENANT] = SERVICE_TENANT 1161 new_cached_tenants[SERVICE_TENANT] = SERVICE_TENANT
1057 1162
diff --git a/networking_bigswitch/plugins/ml2/drivers/mech_bigswitch/driver.py b/networking_bigswitch/plugins/ml2/drivers/mech_bigswitch/driver.py
index 418d9ab..4338b7b 100644
--- a/networking_bigswitch/plugins/ml2/drivers/mech_bigswitch/driver.py
+++ b/networking_bigswitch/plugins/ml2/drivers/mech_bigswitch/driver.py
@@ -240,24 +240,28 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
240 # we retain this section for security groups, because it handles 240 # we retain this section for security groups, because it handles
241 # other events as well. Ignore security group events if disabled in 241 # other events as well. Ignore security group events if disabled in
242 # config 242 # config
243 if (event_type == 'security_group.create.end' and 243 if event_type == 'security_group.create.end':
244 cfg.CONF.RESTPROXY.sync_security_groups):
245 LOG.debug("Security group created: %s", payload) 244 LOG.debug("Security group created: %s", payload)
246 self.bsn_create_security_group(sg=payload['security_group']) 245 if cfg.CONF.RESTPROXY.sync_security_groups:
247 elif (event_type == 'security_group.delete.end' and 246 self.bsn_create_security_group(sg=payload['security_group'])
248 cfg.CONF.RESTPROXY.sync_security_groups): 247 elif event_type == 'security_group.delete.end':
249 LOG.debug("Security group deleted: %s", payload) 248 LOG.debug("Security group deleted: %s", payload)
250 self.bsn_delete_security_group(payload['security_group_id']) 249 if cfg.CONF.RESTPROXY.sync_security_groups:
251 elif (event_type == 'security_group_rule.delete.end' and 250 self.bsn_delete_security_group(payload['security_group_id'])
252 cfg.CONF.RESTPROXY.sync_security_groups): 251 elif event_type == 'security_group_rule.delete.end':
253 LOG.debug("Security group rule deleted: %s", payload) 252 LOG.debug("Security group rule deleted: %s", payload)
254 self.bsn_delete_sg_rule(payload['security_group_rule'], ctxt) 253 if cfg.CONF.RESTPROXY.sync_security_groups:
254 self.bsn_delete_sg_rule(payload['security_group_rule'], ctxt)
255 elif event_type == 'identity.project.deleted': 255 elif event_type == 'identity.project.deleted':
256 LOG.debug("Project deleted: %s", payload) 256 LOG.debug("Project deleted: %s", payload)
257 self.bsn_delete_tenant(payload['resource_info']) 257 self.bsn_delete_tenant(payload['resource_info'])
258 elif event_type == 'identity.project.created': 258 elif event_type == 'identity.project.created':
259 LOG.debug("Project created: %s", payload) 259 LOG.debug("Project created: %s", payload)
260 self.bsn_create_tenant(payload['resource_info']) 260 self.bsn_create_tenant(payload['resource_info'])
261 elif event_type == 'identity.project.updated':
262 LOG.debug("Project updated: %s", payload)
263 # update is the same as create, nsapi will handle it
264 self.bsn_create_tenant(payload['resource_info'])
261 else: 265 else:
262 LOG.debug("Else events: %s payload: %s", (event_type, payload)) 266 LOG.debug("Else events: %s payload: %s", (event_type, payload))
263 267
@@ -419,7 +423,11 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
419 net = context.network.current 423 net = context.network.current
420 port['network'] = net 424 port['network'] = net
421 port['bound_segment'] = context.top_bound_segment 425 port['bound_segment'] = context.top_bound_segment
422 prepped_port = self._map_tenant_name(port) 426 prepped_port = self._map_display_name_or_tenant(port)
427 if prepped_port.get('description'):
428 del (prepped_port['description'])
429 if self.servers.is_unicode_enabled():
430 prepped_port['name'] = None
423 prepped_port = self._map_state_and_status(prepped_port) 431 prepped_port = self._map_state_and_status(prepped_port)
424 prepped_port = self._map_port_hostid(prepped_port, net) 432 prepped_port = self._map_port_hostid(prepped_port, net)
425 return prepped_port 433 return prepped_port
diff --git a/networking_bigswitch/tests/unit/bigswitch/test_base.py b/networking_bigswitch/tests/unit/bigswitch/test_base.py
index 781f384..e2cd8ca 100644
--- a/networking_bigswitch/tests/unit/bigswitch/test_base.py
+++ b/networking_bigswitch/tests/unit/bigswitch/test_base.py
@@ -49,8 +49,11 @@ SPAWN = ('networking_bigswitch.plugins.bigswitch.plugin.eventlet.GreenPool'
49 '.spawn_n') 49 '.spawn_n')
50KSCLIENT = 'keystoneclient.v3.client.Client' 50KSCLIENT = 'keystoneclient.v3.client.Client'
51BACKGROUND = SERVER_MANAGER + '.ServerPool.start_background_tasks' 51BACKGROUND = SERVER_MANAGER + '.ServerPool.start_background_tasks'
52MAP_TENANT_NAME = ('networking_bigswitch.plugins.bigswitch.plugin.' 52MAP_DISPLAY_NAME_OR_TENANT = ('networking_bigswitch.plugins.bigswitch.plugin.'
53 'NeutronRestProxyV2Base._map_tenant_name') 53 'NeutronRestProxyV2Base.'
54 '_map_display_name_or_tenant')
55IS_UNICODE_ENABLED = ('networking_bigswitch.plugins.bigswitch.servermanager.'
56 'ServerPool.is_unicode_enabled')
54LIB_RPC_TRANSPORT = ('neutron_lib.rpc.TRANSPORT') 57LIB_RPC_TRANSPORT = ('neutron_lib.rpc.TRANSPORT')
55 58
56 59
@@ -80,9 +83,14 @@ class BigSwitchTestBase(object):
80 cfg.CONF.set_override('api_extensions_path', False) 83 cfg.CONF.set_override('api_extensions_path', False)
81 84
82 def map_tenant_name_side_effect(self, value): 85 def map_tenant_name_side_effect(self, value):
86 # for old tests, always map tenant name
83 value['tenant_name'] = 'tenant_name' 87 value['tenant_name'] = 'tenant_name'
84 return value 88 return value
85 89
90 def is_unicode_enabled_side_effect(self):
91 # for old tests, always return False
92 return False
93
86 def setup_patches(self): 94 def setup_patches(self):
87 self.plugin_notifier_p = mock.patch(NOTIFIER) 95 self.plugin_notifier_p = mock.patch(NOTIFIER)
88 self.dhcp_notifier_p = mock.patch(DHCP_NOTIFIER) 96 self.dhcp_notifier_p = mock.patch(DHCP_NOTIFIER)
@@ -94,8 +102,12 @@ class BigSwitchTestBase(object):
94 self.log_exc_p = mock.patch(SERVER_MANAGER + ".LOG.exception", 102 self.log_exc_p = mock.patch(SERVER_MANAGER + ".LOG.exception",
95 new=lambda *args, **kwargs: None) 103 new=lambda *args, **kwargs: None)
96 self.ksclient_p = mock.patch(KSCLIENT) 104 self.ksclient_p = mock.patch(KSCLIENT)
97 self.map_tenant_name_p = mock.patch( 105 self.map_display_name_or_tenant_p = mock.patch(
98 MAP_TENANT_NAME, side_effect=self.map_tenant_name_side_effect) 106 MAP_DISPLAY_NAME_OR_TENANT,
107 side_effect=self.map_tenant_name_side_effect)
108 self.is_unicode_enabled_p = mock.patch(
109 IS_UNICODE_ENABLED,
110 side_effect=self.is_unicode_enabled_side_effect)
99 self.lib_rpc_transport_p = mock.patch(LIB_RPC_TRANSPORT) 111 self.lib_rpc_transport_p = mock.patch(LIB_RPC_TRANSPORT)
100 # start all mock patches 112 # start all mock patches
101 self.log_exc_p.start() 113 self.log_exc_p.start()
@@ -104,7 +116,8 @@ class BigSwitchTestBase(object):
104 self.watch_p.start() 116 self.watch_p.start()
105 self.dhcp_notifier_p.start() 117 self.dhcp_notifier_p.start()
106 self.ksclient_p.start() 118 self.ksclient_p.start()
107 self.map_tenant_name_p.start() 119 self.map_display_name_or_tenant_p.start()
120 self.is_unicode_enabled_p.start()
108 self.lib_rpc_transport_p.start() 121 self.lib_rpc_transport_p.start()
109 122
110 def startHttpPatch(self): 123 def startHttpPatch(self):
diff --git a/networking_bigswitch/tests/unit/bigswitch/test_restproxy_plugin.py b/networking_bigswitch/tests/unit/bigswitch/test_restproxy_plugin.py
index 31edb4b..d2d1b6d 100644
--- a/networking_bigswitch/tests/unit/bigswitch/test_restproxy_plugin.py
+++ b/networking_bigswitch/tests/unit/bigswitch/test_restproxy_plugin.py
@@ -29,6 +29,8 @@ from neutron_lib.plugins import directory
29 29
30from networking_bigswitch.plugins.bigswitch import config as pl_config 30from networking_bigswitch.plugins.bigswitch import config as pl_config
31from networking_bigswitch.plugins.bigswitch import constants as bsn_constants 31from networking_bigswitch.plugins.bigswitch import constants as bsn_constants
32from networking_bigswitch.plugins.bigswitch.servermanager import\
33 TenantIDNotFound
32from networking_bigswitch.tests.unit.bigswitch import fake_server 34from networking_bigswitch.tests.unit.bigswitch import fake_server
33from networking_bigswitch.tests.unit.bigswitch \ 35from networking_bigswitch.tests.unit.bigswitch \
34 import test_base as bsn_test_base 36 import test_base as bsn_test_base
@@ -36,6 +38,8 @@ from networking_bigswitch.tests.unit.bigswitch \
36patch = mock.patch 38patch = mock.patch
37HTTPCON = ('networking_bigswitch.plugins.bigswitch.servermanager.httplib' 39HTTPCON = ('networking_bigswitch.plugins.bigswitch.servermanager.httplib'
38 '.HTTPConnection') 40 '.HTTPConnection')
41IS_UNICODE_ENABLED = ('networking_bigswitch.plugins.bigswitch.servermanager.'
42 'ServerPool.is_unicode_enabled')
39 43
40 44
41class BigSwitchProxyPluginV2TestCase(bsn_test_base.BigSwitchTestBase, 45class BigSwitchProxyPluginV2TestCase(bsn_test_base.BigSwitchTestBase,
@@ -340,6 +344,109 @@ class TestBigSwitchProxySync(BigSwitchProxyPluginV2TestCase):
340 self.assertEqual(result[0], 200) 344 self.assertEqual(result[0], 200)
341 345
342 346
347class TestDisplayName(BigSwitchProxyPluginV2TestCase):
348 def get_true(self):
349 """Used for side_effect replacement
350
351 :return:
352 """
353 return True
354
355 def test_map_display_name_or_tenant_unicode_disabled(self):
356 """Test _map_display_name_or_tenant behaviors when unicode is disabled
357
358 :return:
359 """
360 self.map_display_name_or_tenant_p.stop()
361 plugin_obj = directory.get_plugin()
362
363 self.assertFalse(plugin_obj.servers.is_unicode_enabled())
364
365 # object with non-existing tenant_id
366 no_tenant_obj = {'id': 'test_id',
367 'name': 'test_name',
368 'tenant_id': 'non_exist_tenant_id'}
369
370 self.assertRaises(TenantIDNotFound,
371 plugin_obj._map_display_name_or_tenant,
372 no_tenant_obj)
373
374 # add a tenant to cache
375 plugin_obj.servers.keystone_tenants = {'tenant_id': 'tenant_name'}
376
377 # object with name, '_' in name will be replaced with '__'
378 test_obj = {'id': 'test_id',
379 'name': 'test_name',
380 'tenant_id': 'tenant_id'}
381
382 expected_obj = {'id': 'test_id',
383 'name': 'test__name',
384 'tenant_id': 'tenant_id',
385 'tenant_name': 'tenant_name'}
386
387 self.assertEqual(expected_obj,
388 plugin_obj._map_display_name_or_tenant(test_obj))
389
390 # object without name
391 test_obj = {'id': 'test_id',
392 'tenant_id': 'tenant_id'}
393
394 expected_obj = {'id': 'test_id',
395 'tenant_id': 'tenant_id',
396 'tenant_name': 'tenant_name'}
397
398 self.assertEqual(expected_obj,
399 plugin_obj._map_display_name_or_tenant(test_obj))
400
401 def test_map_display_name_or_tenant_unicode_enabled(self):
402 """Test _map_display_name_or_tenant behaviors when unicode is enabled
403
404 :return:
405 """
406 self.map_display_name_or_tenant_p.stop()
407 self.is_unicode_enabled_p.stop()
408 mock.patch(IS_UNICODE_ENABLED, side_effect=self.get_true).start()
409 plugin_obj = directory.get_plugin()
410
411 self.assertTrue(plugin_obj.servers.is_unicode_enabled())
412
413 # object with non-existing tenant_id, unicode enabled
414 no_tenant_obj = {'id': 'test_id',
415 'name': 'test_name',
416 'tenant_id': 'non_exist_tenant_id'}
417
418 self.assertRaises(TenantIDNotFound,
419 plugin_obj._map_display_name_or_tenant,
420 no_tenant_obj)
421
422 # add a tenant to cache
423 plugin_obj.servers.keystone_tenants = {'tenant_id': 'tenant_name'}
424
425 # object with name, unicode enabled
426 test_obj = {'id': 'test_id',
427 'name': 'test_name',
428 'tenant_id': 'tenant_id'}
429
430 expected_obj = {'id': 'test_id',
431 'name': 'test_id',
432 'display-name': 'test_name',
433 'tenant_id': 'tenant_id'}
434
435 self.assertEqual(expected_obj,
436 plugin_obj._map_display_name_or_tenant(test_obj))
437
438 # object without name, unicode enabled
439 test_obj = {'id': 'test_id',
440 'tenant_id': 'tenant_id'}
441
442 expected_obj = {'id': 'test_id',
443 'name': 'test_id',
444 'tenant_id': 'tenant_id'}
445
446 self.assertEqual(expected_obj,
447 plugin_obj._map_display_name_or_tenant(test_obj))
448
449
343class TestBigSwitchAddressPairs(test_addr_pair.TestAllowedAddressPairs, 450class TestBigSwitchAddressPairs(test_addr_pair.TestAllowedAddressPairs,
344 BigSwitchProxyPluginV2TestCase): 451 BigSwitchProxyPluginV2TestCase):
345 def test_create_missing_mac_field(self): 452 def test_create_missing_mac_field(self):
diff --git a/networking_bigswitch/tests/unit/bigswitch/test_servermanager.py b/networking_bigswitch/tests/unit/bigswitch/test_servermanager.py
index 235a361..9039227 100644
--- a/networking_bigswitch/tests/unit/bigswitch/test_servermanager.py
+++ b/networking_bigswitch/tests/unit/bigswitch/test_servermanager.py
@@ -31,6 +31,7 @@ SERVERMANAGER = 'networking_bigswitch.plugins.bigswitch.servermanager'
31CONSISTENCYDB = 'networking_bigswitch.plugins.bigswitch.db.consistency_db' 31CONSISTENCYDB = 'networking_bigswitch.plugins.bigswitch.db.consistency_db'
32HTTPCON = SERVERMANAGER + '.httplib.HTTPConnection' 32HTTPCON = SERVERMANAGER + '.httplib.HTTPConnection'
33HTTPSCON = SERVERMANAGER + '.HTTPSConnectionWithValidation' 33HTTPSCON = SERVERMANAGER + '.HTTPSConnectionWithValidation'
34SERVER_GET_CAPABILITIES = SERVERMANAGER + '.ServerPool.get_capabilities'
34 35
35 36
36class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase): 37class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
@@ -87,8 +88,9 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
87 88
88 def test_consistency_watchdog(self): 89 def test_consistency_watchdog(self):
89 pl = directory.get_plugin() 90 pl = directory.get_plugin()
90 pl.servers.capabilities = [] 91 pl.servers.capabilities = ['dummy']
91 self.watch_p.stop() 92 self.watch_p.stop()
93
92 with mock.patch('eventlet.sleep') as smock,\ 94 with mock.patch('eventlet.sleep') as smock,\
93 mock.patch( 95 mock.patch(
94 SERVERMANAGER + '.ServerPool.rest_call', 96 SERVERMANAGER + '.ServerPool.rest_call',
@@ -102,6 +104,7 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
102 # should return immediately without consistency capability 104 # should return immediately without consistency capability
103 pl.servers._consistency_watchdog() 105 pl.servers._consistency_watchdog()
104 self.assertFalse(smock.called) 106 self.assertFalse(smock.called)
107
105 pl.servers.capabilities = ['consistency'] 108 pl.servers.capabilities = ['consistency']
106 self.assertRaises(KeyError, 109 self.assertRaises(KeyError,
107 pl.servers._consistency_watchdog) 110 pl.servers._consistency_watchdog)
@@ -187,14 +190,18 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
187 190
188 # each server will get different capabilities 191 # each server will get different capabilities
189 rv.read.side_effect = ['["a","b","c"]', '["b","c","d"]'] 192 rv.read.side_effect = ['["a","b","c"]', '["b","c","d"]']
190 # pool capabilities is intersection between both 193 # pool capabilities is union of both
191 self.assertEqual(set(['b', 'c']), sp.get_capabilities()) 194 # normally capabilities should be the same across all servers
195 # this only happens in two situations:
196 # 1. a server is down
197 # 2. during upgrade/downgrade
198 self.assertEqual(set(['a', 'b', 'c', 'd']), sp.get_capabilities())
192 self.assertEqual(2, rv.read.call_count) 199 self.assertEqual(2, rv.read.call_count)
193 200
194 # the pool should cache after the first call so no more 201 # the pool should cache after the first call during a short period
195 # HTTP calls should be made 202 # so no more HTTP calls should be made
196 rv.read.side_effect = ['["w","x","y"]', '["x","y","z"]'] 203 rv.read.side_effect = ['["w","x","y"]', '["x","y","z"]']
197 self.assertEqual(set(['b', 'c']), sp.get_capabilities()) 204 self.assertEqual(set(['a', 'b', 'c', 'd']), sp.get_capabilities())
198 self.assertEqual(2, rv.read.call_count) 205 self.assertEqual(2, rv.read.call_count)
199 206
200 def test_capabilities_retrieval_failure(self): 207 def test_capabilities_retrieval_failure(self):
@@ -206,9 +213,9 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
206 rv.read.return_value = 'XXXXX' 213 rv.read.return_value = 'XXXXX'
207 self.assertEqual([], sp.servers[0].get_capabilities()) 214 self.assertEqual([], sp.servers[0].get_capabilities())
208 215
209 # One broken server should affect all capabilities 216 # as capabilities is empty, it should try to update capabilities
210 rv.read.side_effect = ['{"a": "b"}', '["b","c","d"]'] 217 rv.read.side_effect = ['{"a": "b"}', '["b","c","d"]']
211 self.assertEqual(set(), sp.get_capabilities()) 218 self.assertEqual(set(['a', 'b', 'c', 'd']), sp.get_capabilities())
212 219
213 def test_reconnect_on_timeout_change(self): 220 def test_reconnect_on_timeout_change(self):
214 sp = servermanager.ServerPool() 221 sp = servermanager.ServerPool()
@@ -533,6 +540,59 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
533 self.assertEqual(con._tunnel_port, 3128) 540 self.assertEqual(con._tunnel_port, 3128)
534 self.assertEqual(con.sock, self.wrap_mock()) 541 self.assertEqual(con.sock, self.wrap_mock())
535 542
543 def test_is_unicode_enabled(self):
544 """Verify that unicode is enabled only when both conditions are True:
545
546 1. naming_scheme_unicode is True or empty
547 2. BCF capabilities include display-name
548
549 :return:
550 """
551 self.is_unicode_enabled_p.stop()
552
553 def capability_unicode_supported():
554 return ['dummy', 'display-name']
555
556 def capability_unicode_unsupported():
557 return ['dummy']
558
559 patch_supported = mock.patch(
560 SERVER_GET_CAPABILITIES,
561 side_effect=capability_unicode_supported)
562
563 patch_unsupported = mock.patch(
564 SERVER_GET_CAPABILITIES,
565 side_effect=capability_unicode_unsupported)
566
567 # Create a server pool with default naming_scheme_unicode
568 # verify default value is true
569 sp = servermanager.ServerPool()
570 self.assertTrue(cfg.CONF.RESTPROXY.naming_scheme_unicode)
571
572 # config enabled, and unicode is supported on bcf
573 patch_supported.start()
574 self.assertTrue(sp.is_unicode_enabled())
575 patch_supported.stop()
576
577 # config enabled, but unicode is not supported on bcf
578 patch_unsupported.start()
579 self.assertFalse(sp.is_unicode_enabled())
580 patch_unsupported.stop()
581
582 # Recreate the server pool, as the config is read during initialization
583 cfg.CONF.set_override('naming_scheme_unicode', False, 'RESTPROXY')
584 sp = servermanager.ServerPool()
585
586 # config disabled, though unicode is supported on bcf
587 patch_supported.start()
588 self.assertFalse(sp.is_unicode_enabled())
589 patch_supported.stop()
590
591 # config disabled, and unicode is not supported on bcf
592 patch_unsupported.start()
593 self.assertFalse(sp.is_unicode_enabled())
594 patch_unsupported.stop()
595
536 596
537class TestSockets(test_rp.BigSwitchProxyPluginV2TestCase): 597class TestSockets(test_rp.BigSwitchProxyPluginV2TestCase):
538 598