QoS support for the Coho Data Cinder driver
Implements: blueprint coho-qos DocImpact Documentation for using QoS with the Coho Data Cinder driver Change-Id: I231c31ff2395f0d12a0570695dcd517c167c4602 Signed-off-by: Bardia Keyoumarsi <bardia.keyoumarsi@cohodata.com>
This commit is contained in:
parent
c523afa0fd
commit
272e126ed4
@ -22,11 +22,15 @@ import six
|
||||
import socket
|
||||
import xdrlib
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder import test
|
||||
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'
|
||||
@ -38,10 +42,10 @@ VOLUME = {
|
||||
'volume_id': 'bcc48c61-9691-4e5f-897c-793686093190',
|
||||
'size': 128,
|
||||
'volume_type': 'silver',
|
||||
'volume_type_id': 'test',
|
||||
'volume_type_id': 'type-id',
|
||||
'metadata': [{'key': 'type',
|
||||
'service_label': 'silver'}],
|
||||
'provider_location': None,
|
||||
'provider_location': 'coho-datastream-addr:/test/path',
|
||||
'id': 'bcc48c61-9691-4e5f-897c-793686093190',
|
||||
'status': 'available',
|
||||
}
|
||||
@ -60,6 +64,31 @@ SNAPSHOT = {
|
||||
'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': 'type-id'
|
||||
}
|
||||
|
||||
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'] = ''
|
||||
|
||||
@ -129,6 +158,34 @@ class CohoDriverTest(test.TestCase):
|
||||
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)
|
||||
|
||||
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_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)
|
||||
|
||||
@ -169,24 +226,46 @@ class CohoDriverTest(test.TestCase):
|
||||
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['size'])])
|
||||
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']))])
|
||||
SNAPSHOT['name'], os.path.join(PATH, VOLUME['name'])),
|
||||
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)
|
||||
|
||||
@ -196,6 +275,36 @@ class CohoDriverTest(test.TestCase):
|
||||
[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_extend_volume(self):
|
||||
drv = coho.CohoDriver(configuration=self.configuration)
|
||||
|
@ -23,11 +23,14 @@ 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.drivers import nfs
|
||||
from cinder.volume import qos_specs
|
||||
from cinder.volume import volume_types
|
||||
|
||||
#
|
||||
# RPC Definition
|
||||
@ -57,9 +60,12 @@ 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
|
||||
#
|
||||
@ -262,8 +268,8 @@ class CohoRPCClient(Client):
|
||||
def create_snapshot(self, src, dst, flags):
|
||||
self._call(COHO1_CREATE_SNAPSHOT,
|
||||
[(six.b(src), self.packer.pack_string),
|
||||
(six.b(dst), self.packer.pack_string),
|
||||
(flags, self.packer.pack_uint)])
|
||||
(six.b(dst), self.packer.pack_string),
|
||||
(flags, self.packer.pack_uint)])
|
||||
|
||||
def delete_snapshot(self, name):
|
||||
self._call(COHO1_DELETE_SNAPSHOT,
|
||||
@ -272,14 +278,23 @@ class CohoRPCClient(Client):
|
||||
def create_volume_from_snapshot(self, src, dst):
|
||||
self._call(COHO1_CREATE_VOLUME_FROM_SNAPSHOT,
|
||||
[(six.b(src), self.packer.pack_string),
|
||||
(six.b(dst), self.packer.pack_string)])
|
||||
(six.b(dst), self.packer.pack_string)])
|
||||
|
||||
def set_qos_policy(self, src, qos):
|
||||
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.0.0'
|
||||
VERSION = '1.1.0'
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -300,6 +315,7 @@ class CohoDriver(nfs.NfsDriver):
|
||||
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
|
||||
"""
|
||||
|
||||
# We have to overload this attribute of RemoteFSDriver because
|
||||
@ -309,6 +325,8 @@ class CohoDriver(nfs.NfsDriver):
|
||||
# We are more permissive.
|
||||
SHARE_FORMAT_REGEX = r'.+:/.*'
|
||||
|
||||
COHO_QOS_KEYS = ['maxIOPS', 'maxMBS']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CohoDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(coho_opts)
|
||||
@ -355,6 +373,9 @@ class CohoDriver(nfs.NfsDriver):
|
||||
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."""
|
||||
|
||||
@ -365,6 +386,47 @@ class CohoDriver(nfs.NfsDriver):
|
||||
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'])
|
||||
@ -376,7 +438,7 @@ class CohoDriver(nfs.NfsDriver):
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
"""Delete a volume snapshot."""
|
||||
addr, path = self._get_volume_location(snapshot['volume_id'])
|
||||
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)
|
||||
@ -387,9 +449,13 @@ class CohoDriver(nfs.NfsDriver):
|
||||
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):
|
||||
@ -408,7 +474,23 @@ class CohoDriver(nfs.NfsDriver):
|
||||
|
||||
self._extend_file_sparse(volume_path, new_size)
|
||||
|
||||
def get_volume_stats(self, refresh):
|
||||
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'
|
||||
@ -418,5 +500,6 @@ class CohoDriver(nfs.NfsDriver):
|
||||
_stats["total_capacity_gb"] = 'unknown'
|
||||
_stats["free_capacity_gb"] = 'unknown'
|
||||
_stats["export_paths"] = self._mounted_shares
|
||||
_stats["QoS_support"] = True
|
||||
|
||||
return _stats
|
||||
|
Loading…
Reference in New Issue
Block a user