Add volume type filter to API Get-Pools

Add feature that administrators can get back-end storage pools
filtered by volume-type, Cinder will return the pools filtered
by volume-type's extra-spec. This is depended on generalized
resource filtering feature.

APIImpact
Depends-On: ff3d41b15a
Implements: blueprint add-volume-type-filter-to-get-pool

Change-Id: If2ae4616340d061db833cbbdffc77f3e976d8254
This commit is contained in:
TommyLike 2017-04-25 11:30:23 +08:00
parent cec1d5c249
commit d5a3fdabca
11 changed files with 146 additions and 15 deletions

View File

@ -449,8 +449,9 @@ def get_enabled_resource_filters(resource=None):
def reject_invalid_filters(context, filters, resource,
enable_like_filter=False):
if context.is_admin:
# Allow all options
if context.is_admin and resource not in ['pool']:
# Allow all options except resource is pool
# pool API is only available for admin
return
# Check the configured filters against those passed in resource
configured_filters = get_enabled_resource_filters(resource)

View File

@ -22,6 +22,7 @@ from cinder.scheduler import rpcapi
from cinder import utils
GET_POOL_NAME_FILTER_MICRO_VERSION = '3.28'
GET_POOL_VOLUME_TYPE_FILTER_MICRO_VERSION = '3.35'
def authorize(context, action_name):
@ -59,6 +60,9 @@ class SchedulerStatsController(wsgi.Controller):
filters=filters,
req_version=req_version)
if not req_version.matches(GET_POOL_VOLUME_TYPE_FILTER_MICRO_VERSION):
filters.pop('volume_type', None)
pools = self.scheduler_api.get_pools(context, filters=filters)
return self._view_builder.pools(req, pools, detail)

View File

