237 lines
9.2 KiB
Python
237 lines
9.2 KiB
Python
# (c) Copyright 2016 IBM Corporation, All Rights Reserved.
|
|
# (c) Copyright 2023 SysEleven GmbH
|
|
# 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.models import l3 as l3_models
|
|
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_base
|
|
from neutron_lib.db import model_query
|
|
from neutron_lib import exceptions as n_exc
|
|
from neutron_lib.plugins import constants as plugin_const
|
|
from neutron_lib.plugins import directory
|
|
from oslo_log import log as logging
|
|
from oslo_utils import uuidutils
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import orm
|
|
from sqlalchemy.orm import exc
|
|
|
|
from neutron_vpnaas._i18n import _
|
|
from neutron_vpnaas.services.vpn.common import constants as v_constants
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class RouterIsNotVPNExternal(n_exc.BadRequest):
|
|
message = _("Router %(router_id)s has no VPN external network gateway set")
|
|
|
|
|
|
class RouterHasVPNExternal(n_exc.BadRequest):
|
|
message = _(
|
|
"Router %(router_id)s already has VPN external network gateway")
|
|
|
|
|
|
class VPNNetworkInUse(n_exc.NetworkInUse):
|
|
message = _("Network %(network_id)s is used by VPN service")
|
|
|
|
|
|
class VPNExtGW(model_base.BASEV2, model_base.HasId, model_base.HasProject):
|
|
__tablename__ = 'vpn_ext_gws'
|
|
router_id = sa.Column(sa.String(36), sa.ForeignKey('routers.id'),
|
|
nullable=False, unique=True)
|
|
status = sa.Column(sa.String(16), nullable=False)
|
|
gw_port_id = sa.Column(
|
|
sa.String(36),
|
|
sa.ForeignKey('ports.id', ondelete='SET NULL'))
|
|
transit_port_id = sa.Column(
|
|
sa.String(36),
|
|
sa.ForeignKey('ports.id', ondelete='SET NULL'))
|
|
transit_network_id = sa.Column(
|
|
sa.String(36),
|
|
sa.ForeignKey('networks.id', ondelete='SET NULL'))
|
|
transit_subnet_id = sa.Column(
|
|
sa.String(36),
|
|
sa.ForeignKey('subnets.id', ondelete='SET NULL'))
|
|
|
|
gw_port = orm.relationship(models_v2.Port, lazy='joined',
|
|
foreign_keys=[gw_port_id])
|
|
transit_port = orm.relationship(models_v2.Port, lazy='joined',
|
|
foreign_keys=[transit_port_id])
|
|
transit_network = orm.relationship(models_v2.Network)
|
|
transit_subnet = orm.relationship(models_v2.Subnet)
|
|
router = orm.relationship(l3_models.Router)
|
|
|
|
|
|
@registry.has_registry_receivers
|
|
class VPNExtGWPlugin_db(object):
|
|
"""DB class to support vpn external ports configuration."""
|
|
|
|
@property
|
|
def _core_plugin(self):
|
|
return directory.get_plugin()
|
|
|
|
@property
|
|
def _vpn_plugin(self):
|
|
return directory.get_plugin(plugin_const.VPN)
|
|
|
|
@staticmethod
|
|
@registry.receives(resources.PORT, [events.BEFORE_DELETE])
|
|
def _prevent_vpn_port_delete_callback(resource, event,
|
|
trigger, payload=None):
|
|
vpn_plugin = directory.get_plugin(plugin_const.VPN)
|
|
if vpn_plugin:
|
|
vpn_plugin.prevent_vpn_port_deletion(payload.context,
|
|
payload.resource_id)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def _id_used(self, context, id_column, resource_id):
|
|
return context.session.query(VPNExtGW).filter(
|
|
sa.and_(
|
|
id_column == resource_id,
|
|
VPNExtGW.status != lib_constants.PENDING_DELETE
|
|
)
|
|
).count() > 0
|
|
|
|
def prevent_vpn_port_deletion(self, context, port_id):
|
|
"""Checks to make sure a port is allowed to be deleted.
|
|
|
|
Raises an exception if this is not the case. This should be called by
|
|
any plugin when the API requests the deletion of a port, since some
|
|
ports for L3 are not intended to be deleted directly via a DELETE
|
|
to /ports, but rather via other API calls that perform the proper
|
|
deletion checks.
|
|
"""
|
|
try:
|
|
port = self._core_plugin.get_port(context, port_id)
|
|
except n_exc.PortNotFound:
|
|
# non-existent ports don't need to be protected from deletion
|
|
return
|
|
|
|
port_id_column = {
|
|
v_constants.DEVICE_OWNER_VPN_ROUTER_GW: VPNExtGW.gw_port_id,
|
|
v_constants.DEVICE_OWNER_TRANSIT_NETWORK:
|
|
VPNExtGW.transit_port_id,
|
|
}.get(port['device_owner'])
|
|
|
|
if not port_id_column:
|
|
# This is not a VPN port
|
|
return
|
|
|
|
if self._id_used(context, port_id_column, port_id):
|
|
reason = _('has device owner %s') % port['device_owner']
|
|
raise n_exc.ServicePortInUse(port_id=port['id'], reason=reason)
|
|
|
|
@staticmethod
|
|
@registry.receives(resources.SUBNET, [events.BEFORE_DELETE])
|
|
def _prevent_vpn_subnet_delete_callback(resource, event,
|
|
trigger, payload=None):
|
|
vpn_plugin = directory.get_plugin(plugin_const.VPN)
|
|
if vpn_plugin:
|
|
vpn_plugin.prevent_vpn_subnet_deletion(payload.context,
|
|
payload.resource_id)
|
|
|
|
def prevent_vpn_subnet_deletion(self, context, subnet_id):
|
|
if self._id_used(context, VPNExtGW.transit_subnet_id, subnet_id):
|
|
reason = _('Subnet is used by VPN service')
|
|
raise n_exc.SubnetInUse(subnet_id=subnet_id, reason=reason)
|
|
|
|
@staticmethod
|
|
@registry.receives(resources.NETWORK, [events.BEFORE_DELETE])
|
|
def _prevent_vpn_network_delete_callback(resource, event,
|
|
trigger, payload=None):
|
|
vpn_plugin = directory.get_plugin(plugin_const.VPN)
|
|
if vpn_plugin:
|
|
vpn_plugin.prevent_vpn_network_deletion(payload.context,
|
|
payload.resource_id)
|
|
|
|
def prevent_vpn_network_deletion(self, context, network_id):
|
|
if self._id_used(context, VPNExtGW.transit_network_id, network_id):
|
|
raise VPNNetworkInUse(network_id=network_id)
|
|
|
|
def _make_vpn_ext_gw_dict(self, gateway_db):
|
|
if not gateway_db:
|
|
return None
|
|
gateway = {
|
|
'id': gateway_db['id'],
|
|
'tenant_id': gateway_db['tenant_id'],
|
|
'router_id': gateway_db['router_id'],
|
|
'status': gateway_db['status'],
|
|
}
|
|
if gateway_db.gw_port:
|
|
gateway['network_id'] = gateway_db.gw_port['network_id']
|
|
gateway['external_fixed_ips'] = [
|
|
{'subnet_id': ip["subnet_id"], 'ip_address': ip["ip_address"]}
|
|
for ip in gateway_db.gw_port['fixed_ips']
|
|
]
|
|
for key in ('gw_port_id', 'transit_port_id', 'transit_network_id',
|
|
'transit_subnet_id'):
|
|
value = gateway_db.get(key)
|
|
if value:
|
|
gateway[key] = value
|
|
return gateway
|
|
|
|
def _get_vpn_gw_by_router_id(self, context, router_id):
|
|
try:
|
|
gateway_db = context.session.query(VPNExtGW).filter(
|
|
VPNExtGW.router_id == router_id).one()
|
|
except exc.NoResultFound:
|
|
return None
|
|
return gateway_db
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_vpn_gw_by_router_id(self, context, router_id):
|
|
return self._get_vpn_gw_by_router_id(context, router_id)
|
|
|
|
@db_api.CONTEXT_READER
|
|
def get_vpn_gw_dict_by_router_id(self, context, router_id, refresh=False):
|
|
gateway_db = self._get_vpn_gw_by_router_id(context, router_id)
|
|
if gateway_db and refresh:
|
|
context.session.refresh(gateway_db)
|
|
return self._make_vpn_ext_gw_dict(gateway_db)
|
|
|
|
def create_gateway(self, context, gateway):
|
|
info = gateway['gateway']
|
|
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
gateway_db = VPNExtGW(
|
|
id=uuidutils.generate_uuid(),
|
|
tenant_id=info['tenant_id'],
|
|
router_id=info['router_id'],
|
|
status=lib_constants.PENDING_CREATE,
|
|
gw_port_id=info.get('gw_port_id'),
|
|
transit_port_id=info.get('transit_port_id'),
|
|
transit_network_id=info.get('transit_network_id'),
|
|
transit_subnet_id=info.get('transit_subnet_id'))
|
|
context.session.add(gateway_db)
|
|
|
|
return self._make_vpn_ext_gw_dict(gateway_db)
|
|
|
|
def update_gateway(self, context, gateway_id, gateway):
|
|
info = gateway['gateway']
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
gateway_db = model_query.get_by_id(context, VPNExtGW, gateway_id)
|
|
gateway_db.update(info)
|
|
return self._make_vpn_ext_gw_dict(gateway_db)
|
|
|
|
def delete_gateway(self, context, gateway_id):
|
|
with db_api.CONTEXT_WRITER.using(context):
|
|
query = context.session.query(VPNExtGW)
|
|
return query.filter(VPNExtGW.id == gateway_id).delete()
|