Merge "Add migration support from agent to NSX dhcp/metadata services"

This commit is contained in:
Jenkins 2014-02-12 10:22:32 +00:00 committed by Gerrit Code Review
commit c900327377
23 changed files with 2161 additions and 714 deletions

View File

@ -119,11 +119,18 @@
# metadata proxy services to tenant instances. If 'agent' is chosen (default)
# the NSX plugin relies on external RPC agents (i.e. dhcp and metadata agents) to
# provide such services. In this mode, the plugin supports API extensions 'agent'
# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Havana),
# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Icehouse),
# the plugin will use NSX logical services for DHCP and metadata proxy. This
# simplifies the deployment model for Neutron, in that the plugin no longer requires
# the RPC agents to operate. When 'agentless' is chosen, the config option metadata_mode
# becomes ineffective. The mode 'agentless' is not supported for NSX 4.0 or below.
# becomes ineffective. The 'agentless' mode is supported from NSX 4.2 or above.
# Furthermore, a 'combined' mode is also provided and is used to support existing
# deployments that want to adopt the agentless mode going forward. With this mode,
# existing networks keep being served by the existing infrastructure (thus preserving
# backward compatibility, whereas new networks will be served by the new infrastructure.
# Migration tools are provided to 'move' one network from one model to another; with
# agent_mode set to 'combined', option 'network_auto_schedule' in neutron.conf is
# ignored, as new networks will no longer be scheduled to existing dhcp agents.
# agent_mode = agent
[nsx_sync]
@ -165,6 +172,12 @@
# a considerable impact on overall performance.
# always_read_status = False
[nsx_lsn]
# Pull LSN information from NSX in case it is missing from the local
# data store. This is useful to rebuild the local store in case of
# server recovery
# sync_on_missing_data = False
[nsx_dhcp]
# (Optional) Comma separated list of additional dns servers. Default is an empty list
# extra_domain_name_servers =

View File

@ -114,11 +114,18 @@
# metadata proxy services to tenant instances. If 'agent' is chosen (default)
# the NSX plugin relies on external RPC agents (i.e. dhcp and metadata agents) to
# provide such services. In this mode, the plugin supports API extensions 'agent'
# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Havana),
# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Icehouse),
# the plugin will use NSX logical services for DHCP and metadata proxy. This
# simplifies the deployment model for Neutron, in that the plugin no longer requires
# the RPC agents to operate. When 'agentless' is chosen, the config option metadata_mode
# becomes ineffective. The mode 'agentless' is not supported for NSX 4.0 or below.
# becomes ineffective. The 'agentless' mode is supported from NSX 4.2 or above.
# Furthermore, a 'combined' mode is also provided and is used to support existing
# deployments that want to adopt the agentless mode going forward. With this mode,
# existing networks keep being served by the existing infrastructure (thus preserving
# backward compatibility, whereas new networks will be served by the new infrastructure.
# Migration tools are provided to 'move' one network from one model to another; with
# agent_mode set to 'combined', option 'network_auto_schedule' in neutron.conf is
# ignored, as new networks will no longer be scheduled to existing dhcp agents.
# agent_mode = agent
[nsx_sync]
@ -160,6 +167,12 @@
# a considerable impact on overall performance.
# always_read_status = False
[nsx_lsn]
# Pull LSN information from NSX in case it is missing from the local
# data store. This is useful to rebuild the local store in case of
# server recovery
# sync_on_missing_data = False
[nsx_dhcp]
# (Optional) Comma separated list of additional dns servers. Default is an empty list
# extra_domain_name_servers =

View File

@ -131,5 +131,7 @@
"delete_metering_label_rule": "rule:admin_only",
"get_metering_label_rule": "rule:admin_only",
"get_service_provider": "rule:regular_user"
"get_service_provider": "rule:regular_user",
"get_lsn": "rule:admin_only",
"create_lsn": "rule:admin_only"
}

View File

@ -0,0 +1,72 @@
# 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.
#
"""NSX DHCP/metadata support
Revision ID: 1421183d533f
Revises: 8f682276ee4
Create Date: 2013-10-11 14:33:37.303215
"""
revision = '1421183d533f'
down_revision = '8f682276ee4'
migration_for_plugins = [
'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2',
'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin'
]
from alembic import op
import sqlalchemy as sa
from neutron.db import migration
def upgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.create_table(
'lsn',
sa.Column('net_id',
sa.String(length=36), nullable=False),
sa.Column('lsn_id',
sa.String(length=36), nullable=False),
sa.PrimaryKeyConstraint('lsn_id'))
op.create_table(
'lsn_port',
sa.Column('lsn_port_id',
sa.String(length=36), nullable=False),
sa.Column('lsn_id',
sa.String(length=36), nullable=False),
sa.Column('sub_id',
sa.String(length=36), nullable=False, unique=True),
sa.Column('mac_addr',
sa.String(length=32), nullable=False, unique=True),
sa.ForeignKeyConstraint(['lsn_id'], ['lsn.lsn_id'],
ondelete='CASCADE'),
sa.PrimaryKeyConstraint('lsn_port_id'))
def downgrade(active_plugins=None, options=None):
if not migration.should_run(active_plugins, migration_for_plugins):
return
op.drop_table('lsn_port')
op.drop_table('lsn')

View File

