From f38cecfdfc54c16e7cd8533c91255d07498a982b Mon Sep 17 00:00:00 2001
From: Flavio Percoco <flaper87@gmail.com>
Date: Fri, 18 Mar 2016 09:50:07 -0400
Subject: [PATCH] Disable DEPRECATED API versions by default

API v1.0 has been deprecated for a while and v1.1 will be deprecated
soon. We shouldn't be enabling deprecated API versions by default. This
patch adds a decorator to disable deprecated APIs by default unless they
have been explicitly enabled in the environment.

Co-Authored-By: Fei Long Wang <flwang@catalyst.net.nz>

Change-Id: I62fb4d18bc6c9f5455bff0d56e42269b4590b454
---
 devstack/plugin.sh                            |  1 +
 ...-deprecated-versions-44656aeb8ebb8881.yaml |  7 ++++
 zaqar/common/configs.py                       |  4 ++
 zaqar/common/decorators.py                    | 35 ++++++++++++++++
 zaqar/tests/etc/drivers_storage_invalid.conf  |  1 +
 .../tests/etc/drivers_transport_invalid.conf  |  1 +
 zaqar/tests/etc/functional-tests.conf         |  1 +
 zaqar/tests/etc/functional-zaqar.conf         |  3 ++
 zaqar/tests/etc/keystone_auth.conf            |  1 +
 zaqar/tests/etc/websocket_mongodb.conf        |  1 +
 .../etc/websocket_mongodb_keystone_auth.conf  |  1 +
 .../etc/websocket_mongodb_subscriptions.conf  |  1 +
 zaqar/tests/etc/wsgi_faulty.conf              |  1 +
 zaqar/tests/etc/wsgi_fifo_mongodb.conf        |  1 +
 zaqar/tests/etc/wsgi_mongodb.conf             |  1 +
 .../etc/wsgi_mongodb_default_limits.conf      |  5 ++-
 zaqar/tests/etc/wsgi_mongodb_pooled.conf      |  1 +
 zaqar/tests/etc/wsgi_mongodb_validation.conf  |  3 ++
 zaqar/tests/etc/wsgi_redis.conf               |  1 +
 zaqar/tests/etc/wsgi_redis_pooled.conf        |  1 +
 zaqar/tests/etc/wsgi_sqlalchemy.conf          |  1 +
 zaqar/tests/etc/wsgi_sqlalchemy_pooled.conf   |  1 +
 zaqar/tests/unit/common/test_decorators.py    | 42 +++++++++++++++++++
 zaqar/transport/wsgi/driver.py                |  5 ++-
 zaqar/transport/wsgi/v1_0/__init__.py         |  6 +--
 zaqar/transport/wsgi/v1_1/__init__.py         |  3 ++
 zaqar/transport/wsgi/v2_0/__init__.py         |  3 ++
 27 files changed, 126 insertions(+), 6 deletions(-)
 create mode 100644 releasenotes/notes/support-turnoff-deprecated-versions-44656aeb8ebb8881.yaml

diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index a8547f3a0..a39685301 100755
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -84,6 +84,7 @@ function configure_zaqar {
     iniset $ZAQAR_CONF DEFAULT debug True
     iniset $ZAQAR_CONF DEFAULT unreliable True
     iniset $ZAQAR_CONF DEFAULT admin_mode True
+    iniset $ZAQAR_CONF DEFAULT enable_deprecated_api_versions 1
     iniset $ZAQAR_CONF signed_url secret_key notreallysecret
 
     if is_service_enabled key; then
diff --git a/releasenotes/notes/support-turnoff-deprecated-versions-44656aeb8ebb8881.yaml b/releasenotes/notes/support-turnoff-deprecated-versions-44656aeb8ebb8881.yaml
new file mode 100644
index 000000000..8d332ab6e
--- /dev/null
+++ b/releasenotes/notes/support-turnoff-deprecated-versions-44656aeb8ebb8881.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - Currently, the v1 API is still accessible though it has been deprecated
+    for a while. And we're going to deprecate v1.1 soon. To keep the backward
+    compatibility, a new config option - ``enable_deprecated_api_versions``
+    is added so that operator can totally turn off an API version or still
+    support it by adding the API version to the list of the new config option.
diff --git a/zaqar/common/configs.py b/zaqar/common/configs.py
index 204f7282e..85851d918 100644
--- a/zaqar/common/configs.py
+++ b/zaqar/common/configs.py
@@ -31,6 +31,10 @@ _GENERAL_OPTIONS = (
                 deprecated_opts=[cfg.DeprecatedOpt('sharding')]),
     cfg.BoolOpt('unreliable', default=False,
                 help='Disable all reliability constraints.'),
+    cfg.ListOpt('enable_deprecated_api_versions', default=[],
+                item_type=cfg.types.List(item_type=cfg.types.String(
+                    choices=('1', '1.1'))),
+                help='List of deprecated API versions to enable.'),
 )
 
 _DRIVER_OPTIONS = (
diff --git a/zaqar/common/decorators.py b/zaqar/common/decorators.py
index 5537335e1..5d6c5317c 100644
--- a/zaqar/common/decorators.py
+++ b/zaqar/common/decorators.py
@@ -20,6 +20,9 @@ from oslo_cache import core
 from oslo_log import log as logging
 from oslo_serialization import jsonutils
 
+from zaqar.i18n import _LW
+
+
 LOG = logging.getLogger(__name__)
 
 
@@ -183,3 +186,35 @@ def lazy_property(write=False, delete=True):
                         fdel=delete and deleter,
                         doc=fn.__doc__)
     return wrapper
