Merge "api: Add framework for extra spec validation"

changes/37/708237/2
Zuul 3 years ago committed by Gerrit Code Review
commit 874c2fe329
  1. 84
      doc/source/user/filter-scheduler.rst
  2. 1
      lower-constraints.txt
  3. 34
      nova/api/openstack/compute/flavors_extraspecs.py
  4. 0
      nova/api/validation/extra_specs/__init__.py
  5. 72
      nova/api/validation/extra_specs/aggregate_instance_extra_specs.py
  6. 120
      nova/api/validation/extra_specs/base.py
  7. 112
      nova/api/validation/extra_specs/capabilities.py
  8. 370
      nova/api/validation/extra_specs/hw.py
  9. 57
      nova/api/validation/extra_specs/hw_rng.py
  10. 39
      nova/api/validation/extra_specs/hw_video.py
  11. 51
      nova/api/validation/extra_specs/null.py
  12. 95
      nova/api/validation/extra_specs/os.py
  13. 38
      nova/api/validation/extra_specs/pci_passthrough.py
  14. 271
      nova/api/validation/extra_specs/powervm.py
  15. 103
      nova/api/validation/extra_specs/quota.py
  16. 65
      nova/api/validation/extra_specs/resources.py
  17. 51
      nova/api/validation/extra_specs/traits.py
  18. 79
      nova/api/validation/extra_specs/validators.py
  19. 48
      nova/api/validation/extra_specs/vmware.py
  20. 186
      nova/tests/unit/api/openstack/compute/test_flavors_extra_specs.py
  21. 0
      nova/tests/unit/api/validation/__init__.py
  22. 0
      nova/tests/unit/api/validation/extra_specs/__init__.py
  23. 129
      nova/tests/unit/api/validation/extra_specs/test_validators.py
  24. 1
      requirements.txt
  25. 14
      setup.cfg
  26. 2
      tox.ini

@ -370,42 +370,98 @@ For further details about each of those objects and their corresponding
attributes, refer to the codebase (at least by looking at the other filters
code) or ask for help in the #openstack-nova IRC channel.
In addition, if your custom filter uses non-standard extra specs, you must
register validators for these extra specs. Examples of validators can be found
in the ``nova.api.validation.extra_specs`` module. These should be registered
via the ``nova.api.extra_spec_validator`` `entrypoint`__.
The module containing your custom filter(s) must be packaged and available in
the same environment that nova, or specifically the :program:`nova-scheduler`
service, is available in. As an example, consider the following sample package,
which is the `minimal structure`__ for a standard, setuptools-based Python
package:
the same environment(s) that the nova controllers, or specifically the
:program:`nova-scheduler` and :program:`nova-api` services, are available in.
As an example, consider the following sample package, which is the `minimal
structure`__ for a standard, setuptools-based Python package:
__ https://packaging.python.org/specifications/entry-points/
__ https://python-packaging.readthedocs.io/en/latest/minimal.html
.. code-block:: none
myfilter/
myfilter/
acmefilter/
acmefilter/
__init__.py
validators.py
setup.py
The ``myfilter/myfilter/__init__.py`` could contain something like so:
Where ``__init__.py`` contains:
.. code-block:: python
from oslo_log import log as logging
from nova.scheduler import filters
LOG = logging.getLogger(__name__)
class MyFilter(filters.BaseHostFilter):
class AcmeFilter(filters.BaseHostFilter):
def host_passes(self, host_state, spec_obj):
# do stuff here...
extra_spec = spec_obj.flavor.extra_specs.get('acme:foo')
LOG.info("Extra spec value was '%s'", extra_spec)
# do meaningful stuff here...
return True
``validators.py`` contains:
.. code-block:: python
from nova.api.validation.extra_specs import base
def register():
validators = [
base.ExtraSpecValidator(
name='acme:foo',
description='My custom extra spec.'
value={
'type': str,
'enum': [
'bar',
'baz',
],
},
),
]
return validators
``setup.py`` contains:
.. code-block:: python
from setuptools import setup
setup(
name='acmefilter',
version='0.1',
description='My custom filter',
packages=[
'acmefilter'
],
entry_points={
'nova.api.extra_spec_validators': [
'acme = acmefilter.validators',
],
},
)
To enable this, you would set the following in :file:`nova.conf`:
.. code-block:: ini
[filter_scheduler]
available_filters = nova.scheduler.filters.all_filters
available_filters = myfilter.MyFilter
enabled_filters = ComputeFilter,MyFilter
available_filters = acmefilter.AcmeFilter
enabled_filters = ComputeFilter,AcmeFilter
.. note::
@ -417,9 +473,9 @@ To enable this, you would set the following in :file:`nova.conf`:
includes the filters shipped with nova.
With these settings, nova will use the ``FilterScheduler`` for the scheduler
driver. All of the standard nova filters and the custom ``MyFilter`` filter are
available to the ``FilterScheduler``, but just the ``ComputeFilter`` and
``MyFilter`` will be used on each request.
driver. All of the standard nova filters and the custom ``AcmeFilter`` filter
are available to the ``FilterScheduler``, but just the ``ComputeFilter`` and
``AcmeFilter`` will be used on each request.
Weights
-------

