Merge "Add enough notification for QoS"

This commit is contained in:
Zuul 2018-01-18 18:40:22 +00:00 committed by Gerrit Code Review
commit a786527548
7 changed files with 123 additions and 29 deletions

View File

@ -16,6 +16,7 @@
"""The QoS specs extension"""
from oslo_log import log as logging
from oslo_utils import timeutils
import six
from six.moves import http_client
import webob
@ -90,7 +91,9 @@ class QoSSpecsController(wsgi.Controller):
try:
spec = qos_specs.create(context, name, specs)
notifier_info = dict(name=name, specs=specs)
notifier_info = dict(name=name,
created_at=spec.created_at,
specs=specs)
rpc.get_notifier('QoSSpecs').info(context,
'qos_specs.create',
notifier_info)
@ -119,12 +122,16 @@ class QoSSpecsController(wsgi.Controller):
def update(self, req, id, body=None):
context = req.environ['cinder.context']
context.authorize(policy.UPDATE_POLICY)
self.assert_valid_body(body, 'qos_specs')
specs = body['qos_specs']
try:
spec = qos_specs.get_qos_specs(context, id)
qos_specs.update(context, id, specs)
notifier_info = dict(id=id, specs=specs)
notifier_info = dict(id=id,
created_at=spec.created_at,
updated_at=timeutils.utcnow(),
specs=specs)
rpc.get_notifier('QoSSpecs').info(context,
'qos_specs.update',
notifier_info)
@ -164,10 +171,13 @@ class QoSSpecsController(wsgi.Controller):
force = utils.get_bool_param('force', req.params)
LOG.debug("Delete qos_spec: %(id)s, force: %(force)s",
{'id': id, 'force': force})
try:
spec = qos_specs.get_qos_specs(context, id)
qos_specs.delete(context, id, force)
notifier_info = dict(id=id)
notifier_info = dict(id=id,
created_at=spec.created_at,
deleted_at=timeutils.utcnow())
rpc.get_notifier('QoSSpecs').info(context,
'qos_specs.delete',
notifier_info)
@ -206,7 +216,10 @@ class QoSSpecsController(wsgi.Controller):
try:
qos_specs.delete_keys(context, id, keys)
notifier_info = dict(id=id)
spec = qos_specs.get_qos_specs(context, id)
notifier_info = dict(id=id,
created_at=spec.created_at,
updated_at=spec.updated_at)
rpc.get_notifier('QoSSpecs').info(context, 'qos_specs.delete_keys',
notifier_info)
except exception.NotFound as err:
@ -227,8 +240,11 @@ class QoSSpecsController(wsgi.Controller):
LOG.debug("Get associations for qos_spec id: %s", id)
try:
spec = qos_specs.get_qos_specs(context, id)
associates = qos_specs.get_associations(context, id)
notifier_info = dict(id=id)
notifier_info = dict(id=id,
created_at=spec.created_at)
rpc.get_notifier('QoSSpecs').info(context,
'qos_specs.associations',
notifier_info)
@ -267,8 +283,11 @@ class QoSSpecsController(wsgi.Controller):
{'id': id, 'type_id': type_id})
try:
spec = qos_specs.get_qos_specs(context, id)
qos_specs.associate_qos_with_type(context, id, type_id)
notifier_info = dict(id=id, type_id=type_id)
notifier_info = dict(id=id, type_id=type_id,
created_at=spec.created_at)
rpc.get_notifier('QoSSpecs').info(context,
'qos_specs.associate',
notifier_info)
@ -316,8 +335,11 @@ class QoSSpecsController(wsgi.Controller):
{'id': id, 'type_id': type_id})
try:
spec = qos_specs.get_qos_specs(context, id)
qos_specs.disassociate_qos_specs(context, id, type_id)
notifier_info = dict(id=id, type_id=type_id)
notifier_info = dict(id=id, type_id=type_id,
created_at=spec.created_at)
rpc.get_notifier('QoSSpecs').info(context,
'qos_specs.disassociate',
notifier_info)
@ -346,8 +368,11 @@ class QoSSpecsController(wsgi.Controller):
LOG.debug("Disassociate qos_spec: %s from all.", id)
try:
spec = qos_specs.get_qos_specs(context, id)
qos_specs.disassociate_all(context, id)
notifier_info = dict(id=id)
notifier_info = dict(id=id,
created_at=spec.created_at)
rpc.get_notifier('QoSSpecs').info(context,
'qos_specs.disassociate_all',
notifier_info)

View File

