Merge "OnlyHostFilter allows user to specify host during share create."
This commit is contained in:
commit
d1509256da
@ -2323,9 +2323,10 @@ revert_to_snapshot_support_share_capability:
|
||||
min_version: 2.27
|
||||
scheduler_hints:
|
||||
description: |
|
||||
One or more scheduler_hints key and value pairs as a dictionary
|
||||
of strings. e.g. keys are same_host, different_host and values must be
|
||||
a comma separated list of Share IDs.
|
||||
One or more scheduler_hints key and value pairs as a dictionary of
|
||||
strings. Accepted hints are:
|
||||
- ``same_host`` or ``different_host``: values must be a comma separated list of Share IDs
|
||||
- ``only_host``: value must be a manage-share service host in ``host@backend#POOL`` format (admin only)
|
||||
in: body
|
||||
required: false
|
||||
type: object
|
||||
|
@ -170,15 +170,18 @@ REST_API_VERSION_HISTORY = """
|
||||
actions on the share network's endpoint:
|
||||
'update_security_service', 'update_security_service_check' and
|
||||
'add_security_service_check'.
|
||||
* 2.65 - Added ability to set scheduler hints via the share create API.
|
||||
* 2.65 - Added ability to set affinity scheduler hints via the share
|
||||
create API.
|
||||
* 2.66 - Added filter search by group spec for share group type list.
|
||||
* 2.67 - Added ability to set 'only_host' scheduler hint via the share
|
||||
create API.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
# The default api version request is defined to be the
|
||||
# minimum version of the API supported.
|
||||
_MIN_API_VERSION = "2.0"
|
||||
_MAX_API_VERSION = "2.66"
|
||||
_MAX_API_VERSION = "2.67"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
|
||||
|
@ -367,3 +367,9 @@ user documentation.
|
||||
2.66
|
||||
----
|
||||
Added filter search by group spec for share group type list.
|
||||
|
||||
2.67
|
||||
____
|
||||
Added supprot for 'only_host' key in "scheduler_hints" in the request body
|
||||
of the POST/shares request. This hint will invoke OnlyHost scheduler
|
||||
filter during share creation.
|
||||
|
@ -186,6 +186,9 @@ class ShareController(shares.ShareMixin,
|
||||
|
||||
share = body['share']
|
||||
scheduler_hints = share.pop('scheduler_hints', None)
|
||||
if req.api_version_request < api_version.APIVersionRequest("2.67"):
|
||||
if scheduler_hints:
|
||||
scheduler_hints.pop('only_host', None)
|
||||
return self._create(req, body,
|
||||
check_create_share_from_snapshot_support=True,
|
||||
check_availability_zones_extra_spec=True,
|
||||
|
@ -23,7 +23,6 @@ filters and weighing functions.
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
|
||||
from manila.db import api as db_api
|
||||
from manila import exception
|
||||
from manila.i18n import _
|
||||
from manila.message import api as message_api
|
||||
@ -322,15 +321,29 @@ class FilterScheduler(base.Scheduler):
|
||||
"exc": "exc"
|
||||
})
|
||||
|
||||
def populate_filter_properties_share_scheduler_hint(self, context,
|
||||
share_id, hints,
|
||||
key, hint):
|
||||
try:
|
||||
result = db_api.share_metadata_get_item(context, share_id, key)
|
||||
except exception.ShareMetadataNotFound:
|
||||
pass
|
||||
def _populate_scheduler_hint(self, request_spec, hints, key, hint):
|
||||
share_properties = request_spec.get('share_properties', {})
|
||||
value = share_properties.get('metadata', {}).get(key, None)
|
||||
if value:
|
||||
hints.update({hint: value})
|
||||
|
||||
def populate_filter_properties_scheduler_hints(self, context, request_spec,
|
||||
filter_properties):
|
||||
share_id = request_spec.get('share_id', None)
|
||||
if not share_id:
|
||||
filter_properties['scheduler_hints'] = {}
|
||||
return
|
||||
else:
|
||||
hints.update({hint: result.get(key)})
|
||||
if filter_properties.get('scheduler_hints', None):
|
||||
return
|
||||
hints = {}
|
||||
self._populate_scheduler_hint(request_spec, hints,
|
||||
AFFINITY_KEY,
|
||||
AFFINITY_HINT)
|
||||
self._populate_scheduler_hint(request_spec, hints,
|
||||
ANTI_AFFINITY_KEY,
|
||||
ANTI_AFFINITY_HINT)
|
||||
filter_properties['scheduler_hints'] = hints
|
||||
|
||||
def populate_filter_properties_share(self, context, request_spec,
|
||||
filter_properties):
|
||||
@ -347,25 +360,8 @@ class FilterScheduler(base.Scheduler):
|
||||
filter_properties['user_id'] = shr.get('user_id')
|
||||
filter_properties['metadata'] = shr.get('metadata')
|
||||
filter_properties['snapshot_id'] = shr.get('snapshot_id')
|
||||
|
||||
share_id = request_spec.get('share_id', None)
|
||||
if not share_id:
|
||||
filter_properties['scheduler_hints'] = {}
|
||||
return
|
||||
|
||||
try:
|
||||
db_api.share_get(context, share_id)
|
||||
except exception.NotFound:
|
||||
filter_properties['scheduler_hints'] = {}
|
||||
else:
|
||||
hints = {}
|
||||
self.populate_filter_properties_share_scheduler_hint(
|
||||
context, share_id, hints,
|
||||
AFFINITY_KEY, AFFINITY_HINT)
|
||||
self.populate_filter_properties_share_scheduler_hint(
|
||||
context, share_id, hints,
|
||||
ANTI_AFFINITY_KEY, ANTI_AFFINITY_HINT)
|
||||
filter_properties['scheduler_hints'] = hints
|
||||
self.populate_filter_properties_scheduler_hints(context, request_spec,
|
||||
filter_properties)
|
||||
|
||||
def schedule_create_share_group(self, context, share_group_id,
|
||||
request_spec, filter_properties):
|
||||
|
33
manila/scheduler/filters/host.py
Normal file
33
manila/scheduler/filters/host.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright 2021 Cloudification GmbH.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manila.scheduler.filters import base_host
|
||||
|
||||
|
||||
class OnlyHostFilter(base_host.BaseHostFilter):
|
||||
"""Filters Hosts by 'only_host' scheduler_hint."""
|
||||
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
context = filter_properties['context']
|
||||
if not context.is_admin:
|
||||
return True
|
||||
hints = filter_properties.get('scheduler_hints')
|
||||
if hints is None:
|
||||
return True
|
||||
requested_host = hints.get('only_host', None)
|
||||
if requested_host is None:
|
||||
return True
|
||||
# e.g. "only_host=hostname@generic2#GENERIC2"
|
||||
return host_state.host == requested_host
|
@ -42,6 +42,7 @@ from manila import utils
|
||||
host_manager_opts = [
|
||||
cfg.ListOpt('scheduler_default_filters',
|
||||
default=[
|
||||
'OnlyHostFilter',
|
||||
'AvailabilityZoneFilter',
|
||||
'CapacityFilter',
|
||||
'CapabilitiesFilter',
|
||||
|
@ -884,7 +884,8 @@ class API(base.Base):
|
||||
export_location_path)
|
||||
|
||||
request_spec = self._get_request_spec_dict(
|
||||
share, share_type, size=0, share_proto=share_data['share_proto'],
|
||||
share, share_type, context, size=0,
|
||||
share_proto=share_data['share_proto'],
|
||||
host=share_data['host'])
|
||||
|
||||
# NOTE(ganso): Scheduler is called to validate if share type
|
||||
@ -895,7 +896,7 @@ class API(base.Base):
|
||||
|
||||
return self.db.share_get(context, share['id'])
|
||||
|
||||
def _get_request_spec_dict(self, share, share_type, **kwargs):
|
||||
def _get_request_spec_dict(self, share, share_type, context, **kwargs):
|
||||
|
||||
if share is None:
|
||||
share = {'instance': {}}
|
||||
@ -908,6 +909,8 @@ class API(base.Base):
|
||||
'size': kwargs.get('size', share.get('size')),
|
||||
'user_id': kwargs.get('user_id', share.get('user_id')),
|
||||
'project_id': kwargs.get('project_id', share.get('project_id')),
|
||||
'metadata': self.db.share_metadata_get(
|
||||
context, share_instance.get('share_id')),
|
||||
'snapshot_support': kwargs.get(
|
||||
'snapshot_support',
|
||||
share_type.get('extra_specs', {}).get('snapshot_support')
|
||||
@ -1548,6 +1551,7 @@ class API(base.Base):
|
||||
request_spec = self._get_request_spec_dict(
|
||||
share,
|
||||
share_type,
|
||||
context,
|
||||
availability_zone_id=service['availability_zone_id'],
|
||||
share_network_id=new_share_network_id)
|
||||
|
||||
@ -2323,7 +2327,8 @@ class API(base.Base):
|
||||
else:
|
||||
share_type = share_types.get_share_type(
|
||||
context, share['instance']['share_type_id'])
|
||||
request_spec = self._get_request_spec_dict(share, share_type)
|
||||
request_spec = self._get_request_spec_dict(share, share_type,
|
||||
context)
|
||||
self.scheduler_rpcapi.extend_share(context, share['id'], new_size,
|
||||
reservations, request_spec)
|
||||
LOG.info("Extend share request issued successfully.",
|
||||
@ -2474,7 +2479,7 @@ class API(base.Base):
|
||||
share_type_id = share_instance['share_type_id']
|
||||
share_type = share_types.get_share_type(context, share_type_id)
|
||||
req_spec = self._get_request_spec_dict(share_instance,
|
||||
share_type,
|
||||
share_type, context,
|
||||
**kwargs)
|
||||
shares_req_spec.append(req_spec)
|
||||
|
||||
|
63
manila/tests/scheduler/filters/test_host.py
Normal file
63
manila/tests/scheduler/filters/test_host.py
Normal file
@ -0,0 +1,63 @@
|
||||
# Copyright 2021 Cloudification GmbH.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
|
||||
from manila import context
|
||||
from manila.scheduler.filters import host
|
||||
from manila import test
|
||||
from manila.tests.scheduler import fakes
|
||||
|
||||
|
||||
fake_host1 = fakes.FakeHostState('host1', {})
|
||||
fake_host2 = fakes.FakeHostState('host2', {})
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class OnlyHostFilterTestCase(test.TestCase):
|
||||
"""Test case for OnlyHostFilter."""
|
||||
|
||||
def setUp(self):
|
||||
super(OnlyHostFilterTestCase, self).setUp()
|
||||
self.filter = host.OnlyHostFilter()
|
||||
self.user_context = context.RequestContext('user', 'project')
|
||||
self.admin_context = context.RequestContext('user', 'project',
|
||||
is_admin=True)
|
||||
|
||||
def _make_filter_properties(self, hint):
|
||||
return {
|
||||
'context': self.admin_context,
|
||||
'scheduler_hints': hint,
|
||||
}
|
||||
|
||||
@ddt.data((fake_host1, {'scheduler_hints': None}),
|
||||
(fake_host1, {'scheduler_hints': {}}),
|
||||
(fake_host1,
|
||||
{'scheduler_hints': {'only_host': fake_host2.host}}))
|
||||
@ddt.unpack
|
||||
def test_only_host_filter_user_context(self, host, filter_properties):
|
||||
context = {'context': self.user_context}
|
||||
filter_properties.update(context)
|
||||
self.assertTrue(self.filter.host_passes(host, filter_properties))
|
||||
|
||||
@ddt.data((fake_host1, None, True),
|
||||
(fake_host1, {}, True),
|
||||
(fake_host1, {'only_host': fake_host1.host}, True),
|
||||
(fake_host2, {'only_host': fake_host1.host}, False))
|
||||
@ddt.unpack
|
||||
def test_only_host_filter_admin_context(self, host, hint, host_passes):
|
||||
filter_properties = self._make_filter_properties(hint)
|
||||
self.assertEqual(host_passes,
|
||||
self.filter.host_passes(host, filter_properties))
|
@ -1170,7 +1170,8 @@ class ShareAPITestCase(test.TestCase):
|
||||
})
|
||||
|
||||
expected_request_spec = self._get_request_spec_dict(
|
||||
share, fake_type, size=0, share_proto=share_data['share_proto'],
|
||||
share, fake_type, self.context, size=0,
|
||||
share_proto=share_data['share_proto'],
|
||||
host=share_data['host'])
|
||||
|
||||
if dhss:
|
||||
@ -1378,7 +1379,7 @@ class ShareAPITestCase(test.TestCase):
|
||||
self.assertRaises(exception.InvalidShare, self.api.manage,
|
||||
self.context, share_data, driver_options)
|
||||
|
||||
def _get_request_spec_dict(self, share, share_type, **kwargs):
|
||||
def _get_request_spec_dict(self, share, share_type, context, **kwargs):
|
||||
|
||||
if share is None:
|
||||
share = {'instance': {}}
|
||||
@ -1389,6 +1390,7 @@ class ShareAPITestCase(test.TestCase):
|
||||
'size': kwargs.get('size', share.get('size')),
|
||||
'user_id': kwargs.get('user_id', share.get('user_id')),
|
||||
'project_id': kwargs.get('project_id', share.get('project_id')),
|
||||
'metadata': db_api.share_metadata_get(context, share['id']),
|
||||
'snapshot_support': kwargs.get(
|
||||
'snapshot_support',
|
||||
share_type['extra_specs']['snapshot_support']),
|
||||
@ -3314,7 +3316,8 @@ class ShareAPITestCase(test.TestCase):
|
||||
share_network_id=share_network_id)
|
||||
|
||||
request_spec = self._get_request_spec_dict(
|
||||
share, fake_type_2, size=0, availability_zone_id='fake_az_id',
|
||||
share, fake_type_2, self.context, size=0,
|
||||
availability_zone_id='fake_az_id',
|
||||
share_network_id=share_network_id)
|
||||
|
||||
self.mock_object(self.scheduler_rpcapi, 'migrate_share_to_host')
|
||||
@ -3551,7 +3554,7 @@ class ShareAPITestCase(test.TestCase):
|
||||
share_type_id=fake_type['id'])
|
||||
|
||||
request_spec = self._get_request_spec_dict(
|
||||
share, fake_type, availability_zone_id='fake_az_id')
|
||||
share, fake_type, self.context, availability_zone_id='fake_az_id')
|
||||
|
||||
self.api.migration_start(self.context, share, host, False, True, True,
|
||||
True, True)
|
||||
@ -5093,7 +5096,7 @@ class ShareAPITestCase(test.TestCase):
|
||||
get_type_calls.append(
|
||||
mock.call(self.context, instance['share_type_id']))
|
||||
get_request_spec_calls.append(
|
||||
mock.call(instance, fake_share_type))
|
||||
mock.call(instance, fake_share_type, self.context))
|
||||
|
||||
mock_get_type = self.mock_object(
|
||||
share_types, 'get_share_type',
|
||||
|
10
releasenotes/notes/hostonly-filter-1a17a70dd0aafb86.yaml
Normal file
10
releasenotes/notes/hostonly-filter-1a17a70dd0aafb86.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
features:
|
||||
- Add OnlyHostFilter to manila's scheduler. This filter needs admin to
|
||||
specify host@backend#pool to "share.scheduler_hints.only_host" in the
|
||||
request payload when creating a manila share. The hint is used only
|
||||
for share creation and not stored as share metadata. For non-admin users
|
||||
the OnlyHostFilter will always be ignored.
|
||||
upgrade:
|
||||
- To add OnlyHostFilter to an active deployment, its reference must be
|
||||
enabled in manila.conf.
|
@ -46,6 +46,7 @@ wsgi_scripts =
|
||||
manila.scheduler.filters =
|
||||
AffinityFilter = manila.scheduler.filters.affinity:AffinityFilter
|
||||
AntiAffinityFilter = manila.scheduler.filters.affinity:AntiAffinityFilter
|
||||
OnlyHostFilter = manila.scheduler.filters.host:OnlyHostFilter
|
||||
AvailabilityZoneFilter = manila.scheduler.filters.availability_zone:AvailabilityZoneFilter
|
||||
CapabilitiesFilter = manila.scheduler.filters.capabilities:CapabilitiesFilter
|
||||
CapacityFilter = manila.scheduler.filters.capacity:CapacityFilter
|
||||
|
Loading…
Reference in New Issue
Block a user