@ -15,6 +15,7 @@ colorama==0.3.9
coverage==4.0
cryptography==2.7
cursive==0.2.1
dataclasses==0.7
ddt==1.0.1
debtcollector==1.19.0
decorator==3.4.0

@ -20,6 +20,7 @@ from nova.api.openstack import common
from nova.api.openstack.compute.schemas import flavors_extraspecs
from nova.api.openstack import wsgi
from nova.api import validation
from nova.api.validation.extra_specs import validators
from nova import exception
from nova.i18n import _
from nova.policies import flavor_extra_specs as fes_policies
@ -28,22 +29,31 @@ from nova import utils
class FlavorExtraSpecsController(wsgi.Controller):
"""The flavor extra specs API controller for the OpenStack API."""
def _get_extra_specs(self, context, flavor_id):
flavor = common.get_flavor(context, flavor_id)
return dict(extra_specs=flavor.extra_specs)
# NOTE(gmann): Max length for numeric value is being checked
# explicitly as json schema cannot have max length check for numeric value
def _check_extra_specs_value(self, specs):
for value in specs.values():
try:
if isinstance(value, (six.integer_types, float)):
value = six.text_type(value)
def _check_extra_specs_value(self, req, specs):
# TODO(stephenfin): Wire this up to check the API microversion
validation_supported = False
validation_mode = 'strict'
for name, value in specs.items():
# NOTE(gmann): Max length for numeric value is being checked
# explicitly as json schema cannot have max length check for
# numeric value
if isinstance(value, (six.integer_types, float)):
value = six.text_type(value)
try:
utils.check_string_length(value, 'extra_specs value',
max_length=255)
except exception.InvalidInput as error:
raise webob.exc.HTTPBadRequest(
explanation=error.format_message())
except exception.InvalidInput as error:
raise webob.exc.HTTPBadRequest(
explanation=error.format_message())
if validation_supported:
validators.validate(name, value, validation_mode)
@wsgi.expected_errors(404)
def index(self, req, flavor_id):
@ -62,7 +72,7 @@ class FlavorExtraSpecsController(wsgi.Controller):
context.can(fes_policies.POLICY_ROOT % 'create')
specs = body['extra_specs']
self._check_extra_specs_value(specs)
self._check_extra_specs_value(req, specs)
flavor = common.get_flavor(context, flavor_id)
try:
flavor.extra_specs = dict(flavor.extra_specs, **specs)
@ -79,7 +89,7 @@ class FlavorExtraSpecsController(wsgi.Controller):
context = req.environ['nova.context']
context.can(fes_policies.POLICY_ROOT % 'update')
self._check_extra_specs_value(body)
self._check_extra_specs_value(req, body)
if id not in body:
expl = _('Request body and URI mismatch')
raise webob.exc.HTTPBadRequest(explanation=expl)

