]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Update Datera's Authentication method
authorMike Perez <thingee@gmail.com>
Sat, 28 Feb 2015 07:48:46 +0000 (23:48 -0800)
committerMike Perez <thingee@gmail.com>
Thu, 5 Mar 2015 01:39:05 +0000 (17:39 -0800)
Previous auth-token/client verification auth method is deprecated. It's
supported until Liberty release. This also supports the new method,
which is using san_login and san_password, which will fetch an
auth-token to use.

blueprint datera-auth-update
Change-Id: I31648409d8a86f6d826a9294f7d6329ff2c7ec03

cinder/tests/volume/drivers/datera.py
cinder/volume/drivers/datera.py

index 2ea56fed89b19016740538f4e15f3af02a282758..e83c1e188f7d4d57c509358b90d6b39cb79f1710 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 2014 Datera
+# Copyright 2015 Datera
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -37,6 +37,8 @@ class DateraVolumeTestCase(test.TestCase):
         self.cfg.datera_api_port = '7717'
         self.cfg.datera_api_version = '1'
         self.cfg.datera_num_replicas = '2'
+        self.cfg.san_login = 'user'
+        self.cfg.san_password = 'pass'
 
         mock_exec = mock.Mock()
         mock_exec.return_value = ('', '')
@@ -244,6 +246,17 @@ class DateraVolumeTestCase(test.TestCase):
         self.assertRaises(exception.DateraAPIException,
                           self.driver.extend_volume, volume, 2)
 
