# To be specified for providing a predefined gateway tenant for connecting their networks.
# default_l2_gw_service_uuid =
+# (Optional) UUID for the default service cluster. A service cluster is introduced to
+# represent a group of gateways and it is needed in order to use Logical Services like
+# dhcp and metadata in the logical space. NOTE: If agent_mode is set to 'agentless' this
+# config parameter *MUST BE* set to a valid pre-existent service cluster uuid.
+# default_service_cluster_uuid =
+
# Name of the default interface name to be used on network-gateway. This value
# will be used for any device associated with a network gateway for which an
# interface name was not specified
# number of network gateways allowed per tenant, -1 means unlimited
# quota_network_gateway = 5
-
[nvp]
# Maximum number of ports for each bridged logical switch
# The recommended value for this parameter varies with NVP version
# (Optional) Asynchronous task status check interval
# default is 2000 (millisecond)
# task_status_check_interval = 2000
+
+[nvp_dhcp]
+# (Optional) Comma separated list of additional dns servers. Default is an empty list
+# extra_domain_name_servers =
+
+# Domain to use for building the hostnames
+# domain_name = openstacklocal
+
+# Default DHCP lease time
+# default_lease_time = 43200
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.v2 import attributes
from neutron.api.v2 import resource as wsgi_resource
+from neutron.common import constants as const
from neutron.common import exceptions
from neutron.openstack.common import log as logging
from neutron.openstack.common.notifier import api as notifier_api
self._policy_attrs = [name for (name, info) in self._attr_info.items()
if info.get('required_by_policy')]
self._publisher_id = notifier_api.publisher_id('network')
- self._dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
+ # use plugin's dhcp notifier, if this is already instantiated
+ agent_notifiers = getattr(plugin, 'agent_notifiers', {})
+ self._dhcp_agent_notifier = (
+ agent_notifiers.get(const.AGENT_TYPE_DHCP) or
+ dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
+ )
self._member_actions = member_actions
self._primary_key = self._get_primary_key()
if self._allow_pagination and self._native_pagination:
# TODO(salv-orlando): Replace These dicts with
# collections.defaultdict for better handling of default values
# Routines for managing logical ports in NVP
+ self.port_special_owners = [l3_db.DEVICE_OWNER_ROUTER_GW,
+ l3_db.DEVICE_OWNER_ROUTER_INTF]
self._port_drivers = {
'create': {l3_db.DEVICE_OWNER_ROUTER_GW:
self._nvp_create_ext_gw_port,
True)
nicira_db.add_neutron_nvp_port_mapping(
context.session, port_data['id'], lport['uuid'])
- if (not port_data['device_owner'] in
- (l3_db.DEVICE_OWNER_ROUTER_GW,
- l3_db.DEVICE_OWNER_ROUTER_INTF)):
+ if port_data['device_owner'] not in self.port_special_owners:
nvplib.plug_interface(self.cluster, selected_lswitch['uuid'],
lport['uuid'], "VifAttachment",
port_data['id'])
cfg.StrOpt('default_l2_gw_service_uuid',
help=_("Unique identifier of the NVP L2 Gateway service "
"which will be used by default for network gateways")),
+ cfg.StrOpt('default_service_cluster_uuid',
+ help=_("Unique identifier of the Service Cluster which will "
+ "be used by logical services like dhcp and metadata")),
cfg.StrOpt('default_interface_name', default='breth0',
help=_("Name of the interface on a L2 Gateway transport node"
"which should be used by default when setting up a "
class NvpVcnsDriverException(NvpServicePluginException):
message = _("Error happened in NVP VCNS Driver: %(err_msg)s")
+
+
+class ServiceClusterUnavailable(NvpPluginException):
+ message = _("Service cluster: '%(cluster_id)s' is unavailable. Please, "
+ "check NVP setup and/or configuration")
+
+
+class PortConfigurationError(NvpPluginException):
+ message = _("An error occurred while connecting LSN %(lsn_id)s "
+ "and network %(net_id)s via port %(port_id)s")
+
+ def __init__(self, **kwargs):
+ super(PortConfigurationError, self).__init__(**kwargs)
+ self.port_id = kwargs.get('port_id')
+
+
+class LsnNotFound(q_exc.NotFound):
+ message = _('Unable to find LSN for %(entity)s %(entity_id)s')
+
+
+class LsnPortNotFound(q_exc.NotFound):
+ message = (_('Unable to find port for LSN %(lsn_id)s '
+ 'and %(entity)s %(entity_id)s'))
+
+
+class LsnConfigurationConflict(NvpPluginException):
+ message = _("Configuration conflict on Logical Service Node %(lsn_id)s")
# under the License.
from neutron.openstack.common import log
+from neutron.version import version_info
+
LOG = log.getLogger(__name__)
MAX_DISPLAY_NAME_LEN = 40
+NEUTRON_VERSION = version_info.release_string()
+
+
+def get_tags(**kwargs):
+ tags = ([dict(tag=value, scope=key)
+ for key, value in kwargs.iteritems()])
+ tags.append({"tag": NEUTRON_VERSION, "scope": "quantum"})
+ return tags
def check_and_truncate(display_name):
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 VMware, Inc.
+# All Rights Reserved
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+from oslo.config import cfg
+
+from neutron.api.v2 import attributes as attr
+from neutron.common import constants as const
+from neutron.common import exceptions as n_exc
+from neutron.db import db_base_plugin_v2
+from neutron.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__)
+
+
+dhcp_opts = [
+ cfg.ListOpt('extra_domain_name_servers',
+ default=[],
+ help=_('Comma separated list of additional '
+ 'domain name servers')),
+ cfg.StrOpt('domain_name',
+ default='openstacklocal',
+ help=_('Domain to use for building the hostnames')),
+ cfg.IntOpt('default_lease_time', default=43200,
+ help=_("Default DHCP lease time")),
+]
+
+
+def register_dhcp_opts(config):
+ config.CONF.register_opts(dhcp_opts, "NVP_DHCP")
+
+
+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)
+
+ 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_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.NVP_DHCP.domain_name,
+ "default_lease_time": cfg.CONF.NVP_DHCP.default_lease_time,
+ }
+ dns_servers = cfg.CONF.NVP_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_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 from 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)
+
+
+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'])
+
+ 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
+ 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 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
+ self.plugin.delete_port(context, ports[0]['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/NVP
+ # 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: nothing to do"))
+ 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(context, port, is_delete=False):
+ # TODO(armando-migliaccio)
+ LOG.info('%s port with data %s' % (is_delete, port))
+
+
+def handle_router_metadata_access(plugin, context, router_id, do_create=True):
+ # TODO(armando-migliaccio)
+ LOG.info('%s router %s' % (do_create, router_id))
# must re-add the router interface
plugin.add_router_interface(context, router_id,
{'subnet_id': meta_sub_id})
- # Tell to stop the metadata agent proxy
+ # Tell to stop the metadata agent proxy
_notify_rpc_agent(
context, {'network': {'id': meta_net_id}}, 'network.delete.end')
from neutron.common import constants as const
from neutron.common import topics
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
+LOG = logging.getLogger(__name__)
+
class DhcpMetadataAccess(object):
"""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
- )
+ mod = nvp_rpc
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()
+ self._setup_nvp_dhcp_metadata()
+ mod = nvp_svc
+ self.handle_network_dhcp_access_delegate = (
+ mod.handle_network_dhcp_access
+ )
+ self.handle_port_dhcp_access_delegate = (
+ mod.handle_port_dhcp_access
+ )
+ self.handle_port_metadata_access_delegate = (
+ mod.handle_port_metadata_access
+ )
+ self.handle_metadata_access_delegate = (
+ mod.handle_router_metadata_access
+ )
def _setup_rpc_dhcp_metadata(self):
self.topic = topics.PLUGIN
cfg.CONF.network_scheduler_driver
)
+ 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)
+ 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)
+ try:
+ error = None
+ nvp_svc.check_services_requirements(self.cluster)
+ except nvp_exc.NvpInvalidVersion:
+ error = _("Unable to run Neutron with config option '%s', as NVP "
+ "does not support it") % config.AgentModes.AGENTLESS
+ except nvp_exc.ServiceClusterUnavailable:
+ error = _("Unmet dependency for config option "
+ "'%s'") % config.AgentModes.AGENTLESS
+ if error:
+ LOG.exception(error)
+ raise nvp_exc.NvpPluginException(err_msg=error)
+
def handle_network_dhcp_access(self, context, network, action):
self.handle_network_dhcp_access_delegate(self, context,
network, action)
--- /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.
+
+import json
+
+from neutron.common import exceptions as exception
+from neutron.openstack.common import log
+from neutron.plugins.nicira.common import exceptions as nvp_exc
+from neutron.plugins.nicira.common import utils
+from neutron.plugins.nicira import NvpApiClient
+from neutron.plugins.nicira.nvplib import _build_uri_path
+from neutron.plugins.nicira.nvplib import do_request
+
+HTTP_GET = "GET"
+HTTP_POST = "POST"
+HTTP_DELETE = "DELETE"
+HTTP_PUT = "PUT"
+
+SERVICECLUSTER_RESOURCE = "service-cluster"
+LSERVICESNODE_RESOURCE = "lservices-node"
+LSERVICESNODEPORT_RESOURCE = "lport/%s" % LSERVICESNODE_RESOURCE
+
+LOG = log.getLogger(__name__)
+
+
+def service_cluster_exists(cluster, svc_cluster_id):
+ exists = False
+ try:
+ exists = (
+ svc_cluster_id and
+ do_request(HTTP_GET,
+ _build_uri_path(SERVICECLUSTER_RESOURCE,
+ resource_id=svc_cluster_id),
+ cluster=cluster) is not None)
+ except exception.NotFound:
+ pass
+ return exists
+
+
+def lsn_for_network_create(cluster, network_id):
+ lsn_obj = {
+ "service_cluster_uuid": cluster.default_service_cluster_uuid,
+ "tags": utils.get_tags(n_network_id=network_id)
+ }
+ return do_request(HTTP_POST,
+ _build_uri_path(LSERVICESNODE_RESOURCE),
+ json.dumps(lsn_obj),
+ cluster=cluster)["uuid"]
+
+
+def lsn_for_network_get(cluster, network_id):
+ filters = {"tag": network_id, "tag_scope": "n_network_id"}
+ results = do_request(HTTP_GET,
+ _build_uri_path(LSERVICESNODE_RESOURCE,
+ fields="uuid",
+ filters=filters),
+ cluster=cluster)['results']
+ if not results:
+ raise exception.NotFound()
+ elif len(results) == 1:
+ return results[0]['uuid']
+
+
+def lsn_delete(cluster, lsn_id):
+ do_request(HTTP_DELETE,
+ _build_uri_path(LSERVICESNODE_RESOURCE,
+ resource_id=lsn_id),
+ cluster=cluster)
+
+
+def lsn_port_create(cluster, lsn_id, port_data):
+ port_obj = {
+ "ip_address": port_data["ip_address"],
+ "mac_address": port_data["mac_address"],
+ "tags": utils.get_tags(n_mac_address=port_data["mac_address"],
+ n_subnet_id=port_data["subnet_id"]),
+ "type": "LogicalServicesNodePortConfig",
+ }
+ return do_request(HTTP_POST,
+ _build_uri_path(LSERVICESNODEPORT_RESOURCE,
+ parent_resource_id=lsn_id),
+ json.dumps(port_obj),
+ cluster=cluster)["uuid"]
+
+
+def lsn_port_delete(cluster, lsn_id, lsn_port_id):
+ return do_request(HTTP_DELETE,
+ _build_uri_path(LSERVICESNODEPORT_RESOURCE,
+ parent_resource_id=lsn_id,
+ resource_id=lsn_port_id),
+ cluster=cluster)
+
+
+def _lsn_port_get(cluster, lsn_id, filters):
+ results = do_request(HTTP_GET,
+ _build_uri_path(LSERVICESNODEPORT_RESOURCE,
+ parent_resource_id=lsn_id,
+ fields="uuid",
+ filters=filters),
+ cluster=cluster)['results']
+ if not results:
+ raise exception.NotFound()
+ elif len(results) == 1:
+ return results[0]['uuid']
+
+
+def lsn_port_by_mac_get(cluster, lsn_id, mac_address):
+ filters = {"tag": mac_address, "tag_scope": "n_mac_address"}
+ return _lsn_port_get(cluster, lsn_id, filters)
+
+
+def lsn_port_by_subnet_get(cluster, lsn_id, subnet_id):
+ filters = {"tag": subnet_id, "tag_scope": "n_subnet_id"}
+ return _lsn_port_get(cluster, lsn_id, filters)
+
+
+def lsn_port_plug_network(cluster, lsn_id, lsn_port_id, lswitch_port_id):
+ patch_obj = {
+ "type": "PatchAttachment",
+ "peer_port_uuid": lswitch_port_id
+ }
+ try:
+ do_request(HTTP_PUT,
+ _build_uri_path(LSERVICESNODEPORT_RESOURCE,
+ parent_resource_id=lsn_id,
+ resource_id=lsn_port_id,
+ is_attachment=True),
+ json.dumps(patch_obj),
+ cluster=cluster)
+ except NvpApiClient.Conflict:
+ # This restriction might be lifted at some point
+ msg = (_("Attempt to plug Logical Services Node %(lsn)s into "
+ "network with port %(port)s failed. PatchAttachment "
+ "already exists with another port") %
+ {'lsn': lsn_id, 'port': lswitch_port_id})
+ LOG.exception(msg)
+ raise nvp_exc.LsnConfigurationConflict(lsn_id=lsn_id)
+
+
+def _lsn_port_configure_action(
+ cluster, lsn_id, lsn_port_id, action, is_enabled, obj):
+ do_request(HTTP_PUT,
+ _build_uri_path(LSERVICESNODE_RESOURCE,
+ resource_id=lsn_id,
+ extra_action=action),
+ json.dumps({"enabled": is_enabled}),
+ cluster=cluster)
+ do_request(HTTP_PUT,
+ _build_uri_path(LSERVICESNODEPORT_RESOURCE,
+ parent_resource_id=lsn_id,
+ resource_id=lsn_port_id,
+ extra_action=action),
+ json.dumps(obj),
+ cluster=cluster)
+
+
+def lsn_port_dhcp_configure(
+ cluster, lsn_id, lsn_port_id, is_enabled=True, dhcp_options=None):
+ dhcp_options = dhcp_options or {}
+ opts = ["%s=%s" % (key, val) for key, val in dhcp_options.iteritems()]
+ dhcp_obj = {
+ 'options': {'options': opts}
+ }
+ _lsn_port_configure_action(
+ cluster, lsn_id, lsn_port_id, 'dhcp', is_enabled, dhcp_obj)
+
+
+def _lsn_port_host_action(
+ cluster, lsn_id, lsn_port_id, host_obj, extra_action, action):
+ do_request(HTTP_POST,
+ _build_uri_path(LSERVICESNODEPORT_RESOURCE,
+ parent_resource_id=lsn_id,
+ resource_id=lsn_port_id,
+ extra_action=extra_action,
+ filters={"action": action}),
+ json.dumps(host_obj),
+ cluster=cluster)
+
+
+def lsn_port_dhcp_host_add(cluster, lsn_id, lsn_port_id, host_data):
+ _lsn_port_host_action(
+ cluster, lsn_id, lsn_port_id, host_data, 'dhcp', 'add_host')
+
+
+def lsn_port_dhcp_host_remove(cluster, lsn_id, lsn_port_id, host_data):
+ _lsn_port_host_action(
+ cluster, lsn_id, lsn_port_id, host_data, 'dhcp', 'remove_host')
relations=None,
filters=None,
types=None,
- is_attachment=False):
+ is_attachment=False,
+ extra_action=None):
resources = resource.split('/')
res_path = resources[0] + (resource_id and "/%s" % resource_id or '')
if len(resources) > 1:
res_path)
if is_attachment:
res_path = "%s/attachment" % res_path
+ elif extra_action:
+ res_path = "%s/%s" % (res_path, extra_action)
params = []
params.append(fields and "fields=%s" % fields)
params.append(relations and "relations=%s" % relations)
nvp_password = bar
default_l3_gw_service_uuid = whatever
default_l2_gw_service_uuid = whatever
+default_service_cluster_uuid = whatever
default_interface_name = whatever
req_timeout = 14
http_timeout = 13
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.
+
+import mock
+
+from oslo.config import cfg
+
+from neutron.common import exceptions as n_exc
+from neutron.plugins.nicira.common import exceptions as p_exc
+from neutron.plugins.nicira.dhcp_meta import nvp
+from neutron.plugins.nicira.NvpApiClient import NvpApiException
+from neutron.tests import base
+
+
+class LsnManagerTestCase(base.BaseTestCase):
+
+ def setUp(self):
+ super(LsnManagerTestCase, 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.manager = nvp.LsnManager(mock.Mock())
+ self.mock_lsn_api_p = mock.patch.object(nvp, 'lsn_api')
+ self.mock_lsn_api = self.mock_lsn_api_p.start()
+ nvp.register_dhcp_opts(cfg)
+ self.addCleanup(cfg.CONF.reset)
+ self.addCleanup(self.mock_lsn_api_p.stop)
+
+ def test_lsn_get(self):
+ self.mock_lsn_api.lsn_for_network_get.return_value = self.lsn_id
+ expected = self.manager.lsn_get(mock.ANY, self.net_id)
+ self.mock_lsn_api.lsn_for_network_get.assert_called_once_with(
+ mock.ANY, self.net_id)
+ self.assertEqual(expected, self.lsn_id)
+
+ def _test_lsn_get_raise_not_found_with_exc(self, exc):
+ self.mock_lsn_api.lsn_for_network_get.side_effect = exc
+ self.assertRaises(p_exc.LsnNotFound,
+ self.manager.lsn_get,
+ mock.ANY, self.net_id)
+ self.mock_lsn_api.lsn_for_network_get.assert_called_once_with(
+ mock.ANY, self.net_id)
+
+ def test_lsn_get_raise_not_found_with_not_found(self):
+ self._test_lsn_get_raise_not_found_with_exc(n_exc.NotFound)
+
+ def test_lsn_get_raise_not_found_with_api_error(self):
+ self._test_lsn_get_raise_not_found_with_exc(NvpApiException)
+
+ def _test_lsn_get_silent_raise_with_exc(self, exc):
+ self.mock_lsn_api.lsn_for_network_get.side_effect = exc
+ expected = self.manager.lsn_get(
+ mock.ANY, self.net_id, raise_on_err=False)
+ self.mock_lsn_api.lsn_for_network_get.assert_called_once_with(
+ mock.ANY, self.net_id)
+ self.assertIsNone(expected)
+
+ def test_lsn_get_silent_raise_with_not_found(self):
+ self._test_lsn_get_silent_raise_with_exc(n_exc.NotFound)
+
+ def test_lsn_get_silent_raise_with_api_error(self):
+ self._test_lsn_get_silent_raise_with_exc(NvpApiException)
+
+ def test_lsn_create(self):
+ self.mock_lsn_api.lsn_for_network_create.return_value = self.lsn_id
+ self.manager.lsn_create(mock.ANY, self.net_id)
+ self.mock_lsn_api.lsn_for_network_create.assert_called_once_with(
+ mock.ANY, self.net_id)
+
+ def test_lsn_create_raise_api_error(self):
+ self.mock_lsn_api.lsn_for_network_create.side_effect = NvpApiException
+ self.assertRaises(p_exc.NvpPluginException,
+ self.manager.lsn_create,
+ mock.ANY, self.net_id)
+ self.mock_lsn_api.lsn_for_network_create.assert_called_once_with(
+ mock.ANY, self.net_id)
+
+ def test_lsn_delete(self):
+ self.manager.lsn_delete(mock.ANY, self.lsn_id)
+ self.mock_lsn_api.lsn_delete.assert_called_once_with(
+ mock.ANY, self.lsn_id)
+
+ def _test_lsn_delete_with_exc(self, exc):
+ self.mock_lsn_api.lsn_delete.side_effect = exc
+ self.manager.lsn_delete(mock.ANY, self.lsn_id)
+ self.mock_lsn_api.lsn_delete.assert_called_once_with(
+ mock.ANY, self.lsn_id)
+
+ def test_lsn_delete_with_not_found(self):
+ self._test_lsn_delete_with_exc(n_exc.NotFound)
+
+ def test_lsn_delete_api_exception(self):
+ self._test_lsn_delete_with_exc(NvpApiException)
+
+ def test_lsn_delete_by_network(self):
+ self.mock_lsn_api.lsn_for_network_get.return_value = self.lsn_id
+ with mock.patch.object(self.manager, 'lsn_delete') as f:
+ self.manager.lsn_delete_by_network(mock.ANY, self.net_id)
+ self.mock_lsn_api.lsn_for_network_get.assert_called_once_with(
+ mock.ANY, self.net_id)
+ f.assert_called_once_with(mock.ANY, self.lsn_id)
+
+ 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:
+ self.manager.lsn_delete_by_network(mock.ANY, self.net_id)
+ self.assertEqual(1, l.call_count)
+
+ def test_lsn_delete_by_network_with_not_found(self):
+ self._test_lsn_delete_by_network_with_exc(n_exc.NotFound)
+
+ def test_lsn_delete_by_network_with_not_api_error(self):
+ self._test_lsn_delete_by_network_with_exc(NvpApiException)
+
+ def test_lsn_port_get(self):
+ self.mock_lsn_api.lsn_port_by_subnet_get.return_value = (
+ self.lsn_port_id)
+ with mock.patch.object(
+ self.manager, 'lsn_get', return_value=self.lsn_id):
+ expected = self.manager.lsn_port_get(
+ mock.ANY, self.net_id, self.sub_id)
+ self.assertEqual(expected, (self.lsn_id, self.lsn_port_id))
+
+ def test_lsn_port_get_lsn_not_found_on_raise(self):
+ with mock.patch.object(
+ self.manager, 'lsn_get',
+ side_effect=p_exc.LsnNotFound(entity='network',
+ entity_id=self.net_id)):
+ self.assertRaises(p_exc.LsnNotFound,
+ self.manager.lsn_port_get,
+ mock.ANY, self.net_id, self.sub_id)
+
+ def test_lsn_port_get_lsn_not_found_silent_raise(self):
+ with mock.patch.object(self.manager, 'lsn_get', return_value=None):
+ expected = self.manager.lsn_port_get(
+ mock.ANY, self.net_id, self.sub_id, raise_on_err=False)
+ self.assertEqual(expected, (None, None))
+
+ def test_lsn_port_get_port_not_found_on_raise(self):
+ self.mock_lsn_api.lsn_port_by_subnet_get.side_effect = n_exc.NotFound
+ with mock.patch.object(
+ self.manager, 'lsn_get', return_value=self.lsn_id):
+ self.assertRaises(p_exc.LsnPortNotFound,
+ self.manager.lsn_port_get,
+ mock.ANY, self.net_id, self.sub_id)
+
+ def test_lsn_port_get_port_not_found_silent_raise(self):
+ self.mock_lsn_api.lsn_port_by_subnet_get.side_effect = n_exc.NotFound
+ with mock.patch.object(
+ self.manager, 'lsn_get', return_value=self.lsn_id):
+ expected = self.manager.lsn_port_get(
+ mock.ANY, self.net_id, self.sub_id, raise_on_err=False)
+ self.assertEqual(expected, (self.lsn_id, None))
+
+ def test_lsn_port_create(self):
+ self.mock_lsn_api.lsn_port_create.return_value = self.lsn_port_id
+ expected = self.manager.lsn_port_create(mock.ANY, mock.ANY, mock.ANY)
+ self.assertEqual(expected, self.lsn_port_id)
+
+ def _test_lsn_port_create_with_exc(self, exc, expected):
+ self.mock_lsn_api.lsn_port_create.side_effect = exc
+ self.assertRaises(expected,
+ self.manager.lsn_port_create,
+ mock.ANY, mock.ANY, mock.ANY)
+
+ def test_lsn_port_create_with_not_found(self):
+ self._test_lsn_port_create_with_exc(n_exc.NotFound, p_exc.LsnNotFound)
+
+ def test_lsn_port_create_api_exception(self):
+ self._test_lsn_port_create_with_exc(NvpApiException,
+ p_exc.NvpPluginException)
+
+ def test_lsn_port_delete(self):
+ self.manager.lsn_port_delete(mock.ANY, mock.ANY, mock.ANY)
+ self.assertEqual(1, self.mock_lsn_api.lsn_port_delete.call_count)
+
+ def _test_lsn_port_delete_with_exc(self, exc):
+ self.mock_lsn_api.lsn_port_delete.side_effect = exc
+ with mock.patch.object(nvp.LOG, 'warn') as l:
+ 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)
+
+ def test_lsn_port_delete_with_not_found(self):
+ self._test_lsn_port_delete_with_exc(n_exc.NotFound)
+
+ def test_lsn_port_delete_api_exception(self):
+ self._test_lsn_port_delete_with_exc(NvpApiException)
+
+ def _test_lsn_port_dhcp_setup(self, ret_val, sub):
+ 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'):
+ expected = self.manager.lsn_port_dhcp_setup(
+ mock.ANY, mock.ANY, mock.ANY, mock.ANY, subnet_config=sub)
+ self.assertEqual(
+ 1, self.mock_lsn_api.lsn_port_create.call_count)
+ self.assertEqual(
+ 1, self.mock_lsn_api.lsn_port_plug_network.call_count)
+ self.assertEqual(expected, ret_val)
+
+ def test_lsn_port_dhcp_setup(self):
+ self._test_lsn_port_dhcp_setup((self.lsn_id, self.lsn_port_id), None)
+
+ def test_lsn_port_dhcp_setup_with_config(self):
+ with mock.patch.object(self.manager, 'lsn_port_dhcp_configure') as f:
+ self._test_lsn_port_dhcp_setup(None, mock.ANY)
+ 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:
+ f.side_effect = n_exc.NotFound
+ self.assertRaises(p_exc.PortConfigurationError,
+ self.manager.lsn_port_dhcp_setup,
+ mock.ANY, mock.ANY, mock.ANY, mock.ANY)
+
+ 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(self.manager, 'lsn_port_delete') as g:
+ self.assertRaises(p_exc.PortConfigurationError,
+ self.manager.lsn_port_dhcp_setup,
+ mock.ANY, mock.ANY, mock.ANY, mock.ANY)
+ self.assertEqual(1, g.call_count)
+
+ def _test_lsn_port_dhcp_configure_with_subnet(
+ self, expected, dns=None, gw=None, routes=None):
+ subnet = {
+ 'enable_dhcp': True,
+ 'dns_nameservers': dns or [],
+ 'gateway_ip': gw,
+ 'host_routes': routes
+ }
+ self.manager.lsn_port_dhcp_configure(mock.ANY, self.lsn_id,
+ self.lsn_port_id, subnet)
+ self.mock_lsn_api.lsn_port_dhcp_configure.assert_called_once_with(
+ mock.ANY, self.lsn_id, self.lsn_port_id, subnet['enable_dhcp'],
+ expected)
+
+ def test_lsn_port_dhcp_configure(self):
+ expected = {
+ 'routers': '127.0.0.1',
+ 'default_lease_time': cfg.CONF.NVP_DHCP.default_lease_time,
+ 'domain_name': cfg.CONF.NVP_DHCP.domain_name
+ }
+ self._test_lsn_port_dhcp_configure_with_subnet(
+ expected, dns=[], gw='127.0.0.1', routes=[])
+
+ def test_lsn_port_dhcp_configure_gatewayless(self):
+ expected = {
+ 'default_lease_time': cfg.CONF.NVP_DHCP.default_lease_time,
+ 'domain_name': cfg.CONF.NVP_DHCP.domain_name
+ }
+ self._test_lsn_port_dhcp_configure_with_subnet(expected, gw=None)
+
+ def test_lsn_port_dhcp_configure_with_extra_dns_servers(self):
+ expected = {
+ 'default_lease_time': cfg.CONF.NVP_DHCP.default_lease_time,
+ 'domain_name_servers': '8.8.8.8,9.9.9.9',
+ 'domain_name': cfg.CONF.NVP_DHCP.domain_name
+ }
+ self._test_lsn_port_dhcp_configure_with_subnet(
+ expected, dns=['8.8.8.8', '9.9.9.9'])
+
+ def test_lsn_port_dhcp_configure_with_host_routes(self):
+ expected = {
+ 'default_lease_time': cfg.CONF.NVP_DHCP.default_lease_time,
+ 'domain_name': cfg.CONF.NVP_DHCP.domain_name,
+ 'classless_static_routes': '8.8.8.8,9.9.9.9'
+ }
+ self._test_lsn_port_dhcp_configure_with_subnet(
+ expected, routes=['8.8.8.8', '9.9.9.9'])
+
+ def _test_lsn_port_dispose_with_values(self, lsn_id, lsn_port_id, count):
+ with mock.patch.object(self.manager,
+ 'lsn_port_get_by_mac',
+ return_value=(lsn_id, lsn_port_id)):
+ self.manager.lsn_port_dispose(mock.ANY, self.net_id, self.mac)
+ self.assertEqual(count,
+ self.mock_lsn_api.lsn_port_delete.call_count)
+
+ def test_lsn_port_dispose(self):
+ self._test_lsn_port_dispose_with_values(
+ self.lsn_id, self.lsn_port_id, 1)
+
+ def test_lsn_port_dispose_lsn_not_found(self):
+ self._test_lsn_port_dispose_with_values(None, None, 0)
+
+ def test_lsn_port_dispose_lsn_port_not_found(self):
+ self._test_lsn_port_dispose_with_values(self.lsn_id, None, 0)
+
+ 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:
+ self.manager.lsn_port_dispose(mock.ANY, self.net_id, self.mac)
+ self.assertEqual(1, l.call_count)
+
+ def test_lsn_port_host_conf(self):
+ with mock.patch.object(self.manager,
+ 'lsn_port_get',
+ return_value=(self.lsn_id, self.lsn_port_id)):
+ f = mock.Mock()
+ self.manager._lsn_port_host_conf(mock.ANY, self.net_id,
+ self.sub_id, mock.ANY, f)
+ self.assertEqual(1, f.call_count)
+
+ def test_lsn_port_host_conf_lsn_port_not_found(self):
+ with mock.patch.object(
+ self.manager,
+ 'lsn_port_get',
+ side_effect=p_exc.LsnPortNotFound(lsn_id=self.lsn_id,
+ entity='subnet',
+ entity_id=self.sub_id)):
+ self.assertRaises(p_exc.PortConfigurationError,
+ self.manager._lsn_port_host_conf, mock.ANY,
+ self.net_id, self.sub_id, mock.ANY, mock.Mock())
+
+
+class DhcpAgentNotifyAPITestCase(base.BaseTestCase):
+
+ def setUp(self):
+ super(DhcpAgentNotifyAPITestCase, self).setUp()
+ self.notifier = nvp.DhcpAgentNotifyAPI(mock.Mock(), mock.Mock())
+ self.plugin = self.notifier.plugin
+ self.lsn_manager = self.notifier.lsn_manager
+
+ def _test_notify_subnet_action(self, action):
+ with mock.patch.object(self.notifier, '_subnet_%s' % action) as f:
+ self.notifier._handle_subnet_dhcp_access[action] = f
+ subnet = {'subnet': mock.ANY}
+ self.notifier.notify(
+ mock.ANY, subnet, 'subnet.%s.end' % action)
+ f.assert_called_once_with(mock.ANY, subnet)
+
+ def test_notify_subnet_create(self):
+ self._test_notify_subnet_action('create')
+
+ def test_notify_subnet_update(self):
+ self._test_notify_subnet_action('update')
+
+ def test_notify_subnet_delete(self):
+ self._test_notify_subnet_action('delete')
+
+ def _test_subnet_create(self, enable_dhcp, exc=None,
+ exc_obj=None, call_notify=True):
+ subnet = {
+ 'id': 'foo_subnet_id',
+ 'enable_dhcp': enable_dhcp,
+ 'network_id': 'foo_network_id',
+ 'tenant_id': 'foo_tenant_id',
+ 'cidr': '0.0.0.0/0'
+ }
+ if exc:
+ self.plugin.create_port.side_effect = exc_obj or exc
+ self.assertRaises(exc,
+ self.notifier.notify,
+ mock.ANY,
+ {'subnet': subnet},
+ 'subnet.create.end')
+ self.plugin.delete_subnet.assert_called_with(
+ mock.ANY, subnet['id'])
+ else:
+ if call_notify:
+ self.notifier.notify(
+ mock.ANY, {'subnet': subnet}, 'subnet.create.end')
+ if enable_dhcp:
+ dhcp_port = {
+ 'name': '',
+ 'admin_state_up': True,
+ 'network_id': 'foo_network_id',
+ 'tenant_id': 'foo_tenant_id',
+ 'device_owner': 'network:dhcp',
+ 'mac_address': mock.ANY,
+ 'fixed_ips': [{'subnet_id': 'foo_subnet_id'}],
+ 'device_id': ''
+ }
+ self.plugin.create_port.assert_called_once_with(
+ mock.ANY, {'port': dhcp_port})
+ else:
+ self.assertEqual(0, self.plugin.create_port.call_count)
+
+ def test_subnet_create_enabled_dhcp(self):
+ self._test_subnet_create(True)
+
+ def test_subnet_create_disabled_dhcp(self):
+ self._test_subnet_create(False)
+
+ def test_subnet_create_raise_port_config_error(self):
+ with mock.patch.object(nvp.db_base_plugin_v2.NeutronDbPluginV2,
+ 'delete_port') as d:
+ self._test_subnet_create(
+ True,
+ exc=n_exc.Conflict,
+ exc_obj=p_exc.PortConfigurationError(lsn_id='foo_lsn_id',
+ net_id='foo_net_id',
+ port_id='foo_port_id'))
+ d.assert_called_once_with(self.plugin, mock.ANY, 'foo_port_id')
+
+ def test_subnet_update(self):
+ subnet = {
+ 'id': 'foo_subnet_id',
+ 'network_id': 'foo_network_id',
+ }
+ self.lsn_manager.lsn_port_get.return_value = ('foo_lsn_id',
+ 'foo_lsn_port_id')
+ self.notifier.notify(
+ mock.ANY, {'subnet': subnet}, 'subnet.update.end')
+ self.lsn_manager.lsn_port_dhcp_configure.assert_called_once_with(
+ mock.ANY, 'foo_lsn_id', 'foo_lsn_port_id', subnet)
+
+ def test_subnet_update_raise_lsn_not_found(self):
+ subnet = {
+ 'id': 'foo_subnet_id',
+ 'network_id': 'foo_network_id',
+ }
+ self.lsn_manager.lsn_port_get.side_effect = (
+ p_exc.LsnNotFound(entity='network',
+ entity_id=subnet['network_id']))
+ self.assertRaises(p_exc.LsnNotFound,
+ self.notifier.notify,
+ mock.ANY, {'subnet': subnet}, 'subnet.update.end')
+
+ def _test_subnet_update_lsn_port_not_found(self, dhcp_port):
+ subnet = {
+ 'id': 'foo_subnet_id',
+ 'enable_dhcp': True,
+ 'network_id': 'foo_network_id',
+ 'tenant_id': 'foo_tenant_id'
+ }
+ self.lsn_manager.lsn_port_get.side_effect = (
+ p_exc.LsnPortNotFound(lsn_id='foo_lsn_id',
+ entity='subnet',
+ 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:
+ self.notifier.notify(
+ mock.ANY, {'subnet': subnet}, 'subnet.update.end')
+ self.assertEqual(count, h.call_count)
+ if not dhcp_port:
+ self._test_subnet_create(enable_dhcp=True,
+ exc=None, call_notify=False)
+
+ def test_subnet_update_lsn_port_not_found_without_dhcp_port(self):
+ self._test_subnet_update_lsn_port_not_found(None)
+
+ def test_subnet_update_lsn_port_not_found_with_dhcp_port(self):
+ self._test_subnet_update_lsn_port_not_found([mock.ANY])
+
+ def _test_subnet_delete(self, ports=None):
+ subnet = {
+ 'id': 'foo_subnet_id',
+ 'network_id': 'foo_network_id',
+ 'cidr': '0.0.0.0/0'
+ }
+ self.plugin.get_ports.return_value = ports
+ self.notifier.notify(mock.ANY, {'subnet': subnet}, 'subnet.delete.end')
+ filters = {
+ 'network_id': [subnet['network_id']],
+ 'device_owner': ['network:dhcp']
+ }
+ self.plugin.get_ports.assert_called_once_with(
+ mock.ANY, filters=filters)
+ if ports:
+ self.plugin.delete_port.assert_called_once_with(
+ mock.ANY, ports[0]['id'])
+ else:
+ self.assertEqual(0, self.plugin.delete_port.call_count)
+
+ def test_subnet_delete_enabled_dhcp_no_ports(self):
+ self._test_subnet_delete()
+
+ def test_subnet_delete_enabled_dhcp_with_dhcp_port(self):
+ self._test_subnet_delete([{'id': 'foo_port_id'}])
+
+
+class DhcpTestCase(base.BaseTestCase):
+
+ def setUp(self):
+ super(DhcpTestCase, self).setUp()
+ self.plugin = mock.Mock()
+ self.plugin.lsn_manager = mock.Mock()
+
+ def test_handle_create_network(self):
+ network = {'id': 'foo_network_id'}
+ nvp.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'])
+
+ def test_handle_delete_network(self):
+ network_id = 'foo_network_id'
+ self.plugin.lsn_manager.lsn_delete_by_network.return_value = (
+ 'foo_lsn_id')
+ nvp.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')
+
+ def _test_handle_create_dhcp_owner_port(self, exc=None):
+ subnet = {
+ 'cidr': '0.0.0.0/0',
+ 'id': 'foo_subnet_id'
+ }
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': 'network:dhcp',
+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
+ 'network_id': 'foo_network_id',
+ 'fixed_ips': [{'subnet_id': subnet['id']}]
+ }
+ expected_data = {
+ 'subnet_id': subnet['id'],
+ 'ip_address': subnet['cidr'],
+ 'mac_address': port['mac_address']
+ }
+ self.plugin.get_subnet.return_value = subnet
+ if exc is None:
+ nvp.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'],
+ port['id'], expected_data, subnet))
+ else:
+ self.plugin.lsn_manager.lsn_port_dhcp_setup.side_effect = exc
+ self.assertRaises(n_exc.NeutronException,
+ nvp.handle_port_dhcp_access,
+ self.plugin, mock.ANY, port, 'create_port')
+
+ def test_handle_create_dhcp_owner_port(self):
+ self._test_handle_create_dhcp_owner_port()
+
+ def test_handle_create_dhcp_owner_port_raise_port_config_error(self):
+ config_error = p_exc.PortConfigurationError(lsn_id='foo_lsn_id',
+ net_id='foo_net_id',
+ port_id='foo_port_id')
+ self._test_handle_create_dhcp_owner_port(exc=config_error)
+
+ def test_handle_delete_dhcp_owner_port(self):
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': 'network:dhcp',
+ 'network_id': 'foo_network_id',
+ 'fixed_ips': [],
+ 'mac_address': 'aa:bb:cc:dd:ee:ff'
+ }
+ nvp.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'])
+
+ def _test_handle_user_port(self, action, handler):
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': 'foo_device_owner',
+ 'network_id': 'foo_network_id',
+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
+ 'fixed_ips': [{'subnet_id': 'foo_subnet_id',
+ 'ip_address': '1.2.3.4'}]
+ }
+ expected_data = {
+ 'ip_address': '1.2.3.4',
+ '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)
+ handler.assert_called_once_with(
+ mock.ANY, port['network_id'], 'foo_subnet_id', expected_data)
+
+ def test_handle_create_user_port(self):
+ self._test_handle_user_port(
+ 'create_port', self.plugin.lsn_manager.lsn_port_dhcp_host_add)
+
+ def test_handle_delete_user_port(self):
+ self._test_handle_user_port(
+ 'delete_port', self.plugin.lsn_manager.lsn_port_dhcp_host_remove)
+
+ def _test_handle_user_port_disabled_dhcp(self, action, handler):
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': 'foo_device_owner',
+ 'network_id': 'foo_network_id',
+ 'mac_address': 'aa:bb:cc:dd:ee:ff',
+ 'fixed_ips': [{'subnet_id': 'foo_subnet_id',
+ '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)
+ self.assertEqual(0, handler.call_count)
+
+ def test_handle_create_user_port_disabled_dhcp(self):
+ self._test_handle_user_port_disabled_dhcp(
+ 'create_port', self.plugin.lsn_manager.lsn_port_dhcp_host_add)
+
+ def test_handle_delete_user_port_disabled_dhcp(self):
+ self._test_handle_user_port_disabled_dhcp(
+ 'delete_port', self.plugin.lsn_manager.lsn_port_dhcp_host_remove)
+
+ def _test_handle_user_port_no_fixed_ips(self, action, handler):
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': 'foo_device_owner',
+ 'network_id': 'foo_network_id',
+ 'fixed_ips': []
+ }
+ nvp.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):
+ self._test_handle_user_port_no_fixed_ips(
+ 'create_port', self.plugin.lsn_manager.lsn_port_dhcp_host_add)
+
+ def test_handle_delete_user_port_no_fixed_ips(self):
+ self._test_handle_user_port_no_fixed_ips(
+ 'delete_port', self.plugin.lsn_manager.lsn_port_dhcp_host_remove)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.
+
+import json
+import mock
+
+from neutron.common import exceptions
+from neutron.plugins.nicira.common import exceptions as nvp_exc
+from neutron.plugins.nicira.common import utils
+from neutron.plugins.nicira.nsxlib import lsn as lsnlib
+from neutron.plugins.nicira import NvpApiClient
+from neutron.tests import base
+
+
+class LSNTestCase(base.BaseTestCase):
+
+ def setUp(self):
+ super(LSNTestCase, self).setUp()
+ self.mock_request_p = mock.patch.object(lsnlib, 'do_request')
+ self.mock_request = self.mock_request_p.start()
+ self.cluster = mock.Mock()
+ self.cluster.default_service_cluster_uuid = 'foo'
+ self.addCleanup(self.mock_request_p.stop)
+
+ def test_service_cluster_None(self):
+ self.mock_request.return_value = None
+ expected = lsnlib.service_cluster_exists(None, None)
+ self.assertFalse(expected)
+
+ def test_service_cluster_found(self):
+ self.mock_request.return_value = {
+ "results": [
+ {
+ "_href": "/ws.v1/service-cluster/foo_uuid",
+ "display_name": "foo_name",
+ "uuid": "foo_uuid",
+ "tags": [],
+ "_schema": "/ws.v1/schema/ServiceClusterConfig",
+ "gateways": []
+ }
+ ],
+ "result_count": 1
+ }
+ expected = lsnlib.service_cluster_exists(None, 'foo_uuid')
+ self.assertTrue(expected)
+
+ def test_service_cluster_not_found(self):
+ self.mock_request.side_effect = exceptions.NotFound()
+ expected = lsnlib.service_cluster_exists(None, 'foo_uuid')
+ self.assertFalse(expected)
+
+ def test_lsn_for_network_create(self):
+ net_id = "foo_network_id"
+ tags = utils.get_tags(n_network_id=net_id)
+ obj = {"service_cluster_uuid": "foo", "tags": tags}
+ lsnlib.lsn_for_network_create(self.cluster, net_id)
+ self.mock_request.assert_called_once_with(
+ "POST", "/ws.v1/lservices-node",
+ json.dumps(obj), cluster=self.cluster)
+
+ def test_lsn_for_network_get(self):
+ net_id = "foo_network_id"
+ lsn_id = "foo_lsn_id"
+ self.mock_request.return_value = {
+ "results": [{"uuid": "foo_lsn_id"}],
+ "result_count": 1
+ }
+ result = lsnlib.lsn_for_network_get(self.cluster, net_id)
+ self.assertEqual(lsn_id, result)
+ self.mock_request.assert_called_once_with(
+ "GET",
+ ("/ws.v1/lservices-node?fields=uuid&tag_scope="
+ "n_network_id&tag=%s" % net_id),
+ cluster=self.cluster)
+
+ def test_lsn_for_network_get_none(self):
+ net_id = "foo_network_id"
+ self.mock_request.return_value = {
+ "results": [{"uuid": "foo_lsn_id1"}, {"uuid": "foo_lsn_id2"}],
+ "result_count": 2
+ }
+ result = lsnlib.lsn_for_network_get(self.cluster, net_id)
+ self.assertIsNone(result)
+
+ def test_lsn_for_network_get_raise_not_found(self):
+ net_id = "foo_network_id"
+ self.mock_request.return_value = {
+ "results": [], "result_count": 0
+ }
+ self.assertRaises(exceptions.NotFound,
+ lsnlib.lsn_for_network_get,
+ self.cluster, net_id)
+
+ def test_lsn_delete(self):
+ lsn_id = "foo_id"
+ lsnlib.lsn_delete(self.cluster, lsn_id)
+ self.mock_request.assert_called_once_with(
+ "DELETE",
+ "/ws.v1/lservices-node/%s" % lsn_id, cluster=self.cluster)
+
+ def test_lsn_port_create(self):
+ port_data = {
+ "ip_address": "1.2.3.0/24",
+ "mac_address": "aa:bb:cc:dd:ee:ff",
+ "subnet_id": "foo_subnet_id"
+ }
+ port_id = "foo_port_id"
+ self.mock_request.return_value = {"uuid": port_id}
+ lsn_id = "foo_lsn_id"
+ result = lsnlib.lsn_port_create(self.cluster, lsn_id, port_data)
+ self.assertEqual(result, port_id)
+ tags = utils.get_tags(n_subnet_id=port_data["subnet_id"],
+ n_mac_address=port_data["mac_address"])
+ port_obj = {
+ "ip_address": port_data["ip_address"],
+ "mac_address": port_data["mac_address"],
+ "type": "LogicalServicesNodePortConfig",
+ "tags": tags
+ }
+ self.mock_request.assert_called_once_with(
+ "POST", "/ws.v1/lservices-node/%s/lport" % lsn_id,
+ json.dumps(port_obj), cluster=self.cluster)
+
+ def test_lsn_port_delete(self):
+ lsn_id = "foo_lsn_id"
+ lsn_port_id = "foo_port_id"
+ lsnlib.lsn_port_delete(self.cluster, lsn_id, lsn_port_id)
+ self.mock_request.assert_called_once_with(
+ "DELETE",
+ "/ws.v1/lservices-node/%s/lport/%s" % (lsn_id, lsn_port_id),
+ cluster=self.cluster)
+
+ def test_lsn_port_get_with_filters(self):
+ lsn_id = "foo_lsn_id"
+ port_id = "foo_port_id"
+ filters = {"tag": "foo_tag", "tag_scope": "foo_scope"}
+ self.mock_request.return_value = {
+ "results": [{"uuid": port_id}],
+ "result_count": 1
+ }
+ result = lsnlib._lsn_port_get(self.cluster, lsn_id, filters)
+ self.assertEqual(result, port_id)
+ self.mock_request.assert_called_once_with(
+ "GET",
+ ("/ws.v1/lservices-node/%s/lport?fields=uuid&tag_scope=%s&"
+ "tag=%s" % (lsn_id, filters["tag_scope"], filters["tag"])),
+ cluster=self.cluster)
+
+ def test_lsn_port_get_with_filters_return_none(self):
+ self.mock_request.return_value = {
+ "results": [{"uuid": "foo1"}, {"uuid": "foo2"}],
+ "result_count": 2
+ }
+ result = lsnlib._lsn_port_get(self.cluster, "lsn_id", None)
+ self.assertIsNone(result)
+
+ def test_lsn_port_get_with_filters_raises_not_found(self):
+ self.mock_request.return_value = {"results": [], "result_count": 0}
+ self.assertRaises(exceptions.NotFound,
+ lsnlib._lsn_port_get,
+ self.cluster, "lsn_id", None)
+
+ def test_lsn_port_plug_network(self):
+ lsn_id = "foo_lsn_id"
+ lsn_port_id = "foo_lsn_port_id"
+ lswitch_port_id = "foo_lswitch_port_id"
+ lsnlib.lsn_port_plug_network(
+ self.cluster, lsn_id, lsn_port_id, lswitch_port_id)
+ self.mock_request.assert_called_once_with(
+ "PUT",
+ ("/ws.v1/lservices-node/%s/lport/%s/"
+ "attachment") % (lsn_id, lsn_port_id),
+ json.dumps({"peer_port_uuid": lswitch_port_id,
+ "type": "PatchAttachment"}),
+ cluster=self.cluster)
+
+ def test_lsn_port_plug_network_raise_conflict(self):
+ lsn_id = "foo_lsn_id"
+ lsn_port_id = "foo_lsn_port_id"
+ lswitch_port_id = "foo_lswitch_port_id"
+ self.mock_request.side_effect = NvpApiClient.Conflict
+ self.assertRaises(
+ nvp_exc.LsnConfigurationConflict,
+ lsnlib.lsn_port_plug_network,
+ self.cluster, lsn_id, lsn_port_id, lswitch_port_id)
+
+ def _test_lsn_port_dhcp_configure(
+ self, lsn_id, lsn_port_id, is_enabled, opts):
+ lsnlib.lsn_port_dhcp_configure(
+ self.cluster, lsn_id, lsn_port_id, is_enabled, opts)
+ opt_array = ["%s=%s" % (key, val) for key, val in opts.iteritems()]
+ self.mock_request.assert_has_calls([
+ mock.call("PUT", "/ws.v1/lservices-node/%s/dhcp" % lsn_id,
+ json.dumps({"enabled": is_enabled}),
+ cluster=self.cluster),
+ mock.call("PUT",
+ ("/ws.v1/lservices-node/%s/"
+ "lport/%s/dhcp") % (lsn_id, lsn_port_id),
+ json.dumps({"options": {"options": opt_array}}),
+ cluster=self.cluster)
+ ])
+
+ def test_lsn_port_dhcp_configure_empty_opts(self):
+ lsn_id = "foo_lsn_id"
+ lsn_port_id = "foo_lsn_port_id"
+ is_enabled = False
+ opts = {}
+ self._test_lsn_port_dhcp_configure(
+ lsn_id, lsn_port_id, is_enabled, opts)
+
+ def test_lsn_port_dhcp_configure_with_opts(self):
+ lsn_id = "foo_lsn_id"
+ lsn_port_id = "foo_lsn_port_id"
+ is_enabled = True
+ opts = {"opt1": "val1", "opt2": "val2"}
+ self._test_lsn_port_dhcp_configure(
+ lsn_id, lsn_port_id, is_enabled, opts)
+
+ def _test_lsn_port_host_action(
+ self, lsn_port_action_func, extra_action, action, host):
+ lsn_id = "foo_lsn_id"
+ lsn_port_id = "foo_lsn_port_id"
+ lsn_port_action_func(self.cluster, lsn_id, lsn_port_id, host)
+ self.mock_request.assert_called_once_with(
+ "POST",
+ ("/ws.v1/lservices-node/%s/lport/"
+ "%s/%s?action=%s") % (lsn_id, lsn_port_id, extra_action, action),
+ json.dumps(host), cluster=self.cluster)
+
+ def test_lsn_port_dhcp_host_add(self):
+ host = {
+ "ip_address": "1.2.3.4",
+ "mac_address": "aa:bb:cc:dd:ee:ff"
+ }
+ self._test_lsn_port_host_action(
+ lsnlib.lsn_port_dhcp_host_add, "dhcp", "add_host", host)
+
+ def test_lsn_port_dhcp_host_remove(self):
+ host = {
+ "ip_address": "1.2.3.4",
+ "mac_address": "aa:bb:cc:dd:ee:ff"
+ }
+ self._test_lsn_port_host_action(
+ lsnlib.lsn_port_dhcp_host_remove, "dhcp", "remove_host", host)
result = utils.check_and_truncate(name)
self.assertEqual(len(result), utils.MAX_DISPLAY_NAME_LEN)
+ def test_build_uri_path_plain(self):
+ result = nvplib._build_uri_path('RESOURCE')
+ self.assertEqual("%s/%s" % (nvplib.URI_PREFIX, 'RESOURCE'), result)
+
+ def test_build_uri_path_with_field(self):
+ result = nvplib._build_uri_path('RESOURCE', fields='uuid')
+ expected = "%s/%s?fields=uuid" % (nvplib.URI_PREFIX, 'RESOURCE')
+ self.assertEqual(expected, result)
+
+ def test_build_uri_path_with_filters(self):
+ filters = {"tag": 'foo', "tag_scope": "scope_foo"}
+ result = nvplib._build_uri_path('RESOURCE', filters=filters)
+ expected = (
+ "%s/%s?tag_scope=scope_foo&tag=foo" %
+ (nvplib.URI_PREFIX, 'RESOURCE'))
+ self.assertEqual(expected, result)
+
+ def test_build_uri_path_with_resource_id(self):
+ res = 'RESOURCE'
+ res_id = 'resource_id'
+ result = nvplib._build_uri_path(res, resource_id=res_id)
+ expected = "%s/%s/%s" % (nvplib.URI_PREFIX, res, res_id)
+ self.assertEqual(expected, result)
+
+ def test_build_uri_path_with_parent_and_resource_id(self):
+ parent_res = 'RESOURCE_PARENT'
+ child_res = 'RESOURCE_CHILD'
+ res = '%s/%s' % (child_res, parent_res)
+ par_id = 'parent_resource_id'
+ res_id = 'resource_id'
+ result = nvplib._build_uri_path(
+ res, parent_resource_id=par_id, resource_id=res_id)
+ expected = ("%s/%s/%s/%s/%s" %
+ (nvplib.URI_PREFIX, parent_res, par_id, child_res, res_id))
+ self.assertEqual(expected, result)
+
+ def test_build_uri_path_with_attachment(self):
+ parent_res = 'RESOURCE_PARENT'
+ child_res = 'RESOURCE_CHILD'
+ res = '%s/%s' % (child_res, parent_res)
+ par_id = 'parent_resource_id'
+ res_id = 'resource_id'
+ result = nvplib._build_uri_path(res, parent_resource_id=par_id,
+ resource_id=res_id, is_attachment=True)
+ expected = ("%s/%s/%s/%s/%s/%s" %
+ (nvplib.URI_PREFIX, parent_res,
+ par_id, child_res, res_id, 'attachment'))
+ self.assertEqual(expected, result)
+
+ def test_build_uri_path_with_extra_action(self):
+ parent_res = 'RESOURCE_PARENT'
+ child_res = 'RESOURCE_CHILD'
+ res = '%s/%s' % (child_res, parent_res)
+ par_id = 'parent_resource_id'
+ res_id = 'resource_id'
+ result = nvplib._build_uri_path(res, parent_resource_id=par_id,
+ resource_id=res_id, extra_action='doh')
+ expected = ("%s/%s/%s/%s/%s/%s" %
+ (nvplib.URI_PREFIX, parent_res,
+ par_id, child_res, res_id, 'doh'))
+ self.assertEqual(expected, result)
+
def _nicira_method(method_name, module_name='nvplib'):
return '%s.%s.%s' % ('neutron.plugins.nicira', module_name, method_name)
from neutron.plugins.nicira.common import config # noqa
from neutron.plugins.nicira.common import exceptions
from neutron.plugins.nicira.common import sync
+from neutron.plugins.nicira.nsxlib import lsn as lsnlib
from neutron.plugins.nicira import nvp_cluster
+from neutron.plugins.nicira import NvpApiClient as nvp_client
from neutron.tests.unit.nicira import get_fake_conf
from neutron.tests.unit.nicira import PLUGIN_NAME
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)
+ # The version returned from NVP does not really matter here
+ with mock.patch.object(nvp_client.NVPApiHelper,
+ 'get_nvp_version',
+ return_value=nvp_client.NVPVersion("9.9")):
+ with mock.patch.object(lsnlib,
+ 'service_cluster_exists',
+ return_value=True):
+ plugin = NeutronManager().get_plugin()
+ self.assertNotIn('agent',
+ plugin.supported_extension_aliases)
+ self.assertNotIn('dhcp_agent_scheduler',
+ plugin.supported_extension_aliases)
+
+ def test_agentless_extensions_version_fail(self):
+ 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)
+ with mock.patch.object(nvp_client.NVPApiHelper,
+ 'get_nvp_version',
+ return_value=nvp_client.NVPVersion("3.2")):
+ self.assertRaises(exceptions.NvpPluginException, NeutronManager)
+
+ def test_agentless_extensions_unmet_deps_fail(self):
+ 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)
+ with mock.patch.object(nvp_client.NVPApiHelper,
+ 'get_nvp_version',
+ return_value=nvp_client.NVPVersion("3.2")):
+ with mock.patch.object(lsnlib,
+ 'service_cluster_exists',
+ return_value=False):
+ self.assertRaises(exceptions.NvpPluginException,
+ NeutronManager)
def test_agent_extensions(self):
q_config.parse(['--config-file', NVP_BASE_CONF_PATH,