@ -0,0 +1,72 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for (preferrably) ``aggregate_instance_extra_specs`` namespaced
extra specs.
These are used by the ``AggregateInstanceExtraSpecsFilter`` scheduler filter.
Note that we explicitly do not support the unnamespaced variant of extra specs
since these have been deprecated since Havana (commit fbedf60a432). Users that
insist on using these can disable extra spec validation.
"""
from nova.api.validation.extra_specs import base
DESCRIPTION = """\
Specify metadata that must be present on the aggregate of a host. If this
metadata is not present, the host will be rejected. Requires the
``AggregateInstanceExtraSpecsFilter`` scheduler filter.
The value can be one of the following:
* ``=`` (equal to or greater than as a number; same as vcpus case)
* ``==`` (equal to as a number)
* ``!=`` (not equal to as a number)
* ``>=`` (greater than or equal to as a number)
* ``<=`` (less than or equal to as a number)
* ``s==`` (equal to as a string)
* ``s!=`` (not equal to as a string)
* ``s>=`` (greater than or equal to as a string)
* ``s>`` (greater than as a string)
* ``s<=`` (less than or equal to as a string)
* ``s<`` (less than as a string)
* ``<in>`` (substring)
* ``<all-in>`` (all elements contained in collection)
* ``<or>`` (find one of these)
* A specific value, e.g. ``true``, ``123``, ``testing``
"""
EXTRA_SPEC_VALIDATORS = [
base.ExtraSpecValidator(
name='aggregate_instance_extra_specs:{key}',
description=DESCRIPTION,
parameters=[
{
'name': 'key',
'description': 'The metadata key to match on',
'pattern': r'.+',
},
],
value={
# this is totally arbitary, since we need to support specific
# values
'type': str,
},
),
]
def register():
return EXTRA_SPEC_VALIDATORS

@ -0,0 +1,120 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import dataclasses
import re
import typing as ty
from oslo_utils import strutils
from nova import exception
@dataclasses.dataclass
class ExtraSpecValidator:
name: str
description: str
value: ty.Dict[str, ty.Any]
deprecated: bool = False
parameters: ty.List[ty.Dict[str, ty.Any]] = dataclasses.field(
default_factory=list
)
name_regex: str = None
value_regex: str = None
def __post_init__(self):
# generate a regex for the name
name_regex = self.name
# replace the human-readable patterns with named regex groups; this
# will transform e.g. 'hw:numa_cpus.{id}' to 'hw:numa_cpus.(?P<id>\d+)'
for param in self.parameters:
pattern = f'(?P<{param["name"]}>{param["pattern"]})'
name_regex = name_regex.replace(f'{{{param["name"]}}}', pattern)
self.name_regex = name_regex
# ...and do the same for the value, but only if we're using strings
if self.value['type'] not in (int, str, bool):
raise ValueError(
f"Unsupported parameter type '{self.value['type']}'"
)
value_regex = None
if self.value['type'] == str and self.value.get('pattern'):
value_regex = self.value['pattern']
self.value_regex = value_regex
def _validate_str(self, value):
if 'pattern' in self.value:
value_match = re.fullmatch(self.value_regex, value)
if not value_match:
raise exception.ValidationError(
f"Validation failed; '{value}' is not of the format "
f"'{self.value_regex}'."
)
elif 'enum' in self.value:
if value not in self.value['enum']:
values = ', '.join(str(x) for x in self.value['enum'])
raise exception.ValidationError(
f"Validation failed; '{value}' is not one of: {values}."
)
def _validate_int(self, value):
try:
value = int(value)
except ValueError:
raise exception.ValidationError(
f"Validation failed; '{value}' is not a valid integer value."
)
if 'max' in self.value and self.value['max'] < value:
raise exception.ValidationError(
f"Validation failed; '{value}' is greater than the max value "
f"of '{self.value['max']}'."
)
if 'min' in self.value and self.value['min'] > value:
raise exception.ValidationError(
f"Validation failed; '{value}' is less than the min value "
f"of '{self.value['min']}'."
)
def _validate_bool(self, value):
try:
strutils.bool_from_string(value, strict=True)
except ValueError:
raise exception.ValidationError(
f"Validation failed; '{value}' is not a valid boolean-like "
f"value."
)
def validate(self, name, value):
name_match = re.fullmatch(self.name_regex, name)
if not name_match:
# NOTE(stephenfin): This is mainly here for testing purposes
raise exception.ValidationError(
f"Validation failed; expected a name of format '{self.name}' "
f"but got '{name}'."
)
if self.value['type'] == int:
self._validate_int(value)
elif self.value['type'] == bool:
self._validate_bool(value)
else: # str
self._validate_str(value)

@ -0,0 +1,112 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for (preferrably) ``capabilities`` namespaced extra specs.
These are used by the ``ComputeCapabilitiesFilter`` scheduler filter. Note that
we explicitly do not allow the unnamespaced variant of extra specs since this
has been deprecated since Grizzly (commit 8ce8e4b6c0d). Users that insist on
using these can disable extra spec validation.
"""
from nova.api.validation.extra_specs import base
DESCRIPTION = """\
Specify that the '{capability}' capability provided by the host compute service
satisfy the provided filter value. Requires the ``ComputeCapabilitiesFilter``
scheduler filter.
The value can be one of the following:
* ``=`` (equal to or greater than as a number; same as vcpus case)
* ``==`` (equal to as a number)
* ``!=`` (not equal to as a number)
* ``>=`` (greater than or equal to as a number)
* ``<=`` (less than or equal to as a number)
* ``s==`` (equal to as a string)
* ``s!=`` (not equal to as a string)
* ``s>=`` (greater than or equal to as a string)
* ``s>`` (greater than as a string)
* ``s<=`` (less than or equal to as a string)
* ``s<`` (less than as a string)
* ``<in>`` (substring)
* ``<all-in>`` (all elements contained in collection)
* ``<or>`` (find one of these)
* A specific value, e.g. ``true``, ``123``, ``testing``
Examples are: ``>= 5``, ``s== 2.1.0``, ``<in> gcc``, ``<all-in> aes mmx``, and
``<or> fpu <or> gpu``
"""
EXTRA_SPEC_VALIDATORS = []
# non-nested capabilities (from 'nova.objects.compute_node.ComputeNode' and
# nova.scheduler.host_manager.HostState')
for capability in (
'id', 'uuid', 'service_id', 'host', 'vcpus', 'memory_mb', 'local_gb',
'vcpus_used', 'memory_mb_used', 'local_gb_used',
'hypervisor_type', 'hypervisor_version', 'hypervisor_hostname',
'free_ram_mb', 'free_disk_gb', 'current_workload', 'running_vms',
'disk_available_least', 'host_ip', 'mapped',
'cpu_allocation_ratio', 'ram_allocation_ratio', 'disk_allocation_ratio',
) + (
'total_usable_ram_mb', 'total_usable_disk_gb', 'disk_mb_used',
'free_disk_mb', 'vcpus_total', 'vcpus_used', 'num_instances',
'num_io_ops', 'failed_builds', 'aggregates', 'cell_uuid', 'updated',
):
EXTRA_SPEC_VALIDATORS.append(
base.ExtraSpecValidator(
name=f'capabilities:{capability}',
description=DESCRIPTION.format(capability=capability),
value={
# this is totally arbitary, since we need to support specific
# values
'type': str,
},
),
)
# nested capabilities (from 'nova.objects.compute_node.ComputeNode' and
# nova.scheduler.host_manager.HostState')
for capability in (
'cpu_info', 'metrics', 'stats', 'numa_topology', 'supported_hv_specs',
'pci_device_pools',
) + (
'nodename', 'pci_stats', 'supported_instances', 'limits', 'instances',
):
EXTRA_SPEC_VALIDATORS.extend([
base.ExtraSpecValidator(
name=f'capabilities:{capability}{{filter}}',
description=DESCRIPTION.format(capability=capability),
parameters=[
{
'name': 'filter',
# this is optional, but if it's present it must be preceded
# by ':'
'pattern': r'(:\w+)*',
}
],
value={
'type': str,
},
),
])
def register():
return EXTRA_SPEC_VALIDATORS

