Add --scheduler_hints to share create command

This commit add --scheduler_hints option to share create command. Users
can specify affinity/anti-affinity share ids to share create command as
value in <key=value> pairs of scheduler hints. The possible keys are
same_host and different_host. Available from microversion 2.65.

Partially-implements: bp affinity-antiaffinity-filter
Change-Id: I8a9598eb16f08ed6539e8996e28cfc6e19586483
This commit is contained in:
kpdev 2021-08-29 12:29:55 +02:00
parent cc440609bd
commit 9a2bcb8d87
9 changed files with 144 additions and 9 deletions

View File

@ -27,7 +27,7 @@ from manilaclient import utils
LOG = logging.getLogger(__name__)
MAX_VERSION = '2.63'
MAX_VERSION = '2.65'
MIN_VERSION = '2.0'
DEPRECATED_VERSION = '1.0'
_VERSIONED_METHOD_MAP = {}

View File

@ -179,6 +179,16 @@ class CreateShare(command.ShowOne):
default=False,
help=_('Wait for share creation')
)
parser.add_argument(
"--scheduler-hint",
metavar="<key=value>",
default={},
action=parseractions.KeyValueAction,
help=_("Set Scheduler hints for the share as key=value pairs, "
"possible keys are same_host, different_host."
"(repeat option to set multiple hints)"),
)
return parser
def take_action(self, parsed_args):
@ -210,6 +220,33 @@ class CreateShare(command.ShowOne):
snapshot_id = snapshot.id
size = max(size or 0, snapshot.size)
scheduler_hints = {}
if parsed_args.scheduler_hint:
if share_client.api_version < api_versions.APIVersion('2.65'):
raise exceptions.CommandError(
'Setting share scheduler hints for a share is '
'available only for API microversion >= 2.65')
else:
scheduler_hints = utils.extract_key_value_options(
parsed_args.scheduler_hint)
same_host_hint_shares = scheduler_hints.get('same_host')
different_host_hint_shares = scheduler_hints.get(
'different_host')
if same_host_hint_shares:
same_host_hint_shares = [
apiutils.find_resource(share_client.shares, sh).id
for sh in same_host_hint_shares.split(',')
]
scheduler_hints['same_host'] = (
','.join(same_host_hint_shares))
if different_host_hint_shares:
different_host_hint_shares = [
apiutils.find_resource(share_client.shares, sh).id
for sh in different_host_hint_shares.split(',')
]
scheduler_hints['different_host'] = (
','.join(different_host_hint_shares))
body = {
'share_proto': parsed_args.share_proto,
'size': size,
@ -221,7 +258,8 @@ class CreateShare(command.ShowOne):
'share_type': share_type,
'is_public': parsed_args.public,
'availability_zone': parsed_args.availability_zone,
'share_group_id': share_group
'share_group_id': share_group,
'scheduler_hints': scheduler_hints
}
share = share_client.shares.create(**body)

View File

@ -126,6 +126,7 @@ class FakeShare(object):
"mount_snapshot_support": False,
"revert_to_snapshot_support": False,
"source_share_group_snapshot_member_id": None,
"scheduler_hints": {},
}
# Overwrite default attributes.

View File

@ -116,7 +116,8 @@ class TestShareCreate(TestShare):
share_proto=self.new_share.share_proto,
share_type=None,
size=self.new_share.size,
snapshot_id=None
snapshot_id=None,
scheduler_hints={}
)
self.assertCountEqual(self.columns, columns)
@ -161,7 +162,52 @@ class TestShareCreate(TestShare):
share_proto=self.new_share.share_proto,
share_type=None,
size=self.new_share.size,
snapshot_id=None
snapshot_id=None,
scheduler_hints={}
)
self.assertCountEqual(self.columns, columns)
self.assertCountEqual(self.datalist, data)
def test_share_create_scheduler_hints(self):
"""Verifies scheduler hints are parsed correctly."""
self.app.client_manager.share.api_version = api_versions.APIVersion(
"2.65")
shares = self.setup_shares_mock(count=2)
share1_name = shares[0].name
share2_name = shares[1].name
arglist = [
self.new_share.share_proto,
str(self.new_share.size),
'--scheduler-hint', ('same_host=%s' % share1_name),
'--scheduler-hint', ('different_host=%s' % share2_name),
]
verifylist = [
('share_proto', self.new_share.share_proto),
('size', self.new_share.size),
('scheduler_hint',
{'same_host': share1_name, 'different_host': share2_name}),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.shares_mock.create.assert_called_with(
availability_zone=None,
description=None,
is_public=False,
metadata={},
name=None,
share_group_id=None,
share_network=None,
share_proto=self.new_share.share_proto,
share_type=None,
size=self.new_share.size,
snapshot_id=None,
scheduler_hints={'same_host': shares[0].id,
'different_host': shares[1].id},
)
self.assertCountEqual(self.columns, columns)
@ -197,7 +243,8 @@ class TestShareCreate(TestShare):
share_proto=self.new_share.share_proto,
share_type=None,
size=self.new_share.size,
snapshot_id=self.share_snapshot.id
snapshot_id=self.share_snapshot.id,
scheduler_hints={}
)
self.assertCountEqual(self.columns, columns)
@ -231,7 +278,8 @@ class TestShareCreate(TestShare):
share_proto=self.new_share.share_proto,
share_type=None,
size=self.new_share.size,
snapshot_id=None
snapshot_id=None,
scheduler_hints={}
)
self.shares_mock.get.assert_called_with(self.new_share.id)
@ -268,7 +316,8 @@ class TestShareCreate(TestShare):
share_proto=self.new_share.share_proto,
share_type=None,
size=self.new_share.size,
snapshot_id=None
snapshot_id=None,
scheduler_hints={}
)
mock_logger.error.assert_called_with(

View File

@ -67,6 +67,7 @@ class SharesTest(utils.TestCase):
'share_type': None,
'is_public': False,
'availability_zone': None,
'scheduler_hints': dict(),
}
cs.shares.create(protocol, 1)
cs.assert_called('POST', '/shares', {'share': expected})
@ -87,6 +88,7 @@ class SharesTest(utils.TestCase):
'share_type': None,
'is_public': False,
'availability_zone': None,
'scheduler_hints': dict(),
}
cs.shares.create('nfs', 1, share_network=share_network)
cs.assert_called('POST', '/shares', {'share': expected})
@ -107,6 +109,7 @@ class SharesTest(utils.TestCase):
'share_type': 'fake_st',
'is_public': False,
'availability_zone': None,
'scheduler_hints': dict(),
}
cs.shares.create('nfs', 1, share_type=share_type)
cs.assert_called('POST', '/shares', {'share': expected})
@ -130,6 +133,7 @@ class SharesTest(utils.TestCase):
'share_network_id': None,
'size': 1,
'availability_zone': availability_zone,
'scheduler_hints': {},
}
}
cs.shares.create('nfs', 1, is_public=is_public,

View File

@ -86,6 +86,7 @@ class ShellTest(test_utils.TestCase):
"size": 1,
"is_public": False,
"availability_zone": None,
"scheduler_hints": {},
}
}

