-# 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__)
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.'),
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
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 = {
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
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:
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') % {