# The default network transport type to use (stt, gre, bridge, ipsec_gre, or ipsec_stt)
# default_transport_type = stt
+
+# Specifies in which mode the plugin needs to operate in order to provide DHCP and
+# metadata proxy services to tenant instances. If 'agent' is chosen (default)
+# the NVP 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),
+# the plugin will use NVP 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 NVP 3.2 or below.
+# agent_mode = agent
from sqlalchemy.orm import exc as sa_exc
import webob.exc
-from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import base
from neutron.common import constants
from neutron.common import exceptions as q_exc
-from neutron.common import rpc as q_rpc
-from neutron.common import topics
from neutron.common import utils
from neutron import context as q_context
-from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import api as db
from neutron.db import db_base_plugin_v2
-from neutron.db import dhcp_rpc_base
from neutron.db import extraroute_db
from neutron.db import l3_db
from neutron.db import l3_gwmode_db
from neutron.extensions import providernet as pnet
from neutron.extensions import securitygroup as ext_sg
from neutron.openstack.common import excutils
-from neutron.openstack.common import importutils
-from neutron.openstack.common import rpc
-from neutron.plugins.nicira.common import config # noqa
+from neutron.plugins.nicira.common import config
from neutron.plugins.nicira.common import exceptions as nvp_exc
-from neutron.plugins.nicira.common import metadata_access as nvp_meta
from neutron.plugins.nicira.common import securitygroups as nvp_sec
from neutron.plugins.nicira.dbexts import distributedrouter as dist_rtr
from neutron.plugins.nicira.dbexts import maclearning as mac_db
from neutron.plugins.nicira.dbexts import nicira_db
from neutron.plugins.nicira.dbexts import nicira_networkgw_db as networkgw_db
from neutron.plugins.nicira.dbexts import nicira_qos_db as qos_db
+from neutron.plugins.nicira import dhcpmeta_modes
from neutron.plugins.nicira.extensions import maclearning as mac_ext
from neutron.plugins.nicira.extensions import nvp_networkgw as networkgw
from neutron.plugins.nicira.extensions import nvp_qos as ext_qos
return cluster
-class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
-
- # Set RPC API version to 1.0 by default.
- RPC_API_VERSION = '1.1'
-
- def create_rpc_dispatcher(self):
- '''Get the rpc dispatcher for this manager.
-
- If a manager would like to set an rpc API version, or support more than
- one class as the target of rpc messages, override this method.
- '''
- return q_rpc.PluginRpcDispatcher([self,
- agents_db.AgentExtRpcCallback()])
-
-
-class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
+class NvpPluginV2(agentschedulers_db.DhcpAgentSchedulerDbMixin,
+ db_base_plugin_v2.NeutronDbPluginV2,
+ dhcpmeta_modes.DhcpMetadataAccess,
+ dist_rtr.DistributedRouter_mixin,
extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
- dist_rtr.DistributedRouter_mixin,
- portbindings_db.PortBindingMixin,
- portsecurity_db.PortSecurityDbMixin,
- securitygroups_db.SecurityGroupDbMixin,
mac_db.MacLearningDbMixin,
networkgw_db.NetworkGatewayMixin,
- qos_db.NVPQoSDbMixin,
nvp_sec.NVPSecurityGroups,
- nvp_meta.NvpMetadataAccess,
- agentschedulers_db.DhcpAgentSchedulerDbMixin):
+ portbindings_db.PortBindingMixin,
+ portsecurity_db.PortSecurityDbMixin,
+ qos_db.NVPQoSDbMixin,
+ securitygroups_db.SecurityGroupDbMixin):
"""L2 Virtual network plugin.
NvpPluginV2 is a Neutron plugin that provides L2 Virtual Network
'security-group' in self.supported_extension_aliases}}
db.configure_db()
- # Extend the fault map
self._extend_fault_map()
- # Set up RPC interface for DHCP agent
- self.setup_rpc()
- self.network_scheduler = importutils.import_object(
- cfg.CONF.network_scheduler_driver
- )
+ self.setup_dhcpmeta_access()
# Set this flag to false as the default gateway has not
# been yet updated from the config file
self._is_default_net_gw_in_sync = False
"logical network %s"), network.id)
raise nvp_exc.NvpNoMorePortsException(network=network.id)
- def setup_rpc(self):
- # RPC support for dhcp
- self.topic = topics.PLUGIN
- self.conn = rpc.create_connection(new=True)
- self.dispatcher = NVPRpcCallbacks().create_rpc_dispatcher()
- self.conn.create_consumer(self.topic, self.dispatcher,
- fanout=False)
- self.agent_notifiers[constants.AGENT_TYPE_DHCP] = (
- dhcp_rpc_agent_api.DhcpAgentNotifyAPI())
- # Consume from all consumers in a thread
- self.conn.consume_in_thread()
-
def _convert_to_nvp_transport_zones(self, cluster, network=None,
bindings=None):
nvp_transport_zones_config = []
self._extend_network_dict_provider(context, new_net,
provider_type,
net_bindings)
- self.schedule_network(context, new_net)
+ self.handle_network_dhcp_access(context, new_net,
+ action='create_network')
return new_net
def delete_network(self, context, id):
context.tenant_id)
except q_exc.NotFound:
LOG.warning(_("Did not found lswitch %s in NVP"), id)
+ self.handle_network_dhcp_access(context, id, action='delete_network')
def get_network(self, context, id, fields=None):
with context.session.begin(subtransactions=True):
# ATTR_NOT_SPECIFIED is for the case where a port is created on a
# shared network that is not owned by the tenant.
port_data = port['port']
- notify_dhcp_agent = False
with context.session.begin(subtransactions=True):
# First we allocate port in neutron database
neutron_db = super(NvpPluginV2, self).create_port(context, port)
neutron_port_id = neutron_db['id']
# Update fields obtained from neutron db (eg: MAC address)
port["port"].update(neutron_db)
- # metadata_dhcp_host_route
- if (cfg.CONF.NVP.metadata_mode == "dhcp_host_route" and
- neutron_db.get('device_owner') == constants.DEVICE_OWNER_DHCP):
- if (neutron_db.get('fixed_ips') and
- len(neutron_db['fixed_ips'])):
- notify_dhcp_agent = self._ensure_metadata_host_route(
- context, neutron_db['fixed_ips'][0])
+ self.handle_port_metadata_access(context, neutron_db)
# port security extension checks
(port_security, has_ip) = self._determine_port_security_and_has_ip(
context, port_data)
with context.session.begin(subtransactions=True):
self._delete_port(context, neutron_port_id)
- # Port has been created both on DB and NVP - proceed with
- # scheduling network and notifying DHCP agent
- net = self.get_network(context, port_data['network_id'])
- self.schedule_network(context, net)
- if notify_dhcp_agent:
- self._send_subnet_update_end(
- context, neutron_db['fixed_ips'][0]['subnet_id'])
+ self.handle_port_dhcp_access(context, port_data, action='create_port')
return port_data
def update_port(self, context, id, port):
port_delete_func(context, neutron_db_port)
self.disassociate_floatingips(context, id)
- notify_dhcp_agent = False
with context.session.begin(subtransactions=True):
queue = self._get_port_queue_bindings(context, {'port_id': [id]})
# metadata_dhcp_host_route
- port_device_owner = neutron_db_port['device_owner']
- if (cfg.CONF.NVP.metadata_mode == "dhcp_host_route" and
- port_device_owner == constants.DEVICE_OWNER_DHCP):
- notify_dhcp_agent = self._ensure_metadata_host_route(
- context, neutron_db_port['fixed_ips'][0],
- is_delete=True)
+ self.handle_port_metadata_access(
+ context, neutron_db_port, is_delete=True)
super(NvpPluginV2, self).delete_port(context, id)
# Delete qos queue if possible
if queue:
self.delete_qos_queue(context, queue[0]['queue_id'], False)
- if notify_dhcp_agent:
- self._send_subnet_update_end(
- context, neutron_db_port['fixed_ips'][0]['subnet_id'])
+ self.handle_port_dhcp_access(
+ context, neutron_db_port, action='delete_port')
def get_port(self, context, id, fields=None):
with context.session.begin(subtransactions=True):
nvplib.update_explicit_routes_lrouter(
self.cluster, router_id, previous_routes)
- def delete_router(self, context, id):
+ def delete_router(self, context, router_id):
with context.session.begin(subtransactions=True):
# Ensure metadata access network is detached and destroyed
# This will also destroy relevant objects on NVP platform.
# NOTE(salvatore-orlando): A failure in this operation will
# cause the router delete operation to fail too.
- self._handle_metadata_access_network(context, id, do_create=False)
- super(NvpPluginV2, self).delete_router(context, id)
+ self.handle_router_metadata_access(
+ context, router_id, do_create=False)
+ super(NvpPluginV2, self).delete_router(context, router_id)
# If removal is successful in Neutron it should be so on
# the NVP platform too - otherwise the transaction should
# be automatically aborted
# allow an extra field for storing the cluster information
# together with the resource
try:
- nvplib.delete_lrouter(self.cluster, id)
+ nvplib.delete_lrouter(self.cluster, router_id)
except q_exc.NotFound:
LOG.warning(_("Logical router '%s' not found "
- "on NVP Platform") % id)
+ "on NVP Platform") % router_id)
except NvpApiClient.NvpApiException:
raise nvp_exc.NvpPluginException(
- err_msg=(_("Unable to delete logical router"
- "on NVP Platform")))
+ err_msg=(_("Unable to delete logical router '%s'"
+ "on NVP Platform") % router_id))
def get_router(self, context, id, fields=None):
router = self._get_router(context, id)
# Ensure the NVP logical router has a connection to a 'metadata access'
# network (with a proxy listening on its DHCP port), by creating it
# if needed.
- self._handle_metadata_access_network(context, router_id)
+ self.handle_router_metadata_access(context, router_id)
LOG.debug(_("Add_router_interface completed for subnet:%(subnet_id)s "
"and router:%(router_id)s"),
{'subnet_id': subnet_id, 'router_id': router_id})
# Ensure the connection to the 'metadata access network'
# is removed (with the network) if this the last subnet
# on the router
- self._handle_metadata_access_network(context, router_id)
+ self.handle_router_metadata_access(context, router_id)
try:
if not subnet:
subnet = self._get_subnet(context, subnet_id)
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'
+
+
+class MetadataModes:
+ DIRECT = 'access_network'
+ INDIRECT = 'dhcp_host_route'
+
+
nvp_opts = [
cfg.IntOpt('max_lp_per_bridged_ls', default=64,
help=_("Maximum number of ports of a logical switch on a "
cfg.IntOpt('nvp_gen_timeout', default=-1,
help=_("Number of seconds a generation id should be valid for "
"(default -1 meaning do not time out)")),
- cfg.StrOpt('metadata_mode', default='access_network',
+ cfg.StrOpt('metadata_mode', default=MetadataModes.DIRECT,
help=_("If set to access_network this enables a dedicated "
"connection to the metadata proxy for metadata server "
"access via Neutron router. If set to dhcp_host_route "
cfg.StrOpt('default_transport_type', default='stt',
help=_("The default network tranport type to use (stt, gre, "
"bridge, ipsec_gre, or ipsec_stt)")),
+ cfg.StrOpt('agent_mode', default=AgentModes.AGENT,
+ help=_("The mode used to implement DHCP/metadata services.")),
]
connection_opts = [
+++ /dev/null
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2013 Nicira, 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.
-#
-# @author: Salvatore Orlando, VMware
-
-from eventlet import greenthread
-import netaddr
-from oslo.config import cfg
-
-from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
-from neutron.api.v2 import attributes
-from neutron.common import constants
-from neutron.common import exceptions as ntn_exc
-from neutron.db import db_base_plugin_v2
-from neutron.db import l3_db
-from neutron.db import models_v2
-from neutron.openstack.common import log as logging
-from neutron.plugins.nicira.common import exceptions as nvp_exc
-from neutron.plugins.nicira import NvpApiClient
-
-
-LOG = logging.getLogger(__name__)
-
-METADATA_DEFAULT_PREFIX = 30
-METADATA_SUBNET_CIDR = '169.254.169.252/%d' % METADATA_DEFAULT_PREFIX
-METADATA_GATEWAY_IP = '169.254.169.253'
-METADATA_DHCP_ROUTE = '169.254.169.254/32'
-
-
-class NvpMetadataAccess(object):
-
- def _find_metadata_port(self, context, ports):
- for port in ports:
- for fixed_ip in port['fixed_ips']:
- cidr = netaddr.IPNetwork(
- self.get_subnet(context, fixed_ip['subnet_id'])['cidr'])
- if cidr in netaddr.IPNetwork(METADATA_SUBNET_CIDR):
- return port
-
- def _create_metadata_access_network(self, context, router_id):
- # Add network
- # Network name is likely to be truncated on NVP
- net_data = {'name': 'meta-%s' % router_id,
- 'tenant_id': '', # intentionally not set
- 'admin_state_up': True,
- 'port_security_enabled': False,
- 'shared': False,
- 'status': constants.NET_STATUS_ACTIVE}
- meta_net = self.create_network(context,
- {'network': net_data})
- greenthread.sleep(0) # yield
- # From this point on there will be resources to garbage-collect
- # in case of failures
- meta_sub = None
- try:
- # Add subnet
- subnet_data = {'network_id': meta_net['id'],
- 'tenant_id': '', # intentionally not set
- 'name': 'meta-%s' % router_id,
- 'ip_version': 4,
- 'shared': False,
- 'cidr': METADATA_SUBNET_CIDR,
- 'enable_dhcp': True,
- # Ensure default allocation pool is generated
- 'allocation_pools': attributes.ATTR_NOT_SPECIFIED,
- 'gateway_ip': METADATA_GATEWAY_IP,
- 'dns_nameservers': [],
- 'host_routes': []}
- meta_sub = self.create_subnet(context,
- {'subnet': subnet_data})
- greenthread.sleep(0) # yield
- self.add_router_interface(context, router_id,
- {'subnet_id': meta_sub['id']})
- greenthread.sleep(0) # yield
- except (ntn_exc.NeutronException,
- nvp_exc.NvpPluginException,
- NvpApiClient.NvpApiException):
- # It is not necessary to explicitly delete the subnet
- # as it will be removed with the network
- self.delete_network(context, meta_net['id'])
-
- if cfg.CONF.dhcp_agent_notification:
- # We need to send a notification to the dhcp agent in
- # order to start the metadata agent proxy
- dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
- dhcp_notifier.notify(context, {'network': meta_net},
- 'network.create.end')
-
- def _destroy_metadata_access_network(self, context, router_id, ports):
- if not ports:
- return
- meta_port = self._find_metadata_port(context, ports)
- if not meta_port:
- return
- meta_net_id = meta_port['network_id']
- meta_sub_id = meta_port['fixed_ips'][0]['subnet_id']
- self.remove_router_interface(
- context, router_id, {'port_id': meta_port['id']})
- greenthread.sleep(0) # yield
- try:
- # Remove network (this will remove the subnet too)
- self.delete_network(context, meta_net_id)
- greenthread.sleep(0) # yield
- except (ntn_exc.NeutronException, nvp_exc.NvpPluginException,
- NvpApiClient.NvpApiException):
- # must re-add the router interface
- self.add_router_interface(context, router_id,
- {'subnet_id': meta_sub_id})
-
- if cfg.CONF.dhcp_agent_notification:
- # We need to send a notification to the dhcp agent in
- # order to stop the metadata agent proxy
- dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
- dhcp_notifier.notify(context,
- {'network': {'id': meta_net_id}},
- 'network.delete.end')
-
- def _handle_metadata_access_network(self, context, router_id,
- do_create=True):
- if cfg.CONF.NVP.metadata_mode != "access_network":
- LOG.debug(_("Metadata access network is disabled"))
- return
- if not cfg.CONF.allow_overlapping_ips:
- LOG.warn(_("Overlapping IPs must be enabled in order to setup "
- "the metadata access network"))
- return
- # As we'll use a different device_owner for metadata interface
- # this query will return only 'real' router interfaces
- ctx_elevated = context.elevated()
- device_filter = {'device_id': [router_id],
- 'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
- # Retrieve ports calling database plugin
- ports = db_base_plugin_v2.NeutronDbPluginV2.get_ports(
- self, context, filters=device_filter)
- try:
- if ports:
- if (do_create and
- not self._find_metadata_port(ctx_elevated, ports)):
- self._create_metadata_access_network(ctx_elevated,
- router_id)
- elif len(ports) == 1:
- # The only port left might be the metadata port
- self._destroy_metadata_access_network(ctx_elevated,
- router_id,
- ports)
- else:
- LOG.debug(_("No router interface found for router '%s'. "
- "No metadata access network should be "
- "created or destroyed"), router_id)
- # TODO(salvatore-orlando): A better exception handling in the
- # NVP plugin would allow us to improve error handling here
- except (ntn_exc.NeutronException, nvp_exc.NvpPluginException,
- NvpApiClient.NvpApiException):
- # Any exception here should be regarded as non-fatal
- LOG.exception(_("An error occurred while operating on the "
- "metadata access network for router:'%s'"),
- router_id)
-
- def _ensure_metadata_host_route(self, context, fixed_ip_data,
- is_delete=False):
- subnet = self._get_subnet(context, fixed_ip_data['subnet_id'])
- # If subnet does not have a gateway do not create metadata route. This
- # is done via the enable_isolated_metadata option if desired.
- if not subnet.get('gateway_ip'):
- return
- metadata_routes = [r for r in subnet.routes
- if r['destination'] == METADATA_DHCP_ROUTE]
-
- if metadata_routes:
- # We should have only a single metadata route at any time
- # because the route logic forbids two routes with the same
- # destination. Update next hop with the provided IP address
- if not is_delete:
- metadata_routes[0].nexthop = fixed_ip_data['ip_address']
- else:
- context.session.delete(metadata_routes[0])
- else:
- # add the metadata route
- route = models_v2.SubnetRoute(subnet_id=subnet.id,
- destination=METADATA_DHCP_ROUTE,
- nexthop=fixed_ip_data['ip_address'])
- context.session.add(route)
- return cfg.CONF.dhcp_agent_notification
-
- def _send_subnet_update_end(self, context, subnet_id):
- updated_subnet = self.get_subnet(context, subnet_id)
- dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
- dhcp_notifier.notify(context,
- {'subnet': updated_subnet},
- 'subnet.update.end')
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 VMware, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 VMware, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from eventlet import greenthread
+import netaddr
+from oslo.config import cfg
+
+from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
+from neutron.api.v2 import attributes
+from neutron.common import constants as const
+from neutron.common import exceptions as ntn_exc
+from neutron.common import rpc as n_rpc
+from neutron.db import agents_db
+from neutron.db import db_base_plugin_v2
+from neutron.db import dhcp_rpc_base
+from neutron.db import l3_db
+from neutron.db import models_v2
+from neutron.openstack.common import log as logging
+from neutron.plugins.nicira.common import config
+from neutron.plugins.nicira.common import exceptions as nvp_exc
+from neutron.plugins.nicira import NvpApiClient
+
+LOG = logging.getLogger(__name__)
+
+METADATA_DEFAULT_PREFIX = 30
+METADATA_SUBNET_CIDR = '169.254.169.252/%d' % METADATA_DEFAULT_PREFIX
+METADATA_GATEWAY_IP = '169.254.169.253'
+METADATA_DHCP_ROUTE = '169.254.169.254/32'
+
+
+class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
+
+ RPC_API_VERSION = '1.1'
+
+ def create_rpc_dispatcher(self):
+ '''Get the rpc dispatcher for this manager.
+
+ If a manager would like to set an rpc API version, or support more than
+ one class as the target of rpc messages, override this method.
+ '''
+ return n_rpc.PluginRpcDispatcher([self,
+ agents_db.AgentExtRpcCallback()])
+
+
+def handle_network_dhcp_access(plugin, context, network, action):
+ # TODO(armando-migliaccio): revise the implementation of this
+ # method in the context bug #1212555; a potential fix might be
+ # as simple as a 'pass', but keeping the hook might be useful
+ # in other agent modes.
+ if action == 'create_network':
+ plugin.schedule_network(context, network)
+
+
+def handle_port_dhcp_access(plugin, context, port_data, action):
+ if action == 'create_port':
+ net = plugin.get_network(context, port_data['network_id'])
+ plugin.schedule_network(context, net)
+
+ active_port = (cfg.CONF.NVP.metadata_mode == config.MetadataModes.INDIRECT
+ and port_data.get('device_owner') == const.DEVICE_OWNER_DHCP
+ and port_data.get('fixed_ips', []))
+ if active_port:
+ subnet_id = port_data['fixed_ips'][0]['subnet_id']
+ subnet = plugin.get_subnet(context, subnet_id)
+ if (cfg.CONF.dhcp_agent_notification and subnet.get('gateway_ip')
+ or action == 'delete_port'):
+ dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
+ dhcp_notifier.notify(
+ context, {'subnet': subnet}, 'subnet.update.end')
+
+
+def handle_port_metadata_access(context, port, is_delete=False):
+ if (cfg.CONF.NVP.metadata_mode == config.MetadataModes.INDIRECT and
+ port.get('device_owner') == const.DEVICE_OWNER_DHCP):
+ if port.get('fixed_ips', []) or is_delete:
+ fixed_ip = port['fixed_ips'][0]
+ query = context.session.query(models_v2.Subnet)
+ subnet = query.filter(
+ models_v2.Subnet.id == fixed_ip['subnet_id']).one()
+ # If subnet does not have a gateway do not create metadata
+ # route. This is done via the enable_isolated_metadata
+ # option if desired.
+ if not subnet.get('gateway_ip'):
+ return
+ metadata_routes = [r for r in subnet.routes
+ if r['destination'] == METADATA_DHCP_ROUTE]
+ if metadata_routes:
+ # We should have only a single metadata route at any time
+ # because the route logic forbids two routes with the same
+ # destination. Update next hop with the provided IP address
+ if not is_delete:
+ metadata_routes[0].nexthop = fixed_ip['ip_address']
+ else:
+ context.session.delete(metadata_routes[0])
+ else:
+ # add the metadata route
+ route = models_v2.SubnetRoute(
+ subnet_id=subnet.id,
+ destination=METADATA_DHCP_ROUTE,
+ nexthop=fixed_ip['ip_address'])
+ context.session.add(route)
+
+
+def handle_router_metadata_access(plugin, context, router_id, do_create=True):
+ if cfg.CONF.NVP.metadata_mode != config.MetadataModes.DIRECT:
+ LOG.debug(_("Metadata access network is disabled"))
+ return
+ if not cfg.CONF.allow_overlapping_ips:
+ LOG.warn(_("Overlapping IPs must be enabled in order to setup "
+ "the metadata access network"))
+ return
+ # As we'll use a different device_owner for metadata interface
+ # this query will return only 'real' router interfaces
+ ctx_elevated = context.elevated()
+ device_filter = {'device_id': [router_id],
+ 'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
+ # Retrieve ports calling database plugin
+ ports = db_base_plugin_v2.NeutronDbPluginV2.get_ports(
+ plugin, context, filters=device_filter)
+ try:
+ if ports:
+ if (do_create and
+ not _find_metadata_port(plugin, ctx_elevated, ports)):
+ _create_metadata_access_network(
+ plugin, ctx_elevated, router_id)
+ elif len(ports) == 1:
+ # The only port left might be the metadata port
+ _destroy_metadata_access_network(
+ plugin, ctx_elevated, router_id, ports)
+ else:
+ LOG.debug(_("No router interface found for router '%s'. "
+ "No metadata access network should be "
+ "created or destroyed"), router_id)
+ # TODO(salvatore-orlando): A better exception handling in the
+ # NVP plugin would allow us to improve error handling here
+ except (ntn_exc.NeutronException, nvp_exc.NvpPluginException,
+ NvpApiClient.NvpApiException):
+ # Any exception here should be regarded as non-fatal
+ LOG.exception(_("An error occurred while operating on the "
+ "metadata access network for router:'%s'"),
+ router_id)
+
+
+def _find_metadata_port(plugin, context, ports):
+ for port in ports:
+ for fixed_ip in port['fixed_ips']:
+ cidr = netaddr.IPNetwork(
+ plugin.get_subnet(context, fixed_ip['subnet_id'])['cidr'])
+ if cidr in netaddr.IPNetwork(METADATA_SUBNET_CIDR):
+ return port
+
+
+def _create_metadata_access_network(plugin, context, router_id):
+ # Add network
+ # Network name is likely to be truncated on NVP
+ net_data = {'name': 'meta-%s' % router_id,
+ 'tenant_id': '', # intentionally not set
+ 'admin_state_up': True,
+ 'port_security_enabled': False,
+ 'shared': False,
+ 'status': const.NET_STATUS_ACTIVE}
+ meta_net = plugin.create_network(context,
+ {'network': net_data})
+ greenthread.sleep(0) # yield
+ # From this point on there will be resources to garbage-collect
+ # in case of failures
+ meta_sub = None
+ try:
+ # Add subnet
+ subnet_data = {'network_id': meta_net['id'],
+ 'tenant_id': '', # intentionally not set
+ 'name': 'meta-%s' % router_id,
+ 'ip_version': 4,
+ 'shared': False,
+ 'cidr': METADATA_SUBNET_CIDR,
+ 'enable_dhcp': True,
+ # Ensure default allocation pool is generated
+ 'allocation_pools': attributes.ATTR_NOT_SPECIFIED,
+ 'gateway_ip': METADATA_GATEWAY_IP,
+ 'dns_nameservers': [],
+ 'host_routes': []}
+ meta_sub = plugin.create_subnet(context,
+ {'subnet': subnet_data})
+ greenthread.sleep(0) # yield
+ plugin.add_router_interface(context, router_id,
+ {'subnet_id': meta_sub['id']})
+ greenthread.sleep(0) # yield
+ except (ntn_exc.NeutronException,
+ nvp_exc.NvpPluginException,
+ NvpApiClient.NvpApiException):
+ # It is not necessary to explicitly delete the subnet
+ # as it will be removed with the network
+ plugin.delete_network(context, meta_net['id'])
+
+ if cfg.CONF.dhcp_agent_notification:
+ # We need to send a notification to the dhcp agent in
+ # order to start the metadata agent proxy
+ dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
+ dhcp_notifier.notify(context, {'network': meta_net},
+ 'network.create.end')
+
+
+def _destroy_metadata_access_network(plugin, context, router_id, ports):
+ if not ports:
+ return
+ meta_port = _find_metadata_port(plugin, context, ports)
+ if not meta_port:
+ return
+ meta_net_id = meta_port['network_id']
+ meta_sub_id = meta_port['fixed_ips'][0]['subnet_id']
+ plugin.remove_router_interface(
+ context, router_id, {'port_id': meta_port['id']})
+ greenthread.sleep(0) # yield
+ try:
+ # Remove network (this will remove the subnet too)
+ plugin.delete_network(context, meta_net_id)
+ greenthread.sleep(0) # yield
+ except (ntn_exc.NeutronException, nvp_exc.NvpPluginException,
+ NvpApiClient.NvpApiException):
+ # must re-add the router interface
+ plugin.add_router_interface(context, router_id,
+ {'subnet_id': meta_sub_id})
+
+ if cfg.CONF.dhcp_agent_notification:
+ # We need to send a notification to the dhcp agent in
+ # order to stop the metadata agent proxy
+ dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
+ dhcp_notifier.notify(context,
+ {'network': {'id': meta_net_id}},
+ 'network.delete.end')
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 VMware, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from oslo.config import cfg
+
+from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
+from neutron.common import constants as const
+from neutron.common import topics
+from neutron.openstack.common import importutils
+from neutron.openstack.common import rpc
+from neutron.plugins.nicira.common import config
+from neutron.plugins.nicira.dhcp_meta import rpc as nvp_rpc
+
+
+class DhcpMetadataAccess(object):
+
+ def setup_dhcpmeta_access(self):
+ """Initialize support for DHCP and Metadata services."""
+ if cfg.CONF.NVP.agent_mode == config.AgentModes.AGENT:
+ self._setup_rpc_dhcp_metadata()
+ self.handle_network_dhcp_access_delegate = (
+ nvp_rpc.handle_network_dhcp_access
+ )
+ self.handle_port_dhcp_access_delegate = (
+ nvp_rpc.handle_port_dhcp_access
+ )
+ self.handle_port_metadata_access_delegate = (
+ nvp_rpc.handle_port_metadata_access
+ )
+ self.handle_metadata_access_delegate = (
+ nvp_rpc.handle_router_metadata_access
+ )
+ elif cfg.CONF.NVP.agent_mode == config.AgentModes.AGENTLESS:
+ # 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")
+ # TODO(armando-migliaccio): agentless support is not yet complete
+ # so it's better to raise an exception for now, in case some admin
+ # decides to jump the gun
+ raise NotImplementedError()
+
+ def _setup_rpc_dhcp_metadata(self):
+ 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.agent_notifiers[const.AGENT_TYPE_DHCP] = (
+ dhcp_rpc_agent_api.DhcpAgentNotifyAPI())
+ self.conn.consume_in_thread()
+ self.network_scheduler = importutils.import_object(
+ cfg.CONF.network_scheduler_driver
+ )
+
+ def handle_network_dhcp_access(self, context, network, action):
+ self.handle_network_dhcp_access_delegate(self, context,
+ network, action)
+
+ def handle_port_dhcp_access(self, context, port_data, action):
+ self.handle_port_dhcp_access_delegate(self, context, port_data, action)
+
+ def handle_port_metadata_access(self, context, port, is_delete=False):
+ self.handle_port_metadata_access_delegate(context, port, is_delete)
+
+ def handle_router_metadata_access(self, context,
+ router_id, do_create=True):
+ self.handle_metadata_access_delegate(self, context,
+ router_id, do_create)
--- /dev/null
+[DEFAULT]
+default_tz_uuid = fake_tz_uuid
+nova_zone_id = whatever
+nvp_cluster_uuid = fake_cluster_uuid
+nvp_controllers = fake_1, fake_2
+nvp_user = foo
+nvp_password = bar
+default_l3_gw_service_uuid = whatever
+default_l2_gw_service_uuid = whatever
+default_interface_name = whatever
+req_timeout = 14
+http_timeout = 13
+redirects = 12
+retries = 11
+
+[NVP]
+agent_mode = agentless
NVP_INI_PATH = get_fake_conf('nvp.ini.basic.test')
NVP_INI_FULL_PATH = get_fake_conf('nvp.ini.full.test')
NVP_INI_DEPR_PATH = get_fake_conf('nvp.ini.grizzly.test')
+NVP_INI_AGENTLESS_PATH = get_fake_conf('nvp.ini.agentless.test')
class NVPClusterTest(testtools.TestCase):
NeutronManager().get_plugin()
self.assertIn('extensions', cfg.CONF.api_extensions_path)
+ def test_agentless_extensions(self):
+ self.skipTest('Enable once agentless support is added')
+ q_config.parse(['--config-file', NVP_BASE_CONF_PATH,
+ '--config-file', NVP_INI_AGENTLESS_PATH])
+ cfg.CONF.set_override('core_plugin', PLUGIN_NAME)
+ self.assertEqual(config.AgentModes.AGENTLESS,
+ cfg.CONF.NVP.agent_mode)
+ plugin = NeutronManager().get_plugin()
+ self.assertNotIn('agent',
+ plugin.supported_extension_aliases)
+ self.assertNotIn('dhcp_agent_scheduler',
+ plugin.supported_extension_aliases)
+
+ def test_agent_extensions(self):
+ q_config.parse(['--config-file', NVP_BASE_CONF_PATH,
+ '--config-file', NVP_INI_FULL_PATH])
+ cfg.CONF.set_override('core_plugin', PLUGIN_NAME)
+ self.assertEqual(config.AgentModes.AGENT,
+ cfg.CONF.NVP.agent_mode)
+ plugin = NeutronManager().get_plugin()
+ self.assertIn('agent',
+ plugin.supported_extension_aliases)
+ self.assertIn('dhcp_agent_scheduler',
+ plugin.supported_extension_aliases)
+
class OldConfigurationTest(testtools.TestCase):