@ -120,10 +120,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
functionality using NVP.
"""
supported_extension_aliases = ["agent",
"allowed-address-pairs",
supported_extension_aliases = ["allowed-address-pairs",
"binding",
"dhcp_agent_scheduler",
"dist-router",
"ext-gw-mode",
"extraroute",

View File

@ -19,9 +19,8 @@ from oslo.config import cfg
class AgentModes:
AGENT = 'agent'
# TODO(armando-migliaccio): support to be added, maybe we could add a
# mixed mode to support no-downtime migrations?
AGENTLESS = 'agentless'
COMBINED = 'combined'
class MetadataModes:

View File

@ -105,5 +105,9 @@ class LsnPortNotFound(q_exc.NotFound):
'and %(entity)s %(entity_id)s'))
class LsnMigrationConflict(q_exc.Conflict):
message = _("Unable to migrate network '%(net_id)s' to LSN: %(reason)s")
class LsnConfigurationConflict(NvpPluginException):
message = _("Configuration conflict on Logical Service Node %(lsn_id)s")

View File

@ -0,0 +1,130 @@
# 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 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.db import exception as d_exc
from neutron.openstack.common import log as logging
from neutron.plugins.nicira.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"))
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):
logger = raise_on_err and LOG.error or LOG.warn
logger(_('Unable to find Logical Service Node for '
'network %s'), network_id)
if raise_on_err:
raise p_exc.LsnNotFound(entity='network',
entity_id=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

@ -0,0 +1,95 @@
# 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 neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.common import constants as const
from neutron.common import topics
from neutron.plugins.nicira.dhcp_meta import nsx as nsx_svc
from neutron.plugins.nicira.dhcp_meta import rpc as nsx_rpc
class DhcpAgentNotifyAPI(dhcp_rpc_agent_api.DhcpAgentNotifyAPI):
def __init__(self, plugin, manager):
super(DhcpAgentNotifyAPI, self).__init__(topic=topics.DHCP_AGENT)
self.agentless_notifier = nsx_svc.DhcpAgentNotifyAPI(plugin, manager)
def notify(self, context, data, methodname):
[resource, action, _e] = methodname.split('.')
lsn_manager = self.agentless_notifier.plugin.lsn_manager
plugin = self.agentless_notifier.plugin
if resource == 'network':
net_id = data['network']['id']
elif resource in ['port', 'subnet']:
net_id = data[resource]['network_id']
else:
# no valid resource
return
lsn_exists = lsn_manager.lsn_exists(context, net_id)
treat_dhcp_owner_specially = False
if lsn_exists:
# if lsn exists, the network is one created with the new model
if (resource == 'subnet' and action == 'create' and
const.DEVICE_OWNER_DHCP not in plugin.port_special_owners):
# network/subnet provisioned in the new model have a plain
# nsx lswitch port, no vif attachment
plugin.port_special_owners.append(const.DEVICE_OWNER_DHCP)
treat_dhcp_owner_specially = True
if (resource == 'port' and action == 'update' or
resource == 'subnet'):
self.agentless_notifier.notify(context, data, methodname)
elif not lsn_exists and resource in ['port', 'subnet']:
# call notifier for the agent-based mode
super(DhcpAgentNotifyAPI, self).notify(context, data, methodname)
if treat_dhcp_owner_specially:
# if subnets belong to networks created with the old model
# dhcp port does not need to be special cased, so put things
# back, since they were modified
plugin.port_special_owners.remove(const.DEVICE_OWNER_DHCP)
def handle_network_dhcp_access(plugin, context, network, action):
nsx_svc.handle_network_dhcp_access(plugin, context, network, action)
def handle_port_dhcp_access(plugin, context, port, action):
if plugin.lsn_manager.lsn_exists(context, port['network_id']):
nsx_svc.handle_port_dhcp_access(plugin, context, port, action)
else:
nsx_rpc.handle_port_dhcp_access(plugin, context, port, action)
def handle_port_metadata_access(plugin, context, port, is_delete=False):
if plugin.lsn_manager.lsn_exists(context, port['network_id']):
nsx_svc.handle_port_metadata_access(plugin, context, port, is_delete)
else:
nsx_rpc.handle_port_metadata_access(plugin, context, port, is_delete)
def handle_router_metadata_access(plugin, context, router_id, interface=None):
if interface:
subnet = plugin.get_subnet(context, interface['subnet_id'])
network_id = subnet['network_id']
if plugin.lsn_manager.lsn_exists(context, network_id):
nsx_svc.handle_router_metadata_access(
plugin, context, router_id, interface)
else:
nsx_rpc.handle_router_metadata_access(
plugin, context, router_id, interface)
else:
nsx_rpc.handle_router_metadata_access(
plugin, context, router_id, interface)

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.
#
from neutron.common import constants as const
from neutron.db import l3_db
# A unique MAC to quickly identify the LSN port used for metadata services
# when dhcp on the subnet is off. Inspired by leet-speak for 'metadata'.
METADATA_MAC = "fa:15:73:74:d4:74"
METADATA_PORT_ID = 'metadata:id'
METADATA_PORT_NAME = 'metadata:name'
METADATA_DEVICE_ID = 'metadata:device'
SPECIAL_OWNERS = (const.DEVICE_OWNER_DHCP,
const.DEVICE_OWNER_ROUTER_GW,
l3_db.DEVICE_OWNER_ROUTER_INTF)

View File

@ -0,0 +1,449 @@
# 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.config import cfg
from neutron.common import exceptions as n_exc
from neutron.openstack.common.db import exception as db_exc
from neutron.openstack.common import log as logging
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.dbexts import lsn_db
from neutron.plugins.nicira.dhcp_meta import constants as const
from neutron.plugins.nicira.nsxlib import lsn as lsn_api
from neutron.plugins.nicira import nvplib as nsxlib
LOG = logging.getLogger(__name__)
META_CONF = 'metadata-proxy'
DHCP_CONF = 'dhcp'
lsn_opts = [
cfg.BoolOpt('sync_on_missing_data', default=False,
help=_('Pull LSN information from NSX in case it is missing '
'from the local data store. This is useful to rebuild '
'the local store in case of server recovery.'))
]
def register_lsn_opts(config):
config.CONF.register_opts(lsn_opts, "NSX_LSN")
class LsnManager(object):
"""Manage LSN entities associated with networks."""
def __init__(self, plugin):
self.plugin = plugin
@property
def cluster(self):
return self.plugin.cluster
def lsn_exists(self, context, network_id):
"""Return True if a Logical Service Node exists for the network."""
return self.lsn_get(
context, network_id, raise_on_err=False) is not None
def lsn_get(self, context, network_id, raise_on_err=True):
"""Retrieve the LSN id associated to the network."""
try:
return lsn_api.lsn_for_network_get(self.cluster, network_id)
except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException):
logger = raise_on_err and LOG.error or LOG.warn
logger(_('Unable to find Logical Service Node for '
'network %s'), network_id)
if raise_on_err:
raise p_exc.LsnNotFound(entity='network',
entity_id=network_id)
def lsn_create(self, context, network_id):
"""Create a LSN associated to the network."""
try:
return lsn_api.lsn_for_network_create(self.cluster, network_id)
except nsxlib.NvpApiClient.NvpApiException:
err_msg = _('Unable to create LSN for network %s') % network_id
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_delete(self, context, lsn_id):
"""Delete a LSN given its id."""
try:
lsn_api.lsn_delete(self.cluster, lsn_id)
except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException):
LOG.warn(_('Unable to delete Logical Service Node %s'), lsn_id)
def lsn_delete_by_network(self, context, network_id):
"""Delete a LSN associated to the network."""
lsn_id = self.lsn_get(context, network_id, raise_on_err=False)
if lsn_id:
self.lsn_delete(context, lsn_id)
def lsn_port_get(self, context, network_id, subnet_id, raise_on_err=True):
"""Retrieve LSN and LSN port for the network and the subnet."""
lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err)
if lsn_id:
try:
lsn_port_id = lsn_api.lsn_port_by_subnet_get(
self.cluster, lsn_id, subnet_id)
except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException):
logger = raise_on_err and LOG.error or LOG.warn
logger(_('Unable to find Logical Service Node Port for '
'LSN %(lsn_id)s and subnet %(subnet_id)s')
% {'lsn_id': lsn_id, 'subnet_id': subnet_id})
if raise_on_err:
raise p_exc.LsnPortNotFound(lsn_id=lsn_id,
entity='subnet',
entity_id=subnet_id)
return (lsn_id, None)
else:
return (lsn_id, lsn_port_id)
else:
return (None, None)
def lsn_port_get_by_mac(self, context, network_id, mac, raise_on_err=True):
"""Retrieve LSN and LSN port given network and mac address."""
lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err)
if lsn_id:
try:
lsn_port_id = lsn_api.lsn_port_by_mac_get(
self.cluster, lsn_id, mac)
except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException):
logger = raise_on_err and LOG.error or LOG.warn
logger(_('Unable to find Logical Service Node Port for '
'LSN %(lsn_id)s and mac address %(mac)s')
% {'lsn_id': lsn_id, 'mac': mac})
if raise_on_err:
raise p_exc.LsnPortNotFound(lsn_id=lsn_id,
entity='MAC',
entity_id=mac)
return (lsn_id, None)
else:
return (lsn_id, lsn_port_id)
else:
return (None, None)
def lsn_port_create(self, context, lsn_id, subnet_info):
"""Create and return LSN port for associated subnet."""
try:
return lsn_api.lsn_port_create(self.cluster, lsn_id, subnet_info)
except n_exc.NotFound:
raise p_exc.LsnNotFound(entity='', entity_id=lsn_id)
except nsxlib.NvpApiClient.NvpApiException:
err_msg = _('Unable to create port for LSN %s') % lsn_id
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_port_delete(self, context, lsn_id, lsn_port_id):
"""Delete a LSN port from the Logical Service Node."""
try:
lsn_api.lsn_port_delete(self.cluster, lsn_id, lsn_port_id)
except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException):
LOG.warn(_('Unable to delete LSN Port %s'), lsn_port_id)
def lsn_port_dispose(self, context, network_id, mac_address):
"""Delete a LSN port given the network and the mac address."""
lsn_id, lsn_port_id = self.lsn_port_get_by_mac(
context, network_id, mac_address, raise_on_err=False)
if lsn_port_id:
self.lsn_port_delete(context, lsn_id, lsn_port_id)
if mac_address == const.METADATA_MAC:
try:
lswitch_port_id = nsxlib.get_port_by_neutron_tag(
self.cluster, network_id,
const.METADATA_PORT_ID)['uuid']
nsxlib.delete_port(
self.cluster, network_id, lswitch_port_id)
except (n_exc.PortNotFoundOnNetwork,
nsxlib.NvpApiClient.NvpApiException):
LOG.warn(_("Metadata port not found while attempting "
"to delete it from network %s"), network_id)
else:
LOG.warn(_("Unable to find Logical Services Node "
"Port with MAC %s"), mac_address)
def lsn_port_dhcp_setup(
self, context, network_id, port_id, port_data, subnet_config=None):
"""Connect network to LSN via specified port and port_data."""
try:
lsn_id = None
lswitch_port_id = nsxlib.get_port_by_neutron_tag(
self.cluster, network_id, port_id)['uuid']
lsn_id = self.lsn_get(context, network_id)
lsn_port_id = self.lsn_port_create(context, lsn_id, port_data)
except (n_exc.NotFound, p_exc.NvpPluginException):
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=port_id)
else:
try:
lsn_api.lsn_port_plug_network(
self.cluster, lsn_id, lsn_port_id, lswitch_port_id)
except p_exc.LsnConfigurationConflict:
self.lsn_port_delete(context, lsn_id, lsn_port_id)
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=port_id)
if subnet_config:
self.lsn_port_dhcp_configure(
context, lsn_id, lsn_port_id, subnet_config)
else:
return (lsn_id, lsn_port_id)
def lsn_port_metadata_setup(self, context, lsn_id, subnet):
"""Connect subnet to specified LSN."""
data = {
"mac_address": const.METADATA_MAC,
"ip_address": subnet['cidr'],
"subnet_id": subnet['id']
}
network_id = subnet['network_id']
tenant_id = subnet['tenant_id']
lswitch_port_id = None
try:
lswitch_port_id = nsxlib.create_lport(
self.cluster, network_id, tenant_id,
const.METADATA_PORT_ID, const.METADATA_PORT_NAME,
const.METADATA_DEVICE_ID, True)['uuid']
lsn_port_id = self.lsn_port_create(self.cluster, lsn_id, data)
except (n_exc.NotFound, p_exc.NvpPluginException,
nsxlib.NvpApiClient.NvpApiException):
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lswitch_port_id)
else:
try:
lsn_api.lsn_port_plug_network(
self.cluster, lsn_id, lsn_port_id, lswitch_port_id)
except p_exc.LsnConfigurationConflict:
self.lsn_port_delete(self.cluster, lsn_id, lsn_port_id)
nsxlib.delete_port(self.cluster, network_id, lswitch_port_id)
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
def lsn_port_dhcp_configure(self, context, lsn_id, lsn_port_id, subnet):
"""Enable/disable dhcp services with the given config options."""
is_enabled = subnet["enable_dhcp"]
dhcp_options = {
"domain_name": cfg.CONF.NSX_DHCP.domain_name,
"default_lease_time": cfg.CONF.NSX_DHCP.default_lease_time,
}
dns_servers = cfg.CONF.NSX_DHCP.extra_domain_name_servers or []
dns_servers.extend(subnet["dns_nameservers"])
if subnet['gateway_ip']:
dhcp_options["routers"] = subnet["gateway_ip"]
if dns_servers:
dhcp_options["domain_name_servers"] = ",".join(dns_servers)
if subnet["host_routes"]:
dhcp_options["classless_static_routes"] = (
",".join(subnet["host_routes"])
)
try:
lsn_api.lsn_port_dhcp_configure(
self.cluster, lsn_id, lsn_port_id, is_enabled, dhcp_options)
except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException):
err_msg = (_('Unable to configure dhcp for Logical Service '
'Node %(lsn_id)s and port %(lsn_port_id)s')
% {'lsn_id': lsn_id, 'lsn_port_id': lsn_port_id})
LOG.error(err_msg)
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_metadata_configure(self, context, subnet_id, is_enabled):
"""Configure metadata service for the specified subnet."""
subnet = self.plugin.get_subnet(context, subnet_id)
network_id = subnet['network_id']
meta_conf = cfg.CONF.NSX_METADATA
metadata_options = {
'metadata_server_ip': meta_conf.metadata_server_address,
'metadata_server_port': meta_conf.metadata_server_port,
'metadata_proxy_shared_secret': meta_conf.metadata_shared_secret
}
try:
lsn_id = self.lsn_get(context, network_id)
lsn_api.lsn_metadata_configure(
self.cluster, lsn_id, is_enabled, metadata_options)
except (p_exc.LsnNotFound, nsxlib.NvpApiClient.NvpApiException):
err_msg = (_('Unable to configure metadata '
'for subnet %s') % subnet_id)
LOG.error(err_msg)
raise p_exc.NvpPluginException(err_msg=err_msg)
if is_enabled:
try:
# test that the lsn port exists
self.lsn_port_get(context, network_id, subnet_id)
except p_exc.LsnPortNotFound:
# this might happen if subnet had dhcp off when created
# so create one, and wire it
self.lsn_port_metadata_setup(context, lsn_id, subnet)
else:
self.lsn_port_dispose(context, network_id, const.METADATA_MAC)
def _lsn_port_host_conf(self, context, network_id, subnet_id, data, hdlr):
lsn_id = None
lsn_port_id = None
try:
lsn_id, lsn_port_id = self.lsn_port_get(
context, network_id, subnet_id)
hdlr(self.cluster, lsn_id, lsn_port_id, data)
except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException):
LOG.error(_('Error while configuring LSN '
'port %s'), lsn_port_id)
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
def lsn_port_dhcp_host_add(self, context, network_id, subnet_id, host):
"""Add dhcp host entry to LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_dhcp_host_add)
def lsn_port_dhcp_host_remove(self, context, network_id, subnet_id, host):
"""Remove dhcp host entry from LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_dhcp_host_remove)
def lsn_port_meta_host_add(self, context, network_id, subnet_id, host):
"""Add dhcp host entry to LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_metadata_host_add)
def lsn_port_meta_host_remove(self, context, network_id, subnet_id, host):
"""Remove dhcp host entry from LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_metadata_host_remove)
def lsn_port_update(
self, context, network_id, subnet_id, dhcp=None, meta=None):
"""Update the specified configuration for the LSN port."""
if not dhcp and not meta:
return
try:
lsn_id, lsn_port_id = self.lsn_port_get(
context, network_id, subnet_id, raise_on_err=False)
if dhcp and lsn_id and lsn_port_id:
lsn_api.lsn_port_host_entries_update(
self.cluster, lsn_id, lsn_port_id, DHCP_CONF, dhcp)
if meta and lsn_id and lsn_port_id:
lsn_api.lsn_port_host_entries_update(
self.cluster, lsn_id, lsn_port_id, META_CONF, meta)
except nsxlib.NvpApiClient.NvpApiException:
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
class PersistentLsnManager(LsnManager):
"""Add local persistent state to LSN Manager."""
def __init__(self, plugin):
super(PersistentLsnManager, self).__init__(plugin)
self.sync_on_missing = cfg.CONF.NSX_LSN.sync_on_missing_data
def lsn_get(self, context, network_id, raise_on_err=True):
try:
obj = lsn_db.lsn_get_for_network(
context, network_id, raise_on_err=raise_on_err)
return obj.lsn_id if obj else None
except p_exc.LsnNotFound:
if self.sync_on_missing:
lsn_id = super(PersistentLsnManager, self).lsn_get(
context, network_id, raise_on_err=raise_on_err)
self.lsn_save(context, network_id, lsn_id)
return lsn_id
if raise_on_err:
raise
def lsn_save(self, context, network_id, lsn_id):
"""Save LSN-Network mapping to the DB."""
try:
lsn_db.lsn_add(context, network_id, lsn_id)
except db_exc.DBError:
err_msg = _('Unable to save LSN for network %s') % network_id
LOG.exception(err_msg)
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_create(self, context, network_id):
lsn_id = super(PersistentLsnManager,
self).lsn_create(context, network_id)
try:
self.lsn_save(context, network_id, lsn_id)
except p_exc.NvpPluginException:
super(PersistentLsnManager, self).lsn_delete(context, lsn_id)
raise
return lsn_id
def lsn_delete(self, context, lsn_id):
lsn_db.lsn_remove(context, lsn_id)
super(PersistentLsnManager, self).lsn_delete(context, lsn_id)
def lsn_port_get(self, context, network_id, subnet_id, raise_on_err=True):
try:
obj = lsn_db.lsn_port_get_for_subnet(
context, subnet_id, raise_on_err=raise_on_err)
return (obj.lsn_id, obj.lsn_port_id) if obj else (None, None)
except p_exc.LsnPortNotFound:
if self.sync_on_missing:
lsn_id, lsn_port_id = (
super(PersistentLsnManager, self).lsn_port_get(
context, network_id, subnet_id,
raise_on_err=raise_on_err))
mac_addr = lsn_api.lsn_port_info_get(
self.cluster, lsn_id, lsn_port_id)['mac_address']
self.lsn_port_save(
context, lsn_port_id, subnet_id, mac_addr, lsn_id)
return (lsn_id, lsn_port_id)
if raise_on_err:
raise
def lsn_port_get_by_mac(self, context, network_id, mac, raise_on_err=True):
try:
obj = lsn_db.lsn_port_get_for_mac(
context, mac, raise_on_err=raise_on_err)
return (obj.lsn_id, obj.lsn_port_id) if obj else (None, None)
except p_exc.LsnPortNotFound:
if self.sync_on_missing:
lsn_id, lsn_port_id = (
super(PersistentLsnManager, self).lsn_port_get_by_mac(
context, network_id, mac,
raise_on_err=raise_on_err))
subnet_id = lsn_api.lsn_port_info_get(
self.cluster, lsn_id, lsn_port_id).get('subnet_id')
self.lsn_port_save(
context, lsn_port_id, subnet_id, mac, lsn_id)
return (lsn_id, lsn_port_id)
if raise_on_err:
raise
def lsn_port_save(self, context, lsn_port_id, subnet_id, mac_addr, lsn_id):
"""Save LSN Port information to the DB."""
try:
lsn_db.lsn_port_add_for_lsn(
context, lsn_port_id, subnet_id, mac_addr, lsn_id)
except db_exc.DBError:
err_msg = _('Unable to save LSN port for subnet %s') % subnet_id
LOG.exception(err_msg)
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_port_create(self, context, lsn_id, subnet_info):
lsn_port_id = super(PersistentLsnManager,
self).lsn_port_create(context, lsn_id, subnet_info)
try:
self.lsn_port_save(context, lsn_port_id, subnet_info['subnet_id'],
subnet_info['mac_address'], lsn_id)
except p_exc.NvpPluginException:
super(PersistentLsnManager, self).lsn_port_delete(
context, lsn_id, lsn_port_id)
raise
return lsn_port_id
def lsn_port_delete(self, context, lsn_id, lsn_port_id):
lsn_db.lsn_port_remove(context, lsn_port_id)
super(PersistentLsnManager, self).lsn_port_delete(
context, lsn_id, lsn_port_id)

View File

@ -0,0 +1,181 @@
# 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 neutron.common import constants as const
from neutron.common import exceptions as n_exc
from neutron.extensions import external_net
from neutron.openstack.common import log as logging
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.dhcp_meta import nsx
from neutron.plugins.nicira.dhcp_meta import rpc
LOG = logging.getLogger(__name__)
class DhcpMetadataBuilder(object):
def __init__(self, plugin, agent_notifier):
self.plugin = plugin
self.notifier = agent_notifier
def dhcp_agent_get_all(self, context, network_id):
"""Return the agents managing the network."""
return self.plugin.list_dhcp_agents_hosting_network(
context, network_id)['agents']
def dhcp_port_get_all(self, context, network_id):
"""Return the dhcp ports allocated for the network."""
filters = {
'network_id': [network_id],
'device_owner': [const.DEVICE_OWNER_DHCP]
}
return self.plugin.get_ports(context, filters=filters)
def router_id_get(self, context, subnet=None):
"""Return the router and interface used for the subnet."""
if not subnet:
return
network_id = subnet['network_id']
filters = {
'network_id': [network_id],
'device_owner': [const.DEVICE_OWNER_ROUTER_INTF]
}
ports = self.plugin.get_ports(context, filters=filters)
for port in ports:
if port['fixed_ips'][0]['subnet_id'] == subnet['id']:
return port['device_id']
else:
raise n_exc.NotFound()
def metadata_deallocate(self, context, router_id, subnet_id):
"""Deallocate metadata services for the subnet."""
interface = {'subnet_id': subnet_id}
self.plugin.remove_router_interface(context, router_id, interface)
def metadata_allocate(self, context, router_id, subnet_id):
"""Allocate metadata resources for the subnet via the router."""
interface = {'subnet_id': subnet_id}
self.plugin.add_router_interface(context, router_id, interface)
def dhcp_deallocate(self, context, network_id, agents, ports):
"""Deallocate dhcp resources for the network."""
for agent in agents:
self.plugin.remove_network_from_dhcp_agent(
context, agent['id'], network_id)
for port in ports:
try:
self.plugin.delete_port(context, port['id'])
except n_exc.PortNotFound:
LOG.error(_('Port %s is already gone'), port['id'])
def dhcp_allocate(self, context, network_id, subnet):
"""Allocate dhcp resources for the subnet."""
# Create LSN resources
network_data = {'id': network_id}
nsx.handle_network_dhcp_access(self.plugin, context,
network_data, 'create_network')
if subnet:
subnet_data = {'subnet': subnet}
self.notifier.notify(context, subnet_data, 'subnet.create.end')
# Get DHCP host and metadata entries created for the LSN
port = {
'network_id': network_id,
'fixed_ips': [{'subnet_id': subnet['id']}]
}
self.notifier.notify(context, {'port': port}, 'port.update.end')
class MigrationManager(object):
def __init__(self, plugin, lsn_manager, agent_notifier):
self.plugin = plugin
self.manager = lsn_manager
self.builder = DhcpMetadataBuilder(plugin, agent_notifier)
def validate(self, context, network_id):
"""Validate and return subnet's dhcp info for migration."""
network = self.plugin.get_network(context, network_id)
if self.manager.lsn_exists(context, network_id):
reason = _("LSN already exist")
raise p_exc.LsnMigrationConflict(net_id=network_id, reason=reason)
if network[external_net.EXTERNAL]:
reason = _("Cannot migrate an external network")
raise n_exc.BadRequest(resource='network', msg=reason)
filters = {'network_id': [network_id]}
subnets = self.plugin.get_subnets(context, filters=filters)
count = len(subnets)
if count == 0:
return None
elif count == 1 and subnets[0]['cidr'] == rpc.METADATA_SUBNET_CIDR:
reason = _("Cannot migrate a 'metadata' network")
raise n_exc.BadRequest(resource='network', msg=reason)
elif count > 1:
reason = _("Unable to support multiple subnets per network")
raise p_exc.LsnMigrationConflict(net_id=network_id, reason=reason)
else:
return subnets[0]
def migrate(self, context, network_id, subnet=None):
"""Migrate subnet resources to LSN."""
router_id = self.builder.router_id_get(context, subnet)
if router_id and subnet:
# Deallocate resources taken for the router, if any
self.builder.metadata_deallocate(context, router_id, subnet['id'])
if subnet:
# Deallocate reources taken for the agent, if any
agents = self.builder.dhcp_agent_get_all(context, network_id)
ports = self.builder.dhcp_port_get_all(context, network_id)
self.builder.dhcp_deallocate(context, network_id, agents, ports)
# (re)create the configuration for LSN
self.builder.dhcp_allocate(context, network_id, subnet)
if router_id and subnet:
# Allocate resources taken for the router, if any
self.builder.metadata_allocate(context, router_id, subnet['id'])
def report(self, context, network_id, subnet_id=None):
"""Return a report of the dhcp and metadata resources in use."""
if subnet_id:
lsn_id, lsn_port_id = self.manager.lsn_port_get(
context, network_id, subnet_id, raise_on_err=False)
else:
subnet = self.validate(context, network_id)
if subnet:
lsn_id, lsn_port_id = self.manager.lsn_port_get(
context, network_id, subnet['id'], raise_on_err=False)
else:
lsn_id = self.manager.lsn_get(context, network_id,
raise_on_err=False)
lsn_port_id = None
if lsn_id:
ports = [lsn_port_id] if lsn_port_id else []
report = {
'type': 'lsn',
'services': [lsn_id],
'ports': ports
}
else:
agents = self.builder.dhcp_agent_get_all(context, network_id)
ports = self.builder.dhcp_port_get_all(context, network_id)
report = {
'type': 'agent',
'services': [a['id'] for a in agents],
'ports': [p['id'] for p in ports]
}
return report

