diff --git a/cinder/exception.py b/cinder/exception.py index 33bddc3b188..e6cdbe6c516 100644 --- a/cinder/exception.py +++ b/cinder/exception.py @@ -1338,11 +1338,6 @@ class HNASConnError(VolumeDriverException): message = "%(message)s" -# Coho drivers -class CohoException(VolumeDriverException): - message = _("Coho Data Cinder driver failure: %(message)s") - - # Tegile Storage drivers class TegileAPIException(VolumeBackendAPIException): message = _("Unexpected response from Tegile IntelliFlash API") diff --git a/cinder/opts.py b/cinder/opts.py index fad6c4d67d0..3bfbdb29587 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -72,7 +72,6 @@ from cinder.volume import api as cinder_volume_api from cinder.volume import driver as cinder_volume_driver from cinder.volume.drivers import blockbridge as \ cinder_volume_drivers_blockbridge -from cinder.volume.drivers import coho as cinder_volume_drivers_coho from cinder.volume.drivers.coprhd import common as \ cinder_volume_drivers_coprhd_common from cinder.volume.drivers.coprhd import scaleio as \ @@ -299,7 +298,6 @@ def list_opts(): cinder_volume_driver.volume_opts, cinder_volume_driver.iser_opts, cinder_volume_drivers_blockbridge.blockbridge_opts, - cinder_volume_drivers_coho.coho_opts, cinder_volume_drivers_coprhd_common.volume_opts, cinder_volume_drivers_coprhd_scaleio.scaleio_opts, cinder_volume_drivers_datera_dateraiscsi.d_opts, diff --git a/cinder/tests/unit/volume/drivers/test_coho.py b/cinder/tests/unit/volume/drivers/test_coho.py deleted file mode 100644 index 4945f2b74c1..00000000000 --- a/cinder/tests/unit/volume/drivers/test_coho.py +++ /dev/null @@ -1,560 +0,0 @@ -# Copyright (c) 2015 Coho Data, 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. -# - -import binascii -import errno -import mock -import os -import six -import socket -import xdrlib - -from cinder import context -from cinder import exception -from cinder import test -from cinder.tests.unit import fake_volume -from cinder.volume import configuration as conf -from cinder.volume.drivers import coho -from cinder.volume.drivers import nfs -from cinder.volume.drivers import remotefs -from cinder.volume import qos_specs -from cinder.volume import volume_types - -ADDR = 'coho-datastream-addr' -PATH = '/test/path' -RPC_PORT = 2049 -LOCAL_PATH = '/opt/cinder/mnt/test/path' - -VOLUME = { - 'name': 'volume-bcc48c61-9691-4e5f-897c-793686093190', - 'volume_id': 'bcc48c61-9691-4e5f-897c-793686093190', - 'size': 128, - 'volume_type': 'silver', - 'volume_type_id': 'deadbeef-aaaa-bbbb-cccc-deadbeefbeef', - 'metadata': [{'key': 'type', - 'service_label': 'silver'}], - 'provider_location': 'coho-datastream-addr:/test/path', - 'id': 'bcc48c61-9691-4e5f-897c-793686093190', - 'status': 'available', -} - -CLONE_VOL = VOLUME.copy() -CLONE_VOL['size'] = 256 - -SNAPSHOT = { - 'name': 'snapshot-51dd4-8d8a-4aa9-9176-086c9d89e7fc', - 'id': '51dd4-8d8a-4aa9-9176-086c9d89e7fc', - 'size': 128, - 'volume_type': None, - 'provider_location': None, - 'volume_size': 128, - 'volume_name': 'volume-bcc48c61-9691-4e5f-897c-793686093190', - 'volume_id': 'bcc48c61-9691-4e5f-897c-793686093191', -} - -VOLUME_TYPE = { - 'name': 'sf-1', - 'qos_specs_id': 'qos-spec-id', - 'deleted': False, - 'created_at': '2016-06-06 04:58:11', - 'updated_at': None, - 'extra_specs': {}, - 'deleted_at': None, - 'id': 'deadbeef-aaaa-bbbb-cccc-deadbeefbeef' -} - -QOS_SPEC = { - 'id': 'qos-spec-id', - 'specs': { - 'maxIOPS': '2000', - 'maxMBS': '500' - } -} - -QOS = { - 'uuid': 'qos-spec-id', - 'maxIOPS': 2000, - 'maxMBS': 500 -} - -INVALID_SNAPSHOT = SNAPSHOT.copy() -INVALID_SNAPSHOT['name'] = '' - -INVALID_HEADER_BIN = binascii.unhexlify('800000') -NO_REPLY_BIN = binascii.unhexlify( - 'aaaaa01000000010000000000000000000000003') -MSG_DENIED_BIN = binascii.unhexlify( - '00000a010000000110000000000000000000000000000003') -PROC_UNAVAIL_BIN = binascii.unhexlify( - '00000a010000000100000000000000000000000000000003') -PROG_UNAVAIL_BIN = binascii.unhexlify( - '000003c70000000100000000000000000000000000000001') -PROG_MISMATCH_BIN = binascii.unhexlify( - '00000f7700000001000000000000000000000000000000020000000100000001') -GARBAGE_ARGS_BIN = binascii.unhexlify( - '00000d6e0000000100000000000000000000000000000004') - - -class CohoDriverTest(test.TestCase): - """Test Coho Data's NFS volume driver.""" - - def __init__(self, *args, **kwargs): - super(CohoDriverTest, self).__init__(*args, **kwargs) - - def setUp(self): - super(CohoDriverTest, self).setUp() - - self.context = mock.Mock() - self.configuration = mock.Mock(spec=conf.Configuration) - self.configuration.max_over_subscription_ratio = 20.0 - self.configuration.reserved_percentage = 0 - self.configuration.volume_backend_name = 'coho-1' - self.configuration.coho_rpc_port = 2049 - self.configuration.nfs_shares_config = '/etc/cinder/coho_shares' - self.configuration.nfs_sparsed_volumes = True - self.configuration.nfs_mount_point_base = '/opt/stack/cinder/mnt' - self.configuration.nfs_mount_options = None - self.configuration.nas_host = None - self.configuration.nas_share_path = None - self.configuration.nas_mount_options = None - - def test_setup_failure_when_rpc_port_unconfigured(self): - self.configuration.coho_rpc_port = None - drv = coho.CohoDriver(configuration=self.configuration) - - self.mock_object(coho, 'LOG') - self.mock_object(nfs.NfsDriver, 'do_setup') - - with self.assertRaisesRegex(exception.CohoException, - ".*Coho rpc port is not configured.*"): - drv.do_setup(self.context) - - self.assertTrue(coho.LOG.warning.called) - self.assertTrue(nfs.NfsDriver.do_setup.called) - - def test_setup_failure_when_coho_rpc_port_is_invalid(self): - self.configuration.coho_rpc_port = 99999 - drv = coho.CohoDriver(configuration=self.configuration) - - self.mock_object(coho, 'LOG') - self.mock_object(nfs.NfsDriver, 'do_setup') - - with self.assertRaisesRegex(exception.CohoException, - "Invalid port number.*"): - drv.do_setup(self.context) - - self.assertTrue(coho.LOG.warning.called) - self.assertTrue(nfs.NfsDriver.do_setup.called) - - def test_create_volume_with_qos(self): - drv = coho.CohoDriver(configuration=self.configuration) - - volume = fake_volume.fake_volume_obj(self.context, - **{'volume_type_id': - VOLUME['volume_type_id'], - 'provider_location': - VOLUME['provider_location']}) - mock_remotefs_create = self.mock_object(remotefs.RemoteFSDriver, - 'create_volume') - mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') - mock_get_volume_type = self.mock_object(volume_types, - 'get_volume_type') - mock_get_volume_type.return_value = VOLUME_TYPE - mock_get_qos_specs = self.mock_object(qos_specs, 'get_qos_specs') - mock_get_qos_specs.return_value = QOS_SPEC - mock_get_admin_context = self.mock_object(context, 'get_admin_context') - mock_get_admin_context.return_value = 'test' - - drv.create_volume(volume) - - self.assertTrue(mock_remotefs_create.called) - self.assertTrue(mock_get_admin_context.called) - mock_remotefs_create.assert_has_calls([mock.call(volume)]) - mock_get_volume_type.assert_has_calls( - [mock.call('test', volume.volume_type_id)]) - mock_get_qos_specs.assert_has_calls( - [mock.call('test', QOS_SPEC['id'])]) - mock_rpc_client.assert_has_calls( - [mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().set_qos_policy(os.path.join(PATH, volume.name), - QOS)]) - - def test_create_snapshot(self): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') - mock_get_volume_location = self.mock_object(coho.CohoDriver, - '_get_volume_location') - mock_get_volume_location.return_value = ADDR, PATH - - drv.create_snapshot(SNAPSHOT) - - mock_get_volume_location.assert_has_calls( - [mock.call(SNAPSHOT['volume_id'])]) - mock_rpc_client.assert_has_calls( - [mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().create_snapshot( - os.path.join(PATH, SNAPSHOT['volume_name']), - SNAPSHOT['name'], 0)]) - - def test_delete_snapshot(self): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') - mock_get_volume_location = self.mock_object(coho.CohoDriver, - '_get_volume_location') - mock_get_volume_location.return_value = ADDR, PATH - - drv.delete_snapshot(SNAPSHOT) - - mock_get_volume_location.assert_has_calls( - [mock.call(SNAPSHOT['volume_id'])]) - mock_rpc_client.assert_has_calls( - [mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().delete_snapshot(SNAPSHOT['name'])]) - - def test_create_volume_from_snapshot(self): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') - mock_find_share = self.mock_object(drv, '_find_share') - mock_find_share.return_value = ADDR + ':' + PATH - mock_get_volume_type = self.mock_object(volume_types, - 'get_volume_type') - mock_get_volume_type.return_value = VOLUME_TYPE - mock_get_qos_specs = self.mock_object(qos_specs, 'get_qos_specs') - mock_get_qos_specs.return_value = QOS_SPEC - mock_get_admin_context = self.mock_object(context, 'get_admin_context') - mock_get_admin_context.return_value = 'test' - - drv.create_volume_from_snapshot(VOLUME, SNAPSHOT) - - mock_find_share.assert_has_calls( - [mock.call(VOLUME)]) - self.assertTrue(mock_get_admin_context.called) - mock_get_volume_type.assert_has_calls( - [mock.call('test', VOLUME_TYPE['id'])]) - mock_get_qos_specs.assert_has_calls( - [mock.call('test', QOS_SPEC['id'])]) - mock_rpc_client.assert_has_calls( - [mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().create_volume_from_snapshot( - SNAPSHOT['name'], os.path.join(PATH, VOLUME['name'])), - mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().set_qos_policy(os.path.join(PATH, VOLUME['name']), - QOS)]) - - def test_create_cloned_volume(self): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') - mock_find_share = self.mock_object(drv, '_find_share') - mock_find_share.return_value = ADDR + ':' + PATH - mock_execute = self.mock_object(drv, '_execute') - mock_local_path = self.mock_object(drv, 'local_path') - mock_local_path.return_value = LOCAL_PATH - mock_get_volume_type = self.mock_object(volume_types, - 'get_volume_type') - mock_get_volume_type.return_value = VOLUME_TYPE - mock_get_qos_specs = self.mock_object(qos_specs, 'get_qos_specs') - mock_get_qos_specs.return_value = QOS_SPEC - mock_get_admin_context = self.mock_object(context, 'get_admin_context') - mock_get_admin_context.return_value = 'test' - - drv.create_cloned_volume(VOLUME, CLONE_VOL) - - mock_find_share.assert_has_calls( - [mock.call(VOLUME)]) - mock_local_path.assert_has_calls( - [mock.call(VOLUME), mock.call(CLONE_VOL)]) - mock_execute.assert_has_calls( - [mock.call('cp', LOCAL_PATH, LOCAL_PATH, run_as_root=True)]) - self.assertTrue(mock_get_admin_context.called) - mock_get_volume_type.assert_has_calls( - [mock.call('test', VOLUME_TYPE['id'])]) - mock_get_qos_specs.assert_has_calls( - [mock.call('test', QOS_SPEC['id'])]) - mock_rpc_client.assert_has_calls( - [mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().set_qos_policy(os.path.join(PATH, VOLUME['name']), - QOS)]) - - def test_retype(self): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') - mock_get_volume_type = self.mock_object(volume_types, - 'get_volume_type') - mock_get_volume_type.return_value = VOLUME_TYPE - mock_get_qos_specs = self.mock_object(qos_specs, 'get_qos_specs') - mock_get_qos_specs.return_value = QOS_SPEC - - drv.retype('test', VOLUME, VOLUME_TYPE, None, None) - - mock_get_volume_type.assert_has_calls( - [mock.call('test', VOLUME_TYPE['id'])]) - mock_get_qos_specs.assert_has_calls( - [mock.call('test', QOS_SPEC['id'])]) - mock_rpc_client.assert_has_calls( - [mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().set_qos_policy(os.path.join(PATH, VOLUME['name']), - QOS)]) - - def test_create_cloned_volume_larger(self): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_rpc_client = self.mock_object(coho, 'CohoRPCClient') - mock_find_share = self.mock_object(drv, '_find_share') - mock_find_share.return_value = ADDR + ':' + PATH - mock_execute = self.mock_object(drv, '_execute') - mock_local_path = self.mock_object(drv, 'local_path') - mock_local_path.return_value = LOCAL_PATH - mock_get_volume_type = self.mock_object(volume_types, - 'get_volume_type') - mock_get_volume_type.return_value = VOLUME_TYPE - mock_get_qos_specs = self.mock_object(qos_specs, 'get_qos_specs') - mock_get_qos_specs.return_value = QOS_SPEC - mock_get_admin_context = self.mock_object(context, 'get_admin_context') - mock_get_admin_context.return_value = 'test' - - drv.create_cloned_volume(CLONE_VOL, VOLUME) - - mock_find_share.assert_has_calls( - [mock.call(CLONE_VOL)]) - mock_local_path.assert_has_calls( - [mock.call(CLONE_VOL), mock.call(VOLUME)]) - mock_execute.assert_has_calls( - [mock.call('cp', LOCAL_PATH, LOCAL_PATH, run_as_root=True)]) - self.assertTrue(mock_get_admin_context.called) - mock_get_volume_type.assert_has_calls( - [mock.call('test', VOLUME_TYPE['id'])]) - mock_get_qos_specs.assert_has_calls( - [mock.call('test', QOS_SPEC['id'])]) - mock_rpc_client.assert_has_calls( - [mock.call(ADDR, self.configuration.coho_rpc_port), - mock.call().set_qos_policy(os.path.join(PATH, VOLUME['name']), - QOS)]) - mock_local_path.assert_has_calls( - [mock.call(CLONE_VOL)]) - mock_execute.assert_has_calls( - [mock.call('truncate', '-s', '256G', - LOCAL_PATH, run_as_root=True)]) - - def test_extend_volume(self): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_execute = self.mock_object(drv, '_execute') - mock_local_path = self.mock_object(drv, 'local_path') - mock_local_path.return_value = LOCAL_PATH - - drv.extend_volume(VOLUME, 512) - - mock_local_path.assert_has_calls( - [mock.call(VOLUME)]) - mock_execute.assert_has_calls( - [mock.call('truncate', '-s', '512G', - LOCAL_PATH, run_as_root=True)]) - - def test_snapshot_failure_when_source_does_not_exist(self): - drv = coho.CohoDriver(configuration=self.configuration) - - self.mock_object(coho.Client, '_make_call') - mock_init_socket = self.mock_object(coho.Client, 'init_socket') - mock_unpack_uint = self.mock_object(xdrlib.Unpacker, 'unpack_uint') - mock_unpack_uint.return_value = errno.ENOENT - mock_get_volume_location = self.mock_object(coho.CohoDriver, - '_get_volume_location') - mock_get_volume_location.return_value = ADDR, PATH - - with self.assertRaisesRegex(exception.CohoException, - "No such file or directory.*"): - drv.create_snapshot(SNAPSHOT) - - self.assertTrue(mock_init_socket.called) - self.assertTrue(mock_unpack_uint.called) - mock_get_volume_location.assert_has_calls( - [mock.call(SNAPSHOT['volume_id'])]) - - def test_snapshot_failure_with_invalid_input(self): - drv = coho.CohoDriver(configuration=self.configuration) - - self.mock_object(coho.Client, '_make_call') - mock_init_socket = self.mock_object(coho.Client, 'init_socket') - mock_unpack_uint = self.mock_object(xdrlib.Unpacker, 'unpack_uint') - mock_unpack_uint.return_value = errno.EINVAL - mock_get_volume_location = self.mock_object(coho.CohoDriver, - '_get_volume_location') - mock_get_volume_location.return_value = ADDR, PATH - - with self.assertRaisesRegex(exception.CohoException, - "Invalid argument"): - drv.delete_snapshot(INVALID_SNAPSHOT) - - self.assertTrue(mock_init_socket.called) - self.assertTrue(mock_unpack_uint.called) - mock_get_volume_location.assert_has_calls( - [mock.call(INVALID_SNAPSHOT['volume_id'])]) - - @mock.patch('cinder.volume.drivers.coho.Client.init_socket', - side_effect=exception.CohoException( - "Failed to establish connection.")) - def test_snapshot_failure_when_remote_is_unreachable(self, - mock_init_socket): - drv = coho.CohoDriver(configuration=self.configuration) - - mock_get_volume_location = self.mock_object(coho.CohoDriver, - '_get_volume_location') - mock_get_volume_location.return_value = 'uknown-address', PATH - - with self.assertRaisesRegex(exception.CohoException, - "Failed to establish connection.*"): - drv.create_snapshot(SNAPSHOT) - - mock_get_volume_location.assert_has_calls( - [mock.call(INVALID_SNAPSHOT['volume_id'])]) - - def test_rpc_client_make_call_proper_order(self): - """This test ensures that the RPC client logic is correct. - - When the RPC client's make_call function is called it creates - a packet and sends it to the Coho cluster RPC server. This test - ensures that the functions needed to complete the process are - called in the proper order with valid arguments. - """ - - mock_packer = self.mock_object(xdrlib, 'Packer') - mock_unpacker = self.mock_object(xdrlib, 'Unpacker') - mock_unpacker.return_value.unpack_uint.return_value = 0 - mock_socket = self.mock_object(socket, 'socket') - mock_init_call = self.mock_object(coho.Client, 'init_call') - mock_init_call.return_value = (1, 2) - mock_sendrecord = self.mock_object(coho.Client, '_sendrecord') - mock_recvrecord = self.mock_object(coho.Client, '_recvrecord') - mock_recvrecord.return_value = 'test_reply' - mock_unpack_replyheader = self.mock_object(coho.Client, - 'unpack_replyheader') - mock_unpack_replyheader.return_value = (123, 1) - - rpc_client = coho.CohoRPCClient(ADDR, RPC_PORT) - rpc_client.create_volume_from_snapshot('src', 'dest') - - self.assertTrue(mock_sendrecord.called) - self.assertTrue(mock_unpack_replyheader.called) - mock_packer.assert_has_calls([mock.call().reset()]) - mock_unpacker.assert_has_calls( - [mock.call().reset('test_reply'), - mock.call().unpack_uint()]) - mock_socket.assert_has_calls( - [mock.call(socket.AF_INET, socket.SOCK_STREAM), - mock.call().connect((ADDR, RPC_PORT))]) - mock_init_call.assert_has_calls( - [mock.call(coho.COHO1_CREATE_VOLUME_FROM_SNAPSHOT, - [(six.b('src'), mock_packer().pack_string), - (six.b('dest'), mock_packer().pack_string)])]) - - def test_rpc_client_error_in_reply_header(self): - """Ensure excpetions in reply header are raised by the RPC client. - - Coho cluster's RPC server packs errors into the reply header. - This test ensures that the RPC client parses the reply header - correctly and raises exceptions on various errors that can be - included in the reply header. - """ - mock_socket = self.mock_object(socket, 'socket') - mock_recvrecord = self.mock_object(coho.Client, '_recvrecord') - rpc_client = coho.CohoRPCClient(ADDR, RPC_PORT) - - mock_recvrecord.return_value = NO_REPLY_BIN - with self.assertRaisesRegex(exception.CohoException, - "no REPLY.*"): - rpc_client.create_snapshot('src', 'dest', 0) - - mock_recvrecord.return_value = MSG_DENIED_BIN - with self.assertRaisesRegex(exception.CohoException, - ".*MSG_DENIED.*"): - rpc_client.delete_snapshot('snapshot') - - mock_recvrecord.return_value = PROG_UNAVAIL_BIN - with self.assertRaisesRegex(exception.CohoException, - ".*PROG_UNAVAIL"): - rpc_client.delete_snapshot('snapshot') - - mock_recvrecord.return_value = PROG_MISMATCH_BIN - with self.assertRaisesRegex(exception.CohoException, - ".*PROG_MISMATCH.*"): - rpc_client.delete_snapshot('snapshot') - - mock_recvrecord.return_value = GARBAGE_ARGS_BIN - with self.assertRaisesRegex(exception.CohoException, - ".*GARBAGE_ARGS"): - rpc_client.delete_snapshot('snapshot') - - mock_recvrecord.return_value = PROC_UNAVAIL_BIN - with self.assertRaisesRegex(exception.CohoException, - ".*PROC_UNAVAIL"): - rpc_client.delete_snapshot('snapshot') - - self.assertTrue(mock_recvrecord.called) - mock_socket.assert_has_calls( - [mock.call(socket.AF_INET, socket.SOCK_STREAM), - mock.call().connect((ADDR, RPC_PORT))]) - - def test_rpc_client_error_in_receive_fragment(self): - """Ensure exception is raised when malformed packet is received.""" - mock_sendrcd = self.mock_object(coho.Client, '_sendrecord') - mock_socket = self.mock_object(socket, 'socket') - mock_socket.return_value.recv.return_value = INVALID_HEADER_BIN - rpc_client = coho.CohoRPCClient(ADDR, RPC_PORT) - - with self.assertRaisesRegex(exception.CohoException, - "Invalid response header.*"): - rpc_client.create_snapshot('src', 'dest', 0) - - self.assertTrue(mock_sendrcd.called) - mock_socket.assert_has_calls( - [mock.call(socket.AF_INET, socket.SOCK_STREAM), - mock.call().connect((ADDR, RPC_PORT)), - mock.call().recv(4)]) - - def test_rpc_client_recovery_on_broken_pipe(self): - """Ensure RPC retry on broken pipe error. - - When the cluster closes the TCP socket, try reconnecting - and retrying the command before returing error for the operation. - """ - mock_socket = self.mock_object(socket, 'socket') - mock_make_call = self.mock_object(coho.Client, '_make_call') - socket_error = socket.error('[Errno 32] Broken pipe') - socket_error.errno = errno.EPIPE - mock_make_call.side_effect = socket_error - rpc_client = coho.CohoRPCClient(ADDR, RPC_PORT) - - with self.assertRaisesRegex(exception.CohoException, - "Failed to establish.*"): - rpc_client.create_snapshot('src', 'dest', 0) - - self.assertEqual(coho.COHO_MAX_RETRIES, mock_make_call.call_count) - self.assertEqual(coho.COHO_MAX_RETRIES + 1, mock_socket.call_count) - - # assert that on a none EPIPE error it only tries once - socket_error.errno = errno.EINVAL - mock_make_call.side_effect = socket_error - with self.assertRaisesRegex(exception.CohoException, - "Unable to send request.*"): - rpc_client.delete_snapshot('src') - - self.assertEqual(coho.COHO_MAX_RETRIES + 1, mock_make_call.call_count) - self.assertEqual(coho.COHO_MAX_RETRIES + 1, mock_socket.call_count) diff --git a/cinder/volume/drivers/coho.py b/cinder/volume/drivers/coho.py deleted file mode 100644 index 837079c33cf..00000000000 --- a/cinder/volume/drivers/coho.py +++ /dev/null @@ -1,510 +0,0 @@ -# Copyright (c) 2015 Coho Data, 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. - -import errno -import os -import six -import socket -import xdrlib - -from oslo_config import cfg -from oslo_log import log as logging -from random import randint - -from cinder import context -from cinder import exception -from cinder.i18n import _ -from cinder import interface -from cinder import utils -from cinder.volume import configuration -from cinder.volume.drivers import nfs -from cinder.volume import qos_specs -from cinder.volume import volume_types - -LOG = logging.getLogger(__name__) - -# -# RPC Definition -# - -RPCVERSION = 2 - -CALL = 0 -REPLY = 1 - -AUTH_NULL = 0 - -MSG_ACCEPTED = 0 -MSG_DENIED = 1 - -SUCCESS = 0 -PROG_UNAVAIL = 1 -PROG_MISMATCH = 2 -PROC_UNAVAIL = 3 -GARBAGE_ARGS = 4 - -RPC_MISMATCH = 0 -AUTH_ERROR = 1 - -COHO_PROGRAM = 400115 -COHO_V1 = 1 -COHO1_CREATE_SNAPSHOT = 1 -COHO1_DELETE_SNAPSHOT = 2 -COHO1_CREATE_VOLUME_FROM_SNAPSHOT = 3 -COHO1_SET_QOS_POLICY = 4 - -COHO_MAX_RETRIES = 5 - -COHO_NO_QOS = {'maxIOPS': 0, 'maxMBS': 0} - -# -# Simple RPC Client -# - - -class Client(object): - - def __init__(self, address, prog, vers, port): - self.packer = xdrlib.Packer() - self.unpacker = xdrlib.Unpacker('') - self.address = address - self.prog = prog - self.vers = vers - self.port = port - self.cred = None - self.verf = None - - self.init_socket() - self.init_xid() - - def init_socket(self): - try: - self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.sock.connect((self.address, self.port)) - except socket.error: - msg = _('Failed to establish connection with Coho cluster') - raise exception.CohoException(msg) - - def init_xid(self): - self.xid = randint(0, 4096) - - def make_xid(self): - self.xid += 1 - - def make_cred(self): - if self.cred is None: - self.cred = (AUTH_NULL, six.b('')) - return self.cred - - def make_verf(self): - if self.verf is None: - self.verf = (AUTH_NULL, six.b('')) - return self.verf - - def pack_auth(self, auth): - flavor, stuff = auth - self.packer.pack_enum(flavor) - self.packer.pack_opaque(stuff) - - def pack_callheader(self, xid, prog, vers, proc, cred, verf): - self.packer.pack_uint(xid) - self.packer.pack_enum(CALL) - self.packer.pack_uint(RPCVERSION) - self.packer.pack_uint(prog) - self.packer.pack_uint(vers) - self.packer.pack_uint(proc) - self.pack_auth(cred) - self.pack_auth(verf) - - def unpack_auth(self): - flavor = self.unpacker.unpack_enum() - stuff = self.unpacker.unpack_opaque() - return (flavor, stuff) - - def unpack_replyheader(self): - xid = self.unpacker.unpack_uint() - mtype = self.unpacker.unpack_enum() - if mtype != REPLY: - raise exception.CohoException( - _('no REPLY but %r') % (mtype,)) - stat = self.unpacker.unpack_enum() - if stat == MSG_DENIED: - stat = self.unpacker.unpack_enum() - if stat == RPC_MISMATCH: - low = self.unpacker.unpack_uint() - high = self.unpacker.unpack_uint() - raise exception.CohoException( - _('MSG_DENIED: RPC_MISMATCH: %r') % ((low, high),)) - if stat == AUTH_ERROR: - stat = self.unpacker.unpack_uint() - raise exception.CohoException( - _('MSG_DENIED: AUTH_ERROR: %r') % (stat,)) - raise exception.CohoException(_('MSG_DENIED: %r') % (stat,)) - if stat != MSG_ACCEPTED: - raise exception.CohoException( - _('Neither MSG_DENIED nor MSG_ACCEPTED: %r') % (stat,)) - verf = self.unpack_auth() - stat = self.unpacker.unpack_enum() - if stat == PROG_UNAVAIL: - raise exception.CohoException(_('call failed: PROG_UNAVAIL')) - if stat == PROG_MISMATCH: - low = self.unpacker.unpack_uint() - high = self.unpacker.unpack_uint() - raise exception.CohoException( - _('call failed: PROG_MISMATCH: %r') % ((low, high),)) - if stat == PROC_UNAVAIL: - raise exception.CohoException(_('call failed: PROC_UNAVAIL')) - if stat == GARBAGE_ARGS: - raise exception.CohoException(_('call failed: GARBAGE_ARGS')) - if stat != SUCCESS: - raise exception.CohoException(_('call failed: %r') % (stat,)) - return xid, verf - - def init_call(self, proc, args): - self.make_xid() - self.packer.reset() - cred = self.make_cred() - verf = self.make_verf() - self.pack_callheader(self.xid, self.prog, self.vers, proc, cred, verf) - - for arg, func in args: - func(arg) - - return self.xid, self.packer.get_buf() - - def _sendfrag(self, last, frag): - x = len(frag) - if last: - x = x | 0x80000000 - header = (six.int2byte(int(x >> 24 & 0xff)) + - six.int2byte(int(x >> 16 & 0xff)) + - six.int2byte(int(x >> 8 & 0xff)) + - six.int2byte(int(x & 0xff))) - self.sock.send(header + frag) - - def _sendrecord(self, record): - self._sendfrag(1, record) - - def _recvfrag(self): - header = self.sock.recv(4) - if len(header) < 4: - raise exception.CohoException( - _('Invalid response header from RPC server')) - x = (six.indexbytes(header, 0) << 24 | - six.indexbytes(header, 1) << 16 | - six.indexbytes(header, 2) << 8 | - six.indexbytes(header, 3)) - last = ((x & 0x80000000) != 0) - n = int(x & 0x7fffffff) - frag = six.b('') - while n > 0: - buf = self.sock.recv(n) - if not buf: - raise exception.CohoException( - _('RPC server response is incomplete')) - n = n - len(buf) - frag = frag + buf - return last, frag - - def _recvrecord(self): - record = six.b('') - last = 0 - while not last: - last, frag = self._recvfrag() - record = record + frag - return record - - def _make_call(self, proc, args): - self.packer.reset() - xid, call = self.init_call(proc, args) - self._sendrecord(call) - reply = self._recvrecord() - self.unpacker.reset(reply) - xid, verf = self.unpack_replyheader() - - @utils.synchronized('coho-rpc', external=True) - def _call(self, proc, args): - for retry in range(COHO_MAX_RETRIES): - try: - self._make_call(proc, args) - break - except socket.error as e: - if e.errno == errno.EPIPE: - # Reopen connection to cluster and retry - LOG.debug('Re-establishing socket, retry number %d', retry) - self.init_socket() - else: - msg = (_('Unable to send requests: %s') % - six.text_type(e)) - raise exception.CohoException(msg) - else: - msg = _('Failed to establish a stable connection') - raise exception.CohoException(msg) - - res = self.unpacker.unpack_uint() - if res != SUCCESS: - raise exception.CohoException(os.strerror(res)) - - -class CohoRPCClient(Client): - - def __init__(self, address, port): - Client.__init__(self, address, COHO_PROGRAM, 1, port) - - def create_snapshot(self, src, dst, flags): - LOG.debug('COHO1_CREATE_SNAPSHOT src %s to dst %s', src, dst) - self._call(COHO1_CREATE_SNAPSHOT, - [(six.b(src), self.packer.pack_string), - (six.b(dst), self.packer.pack_string), - (flags, self.packer.pack_uint)]) - - def delete_snapshot(self, name): - LOG.debug('COHO1_DELETE_SNAPSHOT name %s', name) - self._call(COHO1_DELETE_SNAPSHOT, - [(six.b(name), self.packer.pack_string)]) - - def create_volume_from_snapshot(self, src, dst): - LOG.debug('COHO1_CREATE_VOLUME_FROM_SNAPSHOT src %s to dst %s', - src, dst) - self._call(COHO1_CREATE_VOLUME_FROM_SNAPSHOT, - [(six.b(src), self.packer.pack_string), - (six.b(dst), self.packer.pack_string)]) - - def set_qos_policy(self, src, qos): - LOG.debug('COHO1_SET_QOS_POLICY volume %s, uuid %s, %d:%d', - src, qos.get('uuid', ''), qos.get('maxIOPS', 0), - qos.get('maxMBS', '')) - self._call(COHO1_SET_QOS_POLICY, - [(six.b(src), self.packer.pack_string), - (six.b(qos.get('uuid', '')), self.packer.pack_string), - (0, self.packer.pack_uhyper), - (qos.get('maxIOPS', 0), self.packer.pack_uhyper), - (0, self.packer.pack_uhyper), - (qos.get('maxMBS', 0), self.packer.pack_uhyper)]) - - -# -# Coho Data Volume Driver -# - -VERSION = '1.1.1' - -coho_opts = [ - cfg.IntOpt('coho_rpc_port', - default=2049, - help='RPC port to connect to Coho Data MicroArray') -] - -CONF = cfg.CONF -CONF.register_opts(coho_opts, group=configuration.SHARED_CONF_GROUP) - - -@interface.volumedriver -class CohoDriver(nfs.NfsDriver): - """Coho Data NFS based cinder driver. - - Creates file on NFS share for using it as block device on hypervisor. - Version history: - 1.0.0 - Initial driver - 1.1.0 - Added QoS support - 1.1.1 - Stability fixes in the RPC client - """ - - # We have to overload this attribute of RemoteFSDriver because - # unfortunately the base method doesn't accept exports of the form: - #
:/ - # It expects a non blank export name following the /. - # We are more permissive. - SHARE_FORMAT_REGEX = r'.+:/.*' - - COHO_QOS_KEYS = ['maxIOPS', 'maxMBS'] - - # ThirdPartySystems wiki page name - CI_WIKI_NAME = "Coho_Data_CI" - - # TODO(smcginnis) Remove driver in Queens if CI issues not fixed - SUPPORTED = False - - def __init__(self, *args, **kwargs): - super(CohoDriver, self).__init__(*args, **kwargs) - self.configuration.append_config_values(coho_opts) - self._backend_name = (self.configuration.volume_backend_name or - self.__class__.__name__) - - def _get_rpcclient(self, addr, port): - return CohoRPCClient(addr, port) - - def do_setup(self, context): - """Any initialization the volume driver does while starting.""" - super(CohoDriver, self).do_setup(context) - self._execute_as_root = True - self._context = context - - config = self.configuration.coho_rpc_port - if not config: - msg = _("Coho rpc port is not configured") - LOG.warning(msg) - raise exception.CohoException(msg) - if config < 1 or config > 65535: - msg = (_("Invalid port number %(config)s for Coho rpc port") % - {'config': config}) - LOG.warning(msg) - raise exception.CohoException(msg) - - def _do_clone_volume(self, volume, src): - """Clone volume to source. - - Create a volume on given remote share with the same contents - as the specified source. - """ - volume_path = self.local_path(volume) - source_path = self.local_path(src) - - self._execute('cp', source_path, volume_path, - run_as_root=self._execute_as_root) - - qos = self._retrieve_qos_setting(volume) - self._do_set_qos_policy(volume, qos) - - def _get_volume_location(self, volume_id): - """Returns provider location for given volume.""" - - # The driver should not directly access db, but since volume is not - # passed in create_snapshot and delete_snapshot we are forced to read - # the volume info from the database - volume = self.db.volume_get(self._context, volume_id) - addr, path = volume.provider_location.split(":") - return addr, path - - def _do_set_qos_policy(self, volume, qos): - if qos: - addr, path = volume['provider_location'].split(':') - volume_path = os.path.join(path, volume['name']) - - client = self._get_rpcclient(addr, - self.configuration.coho_rpc_port) - client.set_qos_policy(volume_path, qos) - - def _get_qos_by_volume_type(self, ctxt, type_id): - qos = {} - - # NOTE(bardia): we only honor qos_specs - if type_id: - volume_type = volume_types.get_volume_type(ctxt, type_id) - qos_specs_id = volume_type.get('qos_specs_id') - - if qos_specs_id is not None: - kvs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs'] - qos['uuid'] = qos_specs_id - else: - kvs = {} - - for key, value in kvs.items(): - if key in self.COHO_QOS_KEYS: - qos[key] = int(value) - return qos - - def _retrieve_qos_setting(self, volume): - ctxt = context.get_admin_context() - type_id = volume['volume_type_id'] - - return self._get_qos_by_volume_type(ctxt, type_id) - - def create_volume(self, volume): - resp = super(CohoDriver, self).create_volume(volume) - qos = self._retrieve_qos_setting(volume) - self._do_set_qos_policy(volume, qos) - - return resp - - def create_snapshot(self, snapshot): - """Create a volume snapshot.""" - addr, path = self._get_volume_location(snapshot['volume_id']) - volume_path = os.path.join(path, snapshot['volume_name']) - snapshot_name = snapshot['name'] - flags = 0 # unused at this time - client = self._get_rpcclient(addr, self.configuration.coho_rpc_port) - client.create_snapshot(volume_path, snapshot_name, flags) - - def delete_snapshot(self, snapshot): - """Delete a volume snapshot.""" - addr, unused = self._get_volume_location(snapshot['volume_id']) - snapshot_name = snapshot['name'] - client = self._get_rpcclient(addr, self.configuration.coho_rpc_port) - client.delete_snapshot(snapshot_name) - - def create_volume_from_snapshot(self, volume, snapshot): - """Create a volume from a snapshot.""" - volume['provider_location'] = self._find_share(volume) - addr, path = volume['provider_location'].split(":") - volume_path = os.path.join(path, volume['name']) - snapshot_name = snapshot['name'] - - client = self._get_rpcclient(addr, self.configuration.coho_rpc_port) - client.create_volume_from_snapshot(snapshot_name, volume_path) - - qos = self._retrieve_qos_setting(volume) - self._do_set_qos_policy(volume, qos) - - return {'provider_location': volume['provider_location']} - - def _extend_file_sparse(self, path, size): - """Extend the size of a file (with no additional disk usage).""" - self._execute('truncate', '-s', '%sG' % size, - path, run_as_root=self._execute_as_root) - - def create_cloned_volume(self, volume, src_vref): - volume['provider_location'] = self._find_share(volume) - - self._do_clone_volume(volume, src_vref) - - if volume['size'] > src_vref['size']: - self.extend_volume(volume, volume['size']) - - def extend_volume(self, volume, new_size): - """Extend the specified file to the new_size (sparsely).""" - volume_path = self.local_path(volume) - - self._extend_file_sparse(volume_path, new_size) - - def retype(self, ctxt, volume, new_type, diff, host): - """Convert the volume to be of the new type. - - Changes the volume's QoS policy if needed. - """ - qos = self._get_qos_by_volume_type(ctxt, new_type['id']) - - # Reset the QoS policy on the volume in case the previous - # type had a QoS policy - if not qos: - qos = COHO_NO_QOS - - self._do_set_qos_policy(volume, qos) - - return True, None - - def get_volume_stats(self, refresh=False): - """Pass in Coho Data information in volume stats.""" - _stats = super(CohoDriver, self).get_volume_stats(refresh) - _stats["vendor_name"] = 'Coho Data' - _stats["driver_version"] = VERSION - _stats["storage_protocol"] = 'NFS' - _stats["volume_backend_name"] = self._backend_name - _stats["total_capacity_gb"] = 'unknown' - _stats["free_capacity_gb"] = 'unknown' - _stats["export_paths"] = self._mounted_shares - _stats["QoS_support"] = True - - return _stats diff --git a/doc/source/configuration/block-storage/drivers/coho-data-driver.rst b/doc/source/configuration/block-storage/drivers/coho-data-driver.rst deleted file mode 100644 index 38249f2532e..00000000000 --- a/doc/source/configuration/block-storage/drivers/coho-data-driver.rst +++ /dev/null @@ -1,93 +0,0 @@ -======================= -Coho Data volume driver -======================= - -The Coho DataStream Scale-Out Storage allows your Block Storage service to -scale seamlessly. The architecture consists of commodity storage servers -with SDN ToR switches. Leveraging an SDN OpenFlow controller allows you -to scale storage horizontally, while avoiding storage and network bottlenecks -by intelligent load-balancing and parallelized workloads. High-performance -PCIe NVMe flash, paired with traditional hard disk drives (HDD) or solid-state -drives (SSD), delivers low-latency performance even with highly mixed workloads -in large scale environment. - -Coho Data's storage features include real-time instance level -granularity performance and capacity reporting via API or UI, and -single-IP storage endpoint access. - -Supported operations -~~~~~~~~~~~~~~~~~~~~ - -* Create, delete, attach, detach, retype, clone, and extend volumes. -* Create, list, and delete volume snapshots. -* Create a volume from a snapshot. -* Copy a volume to an image. -* Copy an image to a volume. -* Create a thin provisioned volume. -* Get volume statistics. - -Coho Data QoS support -~~~~~~~~~~~~~~~~~~~~~ - -QoS support for the Coho Data driver includes the ability to set the -following capabilities in the OpenStack Block Storage API -``cinder.api.contrib.qos_specs_manage`` QoS specs extension module: - -* **maxIOPS** - The maximum number of IOPS allowed for this volume. - -* **maxMBS** - The maximum throughput allowed for this volume. - -The QoS keys above must be created and associated with a volume type. -For information about how to set the key-value pairs and associate -them with a volume type, see the `volume qos -