VMware: initial NSXv developments

Add the NSXv code to the repo.

Co-Authored-By: Kobi Samoray <ksamoray@vmware.com>

Change-Id: Iefc76e0d6bfab8136bd2e3300a8b3d4a3fdb3a46
This commit is contained in:
Gary Kotton 2014-12-20 23:17:52 -08:00
parent 3a96a43c53
commit 9254b0aeda
90 changed files with 8471 additions and 1889 deletions

View File

@ -2,7 +2,8 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-e git://git.openstack.org/openstack/neutron.git#egg=neutron
# Temporary, till https://review.openstack.org/#/c/143949/ is merged
-e git://github.com/gkotton/neutron.git#egg=neutron
hacking>=0.9.2,<0.10

View File

@ -23,7 +23,7 @@ from oslo.config import cfg
from neutron.i18n import _LE, _LI, _LW
from neutron.openstack.common import log as logging
from neutron.plugins.vmware import api_client
from vmware_nsx.neutron.plugins.vmware import api_client
LOG = logging.getLogger(__name__)

View File

@ -19,11 +19,11 @@ import httplib
from neutron.i18n import _LE
from neutron.openstack.common import log as logging
from neutron.plugins.vmware.api_client import base
from neutron.plugins.vmware.api_client import eventlet_client
from neutron.plugins.vmware.api_client import eventlet_request
from neutron.plugins.vmware.api_client import exception
from neutron.plugins.vmware.api_client import version
from vmware_nsx.neutron.plugins.vmware.api_client import base
from vmware_nsx.neutron.plugins.vmware.api_client import eventlet_client
from vmware_nsx.neutron.plugins.vmware.api_client import eventlet_request
from vmware_nsx.neutron.plugins.vmware.api_client import version
LOG = logging.getLogger(__name__)

View File

@ -22,8 +22,8 @@ eventlet.monkey_patch()
from neutron.i18n import _LE
from neutron.openstack.common import log as logging
from neutron.plugins.vmware.api_client import base
from neutron.plugins.vmware.api_client import eventlet_request
from vmware_nsx.neutron.plugins.vmware.api_client import base
from vmware_nsx.neutron.plugins.vmware.api_client import eventlet_request
LOG = logging.getLogger(__name__)

View File

@ -22,7 +22,7 @@ from oslo.serialization import jsonutils
from neutron.i18n import _LI, _LW
from neutron.openstack.common import log as logging
from neutron.plugins.vmware.api_client import request
from vmware_nsx.neutron.plugins.vmware.api_client import request
LOG = logging.getLogger(__name__)
USER_AGENT = "Neutron eventlet client/2.0"

View File

@ -27,7 +27,7 @@ import six.moves.urllib.parse as urlparse
from neutron.i18n import _LI, _LW
from neutron.openstack.common import log as logging
from neutron.plugins.vmware import api_client
from vmware_nsx.neutron.plugins.vmware import api_client
LOG = logging.getLogger(__name__)

View File

@ -20,9 +20,9 @@ import sys
from oslo.config import cfg
from neutron.common import config
from neutron.plugins.vmware.common import config as nsx_config # noqa
from neutron.plugins.vmware.common import nsx_utils
from neutron.plugins.vmware import nsxlib
from vmware_nsx.neutron.plugins.vmware.common import config as nsx_config # noqa
from vmware_nsx.neutron.plugins.vmware.common import nsx_utils
from vmware_nsx.neutron.plugins.vmware import nsxlib
config.setup_logging()

View File

