neutron-vpnaas/neutron_vpnaas/db/vpn/vpn_db.py
Bodo Petermann bd3f5f7e7b Add reader context to get_ipsec_site_connection(s)
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
2024-09-11 11:20:32 +02:00

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()