Merge "Add volume type filter to API Get-Pools"
This commit is contained in:
commit
1f7adde05b
@ -460,8 +460,9 @@ def convert_filter_attributes(filters, resource):
|
|||||||
|
|
||||||
def reject_invalid_filters(context, filters, resource,
|
def reject_invalid_filters(context, filters, resource,
|
||||||
enable_like_filter=False):
|
enable_like_filter=False):
|
||||||
if context.is_admin:
|
if context.is_admin and resource not in ['pool']:
|
||||||
# Allow all options
|
# Allow all options except resource is pool
|
||||||
|
# pool API is only available for admin
|
||||||
return
|
return
|
||||||
# Check the configured filters against those passed in resource
|
# Check the configured filters against those passed in resource
|
||||||
configured_filters = get_enabled_resource_filters(resource)
|
configured_filters = get_enabled_resource_filters(resource)
|
||||||
|
@ -22,6 +22,7 @@ from cinder.scheduler import rpcapi
|
|||||||
from cinder import utils
|
from cinder import utils
|
||||||
|
|
||||||
GET_POOL_NAME_FILTER_MICRO_VERSION = '3.28'
|
GET_POOL_NAME_FILTER_MICRO_VERSION = '3.28'
|
||||||
|
GET_POOL_VOLUME_TYPE_FILTER_MICRO_VERSION = '3.35'
|
||||||
|
|
||||||
|
|
||||||
def authorize(context, action_name):
|
def authorize(context, action_name):
|
||||||
@ -59,6 +60,9 @@ class SchedulerStatsController(wsgi.Controller):
|
|||||||
filters=filters,
|
filters=filters,
|
||||||
req_version=req_version)
|
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)
|
pools = self.scheduler_api.get_pools(context, filters=filters)
|
||||||
|
|
||||||
return self._view_builder.pools(req, pools, detail)
|
return self._view_builder.pools(req, pools, detail)
|
||||||
|
@ -88,6 +88,7 @@ REST_API_VERSION_HISTORY = """
|
|||||||
* 3.34 - Add like filter support in ``volume``, ``backup``, ``snapshot``,
|
* 3.34 - Add like filter support in ``volume``, ``backup``, ``snapshot``,
|
||||||
``message``, ``attachment``, ``group`` and ``group-snapshot``
|
``message``, ``attachment``, ``group`` and ``group-snapshot``
|
||||||
list APIs.
|
list APIs.
|
||||||
|
* 3.35 - Add ``volume-type`` filter to Get-Pools API.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ REST_API_VERSION_HISTORY = """
|
|||||||
# minimum version of the API supported.
|
# minimum version of the API supported.
|
||||||
# Explicitly using /v1 or /v2 endpoints will still work
|
# Explicitly using /v1 or /v2 endpoints will still work
|
||||||
_MIN_API_VERSION = "3.0"
|
_MIN_API_VERSION = "3.0"
|
||||||
_MAX_API_VERSION = "3.34"
|
_MAX_API_VERSION = "3.35"
|
||||||
_LEGACY_API_VERSION1 = "1.0"
|
_LEGACY_API_VERSION1 = "1.0"
|
||||||
_LEGACY_API_VERSION2 = "2.0"
|
_LEGACY_API_VERSION2 = "2.0"
|
||||||
|
|
||||||
|
@ -313,3 +313,7 @@ user documentation.
|
|||||||
----
|
----
|
||||||
Add like filter support in ``volume``, ``backup``, ``snapshot``, ``message``,
|
Add like filter support in ``volume``, ``backup``, ``snapshot``, ``message``,
|
||||||
``attachment``, ``group`` and ``group-snapshot`` list APIs.
|
``attachment``, ``group`` and ``group-snapshot`` list APIs.
|
||||||
|
|
||||||
|
3.35
|
||||||
|
----
|
||||||
|
Add ``volume-type`` filter to Get-Pools API.
|
||||||
|
@ -32,6 +32,7 @@ from cinder import objects
|
|||||||
from cinder.scheduler import filters
|
from cinder.scheduler import filters
|
||||||
from cinder import utils
|
from cinder import utils
|
||||||
from cinder.volume import utils as vol_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
|
# FIXME: This file should be renamed to backend_manager, we should also rename
|
||||||
@ -627,15 +628,33 @@ class HostManager(object):
|
|||||||
|
|
||||||
return all_pools.values()
|
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):
|
def get_pools(self, context, filters=None):
|
||||||
"""Returns a dict of all pools on all hosts HostManager knows about."""
|
"""Returns a dict of all pools on all hosts HostManager knows about."""
|
||||||
|
|
||||||
self._update_backend_state_map(context)
|
self._update_backend_state_map(context)
|
||||||
|
|
||||||
all_pools = []
|
all_pools = {}
|
||||||
name = None
|
name = volume_type = None
|
||||||
if filters:
|
if filters:
|
||||||
name = filters.pop('name', None)
|
name = filters.pop('name', None)
|
||||||
|
volume_type = filters.pop('volume_type', None)
|
||||||
|
|
||||||
for backend_key, state in self.backend_state_map.items():
|
for backend_key, state in self.backend_state_map.items():
|
||||||
for key in state.pools:
|
for key in state.pools:
|
||||||
@ -658,9 +677,20 @@ class HostManager(object):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not filtered:
|
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):
|
def get_usage_and_notify(self, capa_new, updated_pools, host, timestamp):
|
||||||
context = cinder_context.get_admin_context()
|
context = cinder_context.get_admin_context()
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from cinder.api.contrib import scheduler_stats
|
from cinder.api.contrib import scheduler_stats
|
||||||
@ -45,6 +46,7 @@ def schedule_rpcapi_get_pools(self, context, filters=None):
|
|||||||
return all_pools
|
return all_pools
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.data
|
||||||
class SchedulerStatsAPITest(test.TestCase):
|
class SchedulerStatsAPITest(test.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(SchedulerStatsAPITest, self).setUp()
|
super(SchedulerStatsAPITest, self).setUp()
|
||||||
@ -171,3 +173,34 @@ class SchedulerStatsAPITest(test.TestCase):
|
|||||||
self.assertRaises(exception.InvalidParameterValue,
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
self.controller.get_pools,
|
self.controller.get_pools,
|
||||||
req)
|
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)
|
||||||
|
@ -379,11 +379,13 @@ class GeneralFiltersTest(test.TestCase):
|
|||||||
@ddt.data({'filters': {'key1': 'value1'},
|
@ddt.data({'filters': {'key1': 'value1'},
|
||||||
'is_admin': False,
|
'is_admin': False,
|
||||||
'result': {'fake_resource': ['key1']},
|
'result': {'fake_resource': ['key1']},
|
||||||
'expected': {'key1': 'value1'}},
|
'expected': {'key1': 'value1'},
|
||||||
|
'resource': 'fake_resource'},
|
||||||
{'filters': {'key1': 'value1', 'key2': 'value2'},
|
{'filters': {'key1': 'value1', 'key2': 'value2'},
|
||||||
'is_admin': False,
|
'is_admin': False,
|
||||||
'result': {'fake_resource': ['key1']},
|
'result': {'fake_resource': ['key1']},
|
||||||
'expected': None},
|
'expected': None,
|
||||||
|
'resource': 'fake_resource'},
|
||||||
{'filters': {'key1': 'value1',
|
{'filters': {'key1': 'value1',
|
||||||
'all_tenants': 'value2',
|
'all_tenants': 'value2',
|
||||||
'key3': 'value3'},
|
'key3': 'value3'},
|
||||||
@ -391,11 +393,19 @@ class GeneralFiltersTest(test.TestCase):
|
|||||||
'result': {'fake_resource': []},
|
'result': {'fake_resource': []},
|
||||||
'expected': {'key1': 'value1',
|
'expected': {'key1': 'value1',
|
||||||
'all_tenants': 'value2',
|
'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
|
@ddt.unpack
|
||||||
@mock.patch('cinder.api.common.get_enabled_resource_filters')
|
@mock.patch('cinder.api.common.get_enabled_resource_filters')
|
||||||
def test_reject_invalid_filters(self, mock_get, filters,
|
def test_reject_invalid_filters(self, mock_get, filters,
|
||||||
is_admin, result, expected):
|
is_admin, result, expected, resource):
|
||||||
class FakeContext(object):
|
class FakeContext(object):
|
||||||
def __init__(self, admin):
|
def __init__(self, admin):
|
||||||
self.is_admin = admin
|
self.is_admin = admin
|
||||||
@ -404,13 +414,13 @@ class GeneralFiltersTest(test.TestCase):
|
|||||||
mock_get.return_value = result
|
mock_get.return_value = result
|
||||||
if expected:
|
if expected:
|
||||||
common.reject_invalid_filters(fake_context,
|
common.reject_invalid_filters(fake_context,
|
||||||
filters, 'fake_resource')
|
filters, resource)
|
||||||
self.assertEqual(expected, filters)
|
self.assertEqual(expected, filters)
|
||||||
else:
|
else:
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
webob.exc.HTTPBadRequest,
|
webob.exc.HTTPBadRequest,
|
||||||
common.reject_invalid_filters, fake_context,
|
common.reject_invalid_filters, fake_context,
|
||||||
filters, 'fake_resource')
|
filters, resource)
|
||||||
|
|
||||||
@ddt.data({'filters': {'name': 'value1'},
|
@ddt.data({'filters': {'name': 'value1'},
|
||||||
'is_admin': False,
|
'is_admin': False,
|
||||||
|
@ -46,6 +46,12 @@ class FakeFilterClass2(filters.BaseBackendFilter):
|
|||||||
pass
|
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
|
@ddt.ddt
|
||||||
class HostManagerTestCase(test.TestCase):
|
class HostManagerTestCase(test.TestCase):
|
||||||
"""Test case for HostManager class."""
|
"""Test case for HostManager class."""
|
||||||
@ -1020,6 +1026,45 @@ class HostManagerTestCase(test.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(expected, res)
|
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.db.service_get_all')
|
||||||
@mock.patch('cinder.objects.service.Service.is_up',
|
@mock.patch('cinder.objects.service.Service.is_up',
|
||||||
new_callable=mock.PropertyMock)
|
new_callable=mock.PropertyMock)
|
||||||
|
@ -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, |
|
| | id, event_id, resource_uuid, resource_type, request_id, message_level, |
|
||||||
| list message* | project_id |
|
| list message* | project_id |
|
||||||
+-----------------+-------------------------------------------------------------------------+
|
+-----------------+-------------------------------------------------------------------------+
|
||||||
| get pools | name |
|
| get pools | name, volume_type |
|
||||||
+-----------------+-------------------------------------------------------------------------+
|
+-----------------+-------------------------------------------------------------------------+
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"attachment": ["volume_id"],
|
"attachment": ["volume_id"],
|
||||||
"message": ["resource_uuid", "resource_type", "event_id",
|
"message": ["resource_uuid", "resource_type", "event_id",
|
||||||
"request_id", "message_level"],
|
"request_id", "message_level"],
|
||||||
"pool": ["name"]
|
"pool": ["name", "volume_type"]
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Add ``volume-type`` filter to API Get-Pools
|
Loading…
x
Reference in New Issue
Block a user