View File

@ -0,0 +1,317 @@
# 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.config import cfg
from neutron.api.v2 import attributes as attr
from neutron.common import constants as const
from neutron.common import exceptions as n_exc
from neutron.db import db_base_plugin_v2
from neutron.db import l3_db
from neutron.extensions import external_net
from neutron.openstack.common import log as logging
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.dhcp_meta import constants as d_const
from neutron.plugins.nicira.nsxlib import lsn as lsn_api
LOG = logging.getLogger(__name__)
dhcp_opts = [
cfg.ListOpt('extra_domain_name_servers',
deprecated_group='NVP_DHCP',
default=[],
help=_('Comma separated list of additional '
'domain name servers')),
cfg.StrOpt('domain_name',
deprecated_group='NVP_DHCP',
default='openstacklocal',
help=_('Domain to use for building the hostnames')),
cfg.IntOpt('default_lease_time', default=43200,
deprecated_group='NVP_DHCP',
help=_("Default DHCP lease time")),
]
metadata_opts = [
cfg.StrOpt('metadata_server_address',
deprecated_group='NVP_METADATA',
default='127.0.0.1',
help=_("IP address used by Metadata server.")),
cfg.IntOpt('metadata_server_port',
deprecated_group='NVP_METADATA',
default=8775,
help=_("TCP Port used by Metadata server.")),
cfg.StrOpt('metadata_shared_secret',
deprecated_group='NVP_METADATA',
default='',
help=_('Shared secret to sign instance-id request'),
secret=True)
]
def register_dhcp_opts(config):
config.CONF.register_opts(dhcp_opts, group="NSX_DHCP")
def register_metadata_opts(config):
config.CONF.register_opts(metadata_opts, group="NSX_METADATA")
class DhcpAgentNotifyAPI(object):
def __init__(self, plugin, lsn_manager):
self.plugin = plugin
self.lsn_manager = lsn_manager
self._handle_subnet_dhcp_access = {'create': self._subnet_create,
'update': self._subnet_update,
'delete': self._subnet_delete}
def notify(self, context, data, methodname):
[resource, action, _e] = methodname.split('.')
if resource == 'subnet':
self._handle_subnet_dhcp_access[action](context, data['subnet'])
elif resource == 'port' and action == 'update':
self._port_update(context, data['port'])
def _port_update(self, context, port):
# With no fixed IP's there's nothing that can be updated
if not port["fixed_ips"]:
return
network_id = port['network_id']
subnet_id = port["fixed_ips"][0]['subnet_id']
filters = {'network_id': [network_id]}
# Because NVP does not support updating a single host entry we
# got to build the whole list from scratch and update in bulk
ports = self.plugin.get_ports(context, filters)
if not ports:
return
dhcp_conf = [
{'mac_address': p['mac_address'],
'ip_address': p["fixed_ips"][0]['ip_address']}
for p in ports if is_user_port(p)
]
meta_conf = [
{'instance_id': p['device_id'],
'ip_address': p["fixed_ips"][0]['ip_address']}
for p in ports if is_user_port(p, check_dev_id=True)
]
self.lsn_manager.lsn_port_update(
context, network_id, subnet_id, dhcp=dhcp_conf, meta=meta_conf)
def _subnet_create(self, context, subnet, clean_on_err=True):
if subnet['enable_dhcp']:
network_id = subnet['network_id']
# Create port for DHCP service
dhcp_port = {
"name": "",
"admin_state_up": True,
"device_id": "",
"device_owner": const.DEVICE_OWNER_DHCP,
"network_id": network_id,
"tenant_id": subnet["tenant_id"],
"mac_address": attr.ATTR_NOT_SPECIFIED,
"fixed_ips": [{"subnet_id": subnet['id']}]
}
try:
# This will end up calling handle_port_dhcp_access
# down below as well as handle_port_metadata_access
self.plugin.create_port(context, {'port': dhcp_port})
except p_exc.PortConfigurationError as e:
err_msg = (_("Error while creating subnet %(cidr)s for "
"network %(network)s. Please, contact "
"administrator") %
{"cidr": subnet["cidr"],
"network": network_id})
LOG.error(err_msg)
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
self.plugin, context, e.port_id)
if clean_on_err:
self.plugin.delete_subnet(context, subnet['id'])
raise n_exc.Conflict()
def _subnet_update(self, context, subnet):
network_id = subnet['network_id']
try:
lsn_id, lsn_port_id = self.lsn_manager.lsn_port_get(
context, network_id, subnet['id'])
self.lsn_manager.lsn_port_dhcp_configure(
context, lsn_id, lsn_port_id, subnet)
except p_exc.LsnPortNotFound:
# It's possible that the subnet was created with dhcp off;
# check if the subnet was uplinked onto a router, and if so
# remove the patch attachment between the metadata port and
# the lsn port, in favor on the one we'll be creating during
# _subnet_create
self.lsn_manager.lsn_port_dispose(
context, network_id, d_const.METADATA_MAC)
# also, check that a dhcp port exists first and provision it
# accordingly
filters = dict(network_id=[network_id],
device_owner=[const.DEVICE_OWNER_DHCP])
ports = self.plugin.get_ports(context, filters=filters)
if ports:
handle_port_dhcp_access(
self.plugin, context, ports[0], 'create_port')
else:
self._subnet_create(context, subnet, clean_on_err=False)
def _subnet_delete(self, context, subnet):
# FIXME(armando-migliaccio): it looks like that a subnet filter
# is ineffective; so filter by network for now.
network_id = subnet['network_id']
filters = dict(network_id=[network_id],
device_owner=[const.DEVICE_OWNER_DHCP])
# FIXME(armando-migliaccio): this may be race-y
ports = self.plugin.get_ports(context, filters=filters)
if ports:
# This will end up calling handle_port_dhcp_access
# down below as well as handle_port_metadata_access
self.plugin.delete_port(context, ports[0]['id'])
def is_user_port(p, check_dev_id=False):
usable = p['fixed_ips'] and p['device_owner'] not in d_const.SPECIAL_OWNERS
return usable if not check_dev_id else usable and p['device_id']
def check_services_requirements(cluster):
ver = cluster.api_client.get_nvp_version()
# It sounds like 4.1 is the first one where DHCP in NSX
# will have the experimental feature
if ver.major >= 4 and ver.minor >= 1:
cluster_id = cfg.CONF.default_service_cluster_uuid
if not lsn_api.service_cluster_exists(cluster, cluster_id):
raise p_exc.ServiceClusterUnavailable(cluster_id=cluster_id)
else:
raise p_exc.NvpInvalidVersion(version=ver)
def handle_network_dhcp_access(plugin, context, network, action):
LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s")
% {"action": action, "resource": network})
if action == 'create_network':
network_id = network['id']
plugin.lsn_manager.lsn_create(context, network_id)
elif action == 'delete_network':
# NOTE(armando-migliaccio): on delete_network, network
# is just the network id
network_id = network
plugin.lsn_manager.lsn_delete_by_network(context, network_id)
LOG.info(_("Logical Services Node for network "
"%s configured successfully"), network_id)
def handle_port_dhcp_access(plugin, context, port, action):
LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s")
% {"action": action, "resource": port})
if port["device_owner"] == const.DEVICE_OWNER_DHCP:
network_id = port["network_id"]
if action == "create_port":
# at this point the port must have a subnet and a fixed ip
subnet_id = port["fixed_ips"][0]['subnet_id']
subnet = plugin.get_subnet(context, subnet_id)
subnet_data = {
"mac_address": port["mac_address"],
"ip_address": subnet['cidr'],
"subnet_id": subnet['id']
}
try:
plugin.lsn_manager.lsn_port_dhcp_setup(
context, network_id, port['id'], subnet_data, subnet)
except p_exc.PortConfigurationError:
err_msg = (_("Error while configuring DHCP for "
"port %s"), port['id'])
LOG.error(err_msg)
raise n_exc.NeutronException()
elif action == "delete_port":
plugin.lsn_manager.lsn_port_dispose(context, network_id,
port['mac_address'])
elif port["device_owner"] != const.DEVICE_OWNER_DHCP:
if port.get("fixed_ips"):
# do something only if there are IP's and dhcp is enabled
subnet_id = port["fixed_ips"][0]['subnet_id']
if not plugin.get_subnet(context, subnet_id)['enable_dhcp']:
LOG.info(_("DHCP is disabled for subnet %s: nothing "
"to do"), subnet_id)
return
host_data = {
"mac_address": port["mac_address"],
"ip_address": port["fixed_ips"][0]['ip_address']
}
network_id = port["network_id"]
if action == "create_port":
handler = plugin.lsn_manager.lsn_port_dhcp_host_add
elif action == "delete_port":
handler = plugin.lsn_manager.lsn_port_dhcp_host_remove
try:
handler(context, network_id, subnet_id, host_data)
except p_exc.PortConfigurationError:
if action == 'create_port':
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
plugin, context, port['id'])
raise
LOG.info(_("DHCP for port %s configured successfully"), port['id'])
def handle_port_metadata_access(plugin, context, port, is_delete=False):
if is_user_port(port, check_dev_id=True):
network_id = port["network_id"]
network = plugin.get_network(context, network_id)
if network[external_net.EXTERNAL]:
LOG.info(_("Network %s is external: nothing to do"), network_id)
return
subnet_id = port["fixed_ips"][0]['subnet_id']
host_data = {
"instance_id": port["device_id"],
"tenant_id": port["tenant_id"],
"ip_address": port["fixed_ips"][0]['ip_address']
}
LOG.info(_("Configuring metadata entry for port %s"), port)
if not is_delete:
handler = plugin.lsn_manager.lsn_port_meta_host_add
else:
handler = plugin.lsn_manager.lsn_port_meta_host_remove
try:
handler(context, network_id, subnet_id, host_data)
except p_exc.PortConfigurationError:
if not is_delete:
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
plugin, context, port['id'])
raise
LOG.info(_("Metadata for port %s configured successfully"), port['id'])
def handle_router_metadata_access(plugin, context, router_id, interface=None):
LOG.info(_("Handle metadata access via router: %(r)s and "
"interface %(i)s") % {'r': router_id, 'i': interface})
if interface:
try:
plugin.get_port(context, interface['port_id'])
is_enabled = True
except n_exc.NotFound:
is_enabled = False
subnet_id = interface['subnet_id']
try:
plugin.lsn_manager.lsn_metadata_configure(
context, subnet_id, is_enabled)
except p_exc.NvpPluginException:
if is_enabled:
l3_db.L3_NAT_db_mixin.remove_router_interface(
plugin, context, router_id, interface)
raise
LOG.info(_("Metadata for router %s handled successfully"), router_id)

