Create Seagate driver from dothill driver

This patch creates a new 'supported' driver for Seagate (STX) FC and
iSCSI arrays by renaming and re-enabling the old 'unsupported' dothill
driver.  Other than marking the driver as 'supported', this patch
contains no changes in functionality except for multiattach fixes from
change I47f02729437cabab92ccc553a4c60d0c0a796952 needed to pass CI and
deprecation of vendor-specific options requested by the core team.

Other drivers which referenced the dothill driver are modified to use
the Seagate class names, so users of those drivers will not be affected
except for option-deprecation warnings.

Change-Id: I3115ae296ae6b5702c7a8fa39249b8735542e17e
This commit is contained in:
Chris M 2019-07-17 05:45:48 +00:00
parent 1962d17cad
commit e18f05e735
23 changed files with 696 additions and 352 deletions

View File

@ -149,6 +149,8 @@ from cinder.volume.drivers.san import san as cinder_volume_drivers_san_san
from cinder.volume.drivers import sheepdog as cinder_volume_drivers_sheepdog from cinder.volume.drivers import sheepdog as cinder_volume_drivers_sheepdog
from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire
from cinder.volume.drivers import storpool as cinder_volume_drivers_storpool from cinder.volume.drivers import storpool as cinder_volume_drivers_storpool
from cinder.volume.drivers.stx import common as \
cinder_volume_drivers_stx_common
from cinder.volume.drivers.synology import synology_common as \ from cinder.volume.drivers.synology import synology_common as \
cinder_volume_drivers_synology_synologycommon cinder_volume_drivers_synology_synologycommon
from cinder.volume.drivers.veritas_access import veritas_iscsi as \ from cinder.volume.drivers.veritas_access import veritas_iscsi as \
@ -357,6 +359,8 @@ def list_opts():
cinder_volume_drivers_san_san.san_opts, cinder_volume_drivers_san_san.san_opts,
cinder_volume_drivers_sheepdog.sheepdog_opts, cinder_volume_drivers_sheepdog.sheepdog_opts,
cinder_volume_drivers_solidfire.sf_opts, cinder_volume_drivers_solidfire.sf_opts,
cinder_volume_drivers_stx_common.common_opts,
cinder_volume_drivers_stx_common.iscsi_opts,
cinder_volume_drivers_synology_synologycommon.cinder_opts, cinder_volume_drivers_synology_synologycommon.cinder_opts,
cinder_volume_drivers_vmware_vmdk.vmdk_opts, cinder_volume_drivers_vmware_vmdk.vmdk_opts,
cinder_volume_drivers_vzstorage.vzstorage_opts, cinder_volume_drivers_vzstorage.vzstorage_opts,

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 DotHill Systems # Copyright 2015 DotHill Systems
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-19 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -14,22 +14,32 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# #
"""Unit tests for OpenStack Cinder DotHill driver.""" """Unit tests for OpenStack Cinder Seagate driver."""
from defusedxml import lxml as etree
import mock
import requests
from cinder import exception from cinder import exception
from cinder.objects import fields
from cinder import test from cinder import test
from cinder.volume.drivers.dothill import dothill_client as dothill
from cinder.volume.drivers.dothill import dothill_common from cinder.objects import fields
from cinder.volume.drivers.dothill import dothill_fc
from cinder.volume.drivers.dothill import dothill_iscsi import cinder.volume.drivers.stx.client
from cinder.volume.drivers.dothill import exception as dh_exception import cinder.volume.drivers.stx.common
import cinder.volume.drivers.stx.exception as stx_exception
import cinder.volume.drivers.stx.fc
import cinder.volume.drivers.stx.iscsi
from cinder.zonemanager import utils as fczm_utils from cinder.zonemanager import utils as fczm_utils
from defusedxml import lxml as etree
import mock
import requests
STXClient = cinder.volume.drivers.stx.client.STXClient
STXCommon = cinder.volume.drivers.stx.common.STXCommon
STXFCDriver = cinder.volume.drivers.stx.fc.STXFCDriver
STXISCSIDriver = cinder.volume.drivers.stx.iscsi.STXISCSIDriver
session_key = '12a1626754554a21d85040760c81b' session_key = '12a1626754554a21d85040760c81b'
resp_login = '''<RESPONSE><OBJECT basetype="status" name="status" oid="1"> resp_login = '''<RESPONSE><OBJECT basetype="status" name="status" oid="1">
<PROPERTY name="response-type">success</PROPERTY> <PROPERTY name="response-type">success</PROPERTY>
@ -119,7 +129,7 @@ test_retype_volume = {'attach_status': fields.VolumeAttachStatus.DETACHED,
'display_name': 'test volume', 'name': 'volume', 'display_name': 'test volume', 'name': 'volume',
'size': 10} 'size': 10}
test_host = {'capabilities': {'location_info': test_host = {'capabilities': {'location_info':
'DotHillVolumeDriver:xxxxx:dg02:A'}} 'SeagateVolumeDriver:xxxxx:dg02:A'}}
test_snap = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', test_snap = {'id': 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
'volume': {'name_id': None}, 'volume': {'name_id': None},
'volume_id': vol_id, 'display_name': 'test volume', 'volume_id': vol_id, 'display_name': 'test volume',
@ -159,16 +169,16 @@ invalid_connector = {'ip': '10.0.0.2',
'host': 'fakehost'} 'host': 'fakehost'}
class TestDotHillClient(test.TestCase): class TestSeagateClient(test.TestCase):
def setUp(self): def setUp(self):
super(TestDotHillClient, self).setUp() super(TestSeagateClient, self).setUp()
self.login = 'manage' self.login = 'manage'
self.passwd = '!manage' self.passwd = '!manage'
self.ip = '10.0.0.1' self.ip = '10.0.0.1'
self.protocol = 'http' self.protocol = 'http'
self.ssl_verify = False self.ssl_verify = False
self.client = dothill.DotHillClient(self.ip, self.login, self.passwd, self.client = STXClient(self.ip, self.login, self.passwd,
self.protocol, self.ssl_verify) self.protocol, self.ssl_verify)
@mock.patch('requests.get') @mock.patch('requests.get')
def test_login(self, mock_requests_get): def test_login(self, mock_requests_get):
@ -176,7 +186,7 @@ class TestDotHillClient(test.TestCase):
mock_requests_get.return_value = m mock_requests_get.return_value = m
m.text.encode.side_effect = [resp_badlogin, resp_badlogin] m.text.encode.side_effect = [resp_badlogin, resp_badlogin]
self.assertRaises(dh_exception.DotHillAuthenticationError, self.assertRaises(stx_exception.AuthenticationError,
self.client.login) self.client.login)
m.text.encode.side_effect = [resp_login, resp_fw, resp_system] m.text.encode.side_effect = [resp_login, resp_fw, resp_system]
@ -209,10 +219,10 @@ class TestDotHillClient(test.TestCase):
mock_requests_get.return_value = m mock_requests_get.return_value = m
ret = self.client._api_request('/path') ret = self.client._api_request('/path')
self.assertTrue(type(ret) == etree.RestrictedElement) self.assertTrue(type(ret) == etree.RestrictedElement)
self.assertRaises(dh_exception.DotHillConnectionError, self.assertRaises(stx_exception.ConnectionError,
self.client._api_request, self.client._api_request,
'/path') '/path')
self.assertRaises(dh_exception.DotHillConnectionError, self.assertRaises(stx_exception.ConnectionError,
self.client._api_request, self.client._api_request,
'/path') '/path')
@ -222,22 +232,22 @@ class TestDotHillClient(test.TestCase):
invalid_tree = etree.XML(invalid_xml) invalid_tree = etree.XML(invalid_xml)
ret = self.client._assert_response_ok(ok_tree) ret = self.client._assert_response_ok(ok_tree)
self.assertIsNone(ret) self.assertIsNone(ret)
self.assertRaises(dh_exception.DotHillRequestError, self.assertRaises(stx_exception.RequestError,
self.client._assert_response_ok, self.client._assert_response_ok,
not_ok_tree) not_ok_tree)
self.assertRaises(dh_exception.DotHillRequestError, self.assertRaises(stx_exception.RequestError,
self.client._assert_response_ok, invalid_tree) self.client._assert_response_ok, invalid_tree)
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_backend_exists(self, mock_request): def test_backend_exists(self, mock_request):
mock_request.side_effect = [dh_exception.DotHillRequestError, mock_request.side_effect = [stx_exception.RequestError,
fake_xml] fake_xml]
self.assertFalse(self.client.backend_exists('backend_name', self.assertFalse(self.client.backend_exists('backend_name',
'linear')) 'linear'))
self.assertTrue(self.client.backend_exists('backend_name', self.assertTrue(self.client.backend_exists('backend_name',
'linear')) 'linear'))
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_backend_stats(self, mock_request): def test_backend_stats(self, mock_request):
stats = {'free_capacity_gb': 1979, stats = {'free_capacity_gb': 1979,
'total_capacity_gb': 1979} 'total_capacity_gb': 1979}
@ -250,7 +260,7 @@ class TestDotHillClient(test.TestCase):
self.assertEqual(stats, self.client.backend_stats('A', self.assertEqual(stats, self.client.backend_stats('A',
'virtual')) 'virtual'))
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_get_lun(self, mock_request): def test_get_lun(self, mock_request):
mock_request.side_effect = [etree.XML(response_no_lun), mock_request.side_effect = [etree.XML(response_no_lun),
etree.XML(response_lun)] etree.XML(response_lun)]
@ -259,7 +269,7 @@ class TestDotHillClient(test.TestCase):
ret = self.client._get_first_available_lun_for_host("fakehost") ret = self.client._get_first_available_lun_for_host("fakehost")
self.assertEqual(2, ret) self.assertEqual(2, ret)
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_get_ports(self, mock_request): def test_get_ports(self, mock_request):
mock_request.side_effect = [etree.XML(response_ports)] mock_request.side_effect = [etree.XML(response_ports)]
ret = self.client.get_active_target_ports() ret = self.client.get_active_target_ports()
@ -273,19 +283,19 @@ class TestDotHillClient(test.TestCase):
'target-id': 'id5', 'target-id': 'id5',
'status': 'Up'}], ret) 'status': 'Up'}], ret)
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_get_fc_ports(self, mock_request): def test_get_fc_ports(self, mock_request):
mock_request.side_effect = [etree.XML(response_ports)] mock_request.side_effect = [etree.XML(response_ports)]
ret = self.client.get_active_fc_target_ports() ret = self.client.get_active_fc_target_ports()
self.assertEqual(['id2'], ret) self.assertEqual(['id2'], ret)
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_get_iscsi_iqns(self, mock_request): def test_get_iscsi_iqns(self, mock_request):
mock_request.side_effect = [etree.XML(response_ports)] mock_request.side_effect = [etree.XML(response_ports)]
ret = self.client.get_active_iscsi_target_iqns() ret = self.client.get_active_iscsi_target_iqns()
self.assertEqual(['id4', 'id5'], ret) self.assertEqual(['id4', 'id5'], ret)
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_get_iscsi_portals(self, mock_request): def test_get_iscsi_portals(self, mock_request):
portals = {'10.0.0.12': 'Up', '10.0.0.11': 'Up'} portals = {'10.0.0.12': 'Up', '10.0.0.11': 'Up'}
mock_request.side_effect = [etree.XML(response_ports_linear), mock_request.side_effect = [etree.XML(response_ports_linear),
@ -295,7 +305,7 @@ class TestDotHillClient(test.TestCase):
ret = self.client.get_active_iscsi_target_portals() ret = self.client.get_active_iscsi_target_portals()
self.assertEqual(portals, ret) self.assertEqual(portals, ret)
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_delete_snapshot(self, mock_request): def test_delete_snapshot(self, mock_request):
mock_request.side_effect = [None, None] mock_request.side_effect = [None, None]
self.client.delete_snapshot('dummy', 'linear') self.client.delete_snapshot('dummy', 'linear')
@ -303,7 +313,7 @@ class TestDotHillClient(test.TestCase):
self.client.delete_snapshot('dummy', 'paged') self.client.delete_snapshot('dummy', 'paged')
mock_request.assert_called_with('/delete/snapshot', 'dummy') mock_request.assert_called_with('/delete/snapshot', 'dummy')
@mock.patch.object(dothill.DotHillClient, '_request') @mock.patch.object(STXClient, '_request')
def test_list_luns_for_host(self, mock_request): def test_list_luns_for_host(self, mock_request):
mock_request.side_effect = [etree.XML(response_no_lun), mock_request.side_effect = [etree.XML(response_no_lun),
etree.XML(response_lun)] etree.XML(response_lun)]
@ -316,42 +326,44 @@ class TestDotHillClient(test.TestCase):
class FakeConfiguration1(object): class FakeConfiguration1(object):
dothill_backend_name = 'OpenStack' seagate_pool_name = 'OpenStack'
dothill_backend_type = 'linear' seagate_pool_type = 'linear'
san_ip = '10.0.0.1' san_ip = '10.0.0.1'
san_login = 'manage' san_login = 'manage'
san_password = '!manage' san_password = '!manage'
dothill_api_protocol = 'http' seagate_api_protocol = 'http'
driver_use_ssl = True
driver_ssl_cert_verify = False
def safe_get(self, key): def safe_get(self, key):
return 'fakevalue' return 'fakevalue'
class FakeConfiguration2(FakeConfiguration1): class FakeConfiguration2(FakeConfiguration1):
dothill_iscsi_ips = ['10.0.0.11'] seagate_iscsi_ips = ['10.0.0.11']
use_chap_auth = None use_chap_auth = None
class TestFCDotHillCommon(test.TestCase): class TestFCSeagateCommon(test.TestCase):
def setUp(self): def setUp(self):
super(TestFCDotHillCommon, self).setUp() super(TestFCSeagateCommon, self).setUp()
self.config = FakeConfiguration1() self.config = FakeConfiguration1()
self.common = dothill_common.DotHillCommon(self.config) self.common = STXCommon(self.config)
self.common.client_login = mock.MagicMock() self.common.client_login = mock.MagicMock()
self.common.client_logout = mock.MagicMock() self.common.client_logout = mock.MagicMock()
self.common.serialNumber = "xxxxx" self.common.serialNumber = "xxxxx"
self.common.owner = "A" self.common.owner = "A"
self.connector_element = "wwpns" self.connector_element = "wwpns"
@mock.patch.object(dothill.DotHillClient, 'get_serial_number') @mock.patch.object(STXClient, 'get_serial_number')
@mock.patch.object(dothill.DotHillClient, 'get_owner_info') @mock.patch.object(STXClient, 'get_owner_info')
@mock.patch.object(dothill.DotHillClient, 'backend_exists') @mock.patch.object(STXClient, 'backend_exists')
def test_do_setup(self, mock_backend_exists, def test_do_setup(self, mock_backend_exists,
mock_owner_info, mock_serial_number): mock_owner_info, mock_serial_number):
mock_backend_exists.side_effect = [False, True] mock_backend_exists.side_effect = [False, True]
mock_owner_info.return_value = "A" mock_owner_info.return_value = "A"
mock_serial_number.return_value = "xxxxx" mock_serial_number.return_value = "xxxxx"
self.assertRaises(dh_exception.DotHillInvalidBackend, self.assertRaises(stx_exception.InvalidBackend,
self.common.do_setup, None) self.common.do_setup, None)
self.assertIsNone(self.common.do_setup(None)) self.assertIsNone(self.common.do_setup(None))
mock_backend_exists.assert_called_with(self.common.backend_name, mock_backend_exists.assert_called_with(self.common.backend_name,
@ -387,9 +399,9 @@ class TestFCDotHillCommon(test.TestCase):
connector, connector,
self.connector_element)) self.connector_element))
@mock.patch.object(dothill.DotHillClient, 'backend_stats') @mock.patch.object(STXClient, 'backend_stats')
def test_update_volume_stats(self, mock_stats): def test_update_volume_stats(self, mock_stats):
mock_stats.side_effect = [dh_exception.DotHillRequestError, mock_stats.side_effect = [stx_exception.RequestError,
stats_large_space] stats_large_space]
self.assertRaises(exception.Invalid, self.common._update_volume_stats) self.assertRaises(exception.Invalid, self.common._update_volume_stats)
@ -403,16 +415,16 @@ class TestFCDotHillCommon(test.TestCase):
'multiattach': True, 'multiattach': True,
'free_capacity_gb': 90, 'free_capacity_gb': 90,
'location_info': 'location_info':
'DotHillVolumeDriver:xxxxx:OpenStack:A', 'SeagateVolumeDriver:xxxxx:OpenStack:A',
'pool_name': 'OpenStack', 'pool_name': 'OpenStack',
'total_capacity_gb': 100}], 'total_capacity_gb': 100}],
'storage_protocol': None, 'storage_protocol': None,
'vendor_name': 'DotHill', 'vendor_name': 'Seagate',
'volume_backend_name': None}, self.common.stats) 'volume_backend_name': None}, self.common.stats)
@mock.patch.object(dothill.DotHillClient, 'create_volume') @mock.patch.object(STXClient, 'create_volume')
def test_create_volume(self, mock_create): def test_create_volume(self, mock_create):
mock_create.side_effect = [dh_exception.DotHillRequestError, None] mock_create.side_effect = [stx_exception.RequestError, None]
self.assertRaises(exception.Invalid, self.common.create_volume, self.assertRaises(exception.Invalid, self.common.create_volume,
test_volume) test_volume)
@ -423,12 +435,12 @@ class TestFCDotHillCommon(test.TestCase):
self.common.backend_name, self.common.backend_name,
self.common.backend_type) self.common.backend_type)
@mock.patch.object(dothill.DotHillClient, 'delete_volume') @mock.patch.object(STXClient, 'delete_volume')
def test_delete_volume(self, mock_delete): def test_delete_volume(self, mock_delete):
not_found_e = dh_exception.DotHillRequestError( not_found_e = stx_exception.RequestError(
'The volume was not found on this system.') 'The volume was not found on this system.')
mock_delete.side_effect = [not_found_e, mock_delete.side_effect = [not_found_e,
dh_exception.DotHillRequestError, stx_exception.RequestError,
None] None]
self.assertIsNone(self.common.delete_volume(test_volume)) self.assertIsNone(self.common.delete_volume(test_volume))
self.assertRaises(exception.Invalid, self.common.delete_volume, self.assertRaises(exception.Invalid, self.common.delete_volume,
@ -436,19 +448,19 @@ class TestFCDotHillCommon(test.TestCase):
self.assertIsNone(self.common.delete_volume(test_volume)) self.assertIsNone(self.common.delete_volume(test_volume))
mock_delete.assert_called_with(encoded_volid) mock_delete.assert_called_with(encoded_volid)
@mock.patch.object(dothill.DotHillClient, 'copy_volume') @mock.patch.object(STXClient, 'copy_volume')
@mock.patch.object(dothill.DotHillClient, 'backend_stats') @mock.patch.object(STXClient, 'backend_stats')
def test_create_cloned_volume(self, mock_stats, mock_copy): def test_create_cloned_volume(self, mock_stats, mock_copy):
mock_stats.side_effect = [stats_low_space, stats_large_space, mock_stats.side_effect = [stats_low_space, stats_large_space,
stats_large_space] stats_large_space]
self.assertRaises( self.assertRaises(
dh_exception.DotHillNotEnoughSpace, stx_exception.NotEnoughSpace,
self.common.create_cloned_volume, self.common.create_cloned_volume,
dest_volume, detached_volume) dest_volume, detached_volume)
self.assertFalse(mock_copy.called) self.assertFalse(mock_copy.called)
mock_copy.side_effect = [dh_exception.DotHillRequestError, None] mock_copy.side_effect = [stx_exception.RequestError, None]
self.assertRaises(exception.Invalid, self.assertRaises(exception.Invalid,
self.common.create_cloned_volume, self.common.create_cloned_volume,
dest_volume, detached_volume) dest_volume, detached_volume)
@ -461,20 +473,20 @@ class TestFCDotHillCommon(test.TestCase):
self.common.backend_name, self.common.backend_name,
self.common.backend_type) self.common.backend_type)
@mock.patch.object(dothill.DotHillClient, 'copy_volume') @mock.patch.object(STXClient, 'copy_volume')
@mock.patch.object(dothill.DotHillClient, 'backend_stats') @mock.patch.object(STXClient, 'backend_stats')
@mock.patch.object(dothill_common.DotHillCommon, 'extend_volume') @mock.patch.object(STXCommon, 'extend_volume')
def test_create_cloned_volume_larger(self, mock_extend, mock_stats, def test_create_cloned_volume_larger(self, mock_extend, mock_stats,
mock_copy): mock_copy):
mock_stats.side_effect = [stats_low_space, stats_large_space, mock_stats.side_effect = [stats_low_space, stats_large_space,
stats_large_space] stats_large_space]
self.assertRaises(dh_exception.DotHillNotEnoughSpace, self.assertRaises(stx_exception.NotEnoughSpace,
self.common.create_cloned_volume, self.common.create_cloned_volume,
dest_volume_larger, detached_volume) dest_volume_larger, detached_volume)
self.assertFalse(mock_copy.called) self.assertFalse(mock_copy.called)
mock_copy.side_effect = [dh_exception.DotHillRequestError, None] mock_copy.side_effect = [stx_exception.RequestError, None]
self.assertRaises(exception.Invalid, self.assertRaises(exception.Invalid,
self.common.create_cloned_volume, self.common.create_cloned_volume,
dest_volume_larger, detached_volume) dest_volume_larger, detached_volume)
@ -489,20 +501,20 @@ class TestFCDotHillCommon(test.TestCase):
mock_extend.assert_called_once_with(dest_volume_larger, mock_extend.assert_called_once_with(dest_volume_larger,
dest_volume_larger['size']) dest_volume_larger['size'])
@mock.patch.object(dothill.DotHillClient, 'get_volume_size') @mock.patch.object(STXClient, 'get_volume_size')
@mock.patch.object(dothill.DotHillClient, 'extend_volume') @mock.patch.object(STXClient, 'extend_volume')
@mock.patch.object(dothill.DotHillClient, 'copy_volume') @mock.patch.object(STXClient, 'copy_volume')
@mock.patch.object(dothill.DotHillClient, 'backend_stats') @mock.patch.object(STXClient, 'backend_stats')
def test_create_volume_from_snapshot(self, mock_stats, mock_copy, def test_create_volume_from_snapshot(self, mock_stats, mock_copy,
mock_extend, mock_get_size): mock_extend, mock_get_size):
mock_stats.side_effect = [stats_low_space, stats_large_space, mock_stats.side_effect = [stats_low_space, stats_large_space,
stats_large_space] stats_large_space]
self.assertRaises(dh_exception.DotHillNotEnoughSpace, self.assertRaises(stx_exception.NotEnoughSpace,
self.common.create_volume_from_snapshot, self.common.create_volume_from_snapshot,
dest_volume, test_snap) dest_volume, test_snap)
mock_copy.side_effect = [dh_exception.DotHillRequestError, None] mock_copy.side_effect = [stx_exception.RequestError, None]
mock_get_size.return_value = test_snap['volume_size'] mock_get_size.return_value = test_snap['volume_size']
self.assertRaises(exception.Invalid, self.assertRaises(exception.Invalid,
self.common.create_volume_from_snapshot, self.common.create_volume_from_snapshot,
@ -517,10 +529,10 @@ class TestFCDotHillCommon(test.TestCase):
self.common.backend_type) self.common.backend_type)
mock_extend.assert_called_with('vqqqqqqqqqqqqqqqqqqq', '10GiB') mock_extend.assert_called_with('vqqqqqqqqqqqqqqqqqqq', '10GiB')
@mock.patch.object(dothill.DotHillClient, 'get_volume_size') @mock.patch.object(STXClient, 'get_volume_size')
@mock.patch.object(dothill.DotHillClient, 'extend_volume') @mock.patch.object(STXClient, 'extend_volume')
def test_extend_volume(self, mock_extend, mock_size): def test_extend_volume(self, mock_extend, mock_size):
mock_extend.side_effect = [dh_exception.DotHillRequestError, None] mock_extend.side_effect = [stx_exception.RequestError, None]
mock_size.side_effect = [10, 10] mock_size.side_effect = [10, 10]
self.assertRaises(exception.Invalid, self.common.extend_volume, self.assertRaises(exception.Invalid, self.common.extend_volume,
test_volume, 20) test_volume, 20)
@ -528,9 +540,9 @@ class TestFCDotHillCommon(test.TestCase):
self.assertIsNone(ret) self.assertIsNone(ret)
mock_extend.assert_called_with(encoded_volid, '10GiB') mock_extend.assert_called_with(encoded_volid, '10GiB')
@mock.patch.object(dothill.DotHillClient, 'create_snapshot') @mock.patch.object(STXClient, 'create_snapshot')
def test_create_snapshot(self, mock_create): def test_create_snapshot(self, mock_create):
mock_create.side_effect = [dh_exception.DotHillRequestError, None] mock_create.side_effect = [stx_exception.RequestError, None]
self.assertRaises(exception.Invalid, self.common.create_snapshot, self.assertRaises(exception.Invalid, self.common.create_snapshot,
test_snap) test_snap)
@ -538,12 +550,12 @@ class TestFCDotHillCommon(test.TestCase):
self.assertIsNone(ret) self.assertIsNone(ret)
mock_create.assert_called_with(encoded_volid, 'sqqqqqqqqqqqqqqqqqqq') mock_create.assert_called_with(encoded_volid, 'sqqqqqqqqqqqqqqqqqqq')
@mock.patch.object(dothill.DotHillClient, 'delete_snapshot') @mock.patch.object(STXClient, 'delete_snapshot')
def test_delete_snapshot(self, mock_delete): def test_delete_snapshot(self, mock_delete):
not_found_e = dh_exception.DotHillRequestError( not_found_e = stx_exception.RequestError(
'The volume was not found on this system.') 'The volume was not found on this system.')
mock_delete.side_effect = [not_found_e, mock_delete.side_effect = [not_found_e,
dh_exception.DotHillRequestError, stx_exception.RequestError,
None] None]
self.assertIsNone(self.common.delete_snapshot(test_snap)) self.assertIsNone(self.common.delete_snapshot(test_snap))
@ -553,9 +565,9 @@ class TestFCDotHillCommon(test.TestCase):
mock_delete.assert_called_with('sqqqqqqqqqqqqqqqqqqq', mock_delete.assert_called_with('sqqqqqqqqqqqqqqqqqqq',
self.common.backend_type) self.common.backend_type)
@mock.patch.object(dothill.DotHillClient, 'map_volume') @mock.patch.object(STXClient, 'map_volume')
def test_map_volume(self, mock_map): def test_map_volume(self, mock_map):
mock_map.side_effect = [dh_exception.DotHillRequestError, 10] mock_map.side_effect = [stx_exception.RequestError, 10]
self.assertRaises(exception.Invalid, self.common.map_volume, self.assertRaises(exception.Invalid, self.common.map_volume,
test_volume, connector, self.connector_element) test_volume, connector, self.connector_element)
@ -565,9 +577,9 @@ class TestFCDotHillCommon(test.TestCase):
mock_map.assert_called_with(encoded_volid, mock_map.assert_called_with(encoded_volid,
connector, self.connector_element) connector, self.connector_element)
@mock.patch.object(dothill.DotHillClient, 'unmap_volume') @mock.patch.object(STXClient, 'unmap_volume')
def test_unmap_volume(self, mock_unmap): def test_unmap_volume(self, mock_unmap):
mock_unmap.side_effect = [dh_exception.DotHillRequestError, None] mock_unmap.side_effect = [stx_exception.RequestError, None]
self.assertRaises(exception.Invalid, self.common.unmap_volume, self.assertRaises(exception.Invalid, self.common.unmap_volume,
test_volume, connector, self.connector_element) test_volume, connector, self.connector_element)
@ -577,11 +589,11 @@ class TestFCDotHillCommon(test.TestCase):
mock_unmap.assert_called_with(encoded_volid, connector, mock_unmap.assert_called_with(encoded_volid, connector,
self.connector_element) self.connector_element)
@mock.patch.object(dothill.DotHillClient, 'copy_volume') @mock.patch.object(STXClient, 'copy_volume')
@mock.patch.object(dothill.DotHillClient, 'delete_volume') @mock.patch.object(STXClient, 'delete_volume')
@mock.patch.object(dothill.DotHillClient, 'modify_volume_name') @mock.patch.object(STXClient, 'modify_volume_name')
def test_retype(self, mock_modify, mock_delete, mock_copy): def test_retype(self, mock_modify, mock_delete, mock_copy):
mock_copy.side_effect = [dh_exception.DotHillRequestError, None] mock_copy.side_effect = [stx_exception.RequestError, None]
self.assertRaises(exception.Invalid, self.common.migrate_volume, self.assertRaises(exception.Invalid, self.common.migrate_volume,
test_retype_volume, test_host) test_retype_volume, test_host)
ret = self.common.migrate_volume(test_retype_volume, test_host) ret = self.common.migrate_volume(test_retype_volume, test_host)
@ -590,20 +602,20 @@ class TestFCDotHillCommon(test.TestCase):
{'capabilities': {}}) {'capabilities': {}})
self.assertEqual((False, None), ret) self.assertEqual((False, None), ret)
@mock.patch.object(dothill_common.DotHillCommon, '_get_vol_name') @mock.patch.object(STXCommon, '_get_vol_name')
@mock.patch.object(dothill.DotHillClient, 'modify_volume_name') @mock.patch.object(STXClient, 'modify_volume_name')
def test_manage_existing(self, mock_modify, mock_volume): def test_manage_existing(self, mock_modify, mock_volume):
existing_ref = {'source-name': 'xxxx'} existing_ref = {'source-name': 'xxxx'}
mock_modify.side_effect = [dh_exception.DotHillRequestError, None] mock_modify.side_effect = [stx_exception.RequestError, None]
self.assertRaises(exception.Invalid, self.common.manage_existing, self.assertRaises(exception.Invalid, self.common.manage_existing,
test_volume, existing_ref) test_volume, existing_ref)
ret = self.common.manage_existing(test_volume, existing_ref) ret = self.common.manage_existing(test_volume, existing_ref)
self.assertIsNone(ret) self.assertIsNone(ret)
@mock.patch.object(dothill.DotHillClient, 'get_volume_size') @mock.patch.object(STXClient, 'get_volume_size')
def test_manage_existing_get_size(self, mock_volume): def test_manage_existing_get_size(self, mock_volume):
existing_ref = {'source-name': 'xxxx'} existing_ref = {'source-name': 'xxxx'}
mock_volume.side_effect = [dh_exception.DotHillRequestError, 1] mock_volume.side_effect = [stx_exception.RequestError, 1]
self.assertRaises(exception.Invalid, self.assertRaises(exception.Invalid,
self.common.manage_existing_get_size, self.common.manage_existing_get_size,
None, existing_ref) None, existing_ref)
@ -611,28 +623,28 @@ class TestFCDotHillCommon(test.TestCase):
self.assertEqual(1, ret) self.assertEqual(1, ret)
class TestISCSIDotHillCommon(TestFCDotHillCommon): class TestISCSISeagateCommon(TestFCSeagateCommon):
def setUp(self): def setUp(self):
super(TestISCSIDotHillCommon, self).setUp() super(TestISCSISeagateCommon, self).setUp()
self.connector_element = 'initiator' self.connector_element = 'initiator'
class TestDotHillFC(test.TestCase): class TestSeagateFC(test.TestCase):
@mock.patch.object(dothill_common.DotHillCommon, 'do_setup') @mock.patch.object(STXCommon, 'do_setup')
def setUp(self, mock_setup): def setUp(self, mock_setup):
super(TestDotHillFC, self).setUp() super(TestSeagateFC, self).setUp()
self.vendor_name = 'DotHill' self.vendor_name = 'Seagate'
mock_setup.return_value = True mock_setup.return_value = True
def fake_init(self, *args, **kwargs): def fake_init(self, *args, **kwargs):
super(dothill_fc.DotHillFCDriver, self).__init__() super(STXFCDriver, self).__init__()
self.common = None self.common = None
self.configuration = FakeConfiguration1() self.configuration = FakeConfiguration1()
self.lookup_service = fczm_utils.create_lookup_service() self.lookup_service = fczm_utils.create_lookup_service()
dothill_fc.DotHillFCDriver.__init__ = fake_init STXFCDriver.__init__ = fake_init
self.driver = dothill_fc.DotHillFCDriver() self.driver = STXFCDriver()
self.driver.do_setup(None) self.driver.do_setup(None)
def _test_with_mock(self, mock, method, args, expected=None): def _test_with_mock(self, mock, method, args, expected=None):
@ -641,42 +653,42 @@ class TestDotHillFC(test.TestCase):
self.assertRaises(exception.Invalid, func, *args) self.assertRaises(exception.Invalid, func, *args)
self.assertEqual(expected, func(*args)) self.assertEqual(expected, func(*args))
@mock.patch.object(dothill_common.DotHillCommon, 'create_volume') @mock.patch.object(STXCommon, 'create_volume')
def test_create_volume(self, mock_create): def test_create_volume(self, mock_create):
self._test_with_mock(mock_create, 'create_volume', [None]) self._test_with_mock(mock_create, 'create_volume', [None])
@mock.patch.object(dothill_common.DotHillCommon, @mock.patch.object(STXCommon,
'create_cloned_volume') 'create_cloned_volume')
def test_create_cloned_volume(self, mock_create): def test_create_cloned_volume(self, mock_create):
self._test_with_mock(mock_create, 'create_cloned_volume', [None, None]) self._test_with_mock(mock_create, 'create_cloned_volume', [None, None])
@mock.patch.object(dothill_common.DotHillCommon, @mock.patch.object(STXCommon,
'create_volume_from_snapshot') 'create_volume_from_snapshot')
def test_create_volume_from_snapshot(self, mock_create): def test_create_volume_from_snapshot(self, mock_create):
self._test_with_mock(mock_create, 'create_volume_from_snapshot', self._test_with_mock(mock_create, 'create_volume_from_snapshot',
[None, None]) [None, None])
@mock.patch.object(dothill_common.DotHillCommon, 'delete_volume') @mock.patch.object(STXCommon, 'delete_volume')
def test_delete_volume(self, mock_delete): def test_delete_volume(self, mock_delete):
self._test_with_mock(mock_delete, 'delete_volume', [None]) self._test_with_mock(mock_delete, 'delete_volume', [None])
@mock.patch.object(dothill_common.DotHillCommon, 'create_snapshot') @mock.patch.object(STXCommon, 'create_snapshot')
def test_create_snapshot(self, mock_create): def test_create_snapshot(self, mock_create):
self._test_with_mock(mock_create, 'create_snapshot', [None]) self._test_with_mock(mock_create, 'create_snapshot', [None])
@mock.patch.object(dothill_common.DotHillCommon, 'delete_snapshot') @mock.patch.object(STXCommon, 'delete_snapshot')
def test_delete_snapshot(self, mock_delete): def test_delete_snapshot(self, mock_delete):
self._test_with_mock(mock_delete, 'delete_snapshot', [None]) self._test_with_mock(mock_delete, 'delete_snapshot', [None])
@mock.patch.object(dothill_common.DotHillCommon, 'extend_volume') @mock.patch.object(STXCommon, 'extend_volume')
def test_extend_volume(self, mock_extend): def test_extend_volume(self, mock_extend):
self._test_with_mock(mock_extend, 'extend_volume', [None, 10]) self._test_with_mock(mock_extend, 'extend_volume', [None, 10])
@mock.patch.object(dothill_common.DotHillCommon, 'client_logout') @mock.patch.object(STXCommon, 'client_logout')
@mock.patch.object(dothill_common.DotHillCommon, @mock.patch.object(STXCommon,
'get_active_fc_target_ports') 'get_active_fc_target_ports')
@mock.patch.object(dothill_common.DotHillCommon, 'map_volume') @mock.patch.object(STXCommon, 'map_volume')
@mock.patch.object(dothill_common.DotHillCommon, 'client_login') @mock.patch.object(STXCommon, 'client_login')
def test_initialize_connection(self, mock_login, mock_map, mock_ports, def test_initialize_connection(self, mock_login, mock_map, mock_ports,
mock_logout): mock_logout):
mock_login.return_value = None mock_login.return_value = None
@ -698,8 +710,8 @@ class TestDotHillFC(test.TestCase):
'target_lun': 1, 'target_lun': 1,
'target_discovered': True}}, ret) 'target_discovered': True}}, ret)
@mock.patch.object(dothill_common.DotHillCommon, 'unmap_volume') @mock.patch.object(STXCommon, 'unmap_volume')
@mock.patch.object(dothill.DotHillClient, 'list_luns_for_host') @mock.patch.object(STXClient, 'list_luns_for_host')
def test_terminate_connection(self, mock_list, mock_unmap): def test_terminate_connection(self, mock_list, mock_unmap):
mock_unmap.side_effect = [1] mock_unmap.side_effect = [1]
mock_list.side_effect = ['yes'] mock_list.side_effect = ['yes']
@ -710,7 +722,7 @@ class TestDotHillFC(test.TestCase):
ret = self.driver.terminate_connection(test_volume, connector) ret = self.driver.terminate_connection(test_volume, connector)
self.assertEqual(actual, ret) self.assertEqual(actual, ret)
@mock.patch.object(dothill_common.DotHillCommon, 'get_volume_stats') @mock.patch.object(STXCommon, 'get_volume_stats')
def test_get_volume_stats(self, mock_stats): def test_get_volume_stats(self, mock_stats):
stats = {'storage_protocol': None, stats = {'storage_protocol': None,
'driver_version': self.driver.VERSION, 'driver_version': self.driver.VERSION,
@ -734,7 +746,7 @@ class TestDotHillFC(test.TestCase):
self.assertEqual(stats, ret) self.assertEqual(stats, ret)
mock_stats.assert_called_with(True) mock_stats.assert_called_with(True)
@mock.patch.object(dothill_common.DotHillCommon, 'retype') @mock.patch.object(STXCommon, 'retype')
def test_retype(self, mock_retype): def test_retype(self, mock_retype):
mock_retype.side_effect = [exception.Invalid, True, False] mock_retype.side_effect = [exception.Invalid, True, False]
args = [None, None, None, None, None] args = [None, None, None, None, None]
@ -742,12 +754,12 @@ class TestDotHillFC(test.TestCase):
self.assertTrue(self.driver.retype(*args)) self.assertTrue(self.driver.retype(*args))
self.assertFalse(self.driver.retype(*args)) self.assertFalse(self.driver.retype(*args))
@mock.patch.object(dothill_common.DotHillCommon, 'manage_existing') @mock.patch.object(STXCommon, 'manage_existing')
def test_manage_existing(self, mock_manage_existing): def test_manage_existing(self, mock_manage_existing):
self._test_with_mock(mock_manage_existing, 'manage_existing', self._test_with_mock(mock_manage_existing, 'manage_existing',
[None, None]) [None, None])
@mock.patch.object(dothill_common.DotHillCommon, @mock.patch.object(STXCommon,
'manage_existing_get_size') 'manage_existing_get_size')
def test_manage_size(self, mock_manage_size): def test_manage_size(self, mock_manage_size):
mock_manage_size.side_effect = [exception.Invalid, 1] mock_manage_size.side_effect = [exception.Invalid, 1]
@ -757,30 +769,30 @@ class TestDotHillFC(test.TestCase):
self.assertEqual(1, self.driver.manage_existing_get_size(None, None)) self.assertEqual(1, self.driver.manage_existing_get_size(None, None))
class TestDotHillISCSI(TestDotHillFC): class TestSeagateISCSI(TestSeagateFC):
@mock.patch.object(dothill_common.DotHillCommon, 'do_setup') @mock.patch.object(STXCommon, 'do_setup')
def setUp(self, mock_setup): def setUp(self, mock_setup):
super(TestDotHillISCSI, self).setUp() super(TestSeagateISCSI, self).setUp()
self.vendor_name = 'DotHill' self.vendor_name = 'Seagate'
mock_setup.return_value = True mock_setup.return_value = True
def fake_init(self, *args, **kwargs): def fake_init(self, *args, **kwargs):
super(dothill_iscsi.DotHillISCSIDriver, self).__init__() super(STXISCSIDriver, self).__init__()
self.common = None self.common = None
self.configuration = FakeConfiguration2() self.configuration = FakeConfiguration2()
self.iscsi_ips = ['10.0.0.11'] self.iscsi_ips = ['10.0.0.11']
dothill_iscsi.DotHillISCSIDriver.__init__ = fake_init STXISCSIDriver.__init__ = fake_init
self.driver = dothill_iscsi.DotHillISCSIDriver() self.driver = STXISCSIDriver()
self.driver.do_setup(None) self.driver.do_setup(None)
@mock.patch.object(dothill_common.DotHillCommon, 'client_logout') @mock.patch.object(STXCommon, 'client_logout')
@mock.patch.object(dothill_common.DotHillCommon, @mock.patch.object(STXCommon,
'get_active_iscsi_target_portals') 'get_active_iscsi_target_portals')
@mock.patch.object(dothill_common.DotHillCommon, @mock.patch.object(STXCommon,
'get_active_iscsi_target_iqns') 'get_active_iscsi_target_iqns')
@mock.patch.object(dothill_common.DotHillCommon, 'map_volume') @mock.patch.object(STXCommon, 'map_volume')
@mock.patch.object(dothill_common.DotHillCommon, 'client_login') @mock.patch.object(STXCommon, 'client_login')
def test_initialize_connection(self, mock_login, mock_map, mock_iqns, def test_initialize_connection(self, mock_login, mock_map, mock_iqns,
mock_portals, mock_logout): mock_portals, mock_logout):
mock_login.return_value = None mock_login.return_value = None
@ -803,7 +815,7 @@ class TestDotHillISCSI(TestDotHillFC):
'target_discovered': True, 'target_discovered': True,
'target_portal': '10.0.0.11:3260'}}, ret) 'target_portal': '10.0.0.11:3260'}}, ret)
@mock.patch.object(dothill_common.DotHillCommon, 'unmap_volume') @mock.patch.object(STXCommon, 'unmap_volume')
def test_terminate_connection(self, mock_unmap): def test_terminate_connection(self, mock_unmap):
mock_unmap.side_effect = [exception.Invalid, 1] mock_unmap.side_effect = [exception.Invalid, 1]

