Merge "Flavor ExtraSpecs containing '/' cannot be deleted"

This commit is contained in:
Jenkins 2014-02-01 20:12:07 +00:00 committed by Gerrit Code Review
commit 810857849e
8 changed files with 96 additions and 0 deletions

View File

@ -286,3 +286,19 @@ class FlattenTestCase(test_utils.TestCase):
"k3": "v3"} "k3": "v3"}
r = utils.pretty_choice_dict(d) r = utils.pretty_choice_dict(d)
self.assertEqual(r, "'k1=v1', 'k2=v2', 'k3=v3'") self.assertEqual(r, "'k1=v1', 'k2=v2', 'k3=v3'")
class ValidationsTestCase(test_utils.TestCase):
def test_validate_flavor_metadata_keys_with_valid_keys(self):
valid_keys = ['key1', 'month.price', 'I-Am:AK-ey.01-', 'spaces and _']
for key in valid_keys:
utils.validate_flavor_metadata_keys(valid_keys)
def test_validate_flavor_metadata_keys_with_invalid_keys(self):
invalid_keys = ['/1', '?1', '%1', '<', '>', '\1']
for key in invalid_keys:
try:
utils.validate_flavor_metadata_keys([key])
self.assertFail()
except exceptions.CommandError as ce:
self.assertTrue(key in str(ce))

View File

