# 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]
# 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 =
# 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]
# 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 =
"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"
}
--- /dev/null
+# 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')
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",
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:
'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")
--- /dev/null
+# 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())
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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)
--- /dev/null
+# 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
--- /dev/null
+# 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)
+++ /dev/null
-# 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)
-# 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
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__)
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
)
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,
--- /dev/null
+# 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 {}
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",
--- /dev/null
+# 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:])
--- /dev/null
+# 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'])
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2013 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
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):
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)
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)
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)
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(
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,
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,
'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)
'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,
'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))
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):
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)
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
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,
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)
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'])
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')
}
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'],
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):
'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'])
'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)
'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):
'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):
'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(
'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(
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:
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)
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)
--- /dev/null
+# 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)
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
# Copyright 2013 VMware, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
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"
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