Merge "Flavor ExtraSpecs containing '/' cannot be deleted"
This commit is contained in:
commit
810857849e
@ -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))
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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'])
|
||||||
|
@ -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,
|
||||||
|
@ -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'])
|
||||||
|
@ -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)
|
||||||
|
@ -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),
|
||||||
|
@ -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" %
|
||||||
|
Loading…
Reference in New Issue
Block a user