diff --git a/cinder/tests/unit/volume/drivers/test_dothill.py b/cinder/tests/unit/volume/drivers/test_dothill.py index 04d147fe116..80f6c6e4990 100644 --- a/cinder/tests/unit/volume/drivers/test_dothill.py +++ b/cinder/tests/unit/volume/drivers/test_dothill.py @@ -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 @@ -35,6 +36,10 @@ resp_login = ''' 0 12a1626754554a21d85040760c81b 1''' + +resp_fw = '''GLS220R001 + 0''' + resp_badlogin = ''' error 1 @@ -109,13 +114,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', @@ -155,14 +165,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) @@ -187,13 +199,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): @@ -379,7 +391,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) @@ -421,7 +433,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] @@ -430,26 +473,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): @@ -622,14 +669,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) diff --git a/cinder/volume/drivers/dothill/dothill_client.py b/cinder/volume/drivers/dothill/dothill_client.py index dbcd5bdc971..4f9fb271d3e 100644 --- a/cinder/volume/drivers/dothill/dothill_client.py +++ b/cinder/volume/drivers/dothill/dothill_client.py @@ -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 diff --git a/cinder/volume/drivers/dothill/dothill_common.py b/cinder/volume/drivers/dothill/dothill_common.py index ee37554b497..c56a83f4e54 100644 --- a/cinder/volume/drivers/dothill/dothill_common.py +++ b/cinder/volume/drivers/dothill/dothill_common.py @@ -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 @@ -62,7 +63,7 @@ CONF.register_opts(iscsi_opts) class DotHillCommon(object): - VERSION = "1.0" + VERSION = "1.6" stats = {} @@ -93,7 +94,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: @@ -124,7 +124,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) @@ -164,7 +163,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'], @@ -230,6 +229,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']) @@ -250,6 +252,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']: @@ -409,17 +414,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) diff --git a/cinder/volume/drivers/dothill/dothill_fc.py b/cinder/volume/drivers/dothill/dothill_fc.py index 04bd300963f..48961ad8332 100644 --- a/cinder/volume/drivers/dothill/dothill_fc.py +++ b/cinder/volume/drivers/dothill/dothill_fc.py @@ -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" @@ -103,13 +105,16 @@ class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver): @fczm_utils.remove_fc_zone 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 = {} @@ -146,7 +151,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): diff --git a/cinder/volume/drivers/dothill/dothill_iscsi.py b/cinder/volume/drivers/dothill/dothill_iscsi.py index d08312a4d69..0e07cdb96de 100644 --- a/cinder/volume/drivers/dothill/dothill_iscsi.py +++ b/cinder/volume/drivers/dothill/dothill_iscsi.py @@ -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" @@ -162,7 +164,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): diff --git a/cinder/volume/drivers/lenovo/lenovo_common.py b/cinder/volume/drivers/lenovo/lenovo_common.py index 199ae9e00d1..9893774ae30 100644 --- a/cinder/volume/drivers/lenovo/lenovo_common.py +++ b/cinder/volume/drivers/lenovo/lenovo_common.py @@ -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 diff --git a/cinder/volume/drivers/lenovo/lenovo_fc.py b/cinder/volume/drivers/lenovo/lenovo_fc.py index 7e8bc42dee2..4715a27fc97 100644 --- a/cinder/volume/drivers/lenovo/lenovo_fc.py +++ b/cinder/volume/drivers/lenovo/lenovo_fc.py @@ -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" diff --git a/cinder/volume/drivers/lenovo/lenovo_iscsi.py b/cinder/volume/drivers/lenovo/lenovo_iscsi.py index 647dabd9b01..7cdc0e84dd2 100644 --- a/cinder/volume/drivers/lenovo/lenovo_iscsi.py +++ b/cinder/volume/drivers/lenovo/lenovo_iscsi.py @@ -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" diff --git a/cinder/volume/drivers/san/hp/hpmsa_common.py b/cinder/volume/drivers/san/hp/hpmsa_common.py index b43f4b146ad..884bc07d601 100644 --- a/cinder/volume/drivers/san/hp/hpmsa_common.py +++ b/cinder/volume/drivers/san/hp/hpmsa_common.py @@ -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 diff --git a/cinder/volume/drivers/san/hp/hpmsa_fc.py b/cinder/volume/drivers/san/hp/hpmsa_fc.py index efa9e83961d..3a987b40170 100644 --- a/cinder/volume/drivers/san/hp/hpmsa_fc.py +++ b/cinder/volume/drivers/san/hp/hpmsa_fc.py @@ -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) diff --git a/cinder/volume/drivers/san/hp/hpmsa_iscsi.py b/cinder/volume/drivers/san/hp/hpmsa_iscsi.py index d24dd3ad311..4c877d10c72 100644 --- a/cinder/volume/drivers/san/hp/hpmsa_iscsi.py +++ b/cinder/volume/drivers/san/hp/hpmsa_iscsi.py @@ -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)