Merge changes from kilo version of the dothill driver

- Add support for management controller failover
  - Allow longer initiator nicknames if not running titanium firmware
  - Handle race conditions in choosing next available LUN and creating
    initiator nicknames
  - Serialize access to the array to avoid overloading it
  - Add timeouts for HTTP(S) requests
  - Reuse array API session keys to reduce array overhead
  - Handle missing wwpns property in terminate_connection
  - Fix behavior of extend_volume() and methods that call it
  - Change units from GB to GiB when creating volumes
  - Update test cases

Change-Id: Idf31e127eaaa360f10b2dd0ce7820c55416f8fbc
Closes-Bug: #1554770
Closes-Bug: #1560646
This commit is contained in:
Chris M 2016-10-06 14:45:21 -06:00
parent 75b89a36e3
commit 65b97f6743
11 changed files with 374 additions and 89 deletions

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -34,6 +35,10 @@ resp_login = '''<RESPONSE><OBJECT basetype="status" name="status" oid="1">
<PROPERTY name="response-type-numeric">0</PROPERTY>
<PROPERTY name="response">12a1626754554a21d85040760c81b</PROPERTY>
<PROPERTY name="return-code">1</PROPERTY></OBJECT></RESPONSE>'''
resp_fw = '''<RESPONSE><PROPERTY name="sc-fw">GLS220R001</PROPERTY>
<PROPERTY name="return-code">0</PROPERTY></RESPONSE>'''
resp_badlogin = '''<RESPONSE><OBJECT basetype="status" name="status" oid="1">
<PROPERTY name="response-type">error</PROPERTY>
<PROPERTY name="response-type-numeric">1</PROPERTY>
@ -107,13 +112,18 @@ test_host = {'capabilities': {'location_info':
'DotHillVolumeDriver:xxxxx:dg02:A'}}
test_snap = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
'volume': {'name_id': None},
'volume_id': vol_id,
'display_name': 'test volume', 'name': 'volume', 'size': 10}
'volume_id': vol_id, 'display_name': 'test volume',
'name': 'volume', 'volume_size': 10}
encoded_volid = 'v_O7DDpi8TOWF_9cwnMF'
encoded_snapid = 's_O7DDpi8TOWF_9cwnMF'
dest_volume = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
'source_volid': vol_id,
'display_name': 'test volume', 'name': 'volume', 'size': 10}
dest_volume_larger = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
'name_id': None,
'source_volid': vol_id,
'display_name': 'test volume',
'name': 'volume', 'size': 20}
attached_volume = {'id': vol_id,
'display_name': 'test volume', 'name': 'volume',
'size': 10, 'status': 'in-use',
@ -153,14 +163,16 @@ class TestDotHillClient(test.TestCase):
@mock.patch('requests.get')
def test_login(self, mock_requests_get):
m = mock.Mock()
m.text.encode.side_effect = [resp_login]
mock_requests_get.return_value = m
self.client.login()
self.assertEqual(session_key, self.client._session_key)
m.text.encode.side_effect = [resp_badlogin]
m.text.encode.side_effect = [resp_badlogin, resp_badlogin]
self.assertRaises(exception.DotHillAuthenticationError,
self.client.login)
m.text.encode.side_effect = [resp_login, resp_fw]
self.client.login()
self.assertEqual(session_key, self.client._session_key)
def test_build_request_url(self):
url = self.client._build_request_url('/path')
self.assertEqual('http://10.0.0.1/api/path', url)
@ -185,13 +197,13 @@ class TestDotHillClient(test.TestCase):
requests.exceptions.
RequestException("error")]
mock_requests_get.return_value = m
ret = self.client._request('/path')
ret = self.client._api_request('/path')
self.assertTrue(type(ret) == etree._Element)
self.assertRaises(exception.DotHillConnectionError,
self.client._request,
self.client._api_request,
'/path')
self.assertRaises(exception.DotHillConnectionError,
self.client._request,
self.client._api_request,
'/path')
def test_assert_response_ok(self):
@ -377,7 +389,7 @@ class TestFCDotHillCommon(test.TestCase):
ret = self.common.create_volume(test_volume)
self.assertIsNone(ret)
mock_create.assert_called_with(encoded_volid,
"%sGB" % test_volume['size'],
"%sGiB" % test_volume['size'],
self.common.backend_name,
self.common.backend_type)
@ -419,7 +431,38 @@ class TestFCDotHillCommon(test.TestCase):
@mock.patch.object(dothill.DotHillClient, 'copy_volume')
@mock.patch.object(dothill.DotHillClient, 'backend_stats')
def test_create_volume_from_snapshot(self, mock_stats, mock_copy):
@mock.patch.object(dothill_common.DotHillCommon, 'extend_volume')
def test_create_cloned_volume_larger(self, mock_extend, mock_stats,
mock_copy):
mock_stats.side_effect = [stats_low_space, stats_large_space,
stats_large_space]
self.assertRaises(exception.DotHillNotEnoughSpace,
self.common.create_cloned_volume,
dest_volume_larger, detached_volume)
self.assertFalse(mock_copy.called)
mock_copy.side_effect = [exception.DotHillRequestError, None]
self.assertRaises(exception.Invalid,
self.common.create_cloned_volume,
dest_volume_larger, detached_volume)
ret = self.common.create_cloned_volume(dest_volume_larger,
detached_volume)
self.assertIsNone(ret)
mock_copy.assert_called_with(encoded_volid,
'vqqqqqqqqqqqqqqqqqqq',
self.common.backend_name,
self.common.backend_type)
mock_extend.assert_called_once_with(dest_volume_larger,
dest_volume_larger['size'])
@mock.patch.object(dothill.DotHillClient, 'get_volume_size')
@mock.patch.object(dothill.DotHillClient, 'extend_volume')
@mock.patch.object(dothill.DotHillClient, 'copy_volume')
@mock.patch.object(dothill.DotHillClient, 'backend_stats')
def test_create_volume_from_snapshot(self, mock_stats, mock_copy,
mock_extend, mock_get_size):
mock_stats.side_effect = [stats_low_space, stats_large_space,
stats_large_space]
@ -428,26 +471,30 @@ class TestFCDotHillCommon(test.TestCase):
dest_volume, test_snap)
mock_copy.side_effect = [exception.DotHillRequestError, None]
mock_get_size.return_value = test_snap['volume_size']
self.assertRaises(exception.Invalid,
self.common.create_volume_from_snapshot,
dest_volume, test_snap)
ret = self.common.create_volume_from_snapshot(dest_volume, test_snap)
ret = self.common.create_volume_from_snapshot(dest_volume_larger,
test_snap)
self.assertIsNone(ret)
mock_copy.assert_called_with('sqqqqqqqqqqqqqqqqqqq',
'vqqqqqqqqqqqqqqqqqqq',
self.common.backend_name,
self.common.backend_type)
mock_extend.assert_called_with('vqqqqqqqqqqqqqqqqqqq', '10GiB')
@mock.patch.object(dothill.DotHillClient, 'get_volume_size')
@mock.patch.object(dothill.DotHillClient, 'extend_volume')
def test_extend_volume(self, mock_extend):
def test_extend_volume(self, mock_extend, mock_size):
mock_extend.side_effect = [exception.DotHillRequestError, None]
mock_size.side_effect = [10, 10]
self.assertRaises(exception.Invalid, self.common.extend_volume,
test_volume, 20)
ret = self.common.extend_volume(test_volume, 20)
self.assertIsNone(ret)
mock_extend.assert_called_with(encoded_volid, '10GB')
mock_extend.assert_called_with(encoded_volid, '10GiB')
@mock.patch.object(dothill.DotHillClient, 'create_snapshot')
def test_create_snapshot(self, mock_create):
@ -620,14 +667,12 @@ class TestDotHillFC(test.TestCase):
@mock.patch.object(dothill_common.DotHillCommon, 'unmap_volume')
@mock.patch.object(dothill.DotHillClient, 'list_luns_for_host')
def test_terminate_connection(self, mock_list, mock_unmap):
mock_unmap.side_effect = [exception.Invalid, 1]
mock_unmap.side_effect = [1]
mock_list.side_effect = ['yes']
actual = {'driver_volume_type': 'fibre_channel', 'data': {}}
self.assertRaises(exception.Invalid,
self.driver.terminate_connection, test_volume,
connector)
ret = self.driver.terminate_connection(test_volume, connector)
self.assertEqual(actual, ret)
mock_unmap.assert_called_with(test_volume, connector, 'wwpns')
ret = self.driver.terminate_connection(test_volume, connector)
self.assertEqual(actual, ret)

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -25,28 +26,83 @@ import requests
import six
from cinder import exception
from cinder.i18n import _LE
from cinder.i18n import _, _LE, _LW, _LI
from cinder import utils
LOG = logging.getLogger(__name__)
class DotHillClient(object):
def __init__(self, host, login, password, protocol, ssl_verify):
self._mgmt_ip_addrs = list(map(str.strip, host.split(',')))
self._login = login
self._password = password
self._base_url = "%s://%s/api" % (protocol, host)
self._protocol = protocol
self._session_key = None
self.ssl_verify = ssl_verify
self._set_host(self._mgmt_ip_addrs[0])
self._fw = ''
self._luns_in_use_by_host = {}
def _set_host(self, ip_addr):
self._curr_ip_addr = ip_addr
self._base_url = "%s://%s/api" % (self._protocol, ip_addr)
def _get_auth_token(self, xml):
"""Parse an XML authentication reply to extract the session key."""
self._session_key = None
tree = etree.XML(xml)
if tree.findtext(".//PROPERTY[@name='response-type']") == "success":
self._session_key = tree.findtext(".//PROPERTY[@name='response']")
try:
tree = etree.XML(xml)
if (tree.findtext(".//PROPERTY[@name='response-type']") ==
"success"):
self._session_key = (
tree.findtext(".//PROPERTY[@name='response']"))
except Exception as e:
msg = _("Cannot parse session key: %s") % e.msg
raise exception.DotHillConnectionError(message=msg)
def login(self):
"""Authenticates the service on the device."""
if self._session_key is None:
return self.session_login()
def session_login(self):
"""Authenticates the service on the device.
Tries all the IP addrs listed in the san_ip parameter
until a working one is found or the list is exhausted.
"""
try:
self._get_session_key()
self.get_firmware_version()
LOG.debug("Logged in to array at %s (session %s)",
self._base_url, self._session_key)
return
except exception.DotHillConnectionError:
not_responding = self._curr_ip_addr
LOG.exception(_LE('session_login failed to connect to %s'),
self._curr_ip_addr)
# Loop through the remaining management addresses
# to find one that's up.
for host in self._mgmt_ip_addrs:
if host is not_responding:
continue
self._set_host(host)
try:
self._get_session_key()
return
except exception.DotHillConnectionError:
LOG.error(_LE('Failed to connect to %s'),
self._curr_ip_addr)
continue
raise exception.DotHillConnectionError(
message=_("Failed to log in to management controller"))
@utils.synchronized(__name__, external = True)
def _get_session_key(self):
"""Retrieve a session key from the array."""
self._session_key = None
hash_ = "%s_%s" % (self._login, self._password)
if six.PY3:
hash_ = hash_.encode('utf-8')
@ -55,12 +111,14 @@ class DotHillClient(object):
url = self._base_url + "/login/" + digest
try:
xml = requests.get(url, verify=self.ssl_verify)
xml = requests.get(url, verify=self.ssl_verify, timeout=30)
except requests.exceptions.RequestException:
raise exception.DotHillConnectionError
msg = _("Failed to obtain MC session key")
LOG.exception(msg)
raise exception.DotHillConnectionError(message=msg)
self._get_auth_token(xml.text.encode('utf8'))
LOG.debug("session key = %s", self._session_key)
if self._session_key is None:
raise exception.DotHillAuthenticationError
@ -97,22 +155,64 @@ class DotHillClient(object):
return url
def _request(self, path, *args, **kargs):
"""Performs an HTTP request on the device.
"""Performs an API request on the array, with retry.
Propagates a DotHillConnectionError if no valid response is
received from the array, e.g. if the network is down.
Propagates a DotHillRequestError if the device returned a response
but the status is not 0. The device error message will be used
in the exception message.
If the status is OK, returns the XML data for further processing.
"""
tries_left = 2
while tries_left > 0:
try:
return self._api_request(path, *args, **kargs)
except exception.DotHillConnectionError as e:
if tries_left < 1:
LOG.error(_LE("Array Connection error: "
"%s (no more retries)"), e.msg)
raise
# Retry on any network connection errors, SSL errors, etc
LOG.error(_LE("Array Connection error: %s (retrying)"), e.msg)
except exception.DotHillRequestError as e:
if tries_left < 1:
LOG.error(_LE("Array Request error: %s (no more retries)"),
e.msg)
raise
# Retry specific errors which may succeed if we log in again
# -10027 => The user is not recognized on this system.
if '(-10027)' in e.msg:
LOG.error(_LE("Array Request error: %s (retrying)"), e.msg)
else:
raise
tries_left -= 1
self.session_login()
@utils.synchronized(__name__, external=True)
def _api_request(self, path, *args, **kargs):
"""Performs an HTTP request on the device, with locking.
Raises a DotHillRequestError if the device returned but the status is
not 0. The device error message will be used in the exception message.
If the status is OK, returns the XML data for further processing.
"""
url = self._build_request_url(path, *args, **kargs)
LOG.debug("DotHill Request URL: %s", url)
LOG.debug("Array Request URL: %s (session %s)",
url, self._session_key)
headers = {'dataType': 'api', 'sessionKey': self._session_key}
try:
xml = requests.get(url, headers=headers, verify=self.ssl_verify)
xml = requests.get(url, headers=headers,
verify=self.ssl_verify, timeout=60)
tree = etree.XML(xml.text.encode('utf8'))
except Exception:
raise exception.DotHillConnectionError
except Exception as e:
message = _("Exception handling URL %(url)s: %(msg)s") % {
'url': url, 'msg': e}
raise exception.DotHillConnectionError(message=message)
if path == "/show/volumecopy-status":
return tree
@ -120,35 +220,77 @@ class DotHillClient(object):
return tree
def logout(self):
pass
def session_logout(self):
url = self._base_url + '/exit'
try:
requests.get(url, verify=self.ssl_verify)
requests.get(url, verify=self.ssl_verify, timeout=30)
return True
except Exception:
return False
def is_titanium(self):
"""True if array is an older generation."""
return True if len(self._fw) > 0 and self._fw[0] == 'T' else False
def create_volume(self, name, size, backend_name, backend_type):
# NOTE: size is in this format: [0-9]+GB
# NOTE: size is in this format: [0-9]+GiB
path_dict = {'size': size}
if backend_type == "linear":
path_dict['vdisk'] = backend_name
else:
path_dict['pool'] = backend_name
self._request("/create/volume", name, **path_dict)
try:
self._request("/create/volume", name, **path_dict)
except exception.DotHillRequestError as e:
# -10186 => The specified name is already in use.
# This can occur during controller failover.
if '(-10186)' in e.msg:
LOG.warning(_LW("Ignoring error in create volume: %s"), e.msg)
return None
raise
return None
def delete_volume(self, name):
self._request("/delete/volumes", name)
try:
self._request("/delete/volumes", name)
except exception.DotHillRequestError as e:
# -10075 => The specified volume was not found.
# This can occur during controller failover.
if '(-10075)' in e.msg:
LOG.warning(_LW("Ignorning error while deleting %(volume)s:"
" %(reason)s"),
{'volume': name, 'reason': e.msg})
return
raise
def extend_volume(self, name, added_size):
self._request("/expand/volume", name, size=added_size)
def create_snapshot(self, volume_name, snap_name):
self._request("/create/snapshots", snap_name, volumes=volume_name)
try:
self._request("/create/snapshots", snap_name, volumes=volume_name)
except exception.DotHillRequestError as e:
# -10186 => The specified name is already in use.
# This can occur during controller failover.
if '(-10186)' in e.msg:
LOG.warning(_LW("Ignoring error attempting to create snapshot:"
" %s"), e.msg)
return None
def delete_snapshot(self, snap_name):
self._request("/delete/snapshot", "cleanup", snap_name)
try:
self._request("/delete/snapshot", "cleanup", snap_name)
except exception.DotHillRequestError as e:
# -10050 => The volume was not found on this system.
# This can occur during controller failover.
if '(-10050)' in e.msg:
LOG.warning(_LW("Ignoring unmap error -10050: %s"), e.msg)
return None
raise
def backend_exists(self, backend_name, backend_type):
try:
@ -191,13 +333,40 @@ class DotHillClient(object):
"//PROPERTY[@name='lun']")]
def _get_first_available_lun_for_host(self, host):
"""Find next available LUN number.
Returns a lun number greater than 0 which is not known to be in
use between the array and the specified host.
"""
luns = self.list_luns_for_host(host)
self._luns_in_use_by_host[host] = luns
lun = 1
while True:
if lun not in luns:
return lun
lun += 1
def _get_next_available_lun_for_host(self, host, after=0):
# host can be a comma-separated list of WWPNs; we only use the first.
firsthost = host.split(',')[0]
LOG.debug('get_next_available_lun: host=%s, firsthost=%s, after=%d',
host, firsthost, after)
if after == 0:
return self._get_first_available_lun_for_host(firsthost)
luns = self._luns_in_use_by_host[firsthost]
lun = after + 1
while lun < 1024:
LOG.debug('get_next_available_lun: host=%s, trying lun %d',
firsthost, lun)
if lun not in luns:
LOG.debug('get_next_available_lun: host=%s, RETURNING lun %d',
firsthost, lun)
return lun
lun += 1
raise exception.DotHillRequestError(
message=_("No LUNs available for mapping to host %s.") % host)
@utils.synchronized(__name__ + '.map_volume', external=True)
def map_volume(self, volume_name, connector, connector_element):
if connector_element == 'wwpns':
lun = self._get_first_available_lun_for_host(connector['wwpns'][0])
@ -207,22 +376,63 @@ class DotHillClient(object):
host_status = self._check_host(host)
if host_status != 0:
hostname = self._safe_hostname(connector['host'])
self._request("/create/host", hostname, id=host)
try:
self._request("/create/host", hostname, id=host)
except exception.DotHillRequestError as e:
# -10058: The host identifier or nickname is already in use
if '(-10058)' in e.msg:
LOG.error(_LE("While trying to create host nickname"
" %(nickname)s: %(error_msg)s"),
{'nickname': hostname,
'error_msg': e.msg})
else:
raise
lun = self._get_first_available_lun_for_host(host)
self._request("/map/volume",
volume_name,
lun=str(lun),
host=host,
access="rw")
return lun
while lun < 255:
try:
self._request("/map/volume",
volume_name,
lun=str(lun),
host=host,
access="rw")
return lun
except exception.DotHillRequestError as e:
# -3177 => "The specified LUN overlaps a previously defined LUN
if '(-3177)' in e.msg:
LOG.info(_LI("Unable to map volume"
" %(volume_name)s to lun %(lun)d:"
" %(reason)s"),
{'volume_name': volume_name,
'lun': lun, 'reason': e.msg})
lun = self._get_next_available_lun_for_host(host,
after=lun)
continue
raise
except Exception as e:
LOG.error(_LE("Error while mapping volume"
" %(volume_name)s to lun %(lun)d:"),
{'volume_name': volume_name, 'lun': lun},
e)
raise
raise exception.DotHillRequestError(
message=_("Failed to find a free LUN for host %s") % host)
def unmap_volume(self, volume_name, connector, connector_element):
if connector_element == 'wwpns':
host = ",".join(connector['wwpns'])
else:
host = connector['initiator']
self._request("/unmap/volume", volume_name, host=host)
try:
self._request("/unmap/volume", volume_name, host=host)
except exception.DotHillRequestError as e:
# -10050 => The volume was not found on this system.
# This can occur during controller failover.
if '(-10050)' in e.msg:
LOG.warning(_LW("Ignoring unmap error -10050: %s"), e.msg)
return None
raise
def get_active_target_ports(self):
ports = []
@ -332,14 +542,16 @@ class DotHillClient(object):
"""Modify an initiator name to match firmware requirements.
Initiator name cannot include certain characters and cannot exceed
15 bytes in 'T' firmware (32 bytes in 'G' firmware).
15 bytes in 'T' firmware (31 bytes in 'G' firmware).
"""
for ch in [',', '"', '\\', '<', '>']:
if ch in hostname:
hostname = hostname.replace(ch, '')
hostname = hostname.replace('.', '_')
name_limit = 15 if self.is_titanium() else 31
index = len(hostname)
if index > 15:
index = 15
if index > name_limit:
index = name_limit
return hostname[:index]
def get_active_iscsi_target_portals(self):
@ -393,3 +605,9 @@ class DotHillClient(object):
tree = self._request("/show/volumes", volume_name)
size = tree.findtext(".//PROPERTY[@name='size-numeric']")
return self._get_size(size)
def get_firmware_version(self):
tree = self._request("/show/controllers")
self._fw = tree.xpath("//PROPERTY[@name='sc-fw']")[0].text
LOG.debug("Array firmware is %s\n", self._fw)
return self._fw

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -61,7 +62,7 @@ CONF.register_opts(iscsi_opts)
class DotHillCommon(object):
VERSION = "1.0"
VERSION = "1.6"
stats = {}
@ -92,7 +93,6 @@ class DotHillCommon(object):
self.client_logout()
def client_login(self):
LOG.debug("Connecting to %s Array.", self.vendor_name)
try:
self.client.login()
except exception.DotHillConnectionError as ex:
@ -123,7 +123,6 @@ class DotHillCommon(object):
def client_logout(self):
self.client.logout()
LOG.debug("Disconnected from %s Array.", self.vendor_name)
def _get_vol_name(self, volume_id):
volume_name = self._encode_name(volume_id)
@ -163,7 +162,7 @@ class DotHillCommon(object):
self.client_login()
# Use base64 to encode the volume name (UUID is too long for DotHill)
volume_name = self._get_vol_name(volume['id'])
volume_size = "%dGB" % volume['size']
volume_size = "%dGiB" % volume['size']
LOG.debug("Create Volume having display_name: %(display_name)s "
"name: %(name)s id: %(id)s size: %(size)s",
{'display_name': volume['display_name'],
@ -229,6 +228,9 @@ class DotHillCommon(object):
finally:
self.client_logout()
if volume['size'] > src_vref['size']:
self.extend_volume(volume, volume['size'])
def create_volume_from_snapshot(self, volume, snapshot):
self.get_volume_stats(True)
self._assert_enough_space_for_copy(volume['size'])
@ -249,6 +251,9 @@ class DotHillCommon(object):
finally:
self.client_logout()
if volume['size'] > snapshot['volume_size']:
self.extend_volume(volume, volume['size'])
def delete_volume(self, volume):
LOG.debug("Deleting Volume: %s", volume['id'])
if volume['name_id']:
@ -408,17 +413,19 @@ class DotHillCommon(object):
volume_name = self._get_vol_name(volume['name_id'])
else:
volume_name = self._get_vol_name(volume['id'])
old_size = volume['size']
old_size = self.client.get_volume_size(volume_name)
growth_size = int(new_size) - old_size
LOG.debug("Extending Volume %(volume_name)s from %(old_size)s to "
"%(new_size)s, by %(growth_size)s GB.",
"%(new_size)s, by %(growth_size)s GiB.",
{'volume_name': volume_name,
'old_size': old_size,
'new_size': new_size,
'growth_size': growth_size, })
if growth_size < 1:
return
self.client_login()
try:
self.client.extend_volume(volume_name, "%dGB" % growth_size)
self.client.extend_volume(volume_name, "%dGiB" % growth_size)
except exception.DotHillRequestError as ex:
LOG.exception(_LE("Extension of volume %s failed."), volume['id'])
raise exception.Invalid(ex)

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -36,10 +37,11 @@ class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver):
- added support for manage/unmanage volume
- added initiator target mapping in FC zoning
- added https support
1.6 - Add management path redundancy and reduce load placed
on management controller.
"""
VERSION = "1.0"
VERSION = "1.6"
# ThirdPartySystems CI wiki
CI_WIKI_NAME = "Vedams_DotHillDriver_CI"
@ -99,13 +101,16 @@ class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver):
@fczm_utils.RemoveFCZone
def terminate_connection(self, volume, connector, **kwargs):
self.common.unmap_volume(volume, connector, 'wwpns')
info = {'driver_volume_type': 'fibre_channel', 'data': {}}
if not self.common.client.list_luns_for_host(connector['wwpns'][0]):
ports, init_targ_map = self.get_init_targ_map(connector)
info['data'] = {'target_wwn': ports,
'initiator_target_map': init_targ_map}
return info
try:
self.common.unmap_volume(volume, connector, 'wwpns')
if not self.common.client.list_luns_for_host(
connector['wwpns'][0]):
ports, init_targ_map = self.get_init_targ_map(connector)
info['data'] = {'target_wwn': ports,
'initiator_target_map': init_targ_map}
finally:
return info
def get_init_targ_map(self, connector):
init_targ_map = {}
@ -142,7 +147,7 @@ class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver):
self.__class__.__name__)
return stats
def create_export(self, context, volume, connector):
def create_export(self, context, volume, connector=None):
pass
def ensure_export(self, context, volume):

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -45,10 +46,11 @@ class DotHillISCSIDriver(cinder.volume.driver.ISCSIDriver):
- added support for retype volume
- added support for manage/unmanage volume
- added https support
1.6 - Add management path redundancy and reduce load placed
on management controller.
"""
VERSION = "1.0"
VERSION = "1.6"
# ThirdPartySystems CI wiki
CI_WIKI_NAME = "Vedams_DotHillDriver_CI"
@ -158,7 +160,7 @@ class DotHillISCSIDriver(cinder.volume.driver.ISCSIDriver):
self.__class__.__name__)
return stats
def create_export(self, context, volume, connector):
def create_export(self, context, volume, connector=None):
pass
def ensure_export(self, context, volume):

View File

@ -50,7 +50,7 @@ CONF.register_opts(iscsi_opts)
class LenovoCommon(dothill_common.DotHillCommon):
VERSION = "1.0"
VERSION = "1.6"
def __init__(self, config):
self.config = config

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -25,10 +26,11 @@ class LenovoFCDriver(dothill_fc.DotHillFCDriver):
Version history:
1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed
on management controller.
"""
VERSION = "1.0"
VERSION = "1.6"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Vedams-LenovoStorage_FCISCSI_CI"

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -25,10 +26,11 @@ class LenovoISCSIDriver(dothill_iscsi.DotHillISCSIDriver):
Version history:
1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed
on management controller.
"""
VERSION = "1.0"
VERSION = "1.6"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Vedams-LenovoStorage_FCISCSI_CI"

View File

@ -51,7 +51,7 @@ CONF.register_opts(iscsi_opts)
class HPMSACommon(dothill_common.DotHillCommon):
VERSION = "1.0"
VERSION = "1.6"
def __init__(self, config):
self.config = config

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -25,10 +26,11 @@ class HPMSAFCDriver(dothill_fc.DotHillFCDriver):
Version history:
1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed
on management controller.
"""
VERSION = "1.0"
VERSION = "1.6"
def __init__(self, *args, **kwargs):
super(HPMSAFCDriver, self).__init__(*args, **kwargs)

View File

@ -1,5 +1,6 @@
# Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems
# Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@ -25,10 +26,11 @@ class HPMSAISCSIDriver(dothill_iscsi.DotHillISCSIDriver):
Version history:
1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed
on management controller.
"""
VERSION = "1.0"
VERSION = "1.6"
def __init__(self, *args, **kwargs):
super(HPMSAISCSIDriver, self).__init__(*args, **kwargs)