Remove Violin volume drivers

These drivers were marked as deprecated and unsupported in Pike due to
lack of CI. This has not changed and they will now be removed.

Change-Id: I935990d09f1374a8789b2a0dad8e8a334aa39abc
This commit is contained in:
Sean McGinnis 2017-09-06 10:48:45 -05:00
parent 6d9b747787
commit 46ee7ebdeb
15 changed files with 1 additions and 4428 deletions

View File

@ -1198,31 +1198,6 @@ class XIODriverException(VolumeDriverException):
message = _("X-IO Volume Driver exception!") message = _("X-IO Volume Driver exception!")
# Violin Memory drivers
class ViolinInvalidBackendConfig(VolumeDriverException):
message = _("Volume backend config is invalid: %(reason)s")
class ViolinRequestRetryTimeout(VolumeDriverException):
message = _("Backend service retry timeout hit: %(timeout)s sec")
class ViolinBackendErr(VolumeBackendAPIException):
message = _("Backend reports: %(message)s")
class ViolinBackendErrExists(VolumeBackendAPIException):
message = _("Backend reports: item already exists")
class ViolinBackendErrNotFound(NotFound):
message = _("Backend reports: item not found")
class ViolinResourceNotFound(NotFound):
message = _("Backend reports: %(message)s")
class BadHTTPResponseStatus(VolumeDriverException): class BadHTTPResponseStatus(VolumeDriverException):
message = _("Bad HTTP response status %(status)s") message = _("Bad HTTP response status %(status)s")

View File

@ -169,8 +169,6 @@ from cinder.volume.drivers.synology import synology_common as \
cinder_volume_drivers_synology_synologycommon cinder_volume_drivers_synology_synologycommon
from cinder.volume.drivers import tegile as cinder_volume_drivers_tegile from cinder.volume.drivers import tegile as cinder_volume_drivers_tegile
from cinder.volume.drivers import tintri as cinder_volume_drivers_tintri from cinder.volume.drivers import tintri as cinder_volume_drivers_tintri
from cinder.volume.drivers.violin import v7000_common as \
cinder_volume_drivers_violin_v7000common
from cinder.volume.drivers.vmware import vmdk as \ from cinder.volume.drivers.vmware import vmdk as \
cinder_volume_drivers_vmware_vmdk cinder_volume_drivers_vmware_vmdk
from cinder.volume.drivers import vzstorage as cinder_volume_drivers_vzstorage from cinder.volume.drivers import vzstorage as cinder_volume_drivers_vzstorage
@ -368,7 +366,6 @@ def list_opts():
cinder_volume_drivers_synology_synologycommon.cinder_opts, cinder_volume_drivers_synology_synologycommon.cinder_opts,
cinder_volume_drivers_tegile.tegile_opts, cinder_volume_drivers_tegile.tegile_opts,
cinder_volume_drivers_tintri.tintri_opts, cinder_volume_drivers_tintri.tintri_opts,
cinder_volume_drivers_violin_v7000common.violin_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,
cinder_volume_drivers_windows_smbfs.volume_opts, cinder_volume_drivers_windows_smbfs.volume_opts,

View File

