]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
VPN as a Service (VPNaaS) API and DataModel
authorSwaminathan Vasudevan <swaminathan.vasudevan@hp.com>
Mon, 20 May 2013 14:00:21 +0000 (07:00 -0700)
committerNachi Ueno <nachi@ntti3.com>
Tue, 30 Jul 2013 17:38:00 +0000 (10:38 -0700)
implements:blueprint VPNaaS-Python-API

It provides the basic API and
DataModel for the "VPN As A
Service" feature that includes:
* VPNService
* IPsecSiteConnection
* IKEPolicy
* IPsecPolicy

Change-Id: I059d4db05118a4eecd388b8e8db0d714a8f9cb8f

13 files changed:
neutron/api/v2/attributes.py
neutron/db/migration/alembic_migrations/versions/52ff27f7567a_support_for_vpnaas.py [new file with mode: 0644]
neutron/db/vpn/__init__.py [new file with mode: 0644]
neutron/db/vpn/vpn_db.py [new file with mode: 0644]
neutron/extensions/vpnaas.py [new file with mode: 0644]
neutron/plugins/common/constants.py
neutron/services/vpn/__init__.py [new file with mode: 0644]
neutron/services/vpn/plugin.py [new file with mode: 0644]
neutron/tests/unit/db/vpn/__init__.py [new file with mode: 0644]
neutron/tests/unit/db/vpn/test_db_vpnaas.py [new file with mode: 0644]
neutron/tests/unit/services/vpn/__init__.py [new file with mode: 0644]
neutron/tests/unit/services/vpn/test_vpnaas_extension.py [new file with mode: 0644]
neutron/tests/unit/test_db_plugin.py

index 68f2fa5b771aa31048221613d9397b2eb71d8234..d7aea8c2aee3db35554cc95802ca7203dbf1b5bc 100644 (file)
@@ -261,6 +261,23 @@ def _validate_subnet(data, valid_values=None):
     return msg
 
 
+def _validate_subnet_list(data, valid_values=None):
+    if not isinstance(data, list):
+        msg = _("'%s' is not a list") % data
+        LOG.debug(msg)
+        return msg
+
+    if len(set(data)) != len(data):
+        msg = _("Duplicate items in the list: '%s'") % ', '.join(data)
+        LOG.debug(msg)
+        return msg
+
+    for item in data:
+        msg = _validate_subnet(item)
+        if msg:
+            return msg
+
+
 def _validate_regex(data, valid_values=None):
     try:
         if re.match(valid_values, data):