@ -89,6 +89,7 @@ REST_API_VERSION_HISTORY = """
* 3.34 - Add like filter support in ``volume``, ``backup``, ``snapshot``,
``message``, ``attachment``, ``group`` and ``group-snapshot``
list APIs.
* 3.35 - Add ``volume-type`` filter to Get-Pools API.
"""
@ -97,7 +98,7 @@ REST_API_VERSION_HISTORY = """
# minimum version of the API supported.
# Explicitly using /v1 or /v2 endpoints will still work
_MIN_API_VERSION = "3.0"
_MAX_API_VERSION = "3.34"
_MAX_API_VERSION = "3.35"
_LEGACY_API_VERSION1 = "1.0"
_LEGACY_API_VERSION2 = "2.0"

View File

@ -313,3 +313,7 @@ user documentation.
----
Add like filter support in ``volume``, ``backup``, ``snapshot``, ``message``,
``attachment``, ``group`` and ``group-snapshot`` list APIs.
3.35
----
Add ``volume-type`` filter to Get-Pools API.

View File

@ -32,6 +32,7 @@ from cinder import objects
from cinder.scheduler import filters
from cinder import utils
from cinder.volume import utils as vol_utils
from cinder.volume import volume_types
# FIXME: This file should be renamed to backend_manager, we should also rename
@ -627,15 +628,33 @@ class HostManager(object):
return all_pools.values()
def _filter_pools_by_volume_type(self, context, volume_type, pools):
"""Return the pools filtered by volume type specs"""
# wrap filter properties only with volume_type
filter_properties = {
'context': context,
'volume_type': volume_type,
'resource_type': volume_type,
'qos_specs': volume_type.get('qos_specs'),
}
filtered = self.get_filtered_backends(pools.values(),
filter_properties)
# filter the pools by value
return {k: v for k, v in pools.items() if v in filtered}
def get_pools(self, context, filters=None):
"""Returns a dict of all pools on all hosts HostManager knows about."""
self._update_backend_state_map(context)
all_pools = []
name = None
all_pools = {}
name = volume_type = None
if filters:
name = filters.pop('name', None)
volume_type = filters.pop('volume_type', None)
for backend_key, state in self.backend_state_map.items():
for key in state.pools:
@ -658,9 +677,20 @@ class HostManager(object):
break
if not filtered:
all_pools.append(new_pool)
all_pools[pool_key] = pool
return all_pools
# filter pools by volume type
if volume_type:
volume_type = volume_types.get_by_name_or_id(
context, volume_type)
all_pools = (
self._filter_pools_by_volume_type(context,
volume_type,
all_pools))
# encapsulate pools in format:{name: XXX, capabilities: XXX}
return [dict(name=key, capabilities=value.capabilities)
for key, value in all_pools.items()]
def get_usage_and_notify(self, capa_new, updated_pools, host, timestamp):
context = cinder_context.get_admin_context()

View File

@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from cinder.api.contrib import scheduler_stats
@ -45,6 +46,7 @@ def schedule_rpcapi_get_pools(self, context, filters=None):
return all_pools
@ddt.data
class SchedulerStatsAPITest(test.TestCase):
def setUp(self):
super(SchedulerStatsAPITest, self).setUp()
@ -171,3 +173,34 @@ class SchedulerStatsAPITest(test.TestCase):
self.assertRaises(exception.InvalidParameterValue,
self.controller.get_pools,
req)
@ddt.data(('3.34', False),
('3.35', True))
@ddt.unpack
@mock.patch('cinder.scheduler.rpcapi.SchedulerAPI.get_pools')
@mock.patch('cinder.api.common.update_general_filters')
def test_get_pools_by_volume_type(self,
version,
support_volume_type,
mock_update_filter,
mock_get_pools
):
req = fakes.HTTPRequest.blank('/v3/%s/scheduler_stats?'
'volume_type=lvm' % fake.PROJECT_ID)
mock_get_pools.return_value = [{'name': 'pool1',
'capabilities': {'foo': 'bar'}}]
req.api_version_request = api_version.APIVersionRequest(version)
req.environ['cinder.context'] = self.ctxt
res = self.controller.get_pools(req)
expected = {
'pools': [{'name': 'pool1'}]
}
filters = None
if support_volume_type:
filters = {'volume_type': 'lvm'}
mock_update_filter.assert_called_once_with(self.ctxt, filters,
'pool')
self.assertDictEqual(expected, res)
mock_get_pools.assert_called_with(mock.ANY, filters=filters)

View File

@ -379,11 +379,13 @@ class GeneralFiltersTest(test.TestCase):
@ddt.data({'filters': {'key1': 'value1'},
'is_admin': False,
'result': {'fake_resource': ['key1']},
'expected': {'key1': 'value1'}},
'expected': {'key1': 'value1'},
'resource': 'fake_resource'},
{'filters': {'key1': 'value1', 'key2': 'value2'},
'is_admin': False,
'result': {'fake_resource': ['key1']},
'expected': None},
'expected': None,
'resource': 'fake_resource'},
{'filters': {'key1': 'value1',
'all_tenants': 'value2',
'key3': 'value3'},
@ -391,11 +393,19 @@ class GeneralFiltersTest(test.TestCase):
'result': {'fake_resource': []},
'expected': {'key1': 'value1',
'all_tenants': 'value2',
'key3': 'value3'}})
'key3': 'value3'},
'resource': 'fake_resource'},
{'filters': {'key1': 'value1',
'all_tenants': 'value2',
'key3': 'value3'},
'is_admin': True,
'result': {'pool': []},
'expected': None,
'resource': 'pool'})
@ddt.unpack
@mock.patch('cinder.api.common.get_enabled_resource_filters')
def test_reject_invalid_filters(self, mock_get, filters,
is_admin, result, expected):
is_admin, result, expected, resource):
class FakeContext(object):
def __init__(self, admin):
self.is_admin = admin
@ -404,13 +414,13 @@ class GeneralFiltersTest(test.TestCase):
mock_get.return_value = result
if expected:
common.reject_invalid_filters(fake_context,
filters, 'fake_resource')
filters, resource)
self.assertEqual(expected, filters)
else:
self.assertRaises(
webob.exc.HTTPBadRequest,
common.reject_invalid_filters, fake_context,
filters, 'fake_resource')
filters, resource)
@ddt.ddt

View File

@ -46,6 +46,12 @@ class FakeFilterClass2(filters.BaseBackendFilter):
pass
class FakeFilterClass3(filters.BaseHostFilter):
def host_passes(self, host_state, filter_properties):
return host_state.get('volume_backend_name') == \
filter_properties.get('volume_type')['volume_backend_name']
@ddt.ddt
class HostManagerTestCase(test.TestCase):
"""Test case for HostManager class."""
@ -1020,6 +1026,45 @@ class HostManagerTestCase(test.TestCase):
self.assertEqual(expected, res)
@mock.patch('cinder.scheduler.host_manager.HostManager.'
'_choose_backend_filters')
def test_get_pools_filtered_by_volume_type(self,
_mock_choose_backend_filters):
context = 'fake_context'
filter_class = FakeFilterClass3
_mock_choose_backend_filters.return_value = [filter_class]
hosts = {
'host1': {'volume_backend_name': 'AAA',
'total_capacity_gb': 512,
'free_capacity_gb': 200,
'timestamp': None,
'reserved_percentage': 0,
'provisioned_capacity_gb': 312},
'host2@back1': {'volume_backend_name': 'BBB',
'total_capacity_gb': 256,
'free_capacity_gb': 100,
'timestamp': None,
'reserved_percentage': 0,
'provisioned_capacity_gb': 156}}
mock_warning = mock.Mock()
host_manager.LOG.warn = mock_warning
mock_volume_type = {
'volume_backend_name': 'AAA',
'qos_specs': 'BBB',
}
res = self.host_manager._filter_pools_by_volume_type(context,
mock_volume_type,
hosts)
expected = {'host1': {'volume_backend_name': 'AAA',
'total_capacity_gb': 512,
'free_capacity_gb': 200,
'timestamp': None, 'reserved_percentage': 0,
'provisioned_capacity_gb': 312}}
self.assertEqual(expected, res)
@mock.patch('cinder.db.service_get_all')
@mock.patch('cinder.objects.service.Service.is_up',
new_callable=mock.PropertyMock)

View File

@ -74,5 +74,5 @@ valid for first. The supported APIs are marked with "*" below in the table.
| | id, event_id, resource_uuid, resource_type, request_id, message_level, |
| list message* | project_id |
+-----------------+-------------------------------------------------------------------------+
| get pools | name |
| get pools | name, volume_type |
+-----------------+-------------------------------------------------------------------------+

View File

@ -8,5 +8,5 @@
"attachment": ["volume_id"],
"message": ["resource_uuid", "resource_type", "event_id",
"request_id", "message_level"],
"pool": ["name"]
"pool": ["name", "volume_type"]
}

View File

@ -0,0 +1,3 @@
---
features:
- Add ``volume-type`` filter to API Get-Pools