@ -0,0 +1,370 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for ``hw`` namespaced extra specs."""
from nova.api.validation.extra_specs import base
realtime_validators = [
base.ExtraSpecValidator(
name='hw:cpu_realtime',
description=(
'Determine whether realtime mode should be enabled for the '
'instance or not. Only supported by the libvirt driver.'
),
value={
'type': bool,
'description': 'Whether to enable realtime priority.',
},
),
base.ExtraSpecValidator(
name='hw:cpu_realtime_mask',
description=(
'A exclusion mask of CPUs that should not be enabled for realtime.'
),
value={
'type': str,
# NOTE(stephenfin): Yes, these things *have* to start with '^'
'pattern': r'\^\d+((-\d+)?(,\^?\d+(-\d+)?)?)*',
},
),
]
cpu_policy_validators = [
base.ExtraSpecValidator(
name='hw:cpu_policy',
description=(
'The policy to apply when determining what host CPUs the guest '
'CPUs can run on. If ``shared`` (default), guest CPUs can be '
'overallocated but cannot float across host cores. If '
'``dedicated``, guest CPUs cannot be overallocated but are '
'individually pinned to their own host core.'
),
value={
'type': str,
'description': 'The CPU policy.',
'enum': [
'dedicated',
'shared'
],
},
),
base.ExtraSpecValidator(
name='hw:cpu_thread_policy',
description=(
'The policy to apply when determining whether the destination '
'host can have hardware threads enabled or not. If ``prefer`` '
'(default), hosts with hardware threads will be preferred. If '
'``require``, hosts with hardware threads will be required. If '
'``isolate``, hosts with hardware threads will be forbidden.'
),
value={
'type': str,
'description': 'The CPU thread policy.',
'enum': [
'prefer',
'isolate',
'require',
],
},
),
base.ExtraSpecValidator(
name='hw:emulator_threads_policy',
description=(
'The policy to apply when determining whether emulator threads '
'should be offloaded to a separate isolated core or to a pool '
'of shared cores. If ``share``, emulator overhead threads will '
'be offloaded to a pool of shared cores. If ``isolate``, '
'emulator overhead threads will be offloaded to their own core.'
),
value={
'type': str,
'description': 'The emulator thread policy.',
'enum': [
'isolate',
'share',
],
},
),
]
hugepage_validators = [
base.ExtraSpecValidator(
name='hw:mem_page_size',
description=(
'The size of memory pages to allocate to the guest with. Can be '
'one of the three alias - ``large``, ``small`` or ``any``, - or '
'an actual size. Only supported by the libvirt virt driver.'
),
value={
'type': str,
'description': 'The size of memory page to allocate',
'pattern': r'(large|small|any|\d+([kKMGT]i?)?(b|bit|B)?)',
},
),
]
numa_validators = [
base.ExtraSpecValidator(
name='hw:numa_nodes',
description=(
'The number of virtual NUMA nodes to allocate to configure the '
'guest with. Each virtual NUMA node will be mapped to a unique '
'host NUMA node. Only supported by the libvirt virt driver.'
),
value={
'type': int,
'description': 'The number of virtual NUMA nodes to allocate',
'min': 1,
},
),
base.ExtraSpecValidator(
name='hw:numa_cpus.{id}',
description=(
'A mapping of **guest** CPUs to the **guest** NUMA node '
'identified by ``{id}``. This can be used to provide asymmetric '
'CPU-NUMA allocation and is necessary where the number of guest '
'NUMA nodes is not a factor of the number of guest CPUs.'
),
parameters=[
{
'name': 'id',
'pattern': r'\d+', # positive integers
'description': 'The ID of the **guest** NUMA node.',
},
],
value={
'type': str,
'description': (
'The guest CPUs, in the form of a CPU map, to allocate to the '
'guest NUMA node identified by ``{id}``.'
),
'pattern': r'\^?\d+((-\d+)?(,\^?\d+(-\d+)?)?)*',
},
),
base.ExtraSpecValidator(
name='hw:numa_mem.{id}',
description=(
'A mapping of **guest** memory to the **guest** NUMA node '
'identified by ``{id}``. This can be used to provide asymmetric '
'memory-NUMA allocation and is necessary where the number of '
'guest NUMA nodes is not a factor of the total guest memory.'
),
parameters=[
{
'name': 'id',
'pattern': r'\d+', # positive integers
'description': 'The ID of the **guest** NUMA node.',
},
],
value={
'type': int,
'description': (
'The guest memory, in MB, to allocate to the guest NUMA node '
'identified by ``{id}``.'
),
'min': 1,
},
),
base.ExtraSpecValidator(
name='hw:pci_numa_affinity_policy',
description=(
'The NUMA affinity policy of any PCI passthrough devices or '
'SR-IOV network interfaces attached to the instance.'
),
value={
'type': str,
'description': 'The PCI NUMA affinity policy',
'enum': [
'required',
'preferred',
'legacy',
],
},
),
]
cpu_topology_validators = [
base.ExtraSpecValidator(
name='hw:cpu_sockets',
description=(
'The number of virtual CPU threads to emulate in the guest '
'CPU topology.'
),
value={
'type': int,
'description': 'A number of vurtla CPU sockets',
'min': 1,
},
),
base.ExtraSpecValidator(
name='hw:cpu_cores',
description=(
'The number of virtual CPU cores to emulate per socket in the '
'guest CPU topology.'
),
value={
'type': int,
'description': 'A number of virtual CPU cores',
'min': 1,
},
),
base.ExtraSpecValidator(
name='hw:cpu_threads',
description=(
'The number of virtual CPU threads to emulate per core in the '
'guest CPU topology.'
),
value={
'type': int,
'description': 'A number of virtual CPU threads',
'min': 1,
},
),
base.ExtraSpecValidator(
name='hw:max_cpu_sockets',
description=(
'The max number of virtual CPU threads to emulate in the '
'guest CPU topology. This is used to limit the topologies that '
'can be requested by an image and will be used to validate the '
'``hw_cpu_sockets`` image metadata property.'
),
value={
'type': int,
'description': 'A number of virtual CPU sockets',
'min': 1,
},
),
base.ExtraSpecValidator(
name='hw:max_cpu_cores',
description=(
'The max number of virtual CPU cores to emulate per socket in the '
'guest CPU topology. This is used to limit the topologies that '
'can be requested by an image and will be used to validate the '
'``hw_cpu_cores`` image metadata property.'
),
value={
'type': int,
'description': 'A number of virtual CPU cores',
'min': 1,
},
),
base.ExtraSpecValidator(
name='hw:max_cpu_threads',
description=(
'The max number of virtual CPU threads to emulate per core in the '
'guest CPU topology. This is used to limit the topologies that '
'can be requested by an image and will be used to validate the '
'``hw_cpu_threads`` image metadata property.'
),
value={
'type': int,
'description': 'A number of virtual CPU threads',
'min': 1,
},
),
]
feature_flag_validators = [
# TODO(stephenfin): Consider deprecating and moving this to the 'os:'
# namespace
base.ExtraSpecValidator(
name='hw:boot_menu',
description=(
'Whether to show a boot menu when booting the guest.'
),
value={
'type': bool,
'description': 'Whether to enable the boot menu',
},
),
base.ExtraSpecValidator(
name='hw:mem_encryption',
description=(
'Whether to enable memory encryption for the guest. Only '
'supported by the libvirt driver on hosts with AMD SEV support.'
),
value={
'type': bool,
'description': 'Whether to enable memory encryption',
},
),
base.ExtraSpecValidator(
name='hw:pmem',
description=(
'A comma-separated list of ``$LABEL``\\ s defined in config for '
'vPMEM devices.'
),
value={
'type': str,
'description': (
'A comma-separated list of valid resource class names.'
),
'pattern': '([a-zA-Z0-9_]+(,)?)+',
},
),
base.ExtraSpecValidator(
name='hw:pmu',
description=(
'Whether to enable the Performance Monitory Unit (PMU) for the '
'guest. Only supported by the libvirt driver.'
),
value={
'type': bool,
'description': 'Whether to enable the PMU',
},
),
base.ExtraSpecValidator(
name='hw:serial_port_count',
description=(
'The number of serial ports to allocate to the guest. Only '
'supported by the libvirt virt driver.'
),
value={
'type': int,
'min': 0,
'description': 'The number of serial ports to allocate',
},
),
base.ExtraSpecValidator(
name='hw:watchdog_action',
description=(
'The action to take when the watchdog timer is kicked. Only '
'supported by the libvirt virt driver.'
),
value={
'type': str,
'description': 'The action to take',
'enum': [
'none',
'pause',
'poweroff',
'reset',
'disabled',
],
},
),
]
def register():
return (
realtime_validators +
cpu_policy_validators +
hugepage_validators +
numa_validators +
cpu_topology_validators +
feature_flag_validators
)

@ -0,0 +1,57 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for ``hw_rng`` namespaced extra specs."""
from nova.api.validation.extra_specs import base
# TODO(stephenfin): Move these to the 'hw:' namespace
EXTRA_SPEC_VALIDATORS = [
base.ExtraSpecValidator(
name='hw_rng:allowed',
description=(
'Whether to disable configuration of a random number generator '
'in their image. Before 21.0.0 (Ussuri), random number generators '
'were not enabled by default so this was used to determine '
'whether to **enable** configuration.'
),
value={
'type': bool,
},
),
base.ExtraSpecValidator(
name='hw_rng:rate_bytes',
description=(
'The allowed amount of bytes for the guest to read from the '
'host\'s entropy per period.'
),
value={
'type': int,
'min': 0,
},
),
base.ExtraSpecValidator(
name='hw_rng:rate_period',
description='The duration of a read period in seconds.',
value={
'type': int,
'min': 0,
},
),
]
def register():
return EXTRA_SPEC_VALIDATORS