@ -1,366 +0,0 @@
# Copyright 2016 Violin Memory, Inc.
# All Rights Reserved.
#
# 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
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests for Violin Memory 7000 Series All-Flash Array ISCSI Driver
"""
import mock
from cinder import exception
from cinder import test
from cinder.tests.unit.volume.drivers.violin import \
fake_vmem_client as vmemclient
from cinder.volume import configuration as conf
from cinder.volume.drivers.violin import v7000_common
from cinder.volume.drivers.violin import v7000_iscsi
VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
VOLUME = {
"name": "volume-" + VOLUME_ID,
"id": VOLUME_ID,
"display_name": "fake_volume",
"size": 2,
"host": "myhost",
"volume_type": None,
"volume_type_id": None,
}
SNAPSHOT_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbb"
SNAPSHOT = {
"name": "snapshot-" + SNAPSHOT_ID,
"id": SNAPSHOT_ID,
"volume_id": VOLUME_ID,
"volume_name": "volume-" + VOLUME_ID,
"volume_size": 2,
"display_name": "fake_snapshot",
"volume": VOLUME,
}
SRC_VOL_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbc"
SRC_VOL = {
"name": "volume-" + SRC_VOL_ID,
"id": SRC_VOL_ID,
"display_name": "fake_src_vol",
"size": 2,
"host": "myhost",
"volume_type": None,
"volume_type_id": None,
}
SRC_VOL_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbc"
SRC_VOL = {
"name": "volume-" + SRC_VOL_ID,
"id": SRC_VOL_ID,
"display_name": "fake_src_vol",
"size": 2,
"host": "myhost",
"volume_type": None,
"volume_type_id": None,
}
INITIATOR_IQN = "iqn.1111-22.org.debian:11:222"
CONNECTOR = {
"initiator": INITIATOR_IQN,
"host": "irrelevant",
"ip": "1.2.3.4",
}
TARGET = "iqn.2004-02.com.vmem:%s" % VOLUME['id']
GET_VOLUME_STATS_RESPONSE = {
'vendor_name': 'Violin Memory, Inc.',
'reserved_percentage': 0,
'QoS_support': False,
'free_capacity_gb': 4094,
'total_capacity_gb': 2558,
}
CLIENT_INFO = {
'issanip_enabled': False,
'sanclient_id': 7,
'ISCSIDevices':
[{'category': 'Virtual Device',
'sizeMB': VOLUME['size'] * 1024,
'name': VOLUME['id'],
'object_id': 'v0000058',
'access': 'ReadWrite',
'ISCSITarget':
{'name': TARGET,
'startingLun': '0',
'ipAddr': '192.168.91.1 192.168.92.1 192.168.93.1 192.168.94.1',
'object_id': '2c68c1a4-67bb-59b3-93df-58bcdf422a66',
'access': 'ReadWrite',
'isInfiniBand': 'false',
'iscsiurl': ''},
'type': 'SAN',
'lun': '8',
'size': VOLUME['size'] * 1024 * 1024}],
'name': 'lab-srv3377',
'isiscsi_enabled': True,
'clusterName': '',
'ipAddress': '',
'isclustered': False,
'username': '',
'isbmr_enabled': False,
'useracl': None,
'isfibrechannel_enabled': False,
'iSCSIPolicy':
{'initiators': ['iqn.1993-08.org.debian:01:1ebcd244a059'],
'authentication':
{'mutualCHAP':
{'enabled': False,
'user': ''},
'enabled': False,
'defaultUser': ''},
'accessType': 'stationary'},
'ISCSITargetList':
[{'name': 'iqn.2004-02.com.vmem:lab-fsp-mga.openstack',
'startingLun': '0',
'ipAddr': '192.168.91.1 192.168.92.1 192.168.93.1 192.168.94.1',
'object_id': '716cc60a-576a-55f1-bfe3-af4a21ca5554',
'access': 'ReadWrite',
'isInfiniBand': 'false',
'iscsiurl': ''}],
'type': 'Windows',
'persistent_reservation': True,
'isxboot_enabled': False}
class V7000ISCSIDriverTestCase(test.TestCase):
"""Test cases for VMEM ISCSI driver."""
def setUp(self):
super(V7000ISCSIDriverTestCase, self).setUp()
self.conf = self.setup_configuration()
self.driver = v7000_iscsi.V7000ISCSIDriver(configuration=self.conf)
self.driver.gateway_iscsi_ip_addresses = [
'192.168.91.1', '192.168.92.1', '192.168.93.1', '192.168.94.1']
self.stats = {}
self.driver.set_initialized()
def setup_configuration(self):
config = mock.Mock(spec=conf.Configuration)
config.volume_backend_name = 'v7000_iscsi'
config.san_ip = '8.8.8.8'
config.san_login = 'admin'
config.san_password = ''
config.san_thin_provision = False
config.san_is_local = False
config.use_igroups = False
config.request_timeout = 300
return config
def setup_mock_concerto(self, m_conf=None):
"""Create a fake Concerto communication object."""
_m_concerto = mock.Mock(name='Concerto',
version='1.1.1',
spec=vmemclient.mock_client_conf)
if m_conf:
_m_concerto.configure_mock(**m_conf)
return _m_concerto
@mock.patch.object(v7000_common.V7000Common, 'check_for_setup_error')
def test_check_for_setup_error(self, m_setup_func):
"""No setup errors are found."""
result = self.driver.check_for_setup_error()
m_setup_func.assert_called_with()
self.assertIsNone(result)
def test_create_volume(self):
"""Volume created successfully."""
self.driver.common._create_lun = mock.Mock()
result = self.driver.create_volume(VOLUME)
self.driver.common._create_lun.assert_called_with(VOLUME)
self.assertIsNone(result)
def test_create_volume_from_snapshot(self):
self.driver.common._create_volume_from_snapshot = mock.Mock()
result = self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
self.driver.common._create_volume_from_snapshot.assert_called_with(
SNAPSHOT, VOLUME)
self.assertIsNone(result)
def test_create_cloned_volume(self):
self.driver.common._create_lun_from_lun = mock.Mock()
result = self.driver.create_cloned_volume(VOLUME, SRC_VOL)
self.driver.common._create_lun_from_lun.assert_called_with(
SRC_VOL, VOLUME)
self.assertIsNone(result)
def test_delete_volume(self):
"""Volume deleted successfully."""
self.driver.common._delete_lun = mock.Mock()
result = self.driver.delete_volume(VOLUME)
self.driver.common._delete_lun.assert_called_with(VOLUME)
self.assertIsNone(result)
def test_extend_volume(self):
"""Volume extended successfully."""
new_size = 10
self.driver.common._extend_lun = mock.Mock()
result = self.driver.extend_volume(VOLUME, new_size)
self.driver.common._extend_lun.assert_called_with(VOLUME, new_size)
self.assertIsNone(result)
def test_create_snapshot(self):
self.driver.common._create_lun_snapshot = mock.Mock()
result = self.driver.create_snapshot(SNAPSHOT)
self.driver.common._create_lun_snapshot.assert_called_with(SNAPSHOT)
self.assertIsNone(result)
def test_delete_snapshot(self):
self.driver.common._delete_lun_snapshot = mock.Mock()
result = self.driver.delete_snapshot(SNAPSHOT)
self.driver.common._delete_lun_snapshot.assert_called_with(SNAPSHOT)
self.assertIsNone(result)
def test_get_volume_stats(self):
self.driver._update_volume_stats = mock.Mock()
self.driver._update_volume_stats()
result = self.driver.get_volume_stats(True)
self.driver._update_volume_stats.assert_called_with()
self.assertEqual(self.driver.stats, result)
def test_update_volume_stats(self):
"""Mock query to the backend collects stats on all physical devices."""
backend_name = self.conf.volume_backend_name
self.driver.common._get_volume_stats = mock.Mock(
return_value=GET_VOLUME_STATS_RESPONSE,
)
result = self.driver._update_volume_stats()
self.driver.common._get_volume_stats.assert_called_with(
self.conf.san_ip)
self.assertEqual(backend_name,
self.driver.stats['volume_backend_name'])
self.assertEqual('iSCSI',
self.driver.stats['storage_protocol'])
self.assertIsNone(result)
def test_initialize_connection(self):
lun_id = 1
response = {'success': True, 'msg': 'None'}
conf = {
'client.create_client.return_value': response,
'client.create_iscsi_target.return_value': response,
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
self.driver._get_iqn = mock.Mock(return_value=TARGET)
self.driver._export_lun = mock.Mock(return_value=lun_id)
props = self.driver.initialize_connection(VOLUME, CONNECTOR)
self.driver._export_lun.assert_called_with(VOLUME, TARGET, CONNECTOR)
self.assertEqual("iscsi", props['driver_volume_type'])
self.assertFalse(props['data']['target_discovered'])
self.assertEqual(TARGET, props['data']['target_iqn'])
self.assertEqual(lun_id, props['data']['target_lun'])
self.assertEqual(VOLUME['id'], props['data']['volume_id'])
def test_terminate_connection(self):
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver._get_iqn = mock.Mock(return_value=TARGET)
self.driver._unexport_lun = mock.Mock()
result = self.driver.terminate_connection(VOLUME, CONNECTOR)
self.driver._unexport_lun.assert_called_with(VOLUME, TARGET, CONNECTOR)
self.assertIsNone(result)
def test_export_lun(self):
lun_id = '1'
response = {'success': True, 'msg': 'Assign device successfully'}
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver.common._send_cmd_and_verify = mock.Mock(
return_value=response)
self.driver._get_lun_id = mock.Mock(return_value=lun_id)
result = self.driver._export_lun(VOLUME, TARGET, CONNECTOR)
self.driver.common._send_cmd_and_verify.assert_called_with(
self.driver.common.vmem_mg.lun.assign_lun_to_iscsi_target,
self.driver._is_lun_id_ready,
'Assign device successfully',
[VOLUME['id'], TARGET],
[VOLUME['id'], CONNECTOR['host']])
self.driver._get_lun_id.assert_called_with(
VOLUME['id'], CONNECTOR['host'])
self.assertEqual(lun_id, result)
def test_export_lun_fails_with_exception(self):
lun_id = '1'
response = {'success': False, 'msg': 'Generic error'}
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver.common._send_cmd_and_verify = mock.Mock(
side_effect=exception.ViolinBackendErr(response['msg']))
self.driver._get_lun_id = mock.Mock(return_value=lun_id)
self.assertRaises(exception.ViolinBackendErr,
self.driver._export_lun,
VOLUME, TARGET, CONNECTOR)
def test_unexport_lun(self):
response = {'success': True, 'msg': 'Unassign device successfully'}
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver.common._send_cmd = mock.Mock(
return_value=response)
result = self.driver._unexport_lun(VOLUME, TARGET, CONNECTOR)
self.driver.common._send_cmd.assert_called_with(
self.driver.common.vmem_mg.lun.unassign_lun_from_iscsi_target,
"Unassign device successfully",
VOLUME['id'], TARGET, True)
self.assertIsNone(result)
def test_is_lun_id_ready(self):
lun_id = '1'
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver._get_lun_id = mock.Mock(return_value=lun_id)
result = self.driver._is_lun_id_ready(
VOLUME['id'], CONNECTOR['host'])
self.assertTrue(result)
def test_get_lun_id(self):
conf = {
'client.get_client_info.return_value': CLIENT_INFO,
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
result = self.driver._get_lun_id(VOLUME['id'], CONNECTOR['host'])
self.assertEqual(8, result)

View File

@ -1,67 +0,0 @@
# Copyright 2014 Violin Memory, Inc.
# All Rights Reserved.
#
# 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
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Fake VMEM REST client for testing drivers.
"""
import sys
import mock
# The following gymnastics to fake an exception class globally is done because
# we want to globally model and make available certain exceptions. If we do
# not do this, then the real-driver's import will not see our fakes.
class NoMatchingObjectIdError(Exception):
pass
error = mock.Mock()
error.NoMatchingObjectIdError = NoMatchingObjectIdError
core = mock.Mock()
core.attach_mock(error, 'error')
vmemclient = mock.Mock()
vmemclient.__version__ = "unknown"
vmemclient.attach_mock(core, 'core')
sys.modules['vmemclient'] = vmemclient
mock_client_conf = [
'basic',
'basic.login',
'basic.get_node_values',
'basic.save_config',
'lun',
'lun.export_lun',
'lun.unexport_lun',
'snapshot',
'snapshot.export_lun_snapshot',
'snapshot.unexport_lun_snapshot',
'iscsi',
'iscsi.bind_ip_to_target',
'iscsi.create_iscsi_target',
'iscsi.delete_iscsi_target',
'igroup',
'client',
'client.get_client_info',
'client.create_client',
'client.delete_client',
'adapter',
'adapter.get_fc_info',
'pool',
'utility',
]

