diff --git a/etc/neutron/plugins/nicira/nvp.ini b/etc/neutron/plugins/nicira/nvp.ini index f031d381b1c..b22448ec440 100644 --- a/etc/neutron/plugins/nicira/nvp.ini +++ b/etc/neutron/plugins/nicira/nvp.ini @@ -119,11 +119,18 @@ # metadata proxy services to tenant instances. If 'agent' is chosen (default) # the NSX plugin relies on external RPC agents (i.e. dhcp and metadata agents) to # provide such services. In this mode, the plugin supports API extensions 'agent' -# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Havana), +# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Icehouse), # the plugin will use NSX logical services for DHCP and metadata proxy. This # simplifies the deployment model for Neutron, in that the plugin no longer requires # the RPC agents to operate. When 'agentless' is chosen, the config option metadata_mode -# becomes ineffective. The mode 'agentless' is not supported for NSX 4.0 or below. +# becomes ineffective. The 'agentless' mode is supported from NSX 4.2 or above. +# Furthermore, a 'combined' mode is also provided and is used to support existing +# deployments that want to adopt the agentless mode going forward. With this mode, +# existing networks keep being served by the existing infrastructure (thus preserving +# backward compatibility, whereas new networks will be served by the new infrastructure. +# Migration tools are provided to 'move' one network from one model to another; with +# agent_mode set to 'combined', option 'network_auto_schedule' in neutron.conf is +# ignored, as new networks will no longer be scheduled to existing dhcp agents. # agent_mode = agent [nsx_sync] @@ -165,6 +172,12 @@ # a considerable impact on overall performance. # always_read_status = False +[nsx_lsn] +# Pull LSN information from NSX in case it is missing from the local +# data store. This is useful to rebuild the local store in case of +# server recovery +# sync_on_missing_data = False + [nsx_dhcp] # (Optional) Comma separated list of additional dns servers. Default is an empty list # extra_domain_name_servers = diff --git a/etc/neutron/plugins/vmware/nsx.ini b/etc/neutron/plugins/vmware/nsx.ini index f2b47a3ae29..f30c501f995 100644 --- a/etc/neutron/plugins/vmware/nsx.ini +++ b/etc/neutron/plugins/vmware/nsx.ini @@ -114,11 +114,18 @@ # metadata proxy services to tenant instances. If 'agent' is chosen (default) # the NSX plugin relies on external RPC agents (i.e. dhcp and metadata agents) to # provide such services. In this mode, the plugin supports API extensions 'agent' -# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Havana), +# and 'dhcp_agent_scheduler'. If 'agentless' is chosen (experimental in Icehouse), # the plugin will use NSX logical services for DHCP and metadata proxy. This # simplifies the deployment model for Neutron, in that the plugin no longer requires # the RPC agents to operate. When 'agentless' is chosen, the config option metadata_mode -# becomes ineffective. The mode 'agentless' is not supported for NSX 4.0 or below. +# becomes ineffective. The 'agentless' mode is supported from NSX 4.2 or above. +# Furthermore, a 'combined' mode is also provided and is used to support existing +# deployments that want to adopt the agentless mode going forward. With this mode, +# existing networks keep being served by the existing infrastructure (thus preserving +# backward compatibility, whereas new networks will be served by the new infrastructure. +# Migration tools are provided to 'move' one network from one model to another; with +# agent_mode set to 'combined', option 'network_auto_schedule' in neutron.conf is +# ignored, as new networks will no longer be scheduled to existing dhcp agents. # agent_mode = agent [nsx_sync] @@ -160,6 +167,12 @@ # a considerable impact on overall performance. # always_read_status = False +[nsx_lsn] +# Pull LSN information from NSX in case it is missing from the local +# data store. This is useful to rebuild the local store in case of +# server recovery +# sync_on_missing_data = False + [nsx_dhcp] # (Optional) Comma separated list of additional dns servers. Default is an empty list # extra_domain_name_servers = diff --git a/etc/policy.json b/etc/policy.json index cd65e6b965f..a72d3a93d16 100644 --- a/etc/policy.json +++ b/etc/policy.json @@ -131,5 +131,7 @@ "delete_metering_label_rule": "rule:admin_only", "get_metering_label_rule": "rule:admin_only", - "get_service_provider": "rule:regular_user" + "get_service_provider": "rule:regular_user", + "get_lsn": "rule:admin_only", + "create_lsn": "rule:admin_only" } diff --git a/neutron/db/migration/alembic_migrations/versions/1421183d533f_nsx_dhcp_metadata.py b/neutron/db/migration/alembic_migrations/versions/1421183d533f_nsx_dhcp_metadata.py new file mode 100644 index 00000000000..2cfbf750577 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/1421183d533f_nsx_dhcp_metadata.py @@ -0,0 +1,72 @@ +# Copyright 2014 VMware, Inc. + +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""NSX DHCP/metadata support + +Revision ID: 1421183d533f +Revises: 8f682276ee4 +Create Date: 2013-10-11 14:33:37.303215 + +""" + +revision = '1421183d533f' +down_revision = '8f682276ee4' + +migration_for_plugins = [ + 'neutron.plugins.nicira.NeutronPlugin.NvpPluginV2', + 'neutron.plugins.nicira.NeutronServicePlugin.NvpAdvancedPlugin' +] + +from alembic import op +import sqlalchemy as sa + +from neutron.db import migration + + +def upgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.create_table( + 'lsn', + sa.Column('net_id', + sa.String(length=36), nullable=False), + sa.Column('lsn_id', + sa.String(length=36), nullable=False), + sa.PrimaryKeyConstraint('lsn_id')) + + op.create_table( + 'lsn_port', + sa.Column('lsn_port_id', + sa.String(length=36), nullable=False), + sa.Column('lsn_id', + sa.String(length=36), nullable=False), + sa.Column('sub_id', + sa.String(length=36), nullable=False, unique=True), + sa.Column('mac_addr', + sa.String(length=32), nullable=False, unique=True), + sa.ForeignKeyConstraint(['lsn_id'], ['lsn.lsn_id'], + ondelete='CASCADE'), + sa.PrimaryKeyConstraint('lsn_port_id')) + + +def downgrade(active_plugins=None, options=None): + if not migration.should_run(active_plugins, migration_for_plugins): + return + + op.drop_table('lsn_port') + op.drop_table('lsn') diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index edef16a79b1..317ddcb4608 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -120,10 +120,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, functionality using NVP. """ - supported_extension_aliases = ["agent", - "allowed-address-pairs", + supported_extension_aliases = ["allowed-address-pairs", "binding", - "dhcp_agent_scheduler", "dist-router", "ext-gw-mode", "extraroute", diff --git a/neutron/plugins/nicira/common/config.py b/neutron/plugins/nicira/common/config.py index 41c3c0679c0..9f1b73534b3 100644 --- a/neutron/plugins/nicira/common/config.py +++ b/neutron/plugins/nicira/common/config.py @@ -19,9 +19,8 @@ from oslo.config import cfg class AgentModes: AGENT = 'agent' - # TODO(armando-migliaccio): support to be added, maybe we could add a - # mixed mode to support no-downtime migrations? AGENTLESS = 'agentless' + COMBINED = 'combined' class MetadataModes: diff --git a/neutron/plugins/nicira/common/exceptions.py b/neutron/plugins/nicira/common/exceptions.py index 14add620194..d4b67cc4fac 100644 --- a/neutron/plugins/nicira/common/exceptions.py +++ b/neutron/plugins/nicira/common/exceptions.py @@ -105,5 +105,9 @@ class LsnPortNotFound(q_exc.NotFound): 'and %(entity)s %(entity_id)s')) +class LsnMigrationConflict(q_exc.Conflict): + message = _("Unable to migrate network '%(net_id)s' to LSN: %(reason)s") + + class LsnConfigurationConflict(NvpPluginException): message = _("Configuration conflict on Logical Service Node %(lsn_id)s") diff --git a/neutron/plugins/nicira/dbexts/lsn_db.py b/neutron/plugins/nicira/dbexts/lsn_db.py new file mode 100644 index 00000000000..6cd6b7ca3dc --- /dev/null +++ b/neutron/plugins/nicira/dbexts/lsn_db.py @@ -0,0 +1,130 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from sqlalchemy import Column +from sqlalchemy import ForeignKey +from sqlalchemy import orm +from sqlalchemy import String + +from neutron.db import models_v2 +from neutron.openstack.common.db import exception as d_exc +from neutron.openstack.common import log as logging +from neutron.plugins.nicira.common import exceptions as p_exc + + +LOG = logging.getLogger(__name__) + + +class LsnPort(models_v2.model_base.BASEV2): + + __tablename__ = 'lsn_port' + + lsn_port_id = Column(String(36), primary_key=True) + + lsn_id = Column(String(36), ForeignKey('lsn.lsn_id', ondelete="CASCADE")) + sub_id = Column(String(36), nullable=False, unique=True) + mac_addr = Column(String(32), nullable=False, unique=True) + + def __init__(self, lsn_port_id, subnet_id, mac_address, lsn_id): + self.lsn_port_id = lsn_port_id + self.lsn_id = lsn_id + self.sub_id = subnet_id + self.mac_addr = mac_address + + +class Lsn(models_v2.model_base.BASEV2): + __tablename__ = 'lsn' + + lsn_id = Column(String(36), primary_key=True) + net_id = Column(String(36), nullable=False) + + def __init__(self, net_id, lsn_id): + self.net_id = net_id + self.lsn_id = lsn_id + + +def lsn_add(context, network_id, lsn_id): + """Add Logical Service Node information to persistent datastore.""" + with context.session.begin(subtransactions=True): + lsn = Lsn(network_id, lsn_id) + context.session.add(lsn) + + +def lsn_remove(context, lsn_id): + """Remove Logical Service Node information from datastore given its id.""" + with context.session.begin(subtransactions=True): + context.session.query(Lsn).filter_by(lsn_id=lsn_id).delete() + + +def lsn_remove_for_network(context, network_id): + """Remove information about the Logical Service Node given its network.""" + with context.session.begin(subtransactions=True): + context.session.query(Lsn).filter_by(net_id=network_id).delete() + + +def lsn_get_for_network(context, network_id, raise_on_err=True): + """Retrieve LSN information given its network id.""" + query = context.session.query(Lsn) + try: + return query.filter_by(net_id=network_id).one() + except (orm.exc.NoResultFound, d_exc.DBError): + logger = raise_on_err and LOG.error or LOG.warn + logger(_('Unable to find Logical Service Node for ' + 'network %s'), network_id) + if raise_on_err: + raise p_exc.LsnNotFound(entity='network', + entity_id=network_id) + + +def lsn_port_add_for_lsn(context, lsn_port_id, subnet_id, mac, lsn_id): + """Add Logical Service Node Port information to persistent datastore.""" + with context.session.begin(subtransactions=True): + lsn_port = LsnPort(lsn_port_id, subnet_id, mac, lsn_id) + context.session.add(lsn_port) + + +def lsn_port_get_for_subnet(context, subnet_id, raise_on_err=True): + """Return Logical Service Node Port information given its subnet id.""" + with context.session.begin(subtransactions=True): + try: + return (context.session.query(LsnPort). + filter_by(sub_id=subnet_id).one()) + except (orm.exc.NoResultFound, d_exc.DBError): + if raise_on_err: + raise p_exc.LsnPortNotFound(lsn_id=None, + entity='subnet', + entity_id=subnet_id) + + +def lsn_port_get_for_mac(context, mac_address, raise_on_err=True): + """Return Logical Service Node Port information given its mac address.""" + with context.session.begin(subtransactions=True): + try: + return (context.session.query(LsnPort). + filter_by(mac_addr=mac_address).one()) + except (orm.exc.NoResultFound, d_exc.DBError): + if raise_on_err: + raise p_exc.LsnPortNotFound(lsn_id=None, + entity='mac', + entity_id=mac_address) + + +def lsn_port_remove(context, lsn_port_id): + """Remove Logical Service Node port from the given Logical Service Node.""" + with context.session.begin(subtransactions=True): + (context.session.query(LsnPort). + filter_by(lsn_port_id=lsn_port_id).delete()) diff --git a/neutron/plugins/nicira/dhcp_meta/combined.py b/neutron/plugins/nicira/dhcp_meta/combined.py new file mode 100644 index 00000000000..3e59a202b6b --- /dev/null +++ b/neutron/plugins/nicira/dhcp_meta/combined.py @@ -0,0 +1,95 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api +from neutron.common import constants as const +from neutron.common import topics +from neutron.plugins.nicira.dhcp_meta import nsx as nsx_svc +from neutron.plugins.nicira.dhcp_meta import rpc as nsx_rpc + + +class DhcpAgentNotifyAPI(dhcp_rpc_agent_api.DhcpAgentNotifyAPI): + + def __init__(self, plugin, manager): + super(DhcpAgentNotifyAPI, self).__init__(topic=topics.DHCP_AGENT) + self.agentless_notifier = nsx_svc.DhcpAgentNotifyAPI(plugin, manager) + + def notify(self, context, data, methodname): + [resource, action, _e] = methodname.split('.') + lsn_manager = self.agentless_notifier.plugin.lsn_manager + plugin = self.agentless_notifier.plugin + if resource == 'network': + net_id = data['network']['id'] + elif resource in ['port', 'subnet']: + net_id = data[resource]['network_id'] + else: + # no valid resource + return + lsn_exists = lsn_manager.lsn_exists(context, net_id) + treat_dhcp_owner_specially = False + if lsn_exists: + # if lsn exists, the network is one created with the new model + if (resource == 'subnet' and action == 'create' and + const.DEVICE_OWNER_DHCP not in plugin.port_special_owners): + # network/subnet provisioned in the new model have a plain + # nsx lswitch port, no vif attachment + plugin.port_special_owners.append(const.DEVICE_OWNER_DHCP) + treat_dhcp_owner_specially = True + if (resource == 'port' and action == 'update' or + resource == 'subnet'): + self.agentless_notifier.notify(context, data, methodname) + elif not lsn_exists and resource in ['port', 'subnet']: + # call notifier for the agent-based mode + super(DhcpAgentNotifyAPI, self).notify(context, data, methodname) + if treat_dhcp_owner_specially: + # if subnets belong to networks created with the old model + # dhcp port does not need to be special cased, so put things + # back, since they were modified + plugin.port_special_owners.remove(const.DEVICE_OWNER_DHCP) + + +def handle_network_dhcp_access(plugin, context, network, action): + nsx_svc.handle_network_dhcp_access(plugin, context, network, action) + + +def handle_port_dhcp_access(plugin, context, port, action): + if plugin.lsn_manager.lsn_exists(context, port['network_id']): + nsx_svc.handle_port_dhcp_access(plugin, context, port, action) + else: + nsx_rpc.handle_port_dhcp_access(plugin, context, port, action) + + +def handle_port_metadata_access(plugin, context, port, is_delete=False): + if plugin.lsn_manager.lsn_exists(context, port['network_id']): + nsx_svc.handle_port_metadata_access(plugin, context, port, is_delete) + else: + nsx_rpc.handle_port_metadata_access(plugin, context, port, is_delete) + + +def handle_router_metadata_access(plugin, context, router_id, interface=None): + if interface: + subnet = plugin.get_subnet(context, interface['subnet_id']) + network_id = subnet['network_id'] + if plugin.lsn_manager.lsn_exists(context, network_id): + nsx_svc.handle_router_metadata_access( + plugin, context, router_id, interface) + else: + nsx_rpc.handle_router_metadata_access( + plugin, context, router_id, interface) + else: + nsx_rpc.handle_router_metadata_access( + plugin, context, router_id, interface) diff --git a/neutron/plugins/nicira/dhcp_meta/constants.py b/neutron/plugins/nicira/dhcp_meta/constants.py new file mode 100644 index 00000000000..1e9476a5b1d --- /dev/null +++ b/neutron/plugins/nicira/dhcp_meta/constants.py @@ -0,0 +1,28 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +from neutron.common import constants as const +from neutron.db import l3_db + +# A unique MAC to quickly identify the LSN port used for metadata services +# when dhcp on the subnet is off. Inspired by leet-speak for 'metadata'. +METADATA_MAC = "fa:15:73:74:d4:74" +METADATA_PORT_ID = 'metadata:id' +METADATA_PORT_NAME = 'metadata:name' +METADATA_DEVICE_ID = 'metadata:device' +SPECIAL_OWNERS = (const.DEVICE_OWNER_DHCP, + const.DEVICE_OWNER_ROUTER_GW, + l3_db.DEVICE_OWNER_ROUTER_INTF) diff --git a/neutron/plugins/nicira/dhcp_meta/lsnmanager.py b/neutron/plugins/nicira/dhcp_meta/lsnmanager.py new file mode 100644 index 00000000000..4c2bd34804f --- /dev/null +++ b/neutron/plugins/nicira/dhcp_meta/lsnmanager.py @@ -0,0 +1,449 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from oslo.config import cfg + +from neutron.common import exceptions as n_exc +from neutron.openstack.common.db import exception as db_exc +from neutron.openstack.common import log as logging +from neutron.plugins.nicira.common import exceptions as p_exc +from neutron.plugins.nicira.dbexts import lsn_db +from neutron.plugins.nicira.dhcp_meta import constants as const +from neutron.plugins.nicira.nsxlib import lsn as lsn_api +from neutron.plugins.nicira import nvplib as nsxlib + +LOG = logging.getLogger(__name__) + +META_CONF = 'metadata-proxy' +DHCP_CONF = 'dhcp' + + +lsn_opts = [ + cfg.BoolOpt('sync_on_missing_data', default=False, + help=_('Pull LSN information from NSX in case it is missing ' + 'from the local data store. This is useful to rebuild ' + 'the local store in case of server recovery.')) +] + + +def register_lsn_opts(config): + config.CONF.register_opts(lsn_opts, "NSX_LSN") + + +class LsnManager(object): + """Manage LSN entities associated with networks.""" + + def __init__(self, plugin): + self.plugin = plugin + + @property + def cluster(self): + return self.plugin.cluster + + def lsn_exists(self, context, network_id): + """Return True if a Logical Service Node exists for the network.""" + return self.lsn_get( + context, network_id, raise_on_err=False) is not None + + def lsn_get(self, context, network_id, raise_on_err=True): + """Retrieve the LSN id associated to the network.""" + try: + return lsn_api.lsn_for_network_get(self.cluster, network_id) + except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException): + logger = raise_on_err and LOG.error or LOG.warn + logger(_('Unable to find Logical Service Node for ' + 'network %s'), network_id) + if raise_on_err: + raise p_exc.LsnNotFound(entity='network', + entity_id=network_id) + + def lsn_create(self, context, network_id): + """Create a LSN associated to the network.""" + try: + return lsn_api.lsn_for_network_create(self.cluster, network_id) + except nsxlib.NvpApiClient.NvpApiException: + err_msg = _('Unable to create LSN for network %s') % network_id + raise p_exc.NvpPluginException(err_msg=err_msg) + + def lsn_delete(self, context, lsn_id): + """Delete a LSN given its id.""" + try: + lsn_api.lsn_delete(self.cluster, lsn_id) + except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException): + LOG.warn(_('Unable to delete Logical Service Node %s'), lsn_id) + + def lsn_delete_by_network(self, context, network_id): + """Delete a LSN associated to the network.""" + lsn_id = self.lsn_get(context, network_id, raise_on_err=False) + if lsn_id: + self.lsn_delete(context, lsn_id) + + def lsn_port_get(self, context, network_id, subnet_id, raise_on_err=True): + """Retrieve LSN and LSN port for the network and the subnet.""" + lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err) + if lsn_id: + try: + lsn_port_id = lsn_api.lsn_port_by_subnet_get( + self.cluster, lsn_id, subnet_id) + except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException): + logger = raise_on_err and LOG.error or LOG.warn + logger(_('Unable to find Logical Service Node Port for ' + 'LSN %(lsn_id)s and subnet %(subnet_id)s') + % {'lsn_id': lsn_id, 'subnet_id': subnet_id}) + if raise_on_err: + raise p_exc.LsnPortNotFound(lsn_id=lsn_id, + entity='subnet', + entity_id=subnet_id) + return (lsn_id, None) + else: + return (lsn_id, lsn_port_id) + else: + return (None, None) + + def lsn_port_get_by_mac(self, context, network_id, mac, raise_on_err=True): + """Retrieve LSN and LSN port given network and mac address.""" + lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err) + if lsn_id: + try: + lsn_port_id = lsn_api.lsn_port_by_mac_get( + self.cluster, lsn_id, mac) + except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException): + logger = raise_on_err and LOG.error or LOG.warn + logger(_('Unable to find Logical Service Node Port for ' + 'LSN %(lsn_id)s and mac address %(mac)s') + % {'lsn_id': lsn_id, 'mac': mac}) + if raise_on_err: + raise p_exc.LsnPortNotFound(lsn_id=lsn_id, + entity='MAC', + entity_id=mac) + return (lsn_id, None) + else: + return (lsn_id, lsn_port_id) + else: + return (None, None) + + def lsn_port_create(self, context, lsn_id, subnet_info): + """Create and return LSN port for associated subnet.""" + try: + return lsn_api.lsn_port_create(self.cluster, lsn_id, subnet_info) + except n_exc.NotFound: + raise p_exc.LsnNotFound(entity='', entity_id=lsn_id) + except nsxlib.NvpApiClient.NvpApiException: + err_msg = _('Unable to create port for LSN %s') % lsn_id + raise p_exc.NvpPluginException(err_msg=err_msg) + + def lsn_port_delete(self, context, lsn_id, lsn_port_id): + """Delete a LSN port from the Logical Service Node.""" + try: + lsn_api.lsn_port_delete(self.cluster, lsn_id, lsn_port_id) + except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException): + LOG.warn(_('Unable to delete LSN Port %s'), lsn_port_id) + + def lsn_port_dispose(self, context, network_id, mac_address): + """Delete a LSN port given the network and the mac address.""" + lsn_id, lsn_port_id = self.lsn_port_get_by_mac( + context, network_id, mac_address, raise_on_err=False) + if lsn_port_id: + self.lsn_port_delete(context, lsn_id, lsn_port_id) + if mac_address == const.METADATA_MAC: + try: + lswitch_port_id = nsxlib.get_port_by_neutron_tag( + self.cluster, network_id, + const.METADATA_PORT_ID)['uuid'] + nsxlib.delete_port( + self.cluster, network_id, lswitch_port_id) + except (n_exc.PortNotFoundOnNetwork, + nsxlib.NvpApiClient.NvpApiException): + LOG.warn(_("Metadata port not found while attempting " + "to delete it from network %s"), network_id) + else: + LOG.warn(_("Unable to find Logical Services Node " + "Port with MAC %s"), mac_address) + + def lsn_port_dhcp_setup( + self, context, network_id, port_id, port_data, subnet_config=None): + """Connect network to LSN via specified port and port_data.""" + try: + lsn_id = None + lswitch_port_id = nsxlib.get_port_by_neutron_tag( + self.cluster, network_id, port_id)['uuid'] + lsn_id = self.lsn_get(context, network_id) + lsn_port_id = self.lsn_port_create(context, lsn_id, port_data) + except (n_exc.NotFound, p_exc.NvpPluginException): + raise p_exc.PortConfigurationError( + net_id=network_id, lsn_id=lsn_id, port_id=port_id) + else: + try: + lsn_api.lsn_port_plug_network( + self.cluster, lsn_id, lsn_port_id, lswitch_port_id) + except p_exc.LsnConfigurationConflict: + self.lsn_port_delete(context, lsn_id, lsn_port_id) + raise p_exc.PortConfigurationError( + net_id=network_id, lsn_id=lsn_id, port_id=port_id) + if subnet_config: + self.lsn_port_dhcp_configure( + context, lsn_id, lsn_port_id, subnet_config) + else: + return (lsn_id, lsn_port_id) + + def lsn_port_metadata_setup(self, context, lsn_id, subnet): + """Connect subnet to specified LSN.""" + data = { + "mac_address": const.METADATA_MAC, + "ip_address": subnet['cidr'], + "subnet_id": subnet['id'] + } + network_id = subnet['network_id'] + tenant_id = subnet['tenant_id'] + lswitch_port_id = None + try: + lswitch_port_id = nsxlib.create_lport( + self.cluster, network_id, tenant_id, + const.METADATA_PORT_ID, const.METADATA_PORT_NAME, + const.METADATA_DEVICE_ID, True)['uuid'] + lsn_port_id = self.lsn_port_create(self.cluster, lsn_id, data) + except (n_exc.NotFound, p_exc.NvpPluginException, + nsxlib.NvpApiClient.NvpApiException): + raise p_exc.PortConfigurationError( + net_id=network_id, lsn_id=lsn_id, port_id=lswitch_port_id) + else: + try: + lsn_api.lsn_port_plug_network( + self.cluster, lsn_id, lsn_port_id, lswitch_port_id) + except p_exc.LsnConfigurationConflict: + self.lsn_port_delete(self.cluster, lsn_id, lsn_port_id) + nsxlib.delete_port(self.cluster, network_id, lswitch_port_id) + raise p_exc.PortConfigurationError( + net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id) + + def lsn_port_dhcp_configure(self, context, lsn_id, lsn_port_id, subnet): + """Enable/disable dhcp services with the given config options.""" + is_enabled = subnet["enable_dhcp"] + dhcp_options = { + "domain_name": cfg.CONF.NSX_DHCP.domain_name, + "default_lease_time": cfg.CONF.NSX_DHCP.default_lease_time, + } + dns_servers = cfg.CONF.NSX_DHCP.extra_domain_name_servers or [] + dns_servers.extend(subnet["dns_nameservers"]) + if subnet['gateway_ip']: + dhcp_options["routers"] = subnet["gateway_ip"] + if dns_servers: + dhcp_options["domain_name_servers"] = ",".join(dns_servers) + if subnet["host_routes"]: + dhcp_options["classless_static_routes"] = ( + ",".join(subnet["host_routes"]) + ) + try: + lsn_api.lsn_port_dhcp_configure( + self.cluster, lsn_id, lsn_port_id, is_enabled, dhcp_options) + except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException): + err_msg = (_('Unable to configure dhcp for Logical Service ' + 'Node %(lsn_id)s and port %(lsn_port_id)s') + % {'lsn_id': lsn_id, 'lsn_port_id': lsn_port_id}) + LOG.error(err_msg) + raise p_exc.NvpPluginException(err_msg=err_msg) + + def lsn_metadata_configure(self, context, subnet_id, is_enabled): + """Configure metadata service for the specified subnet.""" + subnet = self.plugin.get_subnet(context, subnet_id) + network_id = subnet['network_id'] + meta_conf = cfg.CONF.NSX_METADATA + metadata_options = { + 'metadata_server_ip': meta_conf.metadata_server_address, + 'metadata_server_port': meta_conf.metadata_server_port, + 'metadata_proxy_shared_secret': meta_conf.metadata_shared_secret + } + try: + lsn_id = self.lsn_get(context, network_id) + lsn_api.lsn_metadata_configure( + self.cluster, lsn_id, is_enabled, metadata_options) + except (p_exc.LsnNotFound, nsxlib.NvpApiClient.NvpApiException): + err_msg = (_('Unable to configure metadata ' + 'for subnet %s') % subnet_id) + LOG.error(err_msg) + raise p_exc.NvpPluginException(err_msg=err_msg) + if is_enabled: + try: + # test that the lsn port exists + self.lsn_port_get(context, network_id, subnet_id) + except p_exc.LsnPortNotFound: + # this might happen if subnet had dhcp off when created + # so create one, and wire it + self.lsn_port_metadata_setup(context, lsn_id, subnet) + else: + self.lsn_port_dispose(context, network_id, const.METADATA_MAC) + + def _lsn_port_host_conf(self, context, network_id, subnet_id, data, hdlr): + lsn_id = None + lsn_port_id = None + try: + lsn_id, lsn_port_id = self.lsn_port_get( + context, network_id, subnet_id) + hdlr(self.cluster, lsn_id, lsn_port_id, data) + except (n_exc.NotFound, nsxlib.NvpApiClient.NvpApiException): + LOG.error(_('Error while configuring LSN ' + 'port %s'), lsn_port_id) + raise p_exc.PortConfigurationError( + net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id) + + def lsn_port_dhcp_host_add(self, context, network_id, subnet_id, host): + """Add dhcp host entry to LSN port configuration.""" + self._lsn_port_host_conf(context, network_id, subnet_id, host, + lsn_api.lsn_port_dhcp_host_add) + + def lsn_port_dhcp_host_remove(self, context, network_id, subnet_id, host): + """Remove dhcp host entry from LSN port configuration.""" + self._lsn_port_host_conf(context, network_id, subnet_id, host, + lsn_api.lsn_port_dhcp_host_remove) + + def lsn_port_meta_host_add(self, context, network_id, subnet_id, host): + """Add dhcp host entry to LSN port configuration.""" + self._lsn_port_host_conf(context, network_id, subnet_id, host, + lsn_api.lsn_port_metadata_host_add) + + def lsn_port_meta_host_remove(self, context, network_id, subnet_id, host): + """Remove dhcp host entry from LSN port configuration.""" + self._lsn_port_host_conf(context, network_id, subnet_id, host, + lsn_api.lsn_port_metadata_host_remove) + + def lsn_port_update( + self, context, network_id, subnet_id, dhcp=None, meta=None): + """Update the specified configuration for the LSN port.""" + if not dhcp and not meta: + return + try: + lsn_id, lsn_port_id = self.lsn_port_get( + context, network_id, subnet_id, raise_on_err=False) + if dhcp and lsn_id and lsn_port_id: + lsn_api.lsn_port_host_entries_update( + self.cluster, lsn_id, lsn_port_id, DHCP_CONF, dhcp) + if meta and lsn_id and lsn_port_id: + lsn_api.lsn_port_host_entries_update( + self.cluster, lsn_id, lsn_port_id, META_CONF, meta) + except nsxlib.NvpApiClient.NvpApiException: + raise p_exc.PortConfigurationError( + net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id) + + +class PersistentLsnManager(LsnManager): + """Add local persistent state to LSN Manager.""" + + def __init__(self, plugin): + super(PersistentLsnManager, self).__init__(plugin) + self.sync_on_missing = cfg.CONF.NSX_LSN.sync_on_missing_data + + def lsn_get(self, context, network_id, raise_on_err=True): + try: + obj = lsn_db.lsn_get_for_network( + context, network_id, raise_on_err=raise_on_err) + return obj.lsn_id if obj else None + except p_exc.LsnNotFound: + if self.sync_on_missing: + lsn_id = super(PersistentLsnManager, self).lsn_get( + context, network_id, raise_on_err=raise_on_err) + self.lsn_save(context, network_id, lsn_id) + return lsn_id + if raise_on_err: + raise + + def lsn_save(self, context, network_id, lsn_id): + """Save LSN-Network mapping to the DB.""" + try: + lsn_db.lsn_add(context, network_id, lsn_id) + except db_exc.DBError: + err_msg = _('Unable to save LSN for network %s') % network_id + LOG.exception(err_msg) + raise p_exc.NvpPluginException(err_msg=err_msg) + + def lsn_create(self, context, network_id): + lsn_id = super(PersistentLsnManager, + self).lsn_create(context, network_id) + try: + self.lsn_save(context, network_id, lsn_id) + except p_exc.NvpPluginException: + super(PersistentLsnManager, self).lsn_delete(context, lsn_id) + raise + return lsn_id + + def lsn_delete(self, context, lsn_id): + lsn_db.lsn_remove(context, lsn_id) + super(PersistentLsnManager, self).lsn_delete(context, lsn_id) + + def lsn_port_get(self, context, network_id, subnet_id, raise_on_err=True): + try: + obj = lsn_db.lsn_port_get_for_subnet( + context, subnet_id, raise_on_err=raise_on_err) + return (obj.lsn_id, obj.lsn_port_id) if obj else (None, None) + except p_exc.LsnPortNotFound: + if self.sync_on_missing: + lsn_id, lsn_port_id = ( + super(PersistentLsnManager, self).lsn_port_get( + context, network_id, subnet_id, + raise_on_err=raise_on_err)) + mac_addr = lsn_api.lsn_port_info_get( + self.cluster, lsn_id, lsn_port_id)['mac_address'] + self.lsn_port_save( + context, lsn_port_id, subnet_id, mac_addr, lsn_id) + return (lsn_id, lsn_port_id) + if raise_on_err: + raise + + def lsn_port_get_by_mac(self, context, network_id, mac, raise_on_err=True): + try: + obj = lsn_db.lsn_port_get_for_mac( + context, mac, raise_on_err=raise_on_err) + return (obj.lsn_id, obj.lsn_port_id) if obj else (None, None) + except p_exc.LsnPortNotFound: + if self.sync_on_missing: + lsn_id, lsn_port_id = ( + super(PersistentLsnManager, self).lsn_port_get_by_mac( + context, network_id, mac, + raise_on_err=raise_on_err)) + subnet_id = lsn_api.lsn_port_info_get( + self.cluster, lsn_id, lsn_port_id).get('subnet_id') + self.lsn_port_save( + context, lsn_port_id, subnet_id, mac, lsn_id) + return (lsn_id, lsn_port_id) + if raise_on_err: + raise + + def lsn_port_save(self, context, lsn_port_id, subnet_id, mac_addr, lsn_id): + """Save LSN Port information to the DB.""" + try: + lsn_db.lsn_port_add_for_lsn( + context, lsn_port_id, subnet_id, mac_addr, lsn_id) + except db_exc.DBError: + err_msg = _('Unable to save LSN port for subnet %s') % subnet_id + LOG.exception(err_msg) + raise p_exc.NvpPluginException(err_msg=err_msg) + + def lsn_port_create(self, context, lsn_id, subnet_info): + lsn_port_id = super(PersistentLsnManager, + self).lsn_port_create(context, lsn_id, subnet_info) + try: + self.lsn_port_save(context, lsn_port_id, subnet_info['subnet_id'], + subnet_info['mac_address'], lsn_id) + except p_exc.NvpPluginException: + super(PersistentLsnManager, self).lsn_port_delete( + context, lsn_id, lsn_port_id) + raise + return lsn_port_id + + def lsn_port_delete(self, context, lsn_id, lsn_port_id): + lsn_db.lsn_port_remove(context, lsn_port_id) + super(PersistentLsnManager, self).lsn_port_delete( + context, lsn_id, lsn_port_id) diff --git a/neutron/plugins/nicira/dhcp_meta/migration.py b/neutron/plugins/nicira/dhcp_meta/migration.py new file mode 100644 index 00000000000..17f0b09906b --- /dev/null +++ b/neutron/plugins/nicira/dhcp_meta/migration.py @@ -0,0 +1,181 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from neutron.common import constants as const +from neutron.common import exceptions as n_exc +from neutron.extensions import external_net +from neutron.openstack.common import log as logging +from neutron.plugins.nicira.common import exceptions as p_exc +from neutron.plugins.nicira.dhcp_meta import nsx +from neutron.plugins.nicira.dhcp_meta import rpc + +LOG = logging.getLogger(__name__) + + +class DhcpMetadataBuilder(object): + + def __init__(self, plugin, agent_notifier): + self.plugin = plugin + self.notifier = agent_notifier + + def dhcp_agent_get_all(self, context, network_id): + """Return the agents managing the network.""" + return self.plugin.list_dhcp_agents_hosting_network( + context, network_id)['agents'] + + def dhcp_port_get_all(self, context, network_id): + """Return the dhcp ports allocated for the network.""" + filters = { + 'network_id': [network_id], + 'device_owner': [const.DEVICE_OWNER_DHCP] + } + return self.plugin.get_ports(context, filters=filters) + + def router_id_get(self, context, subnet=None): + """Return the router and interface used for the subnet.""" + if not subnet: + return + network_id = subnet['network_id'] + filters = { + 'network_id': [network_id], + 'device_owner': [const.DEVICE_OWNER_ROUTER_INTF] + } + ports = self.plugin.get_ports(context, filters=filters) + for port in ports: + if port['fixed_ips'][0]['subnet_id'] == subnet['id']: + return port['device_id'] + else: + raise n_exc.NotFound() + + def metadata_deallocate(self, context, router_id, subnet_id): + """Deallocate metadata services for the subnet.""" + interface = {'subnet_id': subnet_id} + self.plugin.remove_router_interface(context, router_id, interface) + + def metadata_allocate(self, context, router_id, subnet_id): + """Allocate metadata resources for the subnet via the router.""" + interface = {'subnet_id': subnet_id} + self.plugin.add_router_interface(context, router_id, interface) + + def dhcp_deallocate(self, context, network_id, agents, ports): + """Deallocate dhcp resources for the network.""" + for agent in agents: + self.plugin.remove_network_from_dhcp_agent( + context, agent['id'], network_id) + for port in ports: + try: + self.plugin.delete_port(context, port['id']) + except n_exc.PortNotFound: + LOG.error(_('Port %s is already gone'), port['id']) + + def dhcp_allocate(self, context, network_id, subnet): + """Allocate dhcp resources for the subnet.""" + # Create LSN resources + network_data = {'id': network_id} + nsx.handle_network_dhcp_access(self.plugin, context, + network_data, 'create_network') + if subnet: + subnet_data = {'subnet': subnet} + self.notifier.notify(context, subnet_data, 'subnet.create.end') + # Get DHCP host and metadata entries created for the LSN + port = { + 'network_id': network_id, + 'fixed_ips': [{'subnet_id': subnet['id']}] + } + self.notifier.notify(context, {'port': port}, 'port.update.end') + + +class MigrationManager(object): + + def __init__(self, plugin, lsn_manager, agent_notifier): + self.plugin = plugin + self.manager = lsn_manager + self.builder = DhcpMetadataBuilder(plugin, agent_notifier) + + def validate(self, context, network_id): + """Validate and return subnet's dhcp info for migration.""" + network = self.plugin.get_network(context, network_id) + + if self.manager.lsn_exists(context, network_id): + reason = _("LSN already exist") + raise p_exc.LsnMigrationConflict(net_id=network_id, reason=reason) + + if network[external_net.EXTERNAL]: + reason = _("Cannot migrate an external network") + raise n_exc.BadRequest(resource='network', msg=reason) + + filters = {'network_id': [network_id]} + subnets = self.plugin.get_subnets(context, filters=filters) + count = len(subnets) + if count == 0: + return None + elif count == 1 and subnets[0]['cidr'] == rpc.METADATA_SUBNET_CIDR: + reason = _("Cannot migrate a 'metadata' network") + raise n_exc.BadRequest(resource='network', msg=reason) + elif count > 1: + reason = _("Unable to support multiple subnets per network") + raise p_exc.LsnMigrationConflict(net_id=network_id, reason=reason) + else: + return subnets[0] + + def migrate(self, context, network_id, subnet=None): + """Migrate subnet resources to LSN.""" + router_id = self.builder.router_id_get(context, subnet) + if router_id and subnet: + # Deallocate resources taken for the router, if any + self.builder.metadata_deallocate(context, router_id, subnet['id']) + if subnet: + # Deallocate reources taken for the agent, if any + agents = self.builder.dhcp_agent_get_all(context, network_id) + ports = self.builder.dhcp_port_get_all(context, network_id) + self.builder.dhcp_deallocate(context, network_id, agents, ports) + # (re)create the configuration for LSN + self.builder.dhcp_allocate(context, network_id, subnet) + if router_id and subnet: + # Allocate resources taken for the router, if any + self.builder.metadata_allocate(context, router_id, subnet['id']) + + def report(self, context, network_id, subnet_id=None): + """Return a report of the dhcp and metadata resources in use.""" + if subnet_id: + lsn_id, lsn_port_id = self.manager.lsn_port_get( + context, network_id, subnet_id, raise_on_err=False) + else: + subnet = self.validate(context, network_id) + if subnet: + lsn_id, lsn_port_id = self.manager.lsn_port_get( + context, network_id, subnet['id'], raise_on_err=False) + else: + lsn_id = self.manager.lsn_get(context, network_id, + raise_on_err=False) + lsn_port_id = None + if lsn_id: + ports = [lsn_port_id] if lsn_port_id else [] + report = { + 'type': 'lsn', + 'services': [lsn_id], + 'ports': ports + } + else: + agents = self.builder.dhcp_agent_get_all(context, network_id) + ports = self.builder.dhcp_port_get_all(context, network_id) + report = { + 'type': 'agent', + 'services': [a['id'] for a in agents], + 'ports': [p['id'] for p in ports] + } + return report diff --git a/neutron/plugins/nicira/dhcp_meta/nsx.py b/neutron/plugins/nicira/dhcp_meta/nsx.py new file mode 100644 index 00000000000..f1267149330 --- /dev/null +++ b/neutron/plugins/nicira/dhcp_meta/nsx.py @@ -0,0 +1,317 @@ +# Copyright 2013 VMware, Inc. + +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from oslo.config import cfg + +from neutron.api.v2 import attributes as attr +from neutron.common import constants as const +from neutron.common import exceptions as n_exc +from neutron.db import db_base_plugin_v2 +from neutron.db import l3_db +from neutron.extensions import external_net +from neutron.openstack.common import log as logging +from neutron.plugins.nicira.common import exceptions as p_exc +from neutron.plugins.nicira.dhcp_meta import constants as d_const +from neutron.plugins.nicira.nsxlib import lsn as lsn_api + +LOG = logging.getLogger(__name__) + + +dhcp_opts = [ + cfg.ListOpt('extra_domain_name_servers', + deprecated_group='NVP_DHCP', + default=[], + help=_('Comma separated list of additional ' + 'domain name servers')), + cfg.StrOpt('domain_name', + deprecated_group='NVP_DHCP', + default='openstacklocal', + help=_('Domain to use for building the hostnames')), + cfg.IntOpt('default_lease_time', default=43200, + deprecated_group='NVP_DHCP', + help=_("Default DHCP lease time")), +] + + +metadata_opts = [ + cfg.StrOpt('metadata_server_address', + deprecated_group='NVP_METADATA', + default='127.0.0.1', + help=_("IP address used by Metadata server.")), + cfg.IntOpt('metadata_server_port', + deprecated_group='NVP_METADATA', + default=8775, + help=_("TCP Port used by Metadata server.")), + cfg.StrOpt('metadata_shared_secret', + deprecated_group='NVP_METADATA', + default='', + help=_('Shared secret to sign instance-id request'), + secret=True) +] + + +def register_dhcp_opts(config): + config.CONF.register_opts(dhcp_opts, group="NSX_DHCP") + + +def register_metadata_opts(config): + config.CONF.register_opts(metadata_opts, group="NSX_METADATA") + + +class DhcpAgentNotifyAPI(object): + + def __init__(self, plugin, lsn_manager): + self.plugin = plugin + self.lsn_manager = lsn_manager + self._handle_subnet_dhcp_access = {'create': self._subnet_create, + 'update': self._subnet_update, + 'delete': self._subnet_delete} + + def notify(self, context, data, methodname): + [resource, action, _e] = methodname.split('.') + if resource == 'subnet': + self._handle_subnet_dhcp_access[action](context, data['subnet']) + elif resource == 'port' and action == 'update': + self._port_update(context, data['port']) + + def _port_update(self, context, port): + # With no fixed IP's there's nothing that can be updated + if not port["fixed_ips"]: + return + network_id = port['network_id'] + subnet_id = port["fixed_ips"][0]['subnet_id'] + filters = {'network_id': [network_id]} + # Because NVP does not support updating a single host entry we + # got to build the whole list from scratch and update in bulk + ports = self.plugin.get_ports(context, filters) + if not ports: + return + dhcp_conf = [ + {'mac_address': p['mac_address'], + 'ip_address': p["fixed_ips"][0]['ip_address']} + for p in ports if is_user_port(p) + ] + meta_conf = [ + {'instance_id': p['device_id'], + 'ip_address': p["fixed_ips"][0]['ip_address']} + for p in ports if is_user_port(p, check_dev_id=True) + ] + self.lsn_manager.lsn_port_update( + context, network_id, subnet_id, dhcp=dhcp_conf, meta=meta_conf) + + def _subnet_create(self, context, subnet, clean_on_err=True): + if subnet['enable_dhcp']: + network_id = subnet['network_id'] + # Create port for DHCP service + dhcp_port = { + "name": "", + "admin_state_up": True, + "device_id": "", + "device_owner": const.DEVICE_OWNER_DHCP, + "network_id": network_id, + "tenant_id": subnet["tenant_id"], + "mac_address": attr.ATTR_NOT_SPECIFIED, + "fixed_ips": [{"subnet_id": subnet['id']}] + } + try: + # This will end up calling handle_port_dhcp_access + # down below as well as handle_port_metadata_access + self.plugin.create_port(context, {'port': dhcp_port}) + except p_exc.PortConfigurationError as e: + err_msg = (_("Error while creating subnet %(cidr)s for " + "network %(network)s. Please, contact " + "administrator") % + {"cidr": subnet["cidr"], + "network": network_id}) + LOG.error(err_msg) + db_base_plugin_v2.NeutronDbPluginV2.delete_port( + self.plugin, context, e.port_id) + if clean_on_err: + self.plugin.delete_subnet(context, subnet['id']) + raise n_exc.Conflict() + + def _subnet_update(self, context, subnet): + network_id = subnet['network_id'] + try: + lsn_id, lsn_port_id = self.lsn_manager.lsn_port_get( + context, network_id, subnet['id']) + self.lsn_manager.lsn_port_dhcp_configure( + context, lsn_id, lsn_port_id, subnet) + except p_exc.LsnPortNotFound: + # It's possible that the subnet was created with dhcp off; + # check if the subnet was uplinked onto a router, and if so + # remove the patch attachment between the metadata port and + # the lsn port, in favor on the one we'll be creating during + # _subnet_create + self.lsn_manager.lsn_port_dispose( + context, network_id, d_const.METADATA_MAC) + # also, check that a dhcp port exists first and provision it + # accordingly + filters = dict(network_id=[network_id], + device_owner=[const.DEVICE_OWNER_DHCP]) + ports = self.plugin.get_ports(context, filters=filters) + if ports: + handle_port_dhcp_access( + self.plugin, context, ports[0], 'create_port') + else: + self._subnet_create(context, subnet, clean_on_err=False) + + def _subnet_delete(self, context, subnet): + # FIXME(armando-migliaccio): it looks like that a subnet filter + # is ineffective; so filter by network for now. + network_id = subnet['network_id'] + filters = dict(network_id=[network_id], + device_owner=[const.DEVICE_OWNER_DHCP]) + # FIXME(armando-migliaccio): this may be race-y + ports = self.plugin.get_ports(context, filters=filters) + if ports: + # This will end up calling handle_port_dhcp_access + # down below as well as handle_port_metadata_access + self.plugin.delete_port(context, ports[0]['id']) + + +def is_user_port(p, check_dev_id=False): + usable = p['fixed_ips'] and p['device_owner'] not in d_const.SPECIAL_OWNERS + return usable if not check_dev_id else usable and p['device_id'] + + +def check_services_requirements(cluster): + ver = cluster.api_client.get_nvp_version() + # It sounds like 4.1 is the first one where DHCP in NSX + # will have the experimental feature + if ver.major >= 4 and ver.minor >= 1: + cluster_id = cfg.CONF.default_service_cluster_uuid + if not lsn_api.service_cluster_exists(cluster, cluster_id): + raise p_exc.ServiceClusterUnavailable(cluster_id=cluster_id) + else: + raise p_exc.NvpInvalidVersion(version=ver) + + +def handle_network_dhcp_access(plugin, context, network, action): + LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s") + % {"action": action, "resource": network}) + if action == 'create_network': + network_id = network['id'] + plugin.lsn_manager.lsn_create(context, network_id) + elif action == 'delete_network': + # NOTE(armando-migliaccio): on delete_network, network + # is just the network id + network_id = network + plugin.lsn_manager.lsn_delete_by_network(context, network_id) + LOG.info(_("Logical Services Node for network " + "%s configured successfully"), network_id) + + +def handle_port_dhcp_access(plugin, context, port, action): + LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s") + % {"action": action, "resource": port}) + if port["device_owner"] == const.DEVICE_OWNER_DHCP: + network_id = port["network_id"] + if action == "create_port": + # at this point the port must have a subnet and a fixed ip + subnet_id = port["fixed_ips"][0]['subnet_id'] + subnet = plugin.get_subnet(context, subnet_id) + subnet_data = { + "mac_address": port["mac_address"], + "ip_address": subnet['cidr'], + "subnet_id": subnet['id'] + } + try: + plugin.lsn_manager.lsn_port_dhcp_setup( + context, network_id, port['id'], subnet_data, subnet) + except p_exc.PortConfigurationError: + err_msg = (_("Error while configuring DHCP for " + "port %s"), port['id']) + LOG.error(err_msg) + raise n_exc.NeutronException() + elif action == "delete_port": + plugin.lsn_manager.lsn_port_dispose(context, network_id, + port['mac_address']) + elif port["device_owner"] != const.DEVICE_OWNER_DHCP: + if port.get("fixed_ips"): + # do something only if there are IP's and dhcp is enabled + subnet_id = port["fixed_ips"][0]['subnet_id'] + if not plugin.get_subnet(context, subnet_id)['enable_dhcp']: + LOG.info(_("DHCP is disabled for subnet %s: nothing " + "to do"), subnet_id) + return + host_data = { + "mac_address": port["mac_address"], + "ip_address": port["fixed_ips"][0]['ip_address'] + } + network_id = port["network_id"] + if action == "create_port": + handler = plugin.lsn_manager.lsn_port_dhcp_host_add + elif action == "delete_port": + handler = plugin.lsn_manager.lsn_port_dhcp_host_remove + try: + handler(context, network_id, subnet_id, host_data) + except p_exc.PortConfigurationError: + if action == 'create_port': + db_base_plugin_v2.NeutronDbPluginV2.delete_port( + plugin, context, port['id']) + raise + LOG.info(_("DHCP for port %s configured successfully"), port['id']) + + +def handle_port_metadata_access(plugin, context, port, is_delete=False): + if is_user_port(port, check_dev_id=True): + network_id = port["network_id"] + network = plugin.get_network(context, network_id) + if network[external_net.EXTERNAL]: + LOG.info(_("Network %s is external: nothing to do"), network_id) + return + subnet_id = port["fixed_ips"][0]['subnet_id'] + host_data = { + "instance_id": port["device_id"], + "tenant_id": port["tenant_id"], + "ip_address": port["fixed_ips"][0]['ip_address'] + } + LOG.info(_("Configuring metadata entry for port %s"), port) + if not is_delete: + handler = plugin.lsn_manager.lsn_port_meta_host_add + else: + handler = plugin.lsn_manager.lsn_port_meta_host_remove + try: + handler(context, network_id, subnet_id, host_data) + except p_exc.PortConfigurationError: + if not is_delete: + db_base_plugin_v2.NeutronDbPluginV2.delete_port( + plugin, context, port['id']) + raise + LOG.info(_("Metadata for port %s configured successfully"), port['id']) + + +def handle_router_metadata_access(plugin, context, router_id, interface=None): + LOG.info(_("Handle metadata access via router: %(r)s and " + "interface %(i)s") % {'r': router_id, 'i': interface}) + if interface: + try: + plugin.get_port(context, interface['port_id']) + is_enabled = True + except n_exc.NotFound: + is_enabled = False + subnet_id = interface['subnet_id'] + try: + plugin.lsn_manager.lsn_metadata_configure( + context, subnet_id, is_enabled) + except p_exc.NvpPluginException: + if is_enabled: + l3_db.L3_NAT_db_mixin.remove_router_interface( + plugin, context, router_id, interface) + raise + LOG.info(_("Metadata for router %s handled successfully"), router_id) diff --git a/neutron/plugins/nicira/dhcp_meta/nvp.py b/neutron/plugins/nicira/dhcp_meta/nvp.py deleted file mode 100644 index 574a6521876..00000000000 --- a/neutron/plugins/nicira/dhcp_meta/nvp.py +++ /dev/null @@ -1,625 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 VMware, Inc. -# All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -from oslo.config import cfg - -from neutron.api.v2 import attributes as attr -from neutron.common import constants as const -from neutron.common import exceptions as n_exc -from neutron.db import db_base_plugin_v2 -from neutron.db import l3_db -from neutron.extensions import external_net -from neutron.openstack.common import log as logging -from neutron.plugins.nicira.common import exceptions as p_exc -from neutron.plugins.nicira.nsxlib import lsn as lsn_api -from neutron.plugins.nicira import nvplib - - -LOG = logging.getLogger(__name__) -# A unique MAC to quickly identify the LSN port used for metadata services -# when dhcp on the subnet is off. Inspired by leet-speak for 'metadata'. -METADATA_MAC = "fa:15:73:74:d4:74" -METADATA_PORT_ID = 'metadata:id' -METADATA_PORT_NAME = 'metadata:name' -METADATA_DEVICE_ID = 'metadata:device' -META_CONF = 'metadata-proxy' -DHCP_CONF = 'dhcp' -SPECIAL_OWNERS = (const.DEVICE_OWNER_DHCP, - const.DEVICE_OWNER_ROUTER_GW, - l3_db.DEVICE_OWNER_ROUTER_INTF) - -dhcp_opts = [ - cfg.ListOpt('extra_domain_name_servers', - deprecated_group='NVP_DHCP', - default=[], - help=_('Comma separated list of additional ' - 'domain name servers')), - cfg.StrOpt('domain_name', - deprecated_group='NVP_DHCP', - default='openstacklocal', - help=_('Domain to use for building the hostnames')), - cfg.IntOpt('default_lease_time', default=43200, - deprecated_group='NVP_DHCP', - help=_("Default DHCP lease time")), -] - - -metadata_opts = [ - cfg.StrOpt('metadata_server_address', - deprecated_group='NVP_METADATA', - default='127.0.0.1', - help=_("IP address used by Metadata server.")), - cfg.IntOpt('metadata_server_port', - deprecated_group='NVP_METADATA', - default=8775, - help=_("TCP Port used by Metadata server.")), - cfg.StrOpt('metadata_shared_secret', - deprecated_group='NVP_METADATA', - default='', - help=_('Shared secret to sign instance-id request'), - secret=True) -] - - -def register_dhcp_opts(config): - config.CONF.register_opts(dhcp_opts, group="NSX_DHCP") - - -def register_metadata_opts(config): - config.CONF.register_opts(metadata_opts, group="NSX_METADATA") - - -class LsnManager(object): - """Manage LSN entities associated with networks.""" - - def __init__(self, plugin): - self.plugin = plugin - - @property - def cluster(self): - return self.plugin.cluster - - def lsn_get(self, context, network_id, raise_on_err=True): - """Retrieve the LSN id associated to the network.""" - try: - return lsn_api.lsn_for_network_get(self.cluster, network_id) - except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException): - logger = raise_on_err and LOG.error or LOG.warn - logger(_('Unable to find Logical Service Node for ' - 'network %s'), network_id) - if raise_on_err: - raise p_exc.LsnNotFound(entity='network', - entity_id=network_id) - - def lsn_create(self, context, network_id): - """Create a LSN associated to the network.""" - try: - return lsn_api.lsn_for_network_create(self.cluster, network_id) - except nvplib.NvpApiClient.NvpApiException: - err_msg = _('Unable to create LSN for network %s') % network_id - raise p_exc.NvpPluginException(err_msg=err_msg) - - def lsn_delete(self, context, lsn_id): - """Delete a LSN given its id.""" - try: - lsn_api.lsn_delete(self.cluster, lsn_id) - except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException): - LOG.warn(_('Unable to delete Logical Service Node %s'), lsn_id) - - def lsn_delete_by_network(self, context, network_id): - """Delete a LSN associated to the network.""" - lsn_id = self.lsn_get(context, network_id, raise_on_err=False) - if lsn_id: - self.lsn_delete(context, lsn_id) - - def lsn_port_get(self, context, network_id, subnet_id, raise_on_err=True): - """Retrieve LSN and LSN port for the network and the subnet.""" - lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err) - if lsn_id: - try: - lsn_port_id = lsn_api.lsn_port_by_subnet_get( - self.cluster, lsn_id, subnet_id) - except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException): - logger = raise_on_err and LOG.error or LOG.warn - logger(_('Unable to find Logical Service Node Port for ' - 'LSN %(lsn_id)s and subnet %(subnet_id)s') - % {'lsn_id': lsn_id, 'subnet_id': subnet_id}) - if raise_on_err: - raise p_exc.LsnPortNotFound(lsn_id=lsn_id, - entity='subnet', - entity_id=subnet_id) - return (lsn_id, None) - else: - return (lsn_id, lsn_port_id) - else: - return (None, None) - - def lsn_port_get_by_mac(self, context, network_id, mac, raise_on_err=True): - """Retrieve LSN and LSN port given network and mac address.""" - lsn_id = self.lsn_get(context, network_id, raise_on_err=raise_on_err) - if lsn_id: - try: - lsn_port_id = lsn_api.lsn_port_by_mac_get( - self.cluster, lsn_id, mac) - except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException): - logger = raise_on_err and LOG.error or LOG.warn - logger(_('Unable to find Logical Service Node Port for ' - 'LSN %(lsn_id)s and mac address %(mac)s') - % {'lsn_id': lsn_id, 'mac': mac}) - if raise_on_err: - raise p_exc.LsnPortNotFound(lsn_id=lsn_id, - entity='MAC', - entity_id=mac) - return (lsn_id, None) - else: - return (lsn_id, lsn_port_id) - else: - return (None, None) - - def lsn_port_create(self, context, lsn_id, subnet_info): - """Create and return LSN port for associated subnet.""" - try: - return lsn_api.lsn_port_create(self.cluster, lsn_id, subnet_info) - except n_exc.NotFound: - raise p_exc.LsnNotFound(entity='', entity_id=lsn_id) - except nvplib.NvpApiClient.NvpApiException: - err_msg = _('Unable to create port for LSN %s') % lsn_id - raise p_exc.NvpPluginException(err_msg=err_msg) - - def lsn_port_delete(self, context, lsn_id, lsn_port_id): - """Delete a LSN port from the Logical Service Node.""" - try: - lsn_api.lsn_port_delete(self.cluster, lsn_id, lsn_port_id) - except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException): - LOG.warn(_('Unable to delete LSN Port %s'), lsn_port_id) - - def lsn_port_dispose(self, context, network_id, mac_address): - """Delete a LSN port given the network and the mac address.""" - # NOTE(armando-migliaccio): dispose and delete are functionally - # equivalent, but they use different paraments to identify LSN - # and LSN port resources. - lsn_id, lsn_port_id = self.lsn_port_get_by_mac( - context, network_id, mac_address, raise_on_err=False) - if lsn_port_id: - self.lsn_port_delete(context, lsn_id, lsn_port_id) - if mac_address == METADATA_MAC: - try: - lswitch_port = nvplib.get_port_by_neutron_tag( - self.cluster, network_id, METADATA_PORT_ID) - if lswitch_port: - lswitch_port_id = lswitch_port['uuid'] - nvplib.delete_port( - self.cluster, network_id, lswitch_port_id) - else: - LOG.warn(_("Metadata port not found while attempting " - "to delete it from network %s"), network_id) - except (n_exc.PortNotFoundOnNetwork, - nvplib.NvpApiClient.NvpApiException): - LOG.warn(_("Metadata port not found while attempting " - "to delete it from network %s"), network_id) - else: - LOG.warn(_("Unable to find Logical Services Node " - "Port with MAC %s"), mac_address) - - def lsn_port_dhcp_setup( - self, context, network_id, port_id, port_data, subnet_config=None): - """Connect network to LSN via specified port and port_data.""" - try: - lsn_id = None - lswitch_port_id = nvplib.get_port_by_neutron_tag( - self.cluster, network_id, port_id)['uuid'] - lsn_id = self.lsn_get(context, network_id) - lsn_port_id = self.lsn_port_create(context, lsn_id, port_data) - except (n_exc.NotFound, p_exc.NvpPluginException): - raise p_exc.PortConfigurationError( - net_id=network_id, lsn_id=lsn_id, port_id=port_id) - try: - lsn_api.lsn_port_plug_network( - self.cluster, lsn_id, lsn_port_id, lswitch_port_id) - except p_exc.LsnConfigurationConflict: - self.lsn_port_delete(self.cluster, lsn_id, lsn_port_id) - raise p_exc.PortConfigurationError( - net_id=network_id, lsn_id=lsn_id, port_id=port_id) - if subnet_config: - self.lsn_port_dhcp_configure( - context, lsn_id, lsn_port_id, subnet_config) - else: - return (lsn_id, lsn_port_id) - - def lsn_port_metadata_setup(self, context, lsn_id, subnet): - """Connect subnet to specified LSN.""" - data = { - "mac_address": METADATA_MAC, - "ip_address": subnet['cidr'], - "subnet_id": subnet['id'] - } - network_id = subnet['network_id'] - tenant_id = subnet['tenant_id'] - lswitch_port_id = None - try: - lswitch_port_id = nvplib.create_lport( - self.cluster, network_id, tenant_id, - METADATA_PORT_ID, METADATA_PORT_NAME, - METADATA_DEVICE_ID, True)['uuid'] - lsn_port_id = self.lsn_port_create(self.cluster, lsn_id, data) - except (n_exc.NotFound, p_exc.NvpPluginException, - nvplib.NvpApiClient.NvpApiException): - raise p_exc.PortConfigurationError( - net_id=network_id, lsn_id=lsn_id, port_id=lswitch_port_id) - else: - try: - lsn_api.lsn_port_plug_network( - self.cluster, lsn_id, lsn_port_id, lswitch_port_id) - except p_exc.LsnConfigurationConflict: - self.lsn_port_delete(self.cluster, lsn_id, lsn_port_id) - nvplib.delete_port(self.cluster, network_id, lswitch_port_id) - raise p_exc.PortConfigurationError( - net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id) - - def lsn_port_dhcp_configure(self, context, lsn_id, lsn_port_id, subnet): - """Enable/disable dhcp services with the given config options.""" - is_enabled = subnet["enable_dhcp"] - dhcp_options = { - "domain_name": cfg.CONF.NSX_DHCP.domain_name, - "default_lease_time": cfg.CONF.NSX_DHCP.default_lease_time, - } - dns_servers = cfg.CONF.NSX_DHCP.extra_domain_name_servers - dns_servers.extend(subnet["dns_nameservers"]) - if subnet['gateway_ip']: - dhcp_options["routers"] = subnet["gateway_ip"] - if dns_servers: - dhcp_options["domain_name_servers"] = ",".join(dns_servers) - if subnet["host_routes"]: - dhcp_options["classless_static_routes"] = ( - ",".join(subnet["host_routes"]) - ) - try: - lsn_api.lsn_port_dhcp_configure( - self.cluster, lsn_id, lsn_port_id, is_enabled, dhcp_options) - except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException): - err_msg = (_('Unable to configure dhcp for Logical Service ' - 'Node %(lsn_id)s and port %(lsn_port_id)s') - % {'lsn_id': lsn_id, 'lsn_port_id': lsn_port_id}) - LOG.error(err_msg) - raise p_exc.NvpPluginException(err_msg=err_msg) - - def lsn_metadata_configure(self, context, subnet_id, is_enabled): - """Configure metadata service for the specified subnet.""" - subnet = self.plugin.get_subnet(context, subnet_id) - network_id = subnet['network_id'] - meta_conf = cfg.CONF.NSX_METADATA - metadata_options = { - 'metadata_server_ip': meta_conf.metadata_server_address, - 'metadata_server_port': meta_conf.metadata_server_port, - 'metadata_proxy_shared_secret': meta_conf.metadata_shared_secret - } - try: - lsn_id = self.lsn_get(context, network_id) - lsn_api.lsn_metadata_configure( - self.cluster, lsn_id, is_enabled, metadata_options) - except (p_exc.LsnNotFound, nvplib.NvpApiClient.NvpApiException): - err_msg = (_('Unable to configure metadata access ' - 'for subnet %s') % subnet_id) - LOG.error(err_msg) - raise p_exc.NvpPluginException(err_msg=err_msg) - if is_enabled: - try: - # test that the lsn port exists - self.lsn_port_get(context, network_id, subnet_id) - except p_exc.LsnPortNotFound: - # this might happen if subnet had dhcp off when created - # so create one, and wire it - self.lsn_port_metadata_setup(context, lsn_id, subnet) - else: - self.lsn_port_dispose(context, network_id, METADATA_MAC) - - def _lsn_port_host_conf(self, context, network_id, subnet_id, data, hdlr): - lsn_id = None - lsn_port_id = None - try: - lsn_id, lsn_port_id = self.lsn_port_get( - context, network_id, subnet_id) - hdlr(self.cluster, lsn_id, lsn_port_id, data) - except (n_exc.NotFound, nvplib.NvpApiClient.NvpApiException): - LOG.error(_('Error while configuring LSN ' - 'port %s'), lsn_port_id) - raise p_exc.PortConfigurationError( - net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id) - - def lsn_port_dhcp_host_add(self, context, network_id, subnet_id, host): - """Add dhcp host entry to LSN port configuration.""" - self._lsn_port_host_conf(context, network_id, subnet_id, host, - lsn_api.lsn_port_dhcp_host_add) - - def lsn_port_dhcp_host_remove(self, context, network_id, subnet_id, host): - """Remove dhcp host entry from LSN port configuration.""" - self._lsn_port_host_conf(context, network_id, subnet_id, host, - lsn_api.lsn_port_dhcp_host_remove) - - def lsn_port_meta_host_add(self, context, network_id, subnet_id, host): - """Add metadata host entry to LSN port configuration.""" - self._lsn_port_host_conf(context, network_id, subnet_id, host, - lsn_api.lsn_port_metadata_host_add) - - def lsn_port_meta_host_remove(self, context, network_id, subnet_id, host): - """Remove meta host entry from LSN port configuration.""" - self._lsn_port_host_conf(context, network_id, subnet_id, host, - lsn_api.lsn_port_metadata_host_remove) - - def lsn_port_update( - self, context, network_id, subnet_id, dhcp=None, meta=None): - """Update the specified configuration for the LSN port.""" - if not dhcp and not meta: - return - try: - lsn_id, lsn_port_id = self.lsn_port_get( - context, network_id, subnet_id, raise_on_err=False) - if dhcp and lsn_id and lsn_port_id: - lsn_api.lsn_port_host_entries_update( - self.cluster, lsn_id, lsn_port_id, DHCP_CONF, dhcp) - if meta and lsn_id and lsn_port_id: - lsn_api.lsn_port_host_entries_update( - self.cluster, lsn_id, lsn_port_id, META_CONF, meta) - except nvplib.NvpApiClient.NvpApiException: - raise p_exc.PortConfigurationError( - net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id) - - -class DhcpAgentNotifyAPI(object): - - def __init__(self, plugin, lsn_manager): - self.plugin = plugin - self.lsn_manager = lsn_manager - self._handle_subnet_dhcp_access = {'create': self._subnet_create, - 'update': self._subnet_update, - 'delete': self._subnet_delete} - - def notify(self, context, data, methodname): - [resource, action, _e] = methodname.split('.') - if resource == 'subnet': - self._handle_subnet_dhcp_access[action](context, data['subnet']) - elif resource == 'port' and action == 'update': - self._port_update(context, data['port']) - - def _port_update(self, context, port): - # With no fixed IP's there's nothing that can be updated - if not port["fixed_ips"]: - return - network_id = port['network_id'] - subnet_id = port["fixed_ips"][0]['subnet_id'] - filters = {'network_id': [network_id]} - # Because NVP does not support updating a single host entry we - # got to build the whole list from scratch and update in bulk - ports = self.plugin.get_ports(context, filters) - if not ports: - return - dhcp_conf = [ - {'mac_address': p['mac_address'], - 'ip_address': p["fixed_ips"][0]['ip_address']} - for p in ports if is_user_port(p) - ] - meta_conf = [ - {'instance_id': p['device_id'], - 'ip_address': p["fixed_ips"][0]['ip_address']} - for p in ports if is_user_port(p, check_dev_id=True) - ] - self.lsn_manager.lsn_port_update( - context, network_id, subnet_id, dhcp=dhcp_conf, meta=meta_conf) - - def _subnet_create(self, context, subnet, clean_on_err=True): - if subnet['enable_dhcp']: - network_id = subnet['network_id'] - # Create port for DHCP service - dhcp_port = { - "name": "", - "admin_state_up": True, - "device_id": "", - "device_owner": const.DEVICE_OWNER_DHCP, - "network_id": network_id, - "tenant_id": subnet["tenant_id"], - "mac_address": attr.ATTR_NOT_SPECIFIED, - "fixed_ips": [{"subnet_id": subnet['id']}] - } - try: - # This will end up calling handle_port_dhcp_access - # down below as well as handle_port_metadata_access - self.plugin.create_port(context, {'port': dhcp_port}) - except p_exc.PortConfigurationError as e: - err_msg = (_("Error while creating subnet %(cidr)s for " - "network %(network)s. Please, contact " - "administrator") % - {"cidr": subnet["cidr"], - "network": network_id}) - LOG.error(err_msg) - db_base_plugin_v2.NeutronDbPluginV2.delete_port( - self.plugin, context, e.port_id) - if clean_on_err: - self.plugin.delete_subnet(context, subnet['id']) - raise n_exc.Conflict() - - def _subnet_update(self, context, subnet): - network_id = subnet['network_id'] - try: - lsn_id, lsn_port_id = self.lsn_manager.lsn_port_get( - context, network_id, subnet['id']) - self.lsn_manager.lsn_port_dhcp_configure( - context, lsn_id, lsn_port_id, subnet) - except p_exc.LsnPortNotFound: - # It's possible that the subnet was created with dhcp off; - # check if the subnet was uplinked onto a router, and if so - # remove the patch attachment between the metadata port and - # the lsn port, in favor on the one we'll be creating during - # _subnet_create - self.lsn_manager.lsn_port_dispose( - context, network_id, METADATA_MAC) - # also, check that a dhcp port exists first and provision it - # accordingly - filters = dict(network_id=[network_id], - device_owner=[const.DEVICE_OWNER_DHCP]) - ports = self.plugin.get_ports(context, filters=filters) - if ports: - handle_port_dhcp_access( - self.plugin, context, ports[0], 'create_port') - else: - self._subnet_create(context, subnet, clean_on_err=False) - - def _subnet_delete(self, context, subnet): - # FIXME(armando-migliaccio): it looks like that a subnet filter - # is ineffective; so filter by network for now. - network_id = subnet['network_id'] - filters = dict(network_id=[network_id], - device_owner=[const.DEVICE_OWNER_DHCP]) - # FIXME(armando-migliaccio): this may be race-y - ports = self.plugin.get_ports(context, filters=filters) - if ports: - # This will end up calling handle_port_dhcp_access - # down below as well as handle_port_metadata_access - self.plugin.delete_port(context, ports[0]['id']) - - -def is_user_port(p, check_dev_id=False): - usable = p['fixed_ips'] and p['device_owner'] not in SPECIAL_OWNERS - return usable if not check_dev_id else usable and p['device_id'] - - -def check_services_requirements(cluster): - ver = cluster.api_client.get_nvp_version() - # It sounds like 4.1 is the first one where DHCP in NSX - # will have the experimental feature - if ver.major >= 4 and ver.minor >= 1: - cluster_id = cfg.CONF.default_service_cluster_uuid - if not lsn_api.service_cluster_exists(cluster, cluster_id): - raise p_exc.ServiceClusterUnavailable(cluster_id=cluster_id) - else: - raise p_exc.NvpInvalidVersion(version=ver) - - -def handle_network_dhcp_access(plugin, context, network, action): - LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s") - % {"action": action, "resource": network}) - if action == 'create_network': - network_id = network['id'] - plugin.lsn_manager.lsn_create(context, network_id) - elif action == 'delete_network': - # NOTE(armando-migliaccio): on delete_network, network - # is just the network id - network_id = network - plugin.lsn_manager.lsn_delete_by_network(context, network_id) - LOG.info(_("Logical Services Node for network " - "%s configured successfully"), network_id) - - -def handle_port_dhcp_access(plugin, context, port, action): - LOG.info(_("Performing DHCP %(action)s for resource: %(resource)s") - % {"action": action, "resource": port}) - if port["device_owner"] == const.DEVICE_OWNER_DHCP: - network_id = port["network_id"] - if action == "create_port": - # at this point the port must have a subnet and a fixed ip - subnet_id = port["fixed_ips"][0]['subnet_id'] - subnet = plugin.get_subnet(context, subnet_id) - subnet_data = { - "mac_address": port["mac_address"], - "ip_address": subnet['cidr'], - "subnet_id": subnet['id'] - } - try: - plugin.lsn_manager.lsn_port_dhcp_setup( - context, network_id, port['id'], subnet_data, subnet) - except p_exc.PortConfigurationError: - err_msg = (_("Error while configuring DHCP for " - "port %s"), port['id']) - LOG.error(err_msg) - raise n_exc.NeutronException() - elif action == "delete_port": - plugin.lsn_manager.lsn_port_dispose(context, network_id, - port['mac_address']) - elif port["device_owner"] != const.DEVICE_OWNER_DHCP: - if port.get("fixed_ips"): - # do something only if there are IP's and dhcp is enabled - subnet_id = port["fixed_ips"][0]['subnet_id'] - if not plugin.get_subnet(context, subnet_id)['enable_dhcp']: - LOG.info(_("DHCP is disabled for subnet %s: nothing " - "to do"), subnet_id) - return - host_data = { - "mac_address": port["mac_address"], - "ip_address": port["fixed_ips"][0]['ip_address'] - } - network_id = port["network_id"] - if action == "create_port": - handler = plugin.lsn_manager.lsn_port_dhcp_host_add - elif action == "delete_port": - handler = plugin.lsn_manager.lsn_port_dhcp_host_remove - try: - handler(context, network_id, subnet_id, host_data) - except p_exc.PortConfigurationError: - if action == 'create_port': - db_base_plugin_v2.NeutronDbPluginV2.delete_port( - plugin, context, port['id']) - raise - LOG.info(_("DHCP for port %s configured successfully"), port['id']) - - -def handle_port_metadata_access(plugin, context, port, is_delete=False): - if is_user_port(port, check_dev_id=True): - network_id = port["network_id"] - network = plugin.get_network(context, network_id) - if network[external_net.EXTERNAL]: - LOG.info(_("Network %s is external: nothing to do"), network_id) - return - subnet_id = port["fixed_ips"][0]['subnet_id'] - host_data = { - "instance_id": port["device_id"], - "tenant_id": port["tenant_id"], - "ip_address": port["fixed_ips"][0]['ip_address'] - } - LOG.info(_("Configuring metadata entry for port %s"), port) - if not is_delete: - handler = plugin.lsn_manager.lsn_port_meta_host_add - else: - handler = plugin.lsn_manager.lsn_port_meta_host_remove - try: - handler(context, network_id, subnet_id, host_data) - except p_exc.PortConfigurationError: - if not is_delete: - db_base_plugin_v2.NeutronDbPluginV2.delete_port( - plugin, context, port['id']) - raise - LOG.info(_("Metadata for port %s configured successfully"), port['id']) - - -def handle_router_metadata_access(plugin, context, router_id, interface=None): - LOG.info(_("Handle metadata access via router: %(r)s and " - "interface %(i)s") % {'r': router_id, 'i': interface}) - if interface: - try: - plugin.get_port(context, interface['port_id']) - is_enabled = True - except n_exc.NotFound: - is_enabled = False - subnet_id = interface['subnet_id'] - try: - plugin.lsn_manager.lsn_metadata_configure( - context, subnet_id, is_enabled) - except p_exc.NvpPluginException: - if is_enabled: - l3_db.L3_NAT_db_mixin.remove_router_interface( - plugin, context, router_id, interface) - raise - LOG.info(_("Metadata for router %s handled successfully"), router_id) diff --git a/neutron/plugins/nicira/dhcpmeta_modes.py b/neutron/plugins/nicira/dhcpmeta_modes.py index 030eeb2efb7..4e63abf2658 100644 --- a/neutron/plugins/nicira/dhcpmeta_modes.py +++ b/neutron/plugins/nicira/dhcpmeta_modes.py @@ -1,6 +1,5 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 VMware, Inc. +# # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -25,9 +24,13 @@ from neutron.openstack.common import importutils from neutron.openstack.common import log as logging from neutron.openstack.common import rpc from neutron.plugins.nicira.common import config -from neutron.plugins.nicira.common import exceptions as nvp_exc -from neutron.plugins.nicira.dhcp_meta import nvp as nvp_svc -from neutron.plugins.nicira.dhcp_meta import rpc as nvp_rpc +from neutron.plugins.nicira.common import exceptions as nsx_exc +from neutron.plugins.nicira.dhcp_meta import combined +from neutron.plugins.nicira.dhcp_meta import lsnmanager +from neutron.plugins.nicira.dhcp_meta import migration +from neutron.plugins.nicira.dhcp_meta import nsx as nsx_svc +from neutron.plugins.nicira.dhcp_meta import rpc as nsx_rpc +from neutron.plugins.nicira.extensions import lsn LOG = logging.getLogger(__name__) @@ -36,12 +39,21 @@ class DhcpMetadataAccess(object): def setup_dhcpmeta_access(self): """Initialize support for DHCP and Metadata services.""" + self._init_extensions() if cfg.CONF.NSX.agent_mode == config.AgentModes.AGENT: self._setup_rpc_dhcp_metadata() - mod = nvp_rpc + mod = nsx_rpc elif cfg.CONF.NSX.agent_mode == config.AgentModes.AGENTLESS: - self._setup_nvp_dhcp_metadata() - mod = nvp_svc + self._setup_nsx_dhcp_metadata() + mod = nsx_svc + elif cfg.CONF.NSX.agent_mode == config.AgentModes.COMBINED: + notifier = self._setup_nsx_dhcp_metadata() + self._setup_rpc_dhcp_metadata(notifier=notifier) + mod = combined + else: + error = _("Invalid agent_mode: %s") % cfg.CONF.NSX.agent_mode + LOG.error(error) + raise nsx_exc.NvpPluginException(err_msg=error) self.handle_network_dhcp_access_delegate = ( mod.handle_network_dhcp_access ) @@ -55,49 +67,78 @@ class DhcpMetadataAccess(object): mod.handle_router_metadata_access ) - def _setup_rpc_dhcp_metadata(self): + def _setup_rpc_dhcp_metadata(self, notifier=None): self.topic = topics.PLUGIN self.conn = rpc.create_connection(new=True) - self.dispatcher = nvp_rpc.NVPRpcCallbacks().create_rpc_dispatcher() - self.conn.create_consumer(self.topic, self.dispatcher, - fanout=False) + self.dispatcher = nsx_rpc.NVPRpcCallbacks().create_rpc_dispatcher() + self.conn.create_consumer(self.topic, self.dispatcher, fanout=False) self.agent_notifiers[const.AGENT_TYPE_DHCP] = ( - dhcp_rpc_agent_api.DhcpAgentNotifyAPI()) + notifier or dhcp_rpc_agent_api.DhcpAgentNotifyAPI()) self.conn.consume_in_thread() self.network_scheduler = importutils.import_object( cfg.CONF.network_scheduler_driver ) + self.supported_extension_aliases.extend( + ['agent', 'dhcp_agent_scheduler']) - def _setup_nvp_dhcp_metadata(self): - # In agentless mode the following extensions, and related - # operations, are not supported; so do not publish them - if "agent" in self.supported_extension_aliases: - self.supported_extension_aliases.remove("agent") - if "dhcp_agent_scheduler" in self.supported_extension_aliases: - self.supported_extension_aliases.remove( - "dhcp_agent_scheduler") - nvp_svc.register_dhcp_opts(cfg) - nvp_svc.register_metadata_opts(cfg) - self.lsn_manager = nvp_svc.LsnManager(self) - self.agent_notifiers[const.AGENT_TYPE_DHCP] = ( - nvp_svc.DhcpAgentNotifyAPI(self, self.lsn_manager)) - # In agentless mode, ports whose owner is DHCP need to - # be special cased; so add it to the list of special - # owners list - if const.DEVICE_OWNER_DHCP not in self.port_special_owners: - self.port_special_owners.append(const.DEVICE_OWNER_DHCP) + def _setup_nsx_dhcp_metadata(self): + self._check_services_requirements() + nsx_svc.register_dhcp_opts(cfg) + nsx_svc.register_metadata_opts(cfg) + lsnmanager.register_lsn_opts(cfg) + lsn_manager = lsnmanager.PersistentLsnManager(self) + self.lsn_manager = lsn_manager + if cfg.CONF.NSX.agent_mode == config.AgentModes.AGENTLESS: + notifier = nsx_svc.DhcpAgentNotifyAPI(self, lsn_manager) + self.agent_notifiers[const.AGENT_TYPE_DHCP] = notifier + # In agentless mode, ports whose owner is DHCP need to + # be special cased; so add it to the list of special + # owners list + if const.DEVICE_OWNER_DHCP not in self.port_special_owners: + self.port_special_owners.append(const.DEVICE_OWNER_DHCP) + elif cfg.CONF.NSX.agent_mode == config.AgentModes.COMBINED: + # This becomes ineffective, as all new networks creations + # are handled by Logical Services Nodes in NSX + cfg.CONF.set_override('network_auto_schedule', False) + LOG.warn(_('network_auto_schedule has been disabled')) + notifier = combined.DhcpAgentNotifyAPI(self, lsn_manager) + self.supported_extension_aliases.append(lsn.EXT_ALIAS) + # Add the capability to migrate dhcp and metadata services over + self.migration_manager = ( + migration.MigrationManager(self, lsn_manager, notifier)) + return notifier + + def _init_extensions(self): + extensions = (lsn.EXT_ALIAS, 'agent', 'dhcp_agent_scheduler') + for ext in extensions: + if ext in self.supported_extension_aliases: + self.supported_extension_aliases.remove(ext) + + def _check_services_requirements(self): try: error = None - nvp_svc.check_services_requirements(self.cluster) - except nvp_exc.NvpInvalidVersion: + nsx_svc.check_services_requirements(self.cluster) + except nsx_exc.NvpInvalidVersion: error = _("Unable to run Neutron with config option '%s', as NSX " - "does not support it") % config.AgentModes.AGENTLESS - except nvp_exc.ServiceClusterUnavailable: + "does not support it") % cfg.CONF.NSX.agent_mode + except nsx_exc.ServiceClusterUnavailable: error = _("Unmet dependency for config option " - "'%s'") % config.AgentModes.AGENTLESS + "'%s'") % cfg.CONF.NSX.agent_mode if error: LOG.exception(error) - raise nvp_exc.NvpPluginException(err_msg=error) + raise nsx_exc.NvpPluginException(err_msg=error) + + def get_lsn(self, context, network_id, fields=None): + report = self.migration_manager.report(context, network_id) + return {'network': network_id, 'report': report} + + def create_lsn(self, context, lsn): + network_id = lsn['lsn']['network'] + subnet = self.migration_manager.validate(context, network_id) + subnet_id = None if not subnet else subnet['id'] + self.migration_manager.migrate(context, network_id, subnet) + r = self.migration_manager.report(context, network_id, subnet_id) + return {'network': network_id, 'report': r} def handle_network_dhcp_access(self, context, network, action): self.handle_network_dhcp_access_delegate(self, context, diff --git a/neutron/plugins/nicira/extensions/lsn.py b/neutron/plugins/nicira/extensions/lsn.py new file mode 100644 index 00000000000..4a7d3ca3da5 --- /dev/null +++ b/neutron/plugins/nicira/extensions/lsn.py @@ -0,0 +1,82 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from neutron.api import extensions +from neutron.api.v2 import base +from neutron import manager + + +EXT_ALIAS = 'lsn' +COLLECTION_NAME = "%ss" % EXT_ALIAS + +RESOURCE_ATTRIBUTE_MAP = { + COLLECTION_NAME: { + 'network': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'is_visible': True}, + 'report': {'allow_post': False, 'allow_put': False, + 'is_visible': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'required_by_policy': True, + 'validate': {'type:string': None}, 'is_visible': True}, + }, +} + + +class Lsn(object): + """Enable LSN configuration for Neutron NSX networks.""" + + @classmethod + def get_name(cls): + return "Logical Service Node configuration" + + @classmethod + def get_alias(cls): + return EXT_ALIAS + + @classmethod + def get_description(cls): + return "Enables configuration of NSX Logical Services Node." + + @classmethod + def get_namespace(cls): + return "http://docs.openstack.org/ext/%s/api/v2.0" % EXT_ALIAS + + @classmethod + def get_updated(cls): + return "2013-10-05T10:00:00-00:00" + + @classmethod + def get_resources(cls): + """Returns Ext Resources.""" + exts = [] + plugin = manager.NeutronManager.get_plugin() + resource_name = EXT_ALIAS + collection_name = resource_name.replace('_', '-') + "s" + params = RESOURCE_ATTRIBUTE_MAP.get(COLLECTION_NAME, dict()) + controller = base.create_resource(collection_name, + resource_name, + plugin, params, allow_bulk=False) + ex = extensions.ResourceExtension(collection_name, controller) + exts.append(ex) + return exts + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} diff --git a/neutron/plugins/nicira/nsxlib/lsn.py b/neutron/plugins/nicira/nsxlib/lsn.py index 38966b441fd..ed291e5dc25 100644 --- a/neutron/plugins/nicira/nsxlib/lsn.py +++ b/neutron/plugins/nicira/nsxlib/lsn.py @@ -141,6 +141,19 @@ def lsn_port_by_subnet_get(cluster, lsn_id, subnet_id): return _lsn_port_get(cluster, lsn_id, filters) +def lsn_port_info_get(cluster, lsn_id, lsn_port_id): + result = do_request(HTTP_GET, + _build_uri_path(LSERVICESNODEPORT_RESOURCE, + parent_resource_id=lsn_id, + resource_id=lsn_port_id), + cluster=cluster) + for tag in result['tags']: + if tag['scope'] == 'n_subnet_id': + result['subnet_id'] = tag['tag'] + break + return result + + def lsn_port_plug_network(cluster, lsn_id, lsn_port_id, lswitch_port_id): patch_obj = { "type": "PatchAttachment", diff --git a/neutron/plugins/nicira/shell/__init__.py b/neutron/plugins/nicira/shell/__init__.py new file mode 100644 index 00000000000..be3c07985db --- /dev/null +++ b/neutron/plugins/nicira/shell/__init__.py @@ -0,0 +1,41 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from neutron.plugins.nicira.shell import commands as cmd +from neutronclient import shell + + +class NsxManage(shell.NeutronShell): + + def __init__(self, api_version): + super(NsxManage, self).__init__(api_version) + self.command_manager.add_command('net-migrate', cmd.NetworkMigrate) + self.command_manager.add_command('net-report', cmd.NetworkReport) + + def build_option_parser(self, description, version): + parser = super(NsxManage, self).build_option_parser( + description, version) + return parser + + def initialize_app(self, argv): + super(NsxManage, self).initialize_app(argv) + self.client = self.client_manager.neutron + + +def main(): + return NsxManage(shell.NEUTRON_API_VERSION).run(sys.argv[1:]) diff --git a/neutron/plugins/nicira/shell/commands.py b/neutron/plugins/nicira/shell/commands.py new file mode 100644 index 00000000000..b49fe5092ff --- /dev/null +++ b/neutron/plugins/nicira/shell/commands.py @@ -0,0 +1,66 @@ +# Copyright 2014 VMware, Inc. +# +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from neutronclient.neutron.v2_0 import find_resourceid_by_name_or_id +from neutronclient.neutron.v2_0 import NeutronCommand + +LSN_PATH = '/lsns' + + +def print_report(write_func, report): + write_func(_("\nService type = %s\n") % report['report']['type']) + services = ','.join(report['report']['services']) + ports = ','.join(report['report']['ports']) + write_func(_("Service uuids = %s\n") % services) + write_func(_("Port uuids = %s\n\n") % ports) + + +class NetworkReport(NeutronCommand): + """Retrieve network migration report.""" + + def get_parser(self, prog_name): + parser = super(NetworkReport, self).get_parser(prog_name) + parser.add_argument('network', metavar='network', + help=_('ID or name of network to run report on')) + return parser + + def run(self, parsed_args): + net = parsed_args.network + net_id = find_resourceid_by_name_or_id(self.app.client, 'network', net) + res = self.app.client.get("%s/%s" % (LSN_PATH, net_id)) + if res: + self.app.stdout.write(_('Migration report is:\n')) + print_report(self.app.stdout.write, res['lsn']) + + +class NetworkMigrate(NeutronCommand): + """Perform network migration.""" + + def get_parser(self, prog_name): + parser = super(NetworkMigrate, self).get_parser(prog_name) + parser.add_argument('network', metavar='network', + help=_('ID or name of network to migrate')) + return parser + + def run(self, parsed_args): + net = parsed_args.network + net_id = find_resourceid_by_name_or_id(self.app.client, 'network', net) + body = {'network': net_id} + res = self.app.client.post(LSN_PATH, body={'lsn': body}) + if res: + self.app.stdout.write(_('Migration has been successful:\n')) + print_report(self.app.stdout.write, res['lsn']) diff --git a/neutron/tests/unit/nicira/test_dhcpmeta.py b/neutron/tests/unit/nicira/test_dhcpmeta.py index 5c9cd9407c0..0be43af5a51 100644 --- a/neutron/tests/unit/nicira/test_dhcpmeta.py +++ b/neutron/tests/unit/nicira/test_dhcpmeta.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 VMware, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +18,239 @@ import mock from oslo.config import cfg from neutron.common import exceptions as n_exc +from neutron import context +from neutron.db import api as db from neutron.plugins.nicira.common import exceptions as p_exc -from neutron.plugins.nicira.dhcp_meta import nvp +from neutron.plugins.nicira.dbexts import lsn_db +from neutron.plugins.nicira.dhcp_meta import constants +from neutron.plugins.nicira.dhcp_meta import lsnmanager as lsn_man +from neutron.plugins.nicira.dhcp_meta import migration as mig_man +from neutron.plugins.nicira.dhcp_meta import nsx +from neutron.plugins.nicira.dhcp_meta import rpc from neutron.plugins.nicira.NvpApiClient import NvpApiException from neutron.tests import base +class DhcpMetadataBuilderTestCase(base.BaseTestCase): + + def setUp(self): + super(DhcpMetadataBuilderTestCase, self).setUp() + self.builder = mig_man.DhcpMetadataBuilder(mock.Mock(), mock.Mock()) + self.network_id = 'foo_network_id' + self.subnet_id = 'foo_subnet_id' + self.router_id = 'foo_router_id' + + def test_dhcp_agent_get_all(self): + expected = [] + self.builder.plugin.list_dhcp_agents_hosting_network.return_value = ( + {'agents': expected}) + agents = self.builder.dhcp_agent_get_all(mock.ANY, self.network_id) + self.assertEqual(expected, agents) + + def test_dhcp_port_get_all(self): + expected = [] + self.builder.plugin.get_ports.return_value = expected + ports = self.builder.dhcp_port_get_all(mock.ANY, self.network_id) + self.assertEqual(expected, ports) + + def test_router_id_get(self): + port = { + 'device_id': self.router_id, + 'network_id': self.network_id, + 'fixed_ips': [{'subnet_id': self.subnet_id}] + } + subnet = { + 'id': self.subnet_id, + 'network_id': self.network_id + } + self.builder.plugin.get_ports.return_value = [port] + result = self.builder.router_id_get(context, subnet) + self.assertEqual(self.router_id, result) + + def test_router_id_get_none_subnet(self): + self.assertIsNone(self.builder.router_id_get(mock.ANY, None)) + + def test_metadata_deallocate(self): + self.builder.metadata_deallocate( + mock.ANY, self.router_id, self.subnet_id) + self.assertTrue(self.builder.plugin.remove_router_interface.call_count) + + def test_metadata_allocate(self): + self.builder.metadata_allocate( + mock.ANY, self.router_id, self.subnet_id) + self.assertTrue(self.builder.plugin.add_router_interface.call_count) + + def test_dhcp_deallocate(self): + agents = [{'id': 'foo_agent_id'}] + ports = [{'id': 'foo_port_id'}] + self.builder.dhcp_deallocate(mock.ANY, self.network_id, agents, ports) + self.assertTrue( + self.builder.plugin.remove_network_from_dhcp_agent.call_count) + self.assertTrue(self.builder.plugin.delete_port.call_count) + + def _test_dhcp_allocate(self, subnet, expected_notify_count): + with mock.patch.object(mig_man.nsx, 'handle_network_dhcp_access') as f: + self.builder.dhcp_allocate(mock.ANY, self.network_id, subnet) + self.assertTrue(f.call_count) + self.assertEqual(expected_notify_count, + self.builder.notifier.notify.call_count) + + def test_dhcp_allocate(self): + subnet = {'network_id': self.network_id, 'id': self.subnet_id} + self._test_dhcp_allocate(subnet, 2) + + def test_dhcp_allocate_none_subnet(self): + self._test_dhcp_allocate(None, 0) + + +class MigrationManagerTestCase(base.BaseTestCase): + + def setUp(self): + super(MigrationManagerTestCase, self).setUp() + self.manager = mig_man.MigrationManager(mock.Mock(), + mock.Mock(), + mock.Mock()) + self.network_id = 'foo_network_id' + self.router_id = 'foo_router_id' + self.subnet_id = 'foo_subnet_id' + self.mock_builder_p = mock.patch.object(self.manager, 'builder') + self.mock_builder = self.mock_builder_p.start() + self.addCleanup(self.mock_builder_p.stop) + + def _test_validate(self, lsn_exists=False, ext_net=False, subnets=None): + network = {'router:external': ext_net} + self.manager.manager.lsn_exists.return_value = lsn_exists + self.manager.plugin.get_network.return_value = network + self.manager.plugin.get_subnets.return_value = subnets + result = self.manager.validate(mock.ANY, self.network_id) + if len(subnets): + self.assertEqual(subnets[0], result) + else: + self.assertIsNone(result) + + def test_validate_no_subnets(self): + self._test_validate(subnets=[]) + + def test_validate_with_one_subnet(self): + self._test_validate(subnets=[{'cidr': '0.0.0.0/0'}]) + + def test_validate_raise_conflict_many_subnets(self): + self.assertRaises(p_exc.LsnMigrationConflict, + self._test_validate, + subnets=[{'id': 'sub1'}, {'id': 'sub2'}]) + + def test_validate_raise_conflict_lsn_exists(self): + self.assertRaises(p_exc.LsnMigrationConflict, + self._test_validate, + lsn_exists=True) + + def test_validate_raise_badrequest_external_net(self): + self.assertRaises(n_exc.BadRequest, + self._test_validate, + ext_net=True) + + def test_validate_raise_badrequest_metadata_net(self): + self.assertRaises(n_exc.BadRequest, + self._test_validate, + ext_net=False, + subnets=[{'cidr': rpc.METADATA_SUBNET_CIDR}]) + + def _test_migrate(self, router, subnet, expected_calls): + self.mock_builder.router_id_get.return_value = router + self.manager.migrate(mock.ANY, self.network_id, subnet) + # testing the exact the order of calls is important + self.assertEqual(expected_calls, self.mock_builder.mock_calls) + + def test_migrate(self): + subnet = { + 'id': self.subnet_id, + 'network_id': self.network_id + } + call_sequence = [ + mock.call.router_id_get(mock.ANY, subnet), + mock.call.metadata_deallocate( + mock.ANY, self.router_id, self.subnet_id), + mock.call.dhcp_agent_get_all(mock.ANY, self.network_id), + mock.call.dhcp_port_get_all(mock.ANY, self.network_id), + mock.call.dhcp_deallocate( + mock.ANY, self.network_id, mock.ANY, mock.ANY), + mock.call.dhcp_allocate(mock.ANY, self.network_id, subnet), + mock.call.metadata_allocate( + mock.ANY, self.router_id, self.subnet_id) + ] + self._test_migrate(self.router_id, subnet, call_sequence) + + def test_migrate_no_router_uplink(self): + subnet = { + 'id': self.subnet_id, + 'network_id': self.network_id + } + call_sequence = [ + mock.call.router_id_get(mock.ANY, subnet), + mock.call.dhcp_agent_get_all(mock.ANY, self.network_id), + mock.call.dhcp_port_get_all(mock.ANY, self.network_id), + mock.call.dhcp_deallocate( + mock.ANY, self.network_id, mock.ANY, mock.ANY), + mock.call.dhcp_allocate(mock.ANY, self.network_id, subnet), + ] + self._test_migrate(None, subnet, call_sequence) + + def test_migrate_no_subnet(self): + call_sequence = [ + mock.call.router_id_get(mock.ANY, None), + mock.call.dhcp_allocate(mock.ANY, self.network_id, None), + ] + self._test_migrate(None, None, call_sequence) + + def _test_report(self, lsn_attrs, expected): + self.manager.manager.lsn_port_get.return_value = lsn_attrs + report = self.manager.report(mock.ANY, self.network_id, self.subnet_id) + self.assertEqual(expected, report) + + def test_report_for_lsn(self): + self._test_report(('foo_lsn_id', 'foo_lsn_port_id'), + {'ports': ['foo_lsn_port_id'], + 'services': ['foo_lsn_id'], 'type': 'lsn'}) + + def test_report_for_lsn_without_lsn_port(self): + self._test_report(('foo_lsn_id', None), + {'ports': [], + 'services': ['foo_lsn_id'], 'type': 'lsn'}) + + def _test_report_for_lsn_without_subnet(self, validated_subnet): + with mock.patch.object(self.manager, 'validate', + return_value=validated_subnet): + self.manager.manager.lsn_port_get.return_value = ( + ('foo_lsn_id', 'foo_lsn_port_id')) + report = self.manager.report(context, self.network_id) + expected = { + 'ports': ['foo_lsn_port_id'] if validated_subnet else [], + 'services': ['foo_lsn_id'], 'type': 'lsn' + } + self.assertEqual(expected, report) + + def test_report_for_lsn_without_subnet_subnet_found(self): + self._test_report_for_lsn_without_subnet({'id': self.subnet_id}) + + def test_report_for_lsn_without_subnet_subnet_not_found(self): + self.manager.manager.lsn_get.return_value = 'foo_lsn_id' + self._test_report_for_lsn_without_subnet(None) + + def test_report_for_dhcp_agent(self): + self.manager.manager.lsn_port_get.return_value = (None, None) + self.mock_builder.dhcp_agent_get_all.return_value = ( + [{'id': 'foo_agent_id'}]) + self.mock_builder.dhcp_port_get_all.return_value = ( + [{'id': 'foo_dhcp_port_id'}]) + result = self.manager.report(mock.ANY, self.network_id, self.subnet_id) + expected = { + 'ports': ['foo_dhcp_port_id'], + 'services': ['foo_agent_id'], + 'type': 'agent' + } + self.assertEqual(expected, result) + + class LsnManagerTestCase(base.BaseTestCase): def setUp(self): @@ -37,11 +262,11 @@ class LsnManagerTestCase(base.BaseTestCase): self.mac = 'aa:bb:cc:dd:ee:ff' self.lsn_port_id = 'foo_lsn_port_id' self.tenant_id = 'foo_tenant_id' - self.manager = nvp.LsnManager(mock.Mock()) - self.mock_lsn_api_p = mock.patch.object(nvp, 'lsn_api') + self.manager = lsn_man.LsnManager(mock.Mock()) + self.mock_lsn_api_p = mock.patch.object(lsn_man, 'lsn_api') self.mock_lsn_api = self.mock_lsn_api_p.start() - nvp.register_dhcp_opts(cfg) - nvp.register_metadata_opts(cfg) + nsx.register_dhcp_opts(cfg) + nsx.register_metadata_opts(cfg) self.addCleanup(cfg.CONF.reset) self.addCleanup(self.mock_lsn_api_p.stop) @@ -121,7 +346,7 @@ class LsnManagerTestCase(base.BaseTestCase): def _test_lsn_delete_by_network_with_exc(self, exc): self.mock_lsn_api.lsn_for_network_get.side_effect = exc - with mock.patch.object(nvp.LOG, 'warn') as l: + with mock.patch.object(lsn_man.LOG, 'warn') as l: self.manager.lsn_delete_by_network(mock.ANY, self.net_id) self.assertEqual(1, l.call_count) @@ -195,7 +420,7 @@ class LsnManagerTestCase(base.BaseTestCase): def _test_lsn_port_delete_with_exc(self, exc): self.mock_lsn_api.lsn_port_delete.side_effect = exc - with mock.patch.object(nvp.LOG, 'warn') as l: + with mock.patch.object(lsn_man.LOG, 'warn') as l: self.manager.lsn_port_delete(mock.ANY, mock.ANY, mock.ANY) self.assertEqual(1, self.mock_lsn_api.lsn_port_delete.call_count) self.assertEqual(1, l.call_count) @@ -210,7 +435,7 @@ class LsnManagerTestCase(base.BaseTestCase): self.mock_lsn_api.lsn_port_create.return_value = self.lsn_port_id with mock.patch.object( self.manager, 'lsn_get', return_value=self.lsn_id): - with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag'): + with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag'): expected = self.manager.lsn_port_dhcp_setup( mock.ANY, mock.ANY, mock.ANY, mock.ANY, subnet_config=sub) self.assertEqual( @@ -228,7 +453,7 @@ class LsnManagerTestCase(base.BaseTestCase): self.assertEqual(1, f.call_count) def test_lsn_port_dhcp_setup_with_not_found(self): - with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag') as f: + with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag') as f: f.side_effect = n_exc.NotFound self.assertRaises(p_exc.PortConfigurationError, self.manager.lsn_port_dhcp_setup, @@ -237,7 +462,7 @@ class LsnManagerTestCase(base.BaseTestCase): def test_lsn_port_dhcp_setup_with_conflict(self): self.mock_lsn_api.lsn_port_plug_network.side_effect = ( p_exc.LsnConfigurationConflict(lsn_id=self.lsn_id)) - with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag'): + with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag'): with mock.patch.object(self.manager, 'lsn_port_delete') as g: self.assertRaises(p_exc.PortConfigurationError, self.manager.lsn_port_dhcp_setup, @@ -333,7 +558,7 @@ class LsnManagerTestCase(base.BaseTestCase): 'network_id': self.net_id, 'tenant_id': self.tenant_id } - with mock.patch.object(nvp.nvplib, 'create_lport') as f: + with mock.patch.object(lsn_man.nsxlib, 'create_lport') as f: f.return_value = {'uuid': self.port_id} self.manager.lsn_port_metadata_setup(mock.ANY, self.lsn_id, subnet) self.assertEqual(1, self.mock_lsn_api.lsn_port_create.call_count) @@ -347,7 +572,7 @@ class LsnManagerTestCase(base.BaseTestCase): 'network_id': self.net_id, 'tenant_id': self.tenant_id } - with mock.patch.object(nvp.nvplib, 'create_lport') as f: + with mock.patch.object(lsn_man.nsxlib, 'create_lport') as f: f.side_effect = n_exc.NotFound self.assertRaises(p_exc.PortConfigurationError, self.manager.lsn_port_metadata_setup, @@ -360,8 +585,8 @@ class LsnManagerTestCase(base.BaseTestCase): 'network_id': self.net_id, 'tenant_id': self.tenant_id } - with mock.patch.object(nvp.nvplib, 'create_lport') as f: - with mock.patch.object(nvp.nvplib, 'delete_port') as g: + with mock.patch.object(lsn_man.nsxlib, 'create_lport') as f: + with mock.patch.object(lsn_man.nsxlib, 'delete_port') as g: f.return_value = {'uuid': self.port_id} self.mock_lsn_api.lsn_port_plug_network.side_effect = ( p_exc.LsnConfigurationConflict(lsn_id=self.lsn_id)) @@ -385,14 +610,14 @@ class LsnManagerTestCase(base.BaseTestCase): self.lsn_id, self.lsn_port_id, 1) def test_lsn_port_dispose_meta_mac(self): - self.mac = nvp.METADATA_MAC - with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag') as f: - with mock.patch.object(nvp.nvplib, 'delete_port') as g: + self.mac = constants.METADATA_MAC + with mock.patch.object(lsn_man.nsxlib, 'get_port_by_neutron_tag') as f: + with mock.patch.object(lsn_man.nsxlib, 'delete_port') as g: f.return_value = {'uuid': self.port_id} self._test_lsn_port_dispose_with_values( self.lsn_id, self.lsn_port_id, 1) f.assert_called_once_with( - mock.ANY, self.net_id, nvp.METADATA_PORT_ID) + mock.ANY, self.net_id, constants.METADATA_PORT_ID) g.assert_called_once_with(mock.ANY, self.net_id, self.port_id) def test_lsn_port_dispose_lsn_not_found(self): @@ -403,7 +628,7 @@ class LsnManagerTestCase(base.BaseTestCase): def test_lsn_port_dispose_api_error(self): self.mock_lsn_api.lsn_port_delete.side_effect = NvpApiException - with mock.patch.object(nvp.LOG, 'warn') as l: + with mock.patch.object(lsn_man.LOG, 'warn') as l: self.manager.lsn_port_dispose(mock.ANY, self.net_id, self.mac) self.assertEqual(1, l.call_count) @@ -455,11 +680,188 @@ class LsnManagerTestCase(base.BaseTestCase): mock.ANY, mock.ANY, mock.ANY, mock.ANY) +class PersistentLsnManagerTestCase(base.BaseTestCase): + + def setUp(self): + super(PersistentLsnManagerTestCase, self).setUp() + self.net_id = 'foo_network_id' + self.sub_id = 'foo_subnet_id' + self.port_id = 'foo_port_id' + self.lsn_id = 'foo_lsn_id' + self.mac = 'aa:bb:cc:dd:ee:ff' + self.lsn_port_id = 'foo_lsn_port_id' + self.tenant_id = 'foo_tenant_id' + db.configure_db() + nsx.register_dhcp_opts(cfg) + nsx.register_metadata_opts(cfg) + lsn_man.register_lsn_opts(cfg) + self.manager = lsn_man.PersistentLsnManager(mock.Mock()) + self.context = context.get_admin_context() + self.mock_lsn_api_p = mock.patch.object(lsn_man, 'lsn_api') + self.mock_lsn_api = self.mock_lsn_api_p.start() + self.addCleanup(cfg.CONF.reset) + self.addCleanup(self.mock_lsn_api_p.stop) + self.addCleanup(db.clear_db) + + def test_lsn_get(self): + lsn_db.lsn_add(self.context, self.net_id, self.lsn_id) + result = self.manager.lsn_get(self.context, self.net_id) + self.assertEqual(self.lsn_id, result) + + def test_lsn_get_raise_not_found(self): + self.assertRaises(p_exc.LsnNotFound, + self.manager.lsn_get, self.context, self.net_id) + + def test_lsn_get_silent_not_found(self): + result = self.manager.lsn_get( + self.context, self.net_id, raise_on_err=False) + self.assertIsNone(result) + + def test_lsn_get_sync_on_missing(self): + cfg.CONF.set_override('sync_on_missing_data', True, 'NSX_LSN') + self.manager = lsn_man.PersistentLsnManager(mock.Mock()) + with mock.patch.object(self.manager, 'lsn_save') as f: + self.manager.lsn_get(self.context, self.net_id, raise_on_err=True) + self.assertTrue(self.mock_lsn_api.lsn_for_network_get.call_count) + self.assertTrue(f.call_count) + + def test_lsn_save(self): + self.manager.lsn_save(self.context, self.net_id, self.lsn_id) + result = self.manager.lsn_get(self.context, self.net_id) + self.assertEqual(self.lsn_id, result) + + def test_lsn_create(self): + self.mock_lsn_api.lsn_for_network_create.return_value = self.lsn_id + with mock.patch.object(self.manager, 'lsn_save') as f: + result = self.manager.lsn_create(self.context, self.net_id) + self.assertTrue( + self.mock_lsn_api.lsn_for_network_create.call_count) + self.assertTrue(f.call_count) + self.assertEqual(self.lsn_id, result) + + def test_lsn_create_failure(self): + with mock.patch.object( + self.manager, 'lsn_save', + side_effect=p_exc.NvpPluginException(err_msg='')): + self.assertRaises(p_exc.NvpPluginException, + self.manager.lsn_create, + self.context, self.net_id) + self.assertTrue(self.mock_lsn_api.lsn_delete.call_count) + + def test_lsn_delete(self): + self.mock_lsn_api.lsn_for_network_create.return_value = self.lsn_id + self.manager.lsn_create(self.context, self.net_id) + self.manager.lsn_delete(self.context, self.lsn_id) + self.assertIsNone(self.manager.lsn_get( + self.context, self.net_id, raise_on_err=False)) + + def test_lsn_delete_not_existent(self): + self.manager.lsn_delete(self.context, self.lsn_id) + self.assertTrue(self.mock_lsn_api.lsn_delete.call_count) + + def test_lsn_port_get(self): + lsn_db.lsn_add(self.context, self.net_id, self.lsn_id) + lsn_db.lsn_port_add_for_lsn(self.context, self.lsn_port_id, + self.sub_id, self.mac, self.lsn_id) + res = self.manager.lsn_port_get(self.context, self.net_id, self.sub_id) + self.assertEqual((self.lsn_id, self.lsn_port_id), res) + + def test_lsn_port_get_raise_not_found(self): + self.assertRaises(p_exc.LsnPortNotFound, + self.manager.lsn_port_get, + self.context, self.net_id, self.sub_id) + + def test_lsn_port_get_silent_not_found(self): + result = self.manager.lsn_port_get( + self.context, self.net_id, self.sub_id, raise_on_err=False) + self.assertEqual((None, None), result) + + def test_lsn_port_get_sync_on_missing(self): + return + cfg.CONF.set_override('sync_on_missing_data', True, 'NSX_LSN') + self.manager = lsn_man.PersistentLsnManager(mock.Mock()) + self.mock_lsn_api.lsn_for_network_get.return_value = self.lsn_id + self.mock_lsn_api.lsn_port_by_subnet_get.return_value = ( + self.lsn_id, self.lsn_port_id) + with mock.patch.object(self.manager, 'lsn_save') as f: + with mock.patch.object(self.manager, 'lsn_port_save') as g: + self.manager.lsn_port_get( + self.context, self.net_id, self.sub_id) + self.assertTrue( + self.mock_lsn_api.lsn_port_by_subnet_get.call_count) + self.assertTrue( + self.mock_lsn_api.lsn_port_info_get.call_count) + self.assertTrue(f.call_count) + self.assertTrue(g.call_count) + + def test_lsn_port_get_by_mac(self): + lsn_db.lsn_add(self.context, self.net_id, self.lsn_id) + lsn_db.lsn_port_add_for_lsn(self.context, self.lsn_port_id, + self.sub_id, self.mac, self.lsn_id) + res = self.manager.lsn_port_get_by_mac( + self.context, self.net_id, self.mac) + self.assertEqual((self.lsn_id, self.lsn_port_id), res) + + def test_lsn_port_get_by_mac_raise_not_found(self): + self.assertRaises(p_exc.LsnPortNotFound, + self.manager.lsn_port_get_by_mac, + self.context, self.net_id, self.sub_id) + + def test_lsn_port_get_by_mac_silent_not_found(self): + result = self.manager.lsn_port_get_by_mac( + self.context, self.net_id, self.sub_id, raise_on_err=False) + self.assertEqual((None, None), result) + + def test_lsn_port_create(self): + lsn_db.lsn_add(self.context, self.net_id, self.lsn_id) + self.mock_lsn_api.lsn_port_create.return_value = self.lsn_port_id + subnet = {'subnet_id': self.sub_id, 'mac_address': self.mac} + with mock.patch.object(self.manager, 'lsn_port_save') as f: + result = self.manager.lsn_port_create( + self.context, self.net_id, subnet) + self.assertTrue( + self.mock_lsn_api.lsn_port_create.call_count) + self.assertTrue(f.call_count) + self.assertEqual(self.lsn_port_id, result) + + def test_lsn_port_create_failure(self): + subnet = {'subnet_id': self.sub_id, 'mac_address': self.mac} + with mock.patch.object( + self.manager, 'lsn_port_save', + side_effect=p_exc.NvpPluginException(err_msg='')): + self.assertRaises(p_exc.NvpPluginException, + self.manager.lsn_port_create, + self.context, self.net_id, subnet) + self.assertTrue(self.mock_lsn_api.lsn_port_delete.call_count) + + def test_lsn_port_delete(self): + lsn_db.lsn_add(self.context, self.net_id, self.lsn_id) + lsn_db.lsn_port_add_for_lsn(self.context, self.lsn_port_id, + self.sub_id, self.mac, self.lsn_id) + self.manager.lsn_port_delete( + self.context, self.lsn_id, self.lsn_port_id) + self.assertEqual((None, None), self.manager.lsn_port_get( + self.context, self.lsn_id, self.sub_id, raise_on_err=False)) + + def test_lsn_port_delete_not_existent(self): + self.manager.lsn_port_delete( + self.context, self.lsn_id, self.lsn_port_id) + self.assertTrue(self.mock_lsn_api.lsn_port_delete.call_count) + + def test_lsn_port_save(self): + self.manager.lsn_save(self.context, self.net_id, self.lsn_id) + self.manager.lsn_port_save(self.context, self.lsn_port_id, + self.sub_id, self.mac, self.lsn_id) + result = self.manager.lsn_port_get( + self.context, self.net_id, self.sub_id, raise_on_err=False) + self.assertEqual((self.lsn_id, self.lsn_port_id), result) + + class DhcpAgentNotifyAPITestCase(base.BaseTestCase): def setUp(self): super(DhcpAgentNotifyAPITestCase, self).setUp() - self.notifier = nvp.DhcpAgentNotifyAPI(mock.Mock(), mock.Mock()) + self.notifier = nsx.DhcpAgentNotifyAPI(mock.Mock(), mock.Mock()) self.plugin = self.notifier.plugin self.lsn_manager = self.notifier.lsn_manager @@ -626,7 +1028,7 @@ class DhcpAgentNotifyAPITestCase(base.BaseTestCase): self._test_subnet_create(False) def test_subnet_create_raise_port_config_error(self): - with mock.patch.object(nvp.db_base_plugin_v2.NeutronDbPluginV2, + with mock.patch.object(nsx.db_base_plugin_v2.NeutronDbPluginV2, 'delete_port') as d: self._test_subnet_create( True, @@ -673,7 +1075,7 @@ class DhcpAgentNotifyAPITestCase(base.BaseTestCase): entity_id=subnet['id'])) self.notifier.plugin.get_ports.return_value = dhcp_port count = 0 if dhcp_port is None else 1 - with mock.patch.object(nvp, 'handle_port_dhcp_access') as h: + with mock.patch.object(nsx, 'handle_port_dhcp_access') as h: self.notifier.notify( mock.ANY, {'subnet': subnet}, 'subnet.update.end') self.assertEqual(count, h.call_count) @@ -723,7 +1125,7 @@ class DhcpTestCase(base.BaseTestCase): def test_handle_create_network(self): network = {'id': 'foo_network_id'} - nvp.handle_network_dhcp_access( + nsx.handle_network_dhcp_access( self.plugin, mock.ANY, network, 'create_network') self.plugin.lsn_manager.lsn_create.assert_called_once_with( mock.ANY, network['id']) @@ -732,7 +1134,7 @@ class DhcpTestCase(base.BaseTestCase): network_id = 'foo_network_id' self.plugin.lsn_manager.lsn_delete_by_network.return_value = ( 'foo_lsn_id') - nvp.handle_network_dhcp_access( + nsx.handle_network_dhcp_access( self.plugin, mock.ANY, network_id, 'delete_network') self.plugin.lsn_manager.lsn_delete_by_network.assert_called_once_with( mock.ANY, 'foo_network_id') @@ -756,7 +1158,7 @@ class DhcpTestCase(base.BaseTestCase): } self.plugin.get_subnet.return_value = subnet if exc is None: - nvp.handle_port_dhcp_access( + nsx.handle_port_dhcp_access( self.plugin, mock.ANY, port, 'create_port') (self.plugin.lsn_manager.lsn_port_dhcp_setup. assert_called_once_with(mock.ANY, port['network_id'], @@ -764,7 +1166,7 @@ class DhcpTestCase(base.BaseTestCase): else: self.plugin.lsn_manager.lsn_port_dhcp_setup.side_effect = exc self.assertRaises(n_exc.NeutronException, - nvp.handle_port_dhcp_access, + nsx.handle_port_dhcp_access, self.plugin, mock.ANY, port, 'create_port') def test_handle_create_dhcp_owner_port(self): @@ -784,7 +1186,7 @@ class DhcpTestCase(base.BaseTestCase): 'fixed_ips': [], 'mac_address': 'aa:bb:cc:dd:ee:ff' } - nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, 'delete_port') + nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, 'delete_port') self.plugin.lsn_manager.lsn_port_dispose.assert_called_once_with( mock.ANY, port['network_id'], port['mac_address']) @@ -802,7 +1204,7 @@ class DhcpTestCase(base.BaseTestCase): 'mac_address': 'aa:bb:cc:dd:ee:ff' } self.plugin.get_subnet.return_value = {'enable_dhcp': True} - nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, action) + nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, action) handler.assert_called_once_with( mock.ANY, port['network_id'], 'foo_subnet_id', expected_data) @@ -824,7 +1226,7 @@ class DhcpTestCase(base.BaseTestCase): 'ip_address': '1.2.3.4'}] } self.plugin.get_subnet.return_value = {'enable_dhcp': False} - nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, action) + nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, action) self.assertEqual(0, handler.call_count) def test_handle_create_user_port_disabled_dhcp(self): @@ -842,7 +1244,7 @@ class DhcpTestCase(base.BaseTestCase): 'network_id': 'foo_network_id', 'fixed_ips': [] } - nvp.handle_port_dhcp_access(self.plugin, mock.ANY, port, action) + nsx.handle_port_dhcp_access(self.plugin, mock.ANY, port, action) self.assertEqual(0, handler.call_count) def test_handle_create_user_port_no_fixed_ips(self): @@ -869,7 +1271,7 @@ class MetadataTestCase(base.BaseTestCase): 'device_id': dev_id, 'fixed_ips': ips or [] } - nvp.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY) + nsx.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY) self.assertFalse( self.plugin.lsn_manager.lsn_port_meta_host_add.call_count) self.assertFalse( @@ -884,7 +1286,7 @@ class MetadataTestCase(base.BaseTestCase): 'fixed_ips': [{'subnet_id': 'foo_subnet'}] } self.plugin.get_network.return_value = {'router:external': True} - nvp.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY) + nsx.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY) self.assertFalse( self.plugin.lsn_manager.lsn_port_meta_host_add.call_count) self.assertFalse( @@ -930,10 +1332,10 @@ class MetadataTestCase(base.BaseTestCase): if raise_exc: mock_func.side_effect = p_exc.PortConfigurationError( lsn_id='foo_lsn_id', net_id='foo_net_id', port_id=None) - with mock.patch.object(nvp.db_base_plugin_v2.NeutronDbPluginV2, + with mock.patch.object(nsx.db_base_plugin_v2.NeutronDbPluginV2, 'delete_port') as d: self.assertRaises(p_exc.PortConfigurationError, - nvp.handle_port_metadata_access, + nsx.handle_port_metadata_access, self.plugin, mock.ANY, port, is_delete=is_delete) if not is_delete: @@ -941,7 +1343,7 @@ class MetadataTestCase(base.BaseTestCase): else: self.assertFalse(d.call_count) else: - nvp.handle_port_metadata_access( + nsx.handle_port_metadata_access( self.plugin, mock.ANY, port, is_delete=is_delete) mock_func.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, meta) @@ -971,17 +1373,17 @@ class MetadataTestCase(base.BaseTestCase): if not is_port_found: self.plugin.get_port.side_effect = n_exc.NotFound if raise_exc: - with mock.patch.object(nvp.l3_db.L3_NAT_db_mixin, + with mock.patch.object(nsx.l3_db.L3_NAT_db_mixin, 'remove_router_interface') as d: mock_func.side_effect = p_exc.NvpPluginException(err_msg='') self.assertRaises(p_exc.NvpPluginException, - nvp.handle_router_metadata_access, + nsx.handle_router_metadata_access, self.plugin, mock.ANY, 'foo_router_id', interface) d.assert_called_once_with(mock.ANY, mock.ANY, 'foo_router_id', interface) else: - nvp.handle_router_metadata_access( + nsx.handle_router_metadata_access( self.plugin, mock.ANY, 'foo_router_id', interface) mock_func.assert_called_once_with( mock.ANY, subnet['id'], is_port_found) diff --git a/neutron/tests/unit/nicira/test_lsn_db.py b/neutron/tests/unit/nicira/test_lsn_db.py new file mode 100644 index 00000000000..f84cb7b07e1 --- /dev/null +++ b/neutron/tests/unit/nicira/test_lsn_db.py @@ -0,0 +1,103 @@ +# Copyright 2014 VMware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from sqlalchemy import orm + +from neutron import context +from neutron.db import api as db +from neutron.plugins.nicira.common import exceptions as p_exc +from neutron.plugins.nicira.dbexts import lsn_db +from neutron.tests import base + + +class LSNTestCase(base.BaseTestCase): + + def setUp(self): + super(LSNTestCase, self).setUp() + db.configure_db() + self.ctx = context.get_admin_context() + self.addCleanup(db.clear_db) + self.net_id = 'foo_network_id' + self.lsn_id = 'foo_lsn_id' + self.lsn_port_id = 'foo_port_id' + self.subnet_id = 'foo_subnet_id' + self.mac_addr = 'aa:bb:cc:dd:ee:ff' + + def test_lsn_add(self): + lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id) + lsn = (self.ctx.session.query(lsn_db.Lsn). + filter_by(lsn_id=self.lsn_id).one()) + self.assertEqual(self.lsn_id, lsn.lsn_id) + + def test_lsn_remove(self): + lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id) + lsn_db.lsn_remove(self.ctx, self.lsn_id) + q = self.ctx.session.query(lsn_db.Lsn).filter_by(lsn_id=self.lsn_id) + self.assertRaises(orm.exc.NoResultFound, q.one) + + def test_lsn_remove_for_network(self): + lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id) + lsn_db.lsn_remove_for_network(self.ctx, self.net_id) + q = self.ctx.session.query(lsn_db.Lsn).filter_by(lsn_id=self.lsn_id) + self.assertRaises(orm.exc.NoResultFound, q.one) + + def test_lsn_get_for_network(self): + result = lsn_db.lsn_get_for_network(self.ctx, self.net_id, + raise_on_err=False) + self.assertIsNone(result) + + def test_lsn_get_for_network_raise_not_found(self): + self.assertRaises(p_exc.LsnNotFound, + lsn_db.lsn_get_for_network, + self.ctx, self.net_id) + + def test_lsn_port_add(self): + lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id) + lsn_db.lsn_port_add_for_lsn(self.ctx, self.lsn_port_id, + self.subnet_id, self.mac_addr, self.lsn_id) + result = (self.ctx.session.query(lsn_db.LsnPort). + filter_by(lsn_port_id=self.lsn_port_id).one()) + self.assertEqual(self.lsn_port_id, result.lsn_port_id) + + def test_lsn_port_get_for_mac(self): + lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id) + lsn_db.lsn_port_add_for_lsn(self.ctx, self.lsn_port_id, + self.subnet_id, self.mac_addr, self.lsn_id) + result = lsn_db.lsn_port_get_for_mac(self.ctx, self.mac_addr) + self.assertEqual(self.mac_addr, result.mac_addr) + + def test_lsn_port_get_for_mac_raise_not_found(self): + self.assertRaises(p_exc.LsnPortNotFound, + lsn_db.lsn_port_get_for_mac, + self.ctx, self.mac_addr) + + def test_lsn_port_get_for_subnet(self): + lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id) + lsn_db.lsn_port_add_for_lsn(self.ctx, self.lsn_port_id, + self.subnet_id, self.mac_addr, self.lsn_id) + result = lsn_db.lsn_port_get_for_subnet(self.ctx, self.subnet_id) + self.assertEqual(self.subnet_id, result.sub_id) + + def test_lsn_port_get_for_subnet_raise_not_found(self): + self.assertRaises(p_exc.LsnPortNotFound, + lsn_db.lsn_port_get_for_subnet, + self.ctx, self.mac_addr) + + def test_lsn_port_remove(self): + lsn_db.lsn_add(self.ctx, self.net_id, self.lsn_id) + lsn_db.lsn_port_remove(self.ctx, self.lsn_port_id) + q = (self.ctx.session.query(lsn_db.LsnPort). + filter_by(lsn_port_id=self.lsn_port_id)) + self.assertRaises(orm.exc.NoResultFound, q.one) diff --git a/neutron/tests/unit/nicira/test_lsn_lib.py b/neutron/tests/unit/nicira/test_lsn_lib.py index 86daa39aa24..855a25533a9 100644 --- a/neutron/tests/unit/nicira/test_lsn_lib.py +++ b/neutron/tests/unit/nicira/test_lsn_lib.py @@ -1,5 +1,3 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 VMware, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -199,6 +197,30 @@ class LSNTestCase(base.BaseTestCase): lsnlib._lsn_port_get, self.cluster, "lsn_id", None) + def test_lsn_port_info_get(self): + self.mock_request.return_value = { + "tags": [ + {"scope": "n_mac_address", "tag": "fa:16:3e:27:fd:a0"}, + {"scope": "n_subnet_id", "tag": "foo_subnet_id"}, + ], + "mac_address": "aa:bb:cc:dd:ee:ff", + "ip_address": "0.0.0.0/0", + "uuid": "foo_lsn_port_id" + } + result = lsnlib.lsn_port_info_get( + self.cluster, 'foo_lsn_id', 'foo_lsn_port_id') + self.mock_request.assert_called_once_with( + 'GET', '/ws.v1/lservices-node/foo_lsn_id/lport/foo_lsn_port_id', + cluster=self.cluster) + self.assertIn('subnet_id', result) + self.assertIn('mac_address', result) + + def test_lsn_port_info_get_raise_not_found(self): + self.mock_request.side_effect = exceptions.NotFound + self.assertRaises(exceptions.NotFound, + lsnlib.lsn_port_info_get, + self.cluster, mock.ANY, mock.ANY) + def test_lsn_port_plug_network(self): lsn_id = "foo_lsn_id" lsn_port_id = "foo_lsn_port_id" diff --git a/setup.cfg b/setup.cfg index 87d08e96945..d3060331e0e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -92,6 +92,7 @@ console_scripts = neutron-nec-agent = neutron.plugins.nec.agent.nec_neutron_agent:main neutron-netns-cleanup = neutron.agent.netns_cleanup_util:main neutron-ns-metadata-proxy = neutron.agent.metadata.namespace_proxy:main + neutron-nsx-manage = neutron.plugins.nicira.shell:main neutron-openvswitch-agent = neutron.plugins.openvswitch.agent.ovs_neutron_agent:main neutron-ovs-cleanup = neutron.agent.ovs_cleanup_util:main neutron-ryu-agent = neutron.plugins.ryu.agent.ryu_neutron_agent:main