Make get_capabilities look for clusters

Currently get_capabilities accepts only host as a service identifier and
fails when it cannot find a service with such host property. In A/A
deployment, it should accept cluster_name as well, because
get_capabilities addresses backend and not individual host. This commit
fixes this up by making the call seek for available services using both
host and cluster_name properties.

Although it's an API change, it doesn't need a new microversion, because
it falls under "Did we silently fail to do what is asked?" category.

Change-Id: If7bca131c84fc997da8ad277eccd134e8ca4b316
Closes-Bug: 1660990
This commit is contained in:
Michał Dulko 2017-02-01 14:57:19 +01:00
parent c4ea8f598d
commit 7401d0d098
5 changed files with 35 additions and 11 deletions

View File

@ -44,16 +44,18 @@ class CapabilitiesController(wsgi.Controller):
"""Return capabilities list of given backend.""" """Return capabilities list of given backend."""
context = req.environ['cinder.context'] context = req.environ['cinder.context']
authorize(context, 'capabilities') authorize(context, 'capabilities')
filters = {'host': id, 'binary': 'cinder-volume'} filters = {'host_or_cluster': id, 'binary': 'cinder-volume'}
service = objects.ServiceList.get_all(context, filters) services = objects.ServiceList.get_all(context, filters)
if not service: if not services:
msg = (_("Can't find service: %s") % id) msg = (_("Can't find service: %s") % id)
raise exception.NotFound(msg) raise exception.NotFound(msg)
topic = services[0].service_topic_queue
try: try:
capabilities = self.volume_api.get_capabilities(context, id, False) capabilities = self.volume_api.get_capabilities(context, topic,
False)
except oslo_messaging.MessagingTimeout: except oslo_messaging.MessagingTimeout:
raise exception.RPCTimeout(service=id) raise exception.RPCTimeout(service=topic)
return self._view_builder.summary(req, capabilities, id) return self._view_builder.summary(req, capabilities, topic)
class Capabilities(extensions.ExtensionDescriptor): class Capabilities(extensions.ExtensionDescriptor):

View File

@ -117,7 +117,9 @@ def service_get(context, service_id=None, backend_match_level=None, **filters):
def service_get_all(context, backend_match_level=None, **filters): def service_get_all(context, backend_match_level=None, **filters):
"""Get all services that match the criteria. """Get all services that match the criteria.
A possible filter is is_up=True and it will filter nodes that are down. A possible filter is is_up=True and it will filter nodes that are down,
as well as host_or_cluster, that lets you look for services using both
of these properties.
:param filters: Filters for the query in the form of key/value arguments. :param filters: Filters for the query in the form of key/value arguments.
:param backend_match_level: 'pool', 'backend', or 'host' for host and :param backend_match_level: 'pool', 'backend', or 'host' for host and

View File

@ -449,8 +449,9 @@ def _clustered_bool_field_filter(query, field_name, filter_value):
def _service_query(context, session=None, read_deleted='no', host=None, def _service_query(context, session=None, read_deleted='no', host=None,
cluster_name=None, is_up=None, backend_match_level=None, cluster_name=None, is_up=None, host_or_cluster=None,
disabled=None, frozen=None, **filters): backend_match_level=None, disabled=None, frozen=None,
**filters):
filters = _clean_filters(filters) filters = _clean_filters(filters)
if filters and not is_valid_model_filters(models.Service, filters): if filters and not is_valid_model_filters(models.Service, filters):
return None return None
@ -467,6 +468,13 @@ def _service_query(context, session=None, read_deleted='no', host=None,
if cluster_name: if cluster_name:
query = query.filter(_filter_host(models.Service.cluster_name, query = query.filter(_filter_host(models.Service.cluster_name,
cluster_name, backend_match_level)) cluster_name, backend_match_level))
if host_or_cluster:
query = query.filter(or_(
_filter_host(models.Service.host, host_or_cluster,
backend_match_level),
_filter_host(models.Service.cluster_name, host_or_cluster,
backend_match_level),
))
query = _clustered_bool_field_filter(query, 'disabled', disabled) query = _clustered_bool_field_filter(query, 'disabled', disabled)
query = _clustered_bool_field_filter(query, 'frozen', frozen) query = _clustered_bool_field_filter(query, 'frozen', frozen)

View File

@ -70,13 +70,13 @@ class CapabilitiesAPITest(test.TestCase):
@mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities', @mock.patch('cinder.volume.rpcapi.VolumeAPI.get_capabilities',
rpcapi_get_capabilities) rpcapi_get_capabilities)
def test_capabilities_summary(self, mock_services): def test_capabilities_summary(self, mock_services):
mock_services.return_value = [{'name': 'fake'}] mock_services.return_value = [{'name': 'fake', 'host': 'fake_host'}]
req = fakes.HTTPRequest.blank('/fake/capabilities/fake') req = fakes.HTTPRequest.blank('/fake/capabilities/fake')
req.environ['cinder.context'] = self.ctxt req.environ['cinder.context'] = self.ctxt
res = self.controller.show(req, 'fake') res = self.controller.show(req, 'fake')
expected = { expected = {
'namespace': 'OS::Storage::Capabilities::fake', 'namespace': 'OS::Storage::Capabilities::fake_host',
'vendor_name': 'OpenStack', 'vendor_name': 'OpenStack',
'volume_backend_name': 'lvm', 'volume_backend_name': 'lvm',
'pool_name': 'pool', 'pool_name': 'pool',

View File

@ -277,6 +277,18 @@ class DBAPIServiceTestCase(BaseTest):
real = db.service_get_all(self.ctxt, cluster_name='cluster') real = db.service_get_all(self.ctxt, cluster_name='cluster')
self._assertEqualListsOfObjects(expected, real) self._assertEqualListsOfObjects(expected, real)
def test_service_get_all_by_host_or_cluster(self):
values = [
{'host': 'host1', 'cluster_name': 'cluster'},
{'host': 'host2', 'cluster_name': 'host1'},
{'host': 'host3', 'cluster_name': 'cluster@backend'},
{'host': 'host4', 'cluster_name': 'cluster2'},
]
services = [utils.create_service(self.ctxt, vals) for vals in values]
expected = services[0:2]
real = db.service_get_all(self.ctxt, host_or_cluster='host1')
self._assertEqualListsOfObjects(expected, real)
def test_service_get_by_args_not_found_exception(self): def test_service_get_by_args_not_found_exception(self):
self.assertRaises(exception.ServiceNotFound, self.assertRaises(exception.ServiceNotFound,
db.service_get, db.service_get,