View File

@ -120,7 +120,7 @@ class ShareManager(base.ManagerWithFind):
def create(self, share_proto, size, snapshot_id=None, name=None,
description=None, metadata=None, share_network=None,
share_type=None, is_public=False, availability_zone=None,
share_group_id=None):
share_group_id=None, scheduler_hints=None):
"""Create a share.
:param share_proto: text - share protocol for new share available
@ -135,9 +135,14 @@ class ShareManager(base.ManagerWithFind):
:param is_public: bool, whether to set share as public or not.
:param share_group_id: text - ID of the share group to which the share
should belong
:param scheduler_hints: dict - hints for the scheduler to place share
on most appropriate host e.g. keys are same_host for affinity and
different_host for anti-affinity
:rtype: :class:`Share`
"""
share_metadata = metadata if metadata is not None else dict()
scheduler_hints = (scheduler_hints if scheduler_hints is not None
else dict())
body = {
'size': size,
'snapshot_id': snapshot_id,
@ -149,6 +154,7 @@ class ShareManager(base.ManagerWithFind):
'share_type': common_base.getid(share_type),
'is_public': is_public,
'availability_zone': availability_zone,
'scheduler_hints': scheduler_hints,
}
if share_group_id:
body['share_group_id'] = share_group_id

View File

@ -904,6 +904,14 @@ def do_rate_limits(cs, args):
'--wait',
action='store_true',
help='Wait for share creation')
@cliutils.arg(
'--scheduler-hints', '--scheduler_hints', '--sh',
metavar='<key=value>',
nargs='*',
help='Scheduler hints for the share as key=value pairs, '
'possible keys are same_host, different_host, '
'value must be share_name or share_id.',
default=None)
@cliutils.service_type('sharev2')
def do_create(cs, args):
"""Creates a new share (NFS, CIFS, CephFS, GlusterFS, HDFS or MAPRFS)."""
@ -928,6 +936,25 @@ def do_create(cs, args):
raise exceptions.CommandError(
"Share name cannot be with the value 'None'")
scheduler_hints = {}
if args.scheduler_hints:
scheduler_hints = _extract_key_value_options(args, 'scheduler_hints')
same_host_hint_shares = scheduler_hints.get('same_host')
different_host_hint_shares = scheduler_hints.get('different_host')
if same_host_hint_shares:
same_host_hint_shares = [
_find_share(cs, sh).id
for sh in same_host_hint_shares.split(',')
]
scheduler_hints['same_host'] = ','.join(same_host_hint_shares)
if different_host_hint_shares:
different_host_hint_shares = [
_find_share(cs, sh).id
for sh in different_host_hint_shares.split(',')
]
scheduler_hints['different_host'] = ','.join(
different_host_hint_shares)
share = cs.shares.create(args.share_protocol, args.size, snapshot,
args.name, args.description,
metadata=share_metadata,
@ -935,7 +962,8 @@ def do_create(cs, args):
share_type=args.share_type,
is_public=args.public,
availability_zone=args.availability_zone,
share_group_id=share_group)
share_group_id=share_group,
scheduler_hints=scheduler_hints)
if args.wait:
share = _wait_for_share_status(cs, share)

View File

@ -0,0 +1,8 @@
---
features:
- Added --scheduler_hints to the share create command
upgrade:
- Scheduler hints in the share create command allow scheduler to select
appropriate host using hard affinity and anti-affinity filters. User needs
to specify affinity/anti-affinity share ids using keys "same_host" or
"different_host" when creating a manila share.