+
+
+def api_version_manager(version_info):
+    """Manage API versions based on their status
+
+    This decorator disables `DEPRECATED` APIs by default unless the user
+    explicitly enables it by adding it to the `enable_deprecated_api_versions`
+    configuration option.
+
+    :param version_info: Dictionary containing the API version info.
+    """
+    api_version = version_info['id']
+    api_updated = version_info['updated']
+    deprecated = version_info['status'] == 'DEPRECATED'
+
+    def wrapper(fn):
+        @functools.wraps(fn)
+        def register_api(driver, conf):
+            if (deprecated and
+                    [api_version] not in conf.enable_deprecated_api_versions):
+                return None
+
+            if deprecated:
+                LOG.warning(_LW('Enabling API version %(version)s. '
+                                'This version was marked as deprecated in '
+                                '%(updated)s. Using it may expose security '
+                                'issues, unexpected behavior or damage your '
+                                'data.') % {'version': api_version,
+                                            'updated': api_updated})
+            return fn(driver, conf)
+        return register_api
+    return wrapper
diff --git a/zaqar/tests/etc/drivers_storage_invalid.conf b/zaqar/tests/etc/drivers_storage_invalid.conf
index f289cd591..797fe9bdb 100644
--- a/zaqar/tests/etc/drivers_storage_invalid.conf
+++ b/zaqar/tests/etc/drivers_storage_invalid.conf
@@ -2,6 +2,7 @@
 debug = False
 verbose = False
 admin_mode = False
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/drivers_transport_invalid.conf b/zaqar/tests/etc/drivers_transport_invalid.conf
index 575fbf776..3b1317107 100644
--- a/zaqar/tests/etc/drivers_transport_invalid.conf
+++ b/zaqar/tests/etc/drivers_transport_invalid.conf
@@ -1,6 +1,7 @@
 [DEFAULT]
 debug = False
 verbose = False
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = invalid
diff --git a/zaqar/tests/etc/functional-tests.conf b/zaqar/tests/etc/functional-tests.conf
index 38d8dc66b..f49983ebc 100644
--- a/zaqar/tests/etc/functional-tests.conf
+++ b/zaqar/tests/etc/functional-tests.conf
@@ -1,6 +1,7 @@
 [DEFAULT]
 # run_tests = True
 unreliable = True
+enable_deprecated_api_versions = 1
 
 [zaqar]
 # url = http://0.0.0.0:8888
diff --git a/zaqar/tests/etc/functional-zaqar.conf b/zaqar/tests/etc/functional-zaqar.conf
index f71fad1fa..b00644ce3 100644
--- a/zaqar/tests/etc/functional-zaqar.conf
+++ b/zaqar/tests/etc/functional-zaqar.conf
@@ -5,6 +5,8 @@ verbose = True
 # Show debugging output in logs (sets DEBUG log level output)
 debug = True
 
+enable_deprecated_api_versions = 1
+
 # Log to this file!
 ; log_file = /var/log/zaqar/server.log
 
@@ -20,6 +22,7 @@ debug = True
 ;syslog_log_facility = LOG_LOCAL0
 
 unreliable = True
