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:
Bardia Keyoumarsi 2016-07-22 14:01:45 -07:00
parent c523afa0fd
commit 272e126ed4
2 changed files with 201 additions and 9 deletions

View File

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

View File

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