diff --git a/neutron_lib/api/definitions/__init__.py b/neutron_lib/api/definitions/__init__.py index 0a466a88b..31bf4c017 100644 --- a/neutron_lib/api/definitions/__init__.py +++ b/neutron_lib/api/definitions/__init__.py @@ -29,6 +29,7 @@ from neutron_lib.api.definitions import fip64 from neutron_lib.api.definitions import firewall from neutron_lib.api.definitions import firewall_v2 from neutron_lib.api.definitions import firewallrouterinsertion +from neutron_lib.api.definitions import flavors from neutron_lib.api.definitions import ip_allocation from neutron_lib.api.definitions import l2_adjacency from neutron_lib.api.definitions import l3 @@ -80,6 +81,7 @@ _ALL_API_DEFINITIONS = { firewall, firewall_v2, firewallrouterinsertion, + flavors, ip_allocation, l2_adjacency, l3, diff --git a/neutron_lib/api/definitions/flavors.py b/neutron_lib/api/definitions/flavors.py new file mode 100644 index 000000000..6dc9bddab --- /dev/null +++ b/neutron_lib/api/definitions/flavors.py @@ -0,0 +1,124 @@ +# 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. + +from neutron_lib.api import converters +from neutron_lib.db import constants as db_const + +FLAVOR = 'flavor' +FLAVORS = FLAVOR + 's' +SERVICE_PROFILES = 'service_profiles' +NEXT_PROVIDERS = 'next_providers' + +ALIAS = FLAVORS +IS_SHIM_EXTENSION = False +IS_STANDARD_ATTR_EXTENSION = False +NAME = 'Neutron Service Flavors' +API_PREFIX = '' +DESCRIPTION = 'Flavor specification for Neutron advanced services.' +UPDATED_TIMESTAMP = '2015-09-17T10:00:00-00:00' +RESOURCE_ATTRIBUTE_MAP = { + FLAVORS: { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': db_const.NAME_FIELD_SIZE}, + 'is_visible': True, 'default': ''}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string_or_none': + db_const.LONG_DESCRIPTION_FIELD_SIZE}, + 'is_visible': True, 'default': ''}, + 'service_type': {'allow_post': True, 'allow_put': False, + 'validate': + {'type:service_plugin_type': None}, + 'is_visible': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': { + 'type:string': db_const.PROJECT_ID_FIELD_SIZE}, + 'is_visible': True}, + 'service_profiles': {'allow_post': True, 'allow_put': True, + 'validate': {'type:uuid_list': None}, + 'is_visible': True, 'default': []}, + 'enabled': {'allow_post': True, 'allow_put': True, + 'convert_to': converters.convert_to_boolean_if_not_none, + 'default': True, + 'is_visible': True}, + }, + SERVICE_PROFILES: { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'description': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string_or_none': + db_const.LONG_DESCRIPTION_FIELD_SIZE}, + 'is_visible': True, 'default': ''}, + 'driver': {'allow_post': True, 'allow_put': True, + 'validate': {'type:string': + db_const.LONG_DESCRIPTION_FIELD_SIZE}, + 'is_visible': True, + 'default': ''}, + 'metainfo': {'allow_post': True, 'allow_put': True, + 'is_visible': True, + 'default': ''}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': { + 'type:string': db_const.PROJECT_ID_FIELD_SIZE}, + 'is_visible': True}, + 'enabled': {'allow_post': True, 'allow_put': True, + 'convert_to': converters.convert_to_boolean_if_not_none, + 'is_visible': True, 'default': True}, + }, +} +SUB_RESOURCE_ATTRIBUTE_MAP = { + NEXT_PROVIDERS: { + 'parent': {'collection_name': FLAVORS, + 'member_name': FLAVOR}, + 'parameters': {'provider': {'allow_post': False, + 'allow_put': False, + 'is_visible': True}, + 'driver': {'allow_post': False, + 'allow_put': False, + 'is_visible': True}, + 'metainfo': {'allow_post': False, + 'allow_put': False, + 'is_visible': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': { + 'type:string': + db_const.PROJECT_ID_FIELD_SIZE}, + 'is_visible': True}} + }, + SERVICE_PROFILES: { + 'parent': {'collection_name': FLAVORS, + 'member_name': FLAVOR}, + 'parameters': {'id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': { + 'type:string': + db_const.PROJECT_ID_FIELD_SIZE}, + 'is_visible': True}} + } +} +ACTION_MAP = {} +REQUIRED_EXTENSIONS = [] +OPTIONAL_EXTENSIONS = [] +ACTION_STATUS = {} diff --git a/neutron_lib/api/validators/__init__.py b/neutron_lib/api/validators/__init__.py index 8ccfd69f2..51fe4448b 100644 --- a/neutron_lib/api/validators/__init__.py +++ b/neutron_lib/api/validators/__init__.py @@ -25,6 +25,8 @@ import six from neutron_lib._i18n import _ from neutron_lib import constants from neutron_lib import exceptions as n_exc +from neutron_lib.plugins import directory + LOG = logging.getLogger(__name__) @@ -1015,6 +1017,20 @@ def validate_subports(data, valid_values=None): segmentations[segmentation_type].add(segmentation_id) +def validate_service_plugin_type(data, valid_values=None): + """Validates data is a valid service plugin. + + :param data: The service plugin type to validate. + :param valid_values: Not used. + :returns: None if data is a valid service plugin known to the plugin + directory. + :raises: InvalidServiceType if data is not a service known by the + plugin directory. + """ + if not directory.get_plugin(data): + raise n_exc.InvalidServiceType(service_type=data) + + # Dictionary that maintains a list of validation functions validators = {'type:dict': validate_dict, 'type:dict_or_none': validate_dict_or_none, @@ -1055,7 +1071,8 @@ validators = {'type:dict': validate_dict, 'type:integer': validate_integer, 'type:list_of_unique_strings': validate_list_of_unique_strings, 'type:list_of_any_key_specs_or_none': - validate_any_key_specs_or_none} + validate_any_key_specs_or_none, + 'type:service_plugin_type': validate_service_plugin_type} def _to_validation_type(validation_type): diff --git a/neutron_lib/exceptions/__init__.py b/neutron_lib/exceptions/__init__.py index a29a08164..ea9efb4ea 100644 --- a/neutron_lib/exceptions/__init__.py +++ b/neutron_lib/exceptions/__init__.py @@ -531,3 +531,11 @@ class NetworkMacAddressGenerationFailure(ServiceUnavailable): :param net_id: The ID of the network MAC address generation failed on. """ message = _("Unable to generate unique mac on network %(net_id)s.") + + +class InvalidServiceType(InvalidInput): + """An error due to an invalid service type. + + :param service_type: The service type that's invalid. + """ + message = _("Invalid service type: %(service_type)s.") diff --git a/neutron_lib/exceptions/flavors.py b/neutron_lib/exceptions/flavors.py new file mode 100644 index 000000000..f852277fe --- /dev/null +++ b/neutron_lib/exceptions/flavors.py @@ -0,0 +1,58 @@ +# 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. + +from neutron_lib._i18n import _ +from neutron_lib import exceptions + + +class FlavorNotFound(exceptions.NotFound): + message = _("Flavor %(flavor_id)s could not be found.") + + +class FlavorInUse(exceptions.InUse): + message = _("Flavor %(flavor_id)s is used by some service instance.") + + +class ServiceProfileNotFound(exceptions.NotFound): + message = _("Service Profile %(sp_id)s could not be found.") + + +class ServiceProfileInUse(exceptions.InUse): + message = _("Service Profile %(sp_id)s is used by some service instance.") + + +class FlavorServiceProfileBindingExists(exceptions.Conflict): + message = _("Service Profile %(sp_id)s is already associated " + "with flavor %(fl_id)s.") + + +class FlavorServiceProfileBindingNotFound(exceptions.NotFound): + message = _("Service Profile %(sp_id)s is not associated " + "with flavor %(fl_id)s.") + + +class ServiceProfileDriverNotFound(exceptions.NotFound): + message = _("Service Profile driver %(driver)s could not be found.") + + +class ServiceProfileEmpty(exceptions.InvalidInput): + message = _("Service Profile needs either a driver or metainfo.") + + +class FlavorDisabled(exceptions.ServiceUnavailable): + message = _("Flavor is not enabled.") + + +class ServiceProfileDisabled(exceptions.ServiceUnavailable): + message = _("Service Profile is not enabled.") diff --git a/neutron_lib/tests/unit/api/definitions/test_flavors.py b/neutron_lib/tests/unit/api/definitions/test_flavors.py new file mode 100644 index 000000000..6cca0a931 --- /dev/null +++ b/neutron_lib/tests/unit/api/definitions/test_flavors.py @@ -0,0 +1,24 @@ +# 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. + +from neutron_lib.api.definitions import flavors +from neutron_lib.tests.unit.api.definitions import base + + +class FlavorsDefinitionTestCase(base.DefinitionBaseTestCase): + extension_module = flavors + extension_resources = (flavors.FLAVORS, + flavors.SERVICE_PROFILES,) + extension_subresources = (flavors.NEXT_PROVIDERS, + flavors.SERVICE_PROFILES,) + extension_attributes = ('enabled', 'provider', 'metainfo', 'driver', + flavors.SERVICE_PROFILES, 'service_type',) diff --git a/neutron_lib/tests/unit/api/validators/test_validators.py b/neutron_lib/tests/unit/api/validators/test_validators.py index a41f3e4e5..703812ce9 100644 --- a/neutron_lib/tests/unit/api/validators/test_validators.py +++ b/neutron_lib/tests/unit/api/validators/test_validators.py @@ -22,6 +22,8 @@ from neutron_lib.api.definitions import extra_dhcp_opt from neutron_lib.api import validators from neutron_lib import constants from neutron_lib import exceptions as n_exc +from neutron_lib import fixture +from neutron_lib.plugins import directory from neutron_lib.tests import _base as base @@ -1202,3 +1204,22 @@ class TestAnyKeySpecs(base.BaseTestCase): self.assertIsNone( validators.validate_any_key_specs_or_none( data, key_specs=extra_dhcp_opt.EXTRA_DHCP_OPT_KEY_SPECS)) + + +class TestServicePluginType(base.BaseTestCase): + + def setUp(self): + super(TestServicePluginType, self).setUp() + self._plugins = directory._PluginDirectory() + self._plugins.add_plugin('stype', mock.Mock()) + self.useFixture(fixture.PluginDirectoryFixture( + plugin_directory=self._plugins)) + + def test_valid_plugin_type(self): + self.assertIsNone(validators.validate_service_plugin_type('stype')) + + def test_invalid_plugin_type(self): + self.assertRaisesRegex( + n_exc.InvalidServiceType, + 'Invalid service type', + validators.validate_service_plugin_type, 'ntype') diff --git a/releasenotes/notes/rehome-flavors-apidef-ef84b2c1c7eaeed7.yaml b/releasenotes/notes/rehome-flavors-apidef-ef84b2c1c7eaeed7.yaml new file mode 100644 index 000000000..bfff1c54b --- /dev/null +++ b/releasenotes/notes/rehome-flavors-apidef-ef84b2c1c7eaeed7.yaml @@ -0,0 +1,11 @@ +--- +features: + - The ``flavors`` API definition is now available in + ``neutron_lib.api.definitions.flavors``. + - A new ``type:service_plugin_type`` validator has been added that + allows a service plugin to be validated at runtime by checking + the ``neutron_lib.plugins.directory``. + - Exceptions related to the ``flavor`` API have been added to + ``neutron_lib.exceptions.flavors`` except for + ``InvalidFlavorServiceType`` which is now a generic + ``InvalidServiceType`` in ``neutron_lib.exceptions``.