From 3be95adcebfef6e862eda2827a216048b4e9007a Mon Sep 17 00:00:00 2001
From: Assaf Muller <amuller@redhat.com>
Date: Wed, 11 Mar 2015 21:39:54 -0400
Subject: [PATCH] Add support for multiple namespaces in Targets

In order for projects to use the namespace property of Targets
in a backwards compatible way (To support rolling upgrades),
a Target may now belong to more than a single namespace (i.e.
'namespace1' and None). This way, if the server is upgraded first,
the version that introduces namespaces to a project will place
the server RPC methods in ['some_namespace', None]. Pre-upgrade
agents will send messages in the null namespace while post-upgrade
agents will send messages in 'some_namespace', and both will be
accepted.

Change-Id: I713fe9228111c36aa3f7fb95cbd59c99100e8c96
Closes-Bug: #1430984
---
 oslo_messaging/rpc/dispatcher.py            |  2 +-
 oslo_messaging/target.py                    |  9 ++++++++-
 oslo_messaging/tests/rpc/test_dispatcher.py | 12 ++++++++++++
 3 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/oslo_messaging/rpc/dispatcher.py b/oslo_messaging/rpc/dispatcher.py
index d4a882310..8879f352d 100644
--- a/oslo_messaging/rpc/dispatcher.py
+++ b/oslo_messaging/rpc/dispatcher.py
@@ -111,7 +111,7 @@ class RPCDispatcher(object):
 
     @staticmethod
     def _is_namespace(target, namespace):
-        return namespace == target.namespace
+        return namespace in target.accepted_namespaces
 
     @staticmethod
     def _is_compatible(target, version):
diff --git a/oslo_messaging/target.py b/oslo_messaging/target.py
index f37a2b296..e91cc878a 100644
--- a/oslo_messaging/target.py
+++ b/oslo_messaging/target.py
@@ -57,16 +57,23 @@ class Target(object):
       servers listening on a topic by setting fanout to ``True``, rather than
       just one of them.
     :type fanout: bool
+    :param legacy_namespaces: A server always accepts messages specified via
+      the 'namespace' parameter, and may also accept messages defined via
+      this parameter. This option should be used to switch namespaces safely
+      during rolling upgrades.
+    :type legacy_namespaces: list of strings
     """
 
     def __init__(self, exchange=None, topic=None, namespace=None,
-                 version=None, server=None, fanout=None):
+                 version=None, server=None, fanout=None,
+                 legacy_namespaces=None):
         self.exchange = exchange
         self.topic = topic
         self.namespace = namespace
         self.version = version
         self.server = server
         self.fanout = fanout
+        self.accepted_namespaces = [namespace] + (legacy_namespaces or [])
 
     def __call__(self, **kwargs):
         for a in ('exchange', 'topic', 'namespace',
diff --git a/oslo_messaging/tests/rpc/test_dispatcher.py b/oslo_messaging/tests/rpc/test_dispatcher.py
index edc4e7e0a..32e0b09c5 100644
--- a/oslo_messaging/tests/rpc/test_dispatcher.py
+++ b/oslo_messaging/tests/rpc/test_dispatcher.py
@@ -89,6 +89,18 @@ class TestDispatcher(test_utils.BaseTestCase):
               dispatch_to=None,
               ctxt={}, msg=dict(method='foo', version='3.2'),
               success=False, ex=oslo_messaging.UnsupportedVersion)),
+        ('message_in_null_namespace_with_multiple_namespaces',
+         dict(endpoints=[dict(namespace='testns',
+                              legacy_namespaces=[None])],
+              dispatch_to=dict(endpoint=0, method='foo'),
+              ctxt={}, msg=dict(method='foo', namespace=None),
+              success=True, ex=None)),
+        ('message_in_wrong_namespace_with_multiple_namespaces',
+         dict(endpoints=[dict(namespace='testns',
+                              legacy_namespaces=['second', None])],
+              dispatch_to=None,
+              ctxt={}, msg=dict(method='foo', namespace='wrong'),
+              success=False, ex=oslo_messaging.UnsupportedVersion)),
     ]
 
     def test_dispatcher(self):