Browse Source

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 7 months ago
parent
commit
d5d0267ba8

+ 15
- 3
etc/neutron/plugins/bigswitch/restproxy.ini View File

@@ -18,12 +18,19 @@
18 18
 #   add_meta_server_route :  True | False                 (default: True)
19 19
 #   thread_pool_size      :  <int>                        (default: 4)
20 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.
23 28
 servers=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 32
 # server_auth=username:password
33
+# server_auth=access-token
27 34
 
28 35
 # Use SSL when connecting to the BigSwitch or Floodlight controller.
29 36
 # server_ssl=True
@@ -59,7 +66,7 @@ servers=localhost:8080
59 66
 # User defined identifier for this Neutron deployment
60 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 70
 # add_meta_server_route = True
64 71
 
65 72
 # Number of threads to use to handle large volumes of port creation requests
@@ -69,6 +76,11 @@ servers=localhost:8080
69 76
 # visibility.
70 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 84
 [nova]
73 85
 # Specify the VIF_TYPE that will be controlled on the Nova compute instances
74 86
 #    options: ivs or ovs

+ 5
- 1
networking_bigswitch/plugins/bigswitch/config.py View File

@@ -80,7 +80,11 @@ restproxy_opts = [
80 80
                       "Openstack tenants. (0 to disable)")),
81 81
     cfg.BoolOpt('sync_security_groups', default=False,
82 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
 ]