@ -667,6 +667,10 @@ class FakeHTTPClient(base_client.HTTPClient):
'OS-FLV-EXT-DATA:ephemeral': 20, 'OS-FLV-EXT-DATA:ephemeral': 20,
'os-flavor-access:is_public': False, 'os-flavor-access:is_public': False,
'links': {}}, 'links': {}},
{'id': 4, 'name': '1024 MB Server', 'ram': 1024, 'disk': 10,
'OS-FLV-EXT-DATA:ephemeral': 10,
'os-flavor-access:is_public': True,
'links': {}},
{'id': 'aa1', 'name': '128 MB Server', 'ram': 128, 'disk': 0, {'id': 'aa1', 'name': '128 MB Server', 'ram': 128, 'disk': 0,
'OS-FLV-EXT-DATA:ephemeral': 0, 'OS-FLV-EXT-DATA:ephemeral': 0,
'os-flavor-access:is_public': True, 'os-flavor-access:is_public': True,
@ -730,6 +734,14 @@ class FakeHTTPClient(base_client.HTTPClient):
def get_flavors_aa1(self, **kw): def get_flavors_aa1(self, **kw):
# Aplhanumeric flavor id are allowed. # Aplhanumeric flavor id are allowed.
return (
200,
{},
{'flavor':
self.get_flavors_detail(is_public='None')[2]['flavors'][3]}
)
def get_flavors_4(self, **kw):
return ( return (
200, 200,
{}, {},
@ -765,6 +777,11 @@ class FakeHTTPClient(base_client.HTTPClient):
return (200, {}, return (200, {},
{'extra_specs': {"k3": "v3"}}) {'extra_specs': {"k3": "v3"}})
def get_flavors_4_os_extra_specs(self, **kw):
return (200,
{},
{'extra_specs': {"k4": "v4"}})
def post_flavors_1_os_extra_specs(self, body, **kw): def post_flavors_1_os_extra_specs(self, body, **kw):
assert list(body) == ['extra_specs'] assert list(body) == ['extra_specs']
fakes.assert_has_keys(body['extra_specs'], fakes.assert_has_keys(body['extra_specs'],
@ -773,6 +790,13 @@ class FakeHTTPClient(base_client.HTTPClient):
{}, {},
{'extra_specs': {"k1": "v1"}}) {'extra_specs': {"k1": "v1"}})
def post_flavors_4_os_extra_specs(self, body, **kw):
assert list(body) == ['extra_specs']
return (200,
{},
body)
def delete_flavors_1_os_extra_specs_k1(self, **kw): def delete_flavors_1_os_extra_specs_k1(self, **kw):
return (204, {}, None) return (204, {}, None)

View File

@ -189,6 +189,23 @@ class FlavorsTest(utils.TestCase):
self.cs.assert_called('POST', '/flavors/1/os-extra_specs', self.cs.assert_called('POST', '/flavors/1/os-extra_specs',
{"extra_specs": {'k1': 'v1'}}) {"extra_specs": {'k1': 'v1'}})
def test_set_with_valid_keys(self):
valid_keys = ['key4', 'month.price', 'I-Am:AK-ey.44-',
'key with spaces and _']
f = self.cs.flavors.get(4)
for key in valid_keys:
f.set_keys({key: 'v4'})
self.cs.assert_called('POST', '/flavors/4/os-extra_specs',
{"extra_specs": {key: 'v4'}})
def test_set_with_invalid_keys(self):
invalid_keys = ['/1', '?1', '%1', '<', '>']
f = self.cs.flavors.get(1)
for key in invalid_keys:
self.assertRaises(exceptions.CommandError, f.set_keys, {key: 'v1'})
def test_unset_keys(self): def test_unset_keys(self):
f = self.cs.flavors.get(1) f = self.cs.flavors.get(1)
f.unset_keys(['k1']) f.unset_keys(['k1'])

View File

@ -66,6 +66,9 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
post_flavors_1_flavor_extra_specs = ( post_flavors_1_flavor_extra_specs = (
fakes_v1_1.FakeHTTPClient.post_flavors_1_os_extra_specs) fakes_v1_1.FakeHTTPClient.post_flavors_1_os_extra_specs)
post_flavors_4_flavor_extra_specs = (
fakes_v1_1.FakeHTTPClient.post_flavors_4_os_extra_specs)
delete_flavors_1_flavor_extra_specs_k1 = ( delete_flavors_1_flavor_extra_specs_k1 = (
fakes_v1_1.FakeHTTPClient.delete_flavors_1_os_extra_specs_k1) fakes_v1_1.FakeHTTPClient.delete_flavors_1_os_extra_specs_k1)
@ -79,6 +82,10 @@ class FakeHTTPClient(fakes_v1_1.FakeHTTPClient):
'ephemeral': 20, 'ephemeral': 20,
'flavor-access:is_public': False, 'flavor-access:is_public': False,
'links': {}}, 'links': {}},
{'id': 4, 'name': '1024 MB Server', 'ram': 1024, 'disk': 10,
'ephemeral': 10,
'flavor-access:is_public': True,
'links': {}},
{'id': 'aa1', 'name': '128 MB Server', 'ram': 128, 'disk': 0, {'id': 'aa1', 'name': '128 MB Server', 'ram': 128, 'disk': 0,
'ephemeral': 0, 'ephemeral': 0,
'flavor-access:is_public': True, 'flavor-access:is_public': True,

View File

@ -47,6 +47,16 @@ class FlavorsTest(test_flavors.FlavorsTest):
self.cs.assert_called('POST', '/flavors/1/flavor-extra-specs', self.cs.assert_called('POST', '/flavors/1/flavor-extra-specs',
{"extra_specs": {'k1': 'v1'}}) {"extra_specs": {'k1': 'v1'}})
def test_set_with_valid_keys(self):
valid_keys = ['key4', 'month.price', 'I-Am:AK-ey.44-',
'key with spaces and _']
f = self.cs.flavors.get(4)
for key in valid_keys:
f.set_keys({key: 'v4'})
self.cs.assert_called('POST', '/flavors/4/flavor-extra-specs',
{"extra_specs": {key: 'v4'}})
def test_unset_keys(self): def test_unset_keys(self):
f = self.cs.flavors.get(1) f = self.cs.flavors.get(1)
f.unset_keys(['k1']) f.unset_keys(['k1'])

View File

@ -13,6 +13,7 @@
import json import json
import pkg_resources import pkg_resources
import re
import sys import sys
import textwrap import textwrap
import uuid import uuid
@ -22,6 +23,7 @@ import six
from novaclient import exceptions from novaclient import exceptions
from novaclient.openstack.common import cliutils from novaclient.openstack.common import cliutils
from novaclient.openstack.common.gettextutils import _
from novaclient.openstack.common import jsonutils from novaclient.openstack.common import jsonutils
from novaclient.openstack.common import strutils from novaclient.openstack.common import strutils
@ -29,6 +31,8 @@ from novaclient.openstack.common import strutils
arg = cliutils.arg arg = cliutils.arg
env = cliutils.env env = cliutils.env
VALID_KEY_REGEX = re.compile(r"[\w\.\- :]+$", re.UNICODE)
def add_resource_manager_extra_kwargs_hook(f, hook): def add_resource_manager_extra_kwargs_hook(f, hook):
"""Add hook to bind CLI arguments to ResourceManager calls. """Add hook to bind CLI arguments to ResourceManager calls.
@ -349,3 +353,13 @@ def is_integer_like(val):
return True return True
except (TypeError, ValueError, AttributeError): except (TypeError, ValueError, AttributeError):
return False return False
def validate_flavor_metadata_keys(keys):
for key in keys:
valid_name = VALID_KEY_REGEX.match(key)
if not valid_name:
msg = _('Invalid key: "%s". Keys may only contain letters, '
'numbers, spaces, underscores, periods, colons and '
'hyphens.')
raise exceptions.CommandError(msg % key)

View File

@ -15,10 +15,12 @@
""" """
Flavor interface. Flavor interface.
""" """
from novaclient import base from novaclient import base
from novaclient import exceptions from novaclient import exceptions
from novaclient.openstack.common.py3kcompat import urlutils from novaclient.openstack.common.py3kcompat import urlutils
from novaclient.openstack.common import strutils from novaclient.openstack.common import strutils
from novaclient import utils
class Flavor(base.Resource): class Flavor(base.Resource):
@ -62,6 +64,8 @@ class Flavor(base.Resource):
:param flavor: The :class:`Flavor` to set extra spec on :param flavor: The :class:`Flavor` to set extra spec on
:param metadata: A dict of key/value pairs to be set :param metadata: A dict of key/value pairs to be set
""" """
utils.validate_flavor_metadata_keys(metadata.keys())
body = {'extra_specs': metadata} body = {'extra_specs': metadata}
return self.manager._create( return self.manager._create(
"/flavors/%s/os-extra_specs" % base.getid(self), "/flavors/%s/os-extra_specs" % base.getid(self),

View File

@ -16,7 +16,9 @@
""" """
Flavor interface. Flavor interface.
""" """
from novaclient import base from novaclient import base
from novaclient import utils
from novaclient.v1_1 import flavors from novaclient.v1_1 import flavors
@ -54,6 +56,8 @@ class Flavor(base.Resource):
:param flavor: The :class:`Flavor` to set extra spec on :param flavor: The :class:`Flavor` to set extra spec on
:param metadata: A dict of key/value pairs to be set :param metadata: A dict of key/value pairs to be set
""" """
utils.validate_flavor_metadata_keys(metadata.keys())
body = {'extra_specs': metadata} body = {'extra_specs': metadata}
return self.manager._create( return self.manager._create(
"/flavors/%s/flavor-extra-specs" % "/flavors/%s/flavor-extra-specs" %