@ -12,10 +12,15 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
from oslo.config import cfg
from neutron.i18n import _LW
from neutron.plugins.vmware.common import exceptions as nsx_exc
LOG = logging.getLogger(__name__)
class AgentModes:
AGENT = 'agent'
@ -153,40 +158,87 @@ cluster_opts = [
]
DEFAULT_STATUS_CHECK_INTERVAL = 2000
DEFAULT_MINIMUM_POOLED_EDGES = 1
DEFAULT_MAXIMUM_POOLED_EDGES = 3
DEFAULT_MAXIMUM_TUNNELS_PER_VNIC = 20
vcns_opts = [
nsxv_opts = [
cfg.StrOpt('user',
default='admin',
deprecated_group="vcns",
help=_('User name for vsm')),
cfg.StrOpt('password',
default='default',
deprecated_group="vcns",
secret=True,
help=_('Password for vsm')),
cfg.StrOpt('manager_uri',
deprecated_group="vcns",
help=_('uri for vsm')),
cfg.ListOpt('cluster_moid',
default=[],
help=_('Parameter listing the IDs of the clusters '
'which are used by OpenStack.')),
cfg.StrOpt('datacenter_moid',
deprecated_group="vcns",
help=_('Optional parameter identifying the ID of datacenter '
'to deploy NSX Edges')),
cfg.StrOpt('deployment_container_id',
deprecated_group="vcns",
help=_('Optional parameter identifying the ID of datastore to '
'deploy NSX Edges')),
cfg.StrOpt('resource_pool_id',
deprecated_group="vcns",
help=_('Optional parameter identifying the ID of resource to '
'deploy NSX Edges')),
cfg.StrOpt('datastore_id',
deprecated_group="vcns",
help=_('Optional parameter identifying the ID of datastore to '
'deploy NSX Edges')),
cfg.StrOpt('external_network',
deprecated_group="vcns",
help=_('Network ID for physical network connectivity')),
cfg.IntOpt('task_status_check_interval',
default=DEFAULT_STATUS_CHECK_INTERVAL,
help=_("Task status check interval"))
deprecated_group="vcns",
help=_("Task status check interval")),
cfg.StrOpt('vdn_scope_id',
help=_('Network scope ID for VXLAN virtual wires')),
cfg.StrOpt('dvs_id',
help=_('DVS ID for VLANs')),
cfg.IntOpt('maximum_tunnels_per_vnic',
default=DEFAULT_MAXIMUM_TUNNELS_PER_VNIC,
help=_('Maximum number of sub interfaces supported '
'per vnic in edge. The value should be in 1-110.')),
cfg.ListOpt('backup_edge_pool',
default=['service:large:4:10',
'service:compact:4:10',
'vdr:large:4:10'],
help=_('Defines edge pool using the format: '
'<edge_type>:[edge_size]:<min_edges>:<max_edges>.'
'edge_type: service,vdr. '
'edge_size: compact, large, xlarge, quadlarge '
'and default is large.')),
cfg.IntOpt('retries',
default=10,
help=_('Maximum number of API retries on endpoint.')),
cfg.StrOpt('mgt_net_moid',
help=_('Network ID for management network connectivity')),
cfg.ListOpt('mgt_net_proxy_ips',
help=_('Management network IP address for metadata proxy')),
cfg.StrOpt('mgt_net_proxy_netmask',
help=_('Management network netmask for metadata proxy')),
cfg.ListOpt('nova_metadata_ips',
help=_('IP addresses used by Nova metadata service')),
cfg.IntOpt('nova_metadata_port',
default=8775,
help=_("TCP Port used by Nova metadata server."))
]
# Register the configuration options
cfg.CONF.register_opts(connection_opts)
cfg.CONF.register_opts(cluster_opts)
cfg.CONF.register_opts(vcns_opts, group="vcns")
cfg.CONF.register_opts(nsxv_opts, group="nsxv")
cfg.CONF.register_opts(base_opts, group="NSX")
cfg.CONF.register_opts(sync_opts, group="NSX_SYNC")
@ -197,3 +249,15 @@ def validate_config_options():
error = (_("Invalid replication_mode: %s") %
cfg.CONF.NSX.replication_mode)
raise nsx_exc.NsxPluginException(err_msg=error)
def validate_nsxv_config_options():
if (cfg.CONF.nsxv.manager_uri is None or
cfg.CONF.nsxv.user is None or
cfg.CONF.nsxv.password is None):
error = _("manager_uri, user and passwork be configured!")
raise nsx_exc.NsxPluginException(err_msg=error)
if cfg.CONF.nsxv.dvs_id is None:
LOG.warning(_LW("dvs_id must be configured to support VLAN's!"))
if cfg.CONF.nsxv.vdn_scope_id is None:
LOG.warning(_LW("vdn_scope_id must be configured to support VXLAN's!"))

