Follow-up for flavor-extra-spec-validators series
Clarify the impact of this microversion in the release note and improve some tests. Part of blueprint flavor-extra-spec-validators Change-Id: Idfccfa9831f1df6f261a247489154492a231d832 Signed-off-by: Stephen Finucane <stephenfin@redhat.com>
This commit is contained in:
parent
63e30e022d
commit
4e30693727
@ -1130,7 +1130,7 @@ for the following APIs:
|
|||||||
* ``POST /flavors/{flavor_id}/os-extra_specs``
|
* ``POST /flavors/{flavor_id}/os-extra_specs``
|
||||||
* ``PUT /flavors/{flavor_id}/os-extra_specs/{id}``
|
* ``PUT /flavors/{flavor_id}/os-extra_specs/{id}``
|
||||||
|
|
||||||
Validation is only used for recognized extra spec namespaces, namely:
|
Validation is only used for recognized extra spec namespaces, currently:
|
||||||
``accel``, ``aggregate_instance_extra_specs``, ``capabilities``, ``hw``,
|
``accel``, ``aggregate_instance_extra_specs``, ``capabilities``, ``hw``,
|
||||||
``hw_rng``, ``hw_video``, ``os``, ``pci_passthrough``, ``powervm``, ``quota``,
|
``hw_rng``, ``hw_video``, ``os``, ``pci_passthrough``, ``powervm``, ``quota``,
|
||||||
``resources``, ``trait``, and ``vmware``.
|
``resources``, ``trait``, and ``vmware``.
|
||||||
|
@ -38,10 +38,6 @@ for trait in os_traits.get_traits():
|
|||||||
'name': 'group',
|
'name': 'group',
|
||||||
'pattern': r'(_[a-zA-z0-9_]*|\d+)?',
|
'pattern': r'(_[a-zA-z0-9_]*|\d+)?',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
'name': 'trait',
|
|
||||||
'pattern': r'[a-zA-Z0-9_]+',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -47,8 +47,15 @@ def validate(name: str, value: str):
|
|||||||
validator.validate(name, value)
|
validator.validate(name, value)
|
||||||
return
|
return
|
||||||
|
|
||||||
namespace = name.split(':', 1)[0].split('{')[0] if ':' in name else None
|
# check if there's a namespace; if not, we've done all we can do
|
||||||
if not namespace or namespace not in NAMESPACES: # unregistered namespace
|
if ':' not in name: # no namespace
|
||||||
|
return
|
||||||
|
|
||||||
|
# if there is, check if it's one we recognize
|
||||||
|
for namespace in NAMESPACES:
|
||||||
|
if re.fullmatch(namespace, name.split(':', 1)[0]):
|
||||||
|
break
|
||||||
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
raise exception.ValidationError(
|
raise exception.ValidationError(
|
||||||
@ -72,8 +79,8 @@ def load_validators():
|
|||||||
# TODO(stephenfin): Make 'register' return a dict rather than a list?
|
# TODO(stephenfin): Make 'register' return a dict rather than a list?
|
||||||
for validator in ext.plugin.register():
|
for validator in ext.plugin.register():
|
||||||
VALIDATORS[validator.name] = validator
|
VALIDATORS[validator.name] = validator
|
||||||
if ':' in validator.name:
|
if ':' in validator.name_regex:
|
||||||
NAMESPACES.add(validator.name.split(':', 1)[0].split('{')[0])
|
NAMESPACES.add(validator.name_regex.split(':', 1)[0])
|
||||||
|
|
||||||
|
|
||||||
load_validators()
|
load_validators()
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
"""Tests for os-extra_specs API."""
|
"""Tests for os-extra_specs API."""
|
||||||
|
|
||||||
import testtools
|
|
||||||
|
|
||||||
from nova.tests.functional.api import client as api_client
|
from nova.tests.functional.api import client as api_client
|
||||||
from nova.tests.functional import integrated_helpers
|
from nova.tests.functional import integrated_helpers
|
||||||
|
|
||||||
@ -43,7 +41,7 @@ class FlavorExtraSpecsTest(integrated_helpers._IntegratedTestBase):
|
|||||||
This should pass because validation is not enabled in this API
|
This should pass because validation is not enabled in this API
|
||||||
microversion.
|
microversion.
|
||||||
"""
|
"""
|
||||||
body = {'extra_specs': {'hw:numa_nodes': '1', 'foo': 'bar'}}
|
body = {'extra_specs': {'hw:numa_nodes': 'foo', 'foo': 'bar'}}
|
||||||
self.admin_api.post_extra_spec(self.flavor_id, body)
|
self.admin_api.post_extra_spec(self.flavor_id, body)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
body['extra_specs'], self.admin_api.get_extra_specs(self.flavor_id)
|
body['extra_specs'], self.admin_api.get_extra_specs(self.flavor_id)
|
||||||
@ -64,8 +62,8 @@ class FlavorExtraSpecsTest(integrated_helpers._IntegratedTestBase):
|
|||||||
This should pass because validation is not enabled in this API
|
This should pass because validation is not enabled in this API
|
||||||
microversion.
|
microversion.
|
||||||
"""
|
"""
|
||||||
spec_id = 'foo:bar'
|
spec_id = 'hw:foo'
|
||||||
body = {'foo:bar': 'baz'}
|
body = {'hw:foo': 'bar'}
|
||||||
self.admin_api.put_extra_spec(self.flavor_id, spec_id, body)
|
self.admin_api.put_extra_spec(self.flavor_id, spec_id, body)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
body, self.admin_api.get_extra_spec(self.flavor_id, spec_id)
|
body, self.admin_api.get_extra_spec(self.flavor_id, spec_id)
|
||||||
@ -82,11 +80,12 @@ class FlavorExtraSpecsV286Test(FlavorExtraSpecsTest):
|
|||||||
|
|
||||||
# this should fail because 'foo' is not a suitable value for
|
# this should fail because 'foo' is not a suitable value for
|
||||||
# 'hw:numa_nodes'
|
# 'hw:numa_nodes'
|
||||||
with testtools.ExpectedException(
|
exc = self.assertRaises(
|
||||||
api_client.OpenStackApiException
|
api_client.OpenStackApiException,
|
||||||
) as exc:
|
self.admin_api.post_extra_spec,
|
||||||
self.admin_api.post_extra_spec(self.flavor_id, body)
|
self.flavor_id, body,
|
||||||
self.assertEqual(400, exc.response.status_code)
|
)
|
||||||
|
self.assertEqual(400, exc.response.status_code)
|
||||||
|
|
||||||
# ...and the extra specs should not be saved
|
# ...and the extra specs should not be saved
|
||||||
self.assertEqual({}, self.admin_api.get_extra_specs(self.flavor_id))
|
self.assertEqual({}, self.admin_api.get_extra_specs(self.flavor_id))
|
||||||
@ -102,11 +101,12 @@ class FlavorExtraSpecsV286Test(FlavorExtraSpecsTest):
|
|||||||
body = {'extra_specs': {'hw:numa_nodes': '1', 'hw:foo': 'bar'}}
|
body = {'extra_specs': {'hw:numa_nodes': '1', 'hw:foo': 'bar'}}
|
||||||
|
|
||||||
# ...but this should fail because we do recognize the namespace
|
# ...but this should fail because we do recognize the namespace
|
||||||
with testtools.ExpectedException(
|
exc = self.assertRaises(
|
||||||
api_client.OpenStackApiException
|
api_client.OpenStackApiException,
|
||||||
) as exc:
|
self.admin_api.post_extra_spec,
|
||||||
self.admin_api.post_extra_spec(self.flavor_id, body)
|
self.flavor_id, body,
|
||||||
self.assertEqual(400, exc.response.status_code)
|
)
|
||||||
|
self.assertEqual(400, exc.response.status_code)
|
||||||
|
|
||||||
def test_update_invalid_spec(self):
|
def test_update_invalid_spec(self):
|
||||||
"""Test updating extra specs with invalid specs."""
|
"""Test updating extra specs with invalid specs."""
|
||||||
@ -114,21 +114,23 @@ class FlavorExtraSpecsV286Test(FlavorExtraSpecsTest):
|
|||||||
body = {'hw:foo': 'bar'}
|
body = {'hw:foo': 'bar'}
|
||||||
|
|
||||||
# this should fail because we don't recognize the extra spec
|
# this should fail because we don't recognize the extra spec
|
||||||
with testtools.ExpectedException(
|
exc = self.assertRaises(
|
||||||
api_client.OpenStackApiException
|
api_client.OpenStackApiException,
|
||||||
) as exc:
|
self.admin_api.put_extra_spec,
|
||||||
self.admin_api.put_extra_spec(self.flavor_id, spec_id, body)
|
self.flavor_id, spec_id, body,
|
||||||
self.assertEqual(400, exc.response.status_code)
|
)
|
||||||
|
self.assertEqual(400, exc.response.status_code)
|
||||||
|
|
||||||
spec_id = 'hw:numa_nodes'
|
spec_id = 'hw:numa_nodes'
|
||||||
body = {'hw:numa_nodes': 'foo'}
|
body = {'hw:numa_nodes': 'foo'}
|
||||||
|
|
||||||
# ...while this should fail because the value is not valid
|
# ...while this should fail because the value is not valid
|
||||||
with testtools.ExpectedException(
|
exc = self.assertRaises(
|
||||||
api_client.OpenStackApiException
|
api_client.OpenStackApiException,
|
||||||
) as exc:
|
self.admin_api.put_extra_spec,
|
||||||
self.admin_api.put_extra_spec(self.flavor_id, spec_id, body)
|
self.flavor_id, spec_id, body,
|
||||||
self.assertEqual(400, exc.response.status_code)
|
)
|
||||||
|
self.assertEqual(400, exc.response.status_code)
|
||||||
|
|
||||||
# ...and neither extra spec should be saved
|
# ...and neither extra spec should be saved
|
||||||
self.assertEqual({}, self.admin_api.get_extra_specs(self.flavor_id))
|
self.assertEqual({}, self.admin_api.get_extra_specs(self.flavor_id))
|
||||||
@ -141,3 +143,4 @@ class FlavorExtraSpecsV286Test(FlavorExtraSpecsTest):
|
|||||||
# this should pass because we don't recognize the extra spec but it's
|
# this should pass because we don't recognize the extra spec but it's
|
||||||
# not in a namespace we care about
|
# not in a namespace we care about
|
||||||
self.admin_api.put_extra_spec(self.flavor_id, spec_id, body)
|
self.admin_api.put_extra_spec(self.flavor_id, spec_id, body)
|
||||||
|
self.assertEqual(body, self.admin_api.get_extra_specs(self.flavor_id))
|
||||||
|
@ -272,6 +272,10 @@ class FlavorsExtraSpecsTestV21(test.TestCase):
|
|||||||
'hw:cpu_policy': 'sharrred',
|
'hw:cpu_policy': 'sharrred',
|
||||||
'hw:cpu_policyyyyyyy': 'shared',
|
'hw:cpu_policyyyyyyy': 'shared',
|
||||||
'hw:foo': 'bar',
|
'hw:foo': 'bar',
|
||||||
|
'trait:STORAGE_DISK_SSD': 'forbiden',
|
||||||
|
'trait_foo:HW_CPU_X86_AVX2': 'foo',
|
||||||
|
'trait:bar': 'required',
|
||||||
|
'trait_foo:bar': 'required',
|
||||||
}
|
}
|
||||||
for key, value in invalid_specs.items():
|
for key, value in invalid_specs.items():
|
||||||
body = {'extra_specs': {key: value}}
|
body = {'extra_specs': {key: value}}
|
||||||
@ -303,6 +307,8 @@ class FlavorsExtraSpecsTestV21(test.TestCase):
|
|||||||
'hide_hypervisor_id': 'true',
|
'hide_hypervisor_id': 'true',
|
||||||
'hw:numa_nodes': '1',
|
'hw:numa_nodes': '1',
|
||||||
'hw:numa_cpus.0': '0-3,8-9,11,10',
|
'hw:numa_cpus.0': '0-3,8-9,11,10',
|
||||||
|
'trait:STORAGE_DISK_SSD': 'forbidden',
|
||||||
|
'trait_foo:HW_CPU_X86_AVX2': 'required',
|
||||||
}
|
}
|
||||||
mock_flavor_extra_specs.side_effect = return_create_flavor_extra_specs
|
mock_flavor_extra_specs.side_effect = return_create_flavor_extra_specs
|
||||||
|
|
||||||
|
@ -29,7 +29,8 @@ class TestValidators(test.NoDBTestCase):
|
|||||||
namespaces = {
|
namespaces = {
|
||||||
'accel', 'aggregate_instance_extra_specs', 'capabilities', 'hw',
|
'accel', 'aggregate_instance_extra_specs', 'capabilities', 'hw',
|
||||||
'hw_rng', 'hw_video', 'os', 'pci_passthrough', 'powervm', 'quota',
|
'hw_rng', 'hw_video', 'os', 'pci_passthrough', 'powervm', 'quota',
|
||||||
'resources', 'trait', 'vmware',
|
'resources(?P<group>(_[a-zA-z0-9_]*|\\d+)?)',
|
||||||
|
'trait(?P<group>(_[a-zA-z0-9_]*|\\d+)?)', 'vmware',
|
||||||
}
|
}
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
namespaces.issubset(validators.NAMESPACES),
|
namespaces.issubset(validators.NAMESPACES),
|
||||||
|
@ -3,4 +3,19 @@ features:
|
|||||||
- |
|
- |
|
||||||
The 2.86 microversion adds support for flavor extra spec validation when
|
The 2.86 microversion adds support for flavor extra spec validation when
|
||||||
creating or updating flavor extra specs. Use of an unrecognized or invalid
|
creating or updating flavor extra specs. Use of an unrecognized or invalid
|
||||||
flavor extra spec will result in a HTTP 400 response.
|
flavor extra spec in the following namespaces will result in a HTTP 400
|
||||||
|
response.
|
||||||
|
|
||||||
|
- ``accel``
|
||||||
|
- ``aggregate_instance_extra_specs``
|
||||||
|
- ``capabilities``
|
||||||
|
- ``hw``
|
||||||
|
- ``hw_rng``
|
||||||
|
- ``hw_video``
|
||||||
|
- ``os``
|
||||||
|
- ``pci_passthrough``
|
||||||
|
- ``powervm``
|
||||||
|
- ``quota``
|
||||||
|
- ``resources`` (including ``_{group}`` suffixes)
|
||||||
|
- ``trait`` (including ``_{group}`` suffixes)
|
||||||
|
- ``vmware``
|
||||||
|
Loading…
x
Reference in New Issue
Block a user