@ -62,7 +62,6 @@ from cinder.objects import fields
from cinder import utils
from cinder.volume import utils as vol_utils
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
@ -4572,11 +4571,21 @@ def _qos_specs_get_all_ref(context, qos_specs_id, session=None,
def _dict_with_children_specs(specs):
"""Convert specs list to a dict."""
result = {}
update_time = None
for spec in specs:
# Skip deleted keys
if not spec['deleted']:
# Add update time to specs list, in order to get the keyword
# 'updated_at' in specs info when printing logs.
if not update_time and spec['updated_at']:
update_time = spec['updated_at']
elif update_time and spec['updated_at']:
if (update_time -
spec['updated_at']).total_seconds() < 0:
update_time = spec['updated_at']
result.update({spec['key']: spec['value']})
if update_time:
result.update({'updated_at': update_time})
return result
@ -4591,10 +4600,15 @@ def _dict_with_qos_specs(rows):
result = []
for row in rows:
if row['key'] == 'QoS_Specs_Name':
member = {'name': row['value'], 'id': row['id']}
# Add create time for member, in order to get the keyword
# 'created_at' in the specs info when printing logs.
member = {'name': row['value'], 'id': row['id'],
'created_at': row['created_at']}
if row.specs:
spec_dict = _dict_with_children_specs(row.specs)
member['consumer'] = spec_dict.pop('consumer')
if spec_dict.get('updated_at'):
member['updated_at'] = spec_dict.pop('updated_at')
member.update(dict(specs=spec_dict))
result.append(member)
return result
@ -4812,7 +4826,6 @@ def qos_specs_update(context, qos_specs_id, updates):
return specs
####################

View File

@ -39,6 +39,8 @@ def stub_qos_specs(id):
"key4": "value4",
"key5": "value5"}
res.update(dict(specs=specs))
res.update(dict(created_at='2017-12-13T02:37:54Z'))
res.update(dict(updated_at='2017-12-13T02:38:58Z'))
return objects.QualityOfServiceSpecs(**res)
@ -102,6 +104,8 @@ def return_qos_specs_create(context, name, specs):
return objects.QualityOfServiceSpecs(name=name,
specs=specs,
created_at='2017-12-13T02:37:54Z',
updated_at='2017-12-13T02:38:58Z',
consumer='back-end',
id=fake.QOS_SPEC_ID)
@ -330,9 +334,12 @@ class QoSSpecManageApiTest(test.TestCase):
self.controller.delete,
req, fake.QOS_SPEC_ID)
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
@mock.patch('cinder.volume.qos_specs.delete_keys',
side_effect=return_qos_specs_delete_keys)
def test_qos_specs_delete_keys(self, mock_qos_delete_keys):
def test_qos_specs_delete_keys(self, mock_qos_delete_keys,
mock_get_qos):
body = {"keys": ['bar', 'zoo']}
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s/delete_keys' %
(fake.PROJECT_ID, fake.IN_USE_ID),
@ -355,9 +362,12 @@ class QoSSpecManageApiTest(test.TestCase):
req, fake.WILL_NOT_BE_FOUND_ID, body)
self.assertEqual(1, self.notifier.get_notification_count())
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
@mock.patch('cinder.volume.qos_specs.delete_keys',
side_effect=return_qos_specs_delete_keys)
def test_qos_specs_delete_keys_badkey(self, mock_qos_specs_delete):
def test_qos_specs_delete_keys_badkey(self, mock_qos_specs_delete,
mock_get_qos):
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s/delete_keys' %
(fake.PROJECT_ID, fake.IN_USE_ID),
use_admin_context=True)
@ -370,7 +380,10 @@ class QoSSpecManageApiTest(test.TestCase):
@mock.patch('cinder.volume.qos_specs.delete_keys',
side_effect=return_qos_specs_delete_keys)
def test_qos_specs_delete_keys_get_notifier(self, mock_qos_delete_keys):
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
def test_qos_specs_delete_keys_get_notifier(self, mock_get_qos_specs,
mock_qos_delete_keys):
body = {"keys": ['bar', 'zoo']}
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s/delete_keys' %
(fake.PROJECT_ID, fake.IN_USE_ID),
@ -467,7 +480,9 @@ class QoSSpecManageApiTest(test.TestCase):
@mock.patch('cinder.volume.qos_specs.update',
side_effect=return_qos_specs_update)
def test_update(self, mock_qos_update):
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
def test_update(self, mock_get_qos, mock_qos_update):
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s' %
(fake.PROJECT_ID, fake.QOS_SPEC_ID),
use_admin_context=True)
@ -479,7 +494,9 @@ class QoSSpecManageApiTest(test.TestCase):
@mock.patch('cinder.volume.qos_specs.update',
side_effect=return_qos_specs_update)
def test_update_not_found(self, mock_qos_update):
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
def test_update_not_found(self, mock_get_qos_specs, mock_qos_update):
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s' %
(fake.PROJECT_ID,
fake.WILL_NOT_BE_FOUND_ID),
@ -491,9 +508,11 @@ class QoSSpecManageApiTest(test.TestCase):
req, fake.WILL_NOT_BE_FOUND_ID, body)
self.assertEqual(1, self.notifier.get_notification_count())
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
@mock.patch('cinder.volume.qos_specs.update',
side_effect=return_qos_specs_update)
def test_update_invalid_input(self, mock_qos_update):
def test_update_invalid_input(self, mock_qos_update, mock_get_qos):
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s' %
(fake.PROJECT_ID, fake.INVALID_ID),
use_admin_context=True)
@ -504,10 +523,12 @@ class QoSSpecManageApiTest(test.TestCase):
req, fake.INVALID_ID, body)
self.assertEqual(1, self.notifier.get_notification_count())
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
@ddt.data({'qos_specs': {'key1': ['value1']}},
{'qos_specs': {1: 'value1'}}
)
def test_update_non_string_key_or_value(self, body):
def test_update_non_string_key_or_value(self, body, mock_get_qos):
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s' %
(fake.PROJECT_ID, fake.UUID1),
use_admin_context=True)
@ -516,9 +537,11 @@ class QoSSpecManageApiTest(test.TestCase):
req, fake.UUID1, body)
self.assertEqual(1, self.notifier.get_notification_count())
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
@mock.patch('cinder.volume.qos_specs.update',
side_effect=return_qos_specs_update)
def test_update_failed(self, mock_qos_update):
def test_update_failed(self, mock_qos_update, mock_get_qos):
req = fakes.HTTPRequest.blank('/v2/%s/qos-specs/%s' %
(fake.PROJECT_ID,
fake.UPDATE_FAILED_ID),
@ -543,7 +566,9 @@ class QoSSpecManageApiTest(test.TestCase):
@mock.patch('cinder.volume.qos_specs.get_associations',
side_effect=return_get_qos_associations)
def test_get_associations(self, mock_get_assciations):
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
def test_get_associations(self, mock_get_qos, mock_get_assciations):
req = fakes.HTTPRequest.blank(
'/v2/%s/qos-specs/%s/associations' % (
fake.PROJECT_ID, fake.QOS_SPEC_ID), use_admin_context=True)
@ -567,7 +592,10 @@ class QoSSpecManageApiTest(test.TestCase):
@mock.patch('cinder.volume.qos_specs.get_associations',
side_effect=return_get_qos_associations)
def test_get_associations_failed(self, mock_get_associations):
@mock.patch('cinder.volume.qos_specs.get_qos_specs',
side_effect=return_qos_specs_get_qos_specs)
def test_get_associations_failed(self, mock_get_qos,
mock_get_associations):
req = fakes.HTTPRequest.blank(
'/v2/%s/qos-specs/%s/associations' % (
fake.PROJECT_ID, fake.RAISE_ID), use_admin_context=True)
@ -706,7 +734,8 @@ class QoSSpecManageApiTest(test.TestCase):
side_effect=return_qos_specs_get_qos_specs)
@mock.patch('cinder.volume.qos_specs.disassociate_all',
side_effect=return_disassociate_all)
def test_disassociate_all_not_found(self, mock_disassociate, mock_get):
def test_disassociate_all_not_found(self, mock_disassociate,
mock_get_qos):
req = fakes.HTTPRequest.blank(
'/v2/%s/qos-specs/%s/disassociate_all' % (
fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID),

View File

@ -76,6 +76,7 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase):
db.qos_specs_get, self.ctxt, fake_id)
specs_returned = db.qos_specs_get(self.ctxt, specs_id)
qos_spec['created_at'] = specs_returned['created_at']
qos_spec['id'] = specs_id
self.assertDictEqual(qos_spec, specs_returned)
@ -91,10 +92,12 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase):
'consumer': 'back-end',
'specs': {'key1': 'v5', 'key2': 'v6'}}]
for qos in qos_list:
for index, qos in enumerate(qos_list):
qos['id'] = self._create_qos_specs(qos['name'],
qos['consumer'],
qos['specs'])
specs = db.qos_specs_get(self.ctxt, qos['id'])
qos_list[index]['created_at'] = specs['created_at']
specs_list_returned = db.qos_specs_get_all(self.ctxt)
self.assertEqual(len(qos_list), len(specs_list_returned),
@ -124,6 +127,7 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase):
'specs': value}
db.qos_specs_item_delete(self.ctxt, specs_id, 'foo')
specs = db.qos_specs_get(self.ctxt, specs_id)
expected['created_at'] = specs['created_at']
self.assertDictEqual(expected, specs)
def test_associate_type_with_qos(self):
@ -201,6 +205,7 @@ class QualityOfServiceSpecsTableTestCase(test.TestCase):
self.ctxt, fake.WILL_NOT_BE_FOUND_ID, value)
db.qos_specs_update(self.ctxt, specs_id, value)
specs = db.qos_specs_get(self.ctxt, specs_id)
value['created_at'] = specs['created_at']
self.assertEqual('new_value2', specs['specs']['key2'])
self.assertEqual('value3', specs['specs']['key3'])
self.assertEqual('both', specs['consumer'])