+    def test_login_successful(self):
+        self.mock_api.return_value = {
+            'key': 'dd2469de081346c28ac100e071709403'
+        }
+        self.assertIsNone(self.driver._login())
+        self.assertEqual(1, self.mock_api.call_count)
+
+    def test_login_unsuccessful(self):
+        self.mock_api.side_effect = exception.NotAuthorized
+        self.assertRaises(exception.NotAuthorized, self.driver._login)
+        self.assertEqual(1, self.mock_api.call_count)
 
 stub_export = {
     u'_ipColl': [u'172.28.121.10', u'172.28.120.10'],
index 2165519b45747da691a20ea0aa3dede195366ffe..6d250ae239470ad7a47caa602b7b3bd9ceb1e68f 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright 2014 Datera
+# Copyright 2015 Datera
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
 import json
 
 from oslo_config import cfg
+from oslo_utils import excutils
 from oslo_utils import units
 import requests
 
 from cinder import exception
-from cinder.i18n import _, _LE
+from cinder.i18n import _, _LE, _LW
 from cinder.openstack.common import log as logging
+from cinder.openstack.common import versionutils
 from cinder.volume.drivers.san import san
 
 LOG = logging.getLogger(__name__)
@@ -29,7 +31,9 @@ LOG = logging.getLogger(__name__)
 d_opts = [
     cfg.StrOpt('datera_api_token',
                default=None,
-               help='Datera API token.'),
+               help='DEPRECATED: This will be removed in the Liberty release. '
+                    'Use san_login and san_password instead. This directly '
+                    'sets the Datera API token.'),
     cfg.StrOpt('datera_api_port',
                default='7717',
                help='Datera API port.'),
@@ -45,9 +49,32 @@ d_opts = [
 CONF = cfg.CONF
 CONF.import_opt('driver_client_cert_key', 'cinder.volume.driver')
 CONF.import_opt('driver_client_cert', 'cinder.volume.driver')
+CONF.import_opt('driver_use_ssl', 'cinder.volume.driver')
 CONF.register_opts(d_opts)
 
 
+def _authenticated(func):
+        """Ensure the driver is authenticated to make a request.
+
+        In do_setup() we fetch an auth token and store it. If that expires when
+        we do API request, we'll fetch a new one.
+        """
+        def func_wrapper(self, *args, **kwargs):
+            try:
+                return func(self, *args, **kwargs)
+            except exception.NotAuthorized:
+                # Prevent recursion loop. After the self arg is the
+                # resource_type arg from _issue_api_request(). If attempt to
+                # login failed, we should just give up.
+                if args[0] == 'login':
+                    raise
+
+                # Token might've expired, get a new one, try again.
+                self._login()
+                return func(self, *args, **kwargs)
+        return func_wrapper
+
+
 class DateraDriver(san.SanISCSIDriver):
     """The OpenStack Datera Driver
 
@@ -60,8 +87,39 @@ class DateraDriver(san.SanISCSIDriver):
         super(DateraDriver, self).__init__(*args, **kwargs)
         self.configuration.append_config_values(d_opts)
         self.num_replicas = self.configuration.datera_num_replicas
+        self.username = self.configuration.san_login
+        self.password = self.configuration.san_password
+        self.auth_token = None
         self.cluster_stats = {}
 
+    def do_setup(self, context):
+        # If any of the deprecated options are set, we'll warn the operator to
+        # use the new authentication method.
+        DEPRECATED_OPTS = [
+            self.configuration.driver_client_cert_key,
+            self.configuration.driver_client_cert,
+            self.configuration.datera_api_token
+        ]
+
+        if any(DEPRECATED_OPTS):
+            msg = _LW("Client cert verification and datera_api_token are "
+                      "deprecated in the Datera driver, and will be removed "
+                      "in the Liberty release. Please set the san_login and "
+                      "san_password in your cinder.conf instead.")
+            versionutils.report_deprecated_feature(LOG, msg)
+            return
+
+        # If we can't authenticate through the old and new method, just fail
+        # now.
+        if not all([self.username, self.password]):
+            msg = _("san_login and/or san_password is not set for Datera "
+                    "driver in the cinder.conf. Set this information and "
+                    "start the cinder-volume service again.")
+            LOG.error(msg)
+            raise exception.InvalidInput(msg)
+
+        self._login()
+
     def create_volume(self, volume):
         """Create a logical volume."""
         params = {
@@ -193,8 +251,28 @@ class DateraDriver(san.SanISCSIDriver):
 
         self.cluster_stats = stats
 
+    def _login(self):
+        """Use the san_login and san_password to set self.auth_token."""
+        data = {
+            'name': self.username,
+            'password': self.password
+        }
+
+        try:
+            LOG.debug('Getting Datera auth token.')
+            results = self._issue_api_request('login', 'post', body=data,
+                                              sensitive=True)
+            self.auth_token = results['key']
+        except exception.NotAuthorized:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_LE('Logging into the Datera cluster failed. Please '
+                              'check your username and password set in the '
+                              'cinder.conf and start the cinder-volume'
+                              'service again.'))
+
+    @_authenticated
     def _issue_api_request(self, resource_type, method='get', resource=None,
-                           body=None, action=None):
+                           body=None, action=None, sensitive=False):
         """All API requests to Datera cluster go through this method.
 
         :param resource_type: the type of the resource
@@ -211,16 +289,26 @@ class DateraDriver(san.SanISCSIDriver):
 
         payload = json.dumps(body, ensure_ascii=False)
         payload.encode('utf-8')
-        header = {'Content-Type': 'application/json; charset=utf-8'}
 
+        if not sensitive:
+            LOG.debug("Payload for Datera API call: %s", payload)
+
+        header = {
+            'Content-Type': 'application/json; charset=utf-8',
+            'auth-token': self.auth_token
+        }
+
+        protocol = 'http'
+        if self.configuration.driver_use_ssl:
+            protocol = 'https'
+
+        # TODO(thingee): Auth method through Auth-Token is deprecated. Remove
+        # this and client cert verification stuff in the Liberty release.
         if api_token:
             header['Auth-Token'] = api_token
 
-        LOG.debug("Payload for Datera API call: %s", payload)
-
         client_cert = self.configuration.driver_client_cert
         client_cert_key = self.configuration.driver_client_cert_key
-        protocol = 'http'
         cert_data = None
 
         if client_cert:
@@ -247,10 +335,14 @@ class DateraDriver(san.SanISCSIDriver):
             raise exception.DateraAPIException(msg)
 
         data = response.json()
-        LOG.debug("Results of Datera API call: %s", data)
+        if not sensitive:
+            LOG.debug("Results of Datera API call: %s", data)
+
         if not response.ok:
             if response.status_code == 404:
                 raise exception.NotFound(data['message'])
+            elif response.status_code in [403, 401]:
+                raise exception.NotAuthorized()
             else:
                 msg = _('Request to Datera cluster returned bad status:'
                         ' %(status)s | %(reason)s') % {