from cinder.volume.drivers import solidfire as cinder_volume_drivers_solidfire
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.violin import v6000_common as \
- cinder_volume_drivers_violin_v6000common
from cinder.volume.drivers.violin import v7000_common as \
cinder_volume_drivers_violin_v7000common
from cinder.volume.drivers.vmware import vmdk as \
cinder_volume_drivers_emc_emcvmaxcommon.emc_opts,
cinder_volume_drivers_remotefs.nas_opts,
cinder_volume_drivers_remotefs.volume_opts,
- cinder_volume_drivers_violin_v6000common.violin_opts,
cinder_volume_drivers_emc_xtremio.XTREMIO_OPTS,
[cinder_api_middleware_auth.use_forwarded_for_opt],
cinder_volume_drivers_hitachi_hbsdcommon.volume_opts,
+++ /dev/null
-# 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.
-
-"""
-Tests for Violin Memory 6000 Series All-Flash Array Common Driver
-"""
-
-import mock
-
-from cinder import exception
-from cinder import test
-from cinder.tests.unit import fake_vmem_client as vmemclient
-from cinder.volume import configuration as conf
-from cinder.volume.drivers.violin import v6000_common
-
-VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
-VOLUME = {
- "name": "volume-" + VOLUME_ID,
- "id": VOLUME_ID,
- "display_name": "fake_volume",
- "size": 2,
- "host": "irrelevant",
- "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": "irrelevant",
- "volume_type": None,
- "volume_type_id": None,
-}
-INITIATOR_IQN = "iqn.1111-22.org.debian:11:222"
-CONNECTOR = {
- "initiator": INITIATOR_IQN,
- "host": "irrelevant"
-}
-
-
-class V6000CommonTestCase(test.TestCase):
- """Test cases for VMEM V6000 driver common class."""
- def setUp(self):
- super(V6000CommonTestCase, self).setUp()
- self.conf = self.setup_configuration()
- self.driver = v6000_common.V6000Common(self.conf)
- self.driver.container = 'myContainer'
- self.driver.device_id = 'ata-VIOLIN_MEMORY_ARRAY_23109R00000022'
- self.stats = {}
-
- def tearDown(self):
- super(V6000CommonTestCase, self).tearDown()
-
- def setup_configuration(self):
- config = mock.Mock(spec=conf.Configuration)
- config.volume_backend_name = 'v6000_common'
- config.san_ip = '1.1.1.1'
- config.san_login = 'admin'
- config.san_password = ''
- config.san_thin_provision = False
- config.san_is_local = False
- config.gateway_mga = '2.2.2.2'
- config.gateway_mgb = '3.3.3.3'
- config.use_igroups = False
- config.request_timeout = 300
- config.container = 'myContainer'
- return config
-
- @mock.patch('vmemclient.open')
- def setup_mock_client(self, _m_client, m_conf=None):
- """Create a fake backend communication factory.
-
- The vmemclient creates a VShare connection object (for V6000
- devices) and returns it for use on a call to vmemclient.open().
- """
- # configure the vshare object mock with defaults
- _m_vshare = mock.Mock(name='VShare',
- version='1.1.1',
- spec=vmemclient.mock_client_conf)
-
- # if m_conf, clobber the defaults with it
- if m_conf:
- _m_vshare.configure_mock(**m_conf)
-
- # set calls to vmemclient.open() to return this mocked vshare object
- _m_client.return_value = _m_vshare
-
- return _m_client
-
- def setup_mock_vshare(self, m_conf=None):
- """Create a fake VShare communication object."""
- _m_vshare = mock.Mock(name='VShare',
- version='1.1.1',
- spec=vmemclient.mock_client_conf)
-
- if m_conf:
- _m_vshare.configure_mock(**m_conf)
-
- return _m_vshare
-
- def test_check_for_setup_error(self):
- """No setup errors are found."""
- bn1 = ("/vshare/state/local/container/%s/threshold/usedspace"
- "/threshold_hard_val" % self.driver.container)
- bn2 = ("/vshare/state/local/container/%s/threshold/provision"
- "/threshold_hard_val" % self.driver.container)
- bn_thresholds = {bn1: 0, bn2: 100}
-
- conf = {
- 'basic.get_node_values.return_value': bn_thresholds,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._is_supported_vmos_version = mock.Mock(return_value=True)
-
- result = self.driver.check_for_setup_error()
-
- self.driver._is_supported_vmos_version.assert_called_with(
- self.driver.vip.version)
- self.driver.vip.basic.get_node_values.assert_called_with(
- [bn1, bn2])
- self.assertIsNone(result)
-
- def test_check_for_setup_error_no_container(self):
- """No container was configured."""
- self.driver.vip = self.setup_mock_vshare()
- self.driver.container = ''
- self.assertRaises(exception.ViolinInvalidBackendConfig,
- self.driver.check_for_setup_error)
-
- def test_check_for_setup_error_invalid_usedspace_threshold(self):
- """The array's usedspace threshold was altered (not supported)."""
- bn1 = ("/vshare/state/local/container/%s/threshold/usedspace"
- "/threshold_hard_val" % self.driver.container)
- bn2 = ("/vshare/state/local/container/%s/threshold/provision"
- "/threshold_hard_val" % self.driver.container)
- bn_thresholds = {bn1: 99, bn2: 100}
-
- conf = {
- 'basic.get_node_values.return_value': bn_thresholds,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._is_supported_vmos_version = mock.Mock(return_value=True)
-
- self.assertRaises(exception.ViolinInvalidBackendConfig,
- self.driver.check_for_setup_error)
-
- def test_check_for_setup_error_invalid_provisionedspace_threshold(self):
- """The array's provisioned threshold was altered (not supported)."""
- bn1 = ("/vshare/state/local/container/%s/threshold/usedspace"
- "/threshold_hard_val" % self.driver.container)
- bn2 = ("/vshare/state/local/container/%s/threshold/provision"
- "/threshold_hard_val" % self.driver.container)
- bn_thresholds = {bn1: 0, bn2: 99}
-
- conf = {
- 'basic.get_node_values.return_value': bn_thresholds,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._is_supported_vmos_version = mock.Mock(return_value=True)
-
- self.assertRaises(exception.ViolinInvalidBackendConfig,
- self.driver.check_for_setup_error)
-
- def test_create_lun(self):
- """Lun is successfully created."""
- response = {'code': 0, 'message': 'LUN create: success!'}
-
- conf = {
- 'lun.create_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(return_value=response)
-
- result = self.driver._create_lun(VOLUME)
-
- self.driver._send_cmd.assert_called_with(
- self.driver.vip.lun.create_lun, 'LUN create: success!',
- self.driver.container, VOLUME['id'], VOLUME['size'], 1, "0",
- "0", "w", 1, 512, False, False, None)
- self.assertTrue(result is None)
-
- def test_create_lun_lun_already_exists(self):
- """Array returns error that the lun already exists."""
- response = {'code': 14005,
- 'message': 'LUN with name ... already exists'}
-
- conf = {
- 'lun.create_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_client(m_conf=conf)
- self.driver._send_cmd = mock.Mock(
- side_effect=exception.ViolinBackendErrExists(
- response['message']))
-
- self.assertTrue(self.driver._create_lun(VOLUME) is None)
-
- def test_create_lun_create_fails_with_exception(self):
- """Array returns a out of space error."""
- response = {'code': 512, 'message': 'Not enough space available'}
- failure = exception.ViolinBackendErr
-
- conf = {
- 'lun.create_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(
- side_effect=failure(response['message']))
-
- self.assertRaises(failure, self.driver._create_lun, VOLUME)
-
- def test_delete_lun(self):
- """Lun is deleted successfully."""
- response = {'code': 0, 'message': 'lun deletion started'}
- success_msgs = ['lun deletion started', '']
-
- conf = {
- 'lun.delete_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(return_value=response)
-
- result = self.driver._delete_lun(VOLUME)
-
- self.driver._send_cmd.assert_called_with(
- self.driver.vip.lun.bulk_delete_luns,
- success_msgs, self.driver.container, VOLUME['id'])
-
- self.assertTrue(result is None)
-
- def test_delete_lun_empty_response_message(self):
- """Array bug where delete action returns no message."""
- response = {'code': 0, 'message': ''}
-
- conf = {
- 'lun.delete_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(return_value=response)
-
- self.assertTrue(self.driver._delete_lun(VOLUME) is None)
-
- def test_delete_lun_lun_already_deleted(self):
- """Array fails to delete a lun that doesn't exist."""
- response = {'code': 14005, 'message': 'LUN ... does not exist.'}
-
- conf = {
- 'lun.delete_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(
- side_effect=exception.ViolinBackendErrNotFound(
- response['message']))
-
- self.assertTrue(self.driver._delete_lun(VOLUME) is None)
-
- def test_delete_lun_delete_fails_with_exception(self):
- """Array returns a generic error."""
- response = {'code': 14000, 'message': 'Generic error'}
- failure = exception.ViolinBackendErr
- conf = {
- 'lun.delete_lun.return_value': response
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(
- side_effect=failure(response['message']))
-
- self.assertRaises(failure, self.driver._delete_lun, VOLUME)
-
- def test_extend_lun(self):
- """Volume extend completes successfully."""
- new_volume_size = 10
- response = {'code': 0, 'message': 'Success '}
-
- conf = {
- 'lun.resize_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(return_value=response)
-
- result = self.driver._extend_lun(VOLUME, new_volume_size)
- self.driver._send_cmd.assert_called_with(
- self.driver.vip.lun.resize_lun,
- 'Success', self.driver.container,
- VOLUME['id'], new_volume_size)
- self.assertTrue(result is None)
-
- def test_extend_lun_new_size_is_too_small(self):
- """Volume extend fails when new size would shrink the volume."""
- new_volume_size = 0
- response = {'code': 14036, 'message': 'Failure'}
-
- conf = {
- 'lun.resize_lun.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(
- side_effect=exception.ViolinBackendErr(message='fail'))
-
- self.assertRaises(exception.ViolinBackendErr,
- self.driver._extend_lun, VOLUME, new_volume_size)
-
- def test_create_lun_snapshot(self):
- """Snapshot creation completes successfully."""
- response = {'code': 0, 'message': 'success'}
- success_msg = 'Snapshot create: success!'
-
- conf = {
- 'snapshot.create_lun_snapshot.return_value': response
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(return_value=response)
-
- result = self.driver._create_lun_snapshot(SNAPSHOT)
-
- self.driver._send_cmd.assert_called_with(
- self.driver.vip.snapshot.create_lun_snapshot, success_msg,
- self.driver.container, SNAPSHOT['volume_id'], SNAPSHOT['id'])
- self.assertTrue(result is None)
-
- def test_delete_lun_snapshot(self):
- """Snapshot deletion completes successfully."""
- response = {'code': 0, 'message': 'success'}
- success_msg = 'Snapshot delete: success!'
-
- conf = {
- 'snapshot.delete_lun_snapshot.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._send_cmd = mock.Mock(return_value=response)
-
- result = self.driver._delete_lun_snapshot(SNAPSHOT)
-
- self.driver._send_cmd.assert_called_with(
- self.driver.vip.snapshot.delete_lun_snapshot, success_msg,
- self.driver.container, SNAPSHOT['volume_id'], SNAPSHOT['id'])
- self.assertTrue(result is None)
-
- def test_get_lun_id(self):
- bn = "/vshare/config/export/container/%s/lun/%s/target/**" \
- % (self.conf.container, VOLUME['id'])
- response = {("/vshare/config/export/container/%s/lun"
- "/%s/target/hba-a1/initiator/openstack/lun_id"
- % (self.conf.container, VOLUME['id'])): 1}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._get_lun_id(VOLUME['id'])
-
- self.driver.vip.basic.get_node_values.assert_called_with(bn)
- self.assertEqual(1, result)
-
- def test_get_lun_id_with_no_lun_config(self):
- response = {}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertRaises(exception.ViolinBackendErrNotFound,
- self.driver._get_lun_id, VOLUME['id'])
-
- def test_get_snapshot_id(self):
- bn = ("/vshare/config/export/snapshot/container/%s/lun/%s/snap/%s"
- "/target/**") % (self.conf.container, VOLUME['id'],
- SNAPSHOT['id'])
- response = {("/vshare/config/export/snapshot/container/%s/lun"
- "/%s/snap/%s/target/hba-a1/initiator/openstack/lun_id"
- % (self.conf.container, VOLUME['id'],
- SNAPSHOT['id'])): 1}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._get_snapshot_id(VOLUME['id'], SNAPSHOT['id'])
-
- self.driver.vip.basic.get_node_values.assert_called_with(bn)
- self.assertEqual(1, result)
-
- def test_get_snapshot_id_with_no_lun_config(self):
- response = {}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertRaises(exception.ViolinBackendErrNotFound,
- self.driver._get_snapshot_id,
- SNAPSHOT['volume_id'], SNAPSHOT['id'])
-
- def test_send_cmd(self):
- """Command callback completes successfully."""
- success_msg = 'success'
- request_args = ['arg1', 'arg2', 'arg3']
- response = {'code': 0, 'message': 'success'}
-
- request_func = mock.Mock(return_value=response)
- self.driver._fatal_error_code = mock.Mock(return_value=None)
-
- result = self.driver._send_cmd(request_func, success_msg, request_args)
-
- self.driver._fatal_error_code.assert_called_with(response)
- self.assertEqual(response, result)
-
- def test_send_cmd_request_timed_out(self):
- """The callback retry timeout hits immediately."""
- success_msg = 'success'
- request_args = ['arg1', 'arg2', 'arg3']
- self.conf.request_timeout = 0
-
- request_func = mock.Mock()
-
- self.assertRaises(exception.ViolinRequestRetryTimeout,
- self.driver._send_cmd,
- request_func, success_msg, request_args)
-
- def test_send_cmd_response_has_no_message(self):
- """The callback returns no message on the first call."""
- success_msg = 'success'
- request_args = ['arg1', 'arg2', 'arg3']
- response1 = {'code': 0, 'message': None}
- response2 = {'code': 0, 'message': 'success'}
-
- request_func = mock.Mock(side_effect=[response1, response2])
- self.driver._fatal_error_code = mock.Mock(return_value=None)
-
- self.assertEqual(response2, self.driver._send_cmd
- (request_func, success_msg, request_args))
-
- def test_send_cmd_response_has_fatal_error(self):
- """The callback response contains a fatal error code."""
- success_msg = 'success'
- request_args = ['arg1', 'arg2', 'arg3']
- response = {'code': 14000, 'message': 'try again later.'}
- failure = exception.ViolinBackendErr
-
- request_func = mock.Mock(return_value=response)
- self.driver._fatal_error_code = mock.Mock(
- side_effect=failure(message='fail'))
- self.assertRaises(failure, self.driver._send_cmd,
- request_func, success_msg, request_args)
-
- def test_get_igroup(self):
- """The igroup is verified and already exists."""
- bn = '/vshare/config/igroup/%s' % CONNECTOR['host']
- response = {bn: CONNECTOR['host']}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._get_igroup(VOLUME, CONNECTOR)
-
- self.driver.vip.basic.get_node_values.assert_called_with(bn)
- self.assertEqual(CONNECTOR['host'], result)
-
- def test_get_igroup_with_new_name(self):
- """The igroup is verified but must be created on the backend."""
- response = {}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertEqual(CONNECTOR['host'],
- self.driver._get_igroup(VOLUME, CONNECTOR))
-
- def test_wait_for_export_state(self):
- """Queries to cluster nodes verify export state."""
- bn = "/vshare/state/local/container/myContainer/lun/%s/usn_id" \
- % VOLUME['id']
- response = {bn: '012345'}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.mga = self.setup_mock_vshare(m_conf=conf)
- self.driver.mgb = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._wait_for_export_state(VOLUME['id'], state=True)
-
- self.driver.mga.basic.get_node_values.assert_called_with(bn)
- self.driver.mgb.basic.get_node_values.assert_called_with(bn)
- self.assertTrue(result)
-
- def test_wait_for_export_state_with_no_state(self):
- """Queries to cluster nodes verify *no* export state."""
- bn = "/vshare/state/local/container/myContainer/lun/%s/usn_id" \
- % VOLUME['id']
- response = {bn: '(not exported)'}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.mga = self.setup_mock_vshare(m_conf=conf)
- self.driver.mgb = self.setup_mock_vshare(m_conf=conf)
-
- self.assertTrue(self.driver._wait_for_export_state(
- VOLUME['id'], state=False))
-
- def test_is_supported_vmos_version(self):
- """Currently supported VMOS version."""
- version = 'V6.3.1'
- self.assertTrue(self.driver._is_supported_vmos_version(version))
-
- def test_is_supported_vmos_version_supported_future_version(self):
- """Potential future supported VMOS version."""
- version = 'V6.3.7'
- self.assertTrue(self.driver._is_supported_vmos_version(version))
-
- def test_is_supported_vmos_version_unsupported_past_version(self):
- """Currently unsupported VMOS version."""
- version = 'G5.5.2'
- self.assertFalse(self.driver._is_supported_vmos_version(version))
-
- def test_is_supported_vmos_version_unsupported_future_version(self):
- """Future incompatible VMOS version."""
- version = 'V7.0.0'
- self.assertFalse(self.driver._is_supported_vmos_version(version))
-
- def test_fatal_error_code(self):
- """Return an exception for a valid fatal error code."""
- response = {'code': 14000, 'message': 'fail city'}
- self.assertRaises(exception.ViolinBackendErr,
- self.driver._fatal_error_code,
- response)
-
- def test_fatal_error_code_non_fatal_error(self):
- """Returns no exception for a non-fatal error code."""
- response = {'code': 1024, 'message': 'try again!'}
- self.assertIsNone(self.driver._fatal_error_code(response))
+++ /dev/null
-# 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.
-
-"""
-Tests for Violin Memory 6000 Series All-Flash Array Fibrechannel Driver
-"""
-
-import mock
-from oslo_utils import units
-
-from cinder import context
-from cinder import exception
-from cinder.objects import snapshot as csnap
-from cinder.objects import volume as cvol
-from cinder import test
-from cinder.tests.unit import fake_vmem_client as vmemclient
-from cinder.volume import configuration as conf
-from cinder.volume.drivers.violin import v6000_common
-from cinder.volume.drivers.violin import v6000_fcp
-
-VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
-VOLUME = {
- "name": "volume-" + VOLUME_ID,
- "id": VOLUME_ID,
- "display_name": "fake_volume",
- "size": 2,
- "host": "irrelevant",
- "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": "irrelevant",
- "volume_type": None,
- "volume_type_id": None,
-}
-INITIATOR_IQN = "iqn.1111-22.org.debian:11:222"
-CONNECTOR = {
- "initiator": INITIATOR_IQN,
- "host": "irrelevant",
- 'wwpns': [u'50014380186b3f65', u'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]]
-}
-
-
-class V6000FCPDriverTestCase(test.TestCase):
- """Test cases for VMEM FCP driver."""
- def setUp(self):
- super(V6000FCPDriverTestCase, self).setUp()
- self.conf = self.setup_configuration()
- self.driver = v6000_fcp.V6000FCDriver(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 tearDown(self):
- super(V6000FCPDriverTestCase, self).tearDown()
-
- def setup_configuration(self):
- config = mock.Mock(spec=conf.Configuration)
- config.volume_backend_name = 'v6000_fcp'
- config.san_ip = '1.1.1.1'
- config.san_login = 'admin'
- config.san_password = ''
- config.san_thin_provision = False
- config.san_is_local = False
- config.gateway_mga = '2.2.2.2'
- config.gateway_mgb = '3.3.3.3'
- config.use_igroups = False
- config.request_timeout = 300
- config.container = 'myContainer'
- return config
-
- def setup_mock_vshare(self, m_conf=None):
- """Create a fake VShare communication object."""
- _m_vshare = mock.Mock(name='VShare',
- version='1.1.1',
- spec=vmemclient.mock_client_conf)
-
- if m_conf:
- _m_vshare.configure_mock(**m_conf)
-
- return _m_vshare
-
- @mock.patch.object(v6000_common.V6000Common, '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.assertTrue(result is None)
-
- @mock.patch.object(v6000_common.V6000Common, '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 = []
- self.assertRaises(exception.ViolinInvalidBackendConfig,
- 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.assertTrue(result is None)
-
- 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.assertTrue(result is None)
-
- def test_create_snapshot(self):
- """Snapshot created successfully."""
- 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.assertTrue(result is None)
-
- def test_delete_snapshot(self):
- """Snapshot deleted successfully."""
- 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.assertTrue(result is None)
-
- @mock.patch.object(context, 'get_admin_context')
- def test_create_volume_from_snapshot(self, m_context_func):
- """Volume created from a snapshot successfully."""
- m_context_func.return_value = None
- self.driver.common._create_lun = mock.Mock()
- self.driver.copy_volume_data = mock.Mock()
-
- result = self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
-
- m_context_func.assert_called_with()
- self.driver.common._create_lun.assert_called_with(VOLUME)
- self.driver.copy_volume_data.assert_called_with(None, SNAPSHOT, VOLUME)
- self.assertTrue(result is None)
-
- @mock.patch.object(context, 'get_admin_context')
- def test_create_cloned_volume(self, m_context_func):
- """Volume clone created successfully."""
- m_context_func.return_value = None
- self.driver.common._create_lun = mock.Mock()
- self.driver.copy_volume_data = mock.Mock()
-
- result = self.driver.create_cloned_volume(VOLUME, SRC_VOL)
-
- m_context_func.assert_called_with()
- self.driver.common._create_lun.assert_called_with(VOLUME)
- self.driver.copy_volume_data.assert_called_with(None, SRC_VOL, VOLUME)
- self.assertTrue(result is None)
-
- def test_initialize_connection(self):
- lun_id = 1
- igroup = None
- target_wwns = self.driver.gateway_fc_wwns
- init_targ_map = {}
- volume = mock.Mock(spec=cvol.Volume)
-
- self.driver.common.vip = self.setup_mock_vshare()
- 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._export_lun.assert_called_with(volume, CONNECTOR, igroup)
- self.driver.common.vip.basic.save_config.assert_called_with()
- 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(target_wwns, props['data']['target_wwn'])
- self.assertEqual(lun_id, props['data']['target_lun'])
- self.assertEqual(init_targ_map, props['data']['initiator_target_map'])
-
- def test_initialize_connection_with_snapshot_object(self):
- lun_id = 1
- igroup = None
- target_wwns = self.driver.gateway_fc_wwns
- init_targ_map = {}
- snapshot = mock.Mock(spec=csnap.Snapshot)
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver._export_snapshot = 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(snapshot, CONNECTOR)
-
- self.driver._export_snapshot.assert_called_with(
- snapshot, CONNECTOR, igroup)
- self.driver.common.vip.basic.save_config.assert_called_with()
- 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(target_wwns, props['data']['target_wwn'])
- self.assertEqual(lun_id, props['data']['target_lun'])
- self.assertEqual(init_targ_map, props['data']['initiator_target_map'])
-
- def test_terminate_connection(self):
- target_wwns = self.driver.gateway_fc_wwns
- init_targ_map = {}
- volume = mock.Mock(spec=cvol.Volume)
-
- self.driver.common.vip = self.setup_mock_vshare()
- 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)
- self.driver.common.vip.basic.save_config.assert_called_with()
- 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_terminate_connection_snapshot_object(self):
- target_wwns = self.driver.gateway_fc_wwns
- init_targ_map = {}
- snapshot = mock.Mock(spec=csnap.Snapshot)
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver._unexport_snapshot = 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(snapshot, 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_get_volume_stats(self):
- self.driver._update_stats = mock.Mock()
- self.driver._update_stats()
-
- result = self.driver.get_volume_stats(True)
-
- self.driver._update_stats.assert_called_with()
- self.assertEqual(self.driver.stats, result)
-
- def test_export_lun(self):
- lun_id = '1'
- igroup = 'test-igroup-1'
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- return_value=response)
- self.driver.common._get_lun_id = mock.Mock(return_value=lun_id)
-
- result = self.driver._export_lun(VOLUME, CONNECTOR, igroup)
-
- self.driver.common._send_cmd_and_verify.assert_called_with(
- self.driver.common.vip.lun.export_lun,
- self.driver.common._wait_for_export_state, '',
- [self.driver.common.container, VOLUME['id'], 'all',
- igroup, 'auto'], [VOLUME['id'], None, True])
- self.driver.common._get_lun_id.assert_called_with(VOLUME['id'])
- self.assertEqual(lun_id, result)
-
- def test_export_lun_fails_with_exception(self):
- lun_id = '1'
- igroup = 'test-igroup-1'
- response = {'code': 14000, 'message': 'Generic error'}
- failure = exception.ViolinBackendErr
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- side_effect=failure(response['message']))
- self.driver.common._get_lun_id = mock.Mock(return_value=lun_id)
-
- self.assertRaises(failure, self.driver._export_lun,
- VOLUME, CONNECTOR, igroup)
-
- def test_unexport_lun(self):
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- return_value=response)
-
- result = self.driver._unexport_lun(VOLUME)
-
- self.driver.common._send_cmd_and_verify.assert_called_with(
- self.driver.common.vip.lun.unexport_lun,
- self.driver.common._wait_for_export_state, '',
- [self.driver.common.container, VOLUME['id'], 'all', 'all', 'auto'],
- [VOLUME['id'], None, False])
- self.assertTrue(result is None)
-
- def test_unexport_lun_fails_with_exception(self):
- response = {'code': 14000, 'message': 'Generic error'}
- failure = exception.ViolinBackendErr
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- side_effect=failure(response['message']))
-
- self.assertRaises(failure, self.driver._unexport_lun, VOLUME)
-
- def test_export_snapshot(self):
- lun_id = '1'
- igroup = 'test-igroup-1'
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd = mock.Mock(return_value=response)
- self.driver.common._wait_for_export_state = mock.Mock()
- self.driver.common._get_snapshot_id = mock.Mock(return_value=lun_id)
-
- result = self.driver._export_snapshot(SNAPSHOT, CONNECTOR, igroup)
-
- self.driver.common._send_cmd.assert_called_with(
- self.driver.common.vip.snapshot.export_lun_snapshot, '',
- self.driver.common.container, SNAPSHOT['volume_id'],
- SNAPSHOT['id'], igroup, 'all', 'auto')
- self.driver.common._wait_for_export_state.assert_called_with(
- SNAPSHOT['volume_id'], SNAPSHOT['id'], state=True)
- self.driver.common._get_snapshot_id.assert_called_once_with(
- SNAPSHOT['volume_id'], SNAPSHOT['id'])
- self.assertEqual(lun_id, result)
-
- def test_unexport_snapshot(self):
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd = mock.Mock(return_value=response)
- self.driver.common._wait_for_export_state = mock.Mock()
-
- result = self.driver._unexport_snapshot(SNAPSHOT)
-
- self.driver.common._send_cmd.assert_called_with(
- self.driver.common.vip.snapshot.unexport_lun_snapshot, '',
- self.driver.common.container, SNAPSHOT['volume_id'],
- SNAPSHOT['id'], 'all', 'all', 'auto', False)
- self.driver.common._wait_for_export_state.assert_called_with(
- SNAPSHOT['volume_id'], SNAPSHOT['id'], state=False)
- self.assertTrue(result is None)
-
- def test_add_igroup_member(self):
- igroup = 'test-group-1'
- response = {'code': 0, 'message': 'success'}
- wwpns = ['wwn.50:01:43:80:18:6b:3f:65', 'wwn.50:01:43:80:18:6b:3f:67']
-
- conf = {
- 'igroup.add_initiators.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.driver._convert_wwns_openstack_to_vmem = mock.Mock(
- return_value=wwpns)
-
- result = self.driver._add_igroup_member(CONNECTOR, igroup)
-
- self.driver._convert_wwns_openstack_to_vmem.assert_called_with(
- CONNECTOR['wwpns'])
- self.driver.common.vip.igroup.add_initiators.assert_called_with(
- igroup, wwpns)
- self.assertTrue(result is None)
-
- 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
-
- (targ_wwns, init_targ_map) = \
- self.driver._build_initiator_target_map(CONNECTOR)
-
- 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."""
- converted_wwpns = ['50:01:43:80:18:6b:3f:65',
- '50:01:43:80:18:6b:3f:67']
- prefix = "/vshare/config/export/container"
- bn = "%s/%s/lun/**" % (prefix, self.driver.common.container)
- resp_binding0 = "%s/%s/lun/%s/target/hba-a1/initiator/%s" \
- % (prefix, self.driver.common.container, VOLUME['id'],
- converted_wwpns[0])
- resp_binding1 = "%s/%s/lun/%s/target/hba-a1/initiator/%s" \
- % (prefix, self.driver.common.container, VOLUME['id'],
- converted_wwpns[1])
- response = {
- resp_binding0: converted_wwpns[0],
- resp_binding1: converted_wwpns[1]
- }
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._convert_wwns_openstack_to_vmem = mock.Mock(
- return_value=converted_wwpns)
-
- self.assertTrue(self.driver._is_initiator_connected_to_array(
- CONNECTOR))
- self.driver.common.vip.basic.get_node_values.assert_called_with(bn)
-
- def test_is_initiator_connected_to_array_empty_response(self):
- """Successfully finds no initiators with remaining active sessions."""
- converted_wwpns = ['50:01:43:80:18:6b:3f:65',
- '50:01:43:80:18:6b:3f:67']
- response = {}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver._convert_wwns_openstack_to_vmem = mock.Mock(
- return_value=converted_wwpns)
-
- self.assertFalse(self.driver._is_initiator_connected_to_array(
- CONNECTOR))
-
- def test_update_stats(self):
- backend_name = self.conf.volume_backend_name
- vendor_name = "Violin Memory, Inc."
- tot_bytes = 100 * units.Gi
- free_bytes = 50 * units.Gi
- bn0 = '/cluster/state/master_id'
- bn1 = "/vshare/state/global/1/container/myContainer/total_bytes"
- bn2 = "/vshare/state/global/1/container/myContainer/free_bytes"
- response1 = {bn0: '1'}
- response2 = {bn1: tot_bytes, bn2: free_bytes}
-
- conf = {
- 'basic.get_node_values.side_effect': [response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._update_stats()
-
- calls = [mock.call(bn0), mock.call([bn1, bn2])]
- self.driver.common.vip.basic.get_node_values.assert_has_calls(calls)
- self.assertEqual(100, self.driver.stats['total_capacity_gb'])
- self.assertEqual(50, 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.assertTrue(result is None)
-
- def test_update_stats_fails_data_query(self):
- backend_name = self.conf.volume_backend_name
- vendor_name = "Violin Memory, Inc."
- bn0 = '/cluster/state/master_id'
- response1 = {bn0: '1'}
- response2 = {}
-
- conf = {
- 'basic.get_node_values.side_effect': [response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertTrue(self.driver._update_stats() is None)
- self.assertEqual(0, self.driver.stats['total_capacity_gb'])
- self.assertEqual(0, 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'])
-
- def test_update_stats_fails_data_query_but_has_cached_stats(self):
- """Stats query to backend fails, but cached stats are available. """
- backend_name = self.conf.volume_backend_name
- vendor_name = "Violin Memory, Inc."
- bn0 = '/cluster/state/master_id'
- response1 = {bn0: '1'}
- response2 = {}
-
- # fake cached stats, from a previous stats query
- self.driver.stats = {'free_capacity_gb': 50, 'total_capacity_gb': 100}
-
- conf = {
- 'basic.get_node_values.side_effect': [response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertIsNone(self.driver._update_stats())
- self.assertEqual(100, self.driver.stats['total_capacity_gb'])
- self.assertEqual(50, 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'])
-
- def test_get_active_fc_targets(self):
- bn0 = '/vshare/state/global/*'
- response0 = {'/vshare/state/global/1': 1,
- '/vshare/state/global/2': 2}
- bn1 = '/vshare/state/global/1/target/fc/**'
- response1 = {'/vshare/state/global/1/target/fc/hba-a1/wwn':
- 'wwn.21:00:00:24:ff:45:fb:22'}
- bn2 = '/vshare/state/global/2/target/fc/**'
- response2 = {'/vshare/state/global/2/target/fc/hba-a1/wwn':
- 'wwn.21:00:00:24:ff:45:e2:30'}
- wwpns = ['21000024ff45fb22', '21000024ff45e230']
-
- conf = {
- 'basic.get_node_values.side_effect':
- [response0, response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._get_active_fc_targets()
-
- calls = [mock.call(bn0), mock.call(bn1), mock.call(bn2)]
- self.driver.common.vip.basic.get_node_values.assert_has_calls(
- calls, any_order=True)
- self.assertEqual(wwpns, result)
-
- def test_convert_wwns_openstack_to_vmem(self):
- vmem_wwns = ['wwn.50:01:43:80:18:6b:3f:65']
- openstack_wwns = ['50014380186b3f65']
- result = self.driver._convert_wwns_openstack_to_vmem(openstack_wwns)
- self.assertEqual(vmem_wwns, result)
-
- def test_convert_wwns_vmem_to_openstack(self):
- vmem_wwns = ['wwn.50:01:43:80:18:6b:3f:65']
- openstack_wwns = ['50014380186b3f65']
- result = self.driver._convert_wwns_vmem_to_openstack(vmem_wwns)
- self.assertEqual(openstack_wwns, result)
+++ /dev/null
-# 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.
-
-"""
-Tests for Violin Memory 6000 Series All-Flash Array iSCSI driver
-"""
-
-import mock
-from oslo_utils import units
-
-from cinder import context
-from cinder import exception
-from cinder.objects import snapshot as csnap
-from cinder.objects import volume as cvol
-from cinder import test
-from cinder.tests.unit import fake_vmem_client as vmemclient
-from cinder.volume import configuration as conf
-from cinder.volume.drivers.violin import v6000_common
-from cinder.volume.drivers.violin import v6000_iscsi
-
-VOLUME_ID = "abcdabcd-1234-abcd-1234-abcdeffedcba"
-VOLUME = {
- "name": "volume-" + VOLUME_ID,
- "id": VOLUME_ID,
- "display_name": "fake_volume",
- "size": 2,
- "host": "irrelevant",
- "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": "irrelevant",
- "volume_type": None,
- "volume_type_id": None,
-}
-INITIATOR_IQN = "iqn.1111-22.org.debian:11:222"
-CONNECTOR = {
- "initiator": INITIATOR_IQN,
- "host": "irrelevant"
-}
-
-
-class V6000ISCSIDriverTestCase(test.TestCase):
- """Test cases for VMEM iSCSI driver."""
- def setUp(self):
- super(V6000ISCSIDriverTestCase, self).setUp()
- self.conf = self.setup_configuration()
- self.driver = v6000_iscsi.V6000ISCSIDriver(configuration=self.conf)
- self.driver.common.container = 'myContainer'
- self.driver.device_id = 'ata-VIOLIN_MEMORY_ARRAY_23109R00000022'
- self.driver.gateway_iscsi_ip_addresses_mga = '1.2.3.4'
- self.driver.gateway_iscsi_ip_addresses_mgb = '1.2.3.4'
- self.driver.array_info = [{"node": 'hostname_mga',
- "addr": '1.2.3.4',
- "conn": self.driver.common.mga},
- {"node": 'hostname_mgb',
- "addr": '1.2.3.4',
- "conn": self.driver.common.mgb}]
- self.stats = {}
- self.driver.set_initialized()
-
- def tearDown(self):
- super(V6000ISCSIDriverTestCase, self).tearDown()
-
- def setup_configuration(self):
- config = mock.Mock(spec=conf.Configuration)
- config.volume_backend_name = 'v6000_iscsi'
- config.san_ip = '1.1.1.1'
- config.san_login = 'admin'
- config.san_password = ''
- config.san_thin_provision = False
- config.san_is_local = False
- config.gateway_mga = '2.2.2.2'
- config.gateway_mgb = '3.3.3.3'
- config.use_igroups = False
- config.request_timeout = 300
- config.container = 'myContainer'
- config.iscsi_port = 3260
- config.iscsi_target_prefix = 'iqn.2004-02.com.vmem:'
- return config
-
- def setup_mock_vshare(self, m_conf=None):
- """Create a fake VShare communication object."""
- _m_vshare = mock.Mock(name='VShare',
- version='1.1.1',
- spec=vmemclient.mock_client_conf)
-
- if m_conf:
- _m_vshare.configure_mock(**m_conf)
-
- return _m_vshare
-
- @mock.patch.object(v6000_common.V6000Common, 'check_for_setup_error')
- def test_check_for_setup_error(self, m_setup_func):
- bn = "/vshare/config/iscsi/enable"
- response = {bn: True}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver.check_for_setup_error()
-
- m_setup_func.assert_called_with()
- self.driver.common.vip.basic.get_node_values.assert_called_with(bn)
- self.assertTrue(result is None)
-
- @mock.patch.object(v6000_common.V6000Common, 'check_for_setup_error')
- def test_check_for_setup_error_iscsi_is_disabled(self, m_setup_func):
- bn = "/vshare/config/iscsi/enable"
- response = {bn: False}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertRaises(exception.ViolinInvalidBackendConfig,
- self.driver.check_for_setup_error)
-
- @mock.patch.object(v6000_common.V6000Common, 'check_for_setup_error')
- def test_check_for_setup_error_no_iscsi_ips_for_mga(self, m_setup_func):
- bn = "/vshare/config/iscsi/enable"
- response = {bn: True}
- self.driver.gateway_iscsi_ip_addresses_mga = ''
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertRaises(exception.ViolinInvalidBackendConfig,
- self.driver.check_for_setup_error)
-
- @mock.patch.object(v6000_common.V6000Common, 'check_for_setup_error')
- def test_check_for_setup_error_no_iscsi_ips_for_mgb(self, m_setup_func):
- bn = "/vshare/config/iscsi/enable"
- response = {bn: True}
- self.driver.gateway_iscsi_ip_addresses_mgb = ''
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertRaises(exception.ViolinInvalidBackendConfig,
- 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.assertTrue(result is None)
-
- 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.assertTrue(result is None)
-
- def test_create_snapshot(self):
- """Snapshot created successfully."""
- 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.assertTrue(result is None)
-
- def test_delete_snapshot(self):
- """Snapshot deleted successfully."""
- 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.assertTrue(result is None)
-
- @mock.patch.object(context, 'get_admin_context')
- def test_create_volume_from_snapshot(self, m_context_func):
- """Volume created from a snapshot successfully."""
- m_context_func.return_value = None
- self.driver.common._create_lun = mock.Mock()
- self.driver.copy_volume_data = mock.Mock()
-
- result = self.driver.create_volume_from_snapshot(VOLUME, SNAPSHOT)
-
- m_context_func.assert_called_with()
- self.driver.common._create_lun.assert_called_with(VOLUME)
- self.driver.copy_volume_data.assert_called_with(None, SNAPSHOT, VOLUME)
- self.assertTrue(result is None)
-
- @mock.patch.object(context, 'get_admin_context')
- def test_create_cloned_volume(self, m_context_func):
- """Volume clone created successfully."""
- m_context_func.return_value = None
- self.driver.common._create_lun = mock.Mock()
- self.driver.copy_volume_data = mock.Mock()
-
- result = self.driver.create_cloned_volume(VOLUME, SRC_VOL)
-
- m_context_func.assert_called_with()
- self.driver.common._create_lun.assert_called_with(VOLUME)
- self.driver.copy_volume_data.assert_called_with(None, SRC_VOL, VOLUME)
- self.assertTrue(result is None)
-
- def test_initialize_connection(self):
- lun_id = 1
- igroup = None
- target_name = self.driver.TARGET_GROUP_NAME
- tgt = self.driver.array_info[0]
- iqn = "%s%s:%s" % (self.conf.iscsi_target_prefix,
- tgt['node'], target_name)
- volume = mock.MagicMock(spec=cvol.Volume)
-
- def getitem(name):
- return VOLUME[name]
-
- volume.__getitem__.side_effect = getitem
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver._get_iscsi_target = mock.Mock(return_value=tgt)
- self.driver._export_lun = mock.Mock(return_value=lun_id)
-
- props = self.driver.initialize_connection(volume, CONNECTOR)
-
- self.driver._get_iscsi_target.assert_called_once_with()
- self.driver._export_lun.assert_called_once_with(
- volume, CONNECTOR, igroup)
- self.driver.common.vip.basic.save_config.assert_called_with()
- self.assertEqual("1.2.3.4:3260", props['data']['target_portal'])
- self.assertEqual(iqn, props['data']['target_iqn'])
- self.assertEqual(lun_id, props['data']['target_lun'])
- self.assertEqual(volume['id'], props['data']['volume_id'])
-
- def test_initialize_connection_with_snapshot_object(self):
- lun_id = 1
- igroup = None
- target_name = self.driver.TARGET_GROUP_NAME
- tgt = self.driver.array_info[0]
- iqn = "%s%s:%s" % (self.conf.iscsi_target_prefix,
- tgt['node'], target_name)
- snapshot = mock.MagicMock(spec=csnap.Snapshot)
-
- def getitem(name):
- return SNAPSHOT[name]
-
- snapshot.__getitem__.side_effect = getitem
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver._get_iscsi_target = mock.Mock(return_value=tgt)
- self.driver._export_snapshot = mock.Mock(return_value=lun_id)
-
- props = self.driver.initialize_connection(snapshot, CONNECTOR)
-
- self.driver._get_iscsi_target.assert_called_once_with()
- self.driver._export_snapshot.assert_called_once_with(
- snapshot, CONNECTOR, igroup)
- self.driver.common.vip.basic.save_config.assert_called_with()
- self.assertEqual("1.2.3.4:3260", props['data']['target_portal'])
- self.assertEqual(iqn, props['data']['target_iqn'])
- self.assertEqual(lun_id, props['data']['target_lun'])
- self.assertEqual(SNAPSHOT['id'], props['data']['volume_id'])
-
- def test_initialize_connection_with_igroups_enabled(self):
- self.conf.use_igroups = True
- lun_id = 1
- igroup = 'test-igroup-1'
- target_name = self.driver.TARGET_GROUP_NAME
- tgt = self.driver.array_info[0]
- iqn = "%s%s:%s" % (self.conf.iscsi_target_prefix,
- tgt['node'], target_name)
- volume = mock.MagicMock(spec=cvol.Volume)
-
- def getitem(name):
- return VOLUME[name]
-
- volume.__getitem__.side_effect = getitem
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._get_igroup = mock.Mock(return_value=igroup)
- self.driver._add_igroup_member = mock.Mock()
- self.driver._get_iscsi_target = mock.Mock(return_value=tgt)
- self.driver._export_lun = mock.Mock(return_value=lun_id)
-
- props = self.driver.initialize_connection(volume, CONNECTOR)
-
- self.driver.common._get_igroup.assert_called_once_with(
- volume, CONNECTOR)
- self.driver._add_igroup_member.assert_called_once_with(
- CONNECTOR, igroup)
- self.driver._get_iscsi_target.assert_called_once_with()
- self.driver._export_lun.assert_called_once_with(
- volume, CONNECTOR, igroup)
- self.driver.common.vip.basic.save_config.assert_called_once_with()
- self.assertEqual("1.2.3.4:3260", props['data']['target_portal'])
- self.assertEqual(iqn, 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):
- volume = mock.MagicMock(spec=cvol.Volume)
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver._unexport_lun = mock.Mock()
-
- result = self.driver.terminate_connection(volume, CONNECTOR)
-
- self.driver._unexport_lun.assert_called_once_with(volume)
- self.driver.common.vip.basic.save_config.assert_called_with()
- self.assertTrue(result is None)
-
- def test_terminate_connection_with_snapshot_object(self):
- snapshot = mock.MagicMock(spec=csnap.Snapshot)
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver._unexport_snapshot = mock.Mock()
-
- result = self.driver.terminate_connection(snapshot, CONNECTOR)
-
- self.driver._unexport_snapshot.assert_called_once_with(snapshot)
- self.driver.common.vip.basic.save_config.assert_called_with()
- self.assertTrue(result is None)
-
- def test_get_volume_stats(self):
- self.driver._update_stats = mock.Mock()
- self.driver._update_stats()
-
- result = self.driver.get_volume_stats(True)
-
- self.driver._update_stats.assert_called_with()
- self.assertEqual(self.driver.stats, result)
-
- def test_create_iscsi_target_group(self):
- target_name = self.driver.TARGET_GROUP_NAME
- bn = "/vshare/config/iscsi/target/%s" % target_name
- response1 = {}
- response2 = {'code': 0, 'message': 'success'}
-
- conf = {
- 'basic.get_node_values.return_value': response1,
- }
- m_vshare = self.setup_mock_vshare(conf)
-
- self.driver.common.vip = m_vshare
- self.driver.common.mga = m_vshare
- self.driver.common.mgb = m_vshare
- self.driver.common._send_cmd_and_verify = mock.Mock(
- return_value=response2)
- self.driver.common._send_cmd = mock.Mock(return_value=response2)
-
- calls = [mock.call(self.driver.common.mga.iscsi.bind_ip_to_target, '',
- target_name,
- self.driver.gateway_iscsi_ip_addresses_mga),
- mock.call(self.driver.common.mgb.iscsi.bind_ip_to_target, '',
- target_name,
- self.driver.gateway_iscsi_ip_addresses_mgb)]
-
- result = self.driver._create_iscsi_target_group()
-
- self.driver.common.vip.basic.get_node_values.assert_called_with(bn)
- self.driver.common._send_cmd_and_verify.assert_called_with(
- self.driver.common.vip.iscsi.create_iscsi_target,
- self.driver._wait_for_target_state, '',
- [target_name], [target_name])
- self.driver.common._send_cmd.assert_has_calls(calls)
- self.assertTrue(result is None)
-
- def test_export_lun(self):
- target_name = self.driver.TARGET_GROUP_NAME
- igroup = 'test-igroup-1'
- lun_id = '1'
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- return_value=response)
- self.driver.common._get_lun_id = mock.Mock(return_value=lun_id)
-
- result = self.driver._export_lun(VOLUME, CONNECTOR, igroup)
-
- self.driver.common._send_cmd_and_verify.assert_called_with(
- self.driver.common.vip.lun.export_lun,
- self.driver.common._wait_for_export_state, '',
- [self.driver.common.container, VOLUME['id'], target_name,
- igroup, 'auto'], [VOLUME['id'], None, True])
- self.driver.common._get_lun_id.assert_called_with(VOLUME['id'])
- self.assertEqual(lun_id, result)
-
- def test_export_lun_fails_with_exception(self):
- igroup = 'test-igroup-1'
- lun_id = '1'
- response = {'code': 14000, 'message': 'Generic error'}
- failure = exception.ViolinBackendErr
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- side_effect=failure(response['message']))
- self.driver._get_lun_id = mock.Mock(return_value=lun_id)
-
- self.assertRaises(failure, self.driver._export_lun,
- VOLUME, CONNECTOR, igroup)
-
- def test_unexport_lun(self):
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- return_value=response)
-
- result = self.driver._unexport_lun(VOLUME)
-
- self.driver.common._send_cmd_and_verify.assert_called_with(
- self.driver.common.vip.lun.unexport_lun,
- self.driver.common._wait_for_export_state, '',
- [self.driver.common.container, VOLUME['id'], 'all', 'all', 'auto'],
- [VOLUME['id'], None, False])
- self.assertTrue(result is None)
-
- def test_unexport_lun_fails_with_exception(self):
- response = {'code': 14000, 'message': 'Generic error'}
- failure = exception.ViolinBackendErr
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd_and_verify = mock.Mock(
- side_effect=failure(response['message']))
-
- self.assertRaises(failure, self.driver._unexport_lun, VOLUME)
-
- def test_export_snapshot(self):
- lun_id = '1'
- target_name = self.driver.TARGET_GROUP_NAME
- igroup = 'test-igroup-1'
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd = mock.Mock(return_value=response)
- self.driver.common._wait_for_export_state = mock.Mock()
- self.driver.common._get_snapshot_id = mock.Mock(return_value=lun_id)
-
- result = self.driver._export_snapshot(SNAPSHOT, CONNECTOR, igroup)
-
- self.driver.common._send_cmd.assert_called_with(
- self.driver.common.vip.snapshot.export_lun_snapshot, '',
- self.driver.common.container, SNAPSHOT['volume_id'],
- SNAPSHOT['id'], igroup, target_name, 'auto')
- self.driver.common._wait_for_export_state.assert_called_with(
- SNAPSHOT['volume_id'], SNAPSHOT['id'], state=True)
- self.driver.common._get_snapshot_id.assert_called_once_with(
- SNAPSHOT['volume_id'], SNAPSHOT['id'])
-
- self.assertEqual(lun_id, result)
-
- def test_unexport_snapshot(self):
- response = {'code': 0, 'message': ''}
-
- self.driver.common.vip = self.setup_mock_vshare()
- self.driver.common._send_cmd = mock.Mock(return_value=response)
- self.driver.common._wait_for_export_state = mock.Mock()
-
- result = self.driver._unexport_snapshot(SNAPSHOT)
-
- self.driver.common._send_cmd.assert_called_with(
- self.driver.common.vip.snapshot.unexport_lun_snapshot, '',
- self.driver.common.container, SNAPSHOT['volume_id'],
- SNAPSHOT['id'], 'all', 'all', 'auto', False)
- self.driver.common._wait_for_export_state.assert_called_with(
- SNAPSHOT['volume_id'], SNAPSHOT['id'], state=False)
- self.assertTrue(result is None)
-
- def test_add_igroup_member(self):
- igroup = 'test-group-1'
- response = {'code': 0, 'message': 'success'}
-
- conf = {
- 'igroup.add_initiators.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._add_igroup_member(CONNECTOR, igroup)
-
- self.driver.common.vip.igroup.add_initiators.assert_called_with(
- igroup, CONNECTOR['initiator'])
- self.assertTrue(result is None)
-
- def test_update_stats(self):
- backend_name = self.conf.volume_backend_name
- vendor_name = "Violin Memory, Inc."
- tot_bytes = 100 * units.Gi
- free_bytes = 50 * units.Gi
- bn0 = '/cluster/state/master_id'
- bn1 = "/vshare/state/global/1/container/myContainer/total_bytes"
- bn2 = "/vshare/state/global/1/container/myContainer/free_bytes"
- response1 = {bn0: '1'}
- response2 = {bn1: tot_bytes, bn2: free_bytes}
-
- conf = {
- 'basic.get_node_values.side_effect': [response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._update_stats()
-
- calls = [mock.call(bn0), mock.call([bn1, bn2])]
- self.driver.common.vip.basic.get_node_values.assert_has_calls(calls)
- self.assertEqual(100, self.driver.stats['total_capacity_gb'])
- self.assertEqual(50, 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.assertTrue(result is None)
-
- def test_update_stats_fails_data_query(self):
- backend_name = self.conf.volume_backend_name
- vendor_name = "Violin Memory, Inc."
- bn0 = '/cluster/state/master_id'
- response1 = {bn0: '1'}
- response2 = {}
-
- conf = {
- 'basic.get_node_values.side_effect': [response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertTrue(self.driver._update_stats() is None)
- self.assertEqual(0, self.driver.stats['total_capacity_gb'])
- self.assertEqual(0, 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'])
-
- def test_update_stats_fails_data_query_but_has_cached_stats(self):
- """Stats query to backend fails, but cached stats are available. """
- backend_name = self.conf.volume_backend_name
- vendor_name = "Violin Memory, Inc."
- bn0 = '/cluster/state/master_id'
- response1 = {bn0: '1'}
- response2 = {}
-
- # fake cached stats, from a previous stats query
- self.driver.stats = {'free_capacity_gb': 50, 'total_capacity_gb': 100}
-
- conf = {
- 'basic.get_node_values.side_effect': [response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertIsNone(self.driver._update_stats())
- self.assertEqual(100, self.driver.stats['total_capacity_gb'])
- self.assertEqual(50, 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'])
-
- def testGetShortName_LongName(self):
- long_name = "abcdefghijklmnopqrstuvwxyz1234567890"
- short_name = "abcdefghijklmnopqrstuvwxyz123456"
- self.assertEqual(short_name, self.driver._get_short_name(long_name))
-
- def testGetShortName_ShortName(self):
- long_name = "abcdef"
- short_name = "abcdef"
- self.assertEqual(short_name, self.driver._get_short_name(long_name))
-
- def testGetShortName_EmptyName(self):
- long_name = ""
- short_name = ""
- self.assertEqual(short_name, self.driver._get_short_name(long_name))
-
- def test_get_active_iscsi_ips(self):
- bn0 = "/net/interface/config/*"
- bn1 = ["/net/interface/state/eth4/addr/ipv4/1/ip",
- "/net/interface/state/eth4/flags/link_up"]
- response1 = {"/net/interface/config/eth4": "eth4"}
- response2 = {"/net/interface/state/eth4/addr/ipv4/1/ip": "1.1.1.1",
- "/net/interface/state/eth4/flags/link_up": True}
-
- conf = {
- 'basic.get_node_values.side_effect': [response1, response2],
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- results = self.driver._get_active_iscsi_ips(self.driver.common.vip)
-
- calls = [mock.call(bn0), mock.call(bn1)]
- self.driver.common.vip.basic.get_node_values.assert_has_calls(calls)
- self.assertEqual(1, len(results))
- self.assertEqual("1.1.1.1", results[0])
-
- def test_get_active_iscsi_ips_with_invalid_interfaces(self):
- response = {"/net/interface/config/lo": "lo",
- "/net/interface/config/vlan10": "vlan10",
- "/net/interface/config/eth1": "eth1",
- "/net/interface/config/eth2": "eth2",
- "/net/interface/config/eth3": "eth3"}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._get_active_iscsi_ips(self.driver.common.vip)
-
- self.assertEqual(0, len(result))
-
- def test_get_active_iscsi_ips_with_no_interfaces(self):
- response = {}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._get_active_iscsi_ips(self.driver.common.vip)
-
- self.assertEqual(0, len(result))
-
- def test_get_hostname(self):
- bn = '/system/hostname'
- response = {bn: 'MYHOST'}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._get_hostname()
-
- self.driver.common.vip.basic.get_node_values.assert_called_with(bn)
- self.assertEqual("MYHOST", result)
-
- def test_get_hostname_mga(self):
- bn = '/system/hostname'
- response = {bn: 'MYHOST'}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver.common.mga = self.setup_mock_vshare(m_conf=conf)
- self.assertEqual("MYHOST", self.driver._get_hostname('mga'))
-
- def test_get_hostname_mgb(self):
- response = {"/system/hostname": "MYHOST"}
- bn = '/system/hostname'
- response = {bn: 'MYHOST'}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
- self.driver.common.mgb = self.setup_mock_vshare(m_conf=conf)
- self.assertEqual("MYHOST", self.driver._get_hostname('mgb'))
-
- def test_get_hostname_query_fails(self):
- response = {}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.vip = self.setup_mock_vshare(m_conf=conf)
-
- self.assertEqual(self.conf.san_ip, self.driver._get_hostname())
-
- def test_wait_for_target_state(self):
- target = 'mytarget'
- bn = "/vshare/state/local/target/iscsi/%s" % target
- response = {bn: target}
-
- conf = {
- 'basic.get_node_values.return_value': response,
- }
- self.driver.common.mga = self.setup_mock_vshare(m_conf=conf)
- self.driver.common.mgb = self.setup_mock_vshare(m_conf=conf)
-
- result = self.driver._wait_for_target_state(target)
-
- self.driver.common.mga.basic.get_node_values.assert_called_with(bn)
- self.driver.common.mgb.basic.get_node_values.assert_called_with(bn)
- self.assertTrue(result)
+++ /dev/null
-# 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.
-
-"""
-Violin Memory 6000 Series All-Flash Array Common Driver for OpenStack Cinder
-
-Provides common (ie., non-protocol specific) management functions for
-V6000 series flash arrays.
-
-Backend array communication is handled via VMEM's python library
-called 'vmemclient'.
-
-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.
-"""
-
-import re
-import time
-
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_service import loopingcall
-from oslo_utils import importutils
-import six
-
-from cinder import exception
-from cinder.i18n import _, _LE, _LW, _LI
-from cinder import utils
-
-LOG = logging.getLogger(__name__)
-
-vmemclient = importutils.try_import("vmemclient")
-if vmemclient:
- LOG.info(_LI("Running with vmemclient version: %s."),
- vmemclient.__version__)
-
-VMOS_SUPPORTED_VERSION_PATTERNS = ['V6.3.0.[4-9]', 'V6.3.[1-9].?[0-9]?']
-
-violin_opts = [
- cfg.StrOpt('gateway_mga',
- help='IP address or hostname of mg-a'),
- cfg.StrOpt('gateway_mgb',
- help='IP address or hostname of mg-b'),
- cfg.BoolOpt('use_igroups',
- default=False,
- help='Use igroups to manage targets and initiators'),
- cfg.IntOpt('request_timeout',
- default=300,
- help='Global backend request timeout, in seconds'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(violin_opts)
-
-
-class V6000Common(object):
- """Contains common code for the Violin V6000 drivers.
-
- Version history:
- 1.0 - Initial driver
- 1.0.1 - Fixes polling for export completion
- """
-
- VERSION = '1.0.1'
-
- def __init__(self, config):
- self.vip = None
- self.mga = None
- self.mgb = None
- self.container = ""
- self.config = config
-
- def do_setup(self, context):
- """Any initialization the driver does while starting."""
- if not self.config.san_ip:
- raise exception.InvalidInput(
- reason=_('Gateway VIP option \'san_ip\' is not set'))
- if not self.config.gateway_mga:
- raise exception.InvalidInput(
- reason=_('Gateway MG-A IP option \'gateway_mga\' is not set'))
- if not self.config.gateway_mgb:
- raise exception.InvalidInput(
- reason=_('Gateway MG-B IP option \'gateway_mgb\' is not set'))
- if self.config.request_timeout <= 0:
- raise exception.InvalidInput(
- reason=_('Global timeout option \'request_timeout\' must be '
- 'greater than 0'))
-
- self.vip = vmemclient.open(self.config.san_ip,
- self.config.san_login,
- self.config.san_password, keepalive=True)
- self.mga = vmemclient.open(self.config.gateway_mga,
- self.config.san_login,
- self.config.san_password, keepalive=True)
- self.mgb = vmemclient.open(self.config.gateway_mgb,
- self.config.san_login,
- self.config.san_password, keepalive=True)
-
- ret_dict = self.vip.basic.get_node_values(
- "/vshare/state/local/container/*")
- if ret_dict:
- self.container = list(ret_dict.items())[0][1]
-
- def check_for_setup_error(self):
- """Returns an error if prerequisites aren't met."""
-
- if len(self.container) == 0:
- msg = _('container is missing')
- raise exception.ViolinInvalidBackendConfig(reason=msg)
-
- if not self._is_supported_vmos_version(self.vip.version):
- msg = _('VMOS version is not supported')
- raise exception.ViolinInvalidBackendConfig(reason=msg)
-
- bn1 = ("/vshare/state/local/container/%s/threshold/usedspace"
- "/threshold_hard_val" % self.container)
- bn2 = ("/vshare/state/local/container/%s/threshold/provision"
- "/threshold_hard_val" % self.container)
- ret_dict = self.vip.basic.get_node_values([bn1, bn2])
-
- for node in ret_dict:
- # The infrastructure does not support space reclamation so
- # ensure it is disabled. When used space exceeds the hard
- # limit, snapshot space reclamation begins. Default is 0
- # => no space reclamation.
- #
- if node.endswith('/usedspace/threshold_hard_val'):
- if ret_dict[node] != 0:
- msg = _('space reclamation threshold is enabled but not '
- 'supported by Cinder infrastructure.')
- raise exception.ViolinInvalidBackendConfig(reason=msg)
-
- # The infrastructure does not support overprovisioning so
- # ensure it is disabled. When provisioned space exceeds
- # the hard limit, further provisioning is stopped.
- # Default is 100 => provisioned space equals usable space.
- #
- elif node.endswith('/provision/threshold_hard_val'):
- if ret_dict[node] != 100:
- msg = _('provisioned space threshold is not equal to '
- 'usable space.')
- raise exception.ViolinInvalidBackendConfig(reason=msg)
-
- @utils.synchronized('vmem-lun')
- def _create_lun(self, volume):
- """Creates a new lun.
-
- The equivalent CLI command is "lun create container
- <container_name> name <lun_name> size <gb>"
-
- Arguments:
- volume -- volume object provided by the Manager
- """
- lun_type = '0'
-
- LOG.debug("Creating LUN %(name)s, %(size)s GB.",
- {'name': volume['name'], 'size': volume['size']})
-
- if self.config.san_thin_provision:
- lun_type = '1'
-
- # using the defaults for fields: quantity, nozero,
- # readonly, startnum, blksize, naca, alua, preferredport
- #
- try:
- self._send_cmd(self.vip.lun.create_lun,
- 'LUN create: success!',
- self.container, volume['id'],
- volume['size'], 1, '0', lun_type, 'w',
- 1, 512, False, False, None)
-
- except exception.ViolinBackendErrExists:
- LOG.debug("Lun %s already exists, continuing.", volume['id'])
-
- except Exception:
- LOG.warning(_LW("Lun create for %s failed!"), volume['id'])
- raise
-
- @utils.synchronized('vmem-lun')
- def _delete_lun(self, volume):
- """Deletes a lun.
-
- The equivalent CLI command is "no lun create container
- <container_name> name <lun_name>"
-
- Arguments:
- volume -- volume object provided by the Manager
- """
- success_msgs = ['lun deletion started', '']
-
- LOG.debug("Deleting lun %s.", volume['id'])
-
- try:
- self._send_cmd(self.vip.lun.bulk_delete_luns,
- success_msgs, self.container, volume['id'])
-
- except exception.ViolinBackendErrNotFound:
- LOG.debug("Lun %s already deleted, continuing.", volume['id'])
-
- except exception.ViolinBackendErrExists:
- LOG.warning(_LW("Lun %s has dependent snapshots, skipping."),
- volume['id'])
- raise exception.VolumeIsBusy(volume_name=volume['id'])
-
- except Exception:
- LOG.exception(_LE("Lun delete for %s failed!"), volume['id'])
- raise
-
- @utils.synchronized('vmem-lun')
- def _extend_lun(self, volume, new_size):
- """Extend an existing volume's size.
-
- The equivalent CLI command is "lun resize container
- <container_name> name <lun_name> size <gb>"
-
- Arguments:
- volume -- volume object provided by the Manager
- new_size -- new (increased) size in GB to be applied
- """
- LOG.debug("Extending lun %(id)s, from %(size)s to %(new_size)s GB.",
- {'id': volume['id'], 'size': volume['size'],
- 'new_size': new_size})
-
- try:
- self._send_cmd(self.vip.lun.resize_lun, 'Success',
- self.container, volume['id'], new_size)
-
- except Exception:
- LOG.exception(_LE("LUN extend for %s failed!"), volume['id'])
- raise
-
- @utils.synchronized('vmem-snap')
- def _create_lun_snapshot(self, snapshot):
- """Creates a new snapshot for a lun.
-
- The equivalent CLI command is "snapshot create container
- <container> lun <volume_name> name <snapshot_name>"
-
- Arguments:
- snapshot -- snapshot object provided by the Manager
- """
- LOG.debug("Creating snapshot %s.", snapshot['id'])
-
- try:
- self._send_cmd(self.vip.snapshot.create_lun_snapshot,
- 'Snapshot create: success!',
- self.container, snapshot['volume_id'],
- snapshot['id'])
-
- except exception.ViolinBackendErrExists:
- LOG.debug("Snapshot %s already exists, continuing.",
- snapshot['id'])
-
- except Exception:
- LOG.exception(_LE("LUN snapshot create for %s failed!"),
- snapshot['id'])
- raise
-
- @utils.synchronized('vmem-snap')
- def _delete_lun_snapshot(self, snapshot):
- """Deletes an existing snapshot for a lun.
-
- The equivalent CLI command is "no snapshot create container
- <container> lun <volume_name> name <snapshot_name>"
-
- Arguments:
- snapshot -- snapshot object provided by the Manager
- """
- LOG.debug("Deleting snapshot %s.", snapshot['id'])
-
- try:
- self._send_cmd(self.vip.snapshot.delete_lun_snapshot,
- 'Snapshot delete: success!',
- self.container, snapshot['volume_id'],
- snapshot['id'])
-
- except exception.ViolinBackendErrNotFound:
- LOG.debug("Snapshot %s already deleted, continuing.",
- snapshot['id'])
-
- except Exception:
- LOG.exception(_LE("LUN snapshot delete for %s failed!"),
- snapshot['id'])
- raise
-
- def _get_lun_id(self, volume_name):
- """Queries the gateway to find the lun id for the exported volume.
-
- Arguments:
- volume_name -- LUN to query
-
- Returns:
- LUN ID for the exported lun.
- """
- lun_id = -1
-
- prefix = "/vshare/config/export/container"
- bn = "%s/%s/lun/%s/target/**" % (prefix, self.container, volume_name)
- resp = self.vip.basic.get_node_values(bn)
-
- for node in resp:
- if node.endswith('/lun_id'):
- lun_id = resp[node]
- break
-
- if lun_id == -1:
- raise exception.ViolinBackendErrNotFound()
- return lun_id
-
- def _get_snapshot_id(self, volume_name, snapshot_name):
- """Queries the gateway to find the lun id for the exported snapshot.
-
- Arguments:
- volume_name -- LUN to query
- snapshot_name -- Exported snapshot associated with LUN
-
- Returns:
- LUN ID for the exported lun
- """
- lun_id = -1
-
- prefix = "/vshare/config/export/snapshot/container"
- bn = "%s/%s/lun/%s/snap/%s/target/**" \
- % (prefix, self.container, volume_name, snapshot_name)
- resp = self.vip.basic.get_node_values(bn)
-
- for node in resp:
- if node.endswith('/lun_id'):
- lun_id = resp[node]
- break
-
- if lun_id == -1:
- raise exception.ViolinBackendErrNotFound()
- return lun_id
-
- def _send_cmd(self, request_func, success_msgs, *args):
- """Run an XG request function, and retry as needed.
-
- The request will be retried until it returns a success
- message, a failure message, or the global request timeout is
- hit.
-
- This wrapper is meant to deal with backend requests that can
- fail for any variety of reasons, for instance, when the system
- is already busy handling other LUN requests. It is also smart
- enough to give up if clustering is down (eg no HA available),
- there is no space left, or other "fatal" errors are returned
- (see _fatal_error_code() for a list of all known error
- conditions).
-
- Arguments:
- request_func -- XG api method to call
- success_msgs -- Success messages expected from the backend
- *args -- argument array to be passed to the request_func
-
- Returns:
- The response dict from the last XG call.
- """
- resp = {}
- start = time.time()
- done = False
-
- if isinstance(success_msgs, six.string_types):
- success_msgs = [success_msgs]
-
- while not done:
- if time.time() - start >= self.config.request_timeout:
- raise exception.ViolinRequestRetryTimeout(
- timeout=self.config.request_timeout)
-
- resp = request_func(*args)
-
- if not resp['message']:
- # XG requests will return None for a message if no message
- # string is passed in the raw response
- resp['message'] = ''
-
- for msg in success_msgs:
- if not resp['code'] and msg in resp['message']:
- done = True
- break
-
- self._fatal_error_code(resp)
-
- return resp
-
- def _send_cmd_and_verify(self, request_func, verify_func,
- request_success_msgs, rargs=None, vargs=None):
- """Run an XG request function, retry if needed, and verify success.
-
- If the verification fails, then retry the request/verify
- cycle until both functions are successful, the request
- function returns a failure message, or the global request
- timeout is hit.
-
- This wrapper is meant to deal with backend requests that can
- fail for any variety of reasons, for instance, when the system
- is already busy handling other LUN requests. It is also smart
- enough to give up if clustering is down (eg no HA available),
- there is no space left, or other "fatal" errors are returned
- (see _fatal_error_code() for a list of all known error
- conditions).
-
- Arguments:
- request_func -- XG api method to call
- verify_func -- function to call to verify request was
- completed successfully (eg for export)
- request_success_msg -- Success message expected from the backend
- for the request_func
- rargs -- argument array to be passed to the
- request_func
- vargs -- argument array to be passed to the
- verify_func
-
- Returns:
- The response dict from the last XG call.
- """
- resp = {}
- start = time.time()
- request_needed = True
- verify_needed = True
-
- if isinstance(request_success_msgs, six.string_types):
- request_success_msgs = [request_success_msgs]
-
- rargs = rargs if rargs else []
- vargs = vargs if vargs else []
-
- while request_needed or verify_needed:
- if time.time() - start >= self.config.request_timeout:
- raise exception.ViolinRequestRetryTimeout(
- timeout=self.config.request_timeout)
-
- if request_needed:
- resp = request_func(*rargs)
- if not resp['message']:
- # XG requests will return None for a message if no message
- # string is passed int the raw response
- resp['message'] = ''
- for msg in request_success_msgs:
- if not resp['code'] and msg in resp['message']:
- # XG request func was completed
- request_needed = False
- break
- self._fatal_error_code(resp)
-
- elif verify_needed:
- success = verify_func(*vargs)
- if success:
- # XG verify func was completed
- verify_needed = False
- else:
- # try sending the request again
- request_needed = True
-
- return resp
-
- def _get_igroup(self, volume, connector):
- """Gets the igroup that should be used when configuring a volume.
-
- Arguments:
- volume -- volume object used to determine the igroup name
-
- Returns:
- igroup_name -- name of igroup (for configuring targets &
- initiators)
- """
- # Use the connector's primary hostname and use that as the
- # name of the igroup. The name must follow syntax rules
- # required by the array: "must contain only alphanumeric
- # characters, dashes, and underscores. The first character
- # must be alphanumeric".
- #
- igroup_name = re.sub(r'[\W]', '_', connector['host'])
-
- # verify that the igroup has been created on the backend, and
- # if it doesn't exist, create it!
- #
- bn = "/vshare/config/igroup/%s" % igroup_name
- resp = self.vip.basic.get_node_values(bn)
-
- if not len(resp):
- self.vip.igroup.create_igroup(igroup_name)
-
- return igroup_name
-
- def _wait_for_export_state(self, volume_name, snapshot_name=None,
- state=False):
- """Polls backend to verify volume's export state.
-
- XG sets/queries following a request to create or delete a lun
- export may fail on the backend if vshared is still processing
- the export action (or times out). We can check whether it is
- done by polling the export binding for a lun to ensure it is
- created or deleted.
-
- This function will try to verify the creation or removal of
- export state on both gateway nodes of the array every 5
- seconds.
-
- Arguments:
- volume_name -- name of volume
- snapshot_name -- name of volume's snapshot
- state -- True to poll for existence, False for lack of
-
- Returns:
- True if the export state was correctly added or removed
- (depending on 'state' param)
- """
- if not snapshot_name:
- bn = "/vshare/state/local/container/%s/lun/%s/usn_id" \
- % (self.container, volume_name)
- else:
- bn = "/vshare/state/snapshot/container/%s/lun/%s/snap/%s/usn_id" \
- % (self.container, volume_name, snapshot_name)
-
- def _loop_func(state):
- status = [False, False]
- mg_conns = [self.mga, self.mgb]
-
- LOG.debug("Entering _wait_for_export_state loop: state=%s.",
- state)
-
- # TODO(rlucio): May need to handle situations where export
- # fails, i.e., HBAs go offline and the array is in
- # degraded mode.
- #
- for node_id in range(2):
- resp = mg_conns[node_id].basic.get_node_values(bn)
-
- if state:
- # Verify export was added. Validates when the usn_id is
- # altered to a non-default binding string.
- #
- if resp[bn] != "(not exported)":
- status[node_id] = True
- else:
- # Verify export was removed. Validates when the usn_id is
- # reset to the default binding string.
- #
- if resp[bn] == "(not exported)":
- status[node_id] = True
-
- if status[0] and status[1]:
- LOG.debug("_wait_for_export_state loopingcall complete.")
- raise loopingcall.LoopingCallDone(retvalue=True)
-
- timer = loopingcall.FixedIntervalLoopingCall(_loop_func, state)
- success = timer.start(interval=5).wait()
-
- return success
-
- def _is_supported_vmos_version(self, version_string):
- """Check that the array s/w version is supported. """
- for pattern in VMOS_SUPPORTED_VERSION_PATTERNS:
- if re.match(pattern, version_string):
- LOG.info(_LI("Verified VMOS version %s is supported."),
- version_string)
- return True
- return False
-
- def _fatal_error_code(self, response):
- """Raise an exception for certain errors in a XG response.
-
- Error codes are extracted from vdmd_mgmt.c.
-
- Arguments:
- response -- a response dict result from an XG request
- """
- # known non-fatal response codes:
- # 1024: 'lun deletion in progress, try again later'
- # 14032: 'lc_err_lock_busy'
-
- if response['code'] == 14000:
- # lc_generic_error
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14002:
- # lc_err_assertion_failed
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14004:
- # lc_err_not_found
- raise exception.ViolinBackendErrNotFound()
- elif response['code'] == 14005:
- # lc_err_exists
- raise exception.ViolinBackendErrExists()
- elif response['code'] == 14008:
- # lc_err_unexpected_arg
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14014:
- # lc_err_io_error
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14016:
- # lc_err_io_closed
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14017:
- # lc_err_io_timeout
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14021:
- # lc_err_unexpected_case
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14025:
- # lc_err_no_fs_space
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14035:
- # lc_err_range
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14036:
- # lc_err_invalid_param
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 14121:
- # lc_err_cancelled_err
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 512:
- # Not enough free space in container (vdmd bug)
- raise exception.ViolinBackendErr(message=response['message'])
- elif response['code'] == 1 and 'LUN ID conflict' \
- in response['message']:
- # lun id conflict while attempting to export
- raise exception.ViolinBackendErr(message=response['message'])
+++ /dev/null
-# 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.
-
-"""
-Violin Memory Fibre Channel Driver for OpenStack Cinder
-
-Provides fibre channel specific LUN services for V6000 series flash
-arrays.
-
-This driver requires VMOS v6.3.0.4 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 V6000
-Fibre Channel Driver along with the required flags:
-
-volume_driver=cinder.volume.drivers.violin.v6000_fcp.V6000FCDriver
-
-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 oslo_utils import units
-from six.moves import range
-
-from cinder import context
-from cinder import exception
-from cinder.i18n import _, _LE, _LI, _LW
-from cinder import utils
-from cinder.volume import driver
-from cinder.volume.drivers.san import san
-from cinder.volume.drivers.violin import v6000_common
-from cinder.zonemanager import utils as fczm_utils
-
-LOG = logging.getLogger(__name__)
-
-
-class V6000FCDriver(driver.FibreChannelDriver):
- """Executes commands relating to fibre channel based Violin Memory Arrays.
-
- Version history:
- 1.0 - Initial driver
- 1.0.1 - Fixes polling for export completion
- """
-
- VERSION = '1.0.1'
-
- def __init__(self, *args, **kwargs):
- super(V6000FCDriver, self).__init__(*args, **kwargs)
- self.gateway_fc_wwns = []
- self.stats = {}
- self.configuration.append_config_values(v6000_common.violin_opts)
- self.configuration.append_config_values(san.san_opts)
- self.common = v6000_common.V6000Common(self.configuration)
- self.lookup_service = fczm_utils.create_lookup_service()
-
- LOG.info(_LI("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(V6000FCDriver, self).do_setup(context)
- self.common.do_setup(context)
- self.gateway_fc_wwns = self._get_active_fc_targets()
-
- 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 delete_volume(self, volume):
- """Deletes a volume."""
- self.common._delete_lun(volume)
-
- def extend_volume(self, volume, new_size):
- """Deletes a volume."""
- self.common._extend_lun(volume, new_size)
-
- def create_snapshot(self, snapshot):
- """Creates a snapshot from an existing volume."""
- self.common._create_lun_snapshot(snapshot)
-
- def delete_snapshot(self, snapshot):
- """Deletes a snapshot."""
- self.common._delete_lun_snapshot(snapshot)
-
- def create_volume_from_snapshot(self, volume, snapshot):
- """Creates a volume from a snapshot."""
- ctxt = context.get_admin_context()
- snapshot['size'] = snapshot['volume']['size']
- self.common._create_lun(volume)
- self.copy_volume_data(ctxt, snapshot, volume)
-
- def create_cloned_volume(self, volume, src_vref):
- """Creates a full clone of the specified volume."""
- ctxt = context.get_admin_context()
- self.common._create_lun(volume)
- self.copy_volume_data(ctxt, src_vref, volume)
-
- 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.AddFCZone
- def initialize_connection(self, volume, connector):
- """Initializes the connection (target<-->initiator)."""
- igroup = None
-
- if self.configuration.use_igroups:
- #
- # Most drivers don't use igroups, because there are a
- # number of issues with multipathing and iscsi/fcp where
- # lun devices either aren't cleaned up properly or are
- # stale (from previous scans).
- #
- # If the customer really wants igroups for whatever
- # reason, we create a new igroup for each host/hypervisor.
- # Every lun that is exported to the particular
- # hypervisor/host will be contained in this igroup. This
- # should prevent other hosts from seeing luns they aren't
- # using when they perform scans.
- #
- igroup = self.common._get_igroup(volume, connector)
- self._add_igroup_member(connector, igroup)
-
- if hasattr(volume, 'volume_id'):
- lun_id = self._export_snapshot(volume, connector, igroup)
- else:
- lun_id = self._export_lun(volume, connector, igroup)
-
- self.common.vip.basic.save_config()
-
- 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.RemoveFCZone
- def terminate_connection(self, volume, connector, force=False, **kwargs):
- """Terminates the connection (target<-->initiator)."""
-
- if hasattr(volume, 'volume_id'):
- self._unexport_snapshot(volume)
- else:
- self._unexport_lun(volume)
-
- self.common.vip.basic.save_config()
-
- 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 or not self.stats:
- self._update_stats()
- return self.stats
-
- @utils.synchronized('vmem-export')
- def _export_lun(self, volume, connector=None, igroup=None):
- """Generates the export configuration for the given volume.
-
- The equivalent CLI command is "lun export container
- <container_name> name <lun_name>"
-
- Arguments:
- volume -- volume object provided by the Manager
- connector -- connector object provided by the Manager
- igroup -- name of igroup to use for exporting
-
- Returns:
- lun_id -- the LUN ID assigned by the backend
- """
- lun_id = -1
- export_to = ''
- v = self.common.vip
-
- if igroup:
- export_to = igroup
- elif connector:
- export_to = self._convert_wwns_openstack_to_vmem(
- connector['wwpns'])
- else:
- raise exception.Error(_("No initiators found, cannot proceed"))
-
- LOG.debug("Exporting lun %s.", volume['id'])
-
- try:
- self.common._send_cmd_and_verify(
- v.lun.export_lun, self.common._wait_for_export_state, '',
- [self.common.container, volume['id'], 'all', export_to,
- 'auto'], [volume['id'], None, True])
-
- except Exception:
- LOG.exception(_LE("LUN export for %s failed!"), volume['id'])
- raise
-
- lun_id = self.common._get_lun_id(volume['id'])
-
- return lun_id
-
- @utils.synchronized('vmem-export')
- def _unexport_lun(self, volume):
- """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.vip
-
- LOG.debug("Unexporting lun %s.", volume['id'])
-
- try:
- self.common._send_cmd_and_verify(
- v.lun.unexport_lun, self.common._wait_for_export_state, '',
- [self.common.container, volume['id'], 'all', 'all', 'auto'],
- [volume['id'], None, False])
-
- except exception.ViolinBackendErrNotFound:
- LOG.debug("Lun %s already unexported, continuing.", volume['id'])
-
- except Exception:
- LOG.exception(_LE("LUN unexport for %s failed!"), volume['id'])
- raise
-
- @utils.synchronized('vmem-export')
- def _export_snapshot(self, snapshot, connector=None, igroup=None):
- """Generates the export configuration for the given snapshot.
-
- The equivalent CLI command is "snapshot export container
- PROD08 lun <snapshot_name> name <volume_name>"
-
- Arguments:
- snapshot -- snapshot object provided by the Manager
- connector -- connector object provided by the Manager
- igroup -- name of igroup to use for exporting
-
- Returns:
- lun_id -- the LUN ID assigned by the backend
- """
- lun_id = -1
- export_to = ''
- v = self.common.vip
-
- if igroup:
- export_to = igroup
- elif connector:
- export_to = self._convert_wwns_openstack_to_vmem(
- connector['wwpns'])
- else:
- raise exception.Error(_("No initiators found, cannot proceed"))
-
- LOG.debug("Exporting snapshot %s.", snapshot['id'])
-
- try:
- self.common._send_cmd(v.snapshot.export_lun_snapshot, '',
- self.common.container, snapshot['volume_id'],
- snapshot['id'], export_to, 'all', 'auto')
-
- except Exception:
- LOG.exception(_LE("Snapshot export for %s failed!"),
- snapshot['id'])
- raise
-
- else:
- self.common._wait_for_export_state(snapshot['volume_id'],
- snapshot['id'], state=True)
- lun_id = self.common._get_snapshot_id(snapshot['volume_id'],
- snapshot['id'])
-
- return lun_id
-
- @utils.synchronized('vmem-export')
- def _unexport_snapshot(self, snapshot):
- """Removes the export configuration for the given snapshot.
-
- The equivalent CLI command is "no snapshot export container
- PROD08 lun <snapshot_name> name <volume_name>"
-
- Arguments:
- snapshot -- snapshot object provided by the Manager
- """
- v = self.common.vip
-
- LOG.debug("Unexporting snapshot %s.", snapshot['id'])
-
- try:
- self.common._send_cmd(v.snapshot.unexport_lun_snapshot, '',
- self.common.container, snapshot['volume_id'],
- snapshot['id'], 'all', 'all', 'auto', False)
-
- except Exception:
- LOG.exception(_LE("Snapshot unexport for %s failed!"),
- snapshot['id'])
- raise
-
- else:
- self.common._wait_for_export_state(snapshot['volume_id'],
- snapshot['id'], state=False)
-
- def _add_igroup_member(self, connector, igroup):
- """Add an initiator to the openstack igroup so it can see exports.
-
- The equivalent CLI command is "igroup addto name <igroup_name>
- initiators <initiator_name>"
-
- Arguments:
- connector -- connector object provided by the Manager
- """
- v = self.common.vip
- wwpns = self._convert_wwns_openstack_to_vmem(connector['wwpns'])
-
- LOG.debug("Adding initiators %(wwpns)s to igroup %(igroup)s.",
- {'wwpns': wwpns, 'igroup': igroup})
-
- resp = v.igroup.add_initiators(igroup, wwpns)
-
- if resp['code'] != 0:
- raise exception.Error(
- _('Failed to add igroup member: %(code)d, %(message)s') % resp)
-
- 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 array to see if any initiator wwns still have active sessions.
-
- We only need to check to see if any one initiator wwn is
- connected, since all initiators are connected to all targets
- on a lun export for fibrechannel.
- """
- v = self.common.vip
- initiator_wwns = self._convert_wwns_openstack_to_vmem(
- connector['wwpns'])
-
- bn = "/vshare/config/export/container/%s/lun/**" \
- % self.common.container
- global_export_config = v.basic.get_node_values(bn)
-
- for node in global_export_config:
- if node.endswith(initiator_wwns[0]):
- return True
- return False
-
- def _update_stats(self):
- """Update array stats.
-
- Gathers array stats from the backend and converts them to GB values.
- """
- data = {}
- total_gb = 0
- free_gb = 0
- v = self.common.vip
-
- master_cluster_id = list(v.basic.get_node_values(
- '/cluster/state/master_id').values())[0]
-
- bn1 = "/vshare/state/global/%s/container/%s/total_bytes" \
- % (master_cluster_id, self.common.container)
- bn2 = "/vshare/state/global/%s/container/%s/free_bytes" \
- % (master_cluster_id, self.common.container)
- resp = v.basic.get_node_values([bn1, bn2])
-
- if bn1 in resp:
- total_gb = resp[bn1] // units.Gi
- else:
- LOG.warning(_LW("Failed to receive update for total_gb stat!"))
- if 'total_capacity_gb' in self.stats:
- total_gb = self.stats['total_capacity_gb']
-
- if bn2 in resp:
- free_gb = resp[bn2] // units.Gi
- else:
- LOG.warning(_LW("Failed to receive update for free_gb stat!"))
- if 'free_capacity_gb' in self.stats:
- free_gb = self.stats['free_capacity_gb']
-
- 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.
-
- Arguments:
- mg_conn -- active XG connection to one of the gateways
-
- Returns:
- active_gw_fcp_wwns -- list of WWNs
- """
- v = self.common.vip
- active_gw_fcp_wwns = []
-
- gateway_ids = v.basic.get_node_values(
- '/vshare/state/global/*').values()
-
- for i in gateway_ids:
- bn = "/vshare/state/global/%d/target/fc/**" % i
- resp = v.basic.get_node_values(bn)
-
- for node in resp:
- if node.endswith('/wwn'):
- active_gw_fcp_wwns.append(resp[node])
-
- return self._convert_wwns_vmem_to_openstack(active_gw_fcp_wwns)
-
- def _convert_wwns_openstack_to_vmem(self, wwns):
- """Convert a list of OpenStack WWNs to VMEM compatible WWN strings.
-
- Input format is '50014380186b3f65', output format is
- 'wwn.50:01:43:80:18:6b:3f:65'.
-
- Arguments:
- wwns -- list of OpenStack-based WWN strings.
-
- Returns:
- output -- list of VMEM-based WWN strings.
- """
- output = []
- for w in wwns:
- output.append('wwn.{0}'.format(
- ':'.join(w[x:x + 2] for x in range(0, len(w), 2))))
- return output
-
- def _convert_wwns_vmem_to_openstack(self, wwns):
- """Convert a list of VMEM WWNs to OpenStack compatible WWN strings.
-
- Input format is 'wwn.50:01:43:80:18:6b:3f:65', output format
- is '50014380186b3f65'.
-
- Arguments:
- wwns -- list of VMEM-based WWN strings.
-
- Returns:
- output -- list of OpenStack-based WWN strings.
- """
- output = []
- for w in wwns:
- output.append(''.join(w[4:].split(':')))
- return output
+++ /dev/null
-# Copyright 2013 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 Memory iSCSI Driver for OpenStack Cinder
-
-Provides iSCSI specific LUN services for V6000 series flash arrays.
-
-This driver requires VMOS v6.3.0.4 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 V6000
-ISCSI Driver along with the required flags:
-
-volume_driver=cinder.volume.drivers.violin.v6000_iscsi.V6000ISCSIDriver
-
-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.
-"""
-
-import random
-
-from oslo_log import log as logging
-from oslo_service import loopingcall
-from oslo_utils import units
-
-from cinder import context
-from cinder import exception
-from cinder.i18n import _, _LE, _LI, _LW
-from cinder import utils
-from cinder.volume import driver
-from cinder.volume.drivers.san import san
-from cinder.volume.drivers.violin import v6000_common
-
-LOG = logging.getLogger(__name__)
-
-
-class V6000ISCSIDriver(driver.ISCSIDriver):
- """Executes commands relating to iSCSI-based Violin Memory Arrays.
-
- Version history:
- 1.0 - Initial driver
- 1.0.1 - Fixes polling for export completion
- """
-
- VERSION = '1.0.1'
- TARGET_GROUP_NAME = 'openstack'
-
- def __init__(self, *args, **kwargs):
- super(V6000ISCSIDriver, self).__init__(*args, **kwargs)
- self.array_info = []
- self.gateway_iscsi_ip_addresses_mga = []
- self.gateway_iscsi_ip_addresses_mgb = []
- self.stats = {}
- self.configuration.append_config_values(v6000_common.violin_opts)
- self.configuration.append_config_values(san.san_opts)
- self.common = v6000_common.V6000Common(self.configuration)
-
- LOG.info(_LI("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(V6000ISCSIDriver, self).do_setup(context)
- self.common.do_setup(context)
-
- self.gateway_iscsi_ip_addresses_mga = self._get_active_iscsi_ips(
- self.common.mga)
- for ip in self.gateway_iscsi_ip_addresses_mga:
- self.array_info.append({"node": self._get_hostname('mga'),
- "addr": ip,
- "conn": self.common.mga})
- self.gateway_iscsi_ip_addresses_mgb = self._get_active_iscsi_ips(
- self.common.mgb)
- for ip in self.gateway_iscsi_ip_addresses_mgb:
- self.array_info.append({"node": self._get_hostname('mgb'),
- "addr": ip,
- "conn": self.common.mgb})
-
- # setup global target group for exports to use
- self._create_iscsi_target_group()
-
- def check_for_setup_error(self):
- """Returns an error if prerequisites aren't met."""
- self.common.check_for_setup_error()
-
- bn = "/vshare/config/iscsi/enable"
- resp = self.common.vip.basic.get_node_values(bn)
- if resp[bn] is not True:
- raise exception.ViolinInvalidBackendConfig(
- reason=_('iSCSI is not enabled'))
- if len(self.gateway_iscsi_ip_addresses_mga) == 0:
- raise exception.ViolinInvalidBackendConfig(
- reason=_('no available iSCSI IPs on mga'))
- if len(self.gateway_iscsi_ip_addresses_mgb) == 0:
- raise exception.ViolinInvalidBackendConfig(
- reason=_('no available iSCSI IPs on mgb'))
-
- def create_volume(self, volume):
- """Creates a volume."""
- self.common._create_lun(volume)
-
- def delete_volume(self, volume):
- """Deletes a volume."""
- self.common._delete_lun(volume)
-
- def extend_volume(self, volume, new_size):
- """Deletes a volume."""
- self.common._extend_lun(volume, new_size)
-
- def create_snapshot(self, snapshot):
- """Creates a snapshot from an existing volume."""
- self.common._create_lun_snapshot(snapshot)
-
- def delete_snapshot(self, snapshot):
- """Deletes a snapshot."""
- self.common._delete_lun_snapshot(snapshot)
-
- def create_volume_from_snapshot(self, volume, snapshot):
- """Creates a volume from a snapshot."""
- ctxt = context.get_admin_context()
- snapshot['size'] = snapshot['volume']['size']
- self.common._create_lun(volume)
- self.copy_volume_data(ctxt, snapshot, volume)
-
- def create_cloned_volume(self, volume, src_vref):
- """Creates a full clone of the specified volume."""
- ctxt = context.get_admin_context()
- self.common._create_lun(volume)
- self.copy_volume_data(ctxt, src_vref, volume)
-
- 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):
- """Initializes the connection (target<-->initiator)."""
- igroup = None
-
- if self.configuration.use_igroups:
- #
- # Most drivers don't use igroups, because there are a
- # number of issues with multipathing and iscsi/fcp where
- # lun devices either aren't cleaned up properly or are
- # stale (from previous scans).
- #
-
- # If the customer really wants igroups for whatever
- # reason, we create a new igroup for each host/hypervisor.
- # Every lun that is exported to the particular
- # hypervisor/host will be contained in this igroup. This
- # should prevent other hosts from seeing luns they aren't
- # using when they perform scans.
- #
- igroup = self.common._get_igroup(volume, connector)
- self._add_igroup_member(connector, igroup)
-
- tgt = self._get_iscsi_target()
- target_name = self.TARGET_GROUP_NAME
-
- if hasattr(volume, 'volume_id'):
- lun = self._export_snapshot(volume, connector, igroup)
- else:
- lun = self._export_lun(volume, connector, igroup)
-
- iqn = "%s%s:%s" % (self.configuration.iscsi_target_prefix,
- tgt['node'], target_name)
- self.common.vip.basic.save_config()
-
- properties = {}
- properties['target_discovered'] = False
- properties['target_portal'] = '%s:%d' \
- % (tgt['addr'], self.configuration.iscsi_port)
- properties['target_iqn'] = iqn
- properties['target_lun'] = lun
- properties['volume_id'] = volume['id']
- properties['auth_method'] = 'CHAP'
- properties['auth_username'] = ''
- properties['auth_password'] = ''
-
- return {'driver_volume_type': 'iscsi', 'data': properties}
-
- def terminate_connection(self, volume, connector, force=False, **kwargs):
- """Terminates the connection (target<-->initiator)."""
- if hasattr(volume, 'volume_id'):
- self._unexport_snapshot(volume)
- else:
- self._unexport_lun(volume)
- self.common.vip.basic.save_config()
-
- def get_volume_stats(self, refresh=False):
- """Get volume stats."""
- if refresh or not self.stats:
- self._update_stats()
- return self.stats
-
- def _create_iscsi_target_group(self):
- """Creates a new target for use in exporting a lun.
-
- Create an HA target on the backend that will be used for all
- lun exports made via this driver.
-
- The equivalent CLI commands are "iscsi target create
- <target_name>" and "iscsi target bind <target_name> to
- <ip_of_mg_eth_intf>".
- """
- v = self.common.vip
- target_name = self.TARGET_GROUP_NAME
-
- bn = "/vshare/config/iscsi/target/%s" % target_name
- resp = self.common.vip.basic.get_node_values(bn)
-
- if resp:
- LOG.debug("iscsi target group %s already exists.", target_name)
- return
-
- LOG.debug("Creating iscsi target %s.", target_name)
-
- try:
- self.common._send_cmd_and_verify(v.iscsi.create_iscsi_target,
- self._wait_for_target_state,
- '', [target_name], [target_name])
-
- except Exception:
- LOG.exception(_LE("Failed to create iscsi target!"))
- raise
-
- try:
- self.common._send_cmd(self.common.mga.iscsi.bind_ip_to_target,
- '', target_name,
- self.gateway_iscsi_ip_addresses_mga)
- self.common._send_cmd(self.common.mgb.iscsi.bind_ip_to_target,
- '', target_name,
- self.gateway_iscsi_ip_addresses_mgb)
-
- except Exception:
- LOG.exception(_LE("Failed to bind iSCSI targets!"))
- raise
-
- def _get_iscsi_target(self):
- """Get a random target IP for OpenStack to connect to.
-
- For the non-multipath case we pick a single random target for
- the OpenStack infrastructure to use. This at least allows us
- to evenly distribute LUN connections across the storage
- cluster.
- """
- return self.array_info[random.randint(0, len(self.array_info) - 1)]
-
- @utils.synchronized('vmem-export')
- def _export_lun(self, volume, connector=None, igroup=None):
- """Generates the export configuration for the given volume.
-
- The equivalent CLI command is "lun export container
- <container_name> name <lun_name>"
-
- Arguments:
- volume -- volume object provided by the Manager
- connector -- connector object provided by the Manager
- igroup -- name of igroup to use for exporting
-
- Returns:
- lun_id -- the LUN ID assigned by the backend
- """
- lun_id = -1
- export_to = ''
- v = self.common.vip
-
- if igroup:
- export_to = igroup
- elif connector:
- export_to = connector['initiator']
- else:
- raise exception.Error(_("No initiators found, cannot proceed"))
-
- target_name = self.TARGET_GROUP_NAME
-
- LOG.debug("Exporting lun %s.", volume['id'])
-
- try:
- self.common._send_cmd_and_verify(
- v.lun.export_lun, self.common._wait_for_export_state, '',
- [self.common.container, volume['id'], target_name,
- export_to, 'auto'], [volume['id'], None, True])
-
- except Exception:
- LOG.exception(_LE("LUN export for %s failed!"), volume['id'])
- raise
-
- lun_id = self.common._get_lun_id(volume['id'])
-
- return lun_id
-
- @utils.synchronized('vmem-export')
- def _unexport_lun(self, volume):
- """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.vip
-
- LOG.debug("Unexporting lun %s.", volume['id'])
-
- try:
- self.common._send_cmd_and_verify(
- v.lun.unexport_lun, self.common._wait_for_export_state, '',
- [self.common.container, volume['id'], 'all', 'all', 'auto'],
- [volume['id'], None, False])
-
- except exception.ViolinBackendErrNotFound:
- LOG.debug("Lun %s already unexported, continuing.", volume['id'])
-
- except Exception:
- LOG.exception(_LE("LUN unexport for %s failed!"), volume['id'])
- raise
-
- @utils.synchronized('vmem-export')
- def _export_snapshot(self, snapshot, connector=None, igroup=None):
- """Generates the export configuration for the given snapshot.
-
- The equivalent CLI command is "snapshot export container
- PROD08 lun <snapshot_name> name <volume_name>"
-
- Arguments:
- snapshot -- snapshot object provided by the Manager
- connector -- connector object provided by the Manager
- igroup -- name of igroup to use for exporting
-
- Returns:
- lun_id -- the LUN ID assigned by the backend
- """
- lun_id = -1
- export_to = ''
- v = self.common.vip
-
- target_name = self.TARGET_GROUP_NAME
-
- LOG.debug("Exporting snapshot %s.", snapshot['id'])
-
- if igroup:
- export_to = igroup
- elif connector:
- export_to = connector['initiator']
- else:
- raise exception.Error(_("No initiators found, cannot proceed"))
-
- try:
- self.common._send_cmd(v.snapshot.export_lun_snapshot, '',
- self.common.container, snapshot['volume_id'],
- snapshot['id'], export_to, target_name,
- 'auto')
-
- except Exception:
- LOG.exception(_LE("Snapshot export for %s failed!"),
- snapshot['id'])
- raise
-
- else:
- self.common._wait_for_export_state(snapshot['volume_id'],
- snapshot['id'], state=True)
- lun_id = self.common._get_snapshot_id(snapshot['volume_id'],
- snapshot['id'])
-
- return lun_id
-
- @utils.synchronized('vmem-export')
- def _unexport_snapshot(self, snapshot):
- """Removes the export configuration for the given snapshot.
-
- The equivalent CLI command is "no snapshot export container
- PROD08 lun <snapshot_name> name <volume_name>"
-
- Arguments:
- snapshot -- snapshot object provided by the Manager
- """
- v = self.common.vip
-
- LOG.debug("Unexporting snapshot %s.", snapshot['id'])
-
- try:
- self.common._send_cmd(v.snapshot.unexport_lun_snapshot, '',
- self.common.container, snapshot['volume_id'],
- snapshot['id'], 'all', 'all', 'auto', False)
-
- except Exception:
- LOG.exception(_LE("Snapshot unexport for %s failed!"),
- snapshot['id'])
- raise
-
- else:
- self.common._wait_for_export_state(snapshot['volume_id'],
- snapshot['id'], state=False)
-
- def _add_igroup_member(self, connector, igroup):
- """Add an initiator to an igroup so it can see exports.
-
- The equivalent CLI command is "igroup addto name <igroup_name>
- initiators <initiator_name>"
-
- Arguments:
- connector -- connector object provided by the Manager
- """
- v = self.common.vip
-
- LOG.debug("Adding initiator %s to igroup.", connector['initiator'])
-
- resp = v.igroup.add_initiators(igroup, connector['initiator'])
-
- if resp['code'] != 0:
- raise exception.Error(
- _('Failed to add igroup member: %(code)d, %(message)s') % resp)
-
- def _update_stats(self):
- """Update array stats.
-
- Gathers array stats from the backend and converts them to GB values.
- """
- data = {}
- total_gb = 0
- free_gb = 0
- v = self.common.vip
-
- master_cluster_id = list(v.basic.get_node_values(
- '/cluster/state/master_id').values())[0]
-
- bn1 = "/vshare/state/global/%s/container/%s/total_bytes" \
- % (master_cluster_id, self.common.container)
- bn2 = "/vshare/state/global/%s/container/%s/free_bytes" \
- % (master_cluster_id, self.common.container)
- resp = v.basic.get_node_values([bn1, bn2])
-
- if bn1 in resp:
- total_gb = resp[bn1] // units.Gi
- else:
- LOG.warning(_LW("Failed to receive update for total_gb stat!"))
- if 'total_capacity_gb' in self.stats:
- total_gb = self.stats['total_capacity_gb']
-
- if bn2 in resp:
- free_gb = resp[bn2] // units.Gi
- else:
- LOG.warning(_LW("Failed to receive update for free_gb stat!"))
- if 'free_capacity_gb' in self.stats:
- free_gb = self.stats['free_capacity_gb']
-
- 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'] = 'iSCSI'
- 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_short_name(self, volume_name):
- """Creates a vSHARE-compatible iSCSI target name.
-
- The Folsom-style volume names are prefix(7) + uuid(36), which
- is too long for vSHARE for target names. To keep things
- simple we can just truncate the name to 32 chars.
-
- Arguments:
- volume_name -- name of volume/lun
-
- Returns:
- Shortened volume name as a string.
- """
- return volume_name[:32]
-
- def _get_active_iscsi_ips(self, mg_conn):
- """Get a list of gateway IP addresses that can be used for iSCSI.
-
- Arguments:
- mg_conn -- active XG connection to one of the gateways
-
- Returns:
- active_gw_iscsi_ips -- list of IP addresses
- """
- active_gw_iscsi_ips = []
- interfaces_to_skip = ['lo', 'vlan10', 'eth1', 'eth2', 'eth3']
-
- bn = "/net/interface/config/*"
- intf_list = mg_conn.basic.get_node_values(bn)
-
- for i in intf_list:
- if intf_list[i] in interfaces_to_skip:
- continue
-
- bn1 = "/net/interface/state/%s/addr/ipv4/1/ip" % intf_list[i]
- bn2 = "/net/interface/state/%s/flags/link_up" % intf_list[i]
- resp = mg_conn.basic.get_node_values([bn1, bn2])
-
- if len(resp.keys()) == 2 and resp[bn2] is True:
- active_gw_iscsi_ips.append(resp[bn1])
-
- return active_gw_iscsi_ips
-
- def _get_hostname(self, mg_to_query=None):
- """Get the hostname of one of the mgs (hostname is used in IQN).
-
- If the remote query fails then fall back to using the hostname
- provided in the cinder configuration file.
-
- Arguments:
- mg_to_query -- name of gateway to query 'mga' or 'mgb'
-
- Returns: hostname -- hostname as a string
- """
- hostname = self.configuration.san_ip
- conn = self.common.vip
-
- if mg_to_query == "mga":
- hostname = self.configuration.gateway_mga
- conn = self.common.mga
- elif mg_to_query == "mgb":
- hostname = self.configuration.gateway_mgb
- conn = self.common.mgb
-
- ret_dict = conn.basic.get_node_values("/system/hostname")
- if ret_dict:
- hostname = list(ret_dict.items())[0][1]
- else:
- LOG.debug("Unable to fetch gateway hostname for %s.", mg_to_query)
-
- return hostname
-
- def _wait_for_target_state(self, target_name):
- """Polls backend to verify an iscsi target configuration.
-
- This function will try to verify the creation of an iscsi
- target on both gateway nodes of the array every 5 seconds.
-
- Arguments:
- target_name -- name of iscsi target to be polled
-
- Returns:
- True if the target state was correctly added
- """
- bn = "/vshare/state/local/target/iscsi/%s" % (target_name)
-
- def _loop_func():
- status = [False, False]
- mg_conns = [self.common.mga, self.common.mgb]
-
- LOG.debug("Entering _wait_for_target_state loop: target=%s.",
- target_name)
-
- for node_id in range(2):
- resp = mg_conns[node_id].basic.get_node_values(bn)
- if len(resp.keys()):
- status[node_id] = True
-
- if status[0] and status[1]:
- raise loopingcall.LoopingCallDone(retvalue=True)
-
- timer = loopingcall.FixedIntervalLoopingCall(_loop_func)
- success = timer.start(interval=5).wait()
-
- return success
--- /dev/null
+---
+upgrade:
+ - Violin Memory 6000 array series drivers are removed.