View File

@ -1,625 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.config import cfg
from neutron.api.v2 import attributes as attr
from neutron.common import constants as const
from neutron.common import exceptions as n_exc
from neutron.db import db_base_plugin_v2
from neutron.db import l3_db
from neutron.extensions import external_net
from neutron.openstack.common import log as logging
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.nsxlib import lsn as lsn_api
from neutron.plugins.nicira import nvplib
LOG = logging.getLogger(__name__)
# A unique MAC to quickly identify the LSN port used for metadata services
# when dhcp on the subnet is off. Inspired by leet-speak for 'metadata'.
METADATA_MAC = "fa:15:73:74:d4:74"
METADATA_PORT_ID = 'metadata:id'
METADATA_PORT_NAME = 'metadata:name'
METADATA_DEVICE_ID = 'metadata:device'
META_CONF = 'metadata-proxy'
DHCP_CONF = 'dhcp'
SPECIAL_OWNERS = (const.DEVICE_OWNER_DHCP,
const.DEVICE_OWNER_ROUTER_GW,
l3_db.DEVICE_OWNER_ROUTER_INTF)
dhcp_opts = [
cfg.ListOpt('extra_domain_name_servers',
deprecated_group='NVP_DHCP',
default=[],
help=_('Comma separated list of additional '
'domain name servers')),
cfg.StrOpt('domain_name',
deprecated_group='NVP_DHCP',
default='openstacklocal',
help=_('Domain to use for building the hostnames')),
cfg.IntOpt('default_lease_time', default=43200,
deprecated_group='NVP_DHCP',
help=_("Default DHCP lease time")),
]
metadata_opts = [
cfg.StrOpt('metadata_server_address',
deprecated_group='NVP_METADATA',
default='127.0.0.1',
help=_("IP address used by Metadata server.")),
cfg.IntOpt('metadata_server_port',
deprecated_group='NVP_METADATA',
default=8775,
help=_("TCP Port used by Metadata server.")),
cfg.StrOpt('metadata_shared_secret',
deprecated_group='NVP_METADATA',
default='',
help=_('Shared secret to sign instance-id request'),
secret=True)
]
def register_dhcp_opts(config):
config.CONF.register_opts(dhcp_opts, group="NSX_DHCP")
def register_metadata_opts(config):
config.CONF.register_opts(metadata_opts, group="NSX_METADATA")
class LsnManager(object):
"""Manage LSN entities associated with networks."""
def __init__(self, plugin):
self.plugin = plugin
@property
def cluster(self):
return self.plugin.cluster
def lsn_get(self, context, network_id, raise_on_err=True):
"""Retrieve the LSN id associated to the network."""
try:
return lsn_api.lsn_for_network_get(self.cluster, network_id)
except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException):
logger = raise_on_err and LOG.error or LOG.warn
logger(_('Unable to find Logical Service Node for '
'network %s'), network_id)
if raise_on_err:
raise p_exc.LsnNotFound(entity='network',
entity_id=network_id)
def lsn_create(self, context, network_id):
"""Create a LSN associated to the network."""
try:
return lsn_api.lsn_for_network_create(self.cluster, network_id)
except nvplib.NvpApiClient.NvpApiException:
err_msg = _('Unable to create LSN for network %s') % network_id
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_delete(self, context, lsn_id):
"""Delete a LSN given its id."""
try:
lsn_api.lsn_delete(self.cluster, lsn_id)
except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException):
LOG.warn(_('Unable to delete Logical Service Node %s'), lsn_id)
def lsn_delete_by_network(self, context, network_id):
"""Delete a LSN associated to the network."""
lsn_id = self.lsn_get(context, network_id, raise_on_err=False)
if lsn_id:
self.lsn_delete(context, lsn_id)
def lsn_port_get(self, context, network_id, subnet_id, raise_on_err=True):
"""Retrieve LSN and LSN port for the network and the subnet."""
lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err)
if lsn_id:
try:
lsn_port_id = lsn_api.lsn_port_by_subnet_get(
self.cluster, lsn_id, subnet_id)
except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException):
logger = raise_on_err and LOG.error or LOG.warn
logger(_('Unable to find Logical Service Node Port for '
'LSN %(lsn_id)s and subnet %(subnet_id)s')
% {'lsn_id': lsn_id, 'subnet_id': subnet_id})
if raise_on_err:
raise p_exc.LsnPortNotFound(lsn_id=lsn_id,
entity='subnet',
entity_id=subnet_id)
return (lsn_id, None)
else:
return (lsn_id, lsn_port_id)
else:
return (None, None)
def lsn_port_get_by_mac(self, context, network_id, mac, raise_on_err=True):
"""Retrieve LSN and LSN port given network and mac address."""
lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err)
if lsn_id:
try:
lsn_port_id = lsn_api.lsn_port_by_mac_get(
self.cluster, lsn_id, mac)
except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException):
logger = raise_on_err and LOG.error or LOG.warn
logger(_('Unable to find Logical Service Node Port for '
'LSN %(lsn_id)s and mac address %(mac)s')
% {'lsn_id': lsn_id, 'mac': mac})
if raise_on_err:
raise p_exc.LsnPortNotFound(lsn_id=lsn_id,
entity='MAC',
entity_id=mac)
return (lsn_id, None)
else:
return (lsn_id, lsn_port_id)
else:
return (None, None)
def lsn_port_create(self, context, lsn_id, subnet_info):
"""Create and return LSN port for associated subnet."""
try:
return lsn_api.lsn_port_create(self.cluster, lsn_id, subnet_info)
except n_exc.NotFound:
raise p_exc.LsnNotFound(entity='', entity_id=lsn_id)
except nvplib.NvpApiClient.NvpApiException:
err_msg = _('Unable to create port for LSN %s') % lsn_id
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_port_delete(self, context, lsn_id, lsn_port_id):
"""Delete a LSN port from the Logical Service Node."""
try:
lsn_api.lsn_port_delete(self.cluster, lsn_id, lsn_port_id)
except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException):
LOG.warn(_('Unable to delete LSN Port %s'), lsn_port_id)
def lsn_port_dispose(self, context, network_id, mac_address):
"""Delete a LSN port given the network and the mac address."""
# NOTE(armando-migliaccio): dispose and delete are functionally
# equivalent, but they use different paraments to identify LSN
# and LSN port resources.
lsn_id, lsn_port_id = self.lsn_port_get_by_mac(
context, network_id, mac_address, raise_on_err=False)
if lsn_port_id:
self.lsn_port_delete(context, lsn_id, lsn_port_id)
if mac_address == METADATA_MAC:
try:
lswitch_port = nvplib.get_port_by_neutron_tag(
self.cluster, network_id, METADATA_PORT_ID)
if lswitch_port:
lswitch_port_id = lswitch_port['uuid']
nvplib.delete_port(
self.cluster, network_id, lswitch_port_id)
else:
LOG.warn(_("Metadata port not found while attempting "
"to delete it from network %s"), network_id)
except (n_exc.PortNotFoundOnNetwork,
nvplib.NvpApiClient.NvpApiException):
LOG.warn(_("Metadata port not found while attempting "
"to delete it from network %s"), network_id)
else:
LOG.warn(_("Unable to find Logical Services Node "
"Port with MAC %s"), mac_address)
def lsn_port_dhcp_setup(
self, context, network_id, port_id, port_data, subnet_config=None):
"""Connect network to LSN via specified port and port_data."""
try:
lsn_id = None
lswitch_port_id = nvplib.get_port_by_neutron_tag(
self.cluster, network_id, port_id)['uuid']
lsn_id = self.lsn_get(context, network_id)
lsn_port_id = self.lsn_port_create(context, lsn_id, port_data)
except (n_exc.NotFound, p_exc.NvpPluginException):
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=port_id)
try:
lsn_api.lsn_port_plug_network(
self.cluster, lsn_id, lsn_port_id, lswitch_port_id)
except p_exc.LsnConfigurationConflict:
self.lsn_port_delete(self.cluster, lsn_id, lsn_port_id)
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=port_id)
if subnet_config:
self.lsn_port_dhcp_configure(
context, lsn_id, lsn_port_id, subnet_config)
else:
return (lsn_id, lsn_port_id)
def lsn_port_metadata_setup(self, context, lsn_id, subnet):
"""Connect subnet to specified LSN."""
data = {
"mac_address": METADATA_MAC,
"ip_address": subnet['cidr'],
"subnet_id": subnet['id']
}
network_id = subnet['network_id']
tenant_id = subnet['tenant_id']
lswitch_port_id = None
try:
lswitch_port_id = nvplib.create_lport(
self.cluster, network_id, tenant_id,
METADATA_PORT_ID, METADATA_PORT_NAME,
METADATA_DEVICE_ID, True)['uuid']
lsn_port_id = self.lsn_port_create(self.cluster, lsn_id, data)
except (n_exc.NotFound, p_exc.NvpPluginException,
nvplib.NvpApiClient.NvpApiException):
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lswitch_port_id)
else:
try:
lsn_api.lsn_port_plug_network(
self.cluster, lsn_id, lsn_port_id, lswitch_port_id)
except p_exc.LsnConfigurationConflict:
self.lsn_port_delete(self.cluster, lsn_id, lsn_port_id)
nvplib.delete_port(self.cluster, network_id, lswitch_port_id)
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
def lsn_port_dhcp_configure(self, context, lsn_id, lsn_port_id, subnet):
"""Enable/disable dhcp services with the given config options."""
is_enabled = subnet["enable_dhcp"]
dhcp_options = {
"domain_name": cfg.CONF.NSX_DHCP.domain_name,
"default_lease_time": cfg.CONF.NSX_DHCP.default_lease_time,
}
dns_servers = cfg.CONF.NSX_DHCP.extra_domain_name_servers
dns_servers.extend(subnet["dns_nameservers"])
if subnet['gateway_ip']:
dhcp_options["routers"] = subnet["gateway_ip"]
if dns_servers:
dhcp_options["domain_name_servers"] = ",".join(dns_servers)
if subnet["host_routes"]:
dhcp_options["classless_static_routes"] = (
",".join(subnet["host_routes"])
)
try:
lsn_api.lsn_port_dhcp_configure(
self.cluster, lsn_id, lsn_port_id, is_enabled, dhcp_options)
except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException):
err_msg = (_('Unable to configure dhcp for Logical Service '
'Node %(lsn_id)s and port %(lsn_port_id)s')
% {'lsn_id': lsn_id, 'lsn_port_id': lsn_port_id})
LOG.error(err_msg)
raise p_exc.NvpPluginException(err_msg=err_msg)
def lsn_metadata_configure(self, context, subnet_id, is_enabled):
"""Configure metadata service for the specified subnet."""
subnet = self.plugin.get_subnet(context, subnet_id)
network_id = subnet['network_id']
meta_conf = cfg.CONF.NSX_METADATA
metadata_options = {
'metadata_server_ip': meta_conf.metadata_server_address,
'metadata_server_port': meta_conf.metadata_server_port,
'metadata_proxy_shared_secret': meta_conf.metadata_shared_secret
}
try:
lsn_id = self.lsn_get(context, network_id)
lsn_api.lsn_metadata_configure(
self.cluster, lsn_id, is_enabled, metadata_options)
except (p_exc.LsnNotFound, nvplib.NvpApiClient.NvpApiException):
err_msg = (_('Unable to configure metadata access '
'for subnet %s') % subnet_id)
LOG.error(err_msg)
raise p_exc.NvpPluginException(err_msg=err_msg)
if is_enabled:
try:
# test that the lsn port exists
self.lsn_port_get(context, network_id, subnet_id)
except p_exc.LsnPortNotFound:
# this might happen if subnet had dhcp off when created
# so create one, and wire it
self.lsn_port_metadata_setup(context, lsn_id, subnet)
else:
self.lsn_port_dispose(context, network_id, METADATA_MAC)
def _lsn_port_host_conf(self, context, network_id, subnet_id, data, hdlr):
lsn_id = None
lsn_port_id = None
try:
lsn_id, lsn_port_id = self.lsn_port_get(
context, network_id, subnet_id)
hdlr(self.cluster, lsn_id, lsn_port_id, data)
except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException):
LOG.error(_('Error while configuring LSN '
'port %s'), lsn_port_id)
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
def lsn_port_dhcp_host_add(self, context, network_id, subnet_id, host):
"""Add dhcp host entry to LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_dhcp_host_add)
def lsn_port_dhcp_host_remove(self, context, network_id, subnet_id, host):
"""Remove dhcp host entry from LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_dhcp_host_remove)
def lsn_port_meta_host_add(self, context, network_id, subnet_id, host):
"""Add metadata host entry to LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_metadata_host_add)
def lsn_port_meta_host_remove(self, context, network_id, subnet_id, host):
"""Remove meta host entry from LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_metadata_host_remove)
def lsn_port_update(
self, context, network_id, subnet_id, dhcp=None, meta=None):
"""Update the specified configuration for the LSN port."""
if not dhcp and not meta:
return
try:
lsn_id, lsn_port_id = self.lsn_port_get(
context, network_id, subnet_id, raise_on_err=False)
if dhcp and lsn_id and lsn_port_id:
lsn_api.lsn_port_host_entries_update(
self.cluster, lsn_id, lsn_port_id, DHCP_CONF, dhcp)
if meta and lsn_id and lsn_port_id:
lsn_api.lsn_port_host_entries_update(
self.cluster, lsn_id, lsn_port_id, META_CONF, meta)
except nvplib.NvpApiClient.NvpApiException:
raise p_exc.PortConfigurationError(
net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
class DhcpAgentNotifyAPI(object):
def __init__(self, plugin, lsn_manager):
self.plugin = plugin
self.lsn_manager = lsn_manager
self._handle_subnet_dhcp_access = {'create': self._subnet_create,
'update': self._subnet_update,
'delete': self._subnet_delete}
def notify(self, context, data, methodname):
[resource, action, _e] = methodname.split('.')
if resource == 'subnet':
self._handle_subnet_dhcp_access[action](context, data['subnet'])
elif resource == 'port' and action == 'update':
self._port_update(context, data['port'])
def _port_update(self, context, port):
# With no fixed IP's there's nothing that can be updated
if not port["fixed_ips"]:
return
network_id = port['network_id']
subnet_id = port["fixed_ips"][0]['subnet_id']
filters = {'network_id': [network_id]}
# Because NVP does not support updating a single host entry we
# got to build the whole list from scratch and update in bulk
ports = self.plugin.get_ports(context, filters)
if not ports:
return
dhcp_conf = [
{'mac_address': p['mac_address'],
'ip_address': p["fixed_ips"][0]['ip_address']}
for p in ports if is_user_port(p)
]
meta_conf = [
{'instance_id': p['device_id'],
'ip_address': p["fixed_ips"][0]['ip_address']}
for p in ports if is_user_port(p, check_dev_id=True)
]
self.lsn_manager.lsn_port_update(
context, network_id, subnet_id, dhcp=dhcp_conf, meta=meta_conf)
def _subnet_create(self, context, subnet, clean_on_err=True):
if subnet['enable_dhcp']:
network_id = subnet['network_id']
# Create port for DHCP service
dhcp_port = {
"name": "",
"admin_state_up": True,
"device_id": "",
"device_owner": const.DEVICE_OWNER_DHCP,
"network_id": network_id,
"tenant_id": subnet["tenant_id"],
"mac_address": attr.ATTR_NOT_SPECIFIED,
"fixed_ips": [{"subnet_id": subnet['id']}]
}
try:
# This will end up calling handle_port_dhcp_access
# down below as well as handle_port_metadata_access
self.plugin.create_port(context, {'port': dhcp_port})
except p_exc.PortConfigurationError as e:
err_msg = (_("Error while creating subnet %(cidr)s for "
"network %(network)s. Please, contact "
"administrator") %
{"cidr": subnet["cidr"],
"network": network_id})
LOG.error(err_msg)
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
self.plugin, context, e.port_id)
if clean_on_err:
self.plugin.delete_subnet(context, subnet['id'])
raise n_exc.Conflict()
def _subnet_update(self, context, subnet):
network_id = subnet['network_id']
try:
lsn_id, lsn_port_id = self.lsn_manager.lsn_port_get(
context, network_id, subnet['id'])
self.lsn_manager.lsn_port_dhcp_configure(
context, lsn_id, lsn_port_id, subnet)
except p_exc.LsnPortNotFound:
# It's possible that the subnet was created with dhcp off;
# check if the subnet was uplinked onto a router, and if so
# remove the patch attachment between the metadata port and
# the lsn port, in favor on the one we'll be creating during
# _subnet_create
self.lsn_manager.lsn_port_dispose(
context, network_id, METADATA_MAC)
# also, check that a dhcp port exists first and provision it
# accordingly
filters = dict(network_id=[network_id],
device_owner=[const.DEVICE_OWNER_DHCP])
ports = self.plugin.get_ports(context, filters=filters)
if ports:
handle_port_dhcp_access(
self.plugin, context, ports[0], 'create_port')
else:
self._subnet_create(context, subnet, clean_on_err=False)
def _subnet_delete(self, context, subnet):
# FIXME(armando-migliaccio): it looks like that a subnet filter
# is ineffective; so filter by network for now.
network_id = subnet['network_id']
filters = dict(network_id=[network_id],
device_owner=[const.DEVICE_OWNER_DHCP])
# FIXME(armando-migliaccio): this may be race-y
ports = self.plugin.get_ports(context, filters=filters)
if ports:
# This will end up calling handle_port_dhcp_access
# down below as well as handle_port_metadata_access
self.plugin.delete_port(context, ports[0]['id'])
def is_user_port(p, check_dev_id=False):
usable = p['fixed_ips'] and p['device_owner'] not in SPECIAL_OWNERS
return usable if not check_dev_id else usable and p['device_id']
def check_services_requirements(cluster):
ver = cluster.api_client.get_nvp_version()
# It sounds like 4.1 is the first one where DHCP in NSX
# will have the experimental feature
if ver.major >= 4 and ver.minor >= 1:
cluster_id = cfg.CONF.default_service_cluster_uuid
if not lsn_api.service_cluster_exists(cluster, cluster_id):
raise p_exc.ServiceClusterUnavailable(cluster_id=cluster_id)
else:
raise p_exc.NvpInvalidVersion(version=ver)
def handle_network_dhcp_access(plugin, context, network, action):
LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s")
% {"action": action, "resource": network})
if action == 'create_network':
network_id = network['id']
plugin.lsn_manager.lsn_create(context, network_id)
elif action == 'delete_network':
# NOTE(armando-migliaccio): on delete_network, network
# is just the network id
network_id = network
plugin.lsn_manager.lsn_delete_by_network(context, network_id)
LOG.info(_("Logical Services Node for network "
"%s configured successfully"), network_id)
def handle_port_dhcp_access(plugin, context, port, action):
LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s")
% {"action": action, "resource": port})
if port["device_owner"] == const.DEVICE_OWNER_DHCP:
network_id = port["network_id"]
if action == "create_port":
# at this point the port must have a subnet and a fixed ip
subnet_id = port["fixed_ips"][0]['subnet_id']
subnet = plugin.get_subnet(context, subnet_id)
subnet_data = {
"mac_address": port["mac_address"],
"ip_address": subnet['cidr'],
"subnet_id": subnet['id']
}
try:
plugin.lsn_manager.lsn_port_dhcp_setup(
context, network_id, port['id'], subnet_data, subnet)
except p_exc.PortConfigurationError:
err_msg = (_("Error while configuring DHCP for "
"port %s"), port['id'])
LOG.error(err_msg)
raise n_exc.NeutronException()
elif action == "delete_port":
plugin.lsn_manager.lsn_port_dispose(context, network_id,
port['mac_address'])
elif port["device_owner"] != const.DEVICE_OWNER_DHCP:
if port.get("fixed_ips"):
# do something only if there are IP's and dhcp is enabled
subnet_id = port["fixed_ips"][0]['subnet_id']
if not plugin.get_subnet(context, subnet_id)['enable_dhcp']:
LOG.info(_("DHCP is disabled for subnet %s: nothing "
"to do"), subnet_id)
return
host_data = {
"mac_address": port["mac_address"],
"ip_address": port["fixed_ips"][0]['ip_address']
}
network_id = port["network_id"]
if action == "create_port":
handler = plugin.lsn_manager.lsn_port_dhcp_host_add
elif action == "delete_port":
handler = plugin.lsn_manager.lsn_port_dhcp_host_remove
try:
handler(context, network_id, subnet_id, host_data)
except p_exc.PortConfigurationError:
if action == 'create_port':
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
plugin, context, port['id'])
raise
LOG.info(_("DHCP for port %s configured successfully"), port['id'])
def handle_port_metadata_access(plugin, context, port, is_delete=False):
if is_user_port(port, check_dev_id=True):
network_id = port["network_id"]
network = plugin.get_network(context, network_id)
if network[external_net.EXTERNAL]:
LOG.info(_("Network %s is external: nothing to do"), network_id)
return
subnet_id = port["fixed_ips"][0]['subnet_id']
host_data = {
"instance_id": port["device_id"],
"tenant_id": port["tenant_id"],
"ip_address": port["fixed_ips"][0]['ip_address']
}
LOG.info(_("Configuring metadata entry for port %s"), port)
if not is_delete:
handler = plugin.lsn_manager.lsn_port_meta_host_add
else:
handler = plugin.lsn_manager.lsn_port_meta_host_remove
try:
handler(context, network_id, subnet_id, host_data)
except p_exc.PortConfigurationError:
if not is_delete:
db_base_plugin_v2.NeutronDbPluginV2.delete_port(
plugin, context, port['id'])
raise
LOG.info(_("Metadata for port %s configured successfully"), port['id'])
def handle_router_metadata_access(plugin, context, router_id, interface=None):
LOG.info(_("Handle metadata access via router: %(r)s and "
"interface %(i)s") % {'r': router_id, 'i': interface})
if interface:
try:
plugin.get_port(context, interface['port_id'])
is_enabled = True
except n_exc.NotFound:
is_enabled = False
subnet_id = interface['subnet_id']
try:
plugin.lsn_manager.lsn_metadata_configure(
context, subnet_id, is_enabled)
except p_exc.NvpPluginException:
if is_enabled:
l3_db.L3_NAT_db_mixin.remove_router_interface(
plugin, context, router_id, interface)
raise
LOG.info(_("Metadata for router %s handled successfully"), router_id)

