bd3f5f7e7b
The following methods are now called from inside a reader context: * ``VPNPluginDb.get_ipsec_site_connection`` * ``VPNPluginDb.get_ipsec_site_connections`` This avoids logged exceptions "ORM session: SQL execution without transaction in progress" Related-Bug: #2080072 Change-Id: I6555419c582b3c8654b13731d0b7c7fd61942957
824 lines
38 KiB
Python
824 lines
38 KiB
Python
# (c) Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
# (c) Copyright 2015 Cisco Systems Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
from neutron.db import models_v2
|
|
from neutron_lib.callbacks import events
|
|
from neutron_lib.callbacks import registry
|
|
from neutron_lib.callbacks import resources
|
|
from neutron_lib import constants as lib_constants
|
|
from neutron_lib.db import api as db_api
|
|
from neutron_lib.db import model_query
|
|
from neutron_lib.db import utils as db_utils
|
|
from neutron_lib.exceptions import l3 as l3_exception
|
|
from neutron_lib.exceptions import vpn as vpn_exception
|
|
from neutron_lib.plugins import constants as p_constants
|
|
from neutron_lib.plugins import directory
|
|
from neutron_lib.plugins import utils
|
|
from oslo_log import log as logging
|
|
from oslo_utils import excutils
|
|
from oslo_utils import uuidutils
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.orm import exc
|
|
|
|
|
|
from neutron_vpnaas.db.vpn import vpn_models
|
|
from neutron_vpnaas.db.vpn import vpn_validator
|
|
from neutron_vpnaas.extensions import vpn_endpoint_groups
|
|
from neutron_vpnaas.extensions import vpnaas
|
|
from neutron_vpnaas.services.vpn.common import constants as v_constants
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class VPNPluginDb(vpnaas.VPNPluginBase,
|
|
vpn_endpoint_groups.VPNEndpointGroupsPluginBase):
|
|
"""VPN plugin database class using SQLAlchemy models."""
|
|
|
|
def _get_validator(self):
|
|
"""Obtain validator to use for attribute validation.
|
|
|
|
Subclasses may override this with a different validator, as needed.
|
|
Note: some UTs will directly create a VPNPluginDb object and then
|
|
call its methods, instead of creating a VPNDriverPlugin, which
|
|
will have a service driver associated that will provide a
|
|
validator object. As a result, we use the reference validator here.
|
|
"""
|
|
return vpn_validator.VpnReferenceValidator()
|
|
|
|
def update_status(self, context, model, v_id, status):
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
v_db = self._get_resource(context, model, v_id)
|
|
v_db.update({'status': status})
|
|
|
|
def _get_resource(self, context, model, v_id):
|
|
try:
|
|
r = model_query.get_by_id(context, model, v_id)
|
|
except exc.NoResultFound:
|
|
with excutils.save_and_reraise_exception(reraise=False) as ctx:
|
|
if issubclass(model, vpn_models.IPsecSiteConnection):
|
|
raise vpn_exception.IPsecSiteConnectionNotFound(
|
|
ipsec_site_conn_id=v_id
|
|
)
|
|
if issubclass(model, vpn_models.IKEPolicy):
|
|
raise vpn_exception.IKEPolicyNotFound(ikepolicy_id=v_id)
|
|
if issubclass(model, vpn_models.IPsecPolicy):
|
|
raise vpn_exception.IPsecPolicyNotFound(
|
|
ipsecpolicy_id=v_id)
|
|
if issubclass(model, vpn_models.VPNService):
|
|
raise vpn_exception.VPNServiceNotFound(vpnservice_id=v_id)
|
|
if issubclass(model, vpn_models.VPNEndpointGroup):
|
|
raise vpn_exception.VPNEndpointGroupNotFound(
|
|
endpoint_group_id=v_id)
|
|
ctx.reraise = True
|
|
return r
|
|
|
|
def assert_update_allowed(self, obj):
|
|
status = getattr(obj, 'status', None)
|
|
_id = getattr(obj, 'id', None)
|
|
if utils.in_pending_status(status):
|
|
raise vpn_exception.VPNStateInvalidToUpdate(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'],
|
|
'local_id': ipsec_site_conn['local_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']],
|
|
'local_ep_group_id': ipsec_site_conn['local_ep_group_id'],
|
|
'peer_ep_group_id': ipsec_site_conn['peer_ep_group_id'],
|
|
}
|
|
|
|
return db_utils.resource_fields(res, fields)
|
|
|
|
def get_endpoint_info(self, context, ipsec_sitecon):
|
|
"""Obtain all endpoint info, and store in connection for validation."""
|
|
ipsec_sitecon['local_epg_subnets'] = self.get_endpoint_group(
|
|
context, ipsec_sitecon['local_ep_group_id'])
|
|
ipsec_sitecon['peer_epg_cidrs'] = self.get_endpoint_group(
|
|
context, ipsec_sitecon['peer_ep_group_id'])
|
|
|
|
def validate_connection_info(self, context, validator, ipsec_sitecon,
|
|
vpnservice):
|
|
"""Collect info and validate connection.
|
|
|
|
If endpoint groups used (default), collect the group info and
|
|
do not specify the IP version (as it will come from endpoints).
|
|
Otherwise, get the IP version from the (legacy) subnet for
|
|
validation purposes.
|
|
|
|
NOTE: Once the deprecated subnet is removed, the caller can just
|
|
call get_endpoint_info() and validate_ipsec_site_connection().
|
|
"""
|
|
if ipsec_sitecon['local_ep_group_id']:
|
|
self.get_endpoint_info(context, ipsec_sitecon)
|
|
ip_version = None
|
|
else:
|
|
ip_version = vpnservice.subnet.ip_version
|
|
validator.validate_ipsec_site_connection(context, ipsec_sitecon,
|
|
ip_version, vpnservice)
|
|
|
|
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
|
ipsec_sitecon = ipsec_site_connection['ipsec_site_connection']
|
|
validator = self._get_validator()
|
|
validator.assign_sensible_ipsec_sitecon_defaults(ipsec_sitecon)
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
#Check permissions
|
|
vpnservice_id = ipsec_sitecon['vpnservice_id']
|
|
self._get_resource(context, vpn_models.VPNService, vpnservice_id)
|
|
self._get_resource(context, vpn_models.IKEPolicy,
|
|
ipsec_sitecon['ikepolicy_id'])
|
|
self._get_resource(context, vpn_models.IPsecPolicy,
|
|
ipsec_sitecon['ipsecpolicy_id'])
|
|
vpnservice = self._get_vpnservice(context, vpnservice_id)
|
|
validator.validate_ipsec_conn_optional_args(ipsec_sitecon,
|
|
vpnservice.subnet)
|
|
self.validate_connection_info(context, validator, ipsec_sitecon,
|
|
vpnservice)
|
|
validator.resolve_peer_address(ipsec_sitecon, vpnservice.router)
|
|
|
|
ipsec_site_conn_db = vpn_models.IPsecSiteConnection(
|
|
id=uuidutils.generate_uuid(),
|
|
tenant_id=ipsec_sitecon['tenant_id'],
|
|
name=ipsec_sitecon['name'],
|
|
description=ipsec_sitecon['description'],
|
|
peer_address=ipsec_sitecon['peer_address'],
|
|
peer_id=ipsec_sitecon['peer_id'],
|
|
local_id=ipsec_sitecon['local_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=lib_constants.PENDING_CREATE,
|
|
vpnservice_id=vpnservice_id,
|
|
ikepolicy_id=ipsec_sitecon['ikepolicy_id'],
|
|
ipsecpolicy_id=ipsec_sitecon['ipsecpolicy_id'],
|
|
local_ep_group_id=ipsec_sitecon['local_ep_group_id'],
|
|
peer_ep_group_id=ipsec_sitecon['peer_ep_group_id']
|
|
)
|
|
context.session.add(ipsec_site_conn_db)
|
|
for cidr in ipsec_sitecon['peer_cidrs']:
|
|
peer_cidr_db = vpn_models.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']
|
|
changed_peer_cidrs = False
|
|
validator = self._get_validator()
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
ipsec_site_conn_db = self._get_resource(
|
|
context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id)
|
|
vpnservice_id = ipsec_site_conn_db['vpnservice_id']
|
|
vpnservice = self._get_vpnservice(context, vpnservice_id)
|
|
|
|
validator.assign_sensible_ipsec_sitecon_defaults(
|
|
ipsec_sitecon, ipsec_site_conn_db)
|
|
validator.validate_ipsec_conn_optional_args(ipsec_sitecon,
|
|
vpnservice.subnet)
|
|
self.validate_connection_info(context, validator, ipsec_sitecon,
|
|
vpnservice)
|
|
if 'peer_address' in ipsec_sitecon:
|
|
validator.resolve_peer_address(ipsec_sitecon,
|
|
vpnservice.router)
|
|
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 = vpn_models.IPsecPeerCidr(
|
|
cidr=peer_cidr,
|
|
ipsec_site_connection_id=ipsec_site_conn_id)
|
|
context.session.add(pcidr)
|
|
# Note: Unconditionally remove peer_cidrs, as they will be set to
|
|
# previous, if unchanged (to be able to validate above).
|
|
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 db_api.CONTEXT_WRITER.using(context):
|
|
ipsec_site_conn_db = self._get_resource(
|
|
context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id)
|
|
context.session.delete(ipsec_site_conn_db)
|
|
|
|
def _get_ipsec_site_connection(
|
|
self, context, ipsec_site_conn_id):
|
|
return self._get_resource(
|
|
context, vpn_models.IPsecSiteConnection, ipsec_site_conn_id)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_ipsec_site_connection(self, context,
|
|
ipsec_site_conn_id, fields=None):
|
|
ipsec_site_conn_db = self._get_ipsec_site_connection(
|
|
context, ipsec_site_conn_id)
|
|
return self._make_ipsec_site_connection_dict(
|
|
ipsec_site_conn_db, fields)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_ipsec_site_connections(self, context, filters=None, fields=None):
|
|
return model_query.get_collection(
|
|
context, vpn_models.IPsecSiteConnection,
|
|
self._make_ipsec_site_connection_dict,
|
|
filters=filters, fields=fields)
|
|
|
|
def update_ipsec_site_conn_status(self, context, conn_id, new_status):
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
self._update_connection_status(context, conn_id, new_status, True)
|
|
|
|
def _update_connection_status(self, context, conn_id, new_status,
|
|
updated_pending):
|
|
"""Update the connection status, if changed.
|
|
|
|
If the connection is not in a pending state, unconditionally update
|
|
the status. Likewise, if in a pending state, and have an indication
|
|
that the status has changed, then update the database.
|
|
"""
|
|
try:
|
|
conn_db = self._get_ipsec_site_connection(context, conn_id)
|
|
except vpn_exception.IPsecSiteConnectionNotFound:
|
|
return
|
|
if not utils.in_pending_status(conn_db.status) or updated_pending:
|
|
conn_db.status = new_status
|
|
|
|
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 db_utils.resource_fields(res, fields)
|
|
|
|
def create_ikepolicy(self, context, ikepolicy):
|
|
ike = ikepolicy['ikepolicy']
|
|
validator = self._get_validator()
|
|
lifetime_info = ike['lifetime']
|
|
lifetime_units = lifetime_info.get('units', 'seconds')
|
|
lifetime_value = lifetime_info.get('value', 3600)
|
|
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
validator.validate_ike_policy(context, ike)
|
|
ike_db = vpn_models.IKEPolicy(
|
|
id=uuidutils.generate_uuid(),
|
|
tenant_id=ike['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']
|
|
validator = self._get_validator()
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
validator.validate_ike_policy(context, ike)
|
|
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
|
|
ikepolicy_id=ikepolicy_id).first():
|
|
raise vpn_exception.IKEPolicyInUse(ikepolicy_id=ikepolicy_id)
|
|
ike_db = self._get_resource(
|
|
context, vpn_models.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 db_api.CONTEXT_WRITER.using(context):
|
|
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
|
|
ikepolicy_id=ikepolicy_id).first():
|
|
raise vpn_exception.IKEPolicyInUse(ikepolicy_id=ikepolicy_id)
|
|
ike_db = self._get_resource(
|
|
context, vpn_models.IKEPolicy, ikepolicy_id)
|
|
context.session.delete(ike_db)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_ikepolicy(self, context, ikepolicy_id, fields=None):
|
|
ike_db = self._get_resource(
|
|
context, vpn_models.IKEPolicy, ikepolicy_id)
|
|
return self._make_ikepolicy_dict(ike_db, fields)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_ikepolicies(self, context, filters=None, fields=None):
|
|
return model_query.get_collection(context, vpn_models.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 db_utils.resource_fields(res, fields)
|
|
|
|
def create_ipsecpolicy(self, context, ipsecpolicy):
|
|
ipsecp = ipsecpolicy['ipsecpolicy']
|
|
validator = self._get_validator()
|
|
lifetime_info = ipsecp['lifetime']
|
|
lifetime_units = lifetime_info.get('units', 'seconds')
|
|
lifetime_value = lifetime_info.get('value', 3600)
|
|
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
validator.validate_ipsec_policy(context, ipsecp)
|
|
ipsecp_db = vpn_models.IPsecPolicy(
|
|
id=uuidutils.generate_uuid(),
|
|
tenant_id=ipsecp['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']
|
|
validator = self._get_validator()
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
validator.validate_ipsec_policy(context, ipsecp)
|
|
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
|
|
ipsecpolicy_id=ipsecpolicy_id).first():
|
|
raise vpn_exception.IPsecPolicyInUse(
|
|
ipsecpolicy_id=ipsecpolicy_id)
|
|
ipsecp_db = self._get_resource(
|
|
context, vpn_models.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.get('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 db_api.CONTEXT_WRITER.using(context):
|
|
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
|
|
ipsecpolicy_id=ipsecpolicy_id).first():
|
|
raise vpn_exception.IPsecPolicyInUse(
|
|
ipsecpolicy_id=ipsecpolicy_id)
|
|
ipsec_db = self._get_resource(
|
|
context, vpn_models.IPsecPolicy, ipsecpolicy_id)
|
|
context.session.delete(ipsec_db)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_ipsecpolicy(self, context, ipsecpolicy_id, fields=None):
|
|
ipsec_db = self._get_resource(
|
|
context, vpn_models.IPsecPolicy, ipsecpolicy_id)
|
|
return self._make_ipsecpolicy_dict(ipsec_db, fields)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_ipsecpolicies(self, context, filters=None, fields=None):
|
|
return model_query.get_collection(context, vpn_models.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'],
|
|
'flavor_id': vpnservice['flavor_id'],
|
|
'admin_state_up': vpnservice['admin_state_up'],
|
|
'external_v4_ip': vpnservice['external_v4_ip'],
|
|
'external_v6_ip': vpnservice['external_v6_ip'],
|
|
'status': vpnservice['status']}
|
|
return db_utils.resource_fields(res, fields)
|
|
|
|
def create_vpnservice(self, context, vpnservice):
|
|
vpns = vpnservice['vpnservice']
|
|
flavor_id = vpns.get('flavor_id', None)
|
|
validator = self._get_validator()
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
validator.validate_vpnservice(context, vpns)
|
|
vpnservice_db = vpn_models.VPNService(
|
|
id=uuidutils.generate_uuid(),
|
|
tenant_id=vpns['tenant_id'],
|
|
name=vpns['name'],
|
|
description=vpns['description'],
|
|
subnet_id=vpns['subnet_id'],
|
|
router_id=vpns['router_id'],
|
|
flavor_id=flavor_id,
|
|
admin_state_up=vpns['admin_state_up'],
|
|
status=lib_constants.PENDING_CREATE)
|
|
context.session.add(vpnservice_db)
|
|
return self._make_vpnservice_dict(vpnservice_db)
|
|
|
|
def set_external_tunnel_ips(self, context, vpnservice_id, v4_ip=None,
|
|
v6_ip=None):
|
|
"""Update the external tunnel IP(s) for service."""
|
|
vpns = {'external_v4_ip': v4_ip, 'external_v6_ip': v6_ip}
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
vpns_db = self._get_resource(context, vpn_models.VPNService,
|
|
vpnservice_id)
|
|
vpns_db.update(vpns)
|
|
return self._make_vpnservice_dict(vpns_db)
|
|
|
|
def set_vpnservice_status(self, context, vpnservice_id, status,
|
|
updated_pending_status=False):
|
|
vpns = {'status': status}
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
vpns_db = self._get_resource(context, vpn_models.VPNService,
|
|
vpnservice_id)
|
|
if (utils.in_pending_status(vpns_db.status) and
|
|
not updated_pending_status):
|
|
raise vpnaas.VPNStateInvalidToUpdate(
|
|
id=vpnservice_id, state=vpns_db.status)
|
|
vpns_db.update(vpns)
|
|
return self._make_vpnservice_dict(vpns_db)
|
|
|
|
def update_vpnservice(self, context, vpnservice_id, vpnservice):
|
|
vpns = vpnservice['vpnservice']
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
vpns_db = self._get_resource(context, vpn_models.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 db_api.CONTEXT_WRITER.using(context):
|
|
if context.session.query(vpn_models.IPsecSiteConnection).filter_by(
|
|
vpnservice_id=vpnservice_id
|
|
).first():
|
|
raise vpn_exception.VPNServiceInUse(
|
|
vpnservice_id=vpnservice_id)
|
|
vpns_db = self._get_resource(context, vpn_models.VPNService,
|
|
vpnservice_id)
|
|
context.session.delete(vpns_db)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def _get_vpnservice(self, context, vpnservice_id):
|
|
return self._get_resource(context, vpn_models.VPNService,
|
|
vpnservice_id)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_vpnservice(self, context, vpnservice_id, fields=None):
|
|
vpns_db = self._get_resource(context, vpn_models.VPNService,
|
|
vpnservice_id)
|
|
return self._make_vpnservice_dict(vpns_db, fields)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_vpnservices(self, context, filters=None, fields=None):
|
|
return model_query.get_collection(context, vpn_models.VPNService,
|
|
self._make_vpnservice_dict,
|
|
filters=filters, fields=fields)
|
|
|
|
def check_router_in_use(self, context, router_id):
|
|
vpnservices = self.get_vpnservices(
|
|
context, filters={'router_id': [router_id]})
|
|
if vpnservices:
|
|
plural = "s" if len(vpnservices) > 1 else ""
|
|
services = ",".join([v['id'] for v in vpnservices])
|
|
raise l3_exception.RouterInUse(
|
|
router_id=router_id,
|
|
reason="is currently used by VPN service%(plural)s "
|
|
"(%(services)s)" % {'plural': plural,
|
|
'services': services})
|
|
|
|
def check_subnet_in_use(self, context, subnet_id, router_id):
|
|
with db_api.CONTEXT_READER.using(context):
|
|
vpnservices = context.session.query(
|
|
vpn_models.VPNService).filter_by(
|
|
subnet_id=subnet_id, router_id=router_id).first()
|
|
if vpnservices:
|
|
raise vpn_exception.SubnetInUseByVPNService(
|
|
subnet_id=subnet_id,
|
|
vpnservice_id=vpnservices['id'])
|
|
|
|
query = context.session.query(vpn_models.IPsecSiteConnection)
|
|
query = query.join(
|
|
vpn_models.VPNEndpointGroup,
|
|
vpn_models.VPNEndpointGroup.id ==
|
|
vpn_models.IPsecSiteConnection.local_ep_group_id).filter(
|
|
vpn_models.VPNEndpointGroup.endpoint_type ==
|
|
v_constants.SUBNET_ENDPOINT)
|
|
query = query.join(
|
|
vpn_models.VPNEndpoint,
|
|
vpn_models.VPNEndpoint.endpoint_group_id ==
|
|
vpn_models.IPsecSiteConnection.local_ep_group_id).filter(
|
|
vpn_models.VPNEndpoint.endpoint == subnet_id)
|
|
query = query.join(
|
|
vpn_models.VPNService,
|
|
vpn_models.VPNService.id ==
|
|
vpn_models.IPsecSiteConnection.vpnservice_id).filter(
|
|
vpn_models.VPNService.router_id == router_id)
|
|
connection = query.first()
|
|
if connection:
|
|
raise vpn_exception.SubnetInUseByIPsecSiteConnection(
|
|
subnet_id=subnet_id,
|
|
ipsec_site_connection_id=connection['id'])
|
|
|
|
def check_subnet_in_use_by_endpoint_group(self, context, subnet_id):
|
|
with db_api.CONTEXT_READER.using(context):
|
|
query = context.session.query(vpn_models.VPNEndpointGroup)
|
|
query = query.filter(vpn_models.VPNEndpointGroup.endpoint_type ==
|
|
v_constants.SUBNET_ENDPOINT)
|
|
query = query.join(
|
|
vpn_models.VPNEndpoint,
|
|
sa.and_(vpn_models.VPNEndpoint.endpoint_group_id ==
|
|
vpn_models.VPNEndpointGroup.id,
|
|
vpn_models.VPNEndpoint.endpoint == subnet_id))
|
|
group = query.first()
|
|
if group:
|
|
raise vpn_exception.SubnetInUseByEndpointGroup(
|
|
subnet_id=subnet_id, group_id=group['id'])
|
|
|
|
def _make_endpoint_group_dict(self, endpoint_group, fields=None):
|
|
res = {'id': endpoint_group['id'],
|
|
'tenant_id': endpoint_group['tenant_id'],
|
|
'name': endpoint_group['name'],
|
|
'description': endpoint_group['description'],
|
|
'type': endpoint_group['endpoint_type'],
|
|
'endpoints': [ep['endpoint']
|
|
for ep in endpoint_group['endpoints']]}
|
|
return db_utils.resource_fields(res, fields)
|
|
|
|
def create_endpoint_group(self, context, endpoint_group):
|
|
group = endpoint_group['endpoint_group']
|
|
validator = self._get_validator()
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
validator.validate_endpoint_group(context, group)
|
|
endpoint_group_db = vpn_models.VPNEndpointGroup(
|
|
id=uuidutils.generate_uuid(),
|
|
tenant_id=group['tenant_id'],
|
|
name=group['name'],
|
|
description=group['description'],
|
|
endpoint_type=group['type'])
|
|
context.session.add(endpoint_group_db)
|
|
for endpoint in group['endpoints']:
|
|
endpoint_db = vpn_models.VPNEndpoint(
|
|
endpoint=endpoint,
|
|
endpoint_group_id=endpoint_group_db.id
|
|
)
|
|
context.session.add(endpoint_db)
|
|
return self._make_endpoint_group_dict(endpoint_group_db)
|
|
|
|
def update_endpoint_group(self, context, endpoint_group_id,
|
|
endpoint_group):
|
|
group_changes = endpoint_group['endpoint_group']
|
|
# Note: Endpoints cannot be changed, so will not do validation
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
endpoint_group_db = self._get_resource(context,
|
|
vpn_models.VPNEndpointGroup,
|
|
endpoint_group_id)
|
|
endpoint_group_db.update(group_changes)
|
|
return self._make_endpoint_group_dict(endpoint_group_db)
|
|
|
|
def delete_endpoint_group(self, context, endpoint_group_id):
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
self.check_endpoint_group_not_in_use(context, endpoint_group_id)
|
|
endpoint_group_db = self._get_resource(
|
|
context, vpn_models.VPNEndpointGroup, endpoint_group_id)
|
|
context.session.delete(endpoint_group_db)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_endpoint_group(self, context, endpoint_group_id, fields=None):
|
|
endpoint_group_db = self._get_resource(
|
|
context, vpn_models.VPNEndpointGroup, endpoint_group_id)
|
|
return self._make_endpoint_group_dict(endpoint_group_db, fields)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_endpoint_groups(self, context, filters=None, fields=None):
|
|
return model_query.get_collection(context, vpn_models.VPNEndpointGroup,
|
|
self._make_endpoint_group_dict,
|
|
filters=filters, fields=fields)
|
|
|
|
def check_endpoint_group_not_in_use(self, context, group_id):
|
|
query = context.session.query(vpn_models.IPsecSiteConnection)
|
|
query = query.filter(
|
|
sa.or_(
|
|
vpn_models.IPsecSiteConnection.local_ep_group_id == group_id,
|
|
vpn_models.IPsecSiteConnection.peer_ep_group_id == group_id)
|
|
)
|
|
if query.first():
|
|
raise vpn_exception.EndpointGroupInUse(group_id=group_id)
|
|
|
|
def get_vpnservice_router_id(self, context, vpnservice_id):
|
|
with db_api.CONTEXT_READER.using(context):
|
|
vpnservice = self._get_vpnservice(context, vpnservice_id)
|
|
return vpnservice['router_id']
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_peer_cidrs_for_router(self, context, router_id):
|
|
filters = {'router_id': [router_id]}
|
|
vpnservices = model_query.get_collection_query(
|
|
context, vpn_models.VPNService, filters=filters).all()
|
|
cidrs = []
|
|
for vpnservice in vpnservices:
|
|
for ipsec_site_connection in vpnservice.ipsec_site_connections:
|
|
if ipsec_site_connection.peer_cidrs:
|
|
for peer_cidr in ipsec_site_connection.peer_cidrs:
|
|
cidrs.append(peer_cidr.cidr)
|
|
if ipsec_site_connection.peer_ep_group is not None:
|
|
for ep in ipsec_site_connection.peer_ep_group.endpoints:
|
|
cidrs.append(ep.endpoint)
|
|
return cidrs
|
|
|
|
|
|
class VPNPluginRpcDbMixin(object):
|
|
def _build_local_subnet_cidr_map(self, context):
|
|
"""Build a dict of all local endpoint subnets, with list of CIDRs."""
|
|
query = context.session.query(models_v2.Subnet.id,
|
|
models_v2.Subnet.cidr)
|
|
query = query.join(vpn_models.VPNEndpoint,
|
|
vpn_models.VPNEndpoint.endpoint ==
|
|
models_v2.Subnet.id)
|
|
query = query.join(vpn_models.VPNEndpointGroup,
|
|
vpn_models.VPNEndpointGroup.id ==
|
|
vpn_models.VPNEndpoint.endpoint_group_id)
|
|
query = query.join(vpn_models.IPsecSiteConnection,
|
|
vpn_models.IPsecSiteConnection.local_ep_group_id ==
|
|
vpn_models.VPNEndpointGroup.id)
|
|
return {sn.id: sn.cidr for sn in query.all()}
|
|
|
|
def update_status_by_agent(self, context, service_status_info_list):
|
|
"""Updating vpnservice and vpnconnection status.
|
|
|
|
:param context: context variable
|
|
:param service_status_info_list: list of status
|
|
The structure is
|
|
[{id: vpnservice_id,
|
|
status: ACTIVE|DOWN|ERROR,
|
|
updated_pending_status: True|False
|
|
ipsec_site_connections: {
|
|
ipsec_site_connection_id: {
|
|
status: ACTIVE|DOWN|ERROR,
|
|
updated_pending_status: True|False
|
|
}
|
|
}]
|
|
The agent will set updated_pending_status as True,
|
|
when agent updates any pending status.
|
|
"""
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
for vpnservice in service_status_info_list:
|
|
try:
|
|
vpnservice_db = self._get_vpnservice(
|
|
context, vpnservice['id'])
|
|
except vpn_exception.VPNServiceNotFound:
|
|
LOG.warning('vpnservice %s in db is already deleted',
|
|
vpnservice['id'])
|
|
continue
|
|
|
|
if (not utils.in_pending_status(vpnservice_db.status) or
|
|
vpnservice['updated_pending_status']):
|
|
vpnservice_db.status = vpnservice['status']
|
|
for conn_id, conn in vpnservice[
|
|
'ipsec_site_connections'].items():
|
|
self._update_connection_status(
|
|
context, conn_id, conn['status'],
|
|
conn['updated_pending_status'])
|
|
|
|
|
|
def vpn_router_gateway_callback(resource, event, trigger, payload=None):
|
|
# the event payload objects
|
|
vpn_plugin = directory.get_plugin(p_constants.VPN)
|
|
if vpn_plugin:
|
|
context = payload.context
|
|
router_id = payload.resource_id
|
|
if resource == resources.ROUTER_GATEWAY:
|
|
vpn_plugin.check_router_in_use(context, router_id)
|
|
elif resource == resources.ROUTER_INTERFACE:
|
|
subnet_id = payload.metadata.get('subnet_id')
|
|
vpn_plugin.check_subnet_in_use(context, subnet_id, router_id)
|
|
|
|
|
|
def migration_callback(resource, event, trigger, payload):
|
|
context = payload.context
|
|
router = payload.latest_state
|
|
vpn_plugin = directory.get_plugin(p_constants.VPN)
|
|
if vpn_plugin:
|
|
vpn_plugin.check_router_in_use(context, router['id'])
|
|
return True
|
|
|
|
|
|
def subnet_callback(resource, event, trigger, payload=None):
|
|
"""Respond to subnet based notifications - see if subnet in use."""
|
|
context = payload.context
|
|
subnet_id = payload.resource_id
|
|
vpn_plugin = directory.get_plugin(p_constants.VPN)
|
|
if vpn_plugin:
|
|
vpn_plugin.check_subnet_in_use_by_endpoint_group(context, subnet_id)
|
|
|
|
|
|
def subscribe():
|
|
registry.subscribe(
|
|
vpn_router_gateway_callback, resources.ROUTER_GATEWAY,
|
|
events.BEFORE_DELETE)
|
|
registry.subscribe(
|
|
vpn_router_gateway_callback, resources.ROUTER_INTERFACE,
|
|
events.BEFORE_DELETE)
|
|
registry.subscribe(
|
|
migration_callback, resources.ROUTER, events.BEFORE_UPDATE)
|
|
registry.subscribe(
|
|
subnet_callback, resources.SUBNET, events.BEFORE_DELETE)
|
|
|
|
|
|
# NOTE(armax): multiple VPN service plugins (potentially out of tree) may
|
|
# inherit from vpn_db and may need the callbacks to be processed. Having an
|
|
# implicit subscription (through the module import) preserves the existing
|
|
# behavior, and at the same time it avoids fixing it manually in each and
|
|
# every vpn plugin outta there. That said, The subscription is also made
|
|
# explicitly in the reference vpn plugin. The subscription operation is
|
|
# idempotent so there is no harm in registering the same callback multiple
|
|
# times.
|
|
subscribe()
|