@ -0,0 +1,39 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for ``hw_video`` namespaced extra specs."""
from nova.api.validation.extra_specs import base
# TODO(stephenfin): Move these to the 'hw:' namespace
EXTRA_SPEC_VALIDATORS = [
base.ExtraSpecValidator(
name='hw_video:ram_max_mb',
description=(
'The maximum amount of memory the user can request using the '
'``hw_video_ram`` image metadata property, which represents the '
'video memory that the guest OS will see. This has no effect for '
'vGPUs.'
),
value={
'type': int,
'min': 0,
},
),
]
def register():
return EXTRA_SPEC_VALIDATORS

@ -0,0 +1,51 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for non-namespaced extra specs."""
from nova.api.validation.extra_specs import base
# TODO(stephenfin): These should be moved to a namespace
EXTRA_SPEC_VALIDATORS = [
base.ExtraSpecValidator(
name='hide_hypervisor_id',
description=(
'Determine whether the hypervisor ID should be hidden from the '
'guest. Only supported by the libvirt driver.'
),
value={
'type': bool,
'description': 'Whether to hide the hypervisor ID.',
},
),
base.ExtraSpecValidator(
name='group_policy',
description=(
'The group policy to apply when using the granular resource '
'request syntax.'
),
value={
'type': str,
'enum': [
'isolate',
'none',
],
},
),
]
def register():
return EXTRA_SPEC_VALIDATORS