View File

@ -1,6 +1,5 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 VMware, Inc.
#
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -25,9 +24,13 @@ from neutron.openstack.common import importutils
from neutron.openstack.common import log as logging
from neutron.openstack.common import rpc
from neutron.plugins.nicira.common import config
from neutron.plugins.nicira.common import exceptions as nvp_exc
from neutron.plugins.nicira.dhcp_meta import nvp as nvp_svc
from neutron.plugins.nicira.dhcp_meta import rpc as nvp_rpc
from neutron.plugins.nicira.common import exceptions as nsx_exc
from neutron.plugins.nicira.dhcp_meta import combined
from neutron.plugins.nicira.dhcp_meta import lsnmanager
from neutron.plugins.nicira.dhcp_meta import migration
from neutron.plugins.nicira.dhcp_meta import nsx as nsx_svc
from neutron.plugins.nicira.dhcp_meta import rpc as nsx_rpc
from neutron.plugins.nicira.extensions import lsn
LOG = logging.getLogger(__name__)
@ -36,12 +39,21 @@ class DhcpMetadataAccess(object):
def setup_dhcpmeta_access(self):
"""Initialize support for DHCP and Metadata services."""
self._init_extensions()
if cfg.CONF.NSX.agent_mode == config.AgentModes.AGENT:
self._setup_rpc_dhcp_metadata()
mod = nvp_rpc
mod = nsx_rpc
elif cfg.CONF.NSX.agent_mode == config.AgentModes.AGENTLESS:
self._setup_nvp_dhcp_metadata()
mod = nvp_svc
self._setup_nsx_dhcp_metadata()
mod = nsx_svc
elif cfg.CONF.NSX.agent_mode == config.AgentModes.COMBINED:
notifier = self._setup_nsx_dhcp_metadata()
self._setup_rpc_dhcp_metadata(notifier=notifier)
mod = combined
else:
error = _("Invalid agent_mode: %s") % cfg.CONF.NSX.agent_mode
LOG.error(error)
raise nsx_exc.NvpPluginException(err_msg=error)
self.handle_network_dhcp_access_delegate = (
mod.handle_network_dhcp_access
)
@ -55,49 +67,78 @@ class DhcpMetadataAccess(object):
mod.handle_router_metadata_access
)
def _setup_rpc_dhcp_metadata(self):
def _setup_rpc_dhcp_metadata(self, notifier=None):
self.topic = topics.PLUGIN
self.conn = rpc.create_connection(new=True)
self.dispatcher = nvp_rpc.NVPRpcCallbacks().create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher,
fanout=False)
self.dispatcher = nsx_rpc.NVPRpcCallbacks().create_rpc_dispatcher()
self.conn.create_consumer(self.topic, self.dispatcher, fanout=False)
self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
dhcp_rpc_agent_api.DhcpAgentNotifyAPI())
notifier or dhcp_rpc_agent_api.DhcpAgentNotifyAPI())
self.conn.consume_in_thread()
self.network_scheduler = importutils.import_object(
cfg.CONF.network_scheduler_driver
)
self.supported_extension_aliases.extend(
['agent', 'dhcp_agent_scheduler'])
def _setup_nvp_dhcp_metadata(self):
# In agentless mode the following extensions, and related
# operations, are not supported; so do not publish them
if "agent" in self.supported_extension_aliases:
self.supported_extension_aliases.remove("agent")
if "dhcp_agent_scheduler" in self.supported_extension_aliases:
self.supported_extension_aliases.remove(
"dhcp_agent_scheduler")
nvp_svc.register_dhcp_opts(cfg)
nvp_svc.register_metadata_opts(cfg)
self.lsn_manager = nvp_svc.LsnManager(self)
self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
nvp_svc.DhcpAgentNotifyAPI(self, self.lsn_manager))
# In agentless mode, ports whose owner is DHCP need to
# be special cased; so add it to the list of special
# owners list
if const.DEVICE_OWNER_DHCP not in self.port_special_owners:
self.port_special_owners.append(const.DEVICE_OWNER_DHCP)
def _setup_nsx_dhcp_metadata(self):
self._check_services_requirements()
nsx_svc.register_dhcp_opts(cfg)
nsx_svc.register_metadata_opts(cfg)
lsnmanager.register_lsn_opts(cfg)
lsn_manager = lsnmanager.PersistentLsnManager(self)
self.lsn_manager = lsn_manager
if cfg.CONF.NSX.agent_mode == config.AgentModes.AGENTLESS:
notifier = nsx_svc.DhcpAgentNotifyAPI(self, lsn_manager)
self.agent_notifiers[const.AGENT_TYPE_DHCP] = notifier
# In agentless mode, ports whose owner is DHCP need to
# be special cased; so add it to the list of special
# owners list
if const.DEVICE_OWNER_DHCP not in self.port_special_owners:
self.port_special_owners.append(const.DEVICE_OWNER_DHCP)
elif cfg.CONF.NSX.agent_mode == config.AgentModes.COMBINED:
# This becomes ineffective, as all new networks creations
# are handled by Logical Services Nodes in NSX
cfg.CONF.set_override('network_auto_schedule', False)
LOG.warn(_('network_auto_schedule has been disabled'))
notifier = combined.DhcpAgentNotifyAPI(self, lsn_manager)
self.supported_extension_aliases.append(lsn.EXT_ALIAS)
# Add the capability to migrate dhcp and metadata services over
self.migration_manager = (
migration.MigrationManager(self, lsn_manager, notifier))
return notifier
def _init_extensions(self):
extensions = (lsn.EXT_ALIAS, 'agent', 'dhcp_agent_scheduler')
for ext in extensions:
if ext in self.supported_extension_aliases:
self.supported_extension_aliases.remove(ext)
def _check_services_requirements(self):
try:
error = None
nvp_svc.check_services_requirements(self.cluster)
except nvp_exc.NvpInvalidVersion:
nsx_svc.check_services_requirements(self.cluster)
except nsx_exc.NvpInvalidVersion:
error = _("Unable to run Neutron with config option '%s', as NSX "
"does not support it") % config.AgentModes.AGENTLESS
except nvp_exc.ServiceClusterUnavailable:
"does not support it") % cfg.CONF.NSX.agent_mode
except nsx_exc.ServiceClusterUnavailable:
error = _("Unmet dependency for config option "
"'%s'") % config.AgentModes.AGENTLESS
"'%s'") % cfg.CONF.NSX.agent_mode
if error:
LOG.exception(error)
raise nvp_exc.NvpPluginException(err_msg=error)
raise nsx_exc.NvpPluginException(err_msg=error)
def get_lsn(self, context, network_id, fields=None):
report = self.migration_manager.report(context, network_id)
return {'network': network_id, 'report': report}
def create_lsn(self, context, lsn):
network_id = lsn['lsn']['network']
subnet = self.migration_manager.validate(context, network_id)
subnet_id = None if not subnet else subnet['id']
self.migration_manager.migrate(context, network_id, subnet)
r = self.migration_manager.report(context, network_id, subnet_id)
return {'network': network_id, 'report': r}
def handle_network_dhcp_access(self, context, network, action):
self.handle_network_dhcp_access_delegate(self, context,

View File

@ -0,0 +1,82 @@
# 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 neutron.api import extensions
from neutron.api.v2 import base
from neutron import manager
EXT_ALIAS = 'lsn'
COLLECTION_NAME = "%ss" % EXT_ALIAS
RESOURCE_ATTRIBUTE_MAP = {
COLLECTION_NAME: {
'network': {'allow_post': True, 'allow_put': False,
'validate': {'type:string': None},
'is_visible': True},
'report': {'allow_post': False, 'allow_put': False,
'is_visible': True},
'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': True,
'validate': {'type:string': None}, 'is_visible': True},
},
}
class Lsn(object):
"""Enable LSN configuration for Neutron NSX networks."""
@classmethod
def get_name(cls):
return "Logical Service Node configuration"
@classmethod
def get_alias(cls):
return EXT_ALIAS
@classmethod
def get_description(cls):
return "Enables configuration of NSX Logical Services Node."
@classmethod
def get_namespace(cls):
return "http://docs.openstack.org/ext/%s/api/v2.0" % EXT_ALIAS
@classmethod
def get_updated(cls):
return "2013-10-05T10:00:00-00:00"
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
exts = []
plugin = manager.NeutronManager.get_plugin()
resource_name = EXT_ALIAS
collection_name = resource_name.replace('_', '-') + "s"
params = RESOURCE_ATTRIBUTE_MAP.get(COLLECTION_NAME, dict())
controller = base.create_resource(collection_name,
resource_name,
plugin, params, allow_bulk=False)
ex = extensions.ResourceExtension(collection_name, controller)
exts.append(ex)
return exts
def get_extended_resources(self, version):
if version == "2.0":
return RESOURCE_ATTRIBUTE_MAP
else:
return {}

