From: Mike Perez Date: Sat, 28 Feb 2015 07:48:46 +0000 (-0800) Subject: Update Datera's Authentication method X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=18e7fc5eece1be252051dbd231cf6a151fd2ed4c;p=openstack-build%2Fcinder-build.git Update Datera's Authentication method 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 --- diff --git a/cinder/tests/volume/drivers/datera.py b/cinder/tests/volume/drivers/datera.py index 2ea56fed8..e83c1e188 100644 --- a/cinder/tests/volume/drivers/datera.py +++ b/cinder/tests/volume/drivers/datera.py @@ -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'], diff --git a/cinder/volume/drivers/datera.py b/cinder/volume/drivers/datera.py index 2165519b4..6d250ae23 100644 --- a/cinder/volume/drivers/datera.py +++ b/cinder/volume/drivers/datera.py @@ -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 @@ -16,12 +16,14 @@ 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') % {