Merge "Remove Coho volume driver"
This commit is contained in:
commit
36d6e7d800
@ -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")
|
||||
|
@ -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,
|
||||
|
@ -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)
|
@ -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:
|
||||
# <address>:/
|
||||
# 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
|
@ -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
|
||||
<https://docs.openstack.org/python-openstackclient/latest/cli/command-objects/volume-qos.html>`_
|
||||
section in the OpenStackClient command list.
|
||||
|
||||
.. note::
|
||||
|
||||
If you change a volume type with QoS to a new volume type
|
||||
without QoS, the QoS configuration settings will be removed.
|
||||
|
||||
System requirements
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
* NFS client on the Block storage controller.
|
||||
|
||||
Coho Data Block Storage driver configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
#. Create cinder volume type.
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ openstack volume type create coho-1
|
||||
|
||||
#. Edit the OpenStack Block Storage service configuration file.
|
||||
The following sample, ``/etc/cinder/cinder.conf``, configuration lists the
|
||||
relevant settings for a typical Block Storage service using a single
|
||||
Coho Data storage:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[DEFAULT]
|
||||
enabled_backends = coho-1
|
||||
default_volume_type = coho-1
|
||||
|
||||
[coho-1]
|
||||
volume_driver = cinder.volume.drivers.coho.CohoDriver
|
||||
volume_backend_name = coho-1
|
||||
nfs_shares_config = /etc/cinder/coho_shares
|
||||
nas_secure_file_operations = 'false'
|
||||
|
||||
#. Add your list of Coho Datastream NFS addresses to the file you specified
|
||||
with the ``nfs_shares_config`` option. For example, if the value of this
|
||||
option was set to ``/etc/cinder/coho_shares``, then:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ cat /etc/cinder/coho_shares
|
||||
<coho-nfs-ip>:/<export-path>
|
||||
|
||||
#. Restart the ``cinder-volume`` service to enable Coho Data driver.
|
||||
|
||||
.. include:: ../../tables/cinder-coho.inc
|
@ -15,7 +15,6 @@ Volume drivers
|
||||
drivers/smbfs-volume-driver.rst
|
||||
drivers/blockbridge-eps-driver.rst
|
||||
drivers/cloudbyte-driver.rst
|
||||
drivers/coho-data-driver.rst
|
||||
drivers/coprhd-driver.rst
|
||||
drivers/datera-volume-driver.rst
|
||||
drivers/dell-emc-scaleio-driver.rst
|
||||
|
@ -1,22 +0,0 @@
|
||||
..
|
||||
Warning: Do not edit this file. It is automatically generated from the
|
||||
software project's code and your changes will be overwritten.
|
||||
|
||||
The tool to generate this file lives in openstack-doc-tools repository.
|
||||
|
||||
Please make any changes needed in the code, then run the
|
||||
autogenerate-config-doc tool from the openstack-doc-tools repository, or
|
||||
ask for help on the documentation mailing list, IRC channel or meeting.
|
||||
|
||||
.. _cinder-coho:
|
||||
|
||||
.. list-table:: Description of Coho volume driver configuration options
|
||||
:header-rows: 1
|
||||
:class: config-ref-table
|
||||
|
||||
* - Configuration option = Default value
|
||||
- Description
|
||||
* - **[DEFAULT]**
|
||||
-
|
||||
* - ``coho_rpc_port`` = ``2049``
|
||||
- (Integer) RPC port to connect to Coho Data MicroArray
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
The following volume drivers were deprecated in the Pike release and have
|
||||
now been removed:
|
||||
|
||||
* Block device driver
|
||||
* Coho
|
||||
|
Loading…
x
Reference in New Issue
Block a user