View File

@ -141,6 +141,19 @@ def lsn_port_by_subnet_get(cluster, lsn_id, subnet_id):
return _lsn_port_get(cluster, lsn_id, filters)
def lsn_port_info_get(cluster, lsn_id, lsn_port_id):
result = do_request(HTTP_GET,
_build_uri_path(LSERVICESNODEPORT_RESOURCE,
parent_resource_id=lsn_id,
resource_id=lsn_port_id),
cluster=cluster)
for tag in result['tags']:
if tag['scope'] == 'n_subnet_id':
result['subnet_id'] = tag['tag']
break
return result
def lsn_port_plug_network(cluster, lsn_id, lsn_port_id, lswitch_port_id):
patch_obj = {
"type": "PatchAttachment",

View File

@ -0,0 +1,41 @@
# 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.
import sys
from neutron.plugins.nicira.shell import commands as cmd
from neutronclient import shell
class NsxManage(shell.NeutronShell):
def __init__(self, api_version):
super(NsxManage, self).__init__(api_version)
self.command_manager.add_command('net-migrate', cmd.NetworkMigrate)
self.command_manager.add_command('net-report', cmd.NetworkReport)
def build_option_parser(self, description, version):
parser = super(NsxManage, self).build_option_parser(
description, version)
return parser
def initialize_app(self, argv):
super(NsxManage, self).initialize_app(argv)
self.client = self.client_manager.neutron
def main():
return NsxManage(shell.NEUTRON_API_VERSION).run(sys.argv[1:])

View File

@ -0,0 +1,66 @@
# 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 neutronclient.neutron.v2_0 import find_resourceid_by_name_or_id
from neutronclient.neutron.v2_0 import NeutronCommand
LSN_PATH = '/lsns'
def print_report(write_func, report):
write_func(_("\nService type = %s\n") % report['report']['type'])
services = ','.join(report['report']['services'])
ports = ','.join(report['report']['ports'])
write_func(_("Service uuids = %s\n") % services)
write_func(_("Port uuids = %s\n\n") % ports)
class NetworkReport(NeutronCommand):
"""Retrieve network migration report."""
def get_parser(self, prog_name):
parser = super(NetworkReport, self).get_parser(prog_name)
parser.add_argument('network', metavar='network',
help=_('ID or name of network to run report on'))
return parser
def run(self, parsed_args):
net = parsed_args.network
net_id = find_resourceid_by_name_or_id(self.app.client, 'network', net)
res = self.app.client.get("%s/%s" % (LSN_PATH, net_id))
if res:
self.app.stdout.write(_('Migration report is:\n'))
print_report(self.app.stdout.write, res['lsn'])
class NetworkMigrate(NeutronCommand):
"""Perform network migration."""
def get_parser(self, prog_name):
parser = super(NetworkMigrate, self).get_parser(prog_name)
parser.add_argument('network', metavar='network',
help=_('ID or name of network to migrate'))
return parser
def run(self, parsed_args):
net = parsed_args.network
net_id = find_resourceid_by_name_or_id(self.app.client, 'network', net)
body = {'network': net_id}
res = self.app.client.post(LSN_PATH, body={'lsn': body})
if res:
self.app.stdout.write(_('Migration has been successful:\n'))
print_report(self.app.stdout.write, res['lsn'])

View File

@ -1,5 +1,3 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -20,12 +18,239 @@ import mock
from oslo.config import cfg
from neutron.common import exceptions as n_exc
from neutron import context
from neutron.db import api as db
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.dhcp_meta import nvp
from neutron.plugins.nicira.dbexts import lsn_db
from neutron.plugins.nicira.dhcp_meta import constants
from neutron.plugins.nicira.dhcp_meta import lsnmanager as lsn_man
from neutron.plugins.nicira.dhcp_meta import migration as mig_man
from neutron.plugins.nicira.dhcp_meta import nsx
from neutron.plugins.nicira.dhcp_meta import rpc
from neutron.plugins.nicira.NvpApiClient import NvpApiException
from neutron.tests import base
class DhcpMetadataBuilderTestCase(base.BaseTestCase):
def setUp(self):
super(DhcpMetadataBuilderTestCase, self).setUp()
self.builder = mig_man.DhcpMetadataBuilder(mock.Mock(), mock.Mock())
self.network_id = 'foo_network_id'
self.subnet_id = 'foo_subnet_id'
self.router_id = 'foo_router_id'
def test_dhcp_agent_get_all(self):
expected = []
self.builder.plugin.list_dhcp_agents_hosting_network.return_value = (
{'agents': expected})
agents = self.builder.dhcp_agent_get_all(mock.ANY, self.network_id)
self.assertEqual(expected, agents)
def test_dhcp_port_get_all(self):
expected = []
self.builder.plugin.get_ports.return_value = expected
ports = self.builder.dhcp_port_get_all(mock.ANY, self.network_id)
self.assertEqual(expected, ports)
def test_router_id_get(self):
port = {
'device_id': self.router_id,
'network_id': self.network_id,
'fixed_ips': [{'subnet_id': self.subnet_id}]
}
subnet = {
'id': self.subnet_id,
'network_id': self.network_id
}
self.builder.plugin.get_ports.return_value = [port]
result = self.builder.router_id_get(context, subnet)
self.assertEqual(self.router_id, result)
def test_router_id_get_none_subnet(self):
self.assertIsNone(self.builder.router_id_get(mock.ANY, None))
def test_metadata_deallocate(self):
self.builder.metadata_deallocate(
mock.ANY, self.router_id, self.subnet_id)
self.assertTrue(self.builder.plugin.remove_router_interface.call_count)
def test_metadata_allocate(self):
self.builder.metadata_allocate(
mock.ANY, self.router_id, self.subnet_id)
self.assertTrue(self.builder.plugin.add_router_interface.call_count)
def test_dhcp_deallocate(self):
agents = [{'id': 'foo_agent_id'}]
ports = [{'id': 'foo_port_id'}]
self.builder.dhcp_deallocate(mock.ANY, self.network_id, agents, ports)
self.assertTrue(
self.builder.plugin.remove_network_from_dhcp_agent.call_count)
self.assertTrue(self.builder.plugin.delete_port.call_count)
def _test_dhcp_allocate(self, subnet, expected_notify_count):
with mock.patch.object(mig_man.nsx, 'handle_network_dhcp_access') as f:
self.builder.dhcp_allocate(mock.ANY, self.network_id, subnet)
self.assertTrue(f.call_count)
self.assertEqual(expected_notify_count,
self.builder.notifier.notify.call_count)
def test_dhcp_allocate(self):
subnet = {'network_id': self.network_id, 'id': self.subnet_id}
self._test_dhcp_allocate(subnet, 2)
def test_dhcp_allocate_none_subnet(self):
self._test_dhcp_allocate(None, 0)
class MigrationManagerTestCase(base.BaseTestCase):
def setUp(self):
super(MigrationManagerTestCase, self).setUp()
self.manager = mig_man.MigrationManager(mock.Mock(),
mock.Mock(),
mock.Mock())
self.network_id = 'foo_network_id'
self.router_id = 'foo_router_id'
self.subnet_id = 'foo_subnet_id'
self.mock_builder_p = mock.patch.object(self.manager, 'builder')
self.mock_builder = self.mock_builder_p.start()
self.addCleanup(self.mock_builder_p.stop)
def _test_validate(self, lsn_exists=False, ext_net=False, subnets=None):
network = {'router:external': ext_net}
self.manager.manager.lsn_exists.return_value = lsn_exists
self.manager.plugin.get_network.return_value = network
self.manager.plugin.get_subnets.return_value = subnets
result = self.manager.validate(mock.ANY, self.network_id)
if len(subnets):
self.assertEqual(subnets[0], result)
else:
self.assertIsNone(result)
def test_validate_no_subnets(self):
self._test_validate(subnets=[])
def test_validate_with_one_subnet(self):
self._test_validate(subnets=[{'cidr': '0.0.0.0/0'}])
def test_validate_raise_conflict_many_subnets(self):
self.assertRaises(p_exc.LsnMigrationConflict,
self._test_validate,
subnets=[{'id': 'sub1'}, {'id': 'sub2'}])
def test_validate_raise_conflict_lsn_exists(self):
self.assertRaises(p_exc.LsnMigrationConflict,
self._test_validate,
lsn_exists=True)
def test_validate_raise_badrequest_external_net(self):
self.assertRaises(n_exc.BadRequest,
self._test_validate,
ext_net=True)
def test_validate_raise_badrequest_metadata_net(self):
self.assertRaises(n_exc.BadRequest,
self._test_validate,
ext_net=False,
subnets=[{'cidr': rpc.METADATA_SUBNET_CIDR}])
def _test_migrate(self, router, subnet, expected_calls):
self.mock_builder.router_id_get.return_value = router
self.manager.migrate(mock.ANY, self.network_id, subnet)
# testing the exact the order of calls is important
self.assertEqual(expected_calls, self.mock_builder.mock_calls)
def test_migrate(self):
subnet = {
'id': self.subnet_id,
'network_id': self.network_id
}
call_sequence = [
mock.call.router_id_get(mock.ANY, subnet),
mock.call.metadata_deallocate(
mock.ANY, self.router_id, self.subnet_id),
mock.call.dhcp_agent_get_all(mock.ANY, self.network_id),
mock.call.dhcp_port_get_all(mock.ANY, self.network_id),
mock.call.dhcp_deallocate(
mock.ANY, self.network_id, mock.ANY, mock.ANY),
mock.call.dhcp_allocate(mock.ANY, self.network_id, subnet),
mock.call.metadata_allocate(
mock.ANY, self.router_id, self.subnet_id)
]
self._test_migrate(self.router_id, subnet, call_sequence)
def test_migrate_no_router_uplink(self):
subnet = {
'id': self.subnet_id,
'network_id': self.network_id
}
call_sequence = [
mock.call.router_id_get(mock.ANY, subnet),
mock.call.dhcp_agent_get_all(mock.ANY, self.network_id),
mock.call.dhcp_port_get_all(mock.ANY, self.network_id),
mock.call.dhcp_deallocate(
mock.ANY, self.network_id, mock.ANY, mock.ANY),
mock.call.dhcp_allocate(mock.ANY, self.network_id, subnet),
]
self._test_migrate(None, subnet, call_sequence)
def test_migrate_no_subnet(self):
call_sequence = [
mock.call.router_id_get(mock.ANY, None),
mock.call.dhcp_allocate(mock.ANY, self.network_id, None),
]
self._test_migrate(None, None, call_sequence)
def _test_report(self, lsn_attrs, expected):
self.manager.manager.lsn_port_get.return_value = lsn_attrs
report = self.manager.report(mock.ANY, self.network_id, self.subnet_id)
self.assertEqual(expected, report)
def test_report_for_lsn(self):
self._test_report(('foo_lsn_id', 'foo_lsn_port_id'),
{'ports': ['foo_lsn_port_id'],
'services': ['foo_lsn_id'], 'type': 'lsn'})
def test_report_for_lsn_without_lsn_port(self):
self._test_report(('foo_lsn_id', None),
{'ports': [],
'services': ['foo_lsn_id'], 'type': 'lsn'})
def _test_report_for_lsn_without_subnet(self, validated_subnet):
with mock.patch.object(self.manager, 'validate',
return_value=validated_subnet):
self.manager.manager.lsn_port_get.return_value = (
('foo_lsn_id', 'foo_lsn_port_id'))
report = self.manager.report(context, self.network_id)
expected = {
'ports': ['foo_lsn_port_id'] if validated_subnet else [],
'services': ['foo_lsn_id'], 'type': 'lsn'
}
self.assertEqual(expected, report)
def test_report_for_lsn_without_subnet_subnet_found(self):
self._test_report_for_lsn_without_subnet({'id': self.subnet_id})
def test_report_for_lsn_without_subnet_subnet_not_found(self):
self.manager.manager.lsn_get.return_value = 'foo_lsn_id'
self._test_report_for_lsn_without_subnet(None)
def test_report_for_dhcp_agent(self):
self.manager.manager.lsn_port_get.return_value = (None, None)
self.mock_builder.dhcp_agent_get_all.return_value = (
[{'id': 'foo_agent_id'}])
self.mock_builder.dhcp_port_get_all.return_value = (
[{'id': 'foo_dhcp_port_id'}])
result = self.manager.report(mock.ANY, self.network_id, self.subnet_id)
expected = {
'ports': ['foo_dhcp_port_id'],
'services': ['foo_agent_id'],
'type': 'agent'
}
self.assertEqual(expected, result)
class LsnManagerTestCase(base.BaseTestCase):
def setUp(self):
@ -37,11 +262,11 @@ class LsnManagerTestCase(base.BaseTestCase):
self.mac = 'aa:bb:cc:dd:ee:ff'
self.lsn_port_id = 'foo_lsn_port_id'
self.tenant_id = 'foo_tenant_id'
self.manager = nvp.LsnManager(mock.Mock())
self.mock_lsn_api_p = mock.patch.object(nvp, 'lsn_api')
self.manager = lsn_man.LsnManager(mock.Mock())
self.mock_lsn_api_p = mock.patch.object(lsn_man, 'lsn_api')
self.mock_lsn_api = self.mock_lsn_api_p.start()
nvp.register_dhcp_opts(cfg)
nvp.register_metadata_opts(cfg)
nsx.register_dhcp_opts(cfg)
nsx.register_metadata_opts(cfg)
self.addCleanup(cfg.CONF.reset)
self.addCleanup(self.mock_lsn_api_p.stop)
@ -121,7 +346,7 @@ class LsnManagerTestCase(base.BaseTestCase):
def _test_lsn_delete_by_network_with_exc(self, exc):
self.mock_lsn_api.lsn_for_network_get.side_effect = exc
with mock.patch.object(nvp.LOG, 'warn') as l:
with mock.patch.object(lsn_man.LOG, 'warn') as l:
self.manager.lsn_delete_by_network(mock.ANY, self.net_id)
self.assertEqual(1, l.call_count)
@ -195,7 +420,7 @@ class LsnManagerTestCase(base.BaseTestCase):
def _test_lsn_port_delete_with_exc(self, exc):
self.mock_lsn_api.lsn_port_delete.side_effect = exc
with mock.patch.object(nvp.LOG, 'warn') as l:
with mock.patch.object(lsn_man.LOG, 'warn') as l:
self.manager.lsn_port_delete(mock.ANY, mock.ANY, mock.ANY)
self.assertEqual(1, self.mock_lsn_api.lsn_port_delete.call_count)
self.assertEqual(1, l.call_count)
@ -210,7 +435,7 @@ class LsnManagerTestCase(base.BaseTestCase):
self.mock_lsn_api.lsn_port_create.return_value = self.lsn_port_id
with mock.patch.object(
self.manager, 'lsn_get', return_value=self.lsn_id):
with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag'):
with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag'):
expected = self.manager.lsn_port_dhcp_setup(
mock.ANY, mock.ANY, mock.ANY, mock.ANY, subnet_config=sub)
self.assertEqual(
@ -228,7 +453,7 @@ class LsnManagerTestCase(base.BaseTestCase):
self.assertEqual(1, f.call_count)
def test_lsn_port_dhcp_setup_with_not_found(self):
with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag') as f:
with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag') as f:
f.side_effect = n_exc.NotFound
self.assertRaises(p_exc.PortConfigurationError,
self.manager.lsn_port_dhcp_setup,
@ -237,7 +462,7 @@ class LsnManagerTestCase(base.BaseTestCase):
def test_lsn_port_dhcp_setup_with_conflict(self):
self.mock_lsn_api.lsn_port_plug_network.side_effect = (
p_exc.LsnConfigurationConflict(lsn_id=self.lsn_id))
with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag'):
with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag'):
with mock.patch.object(self.manager, 'lsn_port_delete') as g:
self.assertRaises(p_exc.PortConfigurationError,
self.manager.lsn_port_dhcp_setup,
@ -333,7 +558,7 @@ class LsnManagerTestCase(base.BaseTestCase):
'network_id': self.net_id,
'tenant_id': self.tenant_id
}
with mock.patch.object(nvp.nvplib, 'create_lport') as f:
with mock.patch.object(lsn_man.nsxlib, 'create_lport') as f:
f.return_value = {'uuid': self.port_id}
self.manager.lsn_port_metadata_setup(mock.ANY, self.lsn_id, subnet)
self.assertEqual(1, self.mock_lsn_api.lsn_port_create.call_count)
@ -347,7 +572,7 @@ class LsnManagerTestCase(base.BaseTestCase):
'network_id': self.net_id,
'tenant_id': self.tenant_id
}
with mock.patch.object(nvp.nvplib, 'create_lport') as f:
with mock.patch.object(lsn_man.nsxlib, 'create_lport') as f:
f.side_effect = n_exc.NotFound
self.assertRaises(p_exc.PortConfigurationError,
self.manager.lsn_port_metadata_setup,
@ -360,8 +585,8 @@ class LsnManagerTestCase(base.BaseTestCase):
'network_id': self.net_id,
'tenant_id': self.tenant_id
}
with mock.patch.object(nvp.nvplib, 'create_lport') as f:
with mock.patch.object(nvp.nvplib, 'delete_port') as g:
with mock.patch.object(lsn_man.nsxlib, 'create_lport') as f:
with mock.patch.object(lsn_man.nsxlib, 'delete_port') as g:
f.return_value = {'uuid': self.port_id}
self.mock_lsn_api.lsn_port_plug_network.side_effect = (
p_exc.LsnConfigurationConflict(lsn_id=self.lsn_id))
@ -385,14 +610,14 @@ class LsnManagerTestCase(base.BaseTestCase):
self.lsn_id, self.lsn_port_id, 1)
def test_lsn_port_dispose_meta_mac(self):
self.mac = nvp.METADATA_MAC
with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag') as f:
with mock.patch.object(nvp.nvplib, 'delete_port') as g:
self.mac = constants.METADATA_MAC
with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag') as f:
with mock.patch.object(lsn_man.nsxlib, 'delete_port') as g:
f.return_value = {'uuid': self.port_id}
self._test_lsn_port_dispose_with_values(
self.lsn_id, self.lsn_port_id, 1)
f.assert_called_once_with(
mock.ANY, self.net_id, nvp.METADATA_PORT_ID)
mock.ANY, self.net_id, constants.METADATA_PORT_ID)
g.assert_called_once_with(mock.ANY, self.net_id, self.port_id)
def test_lsn_port_dispose_lsn_not_found(self):
@ -403,7 +628,7 @@ class LsnManagerTestCase(base.BaseTestCase):
def test_lsn_port_dispose_api_error(self):
self.mock_lsn_api.lsn_port_delete.side_effect = NvpApiException
with mock.patch.object(nvp.LOG, 'warn') as l:
with mock.patch.object(lsn_man.LOG, 'warn') as l:
self.manager.lsn_port_dispose(mock.ANY, self.net_id, self.mac)
self.assertEqual(1, l.call_count)
@ -455,11 +680,188 @@ class LsnManagerTestCase(base.BaseTestCase):
mock.ANY, mock.ANY, mock.ANY, mock.ANY)
class PersistentLsnManagerTestCase(base.BaseTestCase):
def setUp(self):
super(PersistentLsnManagerTestCase, self).setUp()
self.net_id = 'foo_network_id'
self.sub_id = 'foo_subnet_id'
self.port_id = 'foo_port_id'
self.lsn_id = 'foo_lsn_id'
self.mac = 'aa:bb:cc:dd:ee:ff'
self.lsn_port_id = 'foo_lsn_port_id'
self.tenant_id = 'foo_tenant_id'
db.configure_db()
nsx.register_dhcp_opts(cfg)
nsx.register_metadata_opts(cfg)
lsn_man.register_lsn_opts(cfg)
self.manager = lsn_man.PersistentLsnManager(mock.Mock())
self.context = context.get_admin_context()
self.mock_lsn_api_p = mock.patch.object(lsn_man, 'lsn_api')
self.mock_lsn_api = self.mock_lsn_api_p.start()
self.addCleanup(cfg.CONF.reset)
self.addCleanup(self.mock_lsn_api_p.stop)
self.addCleanup(db.clear_db)
def test_lsn_get(self):
lsn_db.lsn_add(self.context, self.net_id, self.lsn_id)
result = self.manager.lsn_get(self.context, self.net_id)
self.assertEqual(self.lsn_id, result)
def test_lsn_get_raise_not_found(self):
self.assertRaises(p_exc.LsnNotFound,
self.manager.lsn_get, self.context, self.net_id)
def test_lsn_get_silent_not_found(self):
result = self.manager.lsn_get(
self.context, self.net_id, raise_on_err=False)
self.assertIsNone(result)
def test_lsn_get_sync_on_missing(self):
cfg.CONF.set_override('sync_on_missing_data', True, 'NSX_LSN')
self.manager = lsn_man.PersistentLsnManager(mock.Mock())
with mock.patch.object(self.manager, 'lsn_save') as f:
self.manager.lsn_get(self.context, self.net_id, raise_on_err=True)
self.assertTrue(self.mock_lsn_api.lsn_for_network_get.call_count)
self.assertTrue(f.call_count)
def test_lsn_save(self):
self.manager.lsn_save(self.context, self.net_id, self.lsn_id)
result = self.manager.lsn_get(self.context, self.net_id)
self.assertEqual(self.lsn_id, result)
def test_lsn_create(self):
self.mock_lsn_api.lsn_for_network_create.return_value = self.lsn_id
with mock.patch.object(self.manager, 'lsn_save') as f:
result = self.manager.lsn_create(self.context, self.net_id)
self.assertTrue(
self.mock_lsn_api.lsn_for_network_create.call_count)
self.assertTrue(f.call_count)
self.assertEqual(self.lsn_id, result)
def test_lsn_create_failure(self):
with mock.patch.object(
self.manager, 'lsn_save',
side_effect=p_exc.NvpPluginException(err_msg='')):
self.assertRaises(p_exc.NvpPluginException,
self.manager.lsn_create,
self.context, self.net_id)
self.assertTrue(self.mock_lsn_api.lsn_delete.call_count)
def test_lsn_delete(self):
self.mock_lsn_api.lsn_for_network_create.return_value = self.lsn_id
self.manager.lsn_create(self.context, self.net_id)
self.manager.lsn_delete(self.context, self.lsn_id)
self.assertIsNone(self.manager.lsn_get(
self.context, self.net_id, raise_on_err=False))
def test_lsn_delete_not_existent(self):
self.manager.lsn_delete(self.context, self.lsn_id)
self.assertTrue(self.mock_lsn_api.lsn_delete.call_count)
def test_lsn_port_get(self):
lsn_db.lsn_add(self.context, self.net_id, self.lsn_id)
lsn_db.lsn_port_add_for_lsn(self.context, self.lsn_port_id,
self.sub_id, self.mac, self.lsn_id)
res = self.manager.lsn_port_get(self.context, self.net_id, self.sub_id)
self.assertEqual((self.lsn_id, self.lsn_port_id), res)
def test_lsn_port_get_raise_not_found(self):
self.assertRaises(p_exc.LsnPortNotFound,
self.manager.lsn_port_get,
self.context, self.net_id, self.sub_id)
def test_lsn_port_get_silent_not_found(self):
result = self.manager.lsn_port_get(
self.context, self.net_id, self.sub_id, raise_on_err=False)
self.assertEqual((None, None), result)
def test_lsn_port_get_sync_on_missing(self):
return
cfg.CONF.set_override('sync_on_missing_data', True, 'NSX_LSN')
self.manager = lsn_man.PersistentLsnManager(mock.Mock())
self.mock_lsn_api.lsn_for_network_get.return_value = self.lsn_id
self.mock_lsn_api.lsn_port_by_subnet_get.return_value = (
self.lsn_id, self.lsn_port_id)
with mock.patch.object(self.manager, 'lsn_save') as f:
with mock.patch.object(self.manager, 'lsn_port_save') as g:
self.manager.lsn_port_get(
self.context, self.net_id, self.sub_id)
self.assertTrue(
self.mock_lsn_api.lsn_port_by_subnet_get.call_count)
self.assertTrue(
self.mock_lsn_api.lsn_port_info_get.call_count)
self.assertTrue(f.call_count)
self.assertTrue(g.call_count)
def test_lsn_port_get_by_mac(self):
lsn_db.lsn_add(self.context, self.net_id, self.lsn_id)
lsn_db.lsn_port_add_for_lsn(self.context, self.lsn_port_id,
self.sub_id, self.mac, self.lsn_id)
res = self.manager.lsn_port_get_by_mac(
self.context, self.net_id, self.mac)
self.assertEqual((self.lsn_id, self.lsn_port_id), res)
def test_lsn_port_get_by_mac_raise_not_found(self):
self.assertRaises(p_exc.LsnPortNotFound,
self.manager.lsn_port_get_by_mac,
self.context, self.net_id, self.sub_id)
def test_lsn_port_get_by_mac_silent_not_found(self):
result = self.manager.lsn_port_get_by_mac(
self.context, self.net_id, self.sub_id, raise_on_err=False)
self.assertEqual((None, None), result)
def test_lsn_port_create(self):
lsn_db.lsn_add(self.context, self.net_id, self.lsn_id)
self.mock_lsn_api.lsn_port_create.return_value = self.lsn_port_id
subnet = {'subnet_id': self.sub_id, 'mac_address': self.mac}
with mock.patch.object(self.manager, 'lsn_port_save') as f:
result = self.manager.lsn_port_create(
self.context, self.net_id, subnet)
self.assertTrue(
self.mock_lsn_api.lsn_port_create.call_count)
self.assertTrue(f.call_count)
self.assertEqual(self.lsn_port_id, result)
def test_lsn_port_create_failure(self):
subnet = {'subnet_id': self.sub_id, 'mac_address': self.mac}
with mock.patch.object(
self.manager, 'lsn_port_save',
side_effect=p_exc.NvpPluginException(err_msg='')):
self.assertRaises(p_exc.NvpPluginException,
self.manager.lsn_port_create,
self.context, self.net_id, subnet)
self.assertTrue(self.mock_lsn_api.lsn_port_delete.call_count)
def test_lsn_port_delete(self):
lsn_db.lsn_add(self.context, self.net_id, self.lsn_id)
lsn_db.lsn_port_add_for_lsn(self.context, self.lsn_port_id,
self.sub_id, self.mac, self.lsn_id)
self.manager.lsn_port_delete(
self.context, self.lsn_id, self.lsn_port_id)
self.assertEqual((None, None), self.manager.lsn_port_get(
self.context, self.lsn_id, self.sub_id, raise_on_err=False))
def test_lsn_port_delete_not_existent(self):
self.manager.lsn_port_delete(
self.context, self.lsn_id, self.lsn_port_id)
self.assertTrue(self.mock_lsn_api.lsn_port_delete.call_count)
def test_lsn_port_save(self):
self.manager.lsn_save(self.context, self.net_id, self.lsn_id)
self.manager.lsn_port_save(self.context, self.lsn_port_id,
self.sub_id, self.mac, self.lsn_id)
result = self.manager.lsn_port_get(
self.context, self.net_id, self.sub_id, raise_on_err=False)
self.assertEqual((self.lsn_id, self.lsn_port_id), result)
class DhcpAgentNotifyAPITestCase(base.BaseTestCase):
def setUp(self):
super(DhcpAgentNotifyAPITestCase, self).setUp()
self.notifier = nvp.DhcpAgentNotifyAPI(mock.Mock(), mock.Mock())
self.notifier = nsx.DhcpAgentNotifyAPI(mock.Mock(), mock.Mock())
self.plugin = self.notifier.plugin
self.lsn_manager = self.notifier.lsn_manager
@ -626,7 +1028,7 @@ class DhcpAgentNotifyAPITestCase(base.BaseTestCase):
self._test_subnet_create(False)
def test_subnet_create_raise_port_config_error(self):
with mock.patch.object(nvp.db_base_plugin_v2.NeutronDbPluginV2,
with mock.patch.object(nsx.db_base_plugin_v2.NeutronDbPluginV2,
'delete_port') as d:
self._test_subnet_create(
True,
@ -673,7 +1075,7 @@ class DhcpAgentNotifyAPITestCase(base.BaseTestCase):
entity_id=subnet['id']))
self.notifier.plugin.get_ports.return_value = dhcp_port
count = 0 if dhcp_port is None else 1
with mock.patch.object(nvp, 'handle_port_dhcp_access') as h:
with mock.patch.object(nsx, 'handle_port_dhcp_access') as h:
self.notifier.notify(
mock.ANY, {'subnet': subnet}, 'subnet.update.end')
self.assertEqual(count, h.call_count)
@ -723,7 +1125,7 @@ class DhcpTestCase(base.BaseTestCase):
def test_handle_create_network(self):
network = {'id': 'foo_network_id'}
nvp.handle_network_dhcp_access(
nsx.handle_network_dhcp_access(
self.plugin, mock.ANY, network, 'create_network')
self.plugin.lsn_manager.lsn_create.assert_called_once_with(
mock.ANY, network['id'])
@ -732,7 +1134,7 @@ class DhcpTestCase(base.BaseTestCase):
network_id = 'foo_network_id'
self.plugin.lsn_manager.lsn_delete_by_network.return_value = (
'foo_lsn_id')
nvp.handle_network_dhcp_access(
nsx.handle_network_dhcp_access(
self.plugin, mock.ANY, network_id, 'delete_network')
self.plugin.lsn_manager.lsn_delete_by_network.assert_called_once_with(
mock.ANY, 'foo_network_id')
@ -756,7 +1158,7 @@ class DhcpTestCase(base.BaseTestCase):
}
self.plugin.get_subnet.return_value = subnet
if exc is None:
nvp.handle_port_dhcp_access(
nsx.handle_port_dhcp_access(
self.plugin, mock.ANY, port, 'create_port')
(self.plugin.lsn_manager.lsn_port_dhcp_setup.
assert_called_once_with(mock.ANY, port['network_id'],
@ -764,7 +1166,7 @@ class DhcpTestCase(base.BaseTestCase):
else:
self.plugin.lsn_manager.lsn_port_dhcp_setup.side_effect = exc
self.assertRaises(n_exc.NeutronException,
nvp.handle_port_dhcp_access,
nsx.handle_port_dhcp_access,
self.plugin, mock.ANY, port, 'create_port')
def test_handle_create_dhcp_owner_port(self):
@ -784,7 +1186,7 @@ class DhcpTestCase(base.BaseTestCase):
'fixed_ips': [],
'mac_address': 'aa:bb:cc:dd:ee:ff'
}
nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, 'delete_port')
nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, 'delete_port')
self.plugin.lsn_manager.lsn_port_dispose.assert_called_once_with(
mock.ANY, port['network_id'], port['mac_address'])
@ -802,7 +1204,7 @@ class DhcpTestCase(base.BaseTestCase):
'mac_address': 'aa:bb:cc:dd:ee:ff'
}
self.plugin.get_subnet.return_value = {'enable_dhcp': True}
nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, action)
nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, action)
handler.assert_called_once_with(
mock.ANY, port['network_id'], 'foo_subnet_id', expected_data)
@ -824,7 +1226,7 @@ class DhcpTestCase(base.BaseTestCase):
'ip_address': '1.2.3.4'}]
}
self.plugin.get_subnet.return_value = {'enable_dhcp': False}
nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, action)
nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, action)
self.assertEqual(0, handler.call_count)
def test_handle_create_user_port_disabled_dhcp(self):
@ -842,7 +1244,7 @@ class DhcpTestCase(base.BaseTestCase):
'network_id': 'foo_network_id',
'fixed_ips': []
}
nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, action)
nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, action)
self.assertEqual(0, handler.call_count)
def test_handle_create_user_port_no_fixed_ips(self):
@ -869,7 +1271,7 @@ class MetadataTestCase(base.BaseTestCase):
'device_id': dev_id,
'fixed_ips': ips or []
}
nvp.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY)
nsx.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY)
self.assertFalse(
self.plugin.lsn_manager.lsn_port_meta_host_add.call_count)
self.assertFalse(
@ -884,7 +1286,7 @@ class MetadataTestCase(base.BaseTestCase):
'fixed_ips': [{'subnet_id': 'foo_subnet'}]
}
self.plugin.get_network.return_value = {'router:external': True}
nvp.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY)
nsx.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY)
self.assertFalse(
self.plugin.lsn_manager.lsn_port_meta_host_add.call_count)
self.assertFalse(
@ -930,10 +1332,10 @@ class MetadataTestCase(base.BaseTestCase):
if raise_exc:
mock_func.side_effect = p_exc.PortConfigurationError(
lsn_id='foo_lsn_id', net_id='foo_net_id', port_id=None)
with mock.patch.object(nvp.db_base_plugin_v2.NeutronDbPluginV2,
with mock.patch.object(nsx.db_base_plugin_v2.NeutronDbPluginV2,
'delete_port') as d:
self.assertRaises(p_exc.PortConfigurationError,
nvp.handle_port_metadata_access,
nsx.handle_port_metadata_access,
self.plugin, mock.ANY, port,
is_delete=is_delete)
if not is_delete:
@ -941,7 +1343,7 @@ class MetadataTestCase(base.BaseTestCase):
else:
self.assertFalse(d.call_count)
else:
nvp.handle_port_metadata_access(
nsx.handle_port_metadata_access(
self.plugin, mock.ANY, port, is_delete=is_delete)
mock_func.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, meta)
@ -971,17 +1373,17 @@ class MetadataTestCase(base.BaseTestCase):
if not is_port_found:
self.plugin.get_port.side_effect = n_exc.NotFound
if raise_exc:
with mock.patch.object(nvp.l3_db.L3_NAT_db_mixin,
with mock.patch.object(nsx.l3_db.L3_NAT_db_mixin,
'remove_router_interface') as d:
mock_func.side_effect = p_exc.NvpPluginException(err_msg='')
self.assertRaises(p_exc.NvpPluginException,
nvp.handle_router_metadata_access,
nsx.handle_router_metadata_access,
self.plugin, mock.ANY, 'foo_router_id',
interface)
d.assert_called_once_with(mock.ANY, mock.ANY, 'foo_router_id',
interface)
else:
nvp.handle_router_metadata_access(
nsx.handle_router_metadata_access(
self.plugin, mock.ANY, 'foo_router_id', interface)
mock_func.assert_called_once_with(
mock.ANY, subnet['id'], is_port_found)

View File

@ -0,0 +1,103 @@
# Copyright 2014 VMware, Inc.
#
# 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 orm
from neutron import context
from neutron.db import api as db
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.dbexts import lsn_db
from neutron.tests import base
class LSNTestCase(base.BaseTestCase):
def setUp(self):
super(LSNTestCase, self).setUp()
db.configure_db()
self.ctx = context.get_admin_context()
self.addCleanup(db.clear_db)
self.net_id = 'foo_network_id'
self.lsn_id = 'foo_lsn_id'
self.lsn_port_id = 'foo_port_id'
self.subnet_id = 'foo_subnet_id'
self.mac_addr = 'aa:bb:cc:dd:ee:ff'
def test_lsn_add(self):
lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id)
lsn = (self.ctx.session.query(lsn_db.Lsn).
filter_by(lsn_id=self.lsn_id).one())
self.assertEqual(self.lsn_id, lsn.lsn_id)
def test_lsn_remove(self):
lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id)
lsn_db.lsn_remove(self.ctx, self.lsn_id)
q = self.ctx.session.query(lsn_db.Lsn).filter_by(lsn_id=self.lsn_id)
self.assertRaises(orm.exc.NoResultFound, q.one)
def test_lsn_remove_for_network(self):
lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id)
lsn_db.lsn_remove_for_network(self.ctx, self.net_id)
q = self.ctx.session.query(lsn_db.Lsn).filter_by(lsn_id=self.lsn_id)
self.assertRaises(orm.exc.NoResultFound, q.one)
def test_lsn_get_for_network(self):
result = lsn_db.lsn_get_for_network(self.ctx, self.net_id,
raise_on_err=False)
self.assertIsNone(result)
def test_lsn_get_for_network_raise_not_found(self):
self.assertRaises(p_exc.LsnNotFound,
lsn_db.lsn_get_for_network,
self.ctx, self.net_id)
def test_lsn_port_add(self):
lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id)
lsn_db.lsn_port_add_for_lsn(self.ctx, self.lsn_port_id,
self.subnet_id, self.mac_addr, self.lsn_id)
result = (self.ctx.session.query(lsn_db.LsnPort).
filter_by(lsn_port_id=self.lsn_port_id).one())
self.assertEqual(self.lsn_port_id, result.lsn_port_id)
def test_lsn_port_get_for_mac(self):
lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id)
lsn_db.lsn_port_add_for_lsn(self.ctx, self.lsn_port_id,
self.subnet_id, self.mac_addr, self.lsn_id)
result = lsn_db.lsn_port_get_for_mac(self.ctx, self.mac_addr)
self.assertEqual(self.mac_addr, result.mac_addr)
def test_lsn_port_get_for_mac_raise_not_found(self):
self.assertRaises(p_exc.LsnPortNotFound,
lsn_db.lsn_port_get_for_mac,
self.ctx, self.mac_addr)
def test_lsn_port_get_for_subnet(self):
lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id)
lsn_db.lsn_port_add_for_lsn(self.ctx, self.lsn_port_id,
self.subnet_id, self.mac_addr, self.lsn_id)
result = lsn_db.lsn_port_get_for_subnet(self.ctx, self.subnet_id)
self.assertEqual(self.subnet_id, result.sub_id)
def test_lsn_port_get_for_subnet_raise_not_found(self):
self.assertRaises(p_exc.LsnPortNotFound,
lsn_db.lsn_port_get_for_subnet,
self.ctx, self.mac_addr)
def test_lsn_port_remove(self):
lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id)
lsn_db.lsn_port_remove(self.ctx, self.lsn_port_id)
q = (self.ctx.session.query(lsn_db.LsnPort).
filter_by(lsn_port_id=self.lsn_port_id))
self.assertRaises(orm.exc.NoResultFound, q.one)