85 89
 router_opts = [
86 90
     cfg.MultiStrOpt('tenant_default_router_rule', default=['*:any:any:permit'],

+ 3
- 3
networking_bigswitch/plugins/bigswitch/l3_router_plugin.py View File

@@ -166,7 +166,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
166 166
         self.txn_cache.add_transaction(router[BSN_TRANSACTION_ID],
167 167
                                        router['id'])
168 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 170
             mapped_router = self._map_state_and_status(mapped_router)
171 171
 
172 172
             # Does not handle external gateway and some other information
@@ -190,7 +190,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
190 190
         default_policy_dict = self._get_tenant_default_router_policy(tenant_id)
191 191
 
192 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 194
             mapped_router = self._map_state_and_status(mapped_router)
195 195
             # populate external tenant_id if it is absent for external network,
196 196
             # This is a new work flow in kilo that user can specify external
@@ -503,7 +503,7 @@ class L3RestProxy(cplugin.NeutronRestProxyV2Base,
503 503
             if ext_tenant_id:
504 504
                 updated_router[l3_apidef.EXTERNAL_GW_INFO]['tenant_id'] = (
505 505
                     ext_tenant_id)
506
-        router = self._map_tenant_name(updated_router)
506
+        router = self._map_display_name_or_tenant(updated_router)
507 507
         router = self._map_state_and_status(router)
508 508
         # look up the network on this side to save an expensive query on
509 509
         # the backend controller.

+ 108
- 44
networking_bigswitch/plugins/bigswitch/plugin.py View File

@@ -203,6 +203,10 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
203 203
             True, if obj name, obj's tenant name and name have supported chars
204 204
             False, otherwise
205 205
         """
206
+
207
+        if self.servers.is_unicode_enabled():
208
+            return True
209
+
206 210
         if name and not servermanager.is_valid_bcf_name(name):
207 211
             LOG.warning('Unsupported characters in Name: %(name)s. ',
208 212
                         {'name': name})
@@ -281,7 +285,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
281 285
                             [const.DEVICE_OWNER_ROUTER_GW,
282 286
                              const.DEVICE_OWNER_ROUTER_HA_INTF]):
283 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 293
                         mapped_port = self._map_state_and_status(mapped_port)
286 294
                         mapped_port = self._map_port_hostid(mapped_port, net)
287 295
                         if not mapped_port:
@@ -327,7 +335,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
327 335
                                 ext_tenant_id)
328 336
 
329 337
                     interfaces = []
330
-                    mapped_router = self._map_tenant_name(router)
338
+                    mapped_router = self._map_display_name_or_tenant(router)
331 339
                     mapped_router = self._map_state_and_status(mapped_router)
332 340
                     if not self._validate_names(mapped_router):
333 341
                         continue
@@ -368,13 +376,16 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
368 376
             new_sgs = []
369 377
             for sg in sgs:
370 378
                 try:
371
-                    mapped_sg = self._map_tenant_name(sg)
379
+                    mapped_sg = self._map_display_name_or_tenant(sg)
372 380
                     if not self._validate_names(mapped_sg):
373 381
                         continue
374 382
                     if 'description' in mapped_sg:
375 383
                         mapped_sg['description'] = ''
376
-                    mapped_sg['name'] = Util.format_resource_name(
377
-                        mapped_sg['name'])
384
+                    if self.servers.is_unicode_enabled():
385
+                        mapped_sg['name'] = None
386
+                    else:
387
+                        mapped_sg['name'] = Util.format_resource_name(
388
+                            mapped_sg['name'])
378 389
                     new_sgs.append(mapped_sg)
379 390
                 except servermanager.TenantIDNotFound:
380 391
                     # if tenant name is not known to keystone, skip the sg
@@ -383,13 +394,27 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
383 394
             data.update({'security-groups': new_sgs})
384 395
 
385 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 418
         return data
394 419
 
395 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 441
     def _assign_resource_to_service_tenant(self, resource):
417 442
         resource['tenant_id'] = (resource['tenant_id'] or
418 443
                                  servermanager.SERVICE_TENANT)
419
-        if resource.get('name'):
420
-            # resource name may contain space. Replace space with -
421
-            resource['name'] = Util.format_resource_name(resource['name'])
444
+
445
+        if not self.servers.is_unicode_enabled():
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 450
     def _get_network_with_floatingips(self, network, context=None):
424 451
         if context is None:
@@ -433,9 +460,11 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
433 460
             for flip in fl_ips:
434 461
                 try:
435 462
                     # BVS-7525: the 'tenant_id' in a floating-ip represents the
436
-                    # tenant to which it is allocated. Validate that the
437
-                    # tenant exists
438
-                    mapped_flip = self._map_tenant_name(flip)
463
+                    # tenant to which it is allocated.
464
+                    # Validate that the tenant exists
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 468
                     if mapped_flip.get('floating_port_id'):
440 469
                         fport = self.get_port(context,
441 470
                                               mapped_flip['floating_port_id'])
@@ -461,7 +490,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
461 490
         if subnets:
462 491
             for subnet in subnets:
463 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 494
                 mapped_subnet = self._map_state_and_status(mapped_subnet)
466 495
                 subnets_details.append(mapped_subnet)
467 496
 
@@ -473,11 +502,15 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
473 502
         This network is not associated with any tenant
474 503
         """
475 504
         sg['tenant_id'] = sg['tenant_id'] or servermanager.SERVICE_TENANT
476
-        sg['tenant_name'] = self.servers.keystone_tenants.get(sg['tenant_id'])
477
-        if not sg['tenant_name']:
505
+        tenant_name = self.servers.keystone_tenants.get(sg['tenant_id'])
506
+
507
+        if not tenant_name:
478 508
             self.servers._update_tenant_cache(reconcile=True)
479 509
             tenant_name = self.servers.keystone_tenants.get(sg['tenant_id'])
510
+
511
+        if not self.servers.is_unicode_enabled():
480 512
             sg['tenant_name'] = tenant_name
513
+        return tenant_name
481 514
 
482 515
     def bsn_create_security_group(self, sg_id=None, sg=None, context=None):
483 516
         if sg_id:
@@ -487,14 +520,19 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
487 520
             sg = self.get_security_group(context, sg_id)
488 521
 
489 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 528
             # remove description as its not used
492
-            if 'description' in sg:
493
-                sg['description'] = ''
494
-            self._tenant_check_for_security_group(sg)
529
+            if sg.get('description'):
530
+                del(sg['description'])
531
+            # check and map tenant_name for sg
532
+            tenant_name = self._tenant_check_for_security_group(sg)
495 533
             # skip the security group if its tenant is unknown
496
-            if sg['tenant_name']:
497
-                if sg['tenant_name'] == servermanager.SERVICE_TENANT:
534
+            if tenant_name:
535
+                if tenant_name == servermanager.SERVICE_TENANT:
498 536
                     self.bsn_create_tenant(servermanager.SERVICE_TENANT,
499 537
                                            context=context)
500 538
                 self.servers.rest_create_securitygroup(sg)
@@ -511,14 +549,15 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
511 549
         self.servers.rest_delete_tenant(tenant_id)
512 550
 
513 551
     def _verify_network_precommit(self, context):
514
-        if context.current['name'] != context.original['name']:
515
-            raise servermanager.NetworkNameChangeError()
552
+        if not self.servers.is_unicode_enabled():
553
+            if context.current['name'] != context.original['name']:
554
+                raise servermanager.NetworkNameChangeError()
516 555
 
517 556
     def _get_mapped_network_with_subnets(self, network, context=None):
518 557
         # if context is not provided, admin context is used
519 558
         if context is None:
520 559
             context = qcontext.get_admin_context()
521
-        network = self._map_tenant_name(network)
560
+        network = self._map_display_name_or_tenant(network)
522 561
         network = self._map_state_and_status(network)
523 562
         subnets = self._get_all_subnets_json_for_network(network['id'],
524 563
                                                          context)
@@ -537,6 +576,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
537 576
         # OSP-45: remove name to avoid NSAPI error in convertToAscii
538 577
         for subnet in (subnets or []):
539 578
             subnet.pop('name', None)
579
+
540 580
         return network
541 581
 
542 582
     def _skip_bcf_network_event(self, network):
@@ -568,14 +608,16 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
568 608
             if default_group:
569 609
                 # VRRP tenant doesn't have tenant_id
570 610
                 self.bsn_create_security_group(sg=default_group[0])
611
+        # display-name is also mapped here
571 612
         mapped_network = self._get_mapped_network_with_subnets(network,
572 613
                                                                context)
573 614
 
574 615
         if not tenant_id:
575 616
             tenant_id = servermanager.SERVICE_TENANT
576 617
             mapped_network['tenant_id'] = servermanager.SERVICE_TENANT
577
-            mapped_network['name'] = Util.format_resource_name(
578
-                mapped_network['name'])
618
+            if not self.servers.is_unicode_enabled():
619
+                mapped_network['name'] = Util.format_resource_name(
620
+                    mapped_network['name'])
579 621
             self.bsn_create_tenant(servermanager.SERVICE_TENANT,
580 622
                                    context=context)
581 623
         self.servers.rest_create_network(tenant_id, mapped_network)
@@ -588,6 +630,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
588 630
                      {'name': network.get('name')})
589 631
             return
590 632
 
633
+        # display-name is also mapped here
591 634
         mapped_network = self._get_mapped_network_with_subnets(network,
592 635
                                                                context)
593 636
         net_fl_ips = self._get_network_with_floatingips(mapped_network,
@@ -595,8 +638,9 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
595 638
         if not tenant_id:
596 639
             tenant_id = servermanager.SERVICE_TENANT
597 640
             net_fl_ips['tenant_id'] = servermanager.SERVICE_TENANT
598
-            net_fl_ips['name'] = Util.format_resource_name(
599
-                net_fl_ips['name'])
641
+            if not self.servers.is_unicode_enabled():
642
+                net_fl_ips['name'] = Util.format_resource_name(
643
+                    net_fl_ips['name'])
600 644
         self.servers.rest_update_network(tenant_id, net_id, net_fl_ips)
601 645
 
602 646
     def _send_delete_network(self, network, context=None):
@@ -604,22 +648,35 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
604 648
         tenant_id = network['tenant_id'] or servermanager.SERVICE_TENANT
605 649
         self.servers.rest_delete_network(tenant_id, net_id)
606 650
 
607
-    def _map_tenant_name(self, resource):
608
-        resource = copy.copy(resource)
651
+    def _map_display_name_or_tenant(self, 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 661
         self._assign_resource_to_service_tenant(resource)
662
+
610 663
         tenant_name = self.servers.keystone_tenants.get(resource['tenant_id'])
611
-        if tenant_name:
612
-            resource['tenant_name'] = tenant_name
613
-        else:
664
+        if not tenant_name:
614 665
             self.servers._update_tenant_cache()
615 666
             tenant_name = self.servers.keystone_tenants.get(
616 667
                 resource['tenant_id'])
617
-            if tenant_name:
618
-                resource['tenant_name'] = tenant_name
619
-            else:
668
+            if not tenant_name:
620 669
                 raise servermanager.TenantIDNotFound(
621 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 680
         return resource
624 681
 
625 682
     def _map_state_and_status(self, resource):
@@ -795,7 +852,7 @@ class NeutronRestProxyV2Base(db_base_plugin_v2.NeutronDbPluginV2,
795 852
         net_id = subnet['network_id']
796 853
         network = self.get_network(context, net_id)
797 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 856
         mapped_subnet = self._map_state_and_status(mapped_subnet)
800 857
 
801 858
         data = {
@@ -1141,7 +1198,10 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
1141 1198
                 self._add_host_route(context, destination, new_port)
1142 1199
 
1143 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 1205
         mapped_port = self._map_state_and_status(mapped_port)
1146 1206
         # ports have to be created synchronously when creating a router
1147 1207
         # port since adding router interfaces is a multi-call process
@@ -1230,7 +1290,11 @@ class NeutronRestProxyV2(NeutronRestProxyV2Base,
1230 1290
                 # tenant_id must come from network in case network is shared
1231 1291
                 net_tenant_id = self._get_port_net_tenantid(context, new_port)
1232 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 1298
                 mapped_port = self._map_state_and_status(mapped_port)
1235 1299
                 self.servers.rest_update_port(net_tenant_id,
1236 1300
                                               new_port["network_id"],

+ 128
- 23
networking_bigswitch/plugins/bigswitch/servermanager.py View File

@@ -158,7 +158,7 @@ class NetworkNameChangeError(exceptions.NeutronException):
158 158
 
159 159
 
160 160
 class RemoteRestError(exceptions.NeutronException):
161
-    message = _("Error in REST call to remote network "
161
+    message = _("Error in REST call to BCF "
162 162
                 "controller: %(reason)s")
163 163
     status = None
164 164
 
@@ -285,8 +285,8 @@ class ServerProxy(object):
285 285
             if body:
286 286
                 self.capabilities = jsonutils.loads(body)
287 287
         except Exception:
288
-            LOG.exception("Couldn't retrieve capabilities. "
289
-                          "Newer API calls won't be supported.")
288
+            LOG.exception("Couldn't retrieve capabilities on server "
289
+                          "%(server)s. ", {'server': self.server})
290 290
         LOG.info("The following capabilities were received "
291 291
                  "for %(server)s: %(cap)s",
292 292
                  {'server': self.server, 'cap': self.capabilities})
@@ -417,6 +417,9 @@ class ServerPool(object):
417 417
         self.auth = cfg.CONF.RESTPROXY.server_auth
418 418
         self.ssl = cfg.CONF.RESTPROXY.server_ssl
419 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 423
         if 'keystone_authtoken' in cfg.CONF:
421 424
             self.auth_user = get_keystoneauth_cfg(cfg.CONF, 'username')
422 425
             self.auth_password = get_keystoneauth_cfg(cfg.CONF, 'password')
@@ -453,6 +456,7 @@ class ServerPool(object):
453 456
         self._update_tenant_cache(reconcile=False)
454 457
         self.timeout = cfg.CONF.RESTPROXY.server_timeout
455 458
         self.always_reconnect = not cfg.CONF.RESTPROXY.cache_connections
459
+        self.capabilities = []
456 460
         default_port = 8000
457 461
         if timeout is not False:
458 462
             self.timeout = timeout
@@ -480,12 +484,20 @@ class ServerPool(object):
480 484
                 server = server[1:-1]
481 485
             self.servers.append(self.server_proxy_for(server, int(port)))
482 486
         self.start_background_tasks()
487
+
483 488
         ServerPool._instance = self
489
+
484 490
         LOG.debug("ServerPool: initialization done")
485 491
 
486 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 498
         eventlet.spawn(self._consistency_watchdog,
488 499
                        cfg.CONF.RESTPROXY.consistency_interval)
500
+
489 501
         # Start keystone sync thread after 5 consistency sync
490 502
         # to give enough time for topology to sync over when
491 503
         # neutron-server starts.
@@ -495,19 +507,39 @@ class ServerPool(object):
495 507
             cfg.CONF.RESTPROXY.keystone_sync_interval)
496 508
 
497 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 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 520
             return self.capabilities
501
-        except AttributeError:
502
-            # this exception is hit when the capabilities haven't been
503
-            # looked up yet
504
-            pass
505
-        # each server should return a list of capabilities it supports
506
-        # e.g. ['floatingip']
507
-        capabilities = [set(server.get_capabilities())
508
-                        for server in self.servers]
509
-        # Pool only supports what all of the servers support
510
-        self.capabilities = set.intersection(*capabilities)
521
+        else:
522
+            return self.get_capabilities_force_update()
523
+
524
+    def get_capabilities_force_update(self):
525
+        """Do REST calls to update capabilities
526
+
527
+        Logs a unicode change message when:
528
+        1. the first time that plugin gets capabilities from BCF
529
+        2. plugin notices the unicode mode is changed
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 543
         # With multiple workers enabled, the fork may occur after the
512 544
         # connections to the DB have been established. We need to clear the
513 545
         # connections after the first attempt to call the backend to ensure
@@ -522,8 +554,60 @@ class ServerPool(object):
522 554
         # ec716b9e68b8b66a88218913ae4c9aa3a26b025a/neutron/wsgi.py#L104
523 555
         if cdb.HashHandler._FACADE:
524 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 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 611
     def server_proxy_for(self, server, port):
528 612
         combined_cert = self._get_combined_cert_for_server(server, port)
529 613
         return ServerProxy(server, port, self.ssl, self.auth, self.neutron_id,
@@ -760,13 +844,16 @@ class ServerPool(object):
760 844
         if not tenant_name:
761 845
             raise TenantIDNotFound(tenant=tenant_id)
762 846
 
763
-        if not is_valid_bcf_name(tenant_name):
764
-            raise UnsupportedNameException(obj_type=ObjTypeEnum.tenant,
765
-                                           obj_id=tenant_id,
766
-                                           obj_name=tenant_name)
767
-
847
+        if self.is_unicode_enabled():
848
+            data = {"tenant_id": tenant_id, 'tenant_name': tenant_id,
849
+                    'display-name': tenant_name}
850
+        else:
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 856
         resource = TENANT_RESOURCE_PATH
769
-        data = {"tenant_id": tenant_id, 'tenant_name': tenant_name}
770 857
         errstr = _("Unable to create tenant: %s")
771 858
         self.rest_action('POST', resource, data, errstr)
772 859
 
@@ -939,7 +1026,7 @@ class ServerPool(object):
939 1026
     def _consistency_watchdog(self, polling_interval=60):
940 1027
         if 'consistency' not in self.get_capabilities():
941 1028
             LOG.warning("Backend server(s) do not support automated "
942
-                        "consitency checks.")
1029
+                        "consistency checks.")
943 1030
             return
944 1031
         if not polling_interval:
945 1032
             LOG.warning("Consistency watchdog disabled by polling "
@@ -957,6 +1044,19 @@ class ServerPool(object):
957 1044
                 LOG.exception("Encountered an error checking controller "
958 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 1060
     def force_topo_sync(self, check_ts=True):
961 1061
         """Execute a topology_sync between OSP and BCF.
962 1062
 
@@ -1050,8 +1150,13 @@ class ServerPool(object):
1050 1150
             sess = session.Session(auth=auth)
1051 1151
             keystone_client = ksclient.Client(session=sess)
1052 1152
             tenants = keystone_client.projects.list()
1053
-            new_cached_tenants = {tn.id: Util.format_resource_name(tn.name)
1054
-                                  for tn in tenants}
1153
+
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 1160
             # Add SERVICE_TENANT to handle hidden network for VRRP
1056 1161
             new_cached_tenants[SERVICE_TENANT] = SERVICE_TENANT
1057 1162
 

+ 18
- 10
networking_bigswitch/plugins/ml2/drivers/mech_bigswitch/driver.py View File

@@ -240,24 +240,28 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
240 240
         # we retain this section for security groups, because it handles
241 241
         # other events as well. Ignore security group events if disabled in
242 242
         # config
243
-        if (event_type == 'security_group.create.end' and
244
-                cfg.CONF.RESTPROXY.sync_security_groups):
243
+        if event_type == 'security_group.create.end':
245 244
             LOG.debug("Security group created: %s", payload)
246
-            self.bsn_create_security_group(sg=payload['security_group'])
247
-        elif (event_type == 'security_group.delete.end' and
248
-                cfg.CONF.RESTPROXY.sync_security_groups):
245
+            if cfg.CONF.RESTPROXY.sync_security_groups:
246
+                self.bsn_create_security_group(sg=payload['security_group'])
247
+        elif event_type == 'security_group.delete.end':
249 248
             LOG.debug("Security group deleted: %s", payload)
250
-            self.bsn_delete_security_group(payload['security_group_id'])
251
-        elif (event_type == 'security_group_rule.delete.end' and
252
-                cfg.CONF.RESTPROXY.sync_security_groups):
249
+            if cfg.CONF.RESTPROXY.sync_security_groups:
250
+                self.bsn_delete_security_group(payload['security_group_id'])
251
+        elif event_type == 'security_group_rule.delete.end':
253 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 255
         elif event_type == 'identity.project.deleted':
256 256
             LOG.debug("Project deleted: %s", payload)
257 257
             self.bsn_delete_tenant(payload['resource_info'])
258 258
         elif event_type == 'identity.project.created':
259 259
             LOG.debug("Project created: %s", payload)
260 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 265
         else:
262 266
             LOG.debug("Else events: %s payload: %s", (event_type, payload))
263 267
 
@@ -419,7 +423,11 @@ class BigSwitchMechanismDriver(plugin.NeutronRestProxyV2Base,
419 423
         net = context.network.current
420 424
         port['network'] = net
421 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 431
         prepped_port = self._map_state_and_status(prepped_port)
424 432
         prepped_port = self._map_port_hostid(prepped_port, net)
425 433
         return prepped_port

+ 18
- 5
networking_bigswitch/tests/unit/bigswitch/test_base.py View File

@@ -49,8 +49,11 @@ SPAWN = ('networking_bigswitch.plugins.bigswitch.plugin.eventlet.GreenPool'
49 49
          '.spawn_n')
50 50
 KSCLIENT = 'keystoneclient.v3.client.Client'
51 51
 BACKGROUND = SERVER_MANAGER + '.ServerPool.start_background_tasks'
52
-MAP_TENANT_NAME = ('networking_bigswitch.plugins.bigswitch.plugin.'
53
-                   'NeutronRestProxyV2Base._map_tenant_name')
52
+MAP_DISPLAY_NAME_OR_TENANT = ('networking_bigswitch.plugins.bigswitch.plugin.'
53
+                              'NeutronRestProxyV2Base.'
54
+                              '_map_display_name_or_tenant')
55
+IS_UNICODE_ENABLED = ('networking_bigswitch.plugins.bigswitch.servermanager.'
56
+                      'ServerPool.is_unicode_enabled')
54 57
 LIB_RPC_TRANSPORT = ('neutron_lib.rpc.TRANSPORT')
55 58
 
56 59
 
@@ -80,9 +83,14 @@ class BigSwitchTestBase(object):
80 83
         cfg.CONF.set_override('api_extensions_path', False)
81 84
 
82 85
     def map_tenant_name_side_effect(self, value):
86
+        # for old tests, always map tenant name
83 87
         value['tenant_name'] = 'tenant_name'
84 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 94
     def setup_patches(self):
87 95
         self.plugin_notifier_p = mock.patch(NOTIFIER)
88 96
         self.dhcp_notifier_p = mock.patch(DHCP_NOTIFIER)
@@ -94,8 +102,12 @@ class BigSwitchTestBase(object):
94 102
         self.log_exc_p = mock.patch(SERVER_MANAGER + ".LOG.exception",
95 103
                                     new=lambda *args, **kwargs: None)
96 104
         self.ksclient_p = mock.patch(KSCLIENT)
97
-        self.map_tenant_name_p = mock.patch(
98
-            MAP_TENANT_NAME, side_effect=self.map_tenant_name_side_effect)
105
+        self.map_display_name_or_tenant_p = mock.patch(
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 111
         self.lib_rpc_transport_p = mock.patch(LIB_RPC_TRANSPORT)
100 112
         # start all mock patches
101 113
         self.log_exc_p.start()
@@ -104,7 +116,8 @@ class BigSwitchTestBase(object):
104 116
         self.watch_p.start()
105 117
         self.dhcp_notifier_p.start()
106 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 121
         self.lib_rpc_transport_p.start()
109 122
 
110 123
     def startHttpPatch(self):

+ 107
- 0
networking_bigswitch/tests/unit/bigswitch/test_restproxy_plugin.py View File

@@ -29,6 +29,8 @@ from neutron_lib.plugins import directory
29 29
 
30 30
 from networking_bigswitch.plugins.bigswitch import config as pl_config
31 31
 from networking_bigswitch.plugins.bigswitch import constants as bsn_constants
32
+from networking_bigswitch.plugins.bigswitch.servermanager import\
33
+    TenantIDNotFound
32 34
 from networking_bigswitch.tests.unit.bigswitch import fake_server
33 35
 from networking_bigswitch.tests.unit.bigswitch \
34 36
     import test_base as bsn_test_base
@@ -36,6 +38,8 @@ from networking_bigswitch.tests.unit.bigswitch \
36 38
 patch = mock.patch
37 39
 HTTPCON = ('networking_bigswitch.plugins.bigswitch.servermanager.httplib'
38 40
            '.HTTPConnection')
41
+IS_UNICODE_ENABLED = ('networking_bigswitch.plugins.bigswitch.servermanager.'
42
+                      'ServerPool.is_unicode_enabled')
39 43
 
40 44
 
41 45
 class BigSwitchProxyPluginV2TestCase(bsn_test_base.BigSwitchTestBase,
@@ -340,6 +344,109 @@ class TestBigSwitchProxySync(BigSwitchProxyPluginV2TestCase):
340 344
         self.assertEqual(result[0], 200)
341 345
 
342 346
 
347
+class 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
+
343 450
 class TestBigSwitchAddressPairs(test_addr_pair.TestAllowedAddressPairs,
344 451
                                 BigSwitchProxyPluginV2TestCase):
345 452
     def test_create_missing_mac_field(self):

+ 68
- 8
networking_bigswitch/tests/unit/bigswitch/test_servermanager.py View File

@@ -31,6 +31,7 @@ SERVERMANAGER = 'networking_bigswitch.plugins.bigswitch.servermanager'
31 31
 CONSISTENCYDB = 'networking_bigswitch.plugins.bigswitch.db.consistency_db'
32 32
 HTTPCON = SERVERMANAGER + '.httplib.HTTPConnection'
33 33
 HTTPSCON = SERVERMANAGER + '.HTTPSConnectionWithValidation'
34
+SERVER_GET_CAPABILITIES = SERVERMANAGER + '.ServerPool.get_capabilities'
34 35
 
35 36
 
36 37
 class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
@@ -87,8 +88,9 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
87 88
 
88 89
     def test_consistency_watchdog(self):
89 90
         pl = directory.get_plugin()
90
-        pl.servers.capabilities = []
91
+        pl.servers.capabilities = ['dummy']
91 92
         self.watch_p.stop()
93
+
92 94
         with mock.patch('eventlet.sleep') as smock,\
93 95
                 mock.patch(
94 96
                     SERVERMANAGER + '.ServerPool.rest_call',
@@ -102,6 +104,7 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
102 104
             # should return immediately without consistency capability
103 105
             pl.servers._consistency_watchdog()
104 106
             self.assertFalse(smock.called)
107
+
105 108
             pl.servers.capabilities = ['consistency']
106 109
             self.assertRaises(KeyError,
107 110
                               pl.servers._consistency_watchdog)
@@ -187,14 +190,18 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
187 190
 
188 191
             # each server will get different capabilities
189 192
             rv.read.side_effect = ['["a","b","c"]', '["b","c","d"]']
190
-            # pool capabilities is intersection between both
191
-            self.assertEqual(set(['b', 'c']), sp.get_capabilities())
193
+            # pool capabilities is union of both
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 199
             self.assertEqual(2, rv.read.call_count)
193 200
 
194
-            # the pool should cache after the first call so no more
195
-            # HTTP calls should be made
201
+            # the pool should cache after the first call during a short period
202
+            # so no more HTTP calls should be made
196 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 205
             self.assertEqual(2, rv.read.call_count)
199 206
 
200 207
     def test_capabilities_retrieval_failure(self):
@@ -206,9 +213,9 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
206 213
             rv.read.return_value = 'XXXXX'
207 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 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 220
     def test_reconnect_on_timeout_change(self):
214 221
         sp = servermanager.ServerPool()
@@ -533,6 +540,59 @@ class ServerManagerTests(test_rp.BigSwitchProxyPluginV2TestCase):
533 540
         self.assertEqual(con._tunnel_port, 3128)
534 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
 
537 597
 class TestSockets(test_rp.BigSwitchProxyPluginV2TestCase):
538 598
 

Loading…
Cancel
Save