Merge "Update Datera's Authentication method"

This commit is contained in:
Jenkins 2015-03-06 06:49:34 +00:00 committed by Gerrit Code Review
commit 35b1a03a45
2 changed files with 115 additions and 10 deletions

View 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'],

View 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
@ -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') % {