View File

@ -1,5 +1,3 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@ -199,6 +197,30 @@ class LSNTestCase(base.BaseTestCase):
lsnlib._lsn_port_get,
self.cluster, "lsn_id", None)
def test_lsn_port_info_get(self):
self.mock_request.return_value = {
"tags": [
{"scope": "n_mac_address", "tag": "fa:16:3e:27:fd:a0"},
{"scope": "n_subnet_id", "tag": "foo_subnet_id"},
],
"mac_address": "aa:bb:cc:dd:ee:ff",
"ip_address": "0.0.0.0/0",
"uuid": "foo_lsn_port_id"
}
result = lsnlib.lsn_port_info_get(
self.cluster, 'foo_lsn_id', 'foo_lsn_port_id')
self.mock_request.assert_called_once_with(
'GET', '/ws.v1/lservices-node/foo_lsn_id/lport/foo_lsn_port_id',
cluster=self.cluster)
self.assertIn('subnet_id', result)
self.assertIn('mac_address', result)
def test_lsn_port_info_get_raise_not_found(self):
self.mock_request.side_effect = exceptions.NotFound
self.assertRaises(exceptions.NotFound,
lsnlib.lsn_port_info_get,
self.cluster, mock.ANY, mock.ANY)
def test_lsn_port_plug_network(self):
lsn_id = "foo_lsn_id"
lsn_port_id = "foo_lsn_port_id"

View File

@ -93,6 +93,7 @@ console_scripts =
neutron-nec-agent = neutron.plugins.nec.agent.nec_neutron_agent:main
neutron-netns-cleanup = neutron.agent.netns_cleanup_util:main
neutron-ns-metadata-proxy = neutron.agent.metadata.namespace_proxy:main
neutron-nsx-manage = neutron.plugins.nicira.shell:main
neutron-openvswitch-agent = neutron.plugins.openvswitch.agent.ovs_neutron_agent:main
neutron-ovs-cleanup = neutron.agent.ovs_cleanup_util:main
neutron-ryu-agent = neutron.plugins.ryu.agent.ryu_neutron_agent:main