View File

@ -76,20 +76,6 @@ class ServiceOverQuota(n_exc.Conflict):
message = _("Quota exceeded for Vcns resource: %(overs)s: %(err_msg)s")
class RouterInUseByLBService(n_exc.InUse):
message = _("Router %(router_id)s is in use by Loadbalancer Service "
"%(vip_id)s")
class RouterInUseByFWService(n_exc.InUse):
message = _("Router %(router_id)s is in use by firewall Service "
"%(firewall_id)s")
class VcnsDriverException(NsxPluginException):
message = _("Error happened in NSX VCNS Driver: %(err_msg)s")
class ServiceClusterUnavailable(NsxPluginException):
message = _("Service cluster: '%(cluster_id)s' is unavailable. Please, "
"check NSX setup and/or configuration")

View File

@ -19,16 +19,16 @@ from neutron.extensions import multiprovidernet as mpnet
from neutron.extensions import providernet as pnet
from neutron.i18n import _LW
from neutron.openstack.common import log
from neutron.plugins.vmware.api_client import client
from neutron.plugins.vmware.api_client import exception as api_exc
from neutron.plugins.vmware.common import utils as vmw_utils
from neutron.plugins.vmware.dbexts import db as nsx_db
from neutron.plugins.vmware.dbexts import networkgw_db
from neutron.plugins.vmware import nsx_cluster
from neutron.plugins.vmware.nsxlib import l2gateway as l2gwlib
from neutron.plugins.vmware.nsxlib import router as routerlib
from neutron.plugins.vmware.nsxlib import secgroup as secgrouplib
from neutron.plugins.vmware.nsxlib import switch as switchlib
from vmware_nsx.neutron.plugins.vmware.api_client import client
from vmware_nsx.neutron.plugins.vmware.common import utils as vmw_utils
from vmware_nsx.neutron.plugins.vmware.dbexts import db as nsx_db
from vmware_nsx.neutron.plugins.vmware import nsx_cluster
from vmware_nsx.neutron.plugins.vmware.nsxlib import l2gateway as l2gwlib
from vmware_nsx.neutron.plugins.vmware.nsxlib import router as routerlib
from vmware_nsx.neutron.plugins.vmware.nsxlib import secgroup as secgrouplib
from vmware_nsx.neutron.plugins.vmware.nsxlib import switch as switchlib
LOG = log.getLogger(__name__)

View File

@ -0,0 +1,28 @@
# Copyright 2014 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.
# Edge size
COMPACT = 'compact'
LARGE = 'large'
XLARGE = 'xlarge'
QUADLARGE = 'quadlarge'
# Edge type
SERVICE_EDGE = 'service'
VDR_EDGE = 'vdr'
# Internal element purpose
INTER_EDGE_PURPOSE = 'inter_edge_net'

View File

@ -14,7 +14,7 @@
# under the License.
from neutron.openstack.common import log
from neutron.plugins.vmware.common import nsx_utils
from vmware_nsx.neutron.plugins.vmware.common import nsx_utils
LOG = log.getLogger(__name__)
# Protocol number look up for supported protocols

View File

@ -30,10 +30,10 @@ from neutron.openstack.common import log
from neutron.openstack.common import loopingcall
from neutron.plugins.vmware.api_client import exception as api_exc
from neutron.plugins.vmware.common import exceptions as nsx_exc
from neutron.plugins.vmware.common import nsx_utils
from neutron.plugins.vmware import nsxlib
from neutron.plugins.vmware.nsxlib import router as routerlib
from neutron.plugins.vmware.nsxlib import switch as switchlib
from vmware_nsx.neutron.plugins.vmware.common import nsx_utils
from vmware_nsx.neutron.plugins.vmware import nsxlib
from vmware_nsx.neutron.plugins.vmware.nsxlib import router as routerlib
from vmware_nsx.neutron.plugins.vmware.nsxlib import switch as switchlib
# Maximum page size for a single request
# NOTE(salv-orlando): This might become a version-dependent map should the

