Merge "Drop concept of '?validation' parameter"

This commit is contained in:
Zuul 2020-04-10 00:19:03 +00:00 committed by Gerrit Code Review
commit 95ca68cfc6
4 changed files with 68 additions and 162 deletions

View File

@ -37,7 +37,6 @@ class FlavorExtraSpecsController(wsgi.Controller):
def _check_extra_specs_value(self, req, specs): def _check_extra_specs_value(self, req, specs):
# TODO(stephenfin): Wire this up to check the API microversion # TODO(stephenfin): Wire this up to check the API microversion
validation_supported = False validation_supported = False
validation_mode = 'strict'
for name, value in specs.items(): for name, value in specs.items():
# NOTE(gmann): Max length for numeric value is being checked # NOTE(gmann): Max length for numeric value is being checked
@ -53,7 +52,7 @@ class FlavorExtraSpecsController(wsgi.Controller):
explanation=error.format_message()) explanation=error.format_message())
if validation_supported: if validation_supported:
validators.validate(name, value, validation_mode) validators.validate(name, value)
@wsgi.expected_errors(404) @wsgi.expected_errors(404)
def index(self, req, flavor_id): def index(self, req, flavor_id):

View File

@ -26,19 +26,16 @@ from nova import exception
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
VALIDATORS: ty.Dict[str, base.ExtraSpecValidator] = {} VALIDATORS: ty.Dict[str, base.ExtraSpecValidator] = {}
NAMESPACES: ty.Set[str] = set()
def validate(name: str, value: str, mode: str): def validate(name: str, value: str):
"""Validate a given extra spec. """Validate a given extra spec.
:param name: Extra spec name. :param name: Extra spec name.
:param value: Extra spec value. :param value: Extra spec value.
:param mode: Validation mode; one of: strict, permissive, disabled
:raises: exception.ValidationError if validation fails. :raises: exception.ValidationError if validation fails.
""" """
if mode == 'disabled':
return
# attempt a basic lookup for extra specs without embedded parameters # attempt a basic lookup for extra specs without embedded parameters
if name in VALIDATORS: if name in VALIDATORS:
VALIDATORS[name].validate(name, value) VALIDATORS[name].validate(name, value)
@ -50,7 +47,8 @@ def validate(name: str, value: str, mode: str):
validator.validate(name, value) validator.validate(name, value)
return return
if mode == 'permissive': # unregistered extra spec, ignore namespace = name.split(':', 1)[0].split('{')[0] if ':' in name else None
if not namespace or namespace not in NAMESPACES: # unregistered namespace
return return
raise exception.ValidationError( raise exception.ValidationError(
@ -74,6 +72,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:
NAMESPACES.add(validator.name.split(':', 1)[0].split('{')[0])
load_validators() load_validators()

View File

@ -268,13 +268,13 @@ class FlavorsExtraSpecsTestV21(test.TestCase):
# TODO(stephenfin): Wire the microversion up # TODO(stephenfin): Wire the microversion up
@unittest.expectedFailure @unittest.expectedFailure
def test_create_invalid_specs_strict(self): def test_create_invalid_known_namespace(self):
"""Test behavior of strict validator.""" """Test behavior of validator with specs from known namespace."""
invalid_specs = { invalid_specs = {
'hw:numa_nodes': 'foo', 'hw:numa_nodes': 'foo',
'hw:cpu_policy': 'sharrred', 'hw:cpu_policy': 'sharrred',
'foo': 'bar',
'hw:cpu_policyyyyyyy': 'shared', 'hw:cpu_policyyyyyyy': 'shared',
'hw:foo': 'bar',
} }
for key, value in invalid_specs.items(): for key, value in invalid_specs.items():
body = {'extra_specs': {key: value}} body = {'extra_specs': {key: value}}
@ -286,68 +286,20 @@ class FlavorsExtraSpecsTestV21(test.TestCase):
): ):
self.controller.create(req, 1, body=body) self.controller.create(req, 1, body=body)
# TODO(stephenfin): Wire the microversion up def test_create_invalid_unknown_namespace(self):
@unittest.expectedFailure """Test behavior of validator with specs from unknown namespace."""
def test_create_invalid_specs_permissive(self): unknown_specs = {
"""Test behavior of permissive validator."""
invalid_specs = {
'hw:numa_nodes': 'foo',
'hw:cpu_policy': 'sharrred',
}
for key, value in invalid_specs.items():
body = {'extra_specs': {key: value}}
req = self._get_request(
'1/os-extra_specs?validation=permissive',
use_admin_context=True, version='2.82',
)
with testtools.ExpectedException(
self.bad_request, 'Validation failed; .*',
):
self.controller.create(req, 1, body=body)
valid_specs = {
'foo': 'bar', 'foo': 'bar',
'hw:cpu_policyyyyyyy': 'shared', 'foo:bar': 'baz',
'hww:cpu_policy': 'sharrred',
} }
for key, value in valid_specs.items(): for key, value in unknown_specs.items():
body = {'extra_specs': {key: value}} body = {'extra_specs': {key: value}}
req = self._get_request( req = self._get_request(
'1/os-extra_specs?validation=permissive', '1/os-extra_specs', use_admin_context=True, version='2.82',
use_admin_context=True, version='2.82',
) )
self.controller.create(req, 1, body=body) self.controller.create(req, 1, body=body)
def test_create_invalid_specs_disabled(self):
"""Test behavior of permissive validator."""
valid_specs = {
'hw:numa_nodes': 'foo',
'hw:cpu_policy': 'sharrred',
'foo': 'bar',
'hw:cpu_policyyyyyyy': 'shared',
}
for key, value in valid_specs.items():
body = {'extra_specs': {key: value}}
req = self._get_request(
'1/os-extra_specs?validation=disabled',
use_admin_context=True, version='2.82',
)
self.controller.create(req, 1, body=body)
# TODO(stephenfin): Wire the microversion up
@unittest.expectedFailure
def test_create_invalid_validator_mode(self):
"""Test behavior with an invalid validator mode."""
body = {'extra_specs': {'hw:numa_nodes': '1'}}
req = self._get_request(
'1/os-extra_specs?validation=foo',
use_admin_context=True, version='2.82',
)
with testtools.ExpectedException(
self.bad_request,
'Invalid input for query parameters validation.*',
):
self.controller.create(req, 1, body=body)
@mock.patch('nova.objects.flavor._flavor_extra_specs_add') @mock.patch('nova.objects.flavor._flavor_extra_specs_add')
def test_create_valid_specs(self, mock_flavor_extra_specs): def test_create_valid_specs(self, mock_flavor_extra_specs):
valid_specs = { valid_specs = {
@ -453,18 +405,18 @@ class FlavorsExtraSpecsTestV21(test.TestCase):
# TODO(stephenfin): Wire the microversion up # TODO(stephenfin): Wire the microversion up
@unittest.expectedFailure @unittest.expectedFailure
def test_update_invalid_specs_strict(self): def test_update_invalid_specs_known_namespace(self):
"""Test behavior of strict validator.""" """Test behavior of validator with specs from known namespace."""
invalid_specs = { invalid_specs = {
'hw:numa_nodes': 'foo', 'hw:numa_nodes': 'foo',
'hw:cpu_policy': 'sharrred', 'hw:cpu_policy': 'sharrred',
'foo': 'bar',
'hw:cpu_policyyyyyyy': 'shared', 'hw:cpu_policyyyyyyy': 'shared',
'hw:foo': 'bar',
} }
for key, value in invalid_specs.items(): for key, value in invalid_specs.items():
body = {key: value} body = {key: value}
req = self._get_request( req = self._get_request(
'1/os-extra_specs/{key}?validation=strict', '1/os-extra_specs/{key}',
use_admin_context=True, version='2.82', use_admin_context=True, version='2.82',
) )
with testtools.ExpectedException( with testtools.ExpectedException(
@ -472,69 +424,21 @@ class FlavorsExtraSpecsTestV21(test.TestCase):
): ):
self.controller.update(req, 1, key, body=body) self.controller.update(req, 1, key, body=body)
# TODO(stephenfin): Wire the microversion up def test_update_invalid_specs_unknown_namespace(self):
@unittest.expectedFailure """Test behavior of validator with specs from unknown namespace."""
def test_update_invalid_specs_permissive(self): unknown_specs = {
"""Test behavior of permissive validator."""
invalid_specs = {
'hw:numa_nodes': 'foo',
'hw:cpu_policy': 'sharrred',
}
for key, value in invalid_specs.items():
body = {key: value}
req = self._get_request(
f'1/os-extra_specs/{key}?validation=permissive',
use_admin_context=True, version='2.82',
)
with testtools.ExpectedException(
self.bad_request, 'Validation failed; .*',
):
self.controller.update(req, 1, key, body=body)
valid_specs = {
'foo': 'bar', 'foo': 'bar',
'hw:cpu_policyyyyyyy': 'shared', 'foo:bar': 'baz',
'hww:cpu_policy': 'sharrred',
} }
for key, value in valid_specs.items(): for key, value in unknown_specs.items():
body = {key: value} body = {key: value}
req = self._get_request( req = self._get_request(
f'1/os-extra_specs/{key}?validation=permissive', f'1/os-extra_specs/{key}',
use_admin_context=True, version='2.82', use_admin_context=True, version='2.82',
) )
self.controller.update(req, 1, key, body=body) self.controller.update(req, 1, key, body=body)
def test_update_invalid_specs_disabled(self):
"""Test behavior of permissive validator."""
valid_specs = {
'hw:numa_nodes': 'foo',
'hw:cpu_policy': 'sharrred',
'foo': 'bar',
'hw:cpu_policyyyyyyy': 'shared',
}
for key, value in valid_specs.items():
body = {key: value}
req = self._get_request(
f'1/os-extra_specs/{key}?validation=disabled',
use_admin_context=True, version='2.82',
)
self.controller.update(req, 1, key, body=body)
# TODO(stephenfin): Wire the microversion up
@unittest.expectedFailure
def test_update_invalid_validator_mode(self):
"""Test behavior with an invalid validator mode."""
key = 'hw:numa_nodes'
body = {'hw:numa_nodes': '1'}
req = self._get_request(
'1/os-extra_specs/{key}?validation=foo',
use_admin_context=True, version='2.82',
)
with testtools.ExpectedException(
self.bad_request,
'Invalid input for query parameters validation.*',
):
self.controller.update(req, 1, key, body=body)
@mock.patch('nova.objects.flavor._flavor_extra_specs_add') @mock.patch('nova.objects.flavor._flavor_extra_specs_add')
def test_update_valid_specs(self, mock_flavor_extra_specs): def test_update_valid_specs(self, mock_flavor_extra_specs):
valid_specs = { valid_specs = {

View File

@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import ddt
import testtools import testtools
from nova.api.validation.extra_specs import validators from nova.api.validation.extra_specs import validators
@ -20,28 +19,43 @@ from nova import exception
from nova import test from nova import test
@ddt.ddt
class TestValidators(test.NoDBTestCase): class TestValidators(test.NoDBTestCase):
@ddt.data('strict', 'permissive', 'disabled') def test_namespaces(self):
def test_spec(self, policy): """Ensure we see at least the in-tree namespaces.
invalid_specs = (
('hw:cpu_realtime_maskk', '^0'), If we add new namespaces, they should be added to this list.
"""
namespaces = {
'accel', 'aggregate_instance_extra_specs', 'capabilities', 'hw',
'hw_rng', 'hw_video', 'os', 'pci_passthrough', 'powervm', 'quota',
'resources', 'trait', 'vmware',
}
self.assertTrue(
namespaces.issubset(validators.NAMESPACES),
f'{namespaces} is not a subset of {validators.NAMESPACES}',
)
def test_spec(self):
unknown_namespaces = (
('hhw:cpu_realtime_mask', '^0'), ('hhw:cpu_realtime_mask', '^0'),
('w:cpu_realtime_mask', '^0'), ('w:cpu_realtime_mask', '^0'),
('hw:cpu_realtime_mas', '^0'),
('hw_cpu_realtime_mask', '^0'), ('hw_cpu_realtime_mask', '^0'),
('foo', 'bar'), ('foo', 'bar'),
) )
for key, value in invalid_specs: for key, value in unknown_namespaces:
if policy == 'strict': validators.validate(key, value)
with testtools.ExpectedException(exception.ValidationError):
validators.validate(key, value, policy)
else:
validators.validate(key, value, policy)
@ddt.data('strict', 'permissive', 'disabled') known_invalid_namespaces = (
def test_value__str(self, policy): ('hw:cpu_realtime_maskk', '^0'),
('hw:cpu_realtime_mas', '^0'),
('hw:foo', 'bar'),
)
for key, value in known_invalid_namespaces:
with testtools.ExpectedException(exception.ValidationError):
validators.validate(key, value)
def test_value__str(self):
valid_specs = ( valid_specs = (
# patterns # patterns
('hw:cpu_realtime_mask', '^0'), ('hw:cpu_realtime_mask', '^0'),
@ -55,7 +69,7 @@ class TestValidators(test.NoDBTestCase):
('hw:pci_numa_affinity_policy', 'legacy'), ('hw:pci_numa_affinity_policy', 'legacy'),
) )
for key, value in valid_specs: for key, value in valid_specs:
validators.validate(key, value, policy) validators.validate(key, value)
invalid_specs = ( invalid_specs = (
# patterns # patterns
@ -70,14 +84,10 @@ class TestValidators(test.NoDBTestCase):
('hw:pci_numa_affinity_policy', 'lgacy'), ('hw:pci_numa_affinity_policy', 'lgacy'),
) )
for key, value in invalid_specs: for key, value in invalid_specs:
if policy in ('strict', 'permissive'): with testtools.ExpectedException(exception.ValidationError):
with testtools.ExpectedException(exception.ValidationError): validators.validate(key, value)
validators.validate(key, value, policy)
else:
validators.validate(key, value, policy)
@ddt.data('strict', 'permissive', 'disabled') def test_value__int(self):
def test_value__int(self, policy):
valid_specs = ( valid_specs = (
('hw:numa_nodes', '1'), ('hw:numa_nodes', '1'),
('os:monitors', '1'), ('os:monitors', '1'),
@ -86,7 +96,7 @@ class TestValidators(test.NoDBTestCase):
('powervm:shared_weight', '255'), ('powervm:shared_weight', '255'),
) )
for key, value in valid_specs: for key, value in valid_specs:
validators.validate(key, value, 'strict') validators.validate(key, value)
invalid_specs = ( invalid_specs = (
('hw:serial_port_count', 'five'), # NaN ('hw:serial_port_count', 'five'), # NaN
@ -98,14 +108,10 @@ class TestValidators(test.NoDBTestCase):
('powervm:shared_weight', '256'), # has max ('powervm:shared_weight', '256'), # has max
) )
for key, value in invalid_specs: for key, value in invalid_specs:
if policy in ('strict', 'permissive'): with testtools.ExpectedException(exception.ValidationError):
with testtools.ExpectedException(exception.ValidationError): validators.validate(key, value)
validators.validate(key, value, policy)
else:
validators.validate(key, value, policy)
@ddt.data('strict', 'permissive', 'disabled') def test_value__bool(self):
def test_value__bool(self, policy):
valid_specs = ( valid_specs = (
('hw:cpu_realtime', '1'), ('hw:cpu_realtime', '1'),
('hw:cpu_realtime', '0'), ('hw:cpu_realtime', '0'),
@ -113,7 +119,7 @@ class TestValidators(test.NoDBTestCase):
('hw:boot_menu', 'y'), ('hw:boot_menu', 'y'),
) )
for key, value in valid_specs: for key, value in valid_specs:
validators.validate(key, value, 'strict') validators.validate(key, value)
invalid_specs = ( invalid_specs = (
('hw:cpu_realtime', '2'), ('hw:cpu_realtime', '2'),
@ -122,8 +128,5 @@ class TestValidators(test.NoDBTestCase):
('hw:boot_menu', 'yah'), ('hw:boot_menu', 'yah'),
) )
for key, value in invalid_specs: for key, value in invalid_specs:
if policy in ('strict', 'permissive'): with testtools.ExpectedException(exception.ValidationError):
with testtools.ExpectedException(exception.ValidationError): validators.validate(key, value)
validators.validate(key, value, policy)
else:
validators.validate(key, value, policy)