View File

@ -29,6 +29,7 @@ from cinder import db
from cinder import exception
from cinder import test
from cinder.tests.unit import fake_constants as fake
from cinder import utils
from cinder.volume import qos_specs
from cinder.volume import volume_types
@ -366,12 +367,15 @@ class QoSSpecsTestCase(test.TestCase):
'key3': 'value3',
'key4': 'value4'}}]
for qos_specs_dict in qos_specs_list:
for index, qos_specs_dict in enumerate(qos_specs_list):
qos_specs_id = self._create_qos_specs(
qos_specs_dict['name'],
qos_specs_dict['consumer'],
qos_specs_dict['specs'])
qos_specs_dict['id'] = qos_specs_id
specs = db.qos_specs_get(self.ctxt, qos_specs_id)
qos_specs_list[index]['created_at'] = utils.time_format(
specs['created_at'])
res = qos_specs.get_all_specs(self.ctxt)
self.assertEqual(len(qos_specs_list), len(res))

View File

@ -361,6 +361,8 @@ class VolumeTypeTestCase(test.TestCase):
'k2': 'v2',
'k3': 'v3'}}}
res = volume_types.get_volume_type_qos_specs(type_ref['id'])
specs = db.qos_specs_get(self.ctxt, qos_ref['id'])
expected['qos_specs']['created_at'] = specs['created_at']
self.assertDictEqual(expected, res)
def test_volume_types_diff(self):
@ -382,7 +384,7 @@ class VolumeTypeTestCase(test.TestCase):
self.assertEqual(('val1', 'val0'), diff['extra_specs']['key1'])
# qos_ref 1 and 2 have the same specs, while 3 has different
qos_keyvals1 = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'}
qos_keyvals1 = {'k1': 'v1', 'k2': 'v2', 'k3': 'v3', 'created_at': 'v4'}
qos_keyvals2 = {'k1': 'v0', 'k2': 'v2', 'k3': 'v3'}
qos_ref1 = qos_specs.create(self.ctxt, 'qos-specs-1', qos_keyvals1)
qos_ref2 = qos_specs.create(self.ctxt, 'qos-specs-2', qos_keyvals1)
@ -437,7 +439,8 @@ class VolumeTypeTestCase(test.TestCase):
self.assertEqual({'consumer': (None, 'back-end'),
'k1': (None, 'v1'),
'k2': (None, 'v2'),
'k3': (None, 'v3')}, diff['qos_specs'])
'k3': (None, 'v3'),
'created_at': (None, 'v4')}, diff['qos_specs'])
self.assertEqual({'cipher': (None, 'c1'),
'control_location': (None, 'front-end'),
'deleted': (None, False),

View File

@ -271,6 +271,21 @@ def last_completed_audit_period(unit=None):
return (begin, end)
def time_format(at=None):
"""Format datetime string to date.
:param at: Type is datetime.datetime (example
'datetime.datetime(2017, 12, 24, 22, 11, 32, 6086)')
:returns: Format date (example '2017-12-24T22:11:32Z').
"""
if not at:
at = timeutils.utcnow()
date_string = at.strftime("%Y-%m-%dT%H:%M:%S")
tz = at.tzname(None) if at.tzinfo else 'UTC'
date_string += ('Z' if tz == 'UTC' else tz)
return date_string
def is_none_string(val):
"""Check if a string represents a None value."""
if not isinstance(val, six.string_types):