summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/compute/v2/server.py48
-rw-r--r--openstackclient/tests/functional/compute/v2/test_agent.py4
-rw-r--r--openstackclient/tests/functional/compute/v2/test_keypair.py4
-rw-r--r--openstackclient/tests/functional/identity/v2/common.py3
-rw-r--r--openstackclient/tests/functional/identity/v3/common.py2
-rw-r--r--openstackclient/tests/functional/object/v1/test_object.py2
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py25
7 files changed, 69 insertions, 19 deletions
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py
index 6c382536..d3bd1c72 100644
--- a/openstackclient/compute/v2/server.py
+++ b/openstackclient/compute/v2/server.py
@@ -201,6 +201,36 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True):
return info
+# TODO(stephenfin): Migrate this to osc-lib
+class KeyValueAppendAction(argparse.Action):
+ """A custom action to parse arguments as key=value pairs
+
+ Ensures that ``dest`` is a dict and values are lists of strings.
+ """
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ # Make sure we have an empty dict rather than None
+ if getattr(namespace, self.dest, None) is None:
+ setattr(namespace, self.dest, {})
+
+ # Add value if an assignment else remove it
+ if '=' in values:
+ key, value = values.split('=', 1)
+ # NOTE(qtang): Prevent null key setting in property
+ if '' == key:
+ msg = _("Property key must be specified: %s")
+ raise argparse.ArgumentTypeError(msg % str(values))
+
+ dest = getattr(namespace, self.dest)
+ if key in dest:
+ dest[key].append(value)
+ else:
+ dest[key] = [value]
+ else:
+ msg = _("Expected 'key=value' type, but got: %s")
+ raise argparse.ArgumentTypeError(msg % str(values))
+
+
class AddFixedIP(command.Command):
_description = _("Add fixed IP address to server")
@@ -650,8 +680,8 @@ class CreateServer(command.ShowOne):
parser.add_argument(
'--hint',
metavar='<key=value>',
- action='append',
- default=[],
+ action=KeyValueAppendAction,
+ default={},
help=_('Hints for the scheduler (optional extension)'),
)
parser.add_argument(
@@ -947,16 +977,12 @@ class CreateServer(command.ShowOne):
security_group_names.append(sg['name'])
hints = {}
- for hint in parsed_args.hint:
- key, _sep, value = hint.partition('=')
- # NOTE(vish): multiple copies of the same hint will
- # result in a list of values
- if key in hints:
- if isinstance(hints[key], six.string_types):
- hints[key] = [hints[key]]
- hints[key] += [value]
+ for key, values in parsed_args.hint.items():
+ # only items with multiple values will result in a list
+ if len(values) == 1:
+ hints[key] = values[0]
else:
- hints[key] = value
+ hints[key] = values
# What does a non-boolean value for config-drive do?
# --config-drive argument is either a volume id or
diff --git a/openstackclient/tests/functional/compute/v2/test_agent.py b/openstackclient/tests/functional/compute/v2/test_agent.py
index 1a112e82..25d8c868 100644
--- a/openstackclient/tests/functional/compute/v2/test_agent.py
+++ b/openstackclient/tests/functional/compute/v2/test_agent.py
@@ -21,10 +21,10 @@ class ComputeAgentTests(base.TestCase):
# Generate two different md5hash
MD5HASH1 = hashlib.md5()
- MD5HASH1.update('agent_1')
+ MD5HASH1.update('agent_1'.encode('utf-8'))
MD5HASH1 = MD5HASH1.hexdigest()
MD5HASH2 = hashlib.md5()
- MD5HASH2.update('agent_2')
+ MD5HASH2.update('agent_2'.encode('utf-8'))
MD5HASH2 = MD5HASH2.hexdigest()
def test_compute_agent_delete(self):
diff --git a/openstackclient/tests/functional/compute/v2/test_keypair.py b/openstackclient/tests/functional/compute/v2/test_keypair.py
index 9a88e66f..42f334a4 100644
--- a/openstackclient/tests/functional/compute/v2/test_keypair.py
+++ b/openstackclient/tests/functional/compute/v2/test_keypair.py
@@ -88,7 +88,7 @@ class KeypairTests(KeypairBase):
1) Create keypair with given public key
2) Delete keypair
"""
- with tempfile.NamedTemporaryFile() as f:
+ with tempfile.NamedTemporaryFile(mode='w+') as f:
f.write(self.PUBLIC_KEY)
f.flush()
@@ -108,7 +108,7 @@ class KeypairTests(KeypairBase):
1) Create keypair with private key file
2) Delete keypair
"""
- with tempfile.NamedTemporaryFile() as f:
+ with tempfile.NamedTemporaryFile(mode='w+') as f:
cmd_output = json.loads(self.openstack(
'keypair create -f json --private-key %s tmpkey' % f.name,
))
diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py
index f4bc10bd..43c0cbf2 100644
--- a/openstackclient/tests/functional/identity/v2/common.py
+++ b/openstackclient/tests/functional/identity/v2/common.py
@@ -11,6 +11,7 @@
# under the License.
import os
+import unittest
import fixtures
from tempest.lib.common.utils import data_utils
@@ -62,7 +63,7 @@ class IdentityTests(base.TestCase):
# TODO(dtroyer): Actually determine if Identity v2 admin is
# enabled in the target cloud. Tuens out OSC
# doesn't make this easy as it should (yet).
- raise cls.skipException('No Identity v2 admin endpoint?')
+ raise unittest.case.SkipTest('No Identity v2 admin endpoint?')
@classmethod
def tearDownClass(cls):
diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py
index 43b416aa..86f090bc 100644
--- a/openstackclient/tests/functional/identity/v3/common.py
+++ b/openstackclient/tests/functional/identity/v3/common.py
@@ -360,7 +360,7 @@ class IdentityTests(base.TestCase):
def _extract_value_from_items(self, key, items):
for d in items:
- for k, v in d.iteritems():
+ for k, v in d.items():
if k == key:
return v
diff --git a/openstackclient/tests/functional/object/v1/test_object.py b/openstackclient/tests/functional/object/v1/test_object.py
index 226ef8ad..b3f23e52 100644
--- a/openstackclient/tests/functional/object/v1/test_object.py
+++ b/openstackclient/tests/functional/object/v1/test_object.py
@@ -33,7 +33,7 @@ class ObjectTests(common.ObjectStoreTests):
self.skipTest("No object-store service present")
def test_object(self):
- with tempfile.NamedTemporaryFile() as f:
+ with tempfile.NamedTemporaryFile(mode='w+') as f:
f.write('test content')
f.flush()
self._test_object(f.name)
diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py
index 5c98188a..819fc354 100644
--- a/openstackclient/tests/unit/compute/v2/test_server.py
+++ b/openstackclient/tests/unit/compute/v2/test_server.py
@@ -687,7 +687,7 @@ class TestServerCreate(TestServer):
('key_name', 'keyname'),
('property', {'Beta': 'b'}),
('security_group', ['securitygroup']),
- ('hint', ['a=b', 'a=c']),
+ ('hint', {'a': ['b', 'c']}),
('config_drive', False),
('server_name', self.new_server.name),
]
@@ -1887,6 +1887,29 @@ class TestServerCreate(TestServer):
self.cmd.take_action,
parsed_args)
+ def test_server_create_invalid_hint(self):
+ # Not a key-value pair
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--hint', 'a0cf03a5-d921-4877-bb5c-86d26cf818e1',
+ self.new_server.name,
+ ]
+ self.assertRaises(argparse.ArgumentTypeError,
+ self.check_parser,
+ self.cmd, arglist, [])
+
+ # Empty key
+ arglist = [
+ '--image', 'image1',
+ '--flavor', 'flavor1',
+ '--hint', '=a0cf03a5-d921-4877-bb5c-86d26cf818e1',
+ self.new_server.name,
+ ]
+ self.assertRaises(argparse.ArgumentTypeError,
+ self.check_parser,
+ self.cmd, arglist, [])
+
def test_server_create_with_description_api_newer(self):
# Description is supported for nova api version 2.19 or above