5cdb6f9489
Change-Id: I5304bd52f7a5ae22fbc0d48206d7c1d282b34a91 Closes-Bug: #1252921
376 lines
17 KiB
Python
376 lines
17 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
#
|
|
# Copyright 2013 Nicira Networks, 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.
|
|
#
|
|
# @author: Salvatore Orlando, VMware
|
|
#
|
|
|
|
import sqlalchemy as sa
|
|
|
|
from sqlalchemy import orm
|
|
from sqlalchemy.orm import exc as sa_orm_exc
|
|
from webob import exc as web_exc
|
|
|
|
from neutron.api.v2 import attributes
|
|
from neutron.api.v2 import base
|
|
from neutron.common import exceptions
|
|
from neutron.db import model_base
|
|
from neutron.db import models_v2
|
|
from neutron.openstack.common import log as logging
|
|
from neutron.openstack.common import uuidutils
|
|
from neutron.plugins.nicira.extensions import nvp_networkgw
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
DEVICE_OWNER_NET_GW_INTF = 'network:gateway-interface'
|
|
NETWORK_ID = 'network_id'
|
|
SEGMENTATION_TYPE = 'segmentation_type'
|
|
SEGMENTATION_ID = 'segmentation_id'
|
|
ALLOWED_CONNECTION_ATTRIBUTES = set((NETWORK_ID,
|
|
SEGMENTATION_TYPE,
|
|
SEGMENTATION_ID))
|
|
|
|
|
|
class GatewayInUse(exceptions.InUse):
|
|
message = _("Network Gateway '%(gateway_id)s' still has active mappings "
|
|
"with one or more neutron networks.")
|
|
|
|
|
|
class GatewayNotFound(exceptions.NotFound):
|
|
message = _("Network Gateway %(gateway_id)s could not be found")
|
|
|
|
|
|
class NetworkGatewayPortInUse(exceptions.InUse):
|
|
message = _("Port '%(port_id)s' is owned by '%(device_owner)s' and "
|
|
"therefore cannot be deleted directly via the port API.")
|
|
|
|
|
|
class GatewayConnectionInUse(exceptions.InUse):
|
|
message = _("The specified mapping '%(mapping)s' is already in use on "
|
|
"network gateway '%(gateway_id)s'.")
|
|
|
|
|
|
class MultipleGatewayConnections(exceptions.NeutronException):
|
|
message = _("Multiple network connections found on '%(gateway_id)s' "
|
|
"with provided criteria.")
|
|
|
|
|
|
class GatewayConnectionNotFound(exceptions.NotFound):
|
|
message = _("The connection %(network_mapping_info)s was not found on the "
|
|
"network gateway '%(network_gateway_id)s'")
|
|
|
|
|
|
class NetworkGatewayUnchangeable(exceptions.InUse):
|
|
message = _("The network gateway %(gateway_id)s "
|
|
"cannot be updated or deleted")
|
|
|
|
# Add exceptions to HTTP Faults mappings
|
|
base.FAULT_MAP.update({GatewayInUse: web_exc.HTTPConflict,
|
|
NetworkGatewayPortInUse: web_exc.HTTPConflict,
|
|
GatewayConnectionInUse: web_exc.HTTPConflict,
|
|
GatewayConnectionNotFound: web_exc.HTTPNotFound,
|
|
MultipleGatewayConnections: web_exc.HTTPConflict})
|
|
|
|
|
|
class NetworkConnection(model_base.BASEV2, models_v2.HasTenant):
|
|
"""Defines a connection between a network gateway and a network."""
|
|
# We use port_id as the primary key as one can connect a gateway
|
|
# to a network in multiple ways (and we cannot use the same port form
|
|
# more than a single gateway)
|
|
network_gateway_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('networkgateways.id',
|
|
ondelete='CASCADE'))
|
|
network_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('networks.id', ondelete='CASCADE'))
|
|
segmentation_type = sa.Column(
|
|
sa.Enum('flat', 'vlan',
|
|
name='networkconnections_segmentation_type'))
|
|
segmentation_id = sa.Column(sa.Integer)
|
|
__table_args__ = (sa.UniqueConstraint(network_gateway_id,
|
|
segmentation_type,
|
|
segmentation_id),)
|
|
# Also, storing port id comes back useful when disconnecting a network
|
|
# from a gateway
|
|
port_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('ports.id', ondelete='CASCADE'),
|
|
primary_key=True)
|
|
|
|
|
|
class NetworkGatewayDevice(model_base.BASEV2):
|
|
id = sa.Column(sa.String(36), primary_key=True)
|
|
network_gateway_id = sa.Column(sa.String(36),
|
|
sa.ForeignKey('networkgateways.id',
|
|
ondelete='CASCADE'))
|
|
interface_name = sa.Column(sa.String(64))
|
|
|
|
|
|
class NetworkGateway(model_base.BASEV2, models_v2.HasId,
|
|
models_v2.HasTenant):
|
|
"""Defines the data model for a network gateway."""
|
|
name = sa.Column(sa.String(255))
|
|
# Tenant id is nullable for this resource
|
|
tenant_id = sa.Column(sa.String(36))
|
|
default = sa.Column(sa.Boolean())
|
|
devices = orm.relationship(NetworkGatewayDevice,
|
|
backref='networkgateways',
|
|
cascade='all,delete')
|
|
network_connections = orm.relationship(NetworkConnection, lazy='joined')
|
|
|
|
|
|
class NetworkGatewayMixin(nvp_networkgw.NetworkGatewayPluginBase):
|
|
|
|
resource = nvp_networkgw.RESOURCE_NAME.replace('-', '_')
|
|
|
|
def _get_network_gateway(self, context, gw_id):
|
|
try:
|
|
gw = self._get_by_id(context, NetworkGateway, gw_id)
|
|
except sa_orm_exc.NoResultFound:
|
|
raise GatewayNotFound(gateway_id=gw_id)
|
|
return gw
|
|
|
|
def _make_gw_connection_dict(self, gw_conn):
|
|
return {'port_id': gw_conn['port_id'],
|
|
'segmentation_type': gw_conn['segmentation_type'],
|
|
'segmentation_id': gw_conn['segmentation_id']}
|
|
|
|
def _make_network_gateway_dict(self, network_gateway, fields=None):
|
|
device_list = []
|
|
for d in network_gateway['devices']:
|
|
device_list.append({'id': d['id'],
|
|
'interface_name': d['interface_name']})
|
|
res = {'id': network_gateway['id'],
|
|
'name': network_gateway['name'],
|
|
'default': network_gateway['default'],
|
|
'devices': device_list,
|
|
'tenant_id': network_gateway['tenant_id']}
|
|
# Query gateway connections only if needed
|
|
if (fields and 'ports' in fields) or not fields:
|
|
res['ports'] = [self._make_gw_connection_dict(conn)
|
|
for conn in network_gateway.network_connections]
|
|
return self._fields(res, fields)
|
|
|
|
def _set_mapping_info_defaults(self, mapping_info):
|
|
if not mapping_info.get('segmentation_type'):
|
|
mapping_info['segmentation_type'] = 'flat'
|
|
if not mapping_info.get('segmentation_id'):
|
|
mapping_info['segmentation_id'] = 0
|
|
|
|
def _validate_network_mapping_info(self, network_mapping_info):
|
|
self._set_mapping_info_defaults(network_mapping_info)
|
|
network_id = network_mapping_info.get(NETWORK_ID)
|
|
if not network_id:
|
|
raise exceptions.InvalidInput(
|
|
error_message=_("A network identifier must be specified "
|
|
"when connecting a network to a network "
|
|
"gateway. Unable to complete operation"))
|
|
connection_attrs = set(network_mapping_info.keys())
|
|
if not connection_attrs.issubset(ALLOWED_CONNECTION_ATTRIBUTES):
|
|
raise exceptions.InvalidInput(
|
|
error_message=(_("Invalid keys found among the ones provided "
|
|
"in request body: %(connection_attrs)s."),
|
|
connection_attrs))
|
|
seg_type = network_mapping_info.get(SEGMENTATION_TYPE)
|
|
seg_id = network_mapping_info.get(SEGMENTATION_ID)
|
|
if not seg_type and seg_id:
|
|
msg = _("In order to specify a segmentation id the "
|
|
"segmentation type must be specified as well")
|
|
raise exceptions.InvalidInput(error_message=msg)
|
|
elif seg_type and seg_type.lower() == 'flat' and seg_id:
|
|
msg = _("Cannot specify a segmentation id when "
|
|
"the segmentation type is flat")
|
|
raise exceptions.InvalidInput(error_message=msg)
|
|
return network_id
|
|
|
|
def _retrieve_gateway_connections(self, context, gateway_id,
|
|
mapping_info={}, only_one=False):
|
|
filters = {'network_gateway_id': [gateway_id]}
|
|
for k, v in mapping_info.iteritems():
|
|
if v and k != NETWORK_ID:
|
|
filters[k] = [v]
|
|
query = self._get_collection_query(context,
|
|
NetworkConnection,
|
|
filters)
|
|
return only_one and query.one() or query.all()
|
|
|
|
def _unset_default_network_gateways(self, context):
|
|
with context.session.begin(subtransactions=True):
|
|
context.session.query(NetworkGateway).update(
|
|
{NetworkGateway.default: False})
|
|
|
|
def _set_default_network_gateway(self, context, gw_id):
|
|
with context.session.begin(subtransactions=True):
|
|
gw = (context.session.query(NetworkGateway).
|
|
filter_by(id=gw_id).one())
|
|
gw['default'] = True
|
|
|
|
def prevent_network_gateway_port_deletion(self, context, port):
|
|
"""Pre-deletion check.
|
|
|
|
Ensures a port will not be deleted if is being used by a network
|
|
gateway. In that case an exception will be raised.
|
|
"""
|
|
if port['device_owner'] == DEVICE_OWNER_NET_GW_INTF:
|
|
raise NetworkGatewayPortInUse(port_id=port['id'],
|
|
device_owner=port['device_owner'])
|
|
|
|
def create_network_gateway(self, context, network_gateway):
|
|
gw_data = network_gateway[self.resource]
|
|
tenant_id = self._get_tenant_id_for_create(context, gw_data)
|
|
with context.session.begin(subtransactions=True):
|
|
gw_db = NetworkGateway(
|
|
id=gw_data.get('id', uuidutils.generate_uuid()),
|
|
tenant_id=tenant_id,
|
|
name=gw_data.get('name'))
|
|
# Device list is guaranteed to be a valid list
|
|
gw_db.devices.extend([NetworkGatewayDevice(**device)
|
|
for device in gw_data['devices']])
|
|
context.session.add(gw_db)
|
|
LOG.debug(_("Created network gateway with id:%s"), gw_db['id'])
|
|
return self._make_network_gateway_dict(gw_db)
|
|
|
|
def update_network_gateway(self, context, id, network_gateway):
|
|
gw_data = network_gateway[self.resource]
|
|
with context.session.begin(subtransactions=True):
|
|
gw_db = self._get_network_gateway(context, id)
|
|
if gw_db.default:
|
|
raise NetworkGatewayUnchangeable(gateway_id=id)
|
|
# Ensure there is something to update before doing it
|
|
if any([gw_db[k] != gw_data[k] for k in gw_data]):
|
|
gw_db.update(gw_data)
|
|
LOG.debug(_("Updated network gateway with id:%s"), id)
|
|
return self._make_network_gateway_dict(gw_db)
|
|
|
|
def get_network_gateway(self, context, id, fields=None):
|
|
gw_db = self._get_network_gateway(context, id)
|
|
return self._make_network_gateway_dict(gw_db, fields)
|
|
|
|
def delete_network_gateway(self, context, id):
|
|
with context.session.begin(subtransactions=True):
|
|
gw_db = self._get_network_gateway(context, id)
|
|
if gw_db.network_connections:
|
|
raise GatewayInUse(gateway_id=id)
|
|
if gw_db.default:
|
|
raise NetworkGatewayUnchangeable(gateway_id=id)
|
|
context.session.delete(gw_db)
|
|
LOG.debug(_("Network gateway '%s' was destroyed."), id)
|
|
|
|
def get_network_gateways(self, context, filters=None, fields=None):
|
|
return self._get_collection(context, NetworkGateway,
|
|
self._make_network_gateway_dict,
|
|
filters=filters, fields=fields)
|
|
|
|
def connect_network(self, context, network_gateway_id,
|
|
network_mapping_info):
|
|
network_id = self._validate_network_mapping_info(network_mapping_info)
|
|
LOG.debug(_("Connecting network '%(network_id)s' to gateway "
|
|
"'%(network_gateway_id)s'"),
|
|
{'network_id': network_id,
|
|
'network_gateway_id': network_gateway_id})
|
|
with context.session.begin(subtransactions=True):
|
|
gw_db = self._get_network_gateway(context, network_gateway_id)
|
|
tenant_id = self._get_tenant_id_for_create(context, gw_db)
|
|
# TODO(salvatore-orlando): Leverage unique constraint instead
|
|
# of performing another query!
|
|
if self._retrieve_gateway_connections(context,
|
|
network_gateway_id,
|
|
network_mapping_info):
|
|
raise GatewayConnectionInUse(mapping=network_mapping_info,
|
|
gateway_id=network_gateway_id)
|
|
# TODO(salvatore-orlando): Creating a port will give it an IP,
|
|
# but we actually do not need any. Instead of wasting an IP we
|
|
# should have a way to say a port shall not be associated with
|
|
# any subnet
|
|
try:
|
|
# We pass the segmentation type and id too - the plugin
|
|
# might find them useful as the network connection object
|
|
# does not exist yet.
|
|
# NOTE: they're not extended attributes, rather extra data
|
|
# passed in the port structure to the plugin
|
|
# TODO(salvatore-orlando): Verify optimal solution for
|
|
# ownership of the gateway port
|
|
port = self.create_port(context, {
|
|
'port':
|
|
{'tenant_id': tenant_id,
|
|
'network_id': network_id,
|
|
'mac_address': attributes.ATTR_NOT_SPECIFIED,
|
|
'admin_state_up': True,
|
|
'fixed_ips': [],
|
|
'device_id': network_gateway_id,
|
|
'device_owner': DEVICE_OWNER_NET_GW_INTF,
|
|
'name': '',
|
|
'gw:segmentation_type':
|
|
network_mapping_info.get('segmentation_type'),
|
|
'gw:segmentation_id':
|
|
network_mapping_info.get('segmentation_id')}})
|
|
except exceptions.NetworkNotFound:
|
|
err_msg = (_("Requested network '%(network_id)s' not found."
|
|
"Unable to create network connection on "
|
|
"gateway '%(network_gateway_id)s") %
|
|
{'network_id': network_id,
|
|
'network_gateway_id': network_gateway_id})
|
|
LOG.error(err_msg)
|
|
raise exceptions.InvalidInput(error_message=err_msg)
|
|
port_id = port['id']
|
|
LOG.debug(_("Gateway port for '%(network_gateway_id)s' "
|
|
"created on network '%(network_id)s':%(port_id)s"),
|
|
{'network_gateway_id': network_gateway_id,
|
|
'network_id': network_id,
|
|
'port_id': port_id})
|
|
# Create NetworkConnection record
|
|
network_mapping_info['port_id'] = port_id
|
|
network_mapping_info['tenant_id'] = tenant_id
|
|
gw_db.network_connections.append(
|
|
NetworkConnection(**network_mapping_info))
|
|
port_id = port['id']
|
|
# now deallocate and recycle ip from the port
|
|
for fixed_ip in port.get('fixed_ips', []):
|
|
self._recycle_ip(context, network_id,
|
|
fixed_ip['subnet_id'],
|
|
fixed_ip['ip_address'])
|
|
LOG.debug(_("Ensured no Ip addresses are configured on port %s"),
|
|
port_id)
|
|
return {'connection_info':
|
|
{'network_gateway_id': network_gateway_id,
|
|
'network_id': network_id,
|
|
'port_id': port_id}}
|
|
|
|
def disconnect_network(self, context, network_gateway_id,
|
|
network_mapping_info):
|
|
network_id = self._validate_network_mapping_info(network_mapping_info)
|
|
LOG.debug(_("Disconnecting network '%(network_id)s' from gateway "
|
|
"'%(network_gateway_id)s'"),
|
|
{'network_id': network_id,
|
|
'network_gateway_id': network_gateway_id})
|
|
with context.session.begin(subtransactions=True):
|
|
# Uniquely identify connection, otherwise raise
|
|
try:
|
|
net_connection = self._retrieve_gateway_connections(
|
|
context, network_gateway_id,
|
|
network_mapping_info, only_one=True)
|
|
except sa_orm_exc.NoResultFound:
|
|
raise GatewayConnectionNotFound(
|
|
network_mapping_info=network_mapping_info,
|
|
network_gateway_id=network_gateway_id)
|
|
except sa_orm_exc.MultipleResultsFound:
|
|
raise MultipleGatewayConnections(
|
|
gateway_id=network_gateway_id)
|
|
# Remove gateway port from network
|
|
# FIXME(salvatore-orlando): Ensure state of port in NVP is
|
|
# consistent with outcome of transaction
|
|
self.delete_port(context, net_connection['port_id'],
|
|
nw_gw_port_check=False)
|
|
# Remove NetworkConnection record
|
|
context.session.delete(net_connection)
|