Merge "Remove Coho volume driver"

This commit is contained in:
Jenkins 2017-09-13 23:57:09 +00:00 committed by Gerrit Code Review
commit 36d6e7d800
8 changed files with 9 additions and 1193 deletions

View File

@ -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")

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,9 @@
---
upgrade:
- |
The following volume drivers were deprecated in the Pike release and have
now been removed:
* Block device driver
* Coho