Make VPN endpoint groups aware from API

There is no way to check whether the VPN endpoint groups feature
is available without sending an API request at now.
This commit split VPN endpoint groups feature as an extension.
By doing so, API consumers including Horizon can know the feature
is available through Neutron extension list API.

Note that this commit touches only the API layer and does not
touch the DB layer because the endpoint groups feature is
the first citizen of VPNaaS API.

Closes-Bug: #1515670
Change-Id: I82a3ab60b5a50eee2b4a84b54829a323cd2b3fb1
This commit is contained in:
Akihiro Motoki 2015-12-24 06:34:52 +09:00 committed by Akihiro Motoki
parent dcdfc8a2f9
commit 1afd7fc22a
6 changed files with 324 additions and 194 deletions

View File

@ -34,13 +34,16 @@ from sqlalchemy.orm import exc
from neutron_vpnaas._i18n import _LW
from neutron_vpnaas.db.vpn import vpn_models
from neutron_vpnaas.db.vpn import vpn_validator
from neutron_vpnaas.extensions import vpn_endpoint_groups
from neutron_vpnaas.extensions import vpnaas
from neutron_vpnaas.services.vpn.common import constants as v_constants
LOG = logging.getLogger(__name__)
class VPNPluginDb(vpnaas.VPNPluginBase, base_db.CommonDbMixin):
class VPNPluginDb(vpnaas.VPNPluginBase,
vpn_endpoint_groups.VPNEndpointGroupsPluginBase,
base_db.CommonDbMixin):
"""VPN plugin database class using SQLAlchemy models."""
def _get_validator(self):

View File

@ -0,0 +1,121 @@
# (c) Copyright 2015 NEC Corporation, 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 abc
import six
from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import resource_helper
from neutron.plugins.common import constants as nconstants
from neutron_vpnaas.services.vpn.common import constants
RESOURCE_ATTRIBUTE_MAP = {
'endpoint_groups': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': attr.NAME_MAX_LEN},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': attr.DESCRIPTION_MAX_LEN},
'is_visible': True, 'default': ''},
'type': {'allow_post': True, 'allow_put': False,
'validate': {
'type:values': constants.VPN_SUPPORTED_ENDPOINT_TYPES,
},
'is_visible': True},
'endpoints': {'allow_post': True, 'allow_put': False,
'convert_to': attr.convert_to_list,
'is_visible': True},
},
}
class Vpn_endpoint_groups(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "VPN Endpoint Groups"
@classmethod
def get_alias(cls):
return "vpn-endpoint-groups"
@classmethod
def get_description(cls):
return "VPN endpoint groups support"
@classmethod
def get_updated(cls):
return "2015-08-04T10:00:00-00:00"
@classmethod
def get_resources(cls):
plural_mappings = resource_helper.build_plural_mappings(
{}, RESOURCE_ATTRIBUTE_MAP)
attr.PLURALS.update(plural_mappings)
return resource_helper.build_resource_info(plural_mappings,
RESOURCE_ATTRIBUTE_MAP,
nconstants.VPN,
register_quota=True,
translate_name=True)
def get_required_extensions(self):
return ["vpnaas"]
def update_attributes_map(self, attributes):
super(Vpn_endpoint_groups, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}
@six.add_metaclass(abc.ABCMeta)
class VPNEndpointGroupsPluginBase(object):
@abc.abstractmethod
def create_endpoint_group(self, context, endpoint_group):
pass
@abc.abstractmethod
def update_endpoint_group(self, context, endpoint_group_id,
endpoint_group):
pass
@abc.abstractmethod
def delete_endpoint_group(self, context, endpoint_group_id):
pass
@abc.abstractmethod
def get_endpoint_group(self, context, endpoint_group_id, fields=None):
pass
@abc.abstractmethod
def get_endpoint_groups(self, context, filters=None, fields=None):
pass

View File

@ -24,8 +24,6 @@ from neutron.common import exceptions as nexception
from neutron.plugins.common import constants as nconstants
from neutron.services import service_base
from neutron_vpnaas.services.vpn.common import constants
class VPNServiceNotFound(nexception.NotFound):
message = _("VPNService %(vpnservice_id)s could not be found")
@ -423,31 +421,6 @@ RESOURCE_ATTRIBUTE_MAP = {
'validate': {'type:values': vpn_supported_pfs},
'is_visible': True}
},
'endpoint_groups': {
'id': {'allow_post': False, 'allow_put': False,
'validate': {'type:uuid': None},
'is_visible': True,
'primary_key': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
'required_by_policy': True,
'is_visible': True},
'name': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': attr.NAME_MAX_LEN},
'is_visible': True, 'default': ''},
'description': {'allow_post': True, 'allow_put': True,
'validate': {'type:string': attr.DESCRIPTION_MAX_LEN},
'is_visible': True, 'default': ''},
'type': {'allow_post': True, 'allow_put': False,
'validate': {
'type:values': constants.VPN_SUPPORTED_ENDPOINT_TYPES,
},
'is_visible': True},
'endpoints': {'allow_post': True, 'allow_put': False,
'convert_to': attr.convert_to_list,
'is_visible': True},
},
}
@ -595,24 +568,3 @@ class VPNPluginBase(service_base.ServicePluginBase):
@abc.abstractmethod
def delete_ipsecpolicy(self, context, ipsecpolicy_id):
pass
@abc.abstractmethod
def create_endpoint_group(self, context, endpoint_group):
pass
@abc.abstractmethod
def update_endpoint_group(self, context, endpoint_group_id,
endpoint_group):
pass
@abc.abstractmethod
def delete_endpoint_group(self, context, endpoint_group_id):
pass
@abc.abstractmethod
def get_endpoint_group(self, context, endpoint_group_id, fields=None):
pass
@abc.abstractmethod
def get_endpoint_groups(self, context, filters=None, fields=None):
pass