File diff suppressed because it is too large Load Diff

View File

@ -1,574 +0,0 @@
# Copyright 2015 Violin Memory, Inc.
# All Rights Reserved.
#
# 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
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Tests for Violin Memory 7000 Series All-Flash Array Fibrechannel Driver
"""
import mock
from cinder import exception
from cinder import test
from cinder.tests.unit.volume.drivers.violin \
import fake_vmem_client as vmemclient
from cinder.volume import configuration as conf
from cinder.volume.drivers.violin import v7000_common
from cinder.volume.drivers.violin import v7000_fcp
VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
VOLUME = {
"name": "volume-" + VOLUME_ID,
"id": VOLUME_ID,
"display_name": "fake_volume",
"size": 2,
"host": "myhost",
"volume_type": None,
"volume_type_id": None,
}
SNAPSHOT_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbb"
SNAPSHOT = {
"name": "snapshot-" + SNAPSHOT_ID,
"id": SNAPSHOT_ID,
"volume_id": VOLUME_ID,
"volume_name": "volume-" + VOLUME_ID,
"volume_size": 2,
"display_name": "fake_snapshot",
"volume": VOLUME,
}
SRC_VOL_ID = "abcdabcd-1234-abcd-1234-abcdeffedcbc"
SRC_VOL = {
"name": "volume-" + SRC_VOL_ID,
"id": SRC_VOL_ID,
"display_name": "fake_src_vol",
"size": 2,
"host": "myhost",
"volume_type": None,
"volume_type_id": None,
}
INITIATOR_IQN = "iqn.1111-22.org.debian:11:222"
CONNECTOR = {
"initiator": INITIATOR_IQN,
"host": "irrelevant",
'wwpns': ['50014380186b3f65', '50014380186b3f67'],
}
FC_TARGET_WWPNS = [
'31000024ff45fb22', '21000024ff45fb23',
'51000024ff45f1be', '41000024ff45f1bf'
]
FC_INITIATOR_WWPNS = [
'50014380186b3f65', '50014380186b3f67'
]
FC_FABRIC_MAP = {
'fabricA':
{'target_port_wwn_list': [FC_TARGET_WWPNS[0], FC_TARGET_WWPNS[1]],
'initiator_port_wwn_list': [FC_INITIATOR_WWPNS[0]]},
'fabricB':
{'target_port_wwn_list': [FC_TARGET_WWPNS[2], FC_TARGET_WWPNS[3]],
'initiator_port_wwn_list': [FC_INITIATOR_WWPNS[1]]}
}
FC_INITIATOR_TARGET_MAP = {
FC_INITIATOR_WWPNS[0]: [FC_TARGET_WWPNS[0], FC_TARGET_WWPNS[1]],
FC_INITIATOR_WWPNS[1]: [FC_TARGET_WWPNS[2], FC_TARGET_WWPNS[3]]
}
PHY_DEVICES_RESPONSE = {
'data':
{'physical_devices':
[{'availsize': 1099504287744,
'availsize_mb': 524284,
'category': 'Virtual Device',
'connection_type': 'block',
'firmware': 'v1.0',
'guid': '3cc4d6dd-166d-77d2-4967-00005463f597',
'inquiry_string': '000002122b000032BKSC OTHDISK-MFCN01 v1.0',
'is_foreign': True,
'name': 'BKSC:OTHDISK-MFCN01.000',
'object_id': '84b834fb-1f4d-5d3b-b7ae-5796f9868151',
'owner': 'example.com',
'pool': None,
'product': 'OTHDISK-MFCN01',
'scsi_address':
{'adapter': '98',
'channel': '0',
'id': '0',
'lun': '0',
'object_id': '6e0106fc-9c1c-52a2-95c9-396b7a653ac1'},
'size': 1099504287744,
'size_mb': 1048569,
'type': 'Direct-Access',
'usedsize': 0,
'usedsize_mb': 0,
'vendor': 'BKSC',
'wwid': 'BKSC OTHDISK-MFCN01 v1.0-0-0-00'},
{'availsize': 1099504287744,
'availsize_mb': 524284,
'category': 'Virtual Device',
'connection_type': 'block',
'firmware': 'v1.0',
'guid': '283b2694-192b-4745-6768-00005463f673',
'inquiry_string': '000002122b000032BKSC OTHDISK-MFCN08 v1.0',
'is_foreign': False,
'name': 'BKSC:OTHDISK-MFCN08.000',
'object_id': '8555b888-bf43-5083-a433-f0c7b0282370',
'owner': 'example.com',
'pool':
{'name': 'mga-pool',
'object_id': '0818d3de-4437-535f-9cac-cc100a2c9313'},
'product': 'OTHDISK-MFCN08',
'scsi_address':
{'adapter': '98',
'channel': '0',
'id': '11',
'lun': '0',
'object_id': '6e0106fc-9c1c-52a2-95c9-396b7a653ac1'},
'size': 1099504287744,
'size_mb': 1048569,
'type': 'Direct-Access',
'usedsize': 0,
'usedsize_mb': 0,
'vendor': 'BKSC',
'wwid': 'BKSC OTHDISK-MFCN08 v1.0-0-0-00'},
{'availsize': 1099504287744,
'availsize_mb': 1048569,
'category': 'Virtual Device',
'connection_type': 'block',
'firmware': 'v1.0',
'guid': '7f47db19-019c-707d-0df1-00005463f949',
'inquiry_string': '000002122b000032BKSC OTHDISK-MFCN09 v1.0',
'is_foreign': False,
'name': 'BKSC:OTHDISK-MFCN09.000',
'object_id': '62a98898-f8b8-5837-af2b-764f5a72e291',
'owner': 'a.b.c.d',
'pool':
{'name': 'mga-pool',
'object_id': '0818d3de-4437-535f-9cac-cc100a2c9313'},
'product': 'OTHDISK-MFCN09',
'scsi_address':
{'adapter': '98',
'channel': '0',
'id': '12',
'lun': '0',
'object_id': '6e0106fc-9c1c-52a2-95c9-396b7a653ac1'},
'size': 1099504287744,
'size_mb': 524284,
'type': 'Direct-Access',
'usedsize': 0,
'usedsize_mb': 0,
'vendor': 'BKSC',
'wwid': 'BKSC OTHDISK-MFCN09 v1.0-0-0-00'}],
'total_physical_devices': 3},
'msg': 'Successful',
'success': True
}
# The FC_INFO dict returned by the backend is keyed on
# object_id of the FC adapter and the values are the
# wwmns
FC_INFO = {
'1a3cdb6a-383d-5ba6-a50b-4ba598074510': ['2100001b9745e25e'],
'4a6bc10a-5547-5cc0-94f2-76222a8f8dff': ['2100001b9745e230'],
'b21bfff5-d89e-51ff-9920-d990a061d722': ['2100001b9745e25f'],
'b508cc6b-f78a-51f9-81cf-47c1aaf53dd1': ['2100001b9745e231']
}
CLIENT_INFO = {
'FCPolicy':
{'AS400enabled': False,
'VSAenabled': False,
'initiatorWWPNList': ['50-01-43-80-18-6b-3f-66',
'50-01-43-80-18-6b-3f-64']},
'FibreChannelDevices':
[{'access': 'ReadWrite',
'id': 'v0000004',
'initiatorWWPN': '*',
'lun': '8',
'name': 'abcdabcd-1234-abcd-1234-abcdeffedcba',
'sizeMB': 10240,
'targetWWPN': '*',
'type': 'SAN'}]
}
CLIENT_INFO1 = {
'FCPolicy':
{'AS400enabled': False,
'VSAenabled': False,
'initiatorWWPNList': ['50-01-43-80-18-6b-3f-66',
'50-01-43-80-18-6b-3f-64']},
'FibreChannelDevices': []
}
class V7000FCPDriverTestCase(test.TestCase):
"""Test cases for VMEM FCP driver."""
def setUp(self):
super(V7000FCPDriverTestCase, self).setUp()
self.conf = self.setup_configuration()
self.driver = v7000_fcp.V7000FCPDriver(configuration=self.conf)
self.driver.common.container = 'myContainer'
self.driver.device_id = 'ata-VIOLIN_MEMORY_ARRAY_23109R00000022'
self.driver.gateway_fc_wwns = FC_TARGET_WWPNS
self.stats = {}
self.driver.set_initialized()
def setup_configuration(self):
config = mock.Mock(spec=conf.Configuration)
config.volume_backend_name = 'v7000_fcp'
config.san_ip = '8.8.8.8'
config.san_login = 'admin'
config.san_password = ''
config.san_thin_provision = False
config.san_is_local = False
config.request_timeout = 300
config.container = 'myContainer'
return config
def setup_mock_concerto(self, m_conf=None):
"""Create a fake Concerto communication object."""
_m_concerto = mock.Mock(name='Concerto',
version='1.1.1',
spec=vmemclient.mock_client_conf)
if m_conf:
_m_concerto.configure_mock(**m_conf)
return _m_concerto
@mock.patch.object(v7000_common.V7000Common, 'check_for_setup_error')
def test_check_for_setup_error(self, m_setup_func):
"""No setup errors are found."""
result = self.driver.check_for_setup_error()
m_setup_func.assert_called_with()
self.assertIsNone(result)
@mock.patch.object(v7000_common.V7000Common, 'check_for_setup_error')
def test_check_for_setup_error_no_wwn_config(self, m_setup_func):
"""No wwns were found during setup."""
self.driver.gateway_fc_wwns = []
failure = exception.ViolinInvalidBackendConfig
self.assertRaises(failure, self.driver.check_for_setup_error)
def test_create_volume(self):
"""Volume created successfully."""
self.driver.common._create_lun = mock.Mock()
result = self.driver.create_volume(VOLUME)
self.driver.common._create_lun.assert_called_with(VOLUME)
self.assertIsNone(result)
def test_create_volume_from_snapshot(self):
self.driver.common._create_volume_from_snapshot = mock.Mock()
result = self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
self.driver.common._create_volume_from_snapshot.assert_called_with(
SNAPSHOT, VOLUME)
self.assertIsNone(result)
def test_create_cloned_volume(self):
self.driver.common._create_lun_from_lun = mock.Mock()
result = self.driver.create_cloned_volume(VOLUME, SRC_VOL)
self.driver.common._create_lun_from_lun.assert_called_with(
SRC_VOL, VOLUME)
self.assertIsNone(result)
def test_delete_volume(self):
"""Volume deleted successfully."""
self.driver.common._delete_lun = mock.Mock()
result = self.driver.delete_volume(VOLUME)
self.driver.common._delete_lun.assert_called_with(VOLUME)
self.assertIsNone(result)
def test_extend_volume(self):
"""Volume extended successfully."""
new_size = 10
self.driver.common._extend_lun = mock.Mock()
result = self.driver.extend_volume(VOLUME, new_size)
self.driver.common._extend_lun.assert_called_with(VOLUME, new_size)
self.assertIsNone(result)
def test_create_snapshot(self):
self.driver.common._create_lun_snapshot = mock.Mock()
result = self.driver.create_snapshot(SNAPSHOT)
self.driver.common._create_lun_snapshot.assert_called_with(SNAPSHOT)
self.assertIsNone(result)
def test_delete_snapshot(self):
self.driver.common._delete_lun_snapshot = mock.Mock()
result = self.driver.delete_snapshot(SNAPSHOT)
self.driver.common._delete_lun_snapshot.assert_called_with(SNAPSHOT)
self.assertIsNone(result)
def test_get_volume_stats(self):
self.driver._update_volume_stats = mock.Mock()
self.driver._update_volume_stats()
result = self.driver.get_volume_stats(True)
self.driver._update_volume_stats.assert_called_with()
self.assertEqual(self.driver.stats, result)
@mock.patch('socket.gethostbyaddr')
def test_update_volume_stats(self, mock_gethost):
"""Test Update Volume Stats.
Makes a mock query to the backend to collect stats on all physical
devices.
"""
def gethostbyaddr(addr):
if addr == '8.8.8.8' or addr == 'example.com':
return ('example.com', [], ['8.8.8.8'])
else:
return ('a.b.c.d', [], addr)
mock_gethost.side_effect = gethostbyaddr
backend_name = self.conf.volume_backend_name
vendor_name = "Violin Memory, Inc."
tot_gb = 2046
free_gb = 1022
phy_devices = "/batch/physicalresource/physicaldevice"
conf = {
'basic.get.side_effect': [PHY_DEVICES_RESPONSE, ],
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
result = self.driver._update_volume_stats()
calls = [mock.call(phy_devices)]
self.driver.common.vmem_mg.basic.get.assert_has_calls(calls)
self.assertEqual(tot_gb, self.driver.stats['total_capacity_gb'])
self.assertEqual(free_gb, self.driver.stats['free_capacity_gb'])
self.assertEqual(backend_name,
self.driver.stats['volume_backend_name'])
self.assertEqual(vendor_name, self.driver.stats['vendor_name'])
self.assertIsNone(result)
def test_get_active_fc_targets(self):
"""Test Get Active FC Targets.
Makes a mock query to the backend to collect all the physical
adapters and extract the WWNs.
"""
conf = {
'adapter.get_fc_info.return_value': FC_INFO,
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
result = self.driver._get_active_fc_targets()
self.assertEqual({'2100001b9745e230', '2100001b9745e25f',
'2100001b9745e231', '2100001b9745e25e'},
set(result))
def test_initialize_connection(self):
lun_id = 1
target_wwns = self.driver.gateway_fc_wwns
init_targ_map = {}
conf = {
'client.create_client.return_value': None,
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
self.driver._export_lun = mock.Mock(return_value=lun_id)
self.driver._build_initiator_target_map = mock.Mock(
return_value=(target_wwns, init_targ_map))
props = self.driver.initialize_connection(VOLUME, CONNECTOR)
self.driver.common.vmem_mg.client.create_client.assert_called_with(
name=CONNECTOR['host'], proto='FC', fc_wwns=CONNECTOR['wwpns'])
self.driver._export_lun.assert_called_with(VOLUME, CONNECTOR)
self.driver._build_initiator_target_map.assert_called_with(
CONNECTOR)
self.assertEqual("fibre_channel", props['driver_volume_type'])
self.assertTrue(props['data']['target_discovered'])
self.assertEqual(self.driver.gateway_fc_wwns,
props['data']['target_wwn'])
self.assertEqual(lun_id, props['data']['target_lun'])
def test_terminate_connection(self):
target_wwns = self.driver.gateway_fc_wwns
init_targ_map = {}
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver._unexport_lun = mock.Mock()
self.driver._is_initiator_connected_to_array = mock.Mock(
return_value=False)
self.driver._build_initiator_target_map = mock.Mock(
return_value=(target_wwns, init_targ_map))
props = self.driver.terminate_connection(VOLUME, CONNECTOR)
self.driver._unexport_lun.assert_called_with(VOLUME, CONNECTOR)
self.driver._is_initiator_connected_to_array.assert_called_with(
CONNECTOR)
self.driver._build_initiator_target_map.assert_called_with(
CONNECTOR)
self.assertEqual("fibre_channel", props['driver_volume_type'])
self.assertEqual(target_wwns, props['data']['target_wwn'])
self.assertEqual(init_targ_map, props['data']['initiator_target_map'])
def test_export_lun(self):
lun_id = '1'
response = {'success': True, 'msg': 'Assign SAN client successfully'}
conf = {
'client.get_client_info.return_value': CLIENT_INFO,
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
self.driver.common._send_cmd_and_verify = mock.Mock(
return_value=response)
self.driver._get_lun_id = mock.Mock(return_value=lun_id)
result = self.driver._export_lun(VOLUME, CONNECTOR)
self.driver.common._send_cmd_and_verify.assert_called_with(
self.driver.common.vmem_mg.lun.assign_lun_to_client,
self.driver._is_lun_id_ready,
'Assign SAN client successfully',
[VOLUME['id'], CONNECTOR['host'], "ReadWrite"],
[VOLUME['id'], CONNECTOR['host']])
self.driver._get_lun_id.assert_called_with(
VOLUME['id'], CONNECTOR['host'])
self.assertEqual(lun_id, result)
def test_export_lun_fails_with_exception(self):
lun_id = '1'
response = {'status': False, 'msg': 'Generic error'}
failure = exception.ViolinBackendErr
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver.common._send_cmd_and_verify = mock.Mock(
side_effect=exception.ViolinBackendErr(response['msg']))
self.driver._get_lun_id = mock.Mock(return_value=lun_id)
self.assertRaises(failure, self.driver._export_lun, VOLUME, CONNECTOR)
def test_unexport_lun(self):
response = {'success': True, 'msg': 'Unassign SAN client successfully'}
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver.common._send_cmd = mock.Mock(
return_value=response)
result = self.driver._unexport_lun(VOLUME, CONNECTOR)
self.driver.common._send_cmd.assert_called_with(
self.driver.common.vmem_mg.lun.unassign_client_lun,
"Unassign SAN client successfully",
VOLUME['id'], CONNECTOR['host'], True)
self.assertIsNone(result)
def test_get_lun_id(self):
conf = {
'client.get_client_info.return_value': CLIENT_INFO,
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
result = self.driver._get_lun_id(VOLUME['id'], CONNECTOR['host'])
self.assertEqual(8, result)
def test_is_lun_id_ready(self):
lun_id = '1'
self.driver.common.vmem_mg = self.setup_mock_concerto()
self.driver._get_lun_id = mock.Mock(return_value=lun_id)
result = self.driver._is_lun_id_ready(
VOLUME['id'], CONNECTOR['host'])
self.assertTrue(result)
def test_build_initiator_target_map(self):
"""Successfully build a map when zoning is enabled."""
expected_targ_wwns = FC_TARGET_WWPNS
self.driver.lookup_service = mock.Mock()
(self.driver.lookup_service.get_device_mapping_from_network.
return_value) = FC_FABRIC_MAP
result = self.driver._build_initiator_target_map(CONNECTOR)
(targ_wwns, init_targ_map) = result
(self.driver.lookup_service.get_device_mapping_from_network.
assert_called_with(CONNECTOR['wwpns'], self.driver.gateway_fc_wwns))
self.assertEqual(set(expected_targ_wwns), set(targ_wwns))
i = FC_INITIATOR_WWPNS[0]
self.assertIn(FC_TARGET_WWPNS[0], init_targ_map[i])
self.assertIn(FC_TARGET_WWPNS[1], init_targ_map[i])
self.assertEqual(2, len(init_targ_map[i]))
i = FC_INITIATOR_WWPNS[1]
self.assertIn(FC_TARGET_WWPNS[2], init_targ_map[i])
self.assertIn(FC_TARGET_WWPNS[3], init_targ_map[i])
self.assertEqual(2, len(init_targ_map[i]))
self.assertEqual(2, len(init_targ_map))
def test_build_initiator_target_map_no_lookup_service(self):
"""Successfully build a map when zoning is disabled."""
expected_targ_wwns = FC_TARGET_WWPNS
expected_init_targ_map = {
CONNECTOR['wwpns'][0]: FC_TARGET_WWPNS,
CONNECTOR['wwpns'][1]: FC_TARGET_WWPNS
}
self.driver.lookup_service = None
targ_wwns, init_targ_map = self.driver._build_initiator_target_map(
CONNECTOR)
self.assertEqual(expected_targ_wwns, targ_wwns)
self.assertEqual(expected_init_targ_map, init_targ_map)
def test_is_initiator_connected_to_array(self):
"""Successfully finds an initiator with remaining active session."""
conf = {
'client.get_client_info.return_value': CLIENT_INFO,
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
self.assertTrue(self.driver._is_initiator_connected_to_array(
CONNECTOR))
self.driver.common.vmem_mg.client.get_client_info.assert_called_with(
CONNECTOR['host'])
def test_is_initiator_connected_to_array_empty_response(self):
"""Successfully finds no initiators with remaining active sessions."""
conf = {
'client.get_client_info.return_value': CLIENT_INFO1
}
self.driver.common.vmem_mg = self.setup_mock_concerto(m_conf=conf)
self.assertFalse(self.driver._is_initiator_connected_to_array(
CONNECTOR))

File diff suppressed because it is too large Load Diff

View File

@ -1,395 +0,0 @@
# Copyright 2015 Violin Memory, Inc.
# All Rights Reserved.
#
# 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
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Violin 7000 Series All-Flash Array Volume Driver
Provides fibre channel specific LUN services for V7000 series flash
arrays.
This driver requires Concerto v7.0.0 or newer software on the array.
You will need to install the Violin Memory REST client library:
sudo pip install vmemclient
Set the following in the cinder.conf file to enable the VMEM V7000
Fibre Channel Driver along with the required flags:
volume_driver=cinder.volume.drivers.violin.v7000_fcp.V7000FCDriver
NOTE: this driver file requires the use of synchronization points for
certain types of backend operations, and as a result may not work
properly in an active-active HA configuration. See OpenStack Cinder
driver documentation for more information.
"""
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder import utils
from cinder.volume import driver
from cinder.volume.drivers.san import san
from cinder.volume.drivers.violin import v7000_common
from cinder.zonemanager import utils as fczm_utils
import socket
LOG = logging.getLogger(__name__)
@interface.volumedriver
class V7000FCPDriver(driver.FibreChannelDriver):
"""Executes commands relating to fibre channel based Violin Memory arrays.
Version history:
1.0 - Initial driver
"""
VERSION = '1.0'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Violin_Memory_CI"
# TODO(smcginnis) Either remove this if CI requirements are met, or
# remove this driver in the Queens release per normal deprecation
SUPPORTED = False
def __init__(self, *args, **kwargs):
super(V7000FCPDriver, self).__init__(*args, **kwargs)
self.gateway_fc_wwns = []
self.stats = {}
self.configuration.append_config_values(v7000_common.violin_opts)
self.configuration.append_config_values(san.san_opts)
self.common = v7000_common.V7000Common(self.configuration)
self.lookup_service = fczm_utils.create_lookup_service()
LOG.info("Initialized driver %(name)s version: %(vers)s",
{'name': self.__class__.__name__, 'vers': self.VERSION})
def do_setup(self, context):
"""Any initialization the driver does while starting."""
super(V7000FCPDriver, self).do_setup(context)
self.common.do_setup(context)
self.gateway_fc_wwns = self._get_active_fc_targets()
# Register the client with the storage array
fc_version = self.VERSION + "-FCP"
self.common.vmem_mg.utility.set_managed_by_openstack_version(
fc_version)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self.common.check_for_setup_error()
if len(self.gateway_fc_wwns) == 0:
raise exception.ViolinInvalidBackendConfig(
reason=_('No FCP targets found'))
def create_volume(self, volume):
"""Creates a volume."""
self.common._create_lun(volume)
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
self.common._create_volume_from_snapshot(snapshot, volume)
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume."""
self.common._create_lun_from_lun(src_vref, volume)
def delete_volume(self, volume):
"""Deletes a volume."""
self.common._delete_lun(volume)
def extend_volume(self, volume, new_size):
"""Extend an existing volume's size."""
self.common._extend_lun(volume, new_size)
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
self.common._create_lun_snapshot(snapshot)
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
self.common._delete_lun_snapshot(snapshot)
def ensure_export(self, context, volume):
"""Synchronously checks and re-exports volumes at cinder start time."""
pass
def create_export(self, context, volume, connector):
"""Exports the volume."""
pass
def remove_export(self, context, volume):
"""Removes an export for a logical volume."""
pass
@fczm_utils.add_fc_zone
def initialize_connection(self, volume, connector):
"""Allow connection to connector and return connection info."""
LOG.debug("Initialize_connection: initiator - %(initiator)s host - "
"%(host)s wwpns - %(wwpns)s",
{'initiator': connector['initiator'],
'host': connector['host'],
'wwpns': connector['wwpns']})
self.common.vmem_mg.client.create_client(
name=connector['host'], proto='FC', fc_wwns=connector['wwpns'])
lun_id = self._export_lun(volume, connector)
target_wwns, init_targ_map = self._build_initiator_target_map(
connector)
properties = {}
properties['target_discovered'] = True
properties['target_wwn'] = target_wwns
properties['target_lun'] = lun_id
properties['initiator_target_map'] = init_targ_map
LOG.debug("Return FC data for zone addition: %(properties)s.",
{'properties': properties})
return {'driver_volume_type': 'fibre_channel', 'data': properties}
@fczm_utils.remove_fc_zone
def terminate_connection(self, volume, connector, **kwargs):
"""Terminates the connection (target<-->initiator)."""
self._unexport_lun(volume, connector)
properties = {}
if not self._is_initiator_connected_to_array(connector):
target_wwns, init_targ_map = self._build_initiator_target_map(
connector)
properties['target_wwn'] = target_wwns
properties['initiator_target_map'] = init_targ_map
LOG.debug("Return FC data for zone deletion: %(properties)s.",
{'properties': properties})
return {'driver_volume_type': 'fibre_channel', 'data': properties}
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If 'refresh' is True, update the stats first.
"""
if refresh or not self.stats:
self._update_volume_stats()
return self.stats
@utils.synchronized('vmem-export')
def _export_lun(self, volume, connector=None):
"""Generates the export configuration for the given volume.
:param volume: volume object provided by the Manager
:param connector: connector object provided by the Manager
:returns: the LUN ID assigned by the backend
"""
lun_id = ''
v = self.common.vmem_mg
if not connector:
raise exception.ViolinInvalidBackendConfig(
reason=_('No initiators found, cannot proceed'))
LOG.debug("Exporting lun %(vol_id)s - initiator wwpns %(i_wwpns)s "
"- target wwpns %(t_wwpns)s.",
{'vol_id': volume['id'], 'i_wwpns': connector['wwpns'],
't_wwpns': self.gateway_fc_wwns})
try:
lun_id = self.common._send_cmd_and_verify(
v.lun.assign_lun_to_client,
self._is_lun_id_ready,
"Assign SAN client successfully",
[volume['id'], connector['host'],
"ReadWrite"],
[volume['id'], connector['host']])
except exception.ViolinBackendErr:
LOG.exception("Backend returned err for lun export.")
raise
except Exception:
raise exception.ViolinInvalidBackendConfig(
reason=_('LUN export failed!'))
lun_id = self._get_lun_id(volume['id'], connector['host'])
LOG.info("Exported lun %(vol_id)s on lun_id %(lun_id)s.",
{'vol_id': volume['id'], 'lun_id': lun_id})
return lun_id
@utils.synchronized('vmem-export')
def _unexport_lun(self, volume, connector=None):
"""Removes the export configuration for the given volume.
:param volume: volume object provided by the Manager
"""
v = self.common.vmem_mg
LOG.info("Unexporting lun %s.", volume['id'])
try:
self.common._send_cmd(v.lun.unassign_client_lun,
"Unassign SAN client successfully",
volume['id'], connector['host'], True)
except exception.ViolinBackendErr:
LOG.exception("Backend returned err for lun export.")
raise
except Exception:
LOG.exception("LUN unexport failed!")
raise
def _update_volume_stats(self):
"""Gathers array stats and converts them to GB values."""
data = {}
total_gb = 0
free_gb = 0
v = self.common.vmem_mg.basic
array_name_triple = socket.gethostbyaddr(self.configuration.san_ip)
array_name = array_name_triple[0]
phy_devices = v.get("/batch/physicalresource/physicaldevice")
all_devices = [x for x in phy_devices['data']['physical_devices']]
for x in all_devices:
if socket.getfqdn(x['owner']) == array_name:
total_gb += x['size_mb'] // 1024
free_gb += x['availsize_mb'] // 1024
backend_name = self.configuration.volume_backend_name
data['volume_backend_name'] = backend_name or self.__class__.__name__
data['vendor_name'] = 'Violin Memory, Inc.'
data['driver_version'] = self.VERSION
data['storage_protocol'] = 'fibre_channel'
data['reserved_percentage'] = 0
data['QoS_support'] = False
data['total_capacity_gb'] = total_gb
data['free_capacity_gb'] = free_gb
for i in data:
LOG.debug("stat update: %(name)s=%(data)s",
{'name': i, 'data': data[i]})
self.stats = data
def _get_active_fc_targets(self):
"""Get a list of gateway WWNs that can be used as FCP targets.
:param mg_conn: active XG connection to one of the gateways
:returns: list of WWNs in openstack format
"""
v = self.common.vmem_mg
active_gw_fcp_wwns = []
fc_info = v.adapter.get_fc_info()
for x in fc_info.values():
active_gw_fcp_wwns.append(x[0])
return active_gw_fcp_wwns
def _get_lun_id(self, volume_name, client_name):
"""Get the lun ID for an exported volume.
If the lun is successfully assigned (exported) to a client, the
client info has the lun_id.
:param volume_name: name of volume to query for lun ID
:param client_name: name of client associated with the volume
:returns: integer value of lun ID
"""
v = self.common.vmem_mg
lun_id = None
client_info = v.client.get_client_info(client_name)
for x in client_info['FibreChannelDevices']:
if volume_name == x['name']:
lun_id = x['lun']
break
if lun_id:
lun_id = int(lun_id)
return lun_id
def _is_lun_id_ready(self, volume_name, client_name):
"""Get the lun ID for an exported volume.
If the lun is successfully assigned (exported) to a client, the
client info has the lun_id.
:param volume_name: name of volume to query for lun ID
:param client_name: name of client associated with the volume
:returns: Returns True if lun is ready, False otherwise
"""
lun_id = -1
lun_id = self._get_lun_id(volume_name, client_name)
if lun_id is None:
return False
else:
return True
def _build_initiator_target_map(self, connector):
"""Build the target_wwns and the initiator target map."""
target_wwns = []
init_targ_map = {}
if self.lookup_service:
dev_map = self.lookup_service.get_device_mapping_from_network(
connector['wwpns'], self.gateway_fc_wwns)
for fabric_name in dev_map:
fabric = dev_map[fabric_name]
target_wwns += fabric['target_port_wwn_list']
for initiator in fabric['initiator_port_wwn_list']:
if initiator not in init_targ_map:
init_targ_map[initiator] = []
init_targ_map[initiator] += fabric['target_port_wwn_list']
init_targ_map[initiator] = list(
set(init_targ_map[initiator]))
target_wwns = list(set(target_wwns))
else:
initiator_wwns = connector['wwpns']
target_wwns = self.gateway_fc_wwns
for initiator in initiator_wwns:
init_targ_map[initiator] = target_wwns
return target_wwns, init_targ_map
def _is_initiator_connected_to_array(self, connector):
"""Check if any initiator wwns still have active sessions."""
v = self.common.vmem_mg
client = v.client.get_client_info(connector['host'])
if len(client['FibreChannelDevices']):
# each entry in the FibreChannelDevices array is a dict
# describing an active lun assignment
return True
return False

View File

@ -1,352 +0,0 @@
# Copyright 2016 Violin Memory, Inc.
# All Rights Reserved.
#
# 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
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Violin 7000 Series All-Flash Array iSCSI Volume Driver
Provides ISCSI specific LUN services for V7000 series flash arrays.
This driver requires Concerto v7.5.4 or newer software on the array.
You will need to install the python VMEM REST client:
sudo pip install vmemclient
Set the following in the cinder.conf file to enable the VMEM V7000
ISCSI Driver along with the required flags:
volume_driver=cinder.volume.drivers.violin.v7000_iscsi.V7000ISCSIDriver
"""
import random
import uuid
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.san import san
from cinder.volume.drivers.violin import v7000_common
LOG = logging.getLogger(__name__)
@interface.volumedriver
class V7000ISCSIDriver(driver.ISCSIDriver):
"""Executes commands relating to iscsi based Violin Memory arrays.
Version history:
1.0 - Initial driver
"""
VERSION = '1.0'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Violin_Memory_CI"
# TODO(smcginnis) Either remove this if CI requirements are met, or
# remove this driver in the Queens release per normal deprecation
SUPPORTED = False
def __init__(self, *args, **kwargs):
super(V7000ISCSIDriver, self).__init__(*args, **kwargs)
self.stats = {}
self.gateway_iscsi_ip_addresses = []
self.configuration.append_config_values(v7000_common.violin_opts)
self.configuration.append_config_values(san.san_opts)
self.common = v7000_common.V7000Common(self.configuration)
LOG.info("Initialized driver %(name)s version: %(vers)s",
{'name': self.__class__.__name__, 'vers': self.VERSION})
def do_setup(self, context):
"""Any initialization the driver does while starting."""
super(V7000ISCSIDriver, self).do_setup(context)
self.common.do_setup(context)
# Register the client with the storage array
iscsi_version = self.VERSION + "-ISCSI"
self.common.vmem_mg.utility.set_managed_by_openstack_version(
iscsi_version, protocol="iSCSI")
# Getting iscsi IPs from the array is incredibly expensive,
# so only do it once.
if not self.configuration.violin_iscsi_target_ips:
LOG.warning("iSCSI target ip addresses not configured.")
self.gateway_iscsi_ip_addresses = (
self.common.vmem_mg.utility.get_iscsi_interfaces())
else:
self.gateway_iscsi_ip_addresses = (
self.configuration.violin_iscsi_target_ips)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self.common.check_for_setup_error()
if len(self.gateway_iscsi_ip_addresses) == 0:
msg = _('No iSCSI IPs configured on SAN gateway')
raise exception.ViolinInvalidBackendConfig(reason=msg)
def create_volume(self, volume):
"""Creates a volume."""
self.common._create_lun(volume)
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
self.common._create_volume_from_snapshot(snapshot, volume)
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume."""
self.common._create_lun_from_lun(src_vref, volume)
def delete_volume(self, volume):
"""Deletes a volume."""
self.common._delete_lun(volume)
def extend_volume(self, volume, new_size):
"""Extend an existing volume's size."""
self.common._extend_lun(volume, new_size)
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
self.common._create_lun_snapshot(snapshot)
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
self.common._delete_lun_snapshot(snapshot)
def ensure_export(self, context, volume):
"""Synchronously checks and re-exports volumes at cinder start time."""
pass
def create_export(self, context, volume, connector):
"""Exports the volume."""
pass
def remove_export(self, context, volume):
"""Removes an export for a logical volume."""
pass
def initialize_connection(self, volume, connector):
"""Allow connection to connector and return connection info."""
resp = {}
LOG.debug("Initialize_connection: initiator - %(initiator)s host - "
"%(host)s ip - %(ip)s",
{'initiator': connector['initiator'],
'host': connector['host'],
'ip': connector['ip']})
iqn = self._get_iqn(connector)
# pick a random single target to give the connector since
# there is no multipathing support
tgt = self.gateway_iscsi_ip_addresses[random.randint(
0, len(self.gateway_iscsi_ip_addresses) - 1)]
resp = self.common.vmem_mg.client.create_client(
name=connector['host'], proto='iSCSI',
iscsi_iqns=connector['initiator'])
# raise if we failed for any reason other than a 'client
# already exists' error code
if not resp['success'] and 'Error: 0x900100cd' not in resp['msg']:
msg = _("Failed to create iscsi client")
raise exception.ViolinBackendErr(message=msg)
resp = self.common.vmem_mg.client.create_iscsi_target(
name=iqn, client_name=connector['host'],
ip=self.gateway_iscsi_ip_addresses, access_mode='ReadWrite')
# same here, raise for any failure other than a 'target
# already exists' error code
if not resp['success'] and 'Error: 0x09024309' not in resp['msg']:
msg = _("Failed to create iscsi target")
raise exception.ViolinBackendErr(message=msg)
lun_id = self._export_lun(volume, iqn, connector)
properties = {}
properties['target_discovered'] = False
properties['target_iqn'] = iqn
properties['target_portal'] = '%s:%s' % (tgt, '3260')
properties['target_lun'] = lun_id
properties['volume_id'] = volume['id']
LOG.debug("Return ISCSI data: %(properties)s.",
{'properties': properties})
return {'driver_volume_type': 'iscsi', 'data': properties}
def terminate_connection(self, volume, connector, **kwargs):
"""Terminates the connection (target<-->initiator)."""
iqn = self._get_iqn(connector)
self._unexport_lun(volume, iqn, connector)
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If 'refresh' is True, update the stats first.
"""
if refresh or not self.stats:
self._update_volume_stats()
return self.stats
def _update_volume_stats(self):
"""Gathers array stats and converts them to GB values."""
data = self.common._get_volume_stats(self.configuration.san_ip)
backend_name = self.configuration.volume_backend_name
data['volume_backend_name'] = backend_name or self.__class__.__name__
data['driver_version'] = self.VERSION
data['storage_protocol'] = 'iSCSI'
for i in data:
LOG.debug("stat update: %(name)s=%(data)s",
{'name': i, 'data': data[i]})
self.stats = data
def _export_lun(self, volume, target, connector):
"""Generates the export configuration for the given volume.
:param volume: volume object provided by the Manager
:param connector: connector object provided by the Manager
:returns: the LUN ID assigned by the backend
"""
lun_id = ''
v = self.common.vmem_mg
LOG.debug("Exporting lun %(vol_id)s - initiator iqns %(i_iqns)s "
"- target iqns %(t_iqns)s.",
{'vol_id': volume['id'], 'i_iqns': connector['initiator'],
't_iqns': self.gateway_iscsi_ip_addresses})
try:
lun_id = self.common._send_cmd_and_verify(
v.lun.assign_lun_to_iscsi_target,
self._is_lun_id_ready,
"Assign device successfully",
[volume['id'], target],
[volume['id'], connector['host']])
except exception.ViolinBackendErr:
LOG.exception("Backend returned error for lun export.")
raise
except Exception:
raise exception.ViolinInvalidBackendConfig(
reason=_('LUN export failed!'))
lun_id = self._get_lun_id(volume['id'], connector['host'])
LOG.info("Exported lun %(vol_id)s on lun_id %(lun_id)s.",
{'vol_id': volume['id'], 'lun_id': lun_id})
return lun_id
def _unexport_lun(self, volume, target, connector):
"""Removes the export configuration for the given volume.
The equivalent CLI command is "no lun export container
<container_name> name <lun_name>"
Arguments:
volume -- volume object provided by the Manager
"""
v = self.common.vmem_mg
LOG.info("Unexporting lun %(vol)s host is %(host)s.",
{'vol': volume['id'], 'host': connector['host']})
try:
self.common._send_cmd(v.lun.unassign_lun_from_iscsi_target,
"Unassign device successfully",
volume['id'], target, True)
except exception.ViolinBackendErrNotFound:
LOG.info("Lun %s already unexported, continuing...",
volume['id'])
except Exception:
LOG.exception("LUN unexport failed!")
msg = _("LUN unexport failed")
raise exception.ViolinBackendErr(message=msg)
def _is_lun_id_ready(self, volume_name, client_name):
"""Get the lun ID for an exported volume.
If the lun is successfully assigned (exported) to a client, the
client info has the lun_id.
Note: The structure returned for iscsi is different from the
one returned for FC. Therefore this function is here instead of
common.
Arguments:
volume_name -- name of volume to query for lun ID
client_name -- name of client associated with the volume
Returns:
lun_id -- Returns True or False
"""
lun_id = -1
lun_id = self._get_lun_id(volume_name, client_name)
if lun_id is None:
return False
else:
return True
def _get_lun_id(self, volume_name, client_name):
"""Get the lun ID for an exported volume.
If the lun is successfully assigned (exported) to a client, the
client info has the lun_id.
Note: The structure returned for iscsi is different from the
one returned for FC. Therefore this function is here instead of
common.
Arguments:
volume_name -- name of volume to query for lun ID
client_name -- name of client associated with the volume
Returns:
lun_id -- integer value of lun ID
"""
v = self.common.vmem_mg
lun_id = None
client_info = v.client.get_client_info(client_name)
for x in client_info['ISCSIDevices']:
if volume_name == x['name']:
lun_id = x['lun']
break
if lun_id:
lun_id = int(lun_id)
return lun_id
def _get_iqn(self, connector):
# The vmemclient connection properties list hostname field may
# change depending on failover cluster config. Use a UUID
# from the backend's IP address to avoid a potential naming
# issue.
host_uuid = uuid.uuid3(uuid.NAMESPACE_DNS, self.configuration.san_ip)
return "%s%s.%s" % (self.configuration.iscsi_target_prefix,
connector['host'], host_uuid)

View File

@ -1,107 +0,0 @@
===========================================
Violin Memory 7000 Series FSP volume driver
===========================================
The OpenStack V7000 driver package from Violin Memory adds Block Storage
service support for Violin 7300 Flash Storage Platforms (FSPs) and 7700 FSP
controllers.
The driver package release can be used with any OpenStack Liberty deployment
for all 7300 FSPs and 7700 FSP controllers running Concerto 7.5.3 and later
using Fibre Channel HBAs.
System requirements
~~~~~~~~~~~~~~~~~~~
To use the Violin driver, the following are required:
- Violin 7300/7700 series FSP with:
- Concerto OS version 7.5.3 or later
- Fibre channel host interfaces
- The Violin block storage driver: This driver implements the block storage API
calls. The driver is included with the OpenStack Liberty release.
- The vmemclient library: This is the Violin Array Communications library to
the Flash Storage Platform through a REST-like interface. The client can be
installed using the python 'pip' installer tool. Further information on
vmemclient can be found on `PyPI
<https://pypi.python.org/pypi/vmemclient/>`__.
.. code-block:: console
pip install vmemclient
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.
.. note::
Listed operations are supported for thick, thin, and dedup luns,
with the exception of cloning. Cloning operations are supported only
on thick luns.
Driver configuration
~~~~~~~~~~~~~~~~~~~~
Once the array is configured as per the installation guide, it is simply a
matter of editing the cinder configuration file to add or modify the
parameters. The driver currently only supports fibre channel configuration.
Fibre channel configuration
---------------------------
Set the following in your ``cinder.conf`` configuration file, replacing the
variables using the guide in the following section:
.. code-block:: ini
volume_driver = cinder.volume.drivers.violin.v7000_fcp.V7000FCPDriver
volume_backend_name = vmem_violinfsp
extra_capabilities = VMEM_CAPABILITIES
san_ip = VMEM_MGMT_IP
san_login = VMEM_USER_NAME
san_password = VMEM_PASSWORD
use_multipath_for_image_xfer = true
Configuration parameters
------------------------
Description of configuration value placeholders:
VMEM_CAPABILITIES
User defined capabilities, a JSON formatted string specifying key-value
pairs (string value). The ones particularly supported are
``dedup`` and ``thin``. Only these two capabilities are listed here in
``cinder.conf`` file, indicating this backend be selected for creating
luns which have a volume type associated with them that have ``dedup``
or ``thin`` extra_specs specified. For example, if the FSP is configured
to support dedup luns, set the associated driver capabilities
to: {"dedup":"True","thin":"True"}.
VMEM_MGMT_IP
External IP address or host name of the Violin 7300 Memory Gateway. This
can be an IP address or host name.
VMEM_USER_NAME
Log-in user name for the Violin 7300 Memory Gateway or 7700 FSP controller.
This user must have administrative rights on the array or controller.
VMEM_PASSWORD
Log-in user's password.

View File

@ -52,7 +52,6 @@ Volume drivers
drivers/solidfire-volume-driver.rst drivers/solidfire-volume-driver.rst
drivers/synology-dsm-driver.rst drivers/synology-dsm-driver.rst
drivers/tintri-volume-driver.rst drivers/tintri-volume-driver.rst
drivers/violin-v7000-driver.rst
drivers/vzstorage-driver.rst drivers/vzstorage-driver.rst
drivers/vmware-vmdk-driver.rst drivers/vmware-vmdk-driver.rst
drivers/windows-iscsi-volume-driver.rst drivers/windows-iscsi-volume-driver.rst

View File

@ -1,30 +0,0 @@
..
Warning: Do not edit this file. It is automatically generated from the
software project's code and your changes will be overwritten.
The tool to generate this file lives in openstack-doc-tools repository.
Please make any changes needed in the code, then run the
autogenerate-config-doc tool from the openstack-doc-tools repository, or
ask for help on the documentation mailing list, IRC channel or meeting.
.. _cinder-violin:
.. list-table:: Description of Violin volume driver configuration options
:header-rows: 1
:class: config-ref-table
* - Configuration option = Default value
- Description
* - **[DEFAULT]**
-
* - ``violin_dedup_capable_pools`` =
- (List) Storage pools capable of dedup and other luns.(Comma separated list)
* - ``violin_dedup_only_pools`` =
- (List) Storage pools to be used to setup dedup luns only.(Comma separated list)
* - ``violin_iscsi_target_ips`` =
- (List) Target iSCSI addresses to use.(Comma separated list)
* - ``violin_pool_allocation_method`` = ``random``
- (String) Method of choosing a storage pool for a lun.
* - ``violin_request_timeout`` = ``300``
- (Integer) Global backend request timeout, in seconds.

View File

@ -11,4 +11,5 @@ upgrade:
* Infortrend * Infortrend
* QNAP * QNAP
* Reduxio * Reduxio
* Violin