+enable_deprecated_api_versions = 1, 1.1
 
 [drivers]
 # Transport driver module (e.g., wsgi, zmq)
diff --git a/zaqar/tests/etc/keystone_auth.conf b/zaqar/tests/etc/keystone_auth.conf
index 647807714..ef8776d77 100644
--- a/zaqar/tests/etc/keystone_auth.conf
+++ b/zaqar/tests/etc/keystone_auth.conf
@@ -3,6 +3,7 @@ auth_strategy = keystone
 
 debug = False
 verbose = False
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/websocket_mongodb.conf b/zaqar/tests/etc/websocket_mongodb.conf
index 81c4f42c0..76e6d7515 100644
--- a/zaqar/tests/etc/websocket_mongodb.conf
+++ b/zaqar/tests/etc/websocket_mongodb.conf
@@ -1,5 +1,6 @@
 [DEFAULT]
 unreliable = True
+enable_deprecated_api_versions = 1
 
 [drivers]
 
diff --git a/zaqar/tests/etc/websocket_mongodb_keystone_auth.conf b/zaqar/tests/etc/websocket_mongodb_keystone_auth.conf
index 64e98f0f3..ebe1ddfc4 100644
--- a/zaqar/tests/etc/websocket_mongodb_keystone_auth.conf
+++ b/zaqar/tests/etc/websocket_mongodb_keystone_auth.conf
@@ -1,5 +1,6 @@
 [DEFAULT]
 auth_strategy = keystone
+enable_deprecated_api_versions = 1
 
 [drivers]
 
diff --git a/zaqar/tests/etc/websocket_mongodb_subscriptions.conf b/zaqar/tests/etc/websocket_mongodb_subscriptions.conf
index 56a97899d..b5014cf50 100644
--- a/zaqar/tests/etc/websocket_mongodb_subscriptions.conf
+++ b/zaqar/tests/etc/websocket_mongodb_subscriptions.conf
@@ -1,5 +1,6 @@
 [DEFAULT]
 unreliable = True
+enable_deprecated_api_versions = 1
 
 [drivers]
 
diff --git a/zaqar/tests/etc/wsgi_faulty.conf b/zaqar/tests/etc/wsgi_faulty.conf
index c07ebc4ba..064d15aa0 100644
--- a/zaqar/tests/etc/wsgi_faulty.conf
+++ b/zaqar/tests/etc/wsgi_faulty.conf
@@ -1,6 +1,7 @@
 [DEFAULT]
 debug = False
 verbose = False
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/wsgi_fifo_mongodb.conf b/zaqar/tests/etc/wsgi_fifo_mongodb.conf
index 5b0c8cb65..27649ad45 100644
--- a/zaqar/tests/etc/wsgi_fifo_mongodb.conf
+++ b/zaqar/tests/etc/wsgi_fifo_mongodb.conf
@@ -2,6 +2,7 @@
 debug = False
 verbose = False
 unreliable = True
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/wsgi_mongodb.conf b/zaqar/tests/etc/wsgi_mongodb.conf
index 134ef39e1..629865a55 100644
--- a/zaqar/tests/etc/wsgi_mongodb.conf
+++ b/zaqar/tests/etc/wsgi_mongodb.conf
@@ -2,6 +2,7 @@
 debug = False
 verbose = False
 unreliable = True
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/wsgi_mongodb_default_limits.conf b/zaqar/tests/etc/wsgi_mongodb_default_limits.conf
index 6c35848e3..38462c674 100644
--- a/zaqar/tests/etc/wsgi_mongodb_default_limits.conf
+++ b/zaqar/tests/etc/wsgi_mongodb_default_limits.conf
@@ -1,3 +1,6 @@
+[DEFAULT]
+enable_deprecated_api_versions = 1
+
 [drivers]
 transport = wsgi