View File

@ -40,7 +40,9 @@ class VPNPlugin(vpn_db.VPNPluginDb):
Most DB related works are implemented in class
vpn_db.VPNPluginDb.
"""
supported_extension_aliases = ["vpnaas", "service-type"]
supported_extension_aliases = ["vpnaas",
"vpn-endpoint-groups",
"service-type"]
path_prefix = "/vpn"

View File

@ -0,0 +1,196 @@
# (c) Copyright 2015 NEC Corporation, 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 copy
import mock
from oslo_utils import uuidutils
from webob import exc
from neutron.plugins.common import constants as nconstants
from neutron.tests.unit.api.v2 import test_base as test_api_v2
from neutron_vpnaas.extensions import vpn_endpoint_groups
from neutron_vpnaas.extensions import vpnaas
from neutron_vpnaas.services.vpn.common import constants
from neutron_vpnaas.tests import base
_uuid = uuidutils.generate_uuid
_get_path = test_api_v2._get_path
class VpnEndpointGroupsTestPlugin(
vpnaas.VPNPluginBase,
vpn_endpoint_groups.VPNEndpointGroupsPluginBase):
pass
class VpnEndpointGroupsTestCase(base.ExtensionTestCase):
fmt = 'json'
def setUp(self):
super(VpnEndpointGroupsTestCase, self).setUp()
plural_mappings = {'endpoint_group': 'endpoint-groups'}
self._setUpExtension(
'neutron_vpnaas.tests.unit.extensions.test_vpn_endpoint_groups.'
'VpnEndpointGroupsTestPlugin',
nconstants.VPN,
vpn_endpoint_groups.RESOURCE_ATTRIBUTE_MAP,
vpn_endpoint_groups.Vpn_endpoint_groups,
'vpn', plural_mappings=plural_mappings,
use_quota=True)
def helper_test_endpoint_group_create(self, data):
"""Check that the endpoint_group_create works.
Uses passed in endpoint group information, which specifies an
endpoint type and values.
"""
data['endpoint_group'].update({'tenant_id': _uuid(),
'name': 'my endpoint group',
'description': 'my description'})
return_value = copy.copy(data['endpoint_group'])
return_value.update({'id': _uuid()})
instance = self.plugin.return_value
instance.create_endpoint_group.return_value = return_value
res = self.api.post(_get_path('vpn/endpoint-groups', fmt=self.fmt),
self.serialize(data),
content_type='application/%s' % self.fmt)
instance.create_endpoint_group.assert_called_with(
mock.ANY, endpoint_group=data)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
res = self.deserialize(res)
self.assertIn('endpoint_group', res)
self.assertEqual(res['endpoint_group'], return_value)
def test_create_cidr_endpoint_group_create(self):
"""Test creation of CIDR type endpoint group."""
data = {'endpoint_group':
{'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24', '20.20.20.0/24']}}
self.helper_test_endpoint_group_create(data)
def test_create_subnet_endpoint_group_create(self):
"""Test creation of subnet type endpoint group."""
data = {'endpoint_group':
{'type': constants.SUBNET_ENDPOINT,
'endpoints': [_uuid(), _uuid()]}}
self.helper_test_endpoint_group_create(data)
def test_create_vlan_endpoint_group_create(self):
"""Test creation of VLAN type endpoint group."""
data = {'endpoint_group':
{'type': constants.VLAN_ENDPOINT,
'endpoints': ['100', '200', '300', '400']}}
self.helper_test_endpoint_group_create(data)
def test_get_endpoint_group(self):
"""Test show for endpoint group."""
endpoint_group_id = _uuid()
return_value = {'id': endpoint_group_id,
'tenant_id': _uuid(),
'name': 'my-endpoint-group',
'description': 'my endpoint group',
'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24']}
instance = self.plugin.return_value
instance.get_endpoint_group.return_value = return_value
res = self.api.get(_get_path('vpn/endpoint-groups',
id=endpoint_group_id,
fmt=self.fmt))
instance.get_endpoint_group.assert_called_with(mock.ANY,
endpoint_group_id,
fields=mock.ANY)
self.assertEqual(res.status_int, exc.HTTPOk.code)
res = self.deserialize(res)
self.assertIn('endpoint_group', res)
self.assertEqual(res['endpoint_group'], return_value)
def test_endpoint_group_list(self):
"""Test listing all endpoint groups."""
return_value = [{'id': _uuid(),
'tenant_id': _uuid(),
'name': 'my-endpoint-group',
'description': 'my endpoint group',
'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24']},
{'id': _uuid(),
'tenant_id': _uuid(),
'name': 'another-endpoint-group',
'description': 'second endpoint group',
'type': constants.VLAN_ENDPOINT,
'endpoints': ['100', '200', '300']}]
instance = self.plugin.return_value
instance.get_endpoint_groups.return_value = return_value
res = self.api.get(_get_path('vpn/endpoint-groups', fmt=self.fmt))
instance.get_endpoint_groups.assert_called_with(mock.ANY,
fields=mock.ANY,
filters=mock.ANY)
self.assertEqual(res.status_int, exc.HTTPOk.code)
def test_endpoint_group_delete(self):
"""Test deleting an endpoint group."""
self._test_entity_delete('endpoint_group')
def test_endpoint_group_update(self):
"""Test updating endpoint_group."""
endpoint_group_id = _uuid()
update_data = {'endpoint_group': {'description': 'new description'}}
return_value = {'id': endpoint_group_id,
'tenant_id': _uuid(),
'name': 'my-endpoint-group',
'description': 'new_description',
'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24']}
instance = self.plugin.return_value
instance.update_endpoint_group.return_value = return_value
res = self.api.put(_get_path('vpn/endpoint-groups',
id=endpoint_group_id,
fmt=self.fmt),
self.serialize(update_data))
instance.update_endpoint_group.assert_called_with(
mock.ANY, endpoint_group_id, endpoint_group=update_data)
self.assertEqual(res.status_int, exc.HTTPOk.code)
res = self.deserialize(res)
self.assertIn('endpoint_group', res)
self.assertEqual(res['endpoint_group'], return_value)
def test_fail_updating_endpoints_in_endpoint_group(self):
"""Test fails to update the endpoints in an endpoint group.
This documents that we are not allowing endpoints to be updated
(currently), as doing so, implies that the connection using the
enclosing endpoint group would also need to be updated. For now,
a new endpoint group can be created, and the connection can be
updated to point to the new endpoint group.
"""
endpoint_group_id = _uuid()
update_data = {'endpoint_group': {'endpoints': ['10.10.10.0/24']}}
res = self.api.put(_get_path('vpn/endpoint-groups',
id=endpoint_group_id,
fmt=self.fmt),
params=self.serialize(update_data),
expect_errors=True)
self.assertEqual(exc.HTTPBadRequest.code, res.status_int)

View File

@ -21,7 +21,6 @@ from oslo_utils import uuidutils
from webob import exc
from neutron_vpnaas.extensions import vpnaas
from neutron_vpnaas.services.vpn.common import constants
from neutron_vpnaas.tests import base
_uuid = uuidutils.generate_uuid
@ -567,146 +566,3 @@ class VpnaasExtensionTestCase(base.ExtensionTestCase):
def test_ipsec_site_connection_delete(self):
"""Test case to delete a ipsec_site_connection."""
self._test_entity_delete('ipsec_site_connection')
def helper_test_endpoint_group_create(self, data):
"""Check that the endpoint_group_create works.
Uses passed in endpoint group information, which specifies an
endpoint type and values.
"""
data['endpoint_group'].update({'tenant_id': _uuid(),
'name': 'my endpoint group',
'description': 'my description'})
return_value = copy.copy(data['endpoint_group'])
return_value.update({'id': _uuid()})
instance = self.plugin.return_value
instance.create_endpoint_group.return_value = return_value
res = self.api.post(_get_path('vpn/endpoint-groups', fmt=self.fmt),
self.serialize(data),
content_type='application/%s' % self.fmt)
instance.create_endpoint_group.assert_called_with(
mock.ANY, endpoint_group=data)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
res = self.deserialize(res)
self.assertIn('endpoint_group', res)
self.assertEqual(res['endpoint_group'], return_value)
def test_create_cidr_endpoint_group_create(self):
"""Test creation of CIDR type endpoint group."""
data = {'endpoint_group':
{'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24', '20.20.20.0/24']}}
self.helper_test_endpoint_group_create(data)
def test_create_subnet_endpoint_group_create(self):
"""Test creation of subnet type endpoint group."""
data = {'endpoint_group':
{'type': constants.SUBNET_ENDPOINT,
'endpoints': [_uuid(), _uuid()]}}
self.helper_test_endpoint_group_create(data)
def test_create_vlan_endpoint_group_create(self):
"""Test creation of VLAN type endpoint group."""
data = {'endpoint_group':
{'type': constants.VLAN_ENDPOINT,
'endpoints': ['100', '200', '300', '400']}}
self.helper_test_endpoint_group_create(data)
def test_get_endpoint_group(self):
"""Test show for endpoint group."""
endpoint_group_id = _uuid()
return_value = {'id': endpoint_group_id,
'tenant_id': _uuid(),
'name': 'my-endpoint-group',
'description': 'my endpoint group',
'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24']}
instance = self.plugin.return_value
instance.get_endpoint_group.return_value = return_value
res = self.api.get(_get_path('vpn/endpoint-groups',
id=endpoint_group_id,
fmt=self.fmt))
instance.get_endpoint_group.assert_called_with(mock.ANY,
endpoint_group_id,
fields=mock.ANY)
self.assertEqual(res.status_int, exc.HTTPOk.code)
res = self.deserialize(res)
self.assertIn('endpoint_group', res)
self.assertEqual(res['endpoint_group'], return_value)
def test_endpoint_group_list(self):
"""Test listing all endpoint groups."""
return_value = [{'id': _uuid(),
'tenant_id': _uuid(),
'name': 'my-endpoint-group',
'description': 'my endpoint group',
'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24']},
{'id': _uuid(),
'tenant_id': _uuid(),
'name': 'another-endpoint-group',
'description': 'second endpoint group',
'type': constants.VLAN_ENDPOINT,
'endpoints': ['100', '200', '300']}]
instance = self.plugin.return_value
instance.get_endpoint_groups.return_value = return_value
res = self.api.get(_get_path('vpn/endpoint-groups', fmt=self.fmt))
instance.get_endpoint_groups.assert_called_with(mock.ANY,
fields=mock.ANY,
filters=mock.ANY)
self.assertEqual(res.status_int, exc.HTTPOk.code)
def test_endpoint_group_delete(self):
"""Test deleting an endpoint group."""
self._test_entity_delete('endpoint_group')
def test_endpoint_group_update(self):
"""Test updating endpoint_group."""
endpoint_group_id = _uuid()
update_data = {'endpoint_group': {'description': 'new description'}}
return_value = {'id': endpoint_group_id,
'tenant_id': _uuid(),
'name': 'my-endpoint-group',
'description': 'new_description',
'type': constants.CIDR_ENDPOINT,
'endpoints': ['10.10.10.0/24']}
instance = self.plugin.return_value
instance.update_endpoint_group.return_value = return_value
res = self.api.put(_get_path('vpn/endpoint-groups',
id=endpoint_group_id,
fmt=self.fmt),
self.serialize(update_data))
instance.update_endpoint_group.assert_called_with(
mock.ANY, endpoint_group_id, endpoint_group=update_data)
self.assertEqual(res.status_int, exc.HTTPOk.code)
res = self.deserialize(res)
self.assertIn('endpoint_group', res)
self.assertEqual(res['endpoint_group'], return_value)
def test_fail_updating_endpoints_in_endpoint_group(self):
"""Test fails to update the endpoints in an endpoint group.
This documents that we are not allowing endpoints to be updated
(currently), as doing so, implies that the connection using the
enclosing endpoint group would also need to be updated. For now,
a new endpoint group can be created, and the connection can be
updated to point to the new endpoint group.
"""
endpoint_group_id = _uuid()
update_data = {'endpoint_group': {'endpoints': ['10.10.10.0/24']}}
res = self.api.put(_get_path('vpn/endpoint-groups',
id=endpoint_group_id,
fmt=self.fmt),
params=self.serialize(update_data),
expect_errors=True)
self.assertEqual(exc.HTTPBadRequest.code, res.status_int)