rehome flavor extension API definition

This patch rehomes the flavor extension API definition into neutron-lib
along with it's associated exceptions and validator. Please see the
release notes for some minor refactoring that's done as part of this
patch.

Change-Id: I0229a80bb168bac8dc0fa17fb2b06f1b140d27b4
This commit is contained in:
Boden R 2017-08-15 12:36:41 -06:00
parent 5e6153b144
commit 3ee0386c31
8 changed files with 266 additions and 1 deletions

View File

@ -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,

View File

@ -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 = {}

View File

@ -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):

View File

@ -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.")

View File

@ -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.")

View File

@ -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',)

View File

@ -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')

View File

@ -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``.