-message_store = mongodb
+message_store = mongodb
\ No newline at end of file
diff --git a/zaqar/tests/etc/wsgi_mongodb_pooled.conf b/zaqar/tests/etc/wsgi_mongodb_pooled.conf
index 4eadd48b4..092ee5e89 100644
--- a/zaqar/tests/etc/wsgi_mongodb_pooled.conf
+++ b/zaqar/tests/etc/wsgi_mongodb_pooled.conf
@@ -2,6 +2,7 @@
 pooling = True
 admin_mode = True
 unreliable = True
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/wsgi_mongodb_validation.conf b/zaqar/tests/etc/wsgi_mongodb_validation.conf
index 7cdedf3c5..cb52665c2 100644
--- a/zaqar/tests/etc/wsgi_mongodb_validation.conf
+++ b/zaqar/tests/etc/wsgi_mongodb_validation.conf
@@ -1,3 +1,6 @@
+[DEFAULT]
+enable_deprecated_api_versions = 1
+
 [drivers]
 transport = wsgi
 message_store = mongodb
diff --git a/zaqar/tests/etc/wsgi_redis.conf b/zaqar/tests/etc/wsgi_redis.conf
index a06ea5108..77f48cb04 100644
--- a/zaqar/tests/etc/wsgi_redis.conf
+++ b/zaqar/tests/etc/wsgi_redis.conf
@@ -1,6 +1,7 @@
 [DEFAULT]
 debug = False
 verbose = False
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/wsgi_redis_pooled.conf b/zaqar/tests/etc/wsgi_redis_pooled.conf
index 4a97eaeb1..4b989a7c1 100644
--- a/zaqar/tests/etc/wsgi_redis_pooled.conf
+++ b/zaqar/tests/etc/wsgi_redis_pooled.conf
@@ -1,5 +1,6 @@
 [DEFAULT]
 pooling = True
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/wsgi_sqlalchemy.conf b/zaqar/tests/etc/wsgi_sqlalchemy.conf
index d13f7b30e..0fb54ad70 100644
--- a/zaqar/tests/etc/wsgi_sqlalchemy.conf
+++ b/zaqar/tests/etc/wsgi_sqlalchemy.conf
@@ -2,6 +2,7 @@
 debug = False
 verbose = False
 admin_mode = False
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/etc/wsgi_sqlalchemy_pooled.conf b/zaqar/tests/etc/wsgi_sqlalchemy_pooled.conf
index 4fc1fe611..160704736 100644
--- a/zaqar/tests/etc/wsgi_sqlalchemy_pooled.conf
+++ b/zaqar/tests/etc/wsgi_sqlalchemy_pooled.conf
@@ -1,6 +1,7 @@
 [DEFAULT]
 pooling = True
 admin_mode = True
+enable_deprecated_api_versions = 1
 
 [drivers]
 transport = wsgi
diff --git a/zaqar/tests/unit/common/test_decorators.py b/zaqar/tests/unit/common/test_decorators.py
index 35512960c..90b2f0cff 100644
--- a/zaqar/tests/unit/common/test_decorators.py
+++ b/zaqar/tests/unit/common/test_decorators.py
@@ -18,12 +18,17 @@ from oslo_cache import core
 from oslo_config import cfg
 
 from zaqar.common import cache as oslo_cache
+from zaqar.common import configs
 from zaqar.common import decorators
 from zaqar.tests import base
 
 
 class TestDecorators(base.TestBase):
 
+    def setUp(self):
+        super(TestDecorators, self).setUp()
+        self.conf.register_opts(configs._GENERAL_OPTIONS)
+
     def test_memoized_getattr(self):
 
         class TestClass(object):
@@ -141,3 +146,40 @@ class TestDecorators(base.TestBase):
 
             self.assertEqual(name, user)
             self.assertEqual(2 + i, instance.user_gets)
+
+    def test_api_version_manager(self):
+        self.config(enable_deprecated_api_versions=[])
+        # 1. Test accessing current API version
+        VERSION = {
+            'id': '1',
+            'status': 'CURRENT',
+            'updated': 'Just yesterday'
+        }
+
+        @decorators.api_version_manager(VERSION)
+        def public_endpoint_1(driver, conf):
+            return True
+
+        self.assertTrue(public_endpoint_1(None, self.conf))
+
+        # 2. Test accessing deprecated API version
+        VERSION = {
+            'id': '1',
+            'status': 'DEPRECATED',
+            'updated': 'A long time ago'
+        }
+
+        @decorators.api_version_manager(VERSION)
+        def public_endpoint_2(driver, conf):
+            self.fail('Deprecated API enabled')
+
+        public_endpoint_2(None, self.conf)
+
+        # 3. Test enabling deprecated API version
+        self.config(enable_deprecated_api_versions=[['1']])
+
+        @decorators.api_version_manager(VERSION)
+        def public_endpoint_3(driver, conf):
+            return True
+
+        self.assertTrue(public_endpoint_3(None, self.conf))
diff --git a/zaqar/transport/wsgi/driver.py b/zaqar/transport/wsgi/driver.py
index 87938525d..a82195c60 100644
--- a/zaqar/transport/wsgi/driver.py
+++ b/zaqar/transport/wsgi/driver.py
@@ -131,8 +131,9 @@ class Driver(transport.DriverBase):
         self.app.add_error_handler(Exception, self._error_handler)
 
         for version_path, endpoints in catalog:
