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:
parent
cc440609bd
commit
9a2bcb8d87
@ -27,7 +27,7 @@ from manilaclient import utils
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_VERSION = '2.63'
|
MAX_VERSION = '2.65'
|
||||||
MIN_VERSION = '2.0'
|
MIN_VERSION = '2.0'
|
||||||
DEPRECATED_VERSION = '1.0'
|
DEPRECATED_VERSION = '1.0'
|
||||||
_VERSIONED_METHOD_MAP = {}
|
_VERSIONED_METHOD_MAP = {}
|
||||||
|
@ -179,6 +179,16 @@ class CreateShare(command.ShowOne):
|
|||||||
default=False,
|
default=False,
|
||||||
help=_('Wait for share creation')
|
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
|
return parser
|
||||||
|
|
||||||
def take_action(self, parsed_args):
|
def take_action(self, parsed_args):
|
||||||
@ -210,6 +220,33 @@ class CreateShare(command.ShowOne):
|
|||||||
snapshot_id = snapshot.id
|
snapshot_id = snapshot.id
|
||||||
size = max(size or 0, snapshot.size)
|
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 = {
|
body = {
|
||||||
'share_proto': parsed_args.share_proto,
|
'share_proto': parsed_args.share_proto,
|
||||||
'size': size,
|
'size': size,
|
||||||
@ -221,7 +258,8 @@ class CreateShare(command.ShowOne):
|
|||||||
'share_type': share_type,
|
'share_type': share_type,
|
||||||
'is_public': parsed_args.public,
|
'is_public': parsed_args.public,
|
||||||
'availability_zone': parsed_args.availability_zone,
|
'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)
|
share = share_client.shares.create(**body)
|
||||||
|
@ -126,6 +126,7 @@ class FakeShare(object):
|
|||||||
"mount_snapshot_support": False,
|
"mount_snapshot_support": False,
|
||||||
"revert_to_snapshot_support": False,
|
"revert_to_snapshot_support": False,
|
||||||
"source_share_group_snapshot_member_id": None,
|
"source_share_group_snapshot_member_id": None,
|
||||||
|
"scheduler_hints": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Overwrite default attributes.
|
# Overwrite default attributes.
|
||||||
|
@ -116,7 +116,8 @@ class TestShareCreate(TestShare):
|
|||||||
share_proto=self.new_share.share_proto,
|
share_proto=self.new_share.share_proto,
|
||||||
share_type=None,
|
share_type=None,
|
||||||
size=self.new_share.size,
|
size=self.new_share.size,
|
||||||
snapshot_id=None
|
snapshot_id=None,
|
||||||
|
scheduler_hints={}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertCountEqual(self.columns, columns)
|
self.assertCountEqual(self.columns, columns)
|
||||||
@ -161,7 +162,52 @@ class TestShareCreate(TestShare):
|
|||||||
share_proto=self.new_share.share_proto,
|
share_proto=self.new_share.share_proto,
|
||||||
share_type=None,
|
share_type=None,
|
||||||
size=self.new_share.size,
|
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)
|
self.assertCountEqual(self.columns, columns)
|
||||||
@ -197,7 +243,8 @@ class TestShareCreate(TestShare):
|
|||||||
share_proto=self.new_share.share_proto,
|
share_proto=self.new_share.share_proto,
|
||||||
share_type=None,
|
share_type=None,
|
||||||
size=self.new_share.size,
|
size=self.new_share.size,
|
||||||
snapshot_id=self.share_snapshot.id
|
snapshot_id=self.share_snapshot.id,
|
||||||
|
scheduler_hints={}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertCountEqual(self.columns, columns)
|
self.assertCountEqual(self.columns, columns)
|
||||||
@ -231,7 +278,8 @@ class TestShareCreate(TestShare):
|
|||||||
share_proto=self.new_share.share_proto,
|
share_proto=self.new_share.share_proto,
|
||||||
share_type=None,
|
share_type=None,
|
||||||
size=self.new_share.size,
|
size=self.new_share.size,
|
||||||
snapshot_id=None
|
snapshot_id=None,
|
||||||
|
scheduler_hints={}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.shares_mock.get.assert_called_with(self.new_share.id)
|
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_proto=self.new_share.share_proto,
|
||||||
share_type=None,
|
share_type=None,
|
||||||
size=self.new_share.size,
|
size=self.new_share.size,
|
||||||
snapshot_id=None
|
snapshot_id=None,
|
||||||
|
scheduler_hints={}
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_logger.error.assert_called_with(
|
mock_logger.error.assert_called_with(
|
||||||
|
@ -67,6 +67,7 @@ class SharesTest(utils.TestCase):
|
|||||||
'share_type': None,
|
'share_type': None,
|
||||||
'is_public': False,
|
'is_public': False,
|
||||||
'availability_zone': None,
|
'availability_zone': None,
|
||||||
|
'scheduler_hints': dict(),
|
||||||
}
|
}
|
||||||
cs.shares.create(protocol, 1)
|
cs.shares.create(protocol, 1)
|
||||||
cs.assert_called('POST', '/shares', {'share': expected})
|
cs.assert_called('POST', '/shares', {'share': expected})
|
||||||
@ -87,6 +88,7 @@ class SharesTest(utils.TestCase):
|
|||||||
'share_type': None,
|
'share_type': None,
|
||||||
'is_public': False,
|
'is_public': False,
|
||||||
'availability_zone': None,
|
'availability_zone': None,
|
||||||
|
'scheduler_hints': dict(),
|
||||||
}
|
}
|
||||||
cs.shares.create('nfs', 1, share_network=share_network)
|
cs.shares.create('nfs', 1, share_network=share_network)
|
||||||
cs.assert_called('POST', '/shares', {'share': expected})
|
cs.assert_called('POST', '/shares', {'share': expected})
|
||||||
@ -107,6 +109,7 @@ class SharesTest(utils.TestCase):
|
|||||||
'share_type': 'fake_st',
|
'share_type': 'fake_st',
|
||||||
'is_public': False,
|
'is_public': False,
|
||||||
'availability_zone': None,
|
'availability_zone': None,
|
||||||
|
'scheduler_hints': dict(),
|
||||||
}
|
}
|
||||||
cs.shares.create('nfs', 1, share_type=share_type)
|
cs.shares.create('nfs', 1, share_type=share_type)
|
||||||
cs.assert_called('POST', '/shares', {'share': expected})
|
cs.assert_called('POST', '/shares', {'share': expected})
|
||||||
@ -130,6 +133,7 @@ class SharesTest(utils.TestCase):
|
|||||||
'share_network_id': None,
|
'share_network_id': None,
|
||||||
'size': 1,
|
'size': 1,
|
||||||
'availability_zone': availability_zone,
|
'availability_zone': availability_zone,
|
||||||
|
'scheduler_hints': {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cs.shares.create('nfs', 1, is_public=is_public,
|
cs.shares.create('nfs', 1, is_public=is_public,
|
||||||
|
@ -86,6 +86,7 @@ class ShellTest(test_utils.TestCase):
|
|||||||
"size": 1,
|
"size": 1,
|
||||||
"is_public": False,
|
"is_public": False,
|
||||||
"availability_zone": None,
|
"availability_zone": None,
|
||||||
|
"scheduler_hints": {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ class ShareManager(base.ManagerWithFind):
|
|||||||
def create(self, share_proto, size, snapshot_id=None, name=None,
|
def create(self, share_proto, size, snapshot_id=None, name=None,
|
||||||
description=None, metadata=None, share_network=None,
|
description=None, metadata=None, share_network=None,
|
||||||
share_type=None, is_public=False, availability_zone=None,
|
share_type=None, is_public=False, availability_zone=None,
|
||||||
share_group_id=None):
|
share_group_id=None, scheduler_hints=None):
|
||||||
"""Create a share.
|
"""Create a share.
|
||||||
|
|
||||||
:param share_proto: text - share protocol for new share available
|
: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 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
|
:param share_group_id: text - ID of the share group to which the share
|
||||||
should belong
|
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`
|
:rtype: :class:`Share`
|
||||||
"""
|
"""
|
||||||
share_metadata = metadata if metadata is not None else dict()
|
share_metadata = metadata if metadata is not None else dict()
|
||||||
|
scheduler_hints = (scheduler_hints if scheduler_hints is not None
|
||||||
|
else dict())
|
||||||
body = {
|
body = {
|
||||||
'size': size,
|
'size': size,
|
||||||
'snapshot_id': snapshot_id,
|
'snapshot_id': snapshot_id,
|
||||||
@ -149,6 +154,7 @@ class ShareManager(base.ManagerWithFind):
|
|||||||
'share_type': common_base.getid(share_type),
|
'share_type': common_base.getid(share_type),
|
||||||
'is_public': is_public,
|
'is_public': is_public,
|
||||||
'availability_zone': availability_zone,
|
'availability_zone': availability_zone,
|
||||||
|
'scheduler_hints': scheduler_hints,
|
||||||
}
|
}
|
||||||
if share_group_id:
|
if share_group_id:
|
||||||
body['share_group_id'] = share_group_id
|
body['share_group_id'] = share_group_id
|
||||||
|
@ -904,6 +904,14 @@ def do_rate_limits(cs, args):
|
|||||||
'--wait',
|
'--wait',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
help='Wait for share creation')
|
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')
|
@cliutils.service_type('sharev2')
|
||||||
def do_create(cs, args):
|
def do_create(cs, args):
|
||||||
"""Creates a new share (NFS, CIFS, CephFS, GlusterFS, HDFS or MAPRFS)."""
|
"""Creates a new share (NFS, CIFS, CephFS, GlusterFS, HDFS or MAPRFS)."""
|
||||||
@ -928,6 +936,25 @@ def do_create(cs, args):
|
|||||||
raise exceptions.CommandError(
|
raise exceptions.CommandError(
|
||||||
"Share name cannot be with the value 'None'")
|
"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,
|
share = cs.shares.create(args.share_protocol, args.size, snapshot,
|
||||||
args.name, args.description,
|
args.name, args.description,
|
||||||
metadata=share_metadata,
|
metadata=share_metadata,
|
||||||
@ -935,7 +962,8 @@ def do_create(cs, args):
|
|||||||
share_type=args.share_type,
|
share_type=args.share_type,
|
||||||
is_public=args.public,
|
is_public=args.public,
|
||||||
availability_zone=args.availability_zone,
|
availability_zone=args.availability_zone,
|
||||||
share_group_id=share_group)
|
share_group_id=share_group,
|
||||||
|
scheduler_hints=scheduler_hints)
|
||||||
|
|
||||||
if args.wait:
|
if args.wait:
|
||||||
share = _wait_for_share_status(cs, share)
|
share = _wait_for_share_status(cs, share)
|
||||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user