View File

@ -36,6 +36,15 @@ class NetworkTypes:
BRIDGE = 'bridge'
# Allowed network types for the NSX-v Plugin
class NsxVNetworkTypes:
"""Allowed provider network types for the NSX-v Plugin."""
FLAT = 'flat'
VLAN = 'vlan'
VXLAN = 'vxlan'
PORTGROUP = 'portgroup'
def get_tags(**kwargs):
tags = ([dict(tag=value, scope=key)
for key, value in kwargs.iteritems()])

View File

@ -1,5 +1,6 @@
# 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
@ -13,15 +14,16 @@
# under the License.
#
from neutron.db import l3_dvr_db
from vmware_nsx.neutron.plugins.vmware.extensions import servicerouter
from vmware_nsx.neutron.plugins.vmware.dbexts import nsxrouter
from vmware_nsx.neutron.plugins.vmware.extensions import (
distributedrouter as dist_rtr)
class ServiceRouter_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin):
"""Mixin class to enable service router support."""
class DistributedRouter_mixin(nsxrouter.NsxRouterMixin):
"""Mixin class to enable distributed router support."""
extra_attributes = (
l3_dvr_db.L3_NAT_with_dvr_db_mixin.extra_attributes + [{
'name': servicerouter.SERVICE_ROUTER,
nsx_attributes = (
nsxrouter.NsxRouterMixin.nsx_attributes + [{
'name': dist_rtr.DISTRIBUTED,
'default': False
}])

View File

@ -1,132 +0,0 @@
# Copyright 2014 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.
#
from oslo.db import exception as d_exc
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import orm
from sqlalchemy import String
from neutron.db import models_v2
from neutron.openstack.common import log as logging
from neutron.plugins.vmware.common import exceptions as p_exc
LOG = logging.getLogger(__name__)
class LsnPort(models_v2.model_base.BASEV2):
__tablename__ = 'lsn_port'
lsn_port_id = Column(String(36), primary_key=True)
lsn_id = Column(String(36), ForeignKey('lsn.lsn_id', ondelete="CASCADE"),
nullable=False)
sub_id = Column(String(36), nullable=False, unique=True)
mac_addr = Column(String(32), nullable=False, unique=True)
def __init__(self, lsn_port_id, subnet_id, mac_address, lsn_id):
self.lsn_port_id = lsn_port_id
self.lsn_id = lsn_id
self.sub_id = subnet_id
self.mac_addr = mac_address
class Lsn(models_v2.model_base.BASEV2):
__tablename__ = 'lsn'
lsn_id = Column(String(36), primary_key=True)
net_id = Column(String(36), nullable=False)
def __init__(self, net_id, lsn_id):
self.net_id = net_id
self.lsn_id = lsn_id
def lsn_add(context, network_id, lsn_id):
"""Add Logical Service Node information to persistent datastore."""
with context.session.begin(subtransactions=True):
lsn = Lsn(network_id, lsn_id)
context.session.add(lsn)
def lsn_remove(context, lsn_id):
"""Remove Logical Service Node information from datastore given its id."""
with context.session.begin(subtransactions=True):
context.session.query(Lsn).filter_by(lsn_id=lsn_id).delete()
def lsn_remove_for_network(context, network_id):
"""Remove information about the Logical Service Node given its network."""
with context.session.begin(subtransactions=True):
context.session.query(Lsn).filter_by(net_id=network_id).delete()
def lsn_get_for_network(context, network_id, raise_on_err=True):
"""Retrieve LSN information given its network id."""
query = context.session.query(Lsn)
try:
return query.filter_by(net_id=network_id).one()
except (orm.exc.NoResultFound, d_exc.DBError):
msg = _('Unable to find Logical Service Node for network %s')
if raise_on_err:
LOG.error(msg, network_id)
raise p_exc.LsnNotFound(entity='network',
entity_id=network_id)
else:
LOG.warn(msg, network_id)
def lsn_port_add_for_lsn(context, lsn_port_id, subnet_id, mac, lsn_id):
"""Add Logical Service Node Port information to persistent datastore."""
with context.session.begin(subtransactions=True):
lsn_port = LsnPort(lsn_port_id, subnet_id, mac, lsn_id)
context.session.add(lsn_port)
def lsn_port_get_for_subnet(context, subnet_id, raise_on_err=True):
"""Return Logical Service Node Port information given its subnet id."""
with context.session.begin(subtransactions=True):
try:
return (context.session.query(LsnPort).
filter_by(sub_id=subnet_id).one())
except (orm.exc.NoResultFound, d_exc.DBError):
if raise_on_err:
raise p_exc.LsnPortNotFound(lsn_id=None,
entity='subnet',
entity_id=subnet_id)
def lsn_port_get_for_mac(context, mac_address, raise_on_err=True):
"""Return Logical Service Node Port information given its mac address."""
with context.session.begin(subtransactions=True):
try:
return (context.session.query(LsnPort).
filter_by(mac_addr=mac_address).one())
except (orm.exc.NoResultFound, d_exc.DBError):
if raise_on_err:
raise p_exc.LsnPortNotFound(lsn_id=None,
entity='mac',
entity_id=mac_address)
def lsn_port_remove(context, lsn_port_id):
"""Remove Logical Service Node port from the given Logical Service Node."""
with context.session.begin(subtransactions=True):
(context.session.query(LsnPort).
filter_by(lsn_port_id=lsn_port_id).delete())

View File

@ -1,78 +0,0 @@
# 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
from neutron.api.v2 import attributes
from neutron.db import db_base_plugin_v2
from neutron.db import model_base
from neutron.db import models_v2
from neutron.openstack.common import log as logging
from neutron.plugins.vmware.extensions import maclearning as mac
LOG = logging.getLogger(__name__)
class MacLearningState(model_base.BASEV2):
port_id = sa.Column(sa.String(36),
sa.ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
mac_learning_enabled = sa.Column(sa.Boolean(), nullable=False)
# Add a relationship to the Port model using the backref attribute.
# This will instruct SQLAlchemy to eagerly load this association.
port = orm.relationship(
models_v2.Port,
backref=orm.backref("mac_learning_state", lazy='joined',
uselist=False, cascade='delete'))
class MacLearningDbMixin(object):
"""Mixin class for mac learning."""
def _make_mac_learning_state_dict(self, port, fields=None):
res = {'port_id': port['port_id'],
mac.MAC_LEARNING: port[mac.MAC_LEARNING]}
return self._fields(res, fields)
def _extend_port_mac_learning_state(self, port_res, port_db):
state = port_db.mac_learning_state
if state and state.mac_learning_enabled:
port_res[mac.MAC_LEARNING] = state.mac_learning_enabled
# Register dict extend functions for ports
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.PORTS, ['_extend_port_mac_learning_state'])
def _update_mac_learning_state(self, context, port_id, enabled):
try:
query = self._model_query(context, MacLearningState)
state = query.filter(MacLearningState.port_id == port_id).one()
state.update({mac.MAC_LEARNING: enabled})
except exc.NoResultFound:
self._create_mac_learning_state(context,
{'id': port_id,
mac.MAC_LEARNING: enabled})
def _create_mac_learning_state(self, context, port):
with context.session.begin(subtransactions=True):
enabled = port[mac.MAC_LEARNING]
state = MacLearningState(port_id=port['id'],
mac_learning_enabled=enabled)
context.session.add(state)
return self._make_mac_learning_state_dict(state)

View File

@ -1,117 +0,0 @@
# 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.
from sqlalchemy import Column, Enum, ForeignKey, Integer, String
from neutron.db import model_base
class TzNetworkBinding(model_base.BASEV2):
"""Represents a binding of a virtual network with a transport zone.
This model class associates a Neutron network with a transport zone;
optionally a vlan ID might be used if the binding type is 'bridge'
"""
__tablename__ = 'tz_network_bindings'
# TODO(arosen) - it might be worth while refactoring the how this data
# is stored later so every column does not need to be a primary key.
network_id = Column(String(36),
ForeignKey('networks.id', ondelete="CASCADE"),
primary_key=True)
# 'flat', 'vlan', stt' or 'gre'
binding_type = Column(Enum('flat', 'vlan', 'stt', 'gre', 'l3_ext',
name='tz_network_bindings_binding_type'),
nullable=False, primary_key=True)
phy_uuid = Column(String(36), primary_key=True, default='')
vlan_id = Column(Integer, primary_key=True, autoincrement=False, default=0)
def __init__(self, network_id, binding_type, phy_uuid, vlan_id):
self.network_id = network_id
self.binding_type = binding_type
self.phy_uuid = phy_uuid
self.vlan_id = vlan_id
def __repr__(self):
return "<NetworkBinding(%s,%s,%s,%s)>" % (self.network_id,
self.binding_type,
self.phy_uuid,
self.vlan_id)
class NeutronNsxNetworkMapping(model_base.BASEV2):
"""Maps neutron network identifiers to NSX identifiers.
Because of chained logical switches more than one mapping might exist
for a single Neutron network.
"""
__tablename__ = 'neutron_nsx_network_mappings'
neutron_id = Column(String(36),
ForeignKey('networks.id', ondelete='CASCADE'),
primary_key=True)
nsx_id = Column(String(36), primary_key=True)
class NeutronNsxSecurityGroupMapping(model_base.BASEV2):
"""Backend mappings for Neutron Security Group identifiers.
This class maps a neutron security group identifier to the corresponding
NSX security profile identifier.
"""
__tablename__ = 'neutron_nsx_security_group_mappings'
neutron_id = Column(String(36),
ForeignKey('securitygroups.id', ondelete="CASCADE"),
primary_key=True)
nsx_id = Column(String(36), primary_key=True)
class NeutronNsxPortMapping(model_base.BASEV2):
"""Represents the mapping between neutron and nsx port uuids."""
__tablename__ = 'neutron_nsx_port_mappings'
neutron_id = Column(String(36),
ForeignKey('ports.id', ondelete="CASCADE"),
primary_key=True)
nsx_switch_id = Column(String(36))
nsx_port_id = Column(String(36), nullable=False)
def __init__(self, neutron_id, nsx_switch_id, nsx_port_id):
self.neutron_id = neutron_id
self.nsx_switch_id = nsx_switch_id
self.nsx_port_id = nsx_port_id
class NeutronNsxRouterMapping(model_base.BASEV2):
"""Maps neutron router identifiers to NSX identifiers."""
__tablename__ = 'neutron_nsx_router_mappings'
neutron_id = Column(String(36),
ForeignKey('routers.id', ondelete='CASCADE'),
primary_key=True)
nsx_id = Column(String(36))
class MultiProviderNetworks(model_base.BASEV2):
"""Networks provisioned through multiprovider extension."""
__tablename__ = 'multi_provider_networks'
network_id = Column(String(36),
ForeignKey('networks.id', ondelete="CASCADE"),
primary_key=True)
def __init__(self, network_id):
self.network_id = network_id

View File

@ -1,521 +0,0 @@
# 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 neutron.api.v2 import attributes
from neutron.common import exceptions
from neutron.common import utils
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"
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 GatewayDeviceInUse(exceptions.InUse):
message = _("Network Gateway Device '%(device_id)s' is still used by "
"one or more network gateways.")
class GatewayDeviceNotFound(exceptions.NotFound):
message = _("Network Gateway Device %(device_id)s could not be found.")
class GatewayDevicesNotFound(exceptions.NotFound):
message = _("One or more Network Gateway Devices could not be found: "
"%(device_ids)s.")
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.Conflict):
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")
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 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)
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))
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(NetworkGatewayDeviceReference,
backref='networkgateways',
cascade='all,delete')
network_connections = orm.relationship(NetworkConnection, lazy='joined')
class NetworkGatewayMixin(networkgw.NetworkGatewayPluginBase):
gateway_resource = networkgw.GATEWAY_RESOURCE_NAME
device_resource = networkgw.DEVICE_RESOURCE_NAME
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 not fields or 'ports' in 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)
# The NSX plugin accepts 0 as a valid vlan tag
seg_id_valid = seg_id == 0 or utils.is_valid_vlan_tag(seg_id)
if 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)
elif (seg_type.lower() == 'vlan' and not seg_id_valid):
msg = _("Invalid segmentation id (%d) for "
"vlan segmentation type") % seg_id
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 query.one() if only_one else 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 _validate_device_list(self, context, tenant_id, gateway_data):
device_query = self._query_gateway_devices(
context, filters={'id': [device['id']
for device in gateway_data['devices']]})
retrieved_device_ids = set()
for device in device_query:
retrieved_device_ids.add(device['id'])
if device['tenant_id'] != tenant_id:
raise GatewayDeviceNotFound(device_id=device['id'])
missing_device_ids = (
set(device['id'] for device in gateway_data['devices']) -
retrieved_device_ids)
if missing_device_ids:
raise GatewayDevicesNotFound(
device_ids=",".join(missing_device_ids))
def create_network_gateway(self, context, network_gateway,
validate_device_list=True):
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, but some devices
# might still either not exist or belong to a different tenant
if validate_device_list:
self._validate_device_list(context, tenant_id, gw_data)
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)
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)
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,
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)
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}}
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 NSX 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)
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)
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)
def _is_device_in_use(self, context, device_id):
query = self._get_collection_query(
context, NetworkGatewayDeviceReference, {'id': [device_id]})
return query.first()
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)
def _query_gateway_devices(self, context,
filters=None, sorts=None,
limit=None, marker=None,
page_reverse=None):
marker_obj = self._get_marker_obj(
context, 'gateway_device', limit, marker)
return self._get_collection_query(context,
NetworkGatewayDevice,
filters=filters,
sorts=sorts,
limit=limit,
marker_obj=marker_obj,
page_reverse=page_reverse)
def get_gateway_devices(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False, include_nsx_id=False):
query = self._query_gateway_devices(context, filters, sorts, limit,
marker, page_reverse)
return [self._make_gateway_device_dict(row, fields, include_nsx_id)
for row in query]
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)
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)
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)