-            for route, resource in endpoints:
-                self.app.add_route(version_path + route, resource)
+            if endpoints:
+                for route, resource in endpoints:
+                    self.app.add_route(version_path + route, resource)
 
     def _init_middleware(self):
         """Initialize WSGI middlewarez."""
diff --git a/zaqar/transport/wsgi/v1_0/__init__.py b/zaqar/transport/wsgi/v1_0/__init__.py
index 40066b605..1176975b4 100644
--- a/zaqar/transport/wsgi/v1_0/__init__.py
+++ b/zaqar/transport/wsgi/v1_0/__init__.py
@@ -14,6 +14,7 @@
 
 from oslo_log import log as logging
 
+from zaqar.common import decorators
 from zaqar.transport.wsgi.v1_0 import claims
 from zaqar.transport.wsgi.v1_0 import health
 from zaqar.transport.wsgi.v1_0 import homedoc
@@ -46,10 +47,8 @@ VERSION = {
 }
 
 
+@decorators.api_version_manager(VERSION)
 def public_endpoints(driver, conf):
-    LOG.warning('Zaqar\'s API version 1.0 will be turned off by default '
-                'during the Newton cycle')
-
     queue_controller = driver._storage.queue_controller
     message_controller = driver._storage.message_controller
     claim_controller = driver._storage.claim_controller
@@ -96,6 +95,7 @@ def public_endpoints(driver, conf):
     ]
 
 
+@decorators.api_version_manager(VERSION)
 def private_endpoints(driver, conf):
     if not conf.pooling:
         return []
diff --git a/zaqar/transport/wsgi/v1_1/__init__.py b/zaqar/transport/wsgi/v1_1/__init__.py
index 3ca00773d..bfdab7122 100644
--- a/zaqar/transport/wsgi/v1_1/__init__.py
+++ b/zaqar/transport/wsgi/v1_1/__init__.py
@@ -14,6 +14,7 @@
 
 from oslo_log import log as logging
 
+from zaqar.common import decorators
 from zaqar.transport.wsgi.v1_1 import claims
 from zaqar.transport.wsgi.v1_1 import flavors
 from zaqar.transport.wsgi.v1_1 import health
@@ -47,6 +48,7 @@ VERSION = {
 }
 
 
+@decorators.api_version_manager(VERSION)
 def public_endpoints(driver, conf):
     LOG.warning('Zaqar\'s API version 1.1 will enter deprecation during the '
                 'Newton cycle. As part of that, it will be marked as '
@@ -104,6 +106,7 @@ def public_endpoints(driver, conf):
     ]
 
 
+@decorators.api_version_manager(VERSION)
 def private_endpoints(driver, conf):
 
     catalogue = [
diff --git a/zaqar/transport/wsgi/v2_0/__init__.py b/zaqar/transport/wsgi/v2_0/__init__.py
index 310326584..46078bd6a 100644
--- a/zaqar/transport/wsgi/v2_0/__init__.py
+++ b/zaqar/transport/wsgi/v2_0/__init__.py
@@ -12,6 +12,7 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
+from zaqar.common import decorators
 from zaqar.transport.wsgi.v2_0 import claims
 from zaqar.transport.wsgi.v2_0 import flavors
 from zaqar.transport.wsgi.v2_0 import health
@@ -44,6 +45,7 @@ VERSION = {
 }
 
 
+@decorators.api_version_manager(VERSION)
 def public_endpoints(driver, conf):
     queue_controller = driver._storage.queue_controller
     message_controller = driver._storage.message_controller
@@ -117,6 +119,7 @@ def public_endpoints(driver, conf):
     ]
 
 
+@decorators.api_version_manager(VERSION)
 def private_endpoints(driver, conf):
 
     catalogue = [