**** CubicPower OpenStack Study ****
# Copyright 2013 VMware, 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.
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.vmware.extensions import 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))
# Constants for gateway device operational status
STATUS_UNKNOWN = "UNKNOWN"
STATUS_ERROR = "ERROR"
STATUS_ACTIVE = "ACTIVE"
STATUS_DOWN = "DOWN"
**** CubicPower OpenStack Study ****
class GatewayInUse(exceptions.InUse):
message = _("Network Gateway '%(gateway_id)s' still has active mappings "
"with one or more neutron networks.")
**** CubicPower OpenStack Study ****
class GatewayNotFound(exceptions.NotFound):
message = _("Network Gateway %(gateway_id)s could not be found")
**** CubicPower OpenStack Study ****
class GatewayDeviceInUse(exceptions.InUse):
message = _("Network Gateway Device '%(device_id)s' is still used by "
"one or more network gateways.")
**** CubicPower OpenStack Study ****
class GatewayDeviceNotFound(exceptions.NotFound):
message = _("Network Gateway Device %(device_id)s could not be found.")
**** CubicPower OpenStack Study ****
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.")
**** CubicPower OpenStack Study ****
class GatewayConnectionInUse(exceptions.InUse):
message = _("The specified mapping '%(mapping)s' is already in use on "
"network gateway '%(gateway_id)s'.")
**** CubicPower OpenStack Study ****
class MultipleGatewayConnections(exceptions.NeutronException):
message = _("Multiple network connections found on '%(gateway_id)s' "
"with provided criteria.")
**** CubicPower OpenStack Study ****
class GatewayConnectionNotFound(exceptions.NotFound):
message = _("The connection %(network_mapping_info)s was not found on the "
"network gateway '%(network_gateway_id)s'")
**** CubicPower OpenStack Study ****
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})
**** CubicPower OpenStack Study ****
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)
**** CubicPower OpenStack Study ****
class NetworkGatewayDeviceReference(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'),
primary_key=True)
interface_name = sa.Column(sa.String(64), primary_key=True)
**** CubicPower OpenStack Study ****
class NetworkGatewayDevice(model_base.BASEV2, models_v2.HasId,
models_v2.HasTenant):
nsx_id = sa.Column(sa.String(36))
# Optional name for the gateway device
name = sa.Column(sa.String(255))
# Transport connector type. Not using enum as range of
# connector types might vary with backend version
connector_type = sa.Column(sa.String(10))
# Transport connector IP Address
connector_ip = sa.Column(sa.String(64))
# operational status
status = sa.Column(sa.String(16))
**** CubicPower OpenStack Study ****
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))
**** CubicPower OpenStack Study ****
class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase):
gateway_resource = networkgw.GATEWAY_RESOURCE_NAME
device_resource = networkgw.DEVICE_RESOURCE_NAME
**** CubicPower OpenStack Study ****
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
**** CubicPower OpenStack Study ****
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']}
**** CubicPower OpenStack Study ****
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)
**** CubicPower OpenStack Study ****
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
**** CubicPower OpenStack Study ****
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
**** CubicPower OpenStack Study ****
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()
**** CubicPower OpenStack Study ****
def _unset_default_network_gateways(self, context):
with context.session.begin(subtransactions=True):
context.session.query(NetworkGateway).update(
{NetworkGateway.default: False})
**** CubicPower OpenStack Study ****
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
**** CubicPower OpenStack Study ****
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'])
**** CubicPower OpenStack Study ****
def create_network_gateway(self, context, network_gateway):
gw_data = network_gateway[self.gateway_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
# TODO(salv-orlando): Enforce that gateway device identifiers
# in this list are among the tenant's NSX network gateway devices
# to avoid risk a tenant 'guessing' other tenant's network devices
gw_db.devices.extend([NetworkGatewayDeviceReference(**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)
**** CubicPower OpenStack Study ****
def update_network_gateway(self, context, id, network_gateway):
gw_data = network_gateway[self.gateway_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)
**** CubicPower OpenStack Study ****
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)
**** CubicPower OpenStack Study ****
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)
**** CubicPower OpenStack Study ****
def get_network_gateways(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
marker_obj = self._get_marker_obj(
context, 'network_gateway', limit, marker)
return self._get_collection(context, NetworkGateway,
self._make_network_gateway_dict,
filters=filters, fields=fields,
sorts=sorts, limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)
**** CubicPower OpenStack Study ****
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._delete_ip_allocation(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}}
**** CubicPower OpenStack Study ****
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)
**** CubicPower OpenStack Study ****
def _make_gateway_device_dict(self, gateway_device, fields=None,
include_nsx_id=False):
res = {'id': gateway_device['id'],
'name': gateway_device['name'],
'status': gateway_device['status'],
'connector_type': gateway_device['connector_type'],
'connector_ip': gateway_device['connector_ip'],
'tenant_id': gateway_device['tenant_id']}
if include_nsx_id:
# Return the NSX mapping as well. This attribute will not be
# returned in the API response anyway. Ensure it will not be
# filtered out in field selection.
if fields:
fields.append('nsx_id')
res['nsx_id'] = gateway_device['nsx_id']
return self._fields(res, fields)
**** CubicPower OpenStack Study ****
def _get_gateway_device(self, context, device_id):
try:
return self._get_by_id(context, NetworkGatewayDevice, device_id)
except sa_orm_exc.NoResultFound:
raise GatewayDeviceNotFound(device_id=device_id)
**** CubicPower OpenStack Study ****
def _is_device_in_use(self, context, device_id):
query = self._get_collection_query(
context, NetworkGatewayDeviceReference, {'id': [device_id]})
return query.first()
**** CubicPower OpenStack Study ****
def get_gateway_device(self, context, device_id, fields=None,
include_nsx_id=False):
return self._make_gateway_device_dict(
self._get_gateway_device(context, device_id),
fields, include_nsx_id)
**** CubicPower OpenStack Study ****
def get_gateway_devices(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False, include_nsx_id=False):
marker_obj = self._get_marker_obj(
context, 'gateway_device', limit, marker)
query = self._get_collection_query(context,
NetworkGatewayDevice,
filters=filters,
fields=fields,
sorts=sorts,
limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)
return [self._make_gateway_device_dict(row, fields, include_nsx_id)
for row in query]
**** CubicPower OpenStack Study ****
def create_gateway_device(self, context, gateway_device,
initial_status=STATUS_UNKNOWN):
device_data = gateway_device[self.device_resource]
tenant_id = self._get_tenant_id_for_create(context, device_data)
with context.session.begin(subtransactions=True):
device_db = NetworkGatewayDevice(
id=device_data.get('id', uuidutils.generate_uuid()),
tenant_id=tenant_id,
name=device_data.get('name'),
connector_type=device_data['connector_type'],
connector_ip=device_data['connector_ip'],
status=initial_status)
context.session.add(device_db)
LOG.debug(_("Created network gateway device: %s"), device_db['id'])
return self._make_gateway_device_dict(device_db)
**** CubicPower OpenStack Study ****
def update_gateway_device(self, context, gateway_device_id,
gateway_device, include_nsx_id=False):
device_data = gateway_device[self.device_resource]
with context.session.begin(subtransactions=True):
device_db = self._get_gateway_device(context, gateway_device_id)
# Ensure there is something to update before doing it
if any([device_db[k] != device_data[k] for k in device_data]):
device_db.update(device_data)
LOG.debug(_("Updated network gateway device: %s"),
gateway_device_id)
return self._make_gateway_device_dict(
device_db, include_nsx_id=include_nsx_id)
**** CubicPower OpenStack Study ****
def delete_gateway_device(self, context, device_id):
with context.session.begin(subtransactions=True):
# A gateway device should not be deleted
# if it is used in any network gateway service
if self._is_device_in_use(context, device_id):
raise GatewayDeviceInUse(device_id=device_id)
device_db = self._get_gateway_device(context, device_id)
context.session.delete(device_db)
LOG.debug(_("Deleted network gateway device: %s."), device_id)