Skip to content

Commit ea27ebb

Browse files
committed
Stop silently ignoring invalid 'server create --hint' options
The '--hint' option for 'server create' expects a key-value pair like so: openstack server create --hint group=245e1dfe-2d0e-4139-80a9-fce124948896 ... However, the command doesn't complain if this isn't the case, meaning typos like the below aren't indicated to the user: openstack server create --hint 245e1dfe-2d0e-4139-80a9-fce124948896 Due to how we'd implemented this here, this ultimately results in us POSTing the following as part of the body to 'os-servers': { ... "OS-SCH-HNT:scheduler_hints": { "245e1dfe-2d0e-4139-80a9-fce124948896": null } ... } Which is unfortunately allowed and ignored by nova due to the use of 'additionalProperties' in the schema [1] Do what we do for loads of other options and explicitly fail on invalid values. This involves adding a new argparse action since none of those defined in osc-lib work for us. This is included here to ease backporting of the fix but will be moved to osc-lib in a future patch. [1] https://github.com/openstack/nova/blob/19.0.0/nova/api/openstack/compute/schemas/servers.py#L142-L146 Change-Id: I9e96d2978912c8dfeadae4a782c481a17cd7e348 Signed-off-by: Stephen Finucane <sfinucan@redhat.com> Story: #2006628 Task: #36840 Related-Bug: #1845322
1 parent 70ab3f9 commit ea27ebb

2 files changed

Lines changed: 61 additions & 12 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,36 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
201201
return info
202202

203203

204+
# TODO(stephenfin): Migrate this to osc-lib
205+
class KeyValueAppendAction(argparse.Action):
206+
"""A custom action to parse arguments as key=value pairs
207+
208+
Ensures that ``dest`` is a dict and values are lists of strings.
209+
"""
210+
211+
def __call__(self, parser, namespace, values, option_string=None):
212+
# Make sure we have an empty dict rather than None
213+
if getattr(namespace, self.dest, None) is None:
214+
setattr(namespace, self.dest, {})
215+
216+
# Add value if an assignment else remove it
217+
if '=' in values:
218+
key, value = values.split('=', 1)
219+
# NOTE(qtang): Prevent null key setting in property
220+
if '' == key:
221+
msg = _("Property key must be specified: %s")
222+
raise argparse.ArgumentTypeError(msg % str(values))
223+
224+
dest = getattr(namespace, self.dest)
225+
if key in dest:
226+
dest[key].append(value)
227+
else:
228+
dest[key] = [value]
229+
else:
230+
msg = _("Expected 'key=value' type, but got: %s")
231+
raise argparse.ArgumentTypeError(msg % str(values))
232+
233+
204234
class AddFixedIP(command.Command):
205235
_description = _("Add fixed IP address to server")
206236

@@ -689,8 +719,8 @@ def get_parser(self, prog_name):
689719
parser.add_argument(
690720
'--hint',
691721
metavar='<key=value>',
692-
action='append',
693-
default=[],
722+
action=KeyValueAppendAction,
723+
default={},
694724
help=_('Hints for the scheduler (optional extension)'),
695725
)
696726
parser.add_argument(
@@ -986,16 +1016,12 @@ def _match_image(image_api, wanted_properties):
9861016
security_group_names.append(sg['name'])
9871017

9881018
hints = {}
989-
for hint in parsed_args.hint:
990-
key, _sep, value = hint.partition('=')
991-
# NOTE(vish): multiple copies of the same hint will
992-
# result in a list of values
993-
if key in hints:
994-
if isinstance(hints[key], six.string_types):
995-
hints[key] = [hints[key]]
996-
hints[key] += [value]
1019+
for key, values in parsed_args.hint.items():
1020+
# only items with multiple values will result in a list
1021+
if len(values) == 1:
1022+
hints[key] = values[0]
9971023
else:
998-
hints[key] = value
1024+
hints[key] = values
9991025

10001026
# What does a non-boolean value for config-drive do?
10011027
# --config-drive argument is either a volume id or

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,7 @@ def test_server_create_with_options(self):
860860
('key_name', 'keyname'),
861861
('property', {'Beta': 'b'}),
862862
('security_group', ['securitygroup']),
863-
('hint', ['a=b', 'a=c']),
863+
('hint', {'a': ['b', 'c']}),
864864
('config_drive', False),
865865
('server_name', self.new_server.name),
866866
]
@@ -2060,6 +2060,29 @@ def test_server_create_image_property_missed(self):
20602060
self.cmd.take_action,
20612061
parsed_args)
20622062

2063+
def test_server_create_invalid_hint(self):
2064+
# Not a key-value pair
2065+
arglist = [
2066+
'--image', 'image1',
2067+
'--flavor', 'flavor1',
2068+
'--hint', 'a0cf03a5-d921-4877-bb5c-86d26cf818e1',
2069+
self.new_server.name,
2070+
]
2071+
self.assertRaises(argparse.ArgumentTypeError,
2072+
self.check_parser,
2073+
self.cmd, arglist, [])
2074+
2075+
# Empty key
2076+
arglist = [
2077+
'--image', 'image1',
2078+
'--flavor', 'flavor1',
2079+
'--hint', '=a0cf03a5-d921-4877-bb5c-86d26cf818e1',
2080+
self.new_server.name,
2081+
]
2082+
self.assertRaises(argparse.ArgumentTypeError,
2083+
self.check_parser,
2084+
self.cmd, arglist, [])
2085+
20632086
def test_server_create_with_description_api_newer(self):
20642087

20652088
# Description is supported for nova api version 2.19 or above

0 commit comments

Comments
 (0)