@ -0,0 +1,95 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for ``os`` namespaced extra specs."""
from nova.api.validation.extra_specs import base
# TODO(stephenfin): Most of these belong in the 'hw:' or 'hyperv:' namespace
# and should be moved.
EXTRA_SPEC_VALIDATORS = [
base.ExtraSpecValidator(
name='os:secure_boot',
description=(
'Determine whether secure boot is enabled or not. Currently only '
'supported by the HyperV driver.'
),
value={
'type': str,
'description': 'Whether secure boot is required or not',
'enum': [
'disabled',
'required',
],
},
),
base.ExtraSpecValidator(
name='os:resolution',
description=(
'Guest VM screen resolution size. Only supported by the HyperV '
'driver.'
),
value={
'type': str,
'description': 'The chosen resolution',
'enum': [
'1024x768',
'1280x1024',
'1600x1200',
'1920x1200',
'2560x1600',
'3840x2160',
],
},
),
base.ExtraSpecValidator(
name='os:monitors',
description=(
'Guest VM number of monitors. Only supported by the HyperV driver.'
),
value={
'type': int,
'description': 'The number of monitors enabled',
'min': 1,
'max': 8,
},
),
# TODO(stephenfin): Consider merging this with the 'hw_video_ram' image
# metadata property or adding a 'hw:video_ram' extra spec that works for
# both Hyper-V and libvirt.
base.ExtraSpecValidator(
name='os:vram',
description=(
'Guest VM VRAM amount. Only supported by the HyperV driver.'
),
# NOTE(stephenfin): This is really an int, but because there's a
# limited range of options we treat it as a string
value={
'type': str,
'description': 'Amount of VRAM to allocate to instance',
'enum': [
'64',
'128',
'256',
'512',
'1024',
],
},
),
]
def register():
return EXTRA_SPEC_VALIDATORS

@ -0,0 +1,38 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for ``pci_passthrough`` namespaced extra specs."""
from nova.api.validation.extra_specs import base
EXTRA_SPEC_VALIDATORS = [
base.ExtraSpecValidator(
name='pci_passthrough:alias',
description=(
'Specify the number of ``$alias`` PCI device(s) to attach to the '
'instance. Must be of format ``$alias:$number``. Use commas to '
'specify multiple values.'
),
value={
'type': str,
# one or more comma-separated '$alias:$num' values
'pattern': r'[^:]+:\d+(?:\s*,\s*[^:]+:\d+)*',
},
),
]
def register():
return EXTRA_SPEC_VALIDATORS