@@ -474,6 +491,7 @@ validators = {'type:dict': _validate_dict,
               'type:regex': _validate_regex,
               'type:string': _validate_string,
               'type:subnet': _validate_subnet,
+              'type:subnet_list': _validate_subnet_list,
               'type:uuid': _validate_uuid,
               'type:uuid_or_none': _validate_uuid_or_none,
               'type:uuid_list': _validate_uuid_list,
diff --git a/neutron/db/migration/alembic_migrations/versions/52ff27f7567a_support_for_vpnaas.py b/neutron/db/migration/alembic_migrations/versions/52ff27f7567a_support_for_vpnaas.py
new file mode 100644 (file)
index 0000000..7ce59e1
--- /dev/null
@@ -0,0 +1,183 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 OpenStack Foundation
+#
+#    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.
+#
+
+"""Support for VPNaaS
+
+Revision ID: 52ff27f7567a
+Revises: 39cf3f799352
+Create Date: 2013-07-14 23:04:13.395955
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '52ff27f7567a'
+down_revision = '39cf3f799352'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+    '*'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+
+
+def upgrade(active_plugin=None, options=None):
+    if not migration.should_run(active_plugin, migration_for_plugins):
+        return
+
+    op.create_table(
+        'ikepolicies',
+        sa.Column('tenant_id', sa.String(length=255), nullable=True),
+        sa.Column('id', sa.String(length=36), nullable=False),
+        sa.Column('name', sa.String(length=255), nullable=True),
+        sa.Column('description', sa.String(length=255), nullable=True),
+        sa.Column(
+            'auth_algorithm',
+            sa.Enum('sha1', name='vpn_auth_algorithms'), nullable=False),
+        sa.Column(
+            'encryption_algorithm',
+            sa.Enum('3des', 'aes-128', 'aes-256', 'aes-192',
+            name='vpn_encrypt_algorithms'), nullable=False),
+        sa.Column(
+            'phase1_negotiation_mode',
+            sa.Enum('main', name='ike_phase1_mode'), nullable=False),
+        sa.Column(
+            'lifetime_units',
+            sa.Enum('seconds', 'kilobytes', name='vpn_lifetime_units'),
+            nullable=False),
+        sa.Column('lifetime_value', sa.Integer(), nullable=False),
+        sa.Column(
+            'ike_version',
+            sa.Enum('v1', 'v2', name='ike_versions'), nullable=False),
+        sa.Column(
+            'pfs',
+            sa.Enum('group2', 'group5', 'group14', name='vpn_pfs'),
+            nullable=False),
+        sa.PrimaryKeyConstraint('id')
+    )
+    op.create_table(
+        'ipsecpolicies',
+        sa.Column('tenant_id', sa.String(length=255), nullable=True),
+        sa.Column('id', sa.String(length=36), nullable=False),
+        sa.Column('name', sa.String(length=255), nullable=True),
+        sa.Column('description', sa.String(length=255), nullable=True),
+        sa.Column(
+            'transform_protocol',
+            sa.Enum('esp', 'ah', 'ah-esp', name='ipsec_transform_protocols'),
+            nullable=False),
+        sa.Column(
+            'auth_algorithm',
+            sa.Enum('sha1', name='vpn_auth_algorithms'), nullable=False),
+        sa.Column(
+            'encryption_algorithm',
+            sa.Enum(
+                '3des', 'aes-128',
+                'aes-256', 'aes-192', name='vpn_encrypt_algorithms'),
+            nullable=False),
+        sa.Column(
+            'encapsulation_mode',
+            sa.Enum('tunnel', 'transport', name='ipsec_encapsulations'),
+            nullable=False),
+        sa.Column(
+            'lifetime_units',
+            sa.Enum(
+                'seconds', 'kilobytes',
+                name='vpn_lifetime_units'), nullable=False),
+        sa.Column(
+            'lifetime_value', sa.Integer(), nullable=False),
+        sa.Column(
+            'pfs',
+            sa.Enum(
+                'group2', 'group5', 'group14', name='vpn_pfs'),
+            nullable=False),
+        sa.PrimaryKeyConstraint('id')
+    )
+    op.create_table(
+        'vpnservices',
+        sa.Column('tenant_id', sa.String(length=255), nullable=True),
+        sa.Column('id', sa.String(length=36), nullable=False),
+        sa.Column('name', sa.String(length=255), nullable=True),
+        sa.Column('description', sa.String(length=255), nullable=True),
+        sa.Column('status', sa.String(length=16), nullable=False),
+        sa.Column('admin_state_up', sa.Boolean(), nullable=False),
+        sa.Column('subnet_id', sa.String(length=36), nullable=False),
+        sa.Column('router_id', sa.String(length=36), nullable=False),
+        sa.ForeignKeyConstraint(['router_id'], ['routers.id'], ),
+        sa.ForeignKeyConstraint(['subnet_id'], ['subnets.id'], ),
+        sa.PrimaryKeyConstraint('id')
+    )
+    op.create_table(
+        'ipsec_site_connections',
+        sa.Column('tenant_id', sa.String(length=255), nullable=True),
+        sa.Column('id', sa.String(length=36), nullable=False),
+        sa.Column('name', sa.String(length=255), nullable=True),
+        sa.Column('description', sa.String(length=255), nullable=True),
+        sa.Column('peer_address', sa.String(length=64), nullable=False),
+        sa.Column('peer_id', sa.String(length=255), nullable=False),
+        sa.Column('route_mode', sa.String(length=8), nullable=False),
+        sa.Column('mtu', sa.Integer(), nullable=False),
+        sa.Column(
+            'initiator',
+            sa.Enum(
+                'bi-directional', 'response-only', name='vpn_initiators'),
+            nullable=False),
+        sa.Column('auth_mode', sa.String(length=16), nullable=False),
+        sa.Column('psk', sa.String(length=255), nullable=False),
+        sa.Column(
+            'dpd_action',
+            sa.Enum(
+                'hold', 'clear', 'restart',
+                'disabled', 'restart-by-peer', name='vpn_dpd_actions'),
+            nullable=False),
+        sa.Column('dpd_interval', sa.Integer(), nullable=False),
+        sa.Column('dpd_timeout', sa.Integer(), nullable=False),
+        sa.Column('status', sa.String(length=16), nullable=False),
+        sa.Column('admin_state_up', sa.Boolean(), nullable=False),
+        sa.Column('vpnservice_id', sa.String(length=36), nullable=False),
+        sa.Column('ipsecpolicy_id', sa.String(length=36), nullable=False),
+        sa.Column('ikepolicy_id', sa.String(length=36), nullable=False),
+        sa.ForeignKeyConstraint(['ikepolicy_id'], ['ikepolicies.id']),
+        sa.ForeignKeyConstraint(['ipsecpolicy_id'], ['ipsecpolicies.id']),
+        sa.ForeignKeyConstraint(['vpnservice_id'], ['vpnservices.id']),
+        sa.PrimaryKeyConstraint('id')
+    )
+    op.create_table(
+        'ipsecpeercidrs',
+        sa.Column('cidr', sa.String(length=32), nullable=False),
+        sa.Column('ipsec_site_connection_id',
+                  sa.String(length=36),
+                  nullable=False),
+        sa.ForeignKeyConstraint(['ipsec_site_connection_id'],
+                                ['ipsecsiteonnections.id'],
+                                ondelete='CASCADE'),
+        sa.PrimaryKeyConstraint('cidr', 'ipsec_site_connection_id')
+    )
+
+
+def downgrade(active_plugin=None, options=None):
+    if not migration.should_run(active_plugin, migration_for_plugins):
+        return
+
+    op.drop_table('ipsecpeercidrs')
+    op.drop_table('ipsec_site_connections')
+    op.drop_table('vpnservices')
+    op.drop_table('ipsecpolicies')
+    op.drop_table('ikepolicies')
diff --git a/neutron/db/vpn/__init__.py b/neutron/db/vpn/__init__.py
new file mode 100644 (file)
index 0000000..7f4f3b9
--- /dev/null
@@ -0,0 +1,18 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+#
+# @author: Swaminathan Vasudevan, Hewlett-Packard.
diff --git a/neutron/db/vpn/vpn_db.py b/neutron/db/vpn/vpn_db.py
new file mode 100644 (file)
index 0000000..87298a4
--- /dev/null
@@ -0,0 +1,591 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+#
+# @author: Swaminathan Vasudevan, Hewlett-Packard.
+
+import sqlalchemy as sa
+from sqlalchemy import orm
+from sqlalchemy.orm import exc
+
+from neutron.common import constants as q_constants
+from neutron.db import agentschedulers_db as agent_db
+from neutron.db import api as qdbapi
+from neutron.db import db_base_plugin_v2 as base_db
+from neutron.db import l3_db
+from neutron.db import model_base
+from neutron.db import models_v2
+from neutron.extensions import vpnaas
+from neutron.extensions.vpnaas import VPNPluginBase
+from neutron import manager
+from neutron.openstack.common import log as logging
+from neutron.openstack.common import uuidutils
+from neutron.plugins.common import constants
+
+LOG = logging.getLogger(__name__)
+
+
+class IPsecPeerCidr(model_base.BASEV2):
+    """Internal representation of a IPsec Peer Cidrs."""
+
+    cidr = sa.Column(sa.String(32), nullable=False, primary_key=True)
+    ipsec_site_connection_id = sa.Column(
+        sa.String(36),
+        sa.ForeignKey('ipsec_site_connections.id',
+                      ondelete="CASCADE"),
+        primary_key=True)
+
+
+class IPsecPolicy(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
+    """Represents a v2 IPsecPolicy Object."""
+    __tablename__ = 'ipsecpolicies'
+    name = sa.Column(sa.String(255))
+    description = sa.Column(sa.String(255))
+    transform_protocol = sa.Column(sa.Enum("esp", "ah", "ah-esp",
+                                           name="ipsec_transform_protocols"),
+                                   nullable=False)
+    auth_algorithm = sa.Column(sa.Enum("sha1",
+                                       name="vpn_auth_algorithms"),
+                               nullable=False)
+    encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128",
+                                             "aes-256", "aes-192",
+                                             name="vpn_encrypt_algorithms"),
+                                     nullable=False)
+    encapsulation_mode = sa.Column(sa.Enum("tunnel", "transport",
+                                           name="ipsec_encapsulations"),
+                                   nullable=False)
+    lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes",
+                                       name="vpn_lifetime_units"),
+                               nullable=False)
+    lifetime_value = sa.Column(sa.Integer, nullable=False)
+    pfs = sa.Column(sa.Enum("group2", "group5", "group14",
+                            name="vpn_pfs"), nullable=False)
+
+
+class IKEPolicy(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
+    """Represents a v2 IKEPolicy Object."""
+    __tablename__ = 'ikepolicies'
+    name = sa.Column(sa.String(255))
+    description = sa.Column(sa.String(255))
+    auth_algorithm = sa.Column(sa.Enum("sha1",
+                                       name="vpn_auth_algorithms"),
+                               nullable=False)
+    encryption_algorithm = sa.Column(sa.Enum("3des", "aes-128",
+                                             "aes-256", "aes-192",
+                                             name="vpn_encrypt_algorithms"),
+                                     nullable=False)
+    phase1_negotiation_mode = sa.Column(sa.Enum("main",
+                                                name="ike_phase1_mode"),
+                                        nullable=False)
+    lifetime_units = sa.Column(sa.Enum("seconds", "kilobytes",
+                                       name="vpn_lifetime_units"),
+                               nullable=False)
+    lifetime_value = sa.Column(sa.Integer, nullable=False)
+    ike_version = sa.Column(sa.Enum("v1", "v2", name="ike_versions"),
+                            nullable=False)
+    pfs = sa.Column(sa.Enum("group2", "group5", "group14",
+                            name="vpn_pfs"), nullable=False)
+
+
+class IPsecSiteConnection(model_base.BASEV2,
+                          models_v2.HasId, models_v2.HasTenant):
+    """Represents a IPsecSiteConnection Object."""
+    __tablename__ = 'ipsec_site_connections'
+    name = sa.Column(sa.String(255))
+    description = sa.Column(sa.String(255))
+    peer_address = sa.Column(sa.String(64), nullable=False)
+    peer_id = sa.Column(sa.String(255), nullable=False)
+    route_mode = sa.Column(sa.String(8), nullable=False)
+    mtu = sa.Column(sa.Integer, nullable=False)
+    initiator = sa.Column(sa.Enum("bi-directional", "response-only",
+                                  name="vpn_initiators"), nullable=False)
+    auth_mode = sa.Column(sa.String(16), nullable=False)
+    psk = sa.Column(sa.String(255), nullable=False)
+    dpd_action = sa.Column(sa.Enum("hold", "clear",
+                                   "restart", "disabled",
+                                   "restart-by-peer", name="vpn_dpd_actions"),
+                           nullable=False)
+    dpd_interval = sa.Column(sa.Integer, nullable=False)
+    dpd_timeout = sa.Column(sa.Integer, nullable=False)
+    status = sa.Column(sa.String(16), nullable=False)
+    admin_state_up = sa.Column(sa.Boolean(), nullable=False)
+    vpnservice_id = sa.Column(sa.String(36),
+                              sa.ForeignKey('vpnservices.id'),
+                              nullable=False)
+    ipsecpolicy_id = sa.Column(sa.String(36),
+                               sa.ForeignKey('ipsecpolicies.id'),
+                               nullable=False)
+    ikepolicy_id = sa.Column(sa.String(36),
+                             sa.ForeignKey('ikepolicies.id'),
+                             nullable=False)
+    ipsecpolicy = orm.relationship(
+        IPsecPolicy, backref='ipsec_site_connection')
+    ikepolicy = orm.relationship(IKEPolicy, backref='ipsec_site_connection')
+    peer_cidrs = orm.relationship(IPsecPeerCidr,
+                                  backref='ipsec_site_connection',
+                                  lazy='joined',
+                                  cascade='all, delete, delete-orphan')
+
+
+class VPNService(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
+    """Represents a v2 VPNService Object."""
+    name = sa.Column(sa.String(255))
+    description = sa.Column(sa.String(255))
+    status = sa.Column(sa.String(16), nullable=False)
+    admin_state_up = sa.Column(sa.Boolean(), nullable=False)
+    subnet_id = sa.Column(sa.String(36), sa.ForeignKey('subnets.id'),
+                          nullable=False)
+    router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'),
+                          nullable=False)
+    subnet = orm.relationship(models_v2.Subnet)
+    router = orm.relationship(l3_db.Router)
+    ipsec_site_connections = orm.relationship(
+        IPsecSiteConnection,
+        backref='vpnservice',
+        cascade="all, delete-orphan")
+
+
+class VPNPluginDb(VPNPluginBase, base_db.CommonDbMixin):
+    """VPN plugin database class using SQLAlchemy models."""
+
+    def __init__(self):
+        """Do the initialization for the vpn service plugin here."""
+        qdbapi.register_models()
+
+    def update_status(self, context, model, v_id, status):
+        with context.session.begin(subtransactions=True):
+            v_db = self._get_resource(context, model, v_id)
+            v_db.update({'status': status})
+
+    def _get_resource(self, context, model, v_id):
+        try:
+            r = self._get_by_id(context, model, v_id)
+        except exc.NoResultFound:
+            if issubclass(model, IPsecSiteConnection):
+                raise vpnaas.IPsecSiteConnectionNotFound(
+                    ipsec_site_conn_id=v_id
+                )
+            elif issubclass(model, IKEPolicy):
+                raise vpnaas.IKEPolicyNotFound(ikepolicy_id=v_id)
+            elif issubclass(model, IPsecPolicy):
+                raise vpnaas.IPsecPolicyNotFound(ipsecpolicy_id=v_id)
+            elif issubclass(model, VPNService):
+                raise vpnaas.VPNServiceNotFound(vpnservice_id=v_id)
+            else:
+                raise
+        return r
+
+    def assert_update_allowed(self, obj):
+        status = getattr(obj, 'status', None)
+        if status != constants.ACTIVE:
+            raise vpnaas.VPNStateInvalid(id=id, state=status)
+
+    def _make_ipsec_site_connection_dict(self, ipsec_site_conn, fields=None):
+
+        res = {'id': ipsec_site_conn['id'],
+               'tenant_id': ipsec_site_conn['tenant_id'],
+               'name': ipsec_site_conn['name'],
+               'description': ipsec_site_conn['description'],
+               'peer_address': ipsec_site_conn['peer_address'],
+               'peer_id': ipsec_site_conn['peer_id'],
+               'route_mode': ipsec_site_conn['route_mode'],
+               'mtu': ipsec_site_conn['mtu'],
+               'auth_mode': ipsec_site_conn['auth_mode'],
+               'psk': ipsec_site_conn['psk'],
+               'initiator': ipsec_site_conn['initiator'],
+               'dpd': {
+                   'action': ipsec_site_conn['dpd_action'],
+                   'interval': ipsec_site_conn['dpd_interval'],
+                   'timeout': ipsec_site_conn['dpd_timeout']
+               },
+               'admin_state_up': ipsec_site_conn['admin_state_up'],
+               'status': ipsec_site_conn['status'],
+               'vpnservice_id': ipsec_site_conn['vpnservice_id'],
+               'ikepolicy_id': ipsec_site_conn['ikepolicy_id'],
+               'ipsecpolicy_id': ipsec_site_conn['ipsecpolicy_id'],
+               'peer_cidrs': [pcidr['cidr']
+                              for pcidr in ipsec_site_conn['peer_cidrs']]
+               }
+
+        return self._fields(res, fields)
+
+    def create_ipsec_site_connection(self, context, ipsec_site_connection):
+        ipsec_sitecon = ipsec_site_connection['ipsec_site_connection']
+        dpd = ipsec_sitecon['dpd']
+        ipsec_sitecon['dpd_action'] = dpd.get('action', 'hold')
+        ipsec_sitecon['dpd_interval'] = dpd.get('interval', 30)
+        ipsec_sitecon['dpd_timeout'] = dpd.get('timeout', 120)
+        tenant_id = self._get_tenant_id_for_create(context, ipsec_sitecon)
+        if ipsec_sitecon['dpd_timeout'] < ipsec_sitecon['dpd_interval']:
+            raise vpnaas.IPsecSiteConnectionDpdIntervalValueError(
+                attribute_a='dpd_timeout')
+        with context.session.begin(subtransactions=True):
+            #Check permissions
+            self._get_resource(context,
+                               VPNService,
+                               ipsec_sitecon['vpnservice_id'])
+            self._get_resource(context,
+                               IKEPolicy,
+                               ipsec_sitecon['ikepolicy_id'])
+            self._get_resource(context,
+                               IPsecPolicy,
+                               ipsec_sitecon['ipsecpolicy_id'])
+            ipsec_site_conn_db = IPsecSiteConnection(
+                id=uuidutils.generate_uuid(),
+                tenant_id=tenant_id,
+                name=ipsec_sitecon['name'],
+                description=ipsec_sitecon['description'],
+                peer_address=ipsec_sitecon['peer_address'],
+                peer_id=ipsec_sitecon['peer_id'],
+                route_mode='static',
+                mtu=ipsec_sitecon['mtu'],
+                auth_mode='psk',
+                psk=ipsec_sitecon['psk'],
+                initiator=ipsec_sitecon['initiator'],
+                dpd_action=ipsec_sitecon['dpd_action'],
+                dpd_interval=ipsec_sitecon['dpd_interval'],
+                dpd_timeout=ipsec_sitecon['dpd_timeout'],
+                admin_state_up=ipsec_sitecon['admin_state_up'],
+                status=constants.PENDING_CREATE,
+                vpnservice_id=ipsec_sitecon['vpnservice_id'],
+                ikepolicy_id=ipsec_sitecon['ikepolicy_id'],
+                ipsecpolicy_id=ipsec_sitecon['ipsecpolicy_id']
+            )
+            context.session.add(ipsec_site_conn_db)
+            for cidr in ipsec_sitecon['peer_cidrs']:
+                peer_cidr_db = IPsecPeerCidr(
+                    cidr=cidr,
+                    ipsec_site_connection_id=ipsec_site_conn_db['id']
+                )
+                context.session.add(peer_cidr_db)
+        return self._make_ipsec_site_connection_dict(ipsec_site_conn_db)
+
+    def update_ipsec_site_connection(
+            self, context,
+            ipsec_site_conn_id, ipsec_site_connection):
+        ipsec_sitecon = ipsec_site_connection['ipsec_site_connection']
+        dpd = ipsec_sitecon.get('dpd', {})
+        if dpd.get('action'):
+            ipsec_sitecon['dpd_action'] = dpd.get('action')
+        if dpd.get('interval'):
+            ipsec_sitecon['dpd_interval'] = dpd.get('interval')
+        if dpd.get('timeout'):
+            ipsec_sitecon['dpd_timeout'] = dpd.get('timeout')
+        changed_peer_cidrs = False
+        with context.session.begin(subtransactions=True):
+            ipsec_site_conn_db = self._get_resource(
+                context,
+                IPsecSiteConnection,
+                ipsec_site_conn_id)
+            self.assert_update_allowed(ipsec_site_conn_db)
+            if "peer_cidrs" in ipsec_sitecon:
+                changed_peer_cidrs = True
+                old_peer_cidr_list = ipsec_site_conn_db['peer_cidrs']
+                old_peer_cidr_dict = dict(
+                    (peer_cidr['cidr'], peer_cidr)
+                    for peer_cidr in old_peer_cidr_list)
+                new_peer_cidr_set = set(ipsec_sitecon["peer_cidrs"])
+                old_peer_cidr_set = set(old_peer_cidr_dict)
+
+                new_peer_cidrs = list(new_peer_cidr_set)
+                for peer_cidr in old_peer_cidr_set - new_peer_cidr_set:
+                    context.session.delete(old_peer_cidr_dict[peer_cidr])
+                for peer_cidr in new_peer_cidr_set - old_peer_cidr_set:
+                    pcidr = IPsecPeerCidr(
+                        cidr=peer_cidr,
+                        ipsec_site_connection_id=ipsec_site_conn_id)
+                    context.session.add(pcidr)
+                del ipsec_sitecon["peer_cidrs"]
+            if ipsec_sitecon:
+                ipsec_site_conn_db.update(ipsec_sitecon)
+        result = self._make_ipsec_site_connection_dict(ipsec_site_conn_db)
+        if changed_peer_cidrs:
+            result['peer_cidrs'] = new_peer_cidrs
+        return result
+
+    def delete_ipsec_site_connection(self, context, ipsec_site_conn_id):
+        with context.session.begin(subtransactions=True):
+            ipsec_site_conn_db = self._get_resource(
+                context, IPsecSiteConnection, ipsec_site_conn_id
+            )
+            context.session.delete(ipsec_site_conn_db)
+
+    def get_ipsec_site_connection(self, context,
+                                  ipsec_site_conn_id, fields=None):
+        ipsec_site_conn_db = self._get_resource(
+            context, IPsecSiteConnection, ipsec_site_conn_id
+        )
+        return self._make_ipsec_site_connection_dict(
+            ipsec_site_conn_db, fields)
+
+    def get_ipsec_site_connections(self, context, filters=None, fields=None):
+        return self._get_collection(context, IPsecSiteConnection,
+                                    self._make_ipsec_site_connection_dict,
+                                    filters=filters, fields=fields)
+
+    def _make_ikepolicy_dict(self, ikepolicy, fields=None):
+        res = {'id': ikepolicy['id'],
+               'tenant_id': ikepolicy['tenant_id'],
+               'name': ikepolicy['name'],
+               'description': ikepolicy['description'],
+               'auth_algorithm': ikepolicy['auth_algorithm'],
+               'encryption_algorithm': ikepolicy['encryption_algorithm'],
+               'phase1_negotiation_mode': ikepolicy['phase1_negotiation_mode'],
+               'lifetime': {
+                   'units': ikepolicy['lifetime_units'],
+                   'value': ikepolicy['lifetime_value'],
+               },
+               'ike_version': ikepolicy['ike_version'],
+               'pfs': ikepolicy['pfs']
+               }
+
+        return self._fields(res, fields)
+
+    def create_ikepolicy(self, context, ikepolicy):
+        ike = ikepolicy['ikepolicy']
+        tenant_id = self._get_tenant_id_for_create(context, ike)
+        lifetime_info = ike.get('lifetime', [])
+        lifetime_units = lifetime_info.get('unit', 'seconds')
+        lifetime_value = lifetime_info.get('value', 3600)
+
+        with context.session.begin(subtransactions=True):
+            ike_db = IKEPolicy(
+                id=uuidutils.generate_uuid(),
+                tenant_id=tenant_id,
+                name=ike['name'],
+                description=ike['description'],
+                auth_algorithm=ike['auth_algorithm'],
+                encryption_algorithm=ike['encryption_algorithm'],
+                phase1_negotiation_mode=ike['phase1_negotiation_mode'],
+                lifetime_units=lifetime_units,
+                lifetime_value=lifetime_value,
+                ike_version=ike['ike_version'],
+                pfs=ike['pfs']
+            )
+
+            context.session.add(ike_db)
+        return self._make_ikepolicy_dict(ike_db)
+
+    def update_ikepolicy(self, context, ikepolicy_id, ikepolicy):
+        ike = ikepolicy['ikepolicy']
+        with context.session.begin(subtransactions=True):
+            ikepolicy = context.session.query(IPsecSiteConnection).filter_by(
+                ikepolicy_id=ikepolicy_id).first()
+            if ikepolicy:
+                raise vpnaas.IKEPolicyInUse(ikepolicy_id=ikepolicy_id)
+            ike_db = self._get_resource(context, IKEPolicy, ikepolicy_id)
+            if ike:
+                lifetime_info = ike.get('lifetime')
+                if lifetime_info:
+                    if lifetime_info.get('units'):
+                        ike['lifetime_units'] = lifetime_info['units']
+                    if lifetime_info.get('value'):
+                        ike['lifetime_value'] = lifetime_info['value']
+                ike_db.update(ike)
+        return self._make_ikepolicy_dict(ike_db)
+
+    def delete_ikepolicy(self, context, ikepolicy_id):
+        with context.session.begin(subtransactions=True):
+            ikepolicy = context.session.query(IPsecSiteConnection).filter_by(
+                ikepolicy_id=ikepolicy_id).first()
+            if ikepolicy:
+                raise vpnaas.IKEPolicyInUse(ikepolicy_id=ikepolicy_id)
+            ike_db = self._get_resource(context, IKEPolicy, ikepolicy_id)
+            context.session.delete(ike_db)
+
+    def get_ikepolicy(self, context, ikepolicy_id, fields=None):
+        ike_db = self._get_resource(context, IKEPolicy, ikepolicy_id)
+        return self._make_ikepolicy_dict(ike_db, fields)
+
+    def get_ikepolicies(self, context, filters=None, fields=None):
+        return self._get_collection(context, IKEPolicy,
+                                    self._make_ikepolicy_dict,
+                                    filters=filters, fields=fields)
+
+    def _make_ipsecpolicy_dict(self, ipsecpolicy, fields=None):
+
+        res = {'id': ipsecpolicy['id'],
+               'tenant_id': ipsecpolicy['tenant_id'],
+               'name': ipsecpolicy['name'],
+               'description': ipsecpolicy['description'],
+               'transform_protocol': ipsecpolicy['transform_protocol'],
+               'auth_algorithm': ipsecpolicy['auth_algorithm'],
+               'encryption_algorithm': ipsecpolicy['encryption_algorithm'],
+               'encapsulation_mode': ipsecpolicy['encapsulation_mode'],
+               'lifetime': {
+                   'units': ipsecpolicy['lifetime_units'],
+                   'value': ipsecpolicy['lifetime_value'],
+               },
+               'pfs': ipsecpolicy['pfs']
+               }
+
+        return self._fields(res, fields)
+
+    def create_ipsecpolicy(self, context, ipsecpolicy):
+        ipsecp = ipsecpolicy['ipsecpolicy']
+        tenant_id = self._get_tenant_id_for_create(context, ipsecp)
+        lifetime_info = ipsecp['lifetime']
+        lifetime_units = lifetime_info.get('units', 'seconds')
+        lifetime_value = lifetime_info.get('value', 3600)
+
+        with context.session.begin(subtransactions=True):
+            ipsecp_db = IPsecPolicy(id=uuidutils.generate_uuid(),
+                                    tenant_id=tenant_id,
+                                    name=ipsecp['name'],
+                                    description=ipsecp['description'],
+                                    transform_protocol=ipsecp['transform_'
+                                                              'protocol'],
+                                    auth_algorithm=ipsecp['auth_algorithm'],
+                                    encryption_algorithm=ipsecp['encryption_'
+                                                                'algorithm'],
+                                    encapsulation_mode=ipsecp['encapsulation_'
+                                                              'mode'],
+                                    lifetime_units=lifetime_units,
+                                    lifetime_value=lifetime_value,
+                                    pfs=ipsecp['pfs'])
+            context.session.add(ipsecp_db)
+        return self._make_ipsecpolicy_dict(ipsecp_db)
+
+    def update_ipsecpolicy(self, context, ipsecpolicy_id, ipsecpolicy):
+        ipsecp = ipsecpolicy['ipsecpolicy']
+        with context.session.begin(subtransactions=True):
+            ipsecpolicy = context.session.query(IPsecSiteConnection).filter_by(
+                ipsecpolicy_id=ipsecpolicy_id).first()
+            if ipsecpolicy:
+                raise vpnaas.IPsecPolicyInUse(ipsecpolicy_id=ipsecpolicy_id)
+            ipsecp_db = self._get_resource(context,
+                                           IPsecPolicy,
+                                           ipsecpolicy_id)
+            if ipsecp:
+                lifetime_info = ipsecp.get('lifetime')
+                if lifetime_info:
+                    if lifetime_info.get('units'):
+                        ipsecp['lifetime_units'] = lifetime_info['units']
+                    if lifetime_info('value'):
+                        ipsecp['lifetime_value'] = lifetime_info['value']
+                ipsecp_db.update(ipsecp)
+        return self._make_ipsecpolicy_dict(ipsecp_db)
+
+    def delete_ipsecpolicy(self, context, ipsecpolicy_id):
+        with context.session.begin(subtransactions=True):
+            ipsecpolicy = context.session.query(IPsecSiteConnection).filter_by(
+                ipsecpolicy_id=ipsecpolicy_id).first()
+            if ipsecpolicy:
+                raise vpnaas.IPsecPolicyInUse(ipsecpolicy_id=ipsecpolicy_id)
+            ipsec_db = self._get_resource(context, IPsecPolicy, ipsecpolicy_id)
+            context.session.delete(ipsec_db)
+
+    def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None):
+        ipsec_db = self._get_resource(context, IPsecPolicy, ipsecpolicy_id)
+        return self._make_ipsecpolicy_dict(ipsec_db, fields)
+
+    def get_ipsecpolicies(self, context, filters=None, fields=None):
+        return self._get_collection(context, IPsecPolicy,
+                                    self._make_ipsecpolicy_dict,
+                                    filters=filters, fields=fields)
+
+    def _make_vpnservice_dict(self, vpnservice, fields=None):
+        res = {'id': vpnservice['id'],
+               'name': vpnservice['name'],
+               'description': vpnservice['description'],
+               'tenant_id': vpnservice['tenant_id'],
+               'subnet_id': vpnservice['subnet_id'],
+               'router_id': vpnservice['router_id'],
+               'admin_state_up': vpnservice['admin_state_up'],
+               'status': vpnservice['status']}
+        return self._fields(res, fields)
+
+    def create_vpnservice(self, context, vpnservice):
+        vpns = vpnservice['vpnservice']
+        tenant_id = self._get_tenant_id_for_create(context, vpns)
+        with context.session.begin(subtransactions=True):
+            vpnservice_db = VPNService(id=uuidutils.generate_uuid(),
+                                       tenant_id=tenant_id,
+                                       name=vpns['name'],
+                                       description=vpns['description'],
+                                       subnet_id=vpns['subnet_id'],
+                                       router_id=vpns['router_id'],
+                                       admin_state_up=vpns['admin_state_up'],
+                                       status=constants.PENDING_CREATE)
+            context.session.add(vpnservice_db)
+        return self._make_vpnservice_dict(vpnservice_db)
+
+    def update_vpnservice(self, context, vpnservice_id, vpnservice):
+        vpns = vpnservice['vpnservice']
+        with context.session.begin(subtransactions=True):
+            vpnservice = context.session.query(IPsecSiteConnection).filter_by(
+                vpnservice_id=vpnservice_id).first()
+            if vpnservice:
+                raise vpnaas.VPNServiceInUse(vpnservice_id=vpnservice_id)
+            vpns_db = self._get_resource(context, VPNService, vpnservice_id)
+            self.assert_update_allowed(vpns_db)
+            if vpns:
+                vpns_db.update(vpns)
+        return self._make_vpnservice_dict(vpns_db)
+
+    def delete_vpnservice(self, context, vpnservice_id):
+        with context.session.begin(subtransactions=True):
+            if context.session.query(IPsecSiteConnection).filter_by(
+                vpnservice_id=vpnservice_id
+            ).first():
+                raise vpnaas.VPNServiceInUse(vpnservice_id=vpnservice_id)
+            vpns_db = self._get_resource(context, VPNService, vpnservice_id)
+            context.session.delete(vpns_db)
+
+    def _get_vpnservice(self, context, vpnservice_id):
+        return self._get_resource(context, VPNService, vpnservice_id)
+
+    def get_vpnservice(self, context, vpnservice_id, fields=None):
+        vpns_db = self._get_resource(context, VPNService, vpnservice_id)
+        return self._make_vpnservice_dict(vpns_db, fields)
+
+    def get_vpnservices(self, context, filters=None, fields=None):
+        return self._get_collection(context, VPNService,
+                                    self._make_vpnservice_dict,
+                                    filters=filters, fields=fields)
+
+
+class VPNPluginRpcDbMixin():
+    def _get_agent_hosting_vpn_services(self, context, host):
+
+        plugin = manager.NeutronManager.get_plugin()
+        agent = plugin._get_agent_by_type_and_host(
+            context, q_constants.AGENT_TYPE_L3, host)
+        if not agent.admin_state_up:
+            return []
+        query = context.session.query(VPNService)
+        query = query.join(IPsecSiteConnection)
+        query = query.join(IKEPolicy)
+        query = query.join(IPsecPolicy)
+        query = query.join(IPsecPeerCidr)
+        query = query.join(agent_db.RouterL3AgentBinding,
+                           agent_db.RouterL3AgentBinding.router_id ==
+                           VPNService.router_id)
+        query = query.filter(
+            agent_db.RouterL3AgentBinding.l3_agent_id == agent.id)
+        return query
+
+    def update_status_on_host(self, context, host, active_services):
+        with context.session.begin(subtransactions=True):
+            vpnservices = self._get_agent_hosting_vpn_services(
+                context, host)
+            for vpnservice in vpnservices:
+                if vpnservice.id in active_services:
+                    if vpnservice.status != constants.ACTIVE:
+                        vpnservice.status = constants.ACTIVE
+                else:
+                    if vpnservice.status != constants.ERROR:
+                        vpnservice.status = constants.ERROR
diff --git a/neutron/extensions/vpnaas.py b/neutron/extensions/vpnaas.py
new file mode 100644 (file)
index 0000000..cb120fc
--- /dev/null
@@ -0,0 +1,477 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+#
+# @author: Swaminathan Vasudevan, Hewlett-Packard.
+
+import abc
+
+from oslo.config import cfg
+
+from neutron.api import extensions
+from neutron.api.v2 import attributes as attr
+from neutron.api.v2 import base
+from neutron.common import exceptions as qexception
+from neutron import manager
+from neutron.plugins.common import constants
+from neutron import quota
+from neutron.services.service_base import ServicePluginBase
+
+
+class VPNServiceNotFound(qexception.NotFound):
+    message = _("VPNService %(vpnservice_id)s could not be found")
+
+
+class IPsecSiteConnectionNotFound(qexception.NotFound):
+    message = _("ipsec_site_connection %(ipsecsite_conn_id)s not found")
+
+
+class IPsecSiteConnectionDpdIntervalValueError(qexception.InvalidInput):
+    message = _("ipsec_site_connection %(attribute_a)s less than dpd_interval")
+
+
+class IKEPolicyNotFound(qexception.NotFound):
+    message = _("IKEPolicy %(ikepolicy_id)s could not be found")
+
+
+class IPsecPolicyNotFound(qexception.NotFound):
+    message = _("IPsecPolicy %(ipsecpolicy_id)s could not be found")
+
+
+class IKEPolicyInUse(qexception.InUse):
+    message = _("IKEPolicy %(ikepolicy_id)s is still in use")
+
+
+class VPNServiceInUse(qexception.InUse):
+    message = _("VPNService %(vpnservice_id)s is still in use")
+
+
+class VPNStateInvalid(qexception.BadRequest):
+    message = _("Invalid state %(state)s of vpnaas resource %(id)s")
+
+
+class IPsecPolicyInUse(qexception.InUse):
+    message = _("IPsecPolicy %(ipsecpolicy_id)s is still in use")
+
+
+vpn_supported_initiators = ['bi-directional', 'response-only']
+vpn_supported_encryption_algorithms = ['3des', 'aes-128',
+                                       'aes-192', 'aes-256']
+vpn_dpd_supported_actions = [
+    'hold', 'clear', 'restart', 'restart-by-peer', 'disabled'
+]
+vpn_supported_transform_protocols = ['esp', 'ah', 'ah-esp']
+vpn_supported_encapsulation_mode = ['tunnel', 'transport']
+vpn_supported_lifetime_units = ['seconds', 'kilobytes']
+vpn_supported_pfs = ['group2', 'group5', 'group14']
+vpn_supported_ike_versions = ['v1', 'v2']
+vpn_supported_auth_mode = ['psk']
+vpn_supported_auth_algorithms = ['sha1']
+vpn_supported_phase1_negotiation_mode = ['main']
+
+
+RESOURCE_ATTRIBUTE_MAP = {
+
+    'vpnservices': {
+        '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': None},
+                      'required_by_policy': True,
+                      'is_visible': True},
+        'name': {'allow_post': True, 'allow_put': True,
+                 'validate': {'type:string': None},
+                 'is_visible': True, 'default': ''},
+        'description': {'allow_post': True, 'allow_put': True,
+                        'validate': {'type:string': None},
+                        'is_visible': True, 'default': ''},
+        'subnet_id': {'allow_post': True, 'allow_put': False,
+                      'validate': {'type:uuid': None},
+                      'is_visible': True},
+        'router_id': {'allow_post': True, 'allow_put': False,
+                      'validate': {'type:uuid': None},
+                      'is_visible': True},
+        'admin_state_up': {'allow_post': True, 'allow_put': True,
+                           'default': True,
+                           'convert_to': attr.convert_to_boolean,
+                           'is_visible': True},
+        'status': {'allow_post': False, 'allow_put': False,
+                   'is_visible': True}
+    },
+
+    'ipsec_site_connections': {
+        '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': None},
+                      'required_by_policy': True,
+                      'is_visible': True},
+        'name': {'allow_post': True, 'allow_put': True,
+                 'validate': {'type:string': None},
+                 'is_visible': True, 'default': ''},
+        'description': {'allow_post': True, 'allow_put': True,
+                        'validate': {'type:string': None},
+                        'is_visible': True, 'default': ''},
+        'peer_address': {'allow_post': True, 'allow_put': True,
+                         'validate': {'type:string': None},
+                         'is_visible': True},
+        'peer_id': {'allow_post': True, 'allow_put': True,
+                    'validate': {'type:string': None},
+                    'is_visible': True},
+        'peer_cidrs': {'allow_post': True, 'allow_put': True,
+                       'convert_to': attr.convert_to_list,
+                       'validate': {'type:subnet_list': None},
+                       'is_visible': True},
+        'route_mode': {'allow_post': False, 'allow_put': False,
+                       'default': 'static',
+                       'is_visible': True},
+        'mtu': {'allow_post': True, 'allow_put': True,
+                'default': '1500',
+                'validate': {'type:non_negative': None},
+                'convert_to': attr.convert_to_int,
+                'is_visible': True},
+        'initiator': {'allow_post': True, 'allow_put': True,
+                      'default': 'bi-directional',
+                      'validate': {'type:values': vpn_supported_initiators},
+                      'is_visible': True},
+        'auth_mode': {'allow_post': False, 'allow_put': False,
+                      'default': 'psk',
+                      'validate': {'type:values': vpn_supported_auth_mode},
+                      'is_visible': True},
+        'psk': {'allow_post': True, 'allow_put': True,
+                'validate': {'type:string': None},
+                'is_visible': True},
+        'dpd': {'allow_post': True, 'allow_put': True,
+                'convert_to': attr.convert_none_to_empty_dict,
+                'is_visible': True,
+                'default': {},
+                'validate': {
+                    'type:dict_or_empty': {
+                        'actions': {
+                            'type:values': vpn_dpd_supported_actions,
+                        },
+                        'interval': {
+                            'type:non_negative': None
+                        },
+                        'timeout': {
+                            'type:non_negative': None
+                        }}}},
+        'admin_state_up': {'allow_post': True, 'allow_put': True,
+                           'default': True,
+                           'convert_to': attr.convert_to_boolean,
+                           'is_visible': True},
+        'status': {'allow_post': False, 'allow_put': False,
+                   'is_visible': True},
+        'vpnservice_id': {'allow_post': True, 'allow_put': False,
+                          'validate': {'type:uuid': None},
+                          'is_visible': True},
+        'ikepolicy_id': {'allow_post': True, 'allow_put': False,
+                         'validate': {'type:uuid': None},
+                         'is_visible': True},
+        'ipsecpolicy_id': {'allow_post': True, 'allow_put': False,
+                           'validate': {'type:uuid': None},
+                           'is_visible': True}
+    },
+
+    'ipsecpolicies': {
+        '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': None},
+                      'required_by_policy': True,
+                      'is_visible': True},
+        'name': {'allow_post': True, 'allow_put': True,
+                 'validate': {'type:string': None},
+                 'is_visible': True, 'default': ''},
+        'description': {'allow_post': True, 'allow_put': True,
+                        'validate': {'type:string': None},
+                        'is_visible': True, 'default': ''},
+        'transform_protocol': {
+            'allow_post': True,
+            'allow_put': True,
+            'default': 'esp',
+            'validate': {
+                'type:values': vpn_supported_transform_protocols},
+            'is_visible': True},
+        'auth_algorithm': {
+            'allow_post': True,
+            'allow_put': True,
+            'default': 'sha1',
+            'validate': {
+                'type:values': vpn_supported_auth_algorithms
+            },
+            'is_visible': True},
+        'encryption_algorithm': {
+            'allow_post': True,
+            'allow_put': True,
+            'default': 'aes-128',
+            'validate': {
+                'type:values': vpn_supported_encryption_algorithms
+            },
+            'is_visible': True},
+        'encapsulation_mode': {
+            'allow_post': True,
+            'allow_put': True,
+            'default': 'tunnel',
+            'validate': {
+                'type:values': vpn_supported_encapsulation_mode
+            },
+            'is_visible': True},
+        'lifetime': {'allow_post': True, 'allow_put': True,
+                     'convert_to': attr.convert_none_to_empty_dict,
+                     'default': {},
+                     'validate': {
+                         'type:dict_or_empty': {
+                             'units': {
+                                 'type:values': vpn_supported_lifetime_units,
+                             },
+                             'value': {
+                                 'type:non_negative': None}}},
+                     'is_visible': True},
+        'pfs': {'allow_post': True, 'allow_put': True,
+                'default': 'group5',
+                'validate': {'type:values': vpn_supported_pfs},
+                'is_visible': True}
+    },
+
+    'ikepolicies': {
+        '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': None},
+                      'required_by_policy': True,
+                      'is_visible': True},
+        'name': {'allow_post': True, 'allow_put': True,
+                 'validate': {'type:string': None},
+                 'is_visible': True, 'default': ''},
+        'description': {'allow_post': True, 'allow_put': True,
+                        'validate': {'type:string': None},
+                        'is_visible': True, 'default': ''},
+        'auth_algorithm': {'allow_post': True, 'allow_put': True,
+                           'default': 'sha1',
+                           'validate': {
+                               'type:values': vpn_supported_auth_algorithms},
+                           'is_visible': True},
+        'encryption_algorithm': {
+            'allow_post': True, 'allow_put': True,
+            'default': 'aes-128',
+            'validate': {'type:values': vpn_supported_encryption_algorithms},
+            'is_visible': True},
+        'phase1_negotiation_mode': {
+            'allow_post': True, 'allow_put': True,
+            'default': 'main',
+            'validate': {
+                'type:values': vpn_supported_phase1_negotiation_mode
+            },
+            'is_visible': True},
+        'lifetime': {'allow_post': True, 'allow_put': True,
+                     'convert_to': attr.convert_none_to_empty_dict,
+                     'default': {},
+                     'validate': {
+                         'type:dict_or_empty': {
+                             'units': {
+                                 'type:values': vpn_supported_lifetime_units,
+                             },
+                             'value': {
+                                 'type:non_negative': None,
+                             }}},
+                     'is_visible': True},
+        'ike_version': {'allow_post': True, 'allow_put': True,
+                        'default': 'v1',
+                        'validate': {
+                            'type:values': vpn_supported_ike_versions},
+                        'is_visible': True},
+        'pfs': {'allow_post': True, 'allow_put': True,
+                'default': 'group5',
+                'validate': {'type:values': vpn_supported_pfs},
+                'is_visible': True}
+    }
+}
+
+
+class Vpnaas(extensions.ExtensionDescriptor):
+
+    @classmethod
+    def get_name(cls):
+        return "VPN service"
+
+    @classmethod
+    def get_alias(cls):
+        return "vpnaas"
+
+    @classmethod
+    def get_description(cls):
+        return "Extension for VPN service"
+
+    @classmethod
+    def get_namespace(cls):
+        return "https://wiki.openstack.org/Neutron/VPNaaS"
+
+    @classmethod
+    def get_updated(cls):
+        return "2013-05-29T10:00:00-00:00"
+
+    @classmethod
+    def get_resources(cls):
+        plural_mapping = {
+            'ikepolicies': 'ikepolicy',
+            'ipsecpolicies': 'ipsecpolicy'
+        }
+        my_plurals = []
+        for plural in RESOURCE_ATTRIBUTE_MAP:
+            singular = plural_mapping.get(plural, plural[:-1])
+            my_plurals.append((plural, singular))
+        my_plurals.append(('peer_cidrs', 'peer_cidr'))
+        attr.PLURALS.update(dict(my_plurals))
+        resources = []
+        plugin = manager.NeutronManager.get_service_plugins()[
+            constants.VPN]
+        for collection_name in RESOURCE_ATTRIBUTE_MAP:
+            resource_name = plural_mapping.get(
+                collection_name, collection_name[:-1])
+            params = RESOURCE_ATTRIBUTE_MAP[collection_name]
+            collection_name = collection_name.replace('_', '-')
+
+            quota.QUOTAS.register_resource_by_name(resource_name)
+            controller = base.create_resource(
+                collection_name, resource_name, plugin, params,
+                allow_pagination=cfg.CONF.allow_pagination,
+                allow_sorting=cfg.CONF.allow_sorting)
+
+            resource = extensions.ResourceExtension(
+                collection_name,
+                controller,
+                path_prefix=constants.COMMON_PREFIXES[constants.VPN],
+                attr_map=params)
+            resources.append(resource)
+        return resources
+
+    @classmethod
+    def get_plugin_interface(cls):
+        return VPNPluginBase
+
+    def update_attributes_map(self, attributes):
+        super(Vpnaas, 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 {}
+
+
+class VPNPluginBase(ServicePluginBase):
+    __metaclass__ = abc.ABCMeta
+
+    def get_plugin_name(self):
+        return constants.VPN
+
+    def get_plugin_type(self):
+        return constants.VPN
+
+    def get_plugin_description(self):
+        return 'VPN service plugin'
+
+    @abc.abstractmethod
+    def get_vpnservices(self, context, filters=None, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def get_vpnservice(self, context, vpnservice_id, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def create_vpnservice(self, context, vpnservice):
+        pass
+
+    @abc.abstractmethod
+    def update_vpnservice(self, context, vpnservice_id, vpnservice):
+        pass
+
+    @abc.abstractmethod
+    def delete_vpnservice(self, context, vpnservice_id):
+        pass
+
+    @abc.abstractmethod
+    def get_ipsec_site_connections(self, context, filters=None, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def get_ipsec_site_connection(self, context,
+                                  ipsecsite_conn_id, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def create_ipsec_site_connection(self, context, ipsec_site_connection):
+        pass
+
+    @abc.abstractmethod
+    def update_ipsec_site_connection(self, context,
+                                     ipsecsite_conn_id, ipsec_site_connection):
+        pass
+
+    @abc.abstractmethod
+    def delete_ipsec_site_connection(self, context, ipsecsite_conn_id):
+        pass
+
+    @abc.abstractmethod
+    def get_ikepolicy(self, context, ikepolicy_id, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def get_ikepolicies(self, context, filters=None, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def create_ikepolicy(self, context, ikepolicy):
+        pass
+
+    @abc.abstractmethod
+    def update_ikepolicy(self, context, ikepolicy_id, ikepolicy):
+        pass
+
+    @abc.abstractmethod
+    def delete_ikepolicy(self, context, ikepolicy_id):
+        pass
+
+    @abc.abstractmethod
+    def get_ipsecpolicies(self, context, filters=None, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None):
+        pass
+
+    @abc.abstractmethod
+    def create_ipsecpolicy(self, context, ipsecpolicy):
+        pass
+
+    @abc.abstractmethod
+    def update_ipsecpolicy(self, context, ipsecpolicy_id, ipsecpolicy):
+        pass
+
+    @abc.abstractmethod
+    def delete_ipsecpolicy(self, context, ipsecpolicy_id):
+        pass
index 2e47fb0d0c8cadc0e167ab73f56a3361c2ad1e37..794e9a7baff952bc6c3caf54622466869b0ffc57 100644 (file)
@@ -20,22 +20,25 @@ CORE = "CORE"
 DUMMY = "DUMMY"
 LOADBALANCER = "LOADBALANCER"
 FIREWALL = "FIREWALL"
+VPN = "VPN"
 
 #maps extension alias to service type
 EXT_TO_SERVICE_MAPPING = {
     'dummy': DUMMY,
     'lbaas': LOADBALANCER,
-    'fwaas': FIREWALL
+    'fwaas': FIREWALL,
+    'vpnaas': VPN,
 }
 
 # TODO(salvatore-orlando): Move these (or derive them) from conf file
-ALLOWED_SERVICES = [CORE, DUMMY, LOADBALANCER, FIREWALL]
+ALLOWED_SERVICES = [CORE, DUMMY, LOADBALANCER, FIREWALL, VPN]
 
 COMMON_PREFIXES = {
     CORE: "",
     DUMMY: "/dummy_svc",
     LOADBALANCER: "/lb",
     FIREWALL: "/fw",
+    VPN: "/vpn",
 }
 
 # Service operation status constants
diff --git a/neutron/services/vpn/__init__.py b/neutron/services/vpn/__init__.py
new file mode 100644 (file)
index 0000000..29c415d
--- /dev/null
@@ -0,0 +1,18 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+#
+# @author: Swaminathan Vasudevan, Hewlett-Packard
diff --git a/neutron/services/vpn/plugin.py b/neutron/services/vpn/plugin.py
new file mode 100644 (file)
index 0000000..6ba7c09
--- /dev/null
@@ -0,0 +1,32 @@
+
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+#
+# @author: Swaminathan Vasudevan, Hewlett-Packard
+
+from neutron.db.vpn import vpn_db
+
+
+class VPNPlugin(vpn_db.VPNPluginDb):
+
+    """Implementation of the VPN Service Plugin.
+
+    This class manages the workflow of VPNaaS request/response.
+    Most DB related works are implemented in class
+    vpn_db.VPNPluginDb.
+    """
+    supported_extension_aliases = ["vpnaas"]
diff --git a/neutron/tests/unit/db/vpn/__init__.py b/neutron/tests/unit/db/vpn/__init__.py
new file mode 100644 (file)
index 0000000..b936bbc
--- /dev/null
@@ -0,0 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+# @author: Swaminathan Vasudevan, Hewlett-Packard.
diff --git a/neutron/tests/unit/db/vpn/test_db_vpnaas.py b/neutron/tests/unit/db/vpn/test_db_vpnaas.py
new file mode 100644 (file)
index 0000000..c17ea08
--- /dev/null
@@ -0,0 +1,1541 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+#
+# @author: Swaminathan Vasudevan, Hewlett-Packard.
+
+import contextlib
+import os
+
+import webob.exc
+
+from neutron.api.extensions import ExtensionMiddleware
+from neutron.api.extensions import PluginAwareExtensionManager
+from neutron.common import config
+from neutron import context
+from neutron.db import agentschedulers_db
+from neutron.db.vpn import vpn_db
+from neutron import extensions
+from neutron.extensions import vpnaas
+from neutron import manager
+from neutron.plugins.common import constants
+from neutron.scheduler import l3_agent_scheduler
+from neutron.services.vpn import plugin as vpn_plugin
+from neutron.tests.unit import test_db_plugin
+from neutron.tests.unit import test_l3_plugin
+
+DB_CORE_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
+DB_VPN_PLUGIN_KLASS = "neutron.services.vpn.plugin.VPNPlugin"
+ROOTDIR = os.path.normpath(os.path.join(
+    os.path.dirname(__file__),
+    '..', '..', '..', '..'))
+
+extensions_path = ':'.join(extensions.__path__)
+
+
+class TestVpnCorePlugin(test_l3_plugin.TestL3NatPlugin,
+                        agentschedulers_db.L3AgentSchedulerDbMixin,
+                        agentschedulers_db.DhcpAgentSchedulerDbMixin):
+    def __init__(self, configfile=None):
+        super(TestVpnCorePlugin, self).__init__()
+        self.router_scheduler = l3_agent_scheduler.ChanceScheduler()
+
+
+class VPNPluginDbTestCase(test_l3_plugin.L3NatTestCaseMixin,
+                          test_db_plugin.NeutronDbPluginV2TestCase):
+    resource_prefix_map = dict(
+        (k.replace('_', '-'),
+         constants.COMMON_PREFIXES[constants.VPN])
+        for k in vpnaas.RESOURCE_ATTRIBUTE_MAP
+    )
+
+    def setUp(self, core_plugin=None, vpnaas_plugin=DB_VPN_PLUGIN_KLASS):
+        service_plugins = {'vpnaas_plugin': vpnaas_plugin}
+        plugin_str = ('neutron.tests.unit.db.vpn.'
+                      'test_db_vpnaas.TestVpnCorePlugin')
+
+        super(VPNPluginDbTestCase, self).setUp(
+            plugin_str,
+            service_plugins=service_plugins
+        )
+        self._subnet_id = "0c798ed8-33ba-11e2-8b28-000c291c4d14"
+        self.core_plugin = TestVpnCorePlugin
+        self.plugin = vpn_plugin.VPNPlugin()
+        ext_mgr = PluginAwareExtensionManager(
+            extensions_path,
+            {constants.CORE: self.core_plugin,
+             constants.VPN: self.plugin}
+        )
+        app = config.load_paste_app('extensions_test_app')
+        self.ext_api = ExtensionMiddleware(app, ext_mgr=ext_mgr)
+
+    def _create_ikepolicy(self, fmt,
+                          name='ikepolicy1',
+                          auth_algorithm='sha1',
+                          encryption_algorithm='aes-128',
+                          phase1_negotiation_mode='main',
+                          lifetime_units='seconds',
+                          lifetime_value=3600,
+                          ike_version='v1',
+                          pfs='group5',
+                          expected_res_status=None, **kwargs):
+
+        data = {'ikepolicy': {
+                'name': name,
+                'auth_algorithm': auth_algorithm,
+                'encryption_algorithm': encryption_algorithm,
+                'phase1_negotiation_mode': phase1_negotiation_mode,
+                'lifetime': {
+                    'units': lifetime_units,
+                    'value': lifetime_value},
+                'ike_version': ike_version,
+                'pfs': pfs,
+                'tenant_id': self._tenant_id
+                }}
+        for arg in ['description']:
+            if arg in kwargs and kwargs[arg] is not None:
+                data['ikepolicy'][arg] = kwargs[arg]
+
+        ikepolicy_req = self.new_create_request('ikepolicies', data, fmt)
+        ikepolicy_res = ikepolicy_req.get_response(self.ext_api)
+        if expected_res_status:
+            self.assertEqual(ikepolicy_res.status_int, expected_res_status)
+
+        return ikepolicy_res
+
+    @contextlib.contextmanager
+    def ikepolicy(self, fmt=None,
+                  name='ikepolicy1',
+                  auth_algorithm='sha1',
+                  encryption_algorithm='aes-128',
+                  phase1_negotiation_mode='main',
+                  lifetime_units='seconds',
+                  lifetime_value=3600,
+                  ike_version='v1',
+                  pfs='group5',
+                  no_delete=False,
+                  **kwargs):
+        if not fmt:
+            fmt = self.fmt
+        res = self._create_ikepolicy(fmt,
+                                     name,
+                                     auth_algorithm,
+                                     encryption_algorithm,
+                                     phase1_negotiation_mode,
+                                     lifetime_units,
+                                     lifetime_value,
+                                     ike_version,
+                                     pfs,
+                                     **kwargs)
+        if res.status_int >= 400:
+            raise webob.exc.HTTPClientError(code=res.status_int)
+        try:
+            ikepolicy = self.deserialize(fmt or self.fmt, res)
+            yield ikepolicy
+        finally:
+            if not no_delete:
+                self._delete('ikepolicies', ikepolicy['ikepolicy']['id'])
+
+    def _create_ipsecpolicy(self, fmt,
+                            name='ipsecpolicy1',
+                            auth_algorithm='sha1',
+                            encryption_algorithm='aes-128',
+                            encapsulation_mode='tunnel',
+                            transform_protocol='esp',
+                            lifetime_units='seconds',
+                            lifetime_value=3600,
+                            pfs='group5',
+                            expected_res_status=None,
+                            **kwargs):
+
+        data = {'ipsecpolicy': {'name': name,
+                                'auth_algorithm': auth_algorithm,
+                                'encryption_algorithm': encryption_algorithm,
+                                'encapsulation_mode': encapsulation_mode,
+                                'transform_protocol': transform_protocol,
+                                'lifetime': {'units': lifetime_units,
+                                             'value': lifetime_value},
+                                'pfs': pfs,
+                                'tenant_id': self._tenant_id}}
+        for arg in ['description']:
+            if arg in kwargs and kwargs[arg] is not None:
+                data['ipsecpolicy'][arg] = kwargs[arg]
+        ipsecpolicy_req = self.new_create_request('ipsecpolicies', data, fmt)
+        ipsecpolicy_res = ipsecpolicy_req.get_response(self.ext_api)
+        if expected_res_status:
+            self.assertEqual(ipsecpolicy_res.status_int, expected_res_status)
+
+        return ipsecpolicy_res
+
+    @contextlib.contextmanager
+    def ipsecpolicy(self, fmt=None,
+                    name='ipsecpolicy1',
+                    auth_algorithm='sha1',
+                    encryption_algorithm='aes-128',
+                    encapsulation_mode='tunnel',
+                    transform_protocol='esp',
+                    lifetime_units='seconds',
+                    lifetime_value=3600,
+                    pfs='group5',
+                    no_delete=False, **kwargs):
+        if not fmt:
+            fmt = self.fmt
+        res = self._create_ipsecpolicy(fmt,
+                                       name,
+                                       auth_algorithm,
+                                       encryption_algorithm,
+                                       encapsulation_mode,
+                                       transform_protocol,
+                                       lifetime_units,
+                                       lifetime_value,
+                                       pfs,
+                                       **kwargs)
+        if res.status_int >= 400:
+            raise webob.exc.HTTPClientError(code=res.status_int)
+        try:
+            ipsecpolicy = self.deserialize(fmt or self.fmt, res)
+            yield ipsecpolicy
+        finally:
+            if not no_delete:
+                self._delete('ipsecpolicies', ipsecpolicy['ipsecpolicy']['id'])
+
+    def _create_vpnservice(self, fmt, name,
+                           admin_state_up,
+                           router_id, subnet_id,
+                           expected_res_status=None, **kwargs):
+        data = {'vpnservice': {'name': name,
+                               'subnet_id': subnet_id,
+                               'router_id': router_id,
+                               'admin_state_up': admin_state_up,
+                               'tenant_id': self._tenant_id}}
+        for arg in ['description']:
+            if arg in kwargs and kwargs[arg] is not None:
+                data['vpnservice'][arg] = kwargs[arg]
+        vpnservice_req = self.new_create_request('vpnservices', data, fmt)
+        vpnservice_res = vpnservice_req.get_response(self.ext_api)
+        if expected_res_status:
+            self.assertEqual(vpnservice_res.status_int, expected_res_status)
+        return vpnservice_res
+
+    @contextlib.contextmanager
+    def vpnservice(self, fmt=None, name='vpnservice1',
+                   subnet=None,
+                   router=None,
+                   admin_state_up=True,
+                   no_delete=False, **kwargs):
+        if not fmt:
+            fmt = self.fmt
+        with test_db_plugin.optional_ctx(subnet, self.subnet) as tmp_subnet:
+            with test_db_plugin.optional_ctx(router,
+                                             self.router) as tmp_router:
+                res = self._create_vpnservice(fmt,
+                                              name,
+                                              admin_state_up,
+                                              router_id=(tmp_router['router']
+                                                         ['id']),
+                                              subnet_id=(tmp_subnet['subnet']
+                                                         ['id']),
+                                              **kwargs)
+                if res.status_int >= 400:
+                    raise webob.exc.HTTPClientError(code=res.status_int)
+                try:
+                    vpnservice = self.deserialize(fmt or self.fmt, res)
+                    yield vpnservice
+                finally:
+                    if not no_delete:
+                        self._delete('vpnservices',
+                                     vpnservice['vpnservice']['id'])
+
+    def _create_ipsec_site_connection(self, fmt, name='test',
+                                      peer_address='192.168.1.10',
+                                      peer_id='192.168.1.10',
+                                      peer_cidrs=None,
+                                      mtu=1500,
+                                      psk='abcdefg',
+                                      initiator='bi-directional',
+                                      dpd_action='hold',
+                                      dpd_interval=30,
+                                      dpd_timeout=120,
+                                      vpnservice_id='fake_id',
+                                      ikepolicy_id='fake_id',
+                                      ipsecpolicy_id='fake_id',
+                                      admin_state_up=True,
+                                      expected_res_status=None, **kwargs):
+        data = {
+            'ipsec_site_connection': {'name': name,
+                                      'peer_address': peer_address,
+                                      'peer_id': peer_id,
+                                      'peer_cidrs': peer_cidrs,
+                                      'mtu': mtu,
+                                      'psk': psk,
+                                      'initiator': initiator,
+                                      'dpd': {
+                                          'action': dpd_action,
+                                          'interval': dpd_interval,
+                                          'timeout': dpd_timeout,
+                                      },
+                                      'vpnservice_id': vpnservice_id,
+                                      'ikepolicy_id': ikepolicy_id,
+                                      'ipsecpolicy_id': ipsecpolicy_id,
+                                      'admin_state_up': admin_state_up,
+                                      'tenant_id': self._tenant_id}
+        }
+        for arg in ['description']:
+            if arg in kwargs and kwargs[arg] is not None:
+                data['ipsec_site_connection'][arg] = kwargs[arg]
+
+        ipsec_site_connection_req = self.new_create_request(
+            'ipsec-site-connections', data, fmt
+        )
+        ipsec_site_connection_res = ipsec_site_connection_req.get_response(
+            self.ext_api
+        )
+        if expected_res_status:
+            self.assertEqual(
+                ipsec_site_connection_res.status_int, expected_res_status
+            )
+
+        return ipsec_site_connection_res
+
+    @contextlib.contextmanager
+    def ipsec_site_connection(self, fmt=None, name='ipsec_site_connection1',
+                              peer_address='192.168.1.10',
+                              peer_id='192.168.1.10',
+                              peer_cidrs=None,
+                              mtu=1500,
+                              psk='abcdefg',
+                              initiator='bi-directional',
+                              dpd_action='hold',
+                              dpd_interval=30,
+                              dpd_timeout=120,
+                              vpnservice=None,
+                              ikepolicy=None,
+                              ipsecpolicy=None,
+                              admin_state_up=True, no_delete=False,
+                              **kwargs):
+        if not fmt:
+            fmt = self.fmt
+        with contextlib.nested(
+            test_db_plugin.optional_ctx(vpnservice,
+                                        self.vpnservice),
+            test_db_plugin.optional_ctx(ikepolicy,
+                                        self.ikepolicy),
+            test_db_plugin.optional_ctx(ipsecpolicy,
+                                        self.ipsecpolicy)
+        ) as (tmp_vpnservice, tmp_ikepolicy, tmp_ipsecpolicy):
+            vpnservice_id = tmp_vpnservice['vpnservice']['id']
+            ikepolicy_id = tmp_ikepolicy['ikepolicy']['id']
+            ipsecpolicy_id = tmp_ipsecpolicy['ipsecpolicy']['id']
+            res = self._create_ipsec_site_connection(fmt,
+                                                     name,
+                                                     peer_address,
+                                                     peer_id,
+                                                     peer_cidrs,
+                                                     mtu,
+                                                     psk,
+                                                     initiator,
+                                                     dpd_action,
+                                                     dpd_interval,
+                                                     dpd_timeout,
+                                                     vpnservice_id,
+                                                     ikepolicy_id,
+                                                     ipsecpolicy_id,
+                                                     admin_state_up,
+                                                     **kwargs)
+            if res.status_int >= 400:
+                raise webob.exc.HTTPClientError(code=res.status_int)
+            try:
+                ipsec_site_connection = self.deserialize(
+                    fmt or self.fmt, res
+                )
+                yield ipsec_site_connection
+            finally:
+                if not no_delete:
+                    self._delete(
+                        'ipsec-site-connections',
+                        ipsec_site_connection[
+                        'ipsec_site_connection']['id']
+                    )
+
+
+class TestVpnaas(VPNPluginDbTestCase):
+
+    def _check_policy(self, policy, keys, lifetime):
+        for k, v in keys:
+            self.assertEqual(policy[k], v)
+        for k, v in lifetime.iteritems():
+            self.assertEqual(policy['lifetime'][k], v)
+
+    def test_create_ikepolicy(self):
+        """Test case to create an ikepolicy."""
+        name = "ikepolicy1"
+        description = 'ipsec-ikepolicy'
+        keys = [('name', name),
+                ('description', 'ipsec-ikepolicy'),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('phase1_negotiation_mode', 'main'),
+                ('ike_version', 'v1'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        lifetime = {
+            'units': 'seconds',
+            'value': 3600}
+        with self.ikepolicy(name=name, description=description) as ikepolicy:
+            self._check_policy(ikepolicy['ikepolicy'], keys, lifetime)
+
+    def test_delete_ikepolicy(self):
+        """Test case to delete an ikepolicy."""
+        with self.ikepolicy(no_delete=True) as ikepolicy:
+            req = self.new_delete_request('ikepolicies',
+                                          ikepolicy['ikepolicy']['id'])
+            res = req.get_response(self.ext_api)
+            self.assertEqual(res.status_int, 204)
+
+    def test_show_ikepolicy(self):
+        """Test case to show or get an ikepolicy."""
+        name = "ikepolicy1"
+        description = 'ipsec-ikepolicy'
+        keys = [('name', name),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('phase1_negotiation_mode', 'main'),
+                ('ike_version', 'v1'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        lifetime = {
+            'units': 'seconds',
+            'value': 3600}
+        with self.ikepolicy(name=name, description=description) as ikepolicy:
+            req = self.new_show_request('ikepolicies',
+                                        ikepolicy['ikepolicy']['id'],
+                                        fmt=self.fmt)
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            self._check_policy(res['ikepolicy'], keys, lifetime)
+
+    def test_list_ikepolicies(self):
+        """Test case to list all ikepolicies."""
+        name = "ikepolicy_list"
+        keys = [('name', name),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('phase1_negotiation_mode', 'main'),
+                ('ike_version', 'v1'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        lifetime = {
+            'units': 'seconds',
+            'value': 3600}
+        with self.ikepolicy(name=name) as ikepolicy:
+            keys.append(('id', ikepolicy['ikepolicy']['id']))
+            req = self.new_list_request('ikepolicies')
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            self.assertEqual(len(res), 1)
+            for k, v in keys:
+                self.assertEqual(res['ikepolicies'][0][k], v)
+            for k, v in lifetime.iteritems():
+                self.assertEqual(res['ikepolicies'][0]['lifetime'][k], v)
+
+    def test_list_ikepolicies_with_sort_emulated(self):
+        """Test case to list all ikepolicies."""
+        with contextlib.nested(self.ikepolicy(name='ikepolicy1'),
+                               self.ikepolicy(name='ikepolicy2'),
+                               self.ikepolicy(name='ikepolicy3')
+                               ) as (ikepolicy1, ikepolicy2, ikepolicy3):
+            self._test_list_with_sort('ikepolicy', (ikepolicy3,
+                                                    ikepolicy2,
+                                                    ikepolicy1),
+                                      [('name', 'desc')],
+                                      'ikepolicies')
+
+    def test_list_ikepolicies_with_pagination_emulated(self):
+        """Test case to list all ikepolicies with pagination."""
+        with contextlib.nested(self.ikepolicy(name='ikepolicy1'),
+                               self.ikepolicy(name='ikepolicy2'),
+                               self.ikepolicy(name='ikepolicy3')
+                               ) as (ikepolicy1, ikepolicy2, ikepolicy3):
+            self._test_list_with_pagination('ikepolicy',
+                                            (ikepolicy1,
+                                             ikepolicy2,
+                                             ikepolicy3),
+                                            ('name', 'asc'), 2, 2,
+                                            'ikepolicies')
+
+    def test_list_ikepolicies_with_pagination_reverse_emulated(self):
+        """Test case to list all ikepolicies with reverse pagination."""
+        with contextlib.nested(self.ikepolicy(name='ikepolicy1'),
+                               self.ikepolicy(name='ikepolicy2'),
+                               self.ikepolicy(name='ikepolicy3')
+                               ) as (ikepolicy1, ikepolicy2, ikepolicy3):
+            self._test_list_with_pagination_reverse('ikepolicy',
+                                                    (ikepolicy1,
+                                                     ikepolicy2,
+                                                     ikepolicy3),
+                                                    ('name', 'asc'), 2, 2,
+                                                    'ikepolicies')
+
+    def test_update_ikepolicy(self):
+        """Test case to update an ikepolicy."""
+        name = "new_ikepolicy1"
+        keys = [('name', name),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('phase1_negotiation_mode', 'main'),
+                ('ike_version', 'v1'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        with self.ikepolicy(name=name) as ikepolicy:
+            data = {'ikepolicy': {'name': name}}
+            req = self.new_update_request("ikepolicies",
+                                          data,
+                                          ikepolicy['ikepolicy']['id'])
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            for k, v in keys:
+                self.assertEqual(res['ikepolicy'][k], v)
+
+    def test_create_ikepolicy_with_invalid_values(self):
+        """Test case to test invalid values."""
+        name = 'ikepolicy1'
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               auth_algorithm='md5',
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               auth_algorithm=200,
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               encryption_algorithm='des',
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               encryption_algorithm=100,
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               phase1_negotiation_mode='aggressive',
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               phase1_negotiation_mode=-100,
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               ike_version='v6',
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               ike_version=500,
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               pfs='group1',
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               pfs=120,
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               lifetime_units='Megabytes',
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               lifetime_units=20000,
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               lifetime_value=-20,
+                               expected_res_status=400)
+        self._create_ikepolicy(name=name,
+                               fmt=self.fmt,
+                               lifetime_value='Megabytes',
+                               expected_res_status=400)
+
+    def test_create_ipsecpolicy(self):
+        """Test case to create an ipsecpolicy."""
+        name = "ipsecpolicy1"
+        description = 'my-ipsecpolicy'
+        keys = [('name', name),
+                ('description', 'my-ipsecpolicy'),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('encapsulation_mode', 'tunnel'),
+                ('transform_protocol', 'esp'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        lifetime = {
+            'units': 'seconds',
+            'value': 3600}
+        with self.ipsecpolicy(name=name,
+                              description=description) as ipsecpolicy:
+            self._check_policy(ipsecpolicy['ipsecpolicy'], keys, lifetime)
+
+    def test_delete_ipsecpolicy(self):
+        """Test case to delete an ipsecpolicy."""
+        with self.ipsecpolicy(no_delete=True) as ipsecpolicy:
+            req = self.new_delete_request('ipsecpolicies',
+                                          ipsecpolicy['ipsecpolicy']['id'])
+            res = req.get_response(self.ext_api)
+            self.assertEqual(res.status_int, 204)
+
+    def test_show_ipsecpolicy(self):
+        """Test case to show or get an ipsecpolicy."""
+        name = "ipsecpolicy1"
+        keys = [('name', name),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('encapsulation_mode', 'tunnel'),
+                ('transform_protocol', 'esp'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        lifetime = {
+            'units': 'seconds',
+            'value': 3600}
+        with self.ipsecpolicy(name=name) as ipsecpolicy:
+            req = self.new_show_request('ipsecpolicies',
+                                        ipsecpolicy['ipsecpolicy']['id'],
+                                        fmt=self.fmt)
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            self._check_policy(res['ipsecpolicy'], keys, lifetime)
+
+    def test_list_ipsecpolicies(self):
+        """Test case to list all ipsecpolicies."""
+        name = "ipsecpolicy_list"
+        keys = [('name', name),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('encapsulation_mode', 'tunnel'),
+                ('transform_protocol', 'esp'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        lifetime = {
+            'units': 'seconds',
+            'value': 3600}
+        with self.ipsecpolicy(name=name) as ipsecpolicy:
+            keys.append(('id', ipsecpolicy['ipsecpolicy']['id']))
+            req = self.new_list_request('ipsecpolicies')
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            self.assertEqual(len(res), 1)
+            self._check_policy(res['ipsecpolicies'][0], keys, lifetime)
+
+    def test_list_ipsecpolicies_with_sort_emulated(self):
+        """Test case to list all ipsecpolicies."""
+        with contextlib.nested(self.ipsecpolicy(name='ipsecpolicy1'),
+                               self.ipsecpolicy(name='ipsecpolicy2'),
+                               self.ipsecpolicy(name='ipsecpolicy3')
+                               ) as(ipsecpolicy1, ipsecpolicy2, ipsecpolicy3):
+            self._test_list_with_sort('ipsecpolicy', (ipsecpolicy3,
+                                                      ipsecpolicy2,
+                                                      ipsecpolicy1),
+                                      [('name', 'desc')],
+                                      'ipsecpolicies')
+
+    def test_list_ipsecpolicies_with_pagination_emulated(self):
+        """Test case to list all ipsecpolicies with pagination."""
+        with contextlib.nested(self.ipsecpolicy(name='ipsecpolicy1'),
+                               self.ipsecpolicy(name='ipsecpolicy2'),
+                               self.ipsecpolicy(name='ipsecpolicy3')
+                               ) as(ipsecpolicy1, ipsecpolicy2, ipsecpolicy3):
+            self._test_list_with_pagination('ipsecpolicy',
+                                            (ipsecpolicy1,
+                                             ipsecpolicy2,
+                                             ipsecpolicy3),
+                                            ('name', 'asc'), 2, 2,
+                                            'ipsecpolicies')
+
+    def test_list_ipsecpolicies_with_pagination_reverse_emulated(self):
+        """Test case to list all ipsecpolicies with reverse pagination."""
+        with contextlib.nested(self.ipsecpolicy(name='ipsecpolicy1'),
+                               self.ipsecpolicy(name='ipsecpolicy2'),
+                               self.ipsecpolicy(name='ipsecpolicy3')
+                               ) as(ipsecpolicy1, ipsecpolicy2, ipsecpolicy3):
+            self._test_list_with_pagination_reverse('ipsecpolicy',
+                                                    (ipsecpolicy1,
+                                                     ipsecpolicy2,
+                                                     ipsecpolicy3),
+                                                    ('name', 'asc'), 2, 2,
+                                                    'ipsecpolicies')
+
+    def test_update_ipsecpolicy(self):
+        """Test case to update an ipsecpolicy."""
+        name = "new_ipsecpolicy1"
+        keys = [('name', name),
+                ('auth_algorithm', 'sha1'),
+                ('encryption_algorithm', 'aes-128'),
+                ('encapsulation_mode', 'tunnel'),
+                ('transform_protocol', 'esp'),
+                ('pfs', 'group5'),
+                ('tenant_id', self._tenant_id)]
+        with self.ipsecpolicy(name=name) as ipsecpolicy:
+            data = {'ipsecpolicy': {'name': name}}
+            req = self.new_update_request("ipsecpolicies",
+                                          data,
+                                          ipsecpolicy['ipsecpolicy']['id'])
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            for k, v in keys:
+                self.assertEqual(res['ipsecpolicy'][k], v)
+
+    def test_create_ipsecpolicy_with_invalid_values(self):
+        """Test case to test invalid values."""
+        name = 'ipsecpolicy1'
+
+        self._create_ipsecpolicy(
+            fmt=self.fmt,
+            name=name, auth_algorithm='md5', expected_res_status=400)
+        self._create_ipsecpolicy(
+            fmt=self.fmt,
+            name=name, auth_algorithm=100, expected_res_status=400)
+
+        self._create_ipsecpolicy(
+            fmt=self.fmt,
+            name=name, encryption_algorithm='des', expected_res_status=400)
+        self._create_ipsecpolicy(
+            fmt=self.fmt,
+            name=name, encryption_algorithm=200, expected_res_status=400)
+
+        self._create_ipsecpolicy(
+            fmt=self.fmt,
+            name=name, transform_protocol='abcd', expected_res_status=400)
+        self._create_ipsecpolicy(
+            fmt=self.fmt,
+            name=name, transform_protocol=500, expected_res_status=400)
+
+        self._create_ipsecpolicy(
+            fmt=self.fmt,
+            name=name,
+            encapsulation_mode='unsupported', expected_res_status=400)
+        self._create_ipsecpolicy(name=name,
+                                 fmt=self.fmt,
+                                 encapsulation_mode=100,
+                                 expected_res_status=400)
+
+        self._create_ipsecpolicy(name=name,
+                                 fmt=self.fmt,
+                                 pfs='group9', expected_res_status=400)
+        self._create_ipsecpolicy(
+            fmt=self.fmt, name=name, pfs=-1, expected_res_status=400)
+
+        self._create_ipsecpolicy(
+            fmt=self.fmt, name=name, lifetime_units='minutes',
+            expected_res_status=400)
+
+        self._create_ipsecpolicy(fmt=self.fmt, name=name, lifetime_units=100,
+                                 expected_res_status=400)
+
+        self._create_ipsecpolicy(fmt=self.fmt, name=name,
+                                 lifetime_value=-800, expected_res_status=400)
+        self._create_ipsecpolicy(fmt=self.fmt, name=name,
+                                 lifetime_value='Megabytes',
+                                 expected_res_status=400)
+
+    def test_create_vpnservice(self, **extras):
+        """Test case to create a vpnservice."""
+        description = 'my-vpn-service'
+        expected = {'name': 'vpnservice1',
+                    'description': 'my-vpn-service',
+                    'admin_state_up': True,
+                    'status': 'PENDING_CREATE',
+                    'tenant_id': self._tenant_id, }
+
+        expected.update(extras)
+        with self.subnet(cidr='10.2.0.0/24') as subnet:
+            with self.router() as router:
+                expected['router_id'] = router['router']['id']
+                expected['subnet_id'] = subnet['subnet']['id']
+                name = expected['name']
+                with self.vpnservice(name=name,
+                                     subnet=subnet,
+                                     router=router,
+                                     description=description,
+                                     **extras) as vpnservice:
+                    self.assertEqual(dict((k, v) for k, v in
+                                          vpnservice['vpnservice'].items()
+                                          if k in expected),
+                                     expected)
+                return vpnservice
+
+    def _set_active(self, model, resource_id):
+        service_plugin = manager.NeutronManager.get_service_plugins()[
+            constants.VPN]
+        adminContext = context.get_admin_context()
+        with adminContext.session.begin(subtransactions=True):
+            resource_db = service_plugin._get_resource(
+                adminContext,
+                model,
+                resource_id)
+            resource_db.status = constants.ACTIVE
+
+    def test_update_vpnservice(self):
+        """Test case to update a vpnservice."""
+        name = 'new_vpnservice1'
+        keys = [('name', name)]
+        with contextlib.nested(
+            self.subnet(cidr='10.2.0.0/24'),
+            self.router()) as (subnet, router):
+            with self.vpnservice(name=name,
+                                 subnet=subnet,
+                                 router=router) as vpnservice:
+                keys.append(('subnet_id',
+                             vpnservice['vpnservice']['subnet_id']))
+                keys.append(('router_id',
+                             vpnservice['vpnservice']['router_id']))
+                data = {'vpnservice': {'name': name}}
+                self._set_active(vpn_db.VPNService,
+                                 vpnservice['vpnservice']['id'])
+                req = self.new_update_request(
+                    'vpnservices',
+                    data,
+                    vpnservice['vpnservice']['id'])
+                res = self.deserialize(self.fmt,
+                                       req.get_response(self.ext_api))
+                for k, v in keys:
+                    self.assertEqual(res['vpnservice'][k], v)
+
+    def test_update_vpnservice_with_invalid_state(self):
+        """Test case to update a vpnservice in invalid state ."""
+        name = 'new_vpnservice1'
+        keys = [('name', name)]
+        with contextlib.nested(
+            self.subnet(cidr='10.2.0.0/24'),
+            self.router()) as (subnet, router):
+            with self.vpnservice(name=name,
+                                 subnet=subnet,
+                                 router=router) as vpnservice:
+                keys.append(('subnet_id',
+                             vpnservice['vpnservice']['subnet_id']))
+                keys.append(('router_id',
+                             vpnservice['vpnservice']['router_id']))
+                data = {'vpnservice': {'name': name}}
+                req = self.new_update_request(
+                    'vpnservices',
+                    data,
+                    vpnservice['vpnservice']['id'])
+                res = req.get_response(self.ext_api)
+                self.assertEqual(400, res.status_int)
+
+    def test_delete_vpnservice(self):
+        """Test case to delete a vpnservice."""
+        with self.vpnservice(name='vpnserver',
+                             no_delete=True) as vpnservice:
+            req = self.new_delete_request('vpnservices',
+                                          vpnservice['vpnservice']['id'])
+            res = req.get_response(self.ext_api)
+            self.assertEqual(res.status_int, 204)
+
+    def test_show_vpnservice(self):
+        """Test case to show or get a vpnservice."""
+        name = "vpnservice1"
+        keys = [('name', name),
+                ('description', ''),
+                ('admin_state_up', True),
+                ('status', 'PENDING_CREATE')]
+        with self.vpnservice(name=name) as vpnservice:
+            req = self.new_show_request('vpnservices',
+                                        vpnservice['vpnservice']['id'])
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            for k, v in keys:
+                self.assertEqual(res['vpnservice'][k], v)
+
+    def test_list_vpnservices(self):
+        """Test case to list all vpnservices."""
+        name = "vpnservice_list"
+        keys = [('name', name),
+                ('description', ''),
+                ('admin_state_up', True),
+                ('status', 'PENDING_CREATE')]
+        with self.vpnservice(name=name) as vpnservice:
+            keys.append(('subnet_id', vpnservice['vpnservice']['subnet_id']))
+            keys.append(('router_id', vpnservice['vpnservice']['router_id']))
+            req = self.new_list_request('vpnservices')
+            res = self.deserialize(self.fmt, req.get_response(self.ext_api))
+            self.assertEqual(len(res), 1)
+            for k, v in keys:
+                self.assertEqual(res['vpnservices'][0][k], v)
+
+    def test_list_vpnservices_with_sort_emulated(self):
+        """Test case to list all vpnservices with sorting."""
+        with self.subnet() as subnet:
+            with self.router() as router:
+                with contextlib.nested(
+                    self.vpnservice(name='vpnservice1',
+                                    subnet=subnet,
+                                    router=router),
+                    self.vpnservice(name='vpnservice2',
+                                    subnet=subnet,
+                                    router=router),
+                    self.vpnservice(name='vpnservice3',
+                                    subnet=subnet,
+                                    router=router)
+                ) as(vpnservice1, vpnservice2, vpnservice3):
+                    self._test_list_with_sort('vpnservice', (vpnservice3,
+                                                             vpnservice2,
+                                                             vpnservice1),
+                                              [('name', 'desc')])
+
+    def test_list_vpnservice_with_pagination_emulated(self):
+        """Test case to list all vpnservices with pagination."""
+        with self.subnet() as subnet:
+            with self.router() as router:
+                with contextlib.nested(
+                    self.vpnservice(name='vpnservice1',
+                                    subnet=subnet,
+                                    router=router),
+                    self.vpnservice(name='vpnservice2',
+                                    subnet=subnet,
+                                    router=router),
+                    self.vpnservice(name='vpnservice3',
+                                    subnet=subnet,
+                                    router=router)
+                ) as(vpnservice1, vpnservice2, vpnservice3):
+                    self._test_list_with_pagination('vpnservice',
+                                                    (vpnservice1,
+                                                     vpnservice2,
+                                                     vpnservice3),
+                                                    ('name', 'asc'), 2, 2)
+
+    def test_list_vpnservice_with_pagination_reverse_emulated(self):
+        """Test case to list all vpnservices with reverse pagination."""
+        with self.subnet() as subnet:
+            with self.router() as router:
+                with contextlib.nested(
+                    self.vpnservice(name='vpnservice1',
+                                    subnet=subnet,
+                                    router=router),
+                    self.vpnservice(name='vpnservice2',
+                                    subnet=subnet,
+                                    router=router),
+                    self.vpnservice(name='vpnservice3',
+                                    subnet=subnet,
+                                    router=router)
+                ) as(vpnservice1, vpnservice2, vpnservice3):
+                    self._test_list_with_pagination_reverse('vpnservice',
+                                                            (vpnservice1,
+                                                             vpnservice2,
+                                                             vpnservice3),
+                                                            ('name', 'asc'),
+                                                            2, 2)
+
+    def test_create_ipsec_site_connection_with_invalid_values(self):
+        """Test case to create an ipsec_site_connection with invalid values."""
+        name = 'connection1'
+        self._create_ipsec_site_connection(
+            fmt=self.fmt,
+            name=name, peer_cidrs='myname', expected_status_int=400)
+        self._create_ipsec_site_connection(
+            fmt=self.fmt,
+            name=name, mtu=-100, expected_status_int=400)
+        self._create_ipsec_site_connection(
+            fmt=self.fmt,
+            name=name, dpd_action='unsupported', expected_status_int=400)
+        self._create_ipsec_site_connection(
+            fmt=self.fmt,
+            name=name, dpd_interval=-1, expected_status_int=400)
+        self._create_ipsec_site_connection(
+            fmt=self.fmt,
+            name=name, dpd_timeout=-200, expected_status_int=400)
+        self._create_ipsec_site_connection(
+            fmt=self.fmt,
+            name=name, initiator='unsupported', expected_status_int=400)
+        self._create_ipsec_site_connection(
+            fmt=self.fmt,
+            name=name,
+            dpd_interval=30,
+            dpd_timeout=20, expected_status_int=400)
+
+    def test_create_ipsec_site_connection(self, **extras):
+        """Test case to create an ipsec_site_connection."""
+        ikename = "ikepolicy1"
+        ipsecname = "ipsecpolicy1"
+        vpnsname = "vpnservice1"
+        name = "connection1"
+        description = "my-ipsec-connection"
+        keys = {'name': name,
+                'description': "my-ipsec-connection",
+                'peer_address': '192.168.1.10',
+                'peer_id': '192.168.1.10',
+                'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'],
+                'initiator': 'bi-directional',
+                'mtu': 1500,
+                'tenant_id': self._tenant_id,
+                'psk': 'abcd',
+                'status': 'PENDING_CREATE',
+                'admin_state_up': True}
+        dpd = {'action': 'hold',
+               'interval': 40,
+               'timeout': 120}
+        keys.update(extras)
+        with contextlib.nested(
+            self.ikepolicy(name=ikename),
+            self.ipsecpolicy(name=ipsecname),
+            self.subnet(),
+            self.router()) as (
+                ikepolicy, ipsecpolicy, subnet, router):
+                with self.vpnservice(name=vpnsname, subnet=subnet,
+                                     router=router) as vpnservice1:
+                    keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id']
+                    keys['ipsecpolicy_id'] = (
+                        ipsecpolicy['ipsecpolicy']['id']
+                    )
+                    keys['vpnservice_id'] = (
+                        vpnservice1['vpnservice']['id']
+                    )
+                    with self.ipsec_site_connection(
+                            self.fmt,
+                            name,
+                            keys['peer_address'],
+                            keys['peer_id'],
+                            keys['peer_cidrs'],
+                            keys['mtu'],
+                            keys['psk'],
+                            keys['initiator'],
+                            dpd['action'],
+                            dpd['interval'],
+                            dpd['timeout'],
+                            vpnservice1,
+                            ikepolicy,
+                            ipsecpolicy,
+                            keys['admin_state_up'],
+                            description=description,
+                            **extras
+                    ) as ipsec_site_connection:
+                        self._check_ipsec_site_connection(
+                            ipsec_site_connection['ipsec_site_connection'],
+                            keys,
+                            dpd)
+
+    def _check_ipsec_site_connection(self, ipsec_site_connection, keys, dpd):
+        self.assertEqual(
+            dict((k, v) for k, v
+                 in ipsec_site_connection.items()
+                 if k in keys), keys)
+        self.assertEqual(
+            dict((k, v) for k, v
+                 in ipsec_site_connection['dpd'].items()
+                 if k in dpd), dpd)
+
+    def test_delete_ipsec_site_connection(self):
+        """Test case to delete a ipsec_site_connection."""
+        with self.ipsec_site_connection(
+                no_delete=True) as ipsec_site_connection:
+            req = self.new_delete_request(
+                'ipsec-site-connections',
+                ipsec_site_connection['ipsec_site_connection']['id']
+            )
+            res = req.get_response(self.ext_api)
+            self.assertEqual(res.status_int, 204)
+
+    def test_update_ipsec_site_connection(self):
+        """Test case to update a ipsec_site_connection."""
+        name = 'new_ipsec_site_connection'
+        ikename = 'ikepolicy1'
+        ipsecname = 'ipsecpolicy1'
+        vpnsname = 'vpnservice1'
+        description = 'my-ipsec-connection'
+        keys = {'name': 'new_ipsec_site_connection',
+                'description': "my-ipsec-connection",
+                'peer_address': '192.168.1.10',
+                'peer_id': '192.168.1.10',
+                'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'],
+                'initiator': 'bi-directional',
+                'mtu': 1500,
+                'tenant_id': self._tenant_id,
+                'psk': 'abcd',
+                'status': 'ACTIVE',
+                'admin_state_up': True}
+        dpd = {'action': 'hold',
+               'interval': 40,
+               'timeout': 120}
+        with contextlib.nested(
+            self.ikepolicy(name=ikename),
+            self.ipsecpolicy(name=ipsecname),
+            self.subnet(cidr='10.2.0.0/24'),
+            self.router()) as (
+                ikepolicy, ipsecpolicy, subnet, router):
+            with self.vpnservice(name=vpnsname, subnet=subnet,
+                                 router=router) as vpnservice1:
+                keys['vpnservice_id'] = (
+                    vpnservice1['vpnservice']['id']
+                )
+                keys['ikepolicy_id'] = (
+                    ikepolicy['ikepolicy']['id']
+                )
+                keys['ipsecpolicy_id'] = (
+                    ipsecpolicy['ipsecpolicy']['id']
+                )
+                with self.ipsec_site_connection(
+                    self.fmt,
+                    name,
+                    keys['peer_address'],
+                    keys['peer_id'],
+                    keys['peer_cidrs'],
+                    keys['mtu'],
+                    keys['psk'],
+                    keys['initiator'],
+                    dpd['action'],
+                    dpd['interval'],
+                    dpd['timeout'],
+                    vpnservice1,
+                    ikepolicy,
+                    ipsecpolicy,
+                    keys['admin_state_up'],
+                    description=description
+                ) as ipsec_site_connection:
+                    data = {'ipsec_site_connection': {'name': name}}
+                    self._set_active(
+                        vpn_db.IPsecSiteConnection,
+                        ipsec_site_connection['ipsec_site_connection']['id'])
+                    req = self.new_update_request(
+                        'ipsec-site-connections',
+                        data,
+                        ipsec_site_connection['ipsec_site_connection']['id']
+                    )
+                    res = self.deserialize(
+                        self.fmt,
+                        req.get_response(self.ext_api)
+                    )
+                    for k, v in keys.items():
+                        self.assertEqual(res['ipsec_site_connection'][k], v)
+
+    def test_update_ipsec_site_connection_with_invalid_state(self):
+        """Test case to update an ipsec_site_connection in invalid state."""
+        name = 'new_ipsec_site_connection'
+        ikename = 'ikepolicy1'
+        ipsecname = 'ipsecpolicy1'
+        vpnsname = 'vpnservice1'
+        description = 'my-ipsec-connection'
+        keys = {'name': 'new_ipsec_site_connection',
+                'description': "my-ipsec-connection",
+                'peer_address': '192.168.1.10',
+                'peer_id': '192.168.1.10',
+                'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'],
+                'initiator': 'bi-directional',
+                'mtu': 1500,
+                'tenant_id': self._tenant_id,
+                'psk': 'abcd',
+                'status': 'ACTIVE',
+                'admin_state_up': True}
+        dpd = {'action': 'hold',
+               'interval': 40,
+               'timeout': 120}
+        with contextlib.nested(
+            self.ikepolicy(name=ikename),
+            self.ipsecpolicy(name=ipsecname),
+            self.subnet(cidr='10.2.0.0/24'),
+            self.router()) as (
+                ikepolicy, ipsecpolicy, subnet, router):
+            with self.vpnservice(name=vpnsname, subnet=subnet,
+                                 router=router) as vpnservice1:
+                keys['vpnservice_id'] = (
+                    vpnservice1['vpnservice']['id']
+                )
+                keys['ikepolicy_id'] = (
+                    ikepolicy['ikepolicy']['id']
+                )
+                keys['ipsecpolicy_id'] = (
+                    ipsecpolicy['ipsecpolicy']['id']
+                )
+                with self.ipsec_site_connection(
+                    self.fmt,
+                    name,
+                    keys['peer_address'],
+                    keys['peer_id'],
+                    keys['peer_cidrs'],
+                    keys['mtu'],
+                    keys['psk'],
+                    keys['initiator'],
+                    dpd['action'],
+                    dpd['interval'],
+                    dpd['timeout'],
+                    vpnservice1,
+                    ikepolicy,
+                    ipsecpolicy,
+                    keys['admin_state_up'],
+                    description=description
+                ) as ipsec_site_connection:
+                    data = {'ipsec_site_connection': {'name': name}}
+                    req = self.new_update_request(
+                        'ipsec-site-connections',
+                        data,
+                        ipsec_site_connection['ipsec_site_connection']['id']
+                    )
+                    res = req.get_response(self.ext_api)
+                    self.assertEqual(400, res.status_int)
+
+    def test_update_ipsec_site_connection_peer_cidrs(self):
+        """Test case to update a ipsec_site_connection for peer_cidrs."""
+        name = 'ipsec_site_connection'
+        ikename = 'ikepolicy1'
+        ipsecname = 'ipsecpolicy1'
+        vpnsname = 'vpnservice1'
+        description = 'my-ipsec-connection'
+        keys = {'name': 'ipsec_site_connection',
+                'description': "my-ipsec-connection",
+                'peer_address': '192.168.1.10',
+                'peer_id': '192.168.1.10',
+                'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'],
+                'initiator': 'bi-directional',
+                'mtu': 1500,
+                'tenant_id': self._tenant_id,
+                'psk': 'abcd',
+                'status': 'ACTIVE',
+                'admin_state_up': True}
+        dpd = {'action': 'hold',
+               'interval': 40,
+               'timeout': 120}
+        with contextlib.nested(
+            self.ikepolicy(name=ikename),
+            self.ipsecpolicy(name=ipsecname),
+            self.subnet(cidr='10.2.0.0/24'),
+            self.router()) as (
+                ikepolicy, ipsecpolicy, subnet, router):
+            with self.vpnservice(name=vpnsname, subnet=subnet,
+                                 router=router) as vpnservice1:
+                keys['vpnservice_id'] = vpnservice1['vpnservice']['id']
+                keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id']
+                keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id']
+                with self.ipsec_site_connection(
+                    self.fmt,
+                    name,
+                    keys['peer_address'],
+                    keys['peer_id'],
+                    keys['peer_cidrs'],
+                    keys['mtu'],
+                    keys['psk'],
+                    keys['initiator'],
+                    dpd['action'],
+                    dpd['interval'],
+                    dpd['timeout'],
+                    vpnservice1,
+                    ikepolicy,
+                    ipsecpolicy,
+                    keys['admin_state_up'],
+                    description=description
+                ) as ipsec_site_connection:
+                    data = {'ipsec_site_connection': {
+                        'peer_cidrs': ['192.168.2.0/24',
+                                       '192.168.3.0/24']
+                    }}
+                    self._set_active(
+                        vpn_db.IPsecSiteConnection,
+                        ipsec_site_connection['ipsec_site_connection']['id'])
+                    req = self.new_update_request(
+                        'ipsec-site-connections',
+                        data,
+                        ipsec_site_connection[
+                        'ipsec_site_connection']['id']
+                    )
+                    res = self.deserialize(
+                        self.fmt,
+                        req.get_response(self.ext_api)
+                    )
+                    self._check_ipsec_site_connection(
+                        res['ipsec_site_connection'],
+                        keys,
+                        dpd)
+
+    def test_show_ipsec_site_connection(self):
+        """Test case to show a ipsec_site_connection."""
+        ikename = "ikepolicy1"
+        ipsecname = "ipsecpolicy1"
+        vpnsname = "vpnservice1"
+        name = "connection1"
+        description = "my-ipsec-connection"
+        keys = {'name': name,
+                'description': "my-ipsec-connection",
+                'peer_address': '192.168.1.10',
+                'peer_id': '192.168.1.10',
+                'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'],
+                'initiator': 'bi-directional',
+                'mtu': 1500,
+                'tenant_id': self._tenant_id,
+                'psk': 'abcd',
+                'status': 'PENDING_CREATE',
+                'admin_state_up': True}
+        dpd = {'action': 'hold',
+               'interval': 40,
+               'timeout': 120}
+        with contextlib.nested(
+            self.ikepolicy(name=ikename),
+            self.ipsecpolicy(name=ipsecname),
+            self.subnet(),
+            self.router()) as (
+                ikepolicy, ipsecpolicy, subnet, router):
+            with self.vpnservice(name=vpnsname, subnet=subnet,
+                                 router=router) as vpnservice1:
+                keys['ikepolicy_id'] = ikepolicy['ikepolicy']['id']
+                keys['ipsecpolicy_id'] = ipsecpolicy['ipsecpolicy']['id']
+                keys['vpnservice_id'] = vpnservice1['vpnservice']['id']
+                with self.ipsec_site_connection(
+                    self.fmt,
+                    name,
+                    keys['peer_address'],
+                    keys['peer_id'],
+                    keys['peer_cidrs'],
+                    keys['mtu'],
+                    keys['psk'],
+                    keys['initiator'],
+                    dpd['action'],
+                    dpd['interval'],
+                    dpd['timeout'],
+                    vpnservice1,
+                    ikepolicy,
+                    ipsecpolicy,
+                    keys['admin_state_up'],
+                    description=description,
+                ) as ipsec_site_connection:
+
+                    req = self.new_show_request(
+                        'ipsec-site-connections',
+                        ipsec_site_connection[
+                        'ipsec_site_connection']['id'],
+                        fmt=self.fmt
+                    )
+                    res = self.deserialize(
+                        self.fmt,
+                        req.get_response(self.ext_api)
+                    )
+
+                    self._check_ipsec_site_connection(
+                        res['ipsec_site_connection'],
+                        keys,
+                        dpd)
+
+    def test_list_ipsec_site_connections_with_sort_emulated(self):
+        """Test case to list all ipsec_site_connections with sort."""
+        with self.subnet(cidr='10.2.0.0/24') as subnet:
+            with self.router() as router:
+                with self.vpnservice(subnet=subnet,
+                                     router=router
+                                     ) as vpnservice:
+                    with contextlib.nested(
+                        self.ipsec_site_connection(
+                            name='connection1', vpnservice=vpnservice
+                        ),
+                        self.ipsec_site_connection(
+                            name='connection2', vpnservice=vpnservice
+                        ),
+                        self.ipsec_site_connection(
+                            name='connection3', vpnservice=vpnservice
+                        )
+                    ) as(ipsec_site_connection1,
+                         ipsec_site_connection2,
+                         ipsec_site_connection3):
+                        self._test_list_with_sort('ipsec-site-connection',
+                                                  (ipsec_site_connection3,
+                                                   ipsec_site_connection2,
+                                                   ipsec_site_connection1),
+                                                  [('name', 'desc')])
+
+    def test_list_ipsec_site_connections_with_pagination_emulated(self):
+        """Test case to list all ipsec_site_connections with pagination."""
+        with self.subnet(cidr='10.2.0.0/24') as subnet:
+            with self.router() as router:
+                with self.vpnservice(subnet=subnet,
+                                     router=router
+                                     ) as vpnservice:
+                    with contextlib.nested(
+                        self.ipsec_site_connection(
+                            name='ipsec_site_connection1',
+                            vpnservice=vpnservice
+                        ),
+                        self.ipsec_site_connection(
+                            name='ipsec_site_connection1',
+                            vpnservice=vpnservice
+                        ),
+                        self.ipsec_site_connection(
+                            name='ipsec_site_connection1',
+                            vpnservice=vpnservice
+                        )
+                    ) as(ipsec_site_connection1,
+                         ipsec_site_connection2,
+                         ipsec_site_connection3):
+                        self._test_list_with_pagination(
+                            'ipsec-site-connection',
+                            (ipsec_site_connection1,
+                             ipsec_site_connection2,
+                             ipsec_site_connection3),
+                            ('name', 'asc'), 2, 2)
+
+    def test_list_ipsec_site_conns_with_pagination_reverse_emulated(self):
+        """Test to list all ipsec_site_connections with reverse pagination."""
+        with self.subnet(cidr='10.2.0.0/24') as subnet:
+            with self.router() as router:
+                with self.vpnservice(subnet=subnet,
+                                     router=router
+                                     ) as vpnservice:
+                    with contextlib.nested(
+                        self.ipsec_site_connection(
+                            name='connection1', vpnservice=vpnservice
+                        ),
+                        self.ipsec_site_connection(
+                            name='connection2', vpnservice=vpnservice
+                        ),
+                        self.ipsec_site_connection(
+                            name='connection3', vpnservice=vpnservice
+                        )
+                    ) as(ipsec_site_connection1,
+                         ipsec_site_connection2,
+                         ipsec_site_connection3):
+                        self._test_list_with_pagination_reverse(
+                            'ipsec-site-connection',
+                            (ipsec_site_connection1,
+                             ipsec_site_connection2,
+                             ipsec_site_connection3),
+                            ('name', 'asc'), 2, 2
+                        )
+
+    def test_create_vpn(self):
+        """Test case to create a vpn."""
+        vpns_name = "vpnservice1"
+        ike_name = "ikepolicy1"
+        ipsec_name = "ipsecpolicy1"
+        name1 = "ipsec_site_connection1"
+        with contextlib.nested(
+            self.ikepolicy(name=ike_name),
+            self.ipsecpolicy(name=ipsec_name),
+            self.vpnservice(name=vpns_name)) as (
+                ikepolicy, ipsecpolicy, vpnservice):
+            vpnservice_id = vpnservice['vpnservice']['id']
+            ikepolicy_id = ikepolicy['ikepolicy']['id']
+            ipsecpolicy_id = ipsecpolicy['ipsecpolicy']['id']
+            with self.ipsec_site_connection(
+                self.fmt,
+                name1,
+                '192.168.1.10',
+                '192.168.1.10',
+                ['192.168.2.0/24',
+                '192.168.3.0/24'],
+                1500,
+                'abcdef',
+                'bi-directional',
+                'hold',
+                30,
+                120,
+                vpnservice,
+                ikepolicy,
+                ipsecpolicy,
+                True
+            ) as vpnconn1:
+
+                vpnservice_req = self.new_show_request(
+                    'vpnservices',
+                    vpnservice_id,
+                    fmt=self.fmt)
+                vpnservice_updated = self.deserialize(
+                    self.fmt,
+                    vpnservice_req.get_response(self.ext_api)
+                )
+                self.assertEqual(
+                    vpnservice_updated['vpnservice']['id'],
+                    vpnconn1['ipsec_site_connection']['vpnservice_id']
+                )
+                ikepolicy_req = self.new_show_request('ikepolicies',
+                                                      ikepolicy_id,
+                                                      fmt=self.fmt)
+                ikepolicy_res = self.deserialize(
+                    self.fmt,
+                    ikepolicy_req.get_response(self.ext_api)
+                )
+                self.assertEqual(
+                    ikepolicy_res['ikepolicy']['id'],
+                    vpnconn1['ipsec_site_connection']['ikepolicy_id'])
+                ipsecpolicy_req = self.new_show_request(
+                    'ipsecpolicies',
+                    ipsecpolicy_id,
+                    fmt=self.fmt)
+                ipsecpolicy_res = self.deserialize(
+                    self.fmt,
+                    ipsecpolicy_req.get_response(self.ext_api)
+                )
+                self.assertEqual(
+                    ipsecpolicy_res['ipsecpolicy']['id'],
+                    vpnconn1['ipsec_site_connection']['ipsecpolicy_id']
+                )
+
+    def test_delete_ikepolicy_inuse(self):
+        """Test case to delete an ikepolicy, that is in use."""
+        vpns_name = "vpnservice1"
+        ike_name = "ikepolicy1"
+        ipsec_name = "ipsecpolicy1"
+        name1 = "ipsec_site_connection1"
+        with self.ikepolicy(name=ike_name) as ikepolicy:
+            with self.ipsecpolicy(name=ipsec_name) as ipsecpolicy:
+                with self.vpnservice(name=vpns_name) as vpnservice:
+                    with self.ipsec_site_connection(
+                        self.fmt,
+                        name1,
+                        '192.168.1.10',
+                        '192.168.1.10',
+                        ['192.168.2.0/24',
+                         '192.168.3.0/24'],
+                        1500,
+                        'abcdef',
+                        'bi-directional',
+                        'hold',
+                        30,
+                        120,
+                        vpnservice,
+                        ikepolicy,
+                        ipsecpolicy,
+                        True
+                    ):
+                        delete_req = self.new_delete_request(
+                            'ikepolicies',
+                            ikepolicy['ikepolicy']['id']
+                        )
+                        delete_res = delete_req.get_response(self.ext_api)
+                        self.assertEqual(409, delete_res.status_int)
+
+    def test_delete_ipsecpolicy_inuse(self):
+        """Test case to delete an ipsecpolicy, that is in use."""
+        vpns_name = "vpnservice1"
+        ike_name = "ikepolicy1"
+        ipsec_name = "ipsecpolicy1"
+        name1 = "ipsec_site_connection1"
+        with self.ikepolicy(name=ike_name) as ikepolicy:
+            with self.ipsecpolicy(name=ipsec_name) as ipsecpolicy:
+                with self.vpnservice(name=vpns_name) as vpnservice:
+                    with self.ipsec_site_connection(
+                        self.fmt,
+                        name1,
+                        '192.168.1.10',
+                        '192.168.1.10',
+                        ['192.168.2.0/24',
+                        '192.168.3.0/24'],
+                        1500,
+                        'abcdef',
+                        'bi-directional',
+                        'hold',
+                        30,
+                        120,
+                        vpnservice,
+                        ikepolicy,
+                        ipsecpolicy,
+                        True
+                    ):
+
+                        delete_req = self.new_delete_request(
+                            'ipsecpolicies',
+                            ipsecpolicy['ipsecpolicy']['id']
+                        )
+                        delete_res = delete_req.get_response(self.ext_api)
+                        self.assertEqual(409, delete_res.status_int)
+
+
+class TestVpnaasXML(TestVpnaas):
+    fmt = 'xml'
diff --git a/neutron/tests/unit/services/vpn/__init__.py b/neutron/tests/unit/services/vpn/__init__.py
new file mode 100644 (file)
index 0000000..b936bbc
--- /dev/null
@@ -0,0 +1,17 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+# @author: Swaminathan Vasudevan, Hewlett-Packard.
diff --git a/neutron/tests/unit/services/vpn/test_vpnaas_extension.py b/neutron/tests/unit/services/vpn/test_vpnaas_extension.py
new file mode 100644 (file)
index 0000000..d1f8954
--- /dev/null
@@ -0,0 +1,592 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#    (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
+#    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.
+#
+# @author: Swaminathan Vasudevan, Hewlett-Packard.
+
+import copy
+
+import mock
+from oslo.config import cfg
+from webob import exc
+import webtest
+
+from neutron.api import extensions
+from neutron.api.v2 import attributes
+from neutron.common import config
+from neutron.extensions import vpnaas
+from neutron import manager
+from neutron.openstack.common import uuidutils
+from neutron.plugins.common import constants
+from neutron.tests.unit import test_api_v2
+from neutron.tests.unit import test_extensions
+from neutron.tests.unit import testlib_api
+
+
+_uuid = uuidutils.generate_uuid
+_get_path = test_api_v2._get_path
+
+
+class VpnaasTestExtensionManager(object):
+
+    def get_resources(self):
+        # Add the resources to the global attribute map
+        # This is done here as the setup process won't
+        # initialize the main API router which extends
+        # the global attribute map
+        attributes.RESOURCE_ATTRIBUTE_MAP.update(
+            vpnaas.RESOURCE_ATTRIBUTE_MAP)
+        return vpnaas.Vpnaas.get_resources()
+
+    def get_actions(self):
+        return []
+
+    def get_request_extensions(self):
+        return []
+
+
+class VpnaasExtensionTestCase(testlib_api.WebTestCase):
+    fmt = 'json'
+
+    def setUp(self):
+        super(VpnaasExtensionTestCase, self).setUp()
+        plugin = 'neutron.extensions.vpnaas.VPNPluginBase'
+        # Ensure 'stale' patched copies of the plugin are never returned
+        manager.NeutronManager._instance = None
+
+        # Ensure existing ExtensionManager is not used
+        extensions.PluginAwareExtensionManager._instance = None
+
+        # Create the default configurations
+        args = ['--config-file', test_api_v2.etcdir('neutron.conf.test')]
+        config.parse(args)
+
+        #just stubbing core plugin with LoadBalancer plugin
+        cfg.CONF.set_override('core_plugin', plugin)
+        cfg.CONF.set_override('service_plugins', [plugin])
+
+        self._plugin_patcher = mock.patch(plugin, autospec=True)
+        self.plugin = self._plugin_patcher.start()
+        instance = self.plugin.return_value
+        instance.get_plugin_type.return_value = constants.VPN
+
+        ext_mgr = VpnaasTestExtensionManager()
+        self.ext_mdw = test_extensions.setup_extensions_middleware(ext_mgr)
+        self.api = webtest.TestApp(self.ext_mdw)
+        super(VpnaasExtensionTestCase, self).setUp()
+
+    def tearDown(self):
+        self._plugin_patcher.stop()
+        self.api = None
+        self.plugin = None
+        cfg.CONF.reset()
+        super(VpnaasExtensionTestCase, self).tearDown()
+
+    def test_ikepolicy_create(self):
+        """Test case to create an ikepolicy."""
+        ikepolicy_id = _uuid()
+        data = {'ikepolicy': {'name': 'ikepolicy1',
+                              'description': 'myikepolicy1',
+                              'auth_algorithm': 'sha1',
+                              'encryption_algorithm': 'aes-128',
+                              'phase1_negotiation_mode': 'main',
+                              'lifetime': {
+                                  'units': 'seconds',
+                                  'value': 3600},
+                              'ike_version': 'v1',
+                              'pfs': 'group5',
+                              'tenant_id': _uuid()}}
+
+        return_value = copy.copy(data['ikepolicy'])
+        return_value.update({'id': ikepolicy_id})
+
+        instance = self.plugin.return_value
+        instance.create_ikepolicy.return_value = return_value
+        res = self.api.post(_get_path('vpn/ikepolicies', fmt=self.fmt),
+                            self.serialize(data),
+                            content_type='application/%s' % self.fmt)
+        instance.create_ikepolicy.assert_called_with(mock.ANY,
+                                                     ikepolicy=data)
+        self.assertEqual(res.status_int, exc.HTTPCreated.code)
+        res = self.deserialize(res)
+        self.assertIn('ikepolicy', res)
+        self.assertEqual(res['ikepolicy'], return_value)
+
+    def test_ikepolicy_list(self):
+        """Test case to list all ikepolicies."""
+        ikepolicy_id = _uuid()
+        return_value = [{'name': 'ikepolicy1',
+                         'auth_algorithm': 'sha1',
+                         'encryption_algorithm': 'aes-128',
+                         'pfs': 'group5',
+                         'ike_version': 'v1',
+                         'id': ikepolicy_id}]
+
+        instance = self.plugin.return_value
+        instance.get_ikepolicies.return_value = return_value
+
+        res = self.api.get(_get_path('vpn/ikepolicies', fmt=self.fmt))
+
+        instance.get_ikepolicies.assert_called_with(mock.ANY,
+                                                    fields=mock.ANY,
+                                                    filters=mock.ANY)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+
+    def test_ikepolicy_update(self):
+        """Test case to update an ikepolicy."""
+        ikepolicy_id = _uuid()
+        update_data = {'ikepolicy': {'name': 'ikepolicy1',
+                                     'encryption_algorithm': 'aes-256'}}
+        return_value = {'name': 'ikepolicy1',
+                        'auth_algorithm': 'sha1',
+                        'encryption_algorithm': 'aes-256',
+                        'phase1_negotiation_mode': 'main',
+                        'lifetime': {
+                            'units': 'seconds',
+                            'value': 3600},
+                        'ike_version': 'v1',
+                        'pfs': 'group5',
+                        'tenant_id': _uuid(),
+                        'id': ikepolicy_id}
+
+        instance = self.plugin.return_value
+        instance.update_ikepolicy.return_value = return_value
+
+        res = self.api.put(_get_path('vpn/ikepolicies', id=ikepolicy_id,
+                                     fmt=self.fmt),
+                           self.serialize(update_data))
+
+        instance.update_ikepolicy.assert_called_with(mock.ANY, ikepolicy_id,
+                                                     ikepolicy=update_data)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('ikepolicy', res)
+        self.assertEqual(res['ikepolicy'], return_value)
+
+    def test_ikepolicy_get(self):
+        """Test case to get or show an ikepolicy."""
+        ikepolicy_id = _uuid()
+        return_value = {'name': 'ikepolicy1',
+                        'auth_algorithm': 'sha1',
+                        'encryption_algorithm': 'aes-128',
+                        'phase1_negotiation_mode': 'main',
+                        'lifetime': {
+                            'units': 'seconds',
+                            'value': 3600},
+                        'ike_version': 'v1',
+                        'pfs': 'group5',
+                        'tenant_id': _uuid(),
+                        'id': ikepolicy_id}
+
+        instance = self.plugin.return_value
+        instance.get_ikepolicy.return_value = return_value
+
+        res = self.api.get(_get_path('vpn/ikepolicies', id=ikepolicy_id,
+                                     fmt=self.fmt))
+
+        instance.get_ikepolicy.assert_called_with(mock.ANY,
+                                                  ikepolicy_id,
+                                                  fields=mock.ANY)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('ikepolicy', res)
+        self.assertEqual(res['ikepolicy'], return_value)
+
+    def test_ikepolicy_delete(self):
+        """Test case to delete an ikepolicy."""
+        self._test_entity_delete('ikepolicy')
+
+    def test_ipsecpolicy_create(self):
+        """Test case to create an ipsecpolicy."""
+        ipsecpolicy_id = _uuid()
+        data = {'ipsecpolicy': {'name': 'ipsecpolicy1',
+                                'description': 'myipsecpolicy1',
+                                'auth_algorithm': 'sha1',
+                                'encryption_algorithm': 'aes-128',
+                                'encapsulation_mode': 'tunnel',
+                                'lifetime': {
+                                    'units': 'seconds',
+                                    'value': 3600},
+                                'transform_protocol': 'esp',
+                                'pfs': 'group5',
+                                'tenant_id': _uuid()}}
+        return_value = copy.copy(data['ipsecpolicy'])
+        return_value.update({'id': ipsecpolicy_id})
+
+        instance = self.plugin.return_value
+        instance.create_ipsecpolicy.return_value = return_value
+        res = self.api.post(_get_path('vpn/ipsecpolicies', fmt=self.fmt),
+                            self.serialize(data),
+                            content_type='application/%s' % self.fmt)
+        instance.create_ipsecpolicy.assert_called_with(mock.ANY,
+                                                       ipsecpolicy=data)
+        self.assertEqual(res.status_int, exc.HTTPCreated.code)
+        res = self.deserialize(res)
+        self.assertIn('ipsecpolicy', res)
+        self.assertEqual(res['ipsecpolicy'], return_value)
+
+    def test_ipsecpolicy_list(self):
+        """Test case to list an ipsecpolicy."""
+        ipsecpolicy_id = _uuid()
+        return_value = [{'name': 'ipsecpolicy1',
+                         'auth_algorithm': 'sha1',
+                         'encryption_algorithm': 'aes-128',
+                         'pfs': 'group5',
+                         'id': ipsecpolicy_id}]
+
+        instance = self.plugin.return_value
+        instance.get_ipsecpolicies.return_value = return_value
+
+        res = self.api.get(_get_path('vpn/ipsecpolicies', fmt=self.fmt))
+
+        instance.get_ipsecpolicies.assert_called_with(mock.ANY,
+                                                      fields=mock.ANY,
+                                                      filters=mock.ANY)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+
+    def test_ipsecpolicy_update(self):
+        """Test case to update an ipsecpolicy."""
+        ipsecpolicy_id = _uuid()
+        update_data = {'ipsecpolicy': {'name': 'ipsecpolicy1',
+                                       'encryption_algorithm': 'aes-256'}}
+        return_value = {'name': 'ipsecpolicy1',
+                        'auth_algorithm': 'sha1',
+                        'encryption_algorithm': 'aes-128',
+                        'encapsulation_mode': 'tunnel',
+                        'lifetime': {
+                            'units': 'seconds',
+                            'value': 3600},
+                        'transform_protocol': 'esp',
+                        'pfs': 'group5',
+                        'tenant_id': _uuid(),
+                        'id': ipsecpolicy_id}
+
+        instance = self.plugin.return_value
+        instance.update_ipsecpolicy.return_value = return_value
+
+        res = self.api.put(_get_path('vpn/ipsecpolicies',
+                                     id=ipsecpolicy_id,
+                                     fmt=self.fmt),
+                           self.serialize(update_data))
+
+        instance.update_ipsecpolicy.assert_called_with(mock.ANY,
+                                                       ipsecpolicy_id,
+                                                       ipsecpolicy=update_data)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('ipsecpolicy', res)
+        self.assertEqual(res['ipsecpolicy'], return_value)
+
+    def test_ipsecpolicy_get(self):
+        """Test case to get or show an ipsecpolicy."""
+        ipsecpolicy_id = _uuid()
+        return_value = {'name': 'ipsecpolicy1',
+                        'auth_algorithm': 'sha1',
+                        'encryption_algorithm': 'aes-128',
+                        'encapsulation_mode': 'tunnel',
+                        'lifetime': {
+                            'units': 'seconds',
+                            'value': 3600},
+                        'transform_protocol': 'esp',
+                        'pfs': 'group5',
+                        'tenant_id': _uuid(),
+                        'id': ipsecpolicy_id}
+
+        instance = self.plugin.return_value
+        instance.get_ipsecpolicy.return_value = return_value
+
+        res = self.api.get(_get_path('vpn/ipsecpolicies',
+                                     id=ipsecpolicy_id,
+                                     fmt=self.fmt))
+
+        instance.get_ipsecpolicy.assert_called_with(mock.ANY,
+                                                    ipsecpolicy_id,
+                                                    fields=mock.ANY)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('ipsecpolicy', res)
+        self.assertEqual(res['ipsecpolicy'], return_value)
+
+    def test_ipsecpolicy_delete(self):
+        """Test case to delete an ipsecpolicy."""
+        self._test_entity_delete('ipsecpolicy')
+
+    def test_vpnservice_create(self):
+        """Test case to create a vpnservice."""
+        vpnservice_id = _uuid()
+        data = {'vpnservice': {'name': 'vpnservice1',
+                               'description': 'descr_vpn1',
+                               'subnet_id': _uuid(),
+                               'router_id': _uuid(),
+                               'admin_state_up': True,
+                               'tenant_id': _uuid()}}
+        return_value = copy.copy(data['vpnservice'])
+        return_value.update({'status': "ACTIVE", 'id': vpnservice_id})
+
+        instance = self.plugin.return_value
+        instance.create_vpnservice.return_value = return_value
+        res = self.api.post(_get_path('vpn/vpnservices', fmt=self.fmt),
+                            self.serialize(data),
+                            content_type='application/%s' % self.fmt)
+        instance.create_vpnservice.assert_called_with(mock.ANY,
+                                                      vpnservice=data)
+        self.assertEqual(res.status_int, exc.HTTPCreated.code)
+        res = self.deserialize(res)
+        self.assertIn('vpnservice', res)
+        self.assertEqual(res['vpnservice'], return_value)
+
+    def test_vpnservice_list(self):
+        """Test case to list all vpnservices."""
+        vpnservice_id = _uuid()
+        return_value = [{'name': 'vpnservice1',
+                         'tenant_id': _uuid(),
+                         'status': 'ACTIVE',
+                         'id': vpnservice_id}]
+
+        instance = self.plugin.return_value
+        instance.get_vpnservice.return_value = return_value
+
+        res = self.api.get(_get_path('vpn/vpnservices', fmt=self.fmt))
+
+        instance.get_vpnservices.assert_called_with(mock.ANY,
+                                                    fields=mock.ANY,
+                                                    filters=mock.ANY)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+
+    def test_vpnservice_update(self):
+        """Test case to update a vpnservice."""
+        vpnservice_id = _uuid()
+        update_data = {'vpnservice': {'admin_state_up': False}}
+        return_value = {'name': 'vpnservice1',
+                        'admin_state_up': False,
+                        'subnet_id': _uuid(),
+                        'router_id': _uuid(),
+                        'tenant_id': _uuid(),
+                        'status': "ACTIVE",
+                        'id': vpnservice_id}
+
+        instance = self.plugin.return_value
+        instance.update_vpnservice.return_value = return_value
+
+        res = self.api.put(_get_path('vpn/vpnservices',
+                                     id=vpnservice_id,
+                                     fmt=self.fmt),
+                           self.serialize(update_data))
+
+        instance.update_vpnservice.assert_called_with(mock.ANY,
+                                                      vpnservice_id,
+                                                      vpnservice=update_data)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('vpnservice', res)
+        self.assertEqual(res['vpnservice'], return_value)
+
+    def test_vpnservice_get(self):
+        """Test case to get or show a vpnservice."""
+        vpnservice_id = _uuid()
+        return_value = {'name': 'vpnservice1',
+                        'admin_state_up': True,
+                        'subnet_id': _uuid(),
+                        'router_id': _uuid(),
+                        'tenant_id': _uuid(),
+                        'status': "ACTIVE",
+                        'id': vpnservice_id}
+
+        instance = self.plugin.return_value
+        instance.get_vpnservice.return_value = return_value
+
+        res = self.api.get(_get_path('vpn/vpnservices',
+                                     id=vpnservice_id,
+                                     fmt=self.fmt))
+
+        instance.get_vpnservice.assert_called_with(mock.ANY,
+                                                   vpnservice_id,
+                                                   fields=mock.ANY)
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('vpnservice', res)
+        self.assertEqual(res['vpnservice'], return_value)
+
+    def _test_entity_delete(self, entity):
+        """does the entity deletion based on naming convention."""
+        entity_id = _uuid()
+        path_map = {'ipsecpolicy': 'vpn/ipsecpolicies',
+                    'ikepolicy': 'vpn/ikepolicies',
+                    'ipsec_site_connection': 'vpn/ipsec-site-connections'}
+        path = path_map.get(entity, 'vpn/' + entity + 's')
+        res = self.api.delete(_get_path(path,
+                                        id=entity_id,
+                                        fmt=self.fmt))
+        delete_entity = getattr(self.plugin.return_value, "delete_" + entity)
+        delete_entity.assert_called_with(mock.ANY, entity_id)
+        self.assertEqual(res.status_int, exc.HTTPNoContent.code)
+
+    def test_vpnservice_delete(self):
+        """Test case to delete a vpnservice."""
+        self._test_entity_delete('vpnservice')
+
+    def test_ipsec_site_connection_create(self):
+        """Test case to create a ipsec_site_connection."""
+        ipsecsite_con_id = _uuid()
+        ikepolicy_id = _uuid()
+        ipsecpolicy_id = _uuid()
+        data = {
+            'ipsec_site_connection': {'name': 'connection1',
+                                      'description': 'Remote-connection1',
+                                      'peer_address': '192.168.1.10',
+                                      'peer_id': '192.168.1.10',
+                                      'peer_cidrs': ['192.168.2.0/24',
+                                                     '192.168.3.0/24'],
+                                      'mtu': 1500,
+                                      'psk': 'abcd',
+                                      'initiator': 'bi-directional',
+                                      'dpd': {
+                                          'action': 'hold',
+                                          'interval': 30,
+                                          'timeout': 120},
+                                      'ikepolicy_id': ikepolicy_id,
+                                      'ipsecpolicy_id': ipsecpolicy_id,
+                                      'vpnservice_id': _uuid(),
+                                      'admin_state_up': True,
+                                      'tenant_id': _uuid()}
+        }
+        return_value = copy.copy(data['ipsec_site_connection'])
+        return_value.update({'status': "ACTIVE", 'id': ipsecsite_con_id})
+
+        instance = self.plugin.return_value
+        instance.create_ipsec_site_connection.return_value = return_value
+        res = self.api.post(_get_path('vpn/ipsec-site-connections',
+                                      fmt=self.fmt),
+                            self.serialize(data),
+                            content_type='application/%s' % self.fmt)
+        instance.create_ipsec_site_connection.assert_called_with(
+            mock.ANY, ipsec_site_connection=data
+        )
+        self.assertEqual(res.status_int, exc.HTTPCreated.code)
+        res = self.deserialize(res)
+        self.assertIn('ipsec_site_connection', res)
+        self.assertEqual(res['ipsec_site_connection'], return_value)
+
+    def test_ipsec_site_connection_list(self):
+        """Test case to list all ipsec_site_connections."""
+        ipsecsite_con_id = _uuid()
+        return_value = [{'name': 'connection1',
+                         'peer_address': '192.168.1.10',
+                         'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'],
+                         'route_mode': 'static',
+                         'auth_mode': 'psk',
+                         'tenant_id': _uuid(),
+                         'status': 'ACTIVE',
+                         'id': ipsecsite_con_id}]
+
+        instance = self.plugin.return_value
+        instance.get_ipsec_site_connections.return_value = return_value
+
+        res = self.api.get(
+            _get_path('vpn/ipsec-site-connections', fmt=self.fmt))
+
+        instance.get_ipsec_site_connections.assert_called_with(
+            mock.ANY, fields=mock.ANY, filters=mock.ANY
+        )
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+
+    def test_ipsec_site_connection_update(self):
+        """Test case to update a ipsec_site_connection."""
+        ipsecsite_con_id = _uuid()
+        update_data = {'ipsec_site_connection': {'admin_state_up': False}}
+        return_value = {'name': 'connection1',
+                        'description': 'Remote-connection1',
+                        'peer_address': '192.168.1.10',
+                        'peer_id': '192.168.1.10',
+                        'peer_cidrs': ['192.168.2.0/24', '192.168.3.0/24'],
+                        'mtu': 1500,
+                        'psk': 'abcd',
+                        'initiator': 'bi-directional',
+                        'dpd': {
+                            'action': 'hold',
+                            'interval': 30,
+                            'timeout': 120},
+                        'ikepolicy_id': _uuid(),
+                        'ipsecpolicy_id': _uuid(),
+                        'vpnservice_id': _uuid(),
+                        'admin_state_up': False,
+                        'tenant_id': _uuid(),
+                        'status': 'ACTIVE',
+                        'id': ipsecsite_con_id}
+
+        instance = self.plugin.return_value
+        instance.update_ipsec_site_connection.return_value = return_value
+
+        res = self.api.put(_get_path('vpn/ipsec-site-connections',
+                                     id=ipsecsite_con_id,
+                                     fmt=self.fmt),
+                           self.serialize(update_data))
+
+        instance.update_ipsec_site_connection.assert_called_with(
+            mock.ANY, ipsecsite_con_id, ipsec_site_connection=update_data
+        )
+
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('ipsec_site_connection', res)
+        self.assertEqual(res['ipsec_site_connection'], return_value)
+
+    def test_ipsec_site_connection_get(self):
+        """Test case to get or show a ipsec_site_connection."""
+        ipsecsite_con_id = _uuid()
+        return_value = {'name': 'connection1',
+                        'description': 'Remote-connection1',
+                        'peer_address': '192.168.1.10',
+                        'peer_id': '192.168.1.10',
+                        'peer_cidrs': ['192.168.2.0/24',
+                                       '192.168.3.0/24'],
+                        'mtu': 1500,
+                        'psk': 'abcd',
+                        'initiator': 'bi-directional',
+                        'dpd': {
+                            'action': 'hold',
+                            'interval': 30,
+                            'timeout': 120},
+                        'ikepolicy_id': _uuid(),
+                        'ipsecpolicy_id': _uuid(),
+                        'vpnservice_id': _uuid(),
+                        'admin_state_up': True,
+                        'tenant_id': _uuid(),
+                        'status': 'ACTIVE',
+                        'id': ipsecsite_con_id}
+
+        instance = self.plugin.return_value
+        instance.get_ipsec_site_connection.return_value = return_value
+
+        res = self.api.get(_get_path('vpn/ipsec-site-connections',
+                                     id=ipsecsite_con_id,
+                                     fmt=self.fmt))
+
+        instance.get_ipsec_site_connection.assert_called_with(
+            mock.ANY, ipsecsite_con_id, fields=mock.ANY
+        )
+        self.assertEqual(res.status_int, exc.HTTPOk.code)
+        res = self.deserialize(res)
+        self.assertIn('ipsec_site_connection', res)
+        self.assertEqual(res['ipsec_site_connection'], return_value)
+
+    def test_ipsec_site_connection_delete(self):
+        """Test case to delete a ipsec_site_connection."""
+        self._test_entity_delete('ipsec_site_connection')
+
+
+class VpnaasExtensionTestCaseXML(VpnaasExtensionTestCase):
+    fmt = 'xml'
index 668ce5388cd9252c05f08ca88d4bb5a5b780fdd6..68261cf783f15b6dbf732941615a33c46d289de5 100644 (file)
@@ -568,84 +568,96 @@ class NeutronDbPluginV2TestCase(testlib_api.WebTestCase):
                 if not no_delete:
                     self._delete('ports', port['port']['id'])
 
-    def _test_list_with_sort(self, collection, items, sorts, query_params=''):
+    def _test_list_with_sort(self, resource,
+                             items, sorts, resources=None, query_params=''):
         query_str = query_params
         for key, direction in sorts:
             query_str = query_str + "&sort_key=%s&sort_dir=%s" % (key,
                                                                   direction)
-        req = self.new_list_request('%ss' % collection,
+        if not resources:
+            resources = '%ss' % resource
+        req = self.new_list_request(resources,
                                     params=query_str)
-        api = self._api_for_resource('%ss' % collection)
+        api = self._api_for_resource(resources)
         res = self.deserialize(self.fmt, req.get_response(api))
-        collection = collection.replace('-', '_')
-        expected_res = [item[collection]['id'] for item in items]
-        self.assertEqual(sorted([n['id'] for n in res["%ss" % collection]]),
+        resource = resource.replace('-', '_')
+        resources = resources.replace('-', '_')
+        expected_res = [item[resource]['id'] for item in items]
+        self.assertEqual(sorted([n['id'] for n in res[resources]]),
                          sorted(expected_res))
 
-    def _test_list_with_pagination(self, collection, items, sort,
-                                   limit, expected_page_num, query_params='',
+    def _test_list_with_pagination(self, resource, items, sort,
+                                   limit, expected_page_num,
+                                   resources=None,
+                                   query_params='',
                                    verify_key='id'):
+        if not resources:
+            resources = '%ss' % resource
         query_str = query_params + '&' if query_params else ''
         query_str = query_str + ("limit=%s&sort_key=%s&"
                                  "sort_dir=%s") % (limit, sort[0], sort[1])
-        req = self.new_list_request("%ss" % collection, params=query_str)
+        req = self.new_list_request(resources, params=query_str)
         items_res = []
         page_num = 0
-        api = self._api_for_resource('%ss' % collection)
-        collection = collection.replace('-', '_')
+        api = self._api_for_resource(resources)
+        resource = resource.replace('-', '_')
+        resources = resources.replace('-', '_')
         while req:
             page_num = page_num + 1
             res = self.deserialize(self.fmt, req.get_response(api))
-            self.assertThat(len(res["%ss" % collection]),
+            self.assertThat(len(res[resources]),
                             matchers.LessThan(limit + 1))
-            items_res = items_res + res["%ss" % collection]
+            items_res = items_res + res[resources]
             req = None
-            if '%ss_links' % collection in res:
-                for link in res['%ss_links' % collection]:
+            if '%s_links' % resources in res:
+                for link in res['%s_links' % resources]:
                     if link['rel'] == 'next':
                         content_type = 'application/%s' % self.fmt
                         req = testlib_api.create_request(link['href'],
                                                          '', content_type)
-                        self.assertEqual(len(res["%ss" % collection]),
+                        self.assertEqual(len(res[resources]),
                                          limit)
         self.assertEqual(page_num, expected_page_num)
         self.assertEqual(sorted([n[verify_key] for n in items_res]),
-                         sorted([item[collection][verify_key]
+                         sorted([item[resource][verify_key]
                                 for item in items]))
 
-    def _test_list_with_pagination_reverse(self, collection, items, sort,
+    def _test_list_with_pagination_reverse(self, resource, items, sort,
                                            limit, expected_page_num,
+                                           resources=None,
                                            query_params=''):
-        resources = '%ss' % collection
-        collection = collection.replace('-', '_')
+        if not resources:
+            resources = '%ss' % resource
+        resource = resource.replace('-', '_')
         api = self._api_for_resource(resources)
-        marker = items[-1][collection]['id']
+        marker = items[-1][resource]['id']
         query_str = query_params + '&' if query_params else ''
         query_str = query_str + ("limit=%s&page_reverse=True&"
                                  "sort_key=%s&sort_dir=%s&"
                                  "marker=%s") % (limit, sort[0], sort[1],
                                                  marker)
         req = self.new_list_request(resources, params=query_str)
-        item_res = [items[-1][collection]]
+        item_res = [items[-1][resource]]
         page_num = 0
+        resources = resources.replace('-', '_')
         while req:
             page_num = page_num + 1
             res = self.deserialize(self.fmt, req.get_response(api))
-            self.assertThat(len(res["%ss" % collection]),
+            self.assertThat(len(res[resources]),
                             matchers.LessThan(limit + 1))
-            res["%ss" % collection].reverse()
-            item_res = item_res + res["%ss" % collection]
+            res[resources].reverse()
+            item_res = item_res + res[resources]
             req = None
-            if '%ss_links' % collection in res:
-                for link in res['%ss_links' % collection]:
+            if '%s_links' % resources in res:
+                for link in res['%s_links' % resources]:
                     if link['rel'] == 'previous':
                         content_type = 'application/%s' % self.fmt
                         req = testlib_api.create_request(link['href'],
                                                          '', content_type)
-                        self.assertEqual(len(res["%ss" % collection]),
+                        self.assertEqual(len(res[resources]),
                                          limit)
         self.assertEqual(page_num, expected_page_num)
-        expected_res = [item[collection]['id'] for item in items]
+        expected_res = [item[resource]['id'] for item in items]
         expected_res.reverse()
         self.assertEqual(sorted([n['id'] for n in item_res]),
                          sorted(expected_res))