View File

@ -0,0 +1,66 @@
# 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.
#
from neutron.db import db_base_plugin_v2
from neutron.extensions import l3
from neutron.openstack.common import log as logging
from neutron.plugins.vmware.dbexts import nsxv_models
LOG = logging.getLogger(__name__)
class NsxRouterMixin(object):
"""Mixin class to enable nsx router support."""
nsx_attributes = []
def _extend_nsx_router_dict(self, router_res, router_db):
nsx_attrs = router_db['nsx_attributes']
# Return False if nsx attributes are not definied for this
# neutron router
for attr in self.nsx_attributes:
name = attr['name']
default = attr['default']
router_res[name] = (
nsx_attrs and nsx_attrs[name] or default)
def _process_nsx_router_create(
self, context, router_db, router_req):
if not router_db['nsx_attributes']:
kwargs = {}
for attr in self.nsx_attributes:
name = attr['name']
default = attr['default']
kwargs[name] = router_req.get(name, default)
nsx_attributes = nsxv_models.NsxvRouterExtAttributes(
router_id=router_db['id'], **kwargs)
context.session.add(nsx_attributes)
router_db['nsx_attributes'] = nsx_attributes
else:
# The situation where the record already exists will
# be likely once the NSXRouterExtAttributes model
# will allow for defining several attributes pertaining
# to different extensions
for attr in self.nsx_attributes:
name = attr['name']
default = attr['default']
router_db['nsx_attributes'][name] = router_req.get(
name, default)
LOG.debug("Nsx router extension successfully processed "
"for router:%s", router_db['id'])
# Register dict extend functions for ports
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
l3.ROUTERS, ['_extend_nsx_router_dict'])

View File

@ -0,0 +1,435 @@
# 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.
from oslo.db import exception as db_exc
from oslo.utils import excutils
from sqlalchemy.orm import exc