@ -0,0 +1,271 @@
# Copyright 2020 Red Hat, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Validators for ``powervm`` namespaced extra specs.
These were all taken from the IBM documentation.
https://www.ibm.com/support/knowledgecenter/SSXK2N_1.4.4/com.ibm.powervc.standard.help.doc/powervc_pg_flavorsextraspecs_hmc.html
"""
from nova.api.validation.extra_specs import base
# TODO(stephenfin): A lot of these seem to overlap with existing 'hw:' extra
# specs and could be deprecated in favour of those.
EXTRA_SPEC_VALIDATORS = [
base.ExtraSpecValidator(
name='powervm:min_mem',
description=(
'Minimum memory (MB). If you do not specify the value, the value '
'is defaulted to the value for ``memory_mb``.'
),
value={
'type': int,
'min': 256,
'description': 'Integer >=256 divisible by LMB size of the target',
},
),
base.ExtraSpecValidator(
name='powervm:max_mem',
description=(
'Maximum memory (MB). If you do not specify the value, the value '
'is defaulted to the value for ``memory_mb``.'
),
value={
'type': int,
'min': 256,
'description': 'Integer >=256 divisible by LMB size of the target',
},
),
base.ExtraSpecValidator(
name='powervm:min_vcpu',
description=(
'Minimum virtual processors. Minimum resource that is required '
'for LPAR to boot is 1. The maximum value can be equal to the '
'value, which is set to vCPUs. If you specify the value of the '
'attribute, you must also specify value of powervm:max_vcpu. '
'Defaults to value set for vCPUs.'
),
value={
'type': int,
'min': 1,
},
),
base.ExtraSpecValidator(
name='powervm:max_vcpu',
description=(
'Minimum virtual processors. Minimum resource that is required '
'for LPAR to boot is 1. The maximum value can be equal to the '
'value, which is set to vCPUs. If you specify the value of the '
'attribute, you must also specify value of powervm:max_vcpu. '
'Defaults to value set for vCPUs.'
),
value={
'type': int,
'min': 1,
},
),
base.ExtraSpecValidator(
name='powervm:proc_units',
description=(
'The wanted ``proc_units``. The value for the attribute cannot be '
'less than 1/10 of the value that is specified for Virtual '
'CPUs (vCPUs) for hosts with firmware level 7.5 or earlier and '
'1/20 of the value that is specified for vCPUs for hosts with '
'firmware level 7.6 or later. If the value is not specified '
'during deployment, it is defaulted to vCPUs * 0.5.'
),
value={
'type': str,
'pattern': r'\d+\.\d+',
'description': (
'Float (divisible by 0.1 for hosts with firmware level 7.5 or '
'earlier and 0.05 for hosts with firmware level 7.6 or later)'
),
},
),
base.ExtraSpecValidator(
name='powervm:min_proc_units',
description=(
'Minimum ``proc_units``. The minimum value for the attribute is '
'0.1 for hosts with firmware level 7.5 or earlier and 0.05 for '
'hosts with firmware level 7.6 or later. The maximum value must '
'be equal to the maximum value of ``powervm:proc_units``. If you '
'specify the attribute, you must also specify '
'``powervm:proc_units``, ``powervm:max_proc_units``, '
'``powervm:min_vcpu``, `powervm:max_vcpu``, and '
'``powervm:dedicated_proc``. Set the ``powervm:dedicated_proc`` '
'to false.'
'\n'
'The value for the attribute cannot be less than 1/10 of the '
'value that is specified for powervm:min_vcpu for hosts with '
'firmware level 7.5 or earlier and 1/20 of the value that is '
'specified for ``powervm:min_vcpu`` for hosts with firmware '
'level 7.6 or later. If you do not specify the value of the '
'attribute during deployment, it is defaulted to equal the value '
'of ``powervm:proc_units``.'
),
value={
'type': str,
'pattern': r'\d+\.\d+',
'description': (
'Float (divisible by 0.1 for hosts with firmware level 7.5 or '
'earlier and 0.05 for hosts with firmware level 7.6 or later)'
),
},
),
base.ExtraSpecValidator(
name='powervm:max_proc_units',
description=(
'Maximum ``proc_units``. The minimum value can be equal to `` '
'``powervm:proc_units``. The maximum value for the attribute '
'cannot be more than the value of the host for maximum allowed '
'processors per partition. If you specify this attribute, you '
'must also specify ``powervm:proc_units``, '
'``powervm:min_proc_units``, ``powervm:min_vcpu``, '
'``powervm:max_vcpu``, and ``powervm:dedicated_proc``. Set the '
'``powervm:dedicated_proc`` to false.'
'\n'
'The value for the attribute cannot be less than 1/10 of the '
'value that is specified for powervm:max_vcpu for hosts with '
'firmware level 7.5 or earlier and 1/20 of the value that is '
'specified for ``powervm:max_vcpu`` for hosts with firmware '
'level 7.6 or later. If you do not specify the value of the '
'attribute during deployment, the value is defaulted to equal the '
'value of ``powervm:proc_units``.'
),
value={
'type': str,
'pattern': r'\d+\.\d+',
'description': (
'Float (divisible by 0.1 for hosts with firmware level 7.5 or '
'earlier and 0.05 for hosts with firmware level 7.6 or later)'
),
},
),
base.ExtraSpecValidator(
name='powervm:dedicated_proc',
description=(
'Use dedicated processors. The attribute defaults to false.'
),
value={
'type': bool,
},
),
base.ExtraSpecValidator(
name='powervm:shared_weight',
description=(
'Shared processor weight. When ``powervm:dedicated_proc`` is set '
'to true and ``powervm:uncapped`` is also set to true, the value '
'of the attribute defaults to 128.'
),
value={
'type': int,
'min': 0,
'max': 255,
},
),
base.ExtraSpecValidator(
name='powervm:availability_priority',
description=(
'Availability priority. The attribute priority of the server if '
'there is a processor failure and there are not enough resources '
'for all servers. VIOS and i5 need to remain high priority '
'default of 191. The value of the attribute defaults to 128.'
),
value={
'type': int,
'min': 0,
'max': 255,
},
),
base.ExtraSpecValidator(
name='powervm:uncapped',
description=(
'LPAR can use unused processor cycles that are beyond or exceed '
'the wanted setting of the attribute. This attribute is '
'supported only when ``powervm:dedicated_proc`` is set to false. '
'When ``powervm:dedicated_proc`` is set to false, '
'``powervm:uncapped`` defaults to true.'
),
value={
'type': bool,
},
),
base.ExtraSpecValidator(
name='powervm:dedicated_sharing_mode',
description=(
'Sharing mode for dedicated processors. The attribute is '
'supported only when ``powervm:dedicated_proc`` is set to true.'
),
value={
'type': str,
'enum': (
'share_idle_procs',
'keep_idle_procs',
'share_idle_procs_active',
'share_idle_procs_always',
)
},
),
base.ExtraSpecValidator(
name='powervm:processor_compatibility',
description=(
'A processor compatibility mode is a value that is assigned to a '
'logical partition by the hypervisor that specifies the processor '
'environment in which the logical partition can successfully '
'operate.'
),
value={
'type': str,
'enum': (
'default',
'POWER6',
'POWER6+',
'POWER6_Enhanced',
'POWER6+_Enhanced',
'POWER7',
'POWER8'