diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index bf8aa4a47..4d0473bd9 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -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 = {} diff --git a/manilaclient/osc/v2/share.py b/manilaclient/osc/v2/share.py index 15c7b165c..1c2d101a3 100644 --- a/manilaclient/osc/v2/share.py +++ b/manilaclient/osc/v2/share.py @@ -179,6 +179,16 @@ class CreateShare(command.ShowOne): default=False, help=_('Wait for share creation') ) + parser.add_argument( + "--scheduler-hint", + metavar="", + 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) diff --git a/manilaclient/tests/unit/osc/v2/fakes.py b/manilaclient/tests/unit/osc/v2/fakes.py index 476ff8fb5..508a79772 100644 --- a/manilaclient/tests/unit/osc/v2/fakes.py +++ b/manilaclient/tests/unit/osc/v2/fakes.py @@ -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. diff --git a/manilaclient/tests/unit/osc/v2/test_share.py b/manilaclient/tests/unit/osc/v2/test_share.py index 338c4d823..0dcb6115c 100644 --- a/manilaclient/tests/unit/osc/v2/test_share.py +++ b/manilaclient/tests/unit/osc/v2/test_share.py @@ -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( diff --git a/manilaclient/tests/unit/v2/test_shares.py b/manilaclient/tests/unit/v2/test_shares.py index d606eb30d..c18fc7a0e 100644 --- a/manilaclient/tests/unit/v2/test_shares.py +++ b/manilaclient/tests/unit/v2/test_shares.py @@ -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, diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py index 811a75373..3c6e82a52 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -86,6 +86,7 @@ class ShellTest(test_utils.TestCase): "size": 1, "is_public": False, "availability_zone": None, + "scheduler_hints": {}, } } diff --git a/manilaclient/v2/shares.py b/manilaclient/v2/shares.py index 44fc16c21..2b1f052ba 100644 --- a/manilaclient/v2/shares.py +++ b/manilaclient/v2/shares.py @@ -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 diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index 2ad2db9b4..dc8fe7239 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -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='', + 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) diff --git a/releasenotes/notes/add-scheduler-hints-to-share-create-70d429cb0aaf8f11.yaml b/releasenotes/notes/add-scheduler-hints-to-share-create-70d429cb0aaf8f11.yaml new file mode 100644 index 000000000..be0d6da19 --- /dev/null +++ b/releasenotes/notes/add-scheduler-hints-to-share-create-70d429cb0aaf8f11.yaml @@ -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.