View File

@ -14,10 +14,10 @@
# under the License. # under the License.
# #
from cinder.volume.drivers.dothill import dothill_client import cinder.volume.drivers.stx.client as client
class LenovoClient(dothill_client.DotHillClient): class LenovoClient(client.STXClient):
def __init__(self, host, login, password, protocol, ssl_verify): def __init__(self, host, login, password, protocol, ssl_verify):
super(LenovoClient, self).__init__(host, login, password, protocol, super(LenovoClient, self).__init__(host, login, password, protocol,

View File

@ -17,25 +17,33 @@
from oslo_config import cfg from oslo_config import cfg
from cinder.volume import configuration from cinder.volume import configuration
from cinder.volume.drivers.dothill import dothill_common import cinder.volume.drivers.lenovo.lenovo_client as lenovo_client
from cinder.volume.drivers.lenovo import lenovo_client import cinder.volume.drivers.stx.common as common
common_opts = [ common_opts = [
cfg.StrOpt('lenovo_backend_name', cfg.StrOpt('lenovo_pool_name',
deprecated_name='lenovo_backend_name',
default='A', default='A',
help="Pool or Vdisk name to use for volume creation."), help="Pool or Vdisk name to use for volume creation."),
cfg.StrOpt('lenovo_backend_type', cfg.StrOpt('lenovo_pool_type',
deprecated_name='lenovo_backend_type',
choices=['linear', 'virtual'], choices=['linear', 'virtual'],
default='virtual', default='virtual',
help="linear (for VDisk) or virtual (for Pool)."), help="linear (for VDisk) or virtual (for Pool)."),
cfg.StrOpt('lenovo_api_protocol', cfg.StrOpt('lenovo_api_protocol',
deprecated_for_removal=True,
deprecated_reason='driver_use_ssl should be used instead.',
choices=['http', 'https'], choices=['http', 'https'],
default='https', default='https',
help="Lenovo api interface protocol."), help="Lenovo api interface protocol."),
cfg.BoolOpt('lenovo_verify_certificate', cfg.BoolOpt('lenovo_verify_certificate',
deprecated_for_removal=True,
deprecated_reason='Use driver_ssl_cert_verify instead.',
default=False, default=False,
help="Whether to verify Lenovo array SSL certificate."), help="Whether to verify Lenovo array SSL certificate."),
cfg.StrOpt('lenovo_verify_certificate_path', cfg.StrOpt('lenovo_verify_certificate_path',
deprecated_for_removal=True,
deprecated_reason='Use driver_ssl_cert_path instead.',
help="Lenovo array SSL certificate path.") help="Lenovo array SSL certificate path.")
] ]
@ -50,19 +58,26 @@ CONF.register_opts(common_opts, group=configuration.SHARED_CONF_GROUP)
CONF.register_opts(iscsi_opts, group=configuration.SHARED_CONF_GROUP) CONF.register_opts(iscsi_opts, group=configuration.SHARED_CONF_GROUP)
class LenovoCommon(dothill_common.DotHillCommon): class LenovoCommon(common.STXCommon):
VERSION = "1.6" VERSION = "2.0"
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.vendor_name = "Lenovo" self.vendor_name = "Lenovo"
self.backend_name = self.config.lenovo_backend_name self.backend_name = self.config.lenovo_pool_name
self.backend_type = self.config.lenovo_backend_type self.backend_type = self.config.lenovo_pool_type
self.api_protocol = self.config.lenovo_api_protocol self.api_protocol = self.config.lenovo_api_protocol
ssl_verify = False ssl_verify = False
# check for deprecated options...
if (self.api_protocol == 'https' and if (self.api_protocol == 'https' and
self.config.lenovo_verify_certificate): self.config.lenovo_verify_certificate):
ssl_verify = self.config.lenovo_verify_certificate_path or True ssl_verify = self.config.lenovo_verify_certificate_path or True
# ...then check common options
if self.config.driver_use_ssl:
self.api_protocol = 'https'
if self.config.driver_ssl_cert_verify:
ssl_verify = self.config.driver_ssl_cert_path or True
self.client = lenovo_client.LenovoClient(self.config.san_ip, self.client = lenovo_client.LenovoClient(self.config.san_ip,
self.config.san_login, self.config.san_login,
self.config.san_password, self.config.san_password,

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -16,12 +16,12 @@
# #
from cinder import interface from cinder import interface
from cinder.volume.drivers.dothill import dothill_fc import cinder.volume.drivers.lenovo.lenovo_common as lenovo_common
from cinder.volume.drivers.lenovo import lenovo_common import cinder.volume.drivers.stx.fc as fc
@interface.volumedriver @interface.volumedriver
class LenovoFCDriver(dothill_fc.DotHillFCDriver): class LenovoFCDriver(fc.STXFCDriver):
"""OpenStack Fibre Channel cinder drivers for Lenovo Storage arrays. """OpenStack Fibre Channel cinder drivers for Lenovo Storage arrays.
.. code-block:: default .. code-block:: default
@ -30,10 +30,10 @@ class LenovoFCDriver(dothill_fc.DotHillFCDriver):
1.0 - Inheriting from DotHill cinder drivers. 1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed 1.6 - Add management path redundancy and reduce load placed
on management controller. on management controller.
2.0 - DotHill driver renamed to Seagate (STX)
""" """
VERSION = "1.6" VERSION = "2.0"
SUPPORTED = True SUPPORTED = True

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -16,12 +16,12 @@
# #
from cinder import interface from cinder import interface
from cinder.volume.drivers.dothill import dothill_iscsi import cinder.volume.drivers.lenovo.lenovo_common as lenovo_common
from cinder.volume.drivers.lenovo import lenovo_common import cinder.volume.drivers.stx.iscsi as iscsi
@interface.volumedriver @interface.volumedriver
class LenovoISCSIDriver(dothill_iscsi.DotHillISCSIDriver): class LenovoISCSIDriver(iscsi.STXISCSIDriver):
"""OpenStack iSCSI cinder drivers for Lenovo Storage arrays. """OpenStack iSCSI cinder drivers for Lenovo Storage arrays.
.. code-block:: default .. code-block:: default
@ -30,9 +30,10 @@ class LenovoISCSIDriver(dothill_iscsi.DotHillISCSIDriver):
1.0 - Inheriting from DotHill cinder drivers. 1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed 1.6 - Add management path redundancy and reduce load placed
on management controller. on management controller.
2.0 - DotHill driver renamed to Seagate (STX)
""" """
VERSION = "1.6" VERSION = "2.0"
SUPPORTED = True SUPPORTED = True

View File

@ -14,10 +14,10 @@
# under the License. # under the License.
# #
from cinder.volume.drivers.dothill import dothill_client import cinder.volume.drivers.stx.client as client
class HPMSAClient(dothill_client.DotHillClient): class HPMSAClient(client.STXClient):
def __init__(self, host, login, password, protocol, ssl_verify): def __init__(self, host, login, password, protocol, ssl_verify):
super(HPMSAClient, self).__init__(host, login, password, super(HPMSAClient, self).__init__(host, login, password,

View File

@ -17,27 +17,34 @@
from oslo_config import cfg from oslo_config import cfg
from cinder.volume import configuration from cinder.volume import configuration
from cinder.volume.drivers.dothill import dothill_common import cinder.volume.drivers.san.hp.hpmsa_client as hpmsa_client
from cinder.volume.drivers.san.hp import hpmsa_client import cinder.volume.drivers.stx.common as common
common_opts = [ common_opts = [
cfg.StrOpt('hpmsa_backend_name', cfg.StrOpt('hpmsa_pool_name',
deprecated_name='hpmsa_backend_name',
default='A', default='A',
help="Pool or Vdisk name to use for volume creation."), help="Pool or Vdisk name to use for volume creation."),
cfg.StrOpt('hpmsa_backend_type', cfg.StrOpt('hpmsa_pool_type',
deprecated_name='hpmsa_backend_type',
choices=['linear', 'virtual'], choices=['linear', 'virtual'],
default='virtual', default='virtual',
help="linear (for Vdisk) or virtual (for Pool)."), help="linear (for Vdisk) or virtual (for Pool)."),
cfg.StrOpt('hpmsa_api_protocol', cfg.StrOpt('hpmsa_api_protocol',
deprecated_for_removal=True,
deprecated_reason='driver_use_ssl should be used instead.',
choices=['http', 'https'], choices=['http', 'https'],
default='https', default='https',
help="HPMSA API interface protocol."), help="HPMSA API interface protocol."),
cfg.BoolOpt('hpmsa_verify_certificate', cfg.BoolOpt('hpmsa_verify_certificate',
deprecated_for_removal=True,
deprecated_reason='Use driver_ssl_cert_verify instead.',
default=False, default=False,
help="Whether to verify HPMSA array SSL certificate."), help="Whether to verify HPMSA array SSL certificate."),
cfg.StrOpt('hpmsa_verify_certificate_path', cfg.StrOpt('hpmsa_verify_certificate_path',
deprecated_for_removal=True,
deprecated_reason='Use driver_ssl_cert_path instead.',
help="HPMSA array SSL certificate path."), help="HPMSA array SSL certificate path."),
] ]
iscsi_opts = [ iscsi_opts = [
@ -51,19 +58,25 @@ CONF.register_opts(common_opts, group=configuration.SHARED_CONF_GROUP)
CONF.register_opts(iscsi_opts, group=configuration.SHARED_CONF_GROUP) CONF.register_opts(iscsi_opts, group=configuration.SHARED_CONF_GROUP)
class HPMSACommon(dothill_common.DotHillCommon): class HPMSACommon(common.STXCommon):
VERSION = "1.6" VERSION = "2.0"
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.vendor_name = "HPMSA" self.vendor_name = "HPMSA"
self.backend_name = self.config.hpmsa_backend_name self.backend_name = self.config.hpmsa_pool_name
self.backend_type = self.config.hpmsa_backend_type self.backend_type = self.config.hpmsa_pool_type
self.api_protocol = self.config.hpmsa_api_protocol self.api_protocol = self.config.hpmsa_api_protocol
ssl_verify = False ssl_verify = False
# check deprecated vendor-specific options ...
if (self.api_protocol == 'https' and if (self.api_protocol == 'https' and
self.config.hpmsa_verify_certificate): self.config.hpmsa_verify_certificate):
ssl_verify = self.config.hpmsa_verify_certificate_path or True ssl_verify = self.config.hpmsa_verify_certificate_path or True
# ... before newer common options
if self.config.driver_use_ssl:
self.api_protocol = 'https'
if self.config.driver_ssl_cert_verify:
ssl_verify = self.config.driver_ssl_cert_path or True
self.client = hpmsa_client.HPMSAClient(self.config.san_ip, self.client = hpmsa_client.HPMSAClient(self.config.san_ip,
self.config.san_login, self.config.san_login,

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -16,12 +16,12 @@
# #
from cinder import interface from cinder import interface
from cinder.volume.drivers.dothill import dothill_fc import cinder.volume.drivers.san.hp.hpmsa_common as hpmsa_common
from cinder.volume.drivers.san.hp import hpmsa_common import cinder.volume.drivers.stx.fc as fc
@interface.volumedriver @interface.volumedriver
class HPMSAFCDriver(dothill_fc.DotHillFCDriver): class HPMSAFCDriver(fc.STXFCDriver):
"""OpenStack Fibre Channel cinder drivers for HPMSA arrays. """OpenStack Fibre Channel cinder drivers for HPMSA arrays.
.. code-block:: default .. code-block:: default
@ -30,12 +30,12 @@ class HPMSAFCDriver(dothill_fc.DotHillFCDriver):
1.0 - Inheriting from DotHill cinder drivers. 1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed 1.6 - Add management path redundancy and reduce load placed
on management controller. on management controller.
2.0 - DotHill driver renamed to Seagate (STX)
""" """
VERSION = "1.6" VERSION = "2.0"
CI_WIKI_NAME = "Vedams-HPMSA_FCISCSIDriver_CI" CI_WIKI_NAME = "HPMSA_CI"
SUPPORTED = True SUPPORTED = True

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -16,12 +16,12 @@
# #
from cinder import interface from cinder import interface
from cinder.volume.drivers.dothill import dothill_iscsi import cinder.volume.drivers.san.hp.hpmsa_common as hpmsa_common
from cinder.volume.drivers.san.hp import hpmsa_common import cinder.volume.drivers.stx.iscsi as iscsi
@interface.volumedriver @interface.volumedriver
class HPMSAISCSIDriver(dothill_iscsi.DotHillISCSIDriver): class HPMSAISCSIDriver(iscsi.STXISCSIDriver):
"""OpenStack iSCSI cinder drivers for HPMSA arrays. """OpenStack iSCSI cinder drivers for HPMSA arrays.
.. code-block:: default .. code-block:: default
@ -30,12 +30,12 @@ class HPMSAISCSIDriver(dothill_iscsi.DotHillISCSIDriver):
1.0 - Inheriting from DotHill cinder drivers. 1.0 - Inheriting from DotHill cinder drivers.
1.6 - Add management path redundancy and reduce load placed 1.6 - Add management path redundancy and reduce load placed
on management controller. on management controller.
2.0 - DotHill driver renamed to Seagate (STX)
""" """
VERSION = "1.6" VERSION = "2.0"
CI_WIKI_NAME = "Vedams-HPMSA_FCISCSIDriver_CI" CI_WIKI_NAME = "HPMSA_CI"
SUPPORTED = True SUPPORTED = True

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -19,22 +19,23 @@ import hashlib
import math import math
import time import time
from cinder import utils
from defusedxml import lxml as etree from defusedxml import lxml as etree
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import strutils from oslo_utils import strutils
from oslo_utils import units from oslo_utils import units
import pprint
import requests import requests
import six import six
from cinder import coordination from cinder import coordination
from cinder.i18n import _ from cinder.i18n import _
from cinder.volume.drivers.dothill import exception as dh_exception import cinder.volume.drivers.stx.exception as stx_exception
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class DotHillClient(object): @six.add_metaclass(utils.TraceWrapperMetaclass)
class STXClient(object):
def __init__(self, host, login, password, protocol, ssl_verify): def __init__(self, host, login, password, protocol, ssl_verify):
self._mgmt_ip_addrs = list(map(str.strip, host.split(','))) self._mgmt_ip_addrs = list(map(str.strip, host.split(',')))
self._login = login self._login = login
@ -65,7 +66,7 @@ class DotHillClient(object):
self._session_key = session_key self._session_key = session_key
except Exception as e: except Exception as e:
msg = _("Cannot parse session key: %s") % e.msg msg = _("Cannot parse session key: %s") % e.msg
raise dh_exception.DotHillConnectionError(message=msg) raise stx_exception.ConnectionError(message=msg)
def login(self): def login(self):
if self._session_key is None: if self._session_key is None:
@ -86,7 +87,7 @@ class DotHillClient(object):
LOG.debug("Logged in to array %s at %s (session %s)", LOG.debug("Logged in to array %s at %s (session %s)",
self._array_name, self._base_url, self._session_key) self._array_name, self._base_url, self._session_key)
return return
except dh_exception.DotHillConnectionError: except stx_exception.ConnectionError:
not_responding = self._curr_ip_addr not_responding = self._curr_ip_addr
LOG.exception('session_login failed to connect to %s', LOG.exception('session_login failed to connect to %s',
self._curr_ip_addr) self._curr_ip_addr)
@ -99,11 +100,11 @@ class DotHillClient(object):
try: try:
self._get_session_key() self._get_session_key()
return return
except dh_exception.DotHillConnectionError: except stx_exception.ConnectionError:
LOG.error('Failed to connect to %s', LOG.error('Failed to connect to %s',
self._curr_ip_addr) self._curr_ip_addr)
continue continue
raise dh_exception.DotHillConnectionError( raise stx_exception.ConnectionError(
message=_("Failed to log in to management controller")) message=_("Failed to log in to management controller"))
@coordination.synchronized('{self._driver_name}-{self._array_name}') @coordination.synchronized('{self._driver_name}-{self._array_name}')
@ -114,7 +115,7 @@ class DotHillClient(object):
hash_ = "%s_%s" % (self._login, self._password) hash_ = "%s_%s" % (self._login, self._password)
if six.PY3: if six.PY3:
hash_ = hash_.encode('utf-8') hash_ = hash_.encode('utf-8')
hash_ = hashlib.md5(hash_) hash_ = hashlib.md5(hash_) # nosec
digest = hash_.hexdigest() digest = hash_.hexdigest()
url = self._base_url + "/login/" + digest url = self._base_url + "/login/" + digest
@ -123,24 +124,24 @@ class DotHillClient(object):
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
msg = _("Failed to obtain MC session key") msg = _("Failed to obtain MC session key")
LOG.exception(msg) LOG.exception(msg)
raise dh_exception.DotHillConnectionError(message=msg) raise stx_exception.ConnectionError(message=msg)
self._get_auth_token(xml.text.encode('utf8')) self._get_auth_token(xml.text.encode('utf8'))
LOG.debug("session key = %s", self._session_key) LOG.debug("session key = %s", self._session_key)
if self._session_key is None: if self._session_key is None:
raise dh_exception.DotHillAuthenticationError raise stx_exception.AuthenticationError
def _assert_response_ok(self, tree): def _assert_response_ok(self, tree):
"""Parses the XML returned by the device to check the return code. """Parses the XML returned by the device to check the return code.
Raises a DotHillRequestError error if the return code is not 0 Raises a RequestError error if the return code is not 0
or if the return code is None. or if the return code is None.
""" """
# Get the return code for the operation, raising an exception # Get the return code for the operation, raising an exception
# if it is not present. # if it is not present.
return_code = tree.findtext(".//PROPERTY[@name='return-code']") return_code = tree.findtext(".//PROPERTY[@name='return-code']")
if not return_code: if not return_code:
raise dh_exception.DotHillRequestError(message="No status found") raise stx_exception.RequestError(message="No status found")
# If no error occurred, just return. # If no error occurred, just return.
if return_code == '0': if return_code == '0':
@ -150,7 +151,7 @@ class DotHillClient(object):
msg = "%s (%s)" % (tree.findtext(".//PROPERTY[@name='response']"), msg = "%s (%s)" % (tree.findtext(".//PROPERTY[@name='response']"),
return_code) return_code)
raise dh_exception.DotHillRequestError(message=msg) raise stx_exception.RequestError(message=msg)
def _build_request_url(self, path, *args, **kargs): def _build_request_url(self, path, *args, **kargs):
url = self._base_url + path url = self._base_url + path
@ -165,10 +166,10 @@ class DotHillClient(object):
def _request(self, path, *args, **kargs): def _request(self, path, *args, **kargs):
"""Performs an API request on the array, with retry. """Performs an API request on the array, with retry.
Propagates a DotHillConnectionError if no valid response is Propagates a ConnectionError if no valid response is
received from the array, e.g. if the network is down. received from the array, e.g. if the network is down.
Propagates a DotHillRequestError if the device returned a response Propagates a RequestError if the device returned a response
but the status is not 0. The device error message will be used but the status is not 0. The device error message will be used
in the exception message. in the exception message.
@ -178,14 +179,14 @@ class DotHillClient(object):
while tries_left > 0: while tries_left > 0:
try: try:
return self._api_request(path, *args, **kargs) return self._api_request(path, *args, **kargs)
except dh_exception.DotHillConnectionError as e: except stx_exception.ConnectionError as e:
if tries_left < 1: if tries_left < 1:
LOG.error("Array Connection error: " LOG.error("Array Connection error: "
"%s (no more retries)", e.msg) "%s (no more retries)", e.msg)
raise raise
# Retry on any network connection errors, SSL errors, etc # Retry on any network connection errors, SSL errors, etc
LOG.error("Array Connection error: %s (retrying)", e.msg) LOG.error("Array Connection error: %s (retrying)", e.msg)
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
if tries_left < 1: if tries_left < 1:
LOG.error("Array Request error: %s (no more retries)", LOG.error("Array Request error: %s (no more retries)",
e.msg) e.msg)
@ -204,7 +205,7 @@ class DotHillClient(object):
def _api_request(self, path, *args, **kargs): def _api_request(self, path, *args, **kargs):
"""Performs an HTTP request on the device, with locking. """Performs an HTTP request on the device, with locking.
Raises a DotHillRequestError if the device returned but the status is Raises a RequestError if the device returned but the status is
not 0. The device error message will be used in the exception message. 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. If the status is OK, returns the XML data for further processing.
@ -221,7 +222,7 @@ class DotHillClient(object):
except Exception as e: except Exception as e:
message = _("Exception handling URL %(url)s: %(msg)s") % { message = _("Exception handling URL %(url)s: %(msg)s") % {
'url': url, 'msg': e} 'url': url, 'msg': e}
raise dh_exception.DotHillConnectionError(message=message) raise stx_exception.ConnectionError(message=message)
if path == "/show/volumecopy-status": if path == "/show/volumecopy-status":
return tree return tree
@ -253,7 +254,7 @@ class DotHillClient(object):
try: try:
self._request("/create/volume", name, **path_dict) self._request("/create/volume", name, **path_dict)
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
# -10186 => The specified name is already in use. # -10186 => The specified name is already in use.
# This can occur during controller failover. # This can occur during controller failover.
if '(-10186)' in e.msg: if '(-10186)' in e.msg:
@ -266,7 +267,7 @@ class DotHillClient(object):
def delete_volume(self, name): def delete_volume(self, name):
try: try:
self._request("/delete/volumes", name) self._request("/delete/volumes", name)
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
# -10075 => The specified volume was not found. # -10075 => The specified volume was not found.
# This can occur during controller failover. # This can occur during controller failover.
if '(-10075)' in e.msg: if '(-10075)' in e.msg:
@ -282,7 +283,7 @@ class DotHillClient(object):
def create_snapshot(self, volume_name, snap_name): def create_snapshot(self, volume_name, snap_name):
try: try:
self._request("/create/snapshots", snap_name, volumes=volume_name) self._request("/create/snapshots", snap_name, volumes=volume_name)
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
# -10186 => The specified name is already in use. # -10186 => The specified name is already in use.
# This can occur during controller failover. # This can occur during controller failover.
if '(-10186)' in e.msg: if '(-10186)' in e.msg:
@ -296,7 +297,7 @@ class DotHillClient(object):
self._request("/delete/snapshot", "cleanup", snap_name) self._request("/delete/snapshot", "cleanup", snap_name)
else: else:
self._request("/delete/snapshot", snap_name) self._request("/delete/snapshot", snap_name)
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
# -10050 => The volume was not found on this system. # -10050 => The volume was not found on this system.
# This can occur during controller failover. # This can occur during controller failover.
if '(-10050)' in e.msg: if '(-10050)' in e.msg:
@ -312,7 +313,7 @@ class DotHillClient(object):
path = "/show/pools" path = "/show/pools"
self._request(path, backend_name) self._request(path, backend_name)
return True return True
except dh_exception.DotHillRequestError: except stx_exception.RequestError:
return False return False
def _get_size(self, size): def _get_size(self, size):
@ -378,7 +379,7 @@ class DotHillClient(object):
firsthost, lun) firsthost, lun)
return lun return lun
lun += 1 lun += 1
raise dh_exception.DotHillRequestError( raise stx_exception.RequestError(
message=_("No LUNs available for mapping to host %s.") % host) message=_("No LUNs available for mapping to host %s.") % host)
def _is_mapped(self, volume_name, ids): def _is_mapped(self, volume_name, ids):
@ -393,7 +394,7 @@ class DotHillClient(object):
if iid in ids: if iid in ids:
LOG.debug("volume '{}' is already mapped to {} at lun {}". LOG.debug("volume '{}' is already mapped to {} at lun {}".
format(volume_name, iid, lun)) format(volume_name, iid, lun))
return lun return int(lun)
except Exception as e: except Exception as e:
LOG.exception("failed to look up mappings for volume '%s'", LOG.exception("failed to look up mappings for volume '%s'",
volume_name) volume_name)
@ -403,8 +404,6 @@ class DotHillClient(object):
@coordination.synchronized('{self._driver_name}-{self._array_name}-map') @coordination.synchronized('{self._driver_name}-{self._array_name}-map')
def map_volume(self, volume_name, connector, connector_element): def map_volume(self, volume_name, connector, connector_element):
# If multiattach enabled, its possible the volume is already mapped # If multiattach enabled, its possible the volume is already mapped
LOG.debug("map_volume(%s, %s, %s)", volume_name,
pprint.pformat(connector), connector_element)
lun = self._is_mapped(volume_name, connector[connector_element]) lun = self._is_mapped(volume_name, connector[connector_element])
if lun: if lun:
return lun return lun
@ -418,7 +417,7 @@ class DotHillClient(object):
hostname = self._safe_hostname(connector['host']) hostname = self._safe_hostname(connector['host'])
try: try:
self._request("/create/host", hostname, id=host) self._request("/create/host", hostname, id=host)
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
# -10058: The host identifier or nickname is already in use # -10058: The host identifier or nickname is already in use
if '(-10058)' in e.msg: if '(-10058)' in e.msg:
LOG.error("While trying to create host nickname" LOG.error("While trying to create host nickname"
@ -437,7 +436,7 @@ class DotHillClient(object):
host=host, host=host,
access="rw") access="rw")
return lun return lun
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
# -3177 => "The specified LUN overlaps a previously defined LUN # -3177 => "The specified LUN overlaps a previously defined LUN
if '(-3177)' in e.msg: if '(-3177)' in e.msg:
LOG.info("Unable to map volume" LOG.info("Unable to map volume"
@ -456,7 +455,7 @@ class DotHillClient(object):
e) e)
raise raise
raise dh_exception.DotHillRequestError( raise stx_exception.RequestError(
message=_("Failed to find a free LUN for host %s") % host) message=_("Failed to find a free LUN for host %s") % host)
def unmap_volume(self, volume_name, connector, connector_element): def unmap_volume(self, volume_name, connector, connector_element):
@ -466,7 +465,7 @@ class DotHillClient(object):
host = connector['initiator'] host = connector['initiator']
try: try:
self._request("/unmap/volume", volume_name, host=host) self._request("/unmap/volume", volume_name, host=host)
except dh_exception.DotHillRequestError as e: except stx_exception.RequestError as e:
# -10050 => The volume was not found on this system. # -10050 => The volume was not found on this system.
# This can occur during controller failover. # This can occur during controller failover.
if '(-10050)' in e.msg: if '(-10050)' in e.msg:
@ -522,7 +521,7 @@ class DotHillClient(object):
else: else:
if count >= 5: if count >= 5:
LOG.error('Error in copying volume: %s', src_name) LOG.error('Error in copying volume: %s', src_name)
raise dh_exception.DotHillRequestError raise stx_exception.RequestError
time.sleep(1) time.sleep(1)
count += 1 count += 1

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -15,7 +15,7 @@
# under the License. # under the License.
# #
""" """
Volume driver common utilities for DotHill Storage array Volume driver common utilities for Seagate storage arrays
""" """
import base64 import base64
@ -28,34 +28,56 @@ from oslo_log import log as logging
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder.objects import fields from cinder.objects import fields
from cinder.volume.drivers.dothill import dothill_client as dothill from cinder import utils
from cinder.volume.drivers.dothill import exception as dh_exception from cinder.volume import configuration
import cinder.volume.drivers.stx.client as client
import cinder.volume.drivers.stx.exception as stx_exception
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
common_opts = [
cfg.StrOpt('seagate_pool_name',
default='A',
help="Pool or vdisk name to use for volume creation."),
cfg.StrOpt('seagate_pool_type',
choices=['linear', 'virtual'],
default='virtual',
help="linear (for vdisk) or virtual (for virtual pool)."),
]
iscsi_opts = [
cfg.ListOpt('seagate_iscsi_ips',
default=[],
help="List of comma-separated target iSCSI IP addresses."),
]
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(common_opts, group=configuration.SHARED_CONF_GROUP)
CONF.register_opts(iscsi_opts, group=configuration.SHARED_CONF_GROUP)
class DotHillCommon(object): @six.add_metaclass(utils.TraceWrapperMetaclass)
VERSION = "1.6" class STXCommon(object):
VERSION = "2.0"
stats = {} stats = {}
def __init__(self, config): def __init__(self, config):
self.config = config self.config = config
self.vendor_name = "DotHill" self.vendor_name = "Seagate"
self.backend_name = self.config.dothill_backend_name self.backend_name = self.config.seagate_pool_name
self.backend_type = self.config.dothill_backend_type self.backend_type = self.config.seagate_pool_type
self.api_protocol = self.config.dothill_api_protocol self.api_protocol = 'http'
ssl_verify = False if self.config.driver_use_ssl:
if (self.api_protocol == 'https' and self.api_protocol = 'https'
self.config.dothill_verify_certificate): ssl_verify = self.config.driver_ssl_cert_verify
ssl_verify = self.config.dothill_verify_certificate_path or True if ssl_verify and self.config.driver_ssl_cert_path:
self.client = dothill.DotHillClient(self.config.san_ip, ssl_verify = self.config.driver_ssl_cert_path
self.config.san_login, self.client = client.STXClient(self.config.san_ip,
self.config.san_password, self.config.san_login,
self.api_protocol, self.config.san_password,
ssl_verify) self.api_protocol,
ssl_verify)
def get_version(self): def get_version(self):
return self.VERSION return self.VERSION
@ -70,18 +92,18 @@ class DotHillCommon(object):
def client_login(self): def client_login(self):
try: try:
self.client.login() self.client.login()
except dh_exception.DotHillConnectionError as ex: except stx_exception.ConnectionError as ex:
msg = _("Failed to connect to %(vendor_name)s Array %(host)s: " msg = _("Failed to connect to %(vendor_name)s Array %(host)s: "
"%(err)s") % {'vendor_name': self.vendor_name, "%(err)s") % {'vendor_name': self.vendor_name,
'host': self.config.san_ip, 'host': self.config.san_ip,
'err': six.text_type(ex)} 'err': six.text_type(ex)}
LOG.error(msg) LOG.error(msg)
raise dh_exception.DotHillConnectionError(message=msg) raise stx_exception.ConnectionError(message=msg)
except dh_exception.DotHillAuthenticationError: except stx_exception.AuthenticationError:
msg = _("Failed to log on %s Array " msg = _("Failed to log on %s Array "
"(invalid login?).") % self.vendor_name "(invalid login?).") % self.vendor_name
LOG.error(msg) LOG.error(msg)
raise dh_exception.DotHillAuthenticationError(message=msg) raise stx_exception.AuthenticationError(message=msg)
def _get_serial_number(self): def _get_serial_number(self):
self.serialNumber = self.client.get_serial_number() self.serialNumber = self.client.get_serial_number()
@ -94,7 +116,7 @@ class DotHillCommon(object):
if not self.client.backend_exists(self.backend_name, if not self.client.backend_exists(self.backend_name,
self.backend_type): self.backend_type):
self.client_logout() self.client_logout()
raise dh_exception.DotHillInvalidBackend(backend=self.backend_name) raise stx_exception.InvalidBackend(backend=self.backend_name)
def client_logout(self): def client_logout(self):
self.client.logout() self.client.logout()
@ -108,7 +130,7 @@ class DotHillCommon(object):
return "s%s" % snapshot_name return "s%s" % snapshot_name
def _encode_name(self, name): def _encode_name(self, name):
"""Get converted DotHill volume name. """Get converted array volume name.
Converts the openstack volume id from Converts the openstack volume id from
fceec30e-98bc-4ce5-85ff-d7309cc17cc2 fceec30e-98bc-4ce5-85ff-d7309cc17cc2
@ -135,7 +157,7 @@ class DotHillCommon(object):
def create_volume(self, volume): def create_volume(self, volume):
self.client_login() self.client_login()
# Use base64 to encode the volume name (UUID is too long for DotHill) # Use base64 to encode the volume name (UUID is too long)
volume_name = self._get_vol_name(volume['id']) volume_name = self._get_vol_name(volume['id'])
volume_size = "%dGiB" % volume['size'] volume_size = "%dGiB" % volume['size']
LOG.debug("Create Volume having display_name: %(display_name)s " LOG.debug("Create Volume having display_name: %(display_name)s "
@ -149,7 +171,7 @@ class DotHillCommon(object):
volume_size, volume_size,
self.backend_name, self.backend_name,
self.backend_type) self.backend_type)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Creation of volume %s failed.", volume['id']) LOG.exception("Creation of volume %s failed.", volume['id'])
raise exception.Invalid(ex) raise exception.Invalid(ex)
@ -157,22 +179,19 @@ class DotHillCommon(object):
self.client_logout() self.client_logout()
def _assert_enough_space_for_copy(self, volume_size): def _assert_enough_space_for_copy(self, volume_size):
"""The DotHill creates a snap pool before trying to copy the volume. """The array creates a snap pool before trying to copy the volume.
The pool is 5.27GB or 20% of the volume size, whichever is larger. The pool is 5.27GB or 20% of the volume size, whichever is larger.
Verify that we have enough space for the pool and then copy Verify that we have enough space for the pool and then copy.
""" """
pool_size = max(volume_size * 0.2, 5.27) pool_size = max(volume_size * 0.2, 5.27)
required_size = pool_size + volume_size required_size = pool_size + volume_size
if required_size > self.stats['pools'][0]['free_capacity_gb']: if required_size > self.stats['pools'][0]['free_capacity_gb']:
raise dh_exception.DotHillNotEnoughSpace(backend=self.backend_name) raise stx_exception.NotEnoughSpace(backend=self.backend_name)
def _assert_source_detached(self, volume): def _assert_source_detached(self, volume):
"""The DotHill requires a volume to be dettached to clone it. """The array requires volume to be detached before cloning."""
Make sure that the volume is not in use when trying to copy it.
"""
if (volume['status'] != "available" or if (volume['status'] != "available" or
volume['attach_status'] == fields.VolumeAttachStatus.ATTACHED): volume['attach_status'] == fields.VolumeAttachStatus.ATTACHED):
LOG.error("Volume must be detached for clone operation.") LOG.error("Volume must be detached for clone operation.")
@ -196,7 +215,7 @@ class DotHillCommon(object):
try: try:
self.client.copy_volume(orig_name, dest_name, self.client.copy_volume(orig_name, dest_name,
self.backend_name, self.backend_type) self.backend_name, self.backend_type)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Cloning of volume %s failed.", LOG.exception("Cloning of volume %s failed.",
src_vref['id']) src_vref['id'])
raise exception.Invalid(ex) raise exception.Invalid(ex)
@ -219,7 +238,7 @@ class DotHillCommon(object):
try: try:
self.client.copy_volume(orig_name, dest_name, self.client.copy_volume(orig_name, dest_name,
self.backend_name, self.backend_type) self.backend_name, self.backend_type)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Create volume failed from snapshot: %s", LOG.exception("Create volume failed from snapshot: %s",
snapshot['id']) snapshot['id'])
raise exception.Invalid(ex) raise exception.Invalid(ex)
@ -239,7 +258,7 @@ class DotHillCommon(object):
self.client_login() self.client_login()
try: try:
self.client.delete_volume(volume_name) self.client.delete_volume(volume_name)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
# if the volume wasn't found, ignore the error # if the volume wasn't found, ignore the error
if 'The volume was not found on this system.' in ex.args: if 'The volume was not found on this system.' in ex.args:
return return
@ -278,7 +297,7 @@ class DotHillCommon(object):
self.backend_name, self.backend_name,
self.owner)) self.owner))
pool['pool_name'] = self.backend_name pool['pool_name'] = self.backend_name
except dh_exception.DotHillRequestError: except stx_exception.RequestError:
err = (_("Unable to get stats for backend_name: %s") % err = (_("Unable to get stats for backend_name: %s") %
self.backend_name) self.backend_name)
LOG.exception(err) LOG.exception(err)
@ -304,7 +323,7 @@ class DotHillCommon(object):
connector, connector,
connector_element) connector_element)
return data return data
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error mapping volume: %s", volume_name) LOG.exception("Error mapping volume: %s", volume_name)
raise exception.Invalid(ex) raise exception.Invalid(ex)
@ -320,7 +339,7 @@ class DotHillCommon(object):
self.client.unmap_volume(volume_name, self.client.unmap_volume(volume_name,
connector, connector,
connector_element) connector_element)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error unmapping volume: %s", volume_name) LOG.exception("Error unmapping volume: %s", volume_name)
raise exception.Invalid(ex) raise exception.Invalid(ex)
finally: finally:
@ -329,21 +348,21 @@ class DotHillCommon(object):
def get_active_fc_target_ports(self): def get_active_fc_target_ports(self):
try: try:
return self.client.get_active_fc_target_ports() return self.client.get_active_fc_target_ports()
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error getting active FC target ports.") LOG.exception("Error getting active FC target ports.")
raise exception.Invalid(ex) raise exception.Invalid(ex)
def get_active_iscsi_target_iqns(self): def get_active_iscsi_target_iqns(self):
try: try:
return self.client.get_active_iscsi_target_iqns() return self.client.get_active_iscsi_target_iqns()
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error getting active ISCSI target iqns.") LOG.exception("Error getting active ISCSI target iqns.")
raise exception.Invalid(ex) raise exception.Invalid(ex)
def get_active_iscsi_target_portals(self): def get_active_iscsi_target_portals(self):
try: try:
return self.client.get_active_iscsi_target_portals() return self.client.get_active_iscsi_target_portals()
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error getting active ISCSI target portals.") LOG.exception("Error getting active ISCSI target portals.")
raise exception.Invalid(ex) raise exception.Invalid(ex)
@ -360,7 +379,7 @@ class DotHillCommon(object):
self.client_login() self.client_login()
try: try:
self.client.create_snapshot(vol_name, snap_name) self.client.create_snapshot(vol_name, snap_name)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Creation of snapshot failed for volume: %s", LOG.exception("Creation of snapshot failed for volume: %s",
snapshot['volume_id']) snapshot['volume_id'])
raise exception.Invalid(ex) raise exception.Invalid(ex)
@ -374,7 +393,7 @@ class DotHillCommon(object):
self.client_login() self.client_login()
try: try:
self.client.delete_snapshot(snap_name, self.backend_type) self.client.delete_snapshot(snap_name, self.backend_type)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
# if the volume wasn't found, ignore the error # if the volume wasn't found, ignore the error
if 'The volume was not found on this system.' in ex.args: if 'The volume was not found on this system.' in ex.args:
return return
@ -401,7 +420,7 @@ class DotHillCommon(object):
self.client_login() self.client_login()
try: try:
self.client.extend_volume(volume_name, "%dGiB" % growth_size) self.client.extend_volume(volume_name, "%dGiB" % growth_size)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Extension of volume %s failed.", volume['id']) LOG.exception("Extension of volume %s failed.", volume['id'])
raise exception.Invalid(ex) raise exception.Invalid(ex)
finally: finally:
@ -410,14 +429,14 @@ class DotHillCommon(object):
def get_chap_record(self, initiator_name): def get_chap_record(self, initiator_name):
try: try:
return self.client.get_chap_record(initiator_name) return self.client.get_chap_record(initiator_name)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error getting chap record.") LOG.exception("Error getting chap record.")
raise exception.Invalid(ex) raise exception.Invalid(ex)
def create_chap_record(self, initiator_name, chap_secret): def create_chap_record(self, initiator_name, chap_secret):
try: try:
self.client.create_chap_record(initiator_name, chap_secret) self.client.create_chap_record(initiator_name, chap_secret)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error creating chap record.") LOG.exception("Error creating chap record.")
raise exception.Invalid(ex) raise exception.Invalid(ex)
@ -444,7 +463,8 @@ class DotHillCommon(object):
except ValueError: except ValueError:
return false_ret return false_ret
if not (dest_type == 'DotHillVolumeDriver' and reqd_dest_type = '%sVolumeDriver' % self.vendor_name
if not (dest_type == reqd_dest_type and
dest_id == self.serialNumber and dest_id == self.serialNumber and
dest_owner == self.owner): dest_owner == self.owner):
return false_ret return false_ret
@ -452,7 +472,7 @@ class DotHillCommon(object):
source_name = self._get_vol_name(volume['name_id']) source_name = self._get_vol_name(volume['name_id'])
else: else:
source_name = self._get_vol_name(volume['id']) source_name = self._get_vol_name(volume['id'])
# DotHill Array does not support duplicate names # the array does not support duplicate names
dest_name = "m%s" % source_name[1:] dest_name = "m%s" % source_name[1:]
self.client_login() self.client_login()
@ -462,7 +482,7 @@ class DotHillCommon(object):
self.client.delete_volume(source_name) self.client.delete_volume(source_name)
self.client.modify_volume_name(dest_name, source_name) self.client.modify_volume_name(dest_name, source_name)
return (True, None) return (True, None)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error migrating volume: %s", source_name) LOG.exception("Error migrating volume: %s", source_name)
raise exception.Invalid(ex) raise exception.Invalid(ex)
finally: finally:
@ -473,10 +493,10 @@ class DotHillCommon(object):
return ret[0] return ret[0]
def manage_existing(self, volume, existing_ref): def manage_existing(self, volume, existing_ref):
"""Manage an existing non-openstack DotHill volume """Manage an existing non-openstack array volume
existing_ref is a dictionary of the form: existing_ref is a dictionary of the form:
{'source-name': <name of the existing DotHill volume>} {'source-name': <name of the existing volume>}
""" """
target_vol_name = existing_ref['source-name'] target_vol_name = existing_ref['source-name']
modify_target_vol_name = self._get_vol_name(volume['id']) modify_target_vol_name = self._get_vol_name(volume['id'])
@ -485,7 +505,7 @@ class DotHillCommon(object):
try: try:
self.client.modify_volume_name(target_vol_name, self.client.modify_volume_name(target_vol_name,
modify_target_vol_name) modify_target_vol_name)
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error manage existing volume.") LOG.exception("Error manage existing volume.")
raise exception.Invalid(ex) raise exception.Invalid(ex)
finally: finally:
@ -503,7 +523,7 @@ class DotHillCommon(object):
try: try:
size = self.client.get_volume_size(target_vol_name) size = self.client.get_volume_size(target_vol_name)
return size return size
except dh_exception.DotHillRequestError as ex: except stx_exception.RequestError as ex:
LOG.exception("Error manage existing get volume size.") LOG.exception("Error manage existing get volume size.")
raise exception.Invalid(ex) raise exception.Invalid(ex)
finally: finally:

View File

@ -15,29 +15,25 @@ from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
class DotHillInvalidBackend(exception.VolumeDriverException): class InvalidBackend(exception.VolumeDriverException):
message = _("Backend doesn't exist (%(backend)s)") message = _("Backend doesn't exist (%(backend)s)")
class DotHillConnectionError(exception.VolumeDriverException): class ConnectionError(exception.VolumeDriverException):
message = "%(message)s" message = "%(message)s"
class DotHillAuthenticationError(exception.VolumeDriverException): class AuthenticationError(exception.VolumeDriverException):
message = "%(message)s" message = "%(message)s"
class DotHillNotEnoughSpace(exception.VolumeDriverException): class NotEnoughSpace(exception.VolumeDriverException):
message = _("Not enough space on backend (%(backend)s)") message = _("Not enough space on backend (%(backend)s)")
class DotHillRequestError(exception.VolumeDriverException): class RequestError(exception.VolumeDriverException):
message = "%(message)s" message = "%(message)s"
class DotHillNotTargetPortal(exception.VolumeDriverException): class NotTargetPortal(exception.VolumeDriverException):
message = _("No active iSCSI portals with supplied iSCSI IPs") message = _("No active iSCSI portals with supplied iSCSI IPs")
class DotHillDriverNotSupported(exception.VolumeDriverException):
message = _("The Dot Hill driver is no longer supported.")

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -16,17 +16,13 @@
# #
import cinder.volume.driver import cinder.volume.driver
from cinder.volume.drivers.dothill import dothill_common import cinder.volume.drivers.san.san as san
from cinder.volume.drivers.dothill import exception as dh_exception import cinder.volume.drivers.stx.common as common
from cinder.volume.drivers.san import san
from cinder.zonemanager import utils as fczm_utils from cinder.zonemanager import utils as fczm_utils
# As of Pike, the DotHill driver is no longer considered supported, class STXFCDriver(cinder.volume.driver.FibreChannelDriver):
# but the code remains as it is still subclassed by other drivers. """OpenStack Fibre Channel cinder drivers for Seagate arrays.
# The __init__() function prevents any direct instantiation.
class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver):
"""OpenStack Fibre Channel cinder drivers for DotHill Arrays.
.. code:: text .. code:: text
@ -44,20 +40,24 @@ class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver):
1.6 - Add management path redundancy and reduce load placed 1.6 - Add management path redundancy and reduce load placed
on management controller. on management controller.
1.7 - Modified so it can't be invoked except as a superclass 1.7 - Modified so it can't be invoked except as a superclass
2.0 - Reworked to create a new Seagate (STX) array driver.
""" """
VERSION = "2.0"
CI_WIKI_NAME = 'Seagate_CI'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Make sure we're not invoked directly super(STXFCDriver, self).__init__(*args, **kwargs)
if type(self) == DotHillFCDriver:
raise dh_exception.DotHillDriverNotSupported
super(DotHillFCDriver, self).__init__(*args, **kwargs)
self.common = None self.common = None
self.configuration.append_config_values(san.san_opts) self.configuration.append_config_values(san.san_opts)
self.lookup_service = fczm_utils.create_lookup_service() self.lookup_service = fczm_utils.create_lookup_service()
if type(self) != STXFCDriver:
return
self.configuration.append_config_values(common.common_opts)
def _init_common(self): def _init_common(self):
return dothill_common.DotHillCommon(self.configuration) return common.STXCommon(self.configuration)
def _check_flags(self): def _check_flags(self):
required_flags = ['san_ip', 'san_login', 'san_password'] required_flags = ['san_ip', 'san_login', 'san_password']
@ -105,12 +105,15 @@ class DotHillFCDriver(cinder.volume.driver.FibreChannelDriver):
def terminate_connection(self, volume, connector, **kwargs): def terminate_connection(self, volume, connector, **kwargs):
info = {'driver_volume_type': 'fibre_channel', 'data': {}} info = {'driver_volume_type': 'fibre_channel', 'data': {}}
try: try:
self.common.unmap_volume(volume, connector, 'wwpns')
if not self.common.client.list_luns_for_host( if not self.common.client.list_luns_for_host(
connector['wwpns'][0]): connector['wwpns'][0]):
ports, init_targ_map = self.get_init_targ_map(connector) ports, init_targ_map = self.get_init_targ_map(connector)
info['data'] = {'target_wwn': ports, info['data'] = {'target_wwn': ports,
'initiator_target_map': init_targ_map} 'initiator_target_map': init_targ_map}
# multiattach volumes cannot be unmapped here, but will
# be implicity unmapped when the volume is deleted.
if not volume.get('multiattach'):
self.common.unmap_volume(volume, connector, 'wwpns')
fczm_utils.remove_fc_zone(info) fczm_utils.remove_fc_zone(info)
finally: finally:
return info return info

View File

@ -1,6 +1,6 @@
# Copyright 2014 Objectif Libre # Copyright 2014 Objectif Libre
# Copyright 2015 Dot Hill Systems Corp. # Copyright 2015 Dot Hill Systems Corp.
# Copyright 2016 Seagate Technology or one of its affiliates # Copyright 2016-2019 Seagate Technology or one of its affiliates
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
@ -20,20 +20,17 @@ from oslo_log import log as logging
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
import cinder.volume.driver import cinder.volume.driver
from cinder.volume.drivers.dothill import dothill_common as dothillcommon import cinder.volume.drivers.san.san as san
from cinder.volume.drivers.dothill import exception as dh_exception import cinder.volume.drivers.stx.common as common
from cinder.volume.drivers.san import san import cinder.volume.drivers.stx.exception as stx_exception
DEFAULT_ISCSI_PORT = "3260" DEFAULT_ISCSI_PORT = "3260"
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# As of Pike, the DotHill driver is no longer considered supported, class STXISCSIDriver(cinder.volume.driver.ISCSIDriver):
# but the code remains as it is still subclassed by other drivers. """OpenStack iSCSI Cinder driver for Seagate storage arrays.
# The __init__() function prevents any direct instantiation.
class DotHillISCSIDriver(cinder.volume.driver.ISCSIDriver):
"""OpenStack iSCSI cinder drivers for DotHill Arrays.
.. code:: text .. code:: text
@ -53,20 +50,25 @@ class DotHillISCSIDriver(cinder.volume.driver.ISCSIDriver):
1.6 - Add management path redundancy and reduce load placed 1.6 - Add management path redundancy and reduce load placed
on management controller. on management controller.
1.7 - Modified so it can't be invoked except as a superclass 1.7 - Modified so it can't be invoked except as a superclass
2.0 - Reworked to create a new Seagate (STX) array driver.
""" """
VERSION = "2.0"
CI_WIKI_NAME = 'Seagate_CI'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Make sure we're not invoked directly super(STXISCSIDriver, self).__init__(*args, **kwargs)
if type(self) == DotHillISCSIDriver:
raise dh_exception.DotHillDriverNotSupported
super(DotHillISCSIDriver, self).__init__(*args, **kwargs)
self.common = None self.common = None
self.configuration.append_config_values(san.san_opts) self.configuration.append_config_values(san.san_opts)
self.iscsi_ips = None if type(self) != STXISCSIDriver:
return
self.configuration.append_config_values(common.common_opts)
self.configuration.append_config_values(common.iscsi_opts)
self.iscsi_ips = self.configuration.seagate_iscsi_ips
def _init_common(self): def _init_common(self):
return dothillcommon.DotHillCommon(self.configuration) return common.STXCommon(self.configuration)
def _check_flags(self): def _check_flags(self):
required_flags = ['san_ip', 'san_login', 'san_password'] required_flags = ['san_ip', 'san_login', 'san_password']
@ -130,7 +132,7 @@ class DotHillISCSIDriver(cinder.volume.driver.ISCSIDriver):
break break
if 'target_portal' not in data: if 'target_portal' not in data:
raise dh_exception.DotHillNotTargetPortal() raise stx_exception.NotTargetPortal()
if self.configuration.use_chap_auth: if self.configuration.use_chap_auth:
chap_secret = self.common.get_chap_record( chap_secret = self.common.get_chap_record(
@ -152,7 +154,10 @@ class DotHillISCSIDriver(cinder.volume.driver.ISCSIDriver):
def terminate_connection(self, volume, connector, **kwargs): def terminate_connection(self, volume, connector, **kwargs):
if type(connector) == dict and 'initiator' in connector: if type(connector) == dict and 'initiator' in connector:
self.common.unmap_volume(volume, connector, 'initiator') # multiattach volumes cannot be unmapped here, but will
# be implicity unmapped when the volume is deleted.
if not volume.get('multiattach'):
self.common.unmap_volume(volume, connector, 'initiator')
def get_volume_stats(self, refresh=False): def get_volume_stats(self, refresh=False):
stats = self.common.get_volume_stats(refresh) stats = self.common.get_volume_stats(refresh)

View File

@ -38,9 +38,10 @@ Supported operations
Configuring the array Configuring the array
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
#. Verify that the array can be managed via an HTTPS connection. HTTP can also #. Verify that the array can be managed using an HTTPS connection. HTTP
be used if ``hpmsa_api_protocol=http`` is placed into the appropriate can also be used if ``hpmsa_api_protocol=http`` is placed into the
sections of the ``cinder.conf`` file. appropriate sections of the ``cinder.conf`` file, but this option is
deprecated and will be removed in a future release.
Confirm that virtual pools A and B are present if you plan to use virtual Confirm that virtual pools A and B are present if you plan to use virtual
pools for OpenStack storage. pools for OpenStack storage.
@ -50,12 +51,12 @@ Configuring the array
creating or setting aside one disk group for each of the A and B creating or setting aside one disk group for each of the A and B
controllers. controllers.
#. Edit the ``cinder.conf`` file to define a storage back end entry for each #. Edit the ``cinder.conf`` file to define a storage back-end entry for each
storage pool on the array that will be managed by OpenStack. Each entry storage pool on the array that will be managed by OpenStack. Each entry
consists of a unique section name, surrounded by square brackets, followed consists of a unique section name, surrounded by square brackets, followed
by options specified in a ``key=value`` format. by options specified in ``key=value`` format.
* The ``hpmsa_backend_name`` value specifies the name of the storage pool * The ``hpmsa_pool_name`` value specifies the name of the storage pool
or vdisk on the array. or vdisk on the array.
* The ``volume_backend_name`` option value can be a unique value, if you * The ``volume_backend_name`` option value can be a unique value, if you
@ -64,92 +65,101 @@ Configuring the array
volume scheduler choose where new volumes are allocated. volume scheduler choose where new volumes are allocated.
* The rest of the options will be repeated for each storage pool in a * The rest of the options will be repeated for each storage pool in a
given array: ``volume_driver`` specifies the Cinder driver name; given array:
``san_ip`` specifies the IP addresses or host names of the array's
management controllers; ``san_login`` and ``san_password`` specify * ``volume_driver`` specifies the Cinder driver name.
the username and password of an array user account with ``manage`` * ``san_ip`` specifies the IP addresses or host names of the array's
privileges; and ``hpmsa_iscsi_ips`` specfies the iSCSI IP addresses management controllers.
for the array if using the iSCSI transport protocol. * ``san_login`` and ``san_password`` specify the username and password
of an array user account with ``manage`` privileges.
* ``driver_use_ssl`` should be set to ``true`` to enable use of the
HTTPS protocol.
* ``hpmsa_iscsi_ips`` specfies the iSCSI IP addresses for the array
if using the iSCSI transport protocol.
In the examples below, two back ends are defined, one for pool A and one for In the examples below, two back ends are defined, one for pool A and one for
pool B, and a common ``volume_backend_name`` is used so that a single pool B, and a common ``volume_backend_name`` is used so that a single
volume type definition can be used to allocate volumes from both pools. volume type definition can be used to allocate volumes from both pools.
**iSCSI example back-end entries** **Example: iSCSI example back-end entries**
.. code-block:: ini .. code-block:: ini
[pool-a] [pool-a]
hpmsa_backend_name = A hpmsa_pool_name = A
volume_backend_name = hpmsa-array volume_backend_name = hpmsa-array
volume_driver = cinder.volume.drivers.san.hp.hpmsa_iscsi.HPMSAISCSIDriver volume_driver = cinder.volume.drivers.san.hp.hpmsa_iscsi.HPMSAISCSIDriver
san_ip = 10.1.2.3,10.1.2.4 san_ip = 10.1.2.3,10.1.2.4
san_login = manage san_login = manage
san_password = !manage san_password = !manage
hpmsa_iscsi_ips = 10.2.3.4,10.2.3.5 hpmsa_iscsi_ips = 10.2.3.4,10.2.3.5
driver_use_ssl = true
[pool-b] [pool-b]
hpmsa_backend_name = B hpmsa_pool_name = B
volume_backend_name = hpmsa-array volume_backend_name = hpmsa-array
volume_driver = cinder.volume.drivers.san.hp.hpmsa_iscsi.HPMSAISCSIDriver volume_driver = cinder.volume.drivers.san.hp.hpmsa_iscsi.HPMSAISCSIDriver
san_ip = 10.1.2.3,10.1.2.4 san_ip = 10.1.2.3,10.1.2.4
san_login = manage san_login = manage
san_password = !manage san_password = !manage
hpmsa_iscsi_ips = 10.2.3.4,10.2.3.5 hpmsa_iscsi_ips = 10.2.3.4,10.2.3.5
driver_use_ssl = true
**Fibre Channel example back-end entries** **Example: Fibre Channel example back-end entries**
.. code-block:: ini .. code-block:: ini
[pool-a] [pool-a]
hpmsa_backend_name = A hpmsa_pool_name = A
volume_backend_name = hpmsa-array volume_backend_name = hpmsa-array
volume_driver = cinder.volume.drivers.san.hp.hpmsa_fc.HPMSAFCDriver volume_driver = cinder.volume.drivers.san.hp.hpmsa_fc.HPMSAFCDriver
san_ip = 10.1.2.3,10.1.2.4 san_ip = 10.1.2.3,10.1.2.4
san_login = manage san_login = manage
san_password = !manage san_password = !manage
driver_use_ssl = true
[pool-b] [pool-b]
hpmsa_backend_name = B hpmsa_pool_name = B
volume_backend_name = hpmsa-array volume_backend_name = hpmsa-array
volume_driver = cinder.volume.drivers.san.hp.hpmsa_fc.HPMSAFCDriver volume_driver = cinder.volume.drivers.san.hp.hpmsa_fc.HPMSAFCDriver
san_ip = 10.1.2.3,10.1.2.4 san_ip = 10.1.2.3,10.1.2.4
san_login = manage san_login = manage
san_password = !manage san_password = !manage
driver_use_ssl = true
#. If any ``volume_backend_name`` value refers to a vdisk rather than a #. If any ``volume_backend_name`` value refers to a vdisk rather than a
virtual pool, add an additional statement ``hpmsa_backend_type = linear`` virtual pool, add an additional statement ``hpmsa_pool_type = linear``
to that back end entry. to that back end entry.
#. If HTTPS is not enabled in the array, include ``hpmsa_api_protocol = http`` #. If HTTPS is not enabled in the array, include ``hpmsa_api_protocol = http``
in each of the back-end definitions. in each of the back-end definitions.
#. If HTTPS is enabled, you can enable certificate verification with the option #. If HTTPS is enabled, you can enable certificate verification with the
``hpmsa_verify_certificate=True``. You may also use the option ``driver_ssl_cert_verify = True``. You may also use the
``hpmsa_verify_certificate_path`` parameter to specify the path to a ``driver_ssl_cert_path`` option to specify the path to a
CA\_BUNDLE file containing CAs other than those in the default list. CA_BUNDLE file containing CAs other than those in the default list.
#. Modify the ``[DEFAULT]`` section of the ``cinder.conf`` file to add an #. Modify the ``[DEFAULT]`` section of the ``cinder.conf`` file to add an
``enabled_back-ends`` parameter specifying the backend entries you added, ``enabled_backends`` parameter specifying the back-end entries you added,
and a ``default_volume_type`` parameter specifying the name of a volume type and a ``default_volume_type`` parameter specifying the name of a volume type
that you will create in the next step. that you will create in the next step.
**Example of [DEFAULT] section changes** **Example: [DEFAULT] section changes**
.. code-block:: ini .. code-block:: ini
[DEFAULT] [DEFAULT]
# ...
enabled_backends = pool-a,pool-b enabled_backends = pool-a,pool-b
default_volume_type = hpmsa default_volume_type = hpmsa
#. Create a new volume type for each distinct ``volume_backend_name`` value #. Create a new volume type for each distinct ``volume_backend_name`` value
that you added in the ``cinder.conf`` file. The example below assumes that that you added to the ``cinder.conf`` file. The example below assumes that
the same ``volume_backend_name=hpmsa-array`` option was specified in all the same ``volume_backend_name=hpmsa-array`` option was specified in all
of the entries, and specifies that the volume type ``hpmsa`` can be used to of the entries, and specifies that the volume type ``hpmsa`` can be used to
allocate volumes from any of them. allocate volumes from any of them.
**Example of creating a volume type** **Example: Creating a volume type**
.. code-block:: console .. code-block:: console

View File

@ -44,9 +44,10 @@ Supported operations
Configuring the array Configuring the array
~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
#. Verify that the array can be managed using an HTTPS connection. HTTP can #. Verify that the array can be managed using an HTTPS connection. HTTP
also be used if ``lenovo_api_protocol=http`` is placed into the can also be used if ``hpmsa_api_protocol=http`` is placed into the
appropriate sections of the ``cinder.conf`` file. appropriate sections of the ``cinder.conf`` file, but this option is
deprecated and will be removed in a future release.
Confirm that virtual pools A and B are present if you plan to use Confirm that virtual pools A and B are present if you plan to use
virtual pools for OpenStack storage. virtual pools for OpenStack storage.
@ -56,21 +57,26 @@ Configuring the array
entry consists of a unique section name, surrounded by square brackets, entry consists of a unique section name, surrounded by square brackets,
followed by options specified in ``key=value`` format. followed by options specified in ``key=value`` format.
- The ``lenovo_backend_name`` value specifies the name of the storage - The ``lenovo_pool_name`` value specifies the name of the storage
pool on the array. pool on the array.
- The ``volume_backend_name`` option value can be a unique value, if - The ``volume_backend_name`` option value can be a unique value, if
you wish to be able to assign volumes to a specific storage pool on you wish to be able to assign volumes to a specific storage pool on
the array, or a name that's shared among multiple storage pools to the array, or a name that is shared among multiple storage pools to
let the volume scheduler choose where new volumes are allocated. let the volume scheduler choose where new volumes are allocated.
- The rest of the options will be repeated for each storage pool in a - The rest of the options will be repeated for each storage pool in a
given array: ``volume_driver`` specifies the Cinder driver name; given array:
``san_ip`` specifies the IP addresses or host names of the array's
management controllers; ``san_login`` and ``san_password`` specify * ``volume_driver`` specifies the Cinder driver name.
the username and password of an array user account with ``manage`` * ``san_ip`` specifies the IP addresses or host names of the array's
privileges; and ``lenovo_iscsi_ips`` specfies the iSCSI IP management controllers.
addresses for the array if using the iSCSI transport protocol. * ``san_login`` and ``san_password`` specify the username and password
of an array user account with ``manage`` privileges.
* ``driver_use_ssl`` should be set to ``true`` to enable use of the
HTTPS protocol.
* ``lenovo_iscsi_ips`` specfies the iSCSI IP addresses for the array
if using the iSCSI transport protocol.
In the examples below, two back ends are defined, one for pool A and one In the examples below, two back ends are defined, one for pool A and one
for pool B, and a common ``volume_backend_name`` is used so that a for pool B, and a common ``volume_backend_name`` is used so that a
@ -82,49 +88,53 @@ Configuring the array
.. code-block:: ini .. code-block:: ini
[pool-a] [pool-a]
lenovo_backend_name = A lenovo_pool_name = A
volume_backend_name = lenovo-array volume_backend_name = lenovo-array
volume_driver = cinder.volume.drivers.lenovo.lenovo_iscsi.LenovoISCSIDriver volume_driver = cinder.volume.drivers.lenovo.lenovo_iscsi.LenovoISCSIDriver
san_ip = 10.1.2.3 san_ip = 10.1.2.3
san_login = manage san_login = manage
san_password = !manage san_password = !manage
lenovo_iscsi_ips = 10.2.3.4,10.2.3.5 lenovo_iscsi_ips = 10.2.3.4,10.2.3.5
driver_use_ssl = true
[pool-b] [pool-b]
lenovo_backend_name = B lenovo_pool_name = B
volume_backend_name = lenovo-array volume_backend_name = lenovo-array
volume_driver = cinder.volume.drivers.lenovo.lenovo_iscsi.LenovoISCSIDriver volume_driver = cinder.volume.drivers.lenovo.lenovo_iscsi.LenovoISCSIDriver
san_ip = 10.1.2.3 san_ip = 10.1.2.3
san_login = manage san_login = manage
san_password = !manage san_password = !manage
lenovo_iscsi_ips = 10.2.3.4,10.2.3.5 lenovo_iscsi_ips = 10.2.3.4,10.2.3.5
driver_use_ssl = true
**Example: Fibre Channel example back-end entries** **Example: Fibre Channel example back-end entries**
.. code-block:: ini .. code-block:: ini
[pool-a] [pool-a]
lenovo_backend_name = A lenovo_pool_name = A
volume_backend_name = lenovo-array volume_backend_name = lenovo-array
volume_driver = cinder.volume.drivers.lenovo.lenovo_fc.LenovoFCDriver volume_driver = cinder.volume.drivers.lenovo.lenovo_fc.LenovoFCDriver
san_ip = 10.1.2.3 san_ip = 10.1.2.3
san_login = manage san_login = manage
san_password = !manage san_password = !manage
driver_use_ssl = true
[pool-b] [pool-b]
lenovo_backend_name = B lenovo_pool_name = B
volume_backend_name = lenovo-array volume_backend_name = lenovo-array
volume_driver = cinder.volume.drivers.lenovo.lenovo_fc.LenovoFCDriver volume_driver = cinder.volume.drivers.lenovo.lenovo_fc.LenovoFCDriver
san_ip = 10.1.2.3 san_ip = 10.1.2.3
san_login = manage san_login = manage
san_password = !manage san_password = !manage
driver_use_ssl = true
#. If HTTPS is not enabled in the array, include #. If HTTPS is not enabled in the array, add
``lenovo_api_protocol = http`` in each of the back-end definitions. ``lenovo_api_protocol = http`` in each of the back-end definitions.
#. If HTTPS is enabled, you can enable certificate verification with the #. If HTTPS is enabled, you can enable certificate verification with the
option ``lenovo_verify_certificate=True``. You may also use the option ``driver_ssl_cert_verify = True``. You may also use the
``lenovo_verify_certificate_path`` parameter to specify the path to a ``driver_ssl_cert_path`` option to specify the path to a
CA_BUNDLE file containing CAs other than those in the default list. CA_BUNDLE file containing CAs other than those in the default list.
#. Modify the ``[DEFAULT]`` section of the ``cinder.conf`` file to add an #. Modify the ``[DEFAULT]`` section of the ``cinder.conf`` file to add an

View File

@ -0,0 +1,181 @@
=============================================
Seagate Array Fibre Channel and iSCSI drivers
=============================================
The ``STXFCDriver`` and ``STXISCSIDriver`` Cinder drivers allow the
Seagate Technology (STX) storage arrays to be used for Block Storage in
OpenStack deployments.
System requirements
~~~~~~~~~~~~~~~~~~~
To use the Seagate drivers, the following are required:
- Seagate storage array with:
- iSCSI or FC host interfaces
- G28x firmware or later
- Network connectivity between the OpenStack host and the array management
interfaces
- The HTTPS or HTTP protocol must be enabled on the array
Supported operations
~~~~~~~~~~~~~~~~~~~~
- Create, delete, attach, and detach volumes.
- Create, list, and delete volume snapshots.
- Create a volume from a snapshot.
- Copy an image to a volume.
- Copy a volume to an image.
- Clone a volume.
- Extend a volume.
- Migrate a volume with back-end assistance.
- Retype a volume.
- Manage and unmanage a volume.
Configuring the array
~~~~~~~~~~~~~~~~~~~~~
#. Verify that the array can be managed via an HTTPS connection. HTTP can also
be used if ``driver_use_ssl`` is set to (or defaults to) False
in the ``cinder.conf`` file.
Confirm that virtual pools A and B are present if you plan to use virtual
pools for OpenStack storage.
If you plan to use vdisks instead of virtual pools, create or identify one
or more vdisks to be used for OpenStack storage; typically this will mean
creating or setting aside one disk group for each of the A and B
controllers.
#. Edit the ``cinder.conf`` file to define a storage back-end entry for each
storage pool on the array that will be managed by OpenStack. Each entry
consists of a unique section name, surrounded by square brackets, followed
by options specified in a ``key=value`` format.
* The ``seagate_pool_name`` value specifies the name of the storage pool
or vdisk on the array.
* The ``volume_backend_name`` option value can be a unique value, if you
wish to be able to assign volumes to a specific storage pool on the
array, or a name that is shared among multiple storage pools to let the
volume scheduler choose where new volumes are allocated.
#. The following ``cinder.conf`` options generally have identical values
for each backend section on the array:
* ``volume_driver`` specifies the Cinder driver name.
* ``san_ip`` specifies the IP addresses or host names of the array's
management controllers.
* ``san_login`` and ``san_password`` specify the username and password
of an array user account with ``manage`` privileges
* ``driver_use_ssl`` must be set to True to enable use of the HTTPS
protocol.
* ``seagate_iscsi_ips`` specfies the iSCSI IP addresses
for the array if using the iSCSI transport protocol
In the examples below, two back ends are defined, one for pool A and one for
pool B, and a common ``volume_backend_name`` is used so that a single
volume type definition can be used to allocate volumes from both pools.
**iSCSI example back-end entries**
.. code-block:: ini
[pool-a]
seagate_pool_name = A
volume_backend_name = seagate-array
volume_driver = cinder.volume.drivers.stx.iscsi.STXISCSIDriver
san_ip = 10.1.2.3,10.1.2.4
san_login = manage
san_password = !manage
seagate_iscsi_ips = 10.2.3.4,10.2.3.5
driver_use_ssl = true
[pool-b]
seagate_backend_name = B
volume_backend_name = seagate-array
volume_driver = cinder.volume.drivers.stx.iscsi.STXISCSIDriver
san_ip = 10.1.2.3,10.1.2.4
san_login = manage
san_password = !manage
seagate_iscsi_ips = 10.2.3.4,10.2.3.5
driver_use_ssl = true
**Fibre Channel example back-end entries**
.. code-block:: ini
[pool-a]
seagate_backend_name = A
volume_backend_name = seagate-array
volume_driver = cinder.volume.drivers.stx.fc.STXFCDriver
san_ip = 10.1.2.3,10.1.2.4
san_login = manage
san_password = !manage
driver_use_ssl = true
[pool-b]
seagate_backend_name = B
volume_backend_name = seagate-array
volume_driver = cinder.volume.drivers.stx.fc.STXFCDriver
san_ip = 10.1.2.3,10.1.2.4
san_login = manage
san_password = !manage
driver_use_ssl = true
#. If any ``volume_backend_name`` value refers to a vdisk rather than a
virtual pool, add an additional statement ``seagate_backend_type = linear``
to that back-end entry.
#. If HTTPS is enabled, you can enable certificate verification with the option
``driver_ssl_cert_verify = True``. You may also use the
``driver_ssl_cert_path`` parameter to specify the path to a
CA\_BUNDLE file containing CAs other than those in the default list.
#. Modify the ``[DEFAULT]`` section of the ``cinder.conf`` file to add an
``enabled_backends`` parameter specifying the backend entries you added,
and a ``default_volume_type`` parameter specifying the name of a volume type
that you will create in the next step.
**Example of [DEFAULT] section changes**
.. code-block:: ini
[DEFAULT]
enabled_backends = pool-a,pool-b
default_volume_type = seagate
#. Create a new volume type for each distinct ``volume_backend_name`` value
that you added in the ``cinder.conf`` file. The example below assumes that
the same ``volume_backend_name=seagate-array`` option was specified in all
of the entries, and specifies that the volume type ``seagate`` can be used
to allocate volumes from any of them.
**Example of creating a volume type**
.. code-block:: console
$ openstack volume type create seagate
$ openstack volume type set --property volume_backend_name=seagate-array seagate
#. After modifying the ``cinder.conf`` file, restart the ``cinder-volume``
service.
Driver-specific options
~~~~~~~~~~~~~~~~~~~~~~~
The following table contains the configuration options that are specific to
the Seagate drivers.
.. config-table::
:config-target: Seagate
cinder.volume.drivers.stx.common

View File

@ -162,6 +162,9 @@ title=Quobyte Storage Driver (quobyte)
[driver.rbd] [driver.rbd]
title=RBD (Ceph) Storage Driver (RBD) title=RBD (Ceph) Storage Driver (RBD)
[driver.seagate]
title=Seagate Driver (iSCSI, FC)
[driver.sheepdog] [driver.sheepdog]
title=Sheepdog Storage Driver (sheepdog) title=Sheepdog Storage Driver (sheepdog)
@ -251,6 +254,7 @@ driver.pure=complete
driver.qnap=complete driver.qnap=complete
driver.quobyte=complete driver.quobyte=complete
driver.rbd=complete driver.rbd=complete
driver.seagate=complete
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=complete driver.synology=complete
@ -316,6 +320,7 @@ driver.pure=complete
driver.qnap=complete driver.qnap=complete
driver.quobyte=complete driver.quobyte=complete
driver.rbd=complete driver.rbd=complete
driver.seagate=complete
driver.sheepdog=complete driver.sheepdog=complete
driver.storpool=complete driver.storpool=complete
driver.synology=complete driver.synology=complete
@ -381,6 +386,7 @@ driver.pure=missing
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=missing driver.rbd=missing
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
@ -449,6 +455,7 @@ driver.pure=missing
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=missing driver.rbd=missing
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
@ -516,6 +523,7 @@ driver.pure=complete
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=complete driver.rbd=complete
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=complete driver.storpool=complete
driver.synology=missing driver.synology=missing
@ -584,6 +592,7 @@ driver.pure=complete
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=missing driver.rbd=missing
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
@ -651,6 +660,7 @@ driver.pure=complete
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=complete driver.rbd=complete
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
@ -719,6 +729,7 @@ driver.pure=missing
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=missing driver.rbd=missing
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=complete driver.storpool=complete
driver.synology=missing driver.synology=missing
@ -787,6 +798,7 @@ driver.pure=complete
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=complete driver.rbd=complete
driver.seagate=complete
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
@ -852,6 +864,7 @@ driver.pure=missing
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=missing driver.rbd=missing
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing
@ -921,6 +934,7 @@ driver.pure=missing
driver.qnap=missing driver.qnap=missing
driver.quobyte=missing driver.quobyte=missing
driver.rbd=complete driver.rbd=complete
driver.seagate=missing
driver.sheepdog=missing driver.sheepdog=missing
driver.storpool=missing driver.storpool=missing
driver.synology=missing driver.synology=missing

View File

@ -0,0 +1,29 @@
---
upgrade:
- |
The HPE MSA driver options ``hpmsa_backend_name`` and ``hpmsa_backend_type``
options were deprecated in favor of ``hpmsa_pool_name`` and
``hpmsa_pool_type`` to avoid confusion, and the
``hpmsa_api_protocol``, ``hpmsa_verify_certificate``, and
``hpmsa_verify_certificate_path`` options were deprecated in favor of
the standard ``driver_use_ssl``, ``driver_ssl_cert_verify``, and
``driver_ssl_cert_path`` options. To retain the default behavior, add
``driver_use_ssl = true`` to back-end entries in ``cinder.conf``
before the deprecated options are removed in a future release.
deprecations:
- |
The HPE MSA driver options ``hpmsa_backend_name`` and ``hpmsa_backend_type``
options were deprecated in favor of ``hpmsa_pool_name`` and
``hpmsa_pool_type`` to avoid confusion, and the
``hpmsa_api_protocol``, ``hpmsa_verify_certificate``, and
``hpmsa_verify_certificate_path`` options were deprecated in favor of
the standard ``driver_use_ssl``, ``driver_ssl_cert_verify``, and
``driver_ssl_cert_path`` options. To retain the default behavior, add
``driver_use_ssl = true`` to back-end entries in ``cinder.conf``
before the deprecated options are removed in a future release.
fixes:
- |
Fixed HPE MSA driver issue where a multi-attached volume could be
unmapped while still in use.

View File

@ -0,0 +1,29 @@
---
upgrade:
- |
The Lenovo driver options ``lenovo_backend_name`` and ``lenovo_backend_type``
options were deprecated in favor of ``lenovo_pool_name`` and
``lenovo_pool_type`` to avoid confusion, and the
``lenovo_api_protocol``, ``lenovo_verify_certificate``, and
``lenovo_verify_certificate_path`` options were deprecated in favor of
the standard ``driver_use_ssl``, ``driver_ssl_cert_verify``, and
``driver_ssl_cert_path`` options. To retain the default behavior, add
``driver_use_ssl = true`` to back-end entries in ``cinder.conf``
before the deprecated options are removed in a future release.
deprecations:
- |
The Lenovo driver options ``lenovo_backend_name`` and ``lenovo_backend_type``
options were deprecated in favor of ``lenovo_pool_name`` and
``lenovo_pool_type`` to avoid confusion, and the
``lenovo_api_protocol``, ``lenovo_verify_certificate``, and
``lenovo_verify_certificate_path`` options were deprecated in favor of
the standard ``driver_use_ssl``, ``driver_ssl_cert_verify``, and
``driver_ssl_cert_path`` options. To retain the default behavior, add
``driver_use_ssl = true`` to back-end entries in ``cinder.conf``
before the deprecated options are removed in a future release.
fixes:
- |
Fixed Lenovo driver issue where a multi-attached volume could be
unmapped while still in use.

View File

@ -0,0 +1,3 @@
---
features:
- New Cinder driver for Seagate FC and iSCSI storage arrays.