# Default DHCP lease time
# default_lease_time = 43200
+
+[nvp_metadata]
+# IP address used by Metadata server
+# metadata_server_address = 127.0.0.1
+
+# TCP Port used by Metadata server
+# metadata_server_port = 8775
+
+# When proxying metadata requests, Neutron signs the Instance-ID header with a
+# shared secret to prevent spoofing. You may select any string for a secret,
+# but it MUST match with the configuration used by the Metadata server
+# metadata_shared_secret =
# router, but if it does, it should not happen within a
# transaction, and it should be restored on rollback
self.handle_router_metadata_access(
- context, router_id, do_create=False)
+ context, router_id, interface=None)
# Pre-delete checks
# NOTE(salv-orlando): These checks will be repeated anyway when
# calling the superclass. This is wasteful, but is the simplest
# 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_router_metadata_access(context, router_id)
+ self.handle_router_metadata_access(
+ context, router_id, interface=router_iface_info)
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_router_metadata_access(context, router_id)
+ self.handle_router_metadata_access(
+ context, router_id, interface=info)
try:
if not subnet:
subnet = self._get_subnet(context, subnet_id)
from neutron.common import constants as const
from neutron.common import exceptions as n_exc
from neutron.db import db_base_plugin_v2
+from neutron.db import l3_db
+from neutron.extensions import external_net
from neutron.openstack.common import log as logging
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.nsxlib import lsn as lsn_api
LOG = logging.getLogger(__name__)
-
+# A unique MAC to quickly identify the LSN port used for metadata services
+# when dhcp on the subnet is off. Inspired by leet-speak for 'metadata'.
+METADATA_MAC = "fa:15:73:74:d4:74"
+METADATA_PORT_ID = 'metadata:id'
+METADATA_PORT_NAME = 'metadata:name'
+METADATA_DEVICE_ID = 'metadata:device'
+META_CONF = 'metadata-proxy'
+DHCP_CONF = 'dhcp'
+SPECIAL_OWNERS = (const.DEVICE_OWNER_DHCP,
+ const.DEVICE_OWNER_ROUTER_GW,
+ l3_db.DEVICE_OWNER_ROUTER_INTF)
dhcp_opts = [
cfg.ListOpt('extra_domain_name_servers',
]
+metadata_opts = [
+ cfg.StrOpt('metadata_server_address', default='127.0.0.1',
+ help=_("IP address used by Metadata server.")),
+ cfg.IntOpt('metadata_server_port',
+ default=8775,
+ help=_("TCP Port used by Metadata server.")),
+ cfg.StrOpt('metadata_shared_secret',
+ default='',
+ help=_('Shared secret to sign instance-id request'),
+ secret=True)
+]
+
+
def register_dhcp_opts(config):
config.CONF.register_opts(dhcp_opts, "NVP_DHCP")
+def register_metadata_opts(config):
+ config.CONF.register_opts(metadata_opts, "NVP_METADATA")
+
+
class LsnManager(object):
"""Manage LSN entities associated with networks."""
context, network_id, mac_address, raise_on_err=False)
if lsn_port_id:
self.lsn_port_delete(context, lsn_id, lsn_port_id)
+ if mac_address == METADATA_MAC:
+ try:
+ lswitch_port = nvplib.get_port_by_neutron_tag(
+ self.cluster, network_id, METADATA_PORT_ID)
+ if lswitch_port:
+ lswitch_port_id = lswitch_port['uuid']
+ nvplib.delete_port(
+ self.cluster, network_id, lswitch_port_id)
+ else:
+ LOG.warn(_("Metadata port not found while attempting "
+ "to delete it from network %s"), network_id)
+ except (n_exc.PortNotFoundOnNetwork,
+ nvplib.NvpApiClient.NvpApiException):
+ LOG.warn(_("Metadata port not found while attempting "
+ "to delete it from network %s"), network_id)
+ else:
+ LOG.warn(_("Unable to find Logical Services Node "
+ "Port with MAC %s"), mac_address)
def lsn_port_dhcp_setup(
self, context, network_id, port_id, port_data, subnet_config=None):
else:
return (lsn_id, lsn_port_id)
+ def lsn_port_metadata_setup(self, context, lsn_id, subnet):
+ """Connect subnet to specified LSN."""
+ data = {
+ "mac_address": METADATA_MAC,
+ "ip_address": subnet['cidr'],
+ "subnet_id": subnet['id']
+ }
+ network_id = subnet['network_id']
+ tenant_id = subnet['tenant_id']
+ lswitch_port_id = None
+ try:
+ lswitch_port_id = nvplib.create_lport(
+ self.cluster, network_id, tenant_id,
+ METADATA_PORT_ID, METADATA_PORT_NAME,
+ METADATA_DEVICE_ID, True)['uuid']
+ lsn_port_id = self.lsn_port_create(self.cluster, lsn_id, data)
+ except (n_exc.NotFound, p_exc.NvpPluginException,
+ nvplib.NvpApiClient.NvpApiException):
+ raise p_exc.PortConfigurationError(
+ net_id=network_id, lsn_id=lsn_id, port_id=lswitch_port_id)
+ else:
+ try:
+ lsn_api.lsn_port_plug_network(
+ self.cluster, lsn_id, lsn_port_id, lswitch_port_id)
+ except p_exc.LsnConfigurationConflict:
+ self.lsn_port_delete(self.cluster, lsn_id, lsn_port_id)
+ nvplib.delete_port(self.cluster, network_id, lswitch_port_id)
+ raise p_exc.PortConfigurationError(
+ net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
+
def lsn_port_dhcp_configure(self, context, lsn_id, lsn_port_id, subnet):
"""Enable/disable dhcp services with the given config options."""
is_enabled = subnet["enable_dhcp"]
LOG.error(err_msg)
raise p_exc.NvpPluginException(err_msg=err_msg)
+ def lsn_metadata_configure(self, context, subnet_id, is_enabled):
+ """Configure metadata service for the specified subnet."""
+ subnet = self.plugin.get_subnet(context, subnet_id)
+ network_id = subnet['network_id']
+ meta_conf = cfg.CONF.NVP_METADATA
+ metadata_options = {
+ 'metadata_server_ip': meta_conf.metadata_server_address,
+ 'metadata_server_port': meta_conf.metadata_server_port,
+ 'metadata_proxy_shared_secret': meta_conf.metadata_shared_secret
+ }
+ try:
+ lsn_id = self.lsn_get(context, network_id)
+ lsn_api.lsn_metadata_configure(
+ self.cluster, lsn_id, is_enabled, metadata_options)
+ except (p_exc.LsnNotFound, nvplib.NvpApiClient.NvpApiException):
+ err_msg = (_('Unable to configure metadata access '
+ 'for subnet %s') % subnet_id)
+ LOG.error(err_msg)
+ raise p_exc.NvpPluginException(err_msg=err_msg)
+ if is_enabled:
+ try:
+ # test that the lsn port exists
+ self.lsn_port_get(context, network_id, subnet_id)
+ except p_exc.LsnPortNotFound:
+ # this might happen if subnet had dhcp off when created
+ # so create one, and wire it
+ self.lsn_port_metadata_setup(context, lsn_id, subnet)
+ else:
+ self.lsn_port_dispose(context, network_id, METADATA_MAC)
+
def _lsn_port_host_conf(self, context, network_id, subnet_id, data, hdlr):
lsn_id = None
lsn_port_id = None
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."""
+ """Add dhcp host entry to LSN port configuration."""
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_dhcp_host_add)
self._lsn_port_host_conf(context, network_id, subnet_id, host,
lsn_api.lsn_port_dhcp_host_remove)
+ def lsn_port_meta_host_add(self, context, network_id, subnet_id, host):
+ """Add metadata host entry to LSN port configuration."""
+ self._lsn_port_host_conf(context, network_id, subnet_id, host,
+ lsn_api.lsn_port_metadata_host_add)
+
+ def lsn_port_meta_host_remove(self, context, network_id, subnet_id, host):
+ """Remove meta host entry from LSN port configuration."""
+ self._lsn_port_host_conf(context, network_id, subnet_id, host,
+ lsn_api.lsn_port_metadata_host_remove)
+
+ def lsn_port_update(
+ self, context, network_id, subnet_id, dhcp=None, meta=None):
+ """Update the specified configuration for the LSN port."""
+ if not dhcp and not meta:
+ return
+ try:
+ lsn_id, lsn_port_id = self.lsn_port_get(
+ context, network_id, subnet_id, raise_on_err=False)
+ if dhcp and lsn_id and lsn_port_id:
+ lsn_api.lsn_port_host_entries_update(
+ self.cluster, lsn_id, lsn_port_id, DHCP_CONF, dhcp)
+ if meta and lsn_id and lsn_port_id:
+ lsn_api.lsn_port_host_entries_update(
+ self.cluster, lsn_id, lsn_port_id, META_CONF, meta)
+ except nvplib.NvpApiClient.NvpApiException:
+ raise p_exc.PortConfigurationError(
+ net_id=network_id, lsn_id=lsn_id, port_id=lsn_port_id)
+
class DhcpAgentNotifyAPI(object):
[resource, action, _e] = methodname.split('.')
if resource == 'subnet':
self._handle_subnet_dhcp_access[action](context, data['subnet'])
+ elif resource == 'port' and action == 'update':
+ self._port_update(context, data['port'])
+
+ def _port_update(self, context, port):
+ # With no fixed IP's there's nothing that can be updated
+ if not port["fixed_ips"]:
+ return
+ network_id = port['network_id']
+ subnet_id = port["fixed_ips"][0]['subnet_id']
+ filters = {'network_id': [network_id]}
+ # Because NVP does not support updating a single host entry we
+ # got to build the whole list from scratch and update in bulk
+ ports = self.plugin.get_ports(context, filters)
+ if not ports:
+ return
+ dhcp_conf = [
+ {'mac_address': p['mac_address'],
+ 'ip_address': p["fixed_ips"][0]['ip_address']}
+ for p in ports if is_user_port(p)
+ ]
+ meta_conf = [
+ {'instance_id': p['device_id'],
+ 'ip_address': p["fixed_ips"][0]['ip_address']}
+ for p in ports if is_user_port(p, check_dev_id=True)
+ ]
+ self.lsn_manager.lsn_port_update(
+ context, network_id, subnet_id, dhcp=dhcp_conf, meta=meta_conf)
def _subnet_create(self, context, subnet, clean_on_err=True):
if subnet['enable_dhcp']:
}
try:
# This will end up calling handle_port_dhcp_access
- # down below
+ # down below as well as handle_port_metadata_access
self.plugin.create_port(context, {'port': dhcp_port})
except p_exc.PortConfigurationError as e:
err_msg = (_("Error while creating subnet %(cidr)s for "
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
+ # check if the subnet was uplinked onto a router, and if so
+ # remove the patch attachment between the metadata port and
+ # the lsn port, in favor on the one we'll be creating during
+ # _subnet_create
+ self.lsn_manager.lsn_port_dispose(
+ context, network_id, METADATA_MAC)
+ # also, check that a dhcp port exists first and provision it
# accordingly
filters = dict(network_id=[network_id],
device_owner=[const.DEVICE_OWNER_DHCP])
ports = self.plugin.get_ports(context, filters=filters)
if ports:
# This will end up calling handle_port_dhcp_access
- # down below
+ # down below as well as handle_port_metadata_access
self.plugin.delete_port(context, ports[0]['id'])
+def is_user_port(p, check_dev_id=False):
+ usable = p['fixed_ips'] and p['device_owner'] not in SPECIAL_OWNERS
+ return usable if not check_dev_id else usable and p['device_id']
+
+
def check_services_requirements(cluster):
ver = cluster.api_client.get_nvp_version()
# It sounds like 4.1 is the first one where DHCP in NSX/NVP
# 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"))
+ LOG.info(_("DHCP is disabled for subnet %s: nothing "
+ "to do"), subnet_id)
return
host_data = {
"mac_address": port["mac_address"],
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_port_metadata_access(plugin, context, port, is_delete=False):
+ if is_user_port(port, check_dev_id=True):
+ network_id = port["network_id"]
+ network = plugin.get_network(context, network_id)
+ if network[external_net.EXTERNAL]:
+ LOG.info(_("Network %s is external: nothing to do"), network_id)
+ return
+ subnet_id = port["fixed_ips"][0]['subnet_id']
+ host_data = {
+ "instance_id": port["device_id"],
+ "tenant_id": port["tenant_id"],
+ "ip_address": port["fixed_ips"][0]['ip_address']
+ }
+ LOG.info(_("Configuring metadata entry for port %s"), port)
+ if not is_delete:
+ handler = plugin.lsn_manager.lsn_port_meta_host_add
+ else:
+ handler = plugin.lsn_manager.lsn_port_meta_host_remove
+ try:
+ handler(context, network_id, subnet_id, host_data)
+ except p_exc.PortConfigurationError:
+ if not is_delete:
+ db_base_plugin_v2.NeutronDbPluginV2.delete_port(
+ plugin, context, port['id'])
+ raise
+ LOG.info(_("Metadata for port %s configured successfully"), port['id'])
-def handle_router_metadata_access(plugin, context, router_id, do_create=True):
- # TODO(armando-migliaccio)
- LOG.info('%s router %s' % (do_create, router_id))
+def handle_router_metadata_access(plugin, context, router_id, interface=None):
+ LOG.info(_("Handle metadata access via router: %(r)s and "
+ "interface %(i)s") % {'r': router_id, 'i': interface})
+ if interface:
+ try:
+ plugin.get_port(context, interface['port_id'])
+ is_enabled = True
+ except n_exc.NotFound:
+ is_enabled = False
+ subnet_id = interface['subnet_id']
+ try:
+ plugin.lsn_manager.lsn_metadata_configure(
+ context, subnet_id, is_enabled)
+ except p_exc.NvpPluginException:
+ if is_enabled:
+ l3_db.L3_NAT_db_mixin.remove_router_interface(
+ plugin, context, router_id, interface)
+ raise
+ LOG.info(_("Metadata for router %s handled successfully"), router_id)
_notify_rpc_agent(context, {'subnet': subnet}, 'subnet.update.end')
-def handle_port_metadata_access(context, port, is_delete=False):
+def handle_port_metadata_access(plugin, 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:
context.session.add(route)
-def handle_router_metadata_access(plugin, context, router_id, do_create=True):
+def handle_router_metadata_access(plugin, context, router_id, interface=None):
if cfg.CONF.NVP.metadata_mode != config.MetadataModes.DIRECT:
LOG.debug(_("Metadata access network is disabled"))
return
plugin, ctx_elevated, filters=device_filter)
try:
if ports:
- if (do_create and
+ if (interface and
not _find_metadata_port(plugin, ctx_elevated, ports)):
_create_metadata_access_network(
plugin, ctx_elevated, router_id)
self.supported_extension_aliases.remove(
"dhcp_agent_scheduler")
nvp_svc.register_dhcp_opts(cfg)
+ nvp_svc.register_metadata_opts(cfg)
self.lsn_manager = nvp_svc.LsnManager(self)
self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
nvp_svc.DhcpAgentNotifyAPI(self, self.lsn_manager))
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)
+ self.handle_port_metadata_access_delegate(self, context,
+ port, is_delete)
def handle_router_metadata_access(self, context,
- router_id, do_create=True):
+ router_id, interface=None):
self.handle_metadata_access_delegate(self, context,
- router_id, do_create)
+ router_id, interface)
SERVICECLUSTER_RESOURCE = "service-cluster"
LSERVICESNODE_RESOURCE = "lservices-node"
LSERVICESNODEPORT_RESOURCE = "lport/%s" % LSERVICESNODE_RESOURCE
+SUPPORTED_METADATA_OPTIONS = ['metadata_proxy_shared_secret']
LOG = log.getLogger(__name__)
cluster=cluster)
+def lsn_port_host_entries_update(
+ cluster, lsn_id, lsn_port_id, conf, hosts_data):
+ hosts_obj = {'hosts': hosts_data}
+ do_request(HTTP_PUT,
+ _build_uri_path(LSERVICESNODEPORT_RESOURCE,
+ parent_resource_id=lsn_id,
+ resource_id=lsn_port_id,
+ extra_action=conf),
+ json.dumps(hosts_obj),
+ cluster=cluster)
+
+
def lsn_port_create(cluster, lsn_id, port_data):
port_obj = {
"ip_address": port_data["ip_address"],
raise nvp_exc.LsnConfigurationConflict(lsn_id=lsn_id)
+def _lsn_configure_action(
+ cluster, lsn_id, action, is_enabled, obj):
+ lsn_obj = {"enabled": is_enabled}
+ lsn_obj.update(obj)
+ do_request(HTTP_PUT,
+ _build_uri_path(LSERVICESNODE_RESOURCE,
+ resource_id=lsn_id,
+ extra_action=action),
+ json.dumps(lsn_obj),
+ cluster=cluster)
+
+
def _lsn_port_configure_action(
cluster, lsn_id, lsn_port_id, action, is_enabled, obj):
do_request(HTTP_PUT,
cluster, lsn_id, lsn_port_id, 'dhcp', is_enabled, dhcp_obj)
+def lsn_metadata_configure(
+ cluster, lsn_id, is_enabled=True, metadata_info=None):
+ opts = [
+ "%s=%s" % (opt, metadata_info[opt])
+ for opt in SUPPORTED_METADATA_OPTIONS
+ if metadata_info.get(opt)
+ ]
+ meta_obj = {
+ 'metadata_server_ip': metadata_info['metadata_server_ip'],
+ 'metadata_server_port': metadata_info['metadata_server_port'],
+ 'misc_options': opts
+ }
+ _lsn_configure_action(
+ cluster, lsn_id, 'metadata-proxy', is_enabled, meta_obj)
+
+
def _lsn_port_host_action(
cluster, lsn_id, lsn_port_id, host_obj, extra_action, action):
do_request(HTTP_POST,
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')
+
+
+def lsn_port_metadata_host_add(cluster, lsn_id, lsn_port_id, host_data):
+ _lsn_port_host_action(
+ cluster, lsn_id, lsn_port_id, host_data, 'metadata-proxy', 'add_host')
+
+
+def lsn_port_metadata_host_remove(cluster, lsn_id, lsn_port_id, host_data):
+ _lsn_port_host_action(cluster, lsn_id, lsn_port_id,
+ host_data, 'metadata-proxy', 'remove_host')
self.lsn_id = 'foo_lsn_id'
self.mac = 'aa:bb:cc:dd:ee:ff'
self.lsn_port_id = 'foo_lsn_port_id'
+ self.tenant_id = 'foo_tenant_id'
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)
+ nvp.register_metadata_opts(cfg)
self.addCleanup(cfg.CONF.reset)
self.addCleanup(self.mock_lsn_api_p.stop)
self._test_lsn_port_dhcp_configure_with_subnet(
expected, routes=['8.8.8.8', '9.9.9.9'])
+ def _test_lsn_metadata_configure(self, is_enabled):
+ with mock.patch.object(self.manager, 'lsn_port_dispose') as f:
+ self.manager.plugin.get_subnet.return_value = (
+ {'network_id': self.net_id})
+ self.manager.lsn_metadata_configure(mock.ANY,
+ self.sub_id, is_enabled)
+ expected = {
+ 'metadata_server_port': 8775,
+ 'metadata_server_ip': '127.0.0.1',
+ 'metadata_proxy_shared_secret': ''
+ }
+ self.mock_lsn_api.lsn_metadata_configure.assert_called_once_with(
+ mock.ANY, mock.ANY, is_enabled, expected)
+ if is_enabled:
+ self.assertEqual(
+ 1, self.mock_lsn_api.lsn_port_by_subnet_get.call_count)
+ else:
+ self.assertEqual(1, f.call_count)
+
+ def test_lsn_metadata_configure_enabled(self):
+ self._test_lsn_metadata_configure(True)
+
+ def test_lsn_metadata_configure_disabled(self):
+ self._test_lsn_metadata_configure(False)
+
+ def test_lsn_metadata_configure_not_found(self):
+ self.mock_lsn_api.lsn_metadata_configure.side_effect = (
+ p_exc.LsnNotFound(entity='lsn', entity_id=self.lsn_id))
+ self.manager.plugin.get_subnet.return_value = (
+ {'network_id': self.net_id})
+ self.assertRaises(p_exc.NvpPluginException,
+ self.manager.lsn_metadata_configure,
+ mock.ANY, self.sub_id, True)
+
+ def test_lsn_port_metadata_setup(self):
+ subnet = {
+ 'cidr': '0.0.0.0/0',
+ 'id': self.sub_id,
+ 'network_id': self.net_id,
+ 'tenant_id': self.tenant_id
+ }
+ with mock.patch.object(nvp.nvplib, 'create_lport') as f:
+ f.return_value = {'uuid': self.port_id}
+ self.manager.lsn_port_metadata_setup(mock.ANY, self.lsn_id, subnet)
+ self.assertEqual(1, self.mock_lsn_api.lsn_port_create.call_count)
+ self.mock_lsn_api.lsn_port_plug_network.assert_called_once_with(
+ mock.ANY, self.lsn_id, mock.ANY, self.port_id)
+
+ def test_lsn_port_metadata_setup_raise_not_found(self):
+ subnet = {
+ 'cidr': '0.0.0.0/0',
+ 'id': self.sub_id,
+ 'network_id': self.net_id,
+ 'tenant_id': self.tenant_id
+ }
+ with mock.patch.object(nvp.nvplib, 'create_lport') as f:
+ f.side_effect = n_exc.NotFound
+ self.assertRaises(p_exc.PortConfigurationError,
+ self.manager.lsn_port_metadata_setup,
+ mock.ANY, self.lsn_id, subnet)
+
+ def test_lsn_port_metadata_setup_raise_conflict(self):
+ subnet = {
+ 'cidr': '0.0.0.0/0',
+ 'id': self.sub_id,
+ 'network_id': self.net_id,
+ 'tenant_id': self.tenant_id
+ }
+ with mock.patch.object(nvp.nvplib, 'create_lport') as f:
+ with mock.patch.object(nvp.nvplib, 'delete_port') as g:
+ f.return_value = {'uuid': self.port_id}
+ self.mock_lsn_api.lsn_port_plug_network.side_effect = (
+ p_exc.LsnConfigurationConflict(lsn_id=self.lsn_id))
+ self.assertRaises(p_exc.PortConfigurationError,
+ self.manager.lsn_port_metadata_setup,
+ mock.ANY, self.lsn_id, subnet)
+ self.assertEqual(1,
+ self.mock_lsn_api.lsn_port_delete.call_count)
+ self.assertEqual(1, g.call_count)
+
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',
self._test_lsn_port_dispose_with_values(
self.lsn_id, self.lsn_port_id, 1)
+ def test_lsn_port_dispose_meta_mac(self):
+ self.mac = nvp.METADATA_MAC
+ with mock.patch.object(nvp.nvplib, 'get_port_by_neutron_tag') as f:
+ with mock.patch.object(nvp.nvplib, 'delete_port') as g:
+ f.return_value = {'uuid': self.port_id}
+ self._test_lsn_port_dispose_with_values(
+ self.lsn_id, self.lsn_port_id, 1)
+ f.assert_called_once_with(
+ mock.ANY, self.net_id, nvp.METADATA_PORT_ID)
+ g.assert_called_once_with(mock.ANY, self.net_id, self.port_id)
+
def test_lsn_port_dispose_lsn_not_found(self):
self._test_lsn_port_dispose_with_values(None, None, 0)
self.manager._lsn_port_host_conf, mock.ANY,
self.net_id, self.sub_id, mock.ANY, mock.Mock())
+ def _test_lsn_port_update(self, dhcp=None, meta=None):
+ self.manager.lsn_port_update(
+ mock.ANY, self.net_id, self.sub_id, dhcp, meta)
+ count = 1 if dhcp else 0
+ count = count + 1 if meta else count
+ self.assertEqual(count, (self.mock_lsn_api.
+ lsn_port_host_entries_update.call_count))
+
+ def test_lsn_port_update(self):
+ self._test_lsn_port_update()
+
+ def test_lsn_port_update_dhcp_meta(self):
+ self._test_lsn_port_update(mock.ANY, mock.ANY)
+
+ def test_lsn_port_update_dhcp_and_nometa(self):
+ self._test_lsn_port_update(mock.ANY, None)
+
+ def test_lsn_port_update_nodhcp_and_nmeta(self):
+ self._test_lsn_port_update(None, mock.ANY)
+
+ def test_lsn_port_update_raise_error(self):
+ self.mock_lsn_api.lsn_port_host_entries_update.side_effect = (
+ NvpApiException)
+ self.assertRaises(p_exc.PortConfigurationError,
+ self.manager.lsn_port_update,
+ mock.ANY, mock.ANY, mock.ANY, mock.ANY)
+
class DhcpAgentNotifyAPITestCase(base.BaseTestCase):
self.plugin = self.notifier.plugin
self.lsn_manager = self.notifier.lsn_manager
+ def _test_notify_port_update(
+ self, ports, expected_count, expected_args=None):
+ port = {
+ 'id': 'foo_port_id',
+ 'network_id': 'foo_network_id',
+ 'fixed_ips': [{'subnet_id': 'foo_subnet_id'}]
+ }
+ self.notifier.plugin.get_ports.return_value = ports
+ self.notifier.notify(mock.ANY, {'port': port}, 'port.update.end')
+ self.lsn_manager.lsn_port_update.assert_has_calls(expected_args)
+
+ def test_notify_ports_update_no_ports(self):
+ self._test_notify_port_update(None, 0, [])
+ self._test_notify_port_update([], 0, [])
+
+ def test_notify_ports_update_one_port(self):
+ ports = [{
+ 'fixed_ips': [{'subnet_id': 'foo_subnet_id',
+ 'ip_address': '1.2.3.4'}],
+ 'device_id': 'foo_device_id',
+ 'device_owner': 'foo_device_owner',
+ 'mac_address': 'fa:16:3e:da:1d:46'
+ }]
+ call_args = mock.call(
+ mock.ANY, 'foo_network_id', 'foo_subnet_id',
+ dhcp=[{'ip_address': '1.2.3.4',
+ 'mac_address': 'fa:16:3e:da:1d:46'}],
+ meta=[{'instance_id': 'foo_device_id',
+ 'ip_address': '1.2.3.4'}])
+ self._test_notify_port_update(ports, 1, call_args)
+
+ def test_notify_ports_update_ports_with_empty_device_id(self):
+ ports = [{
+ 'fixed_ips': [{'subnet_id': 'foo_subnet_id',
+ 'ip_address': '1.2.3.4'}],
+ 'device_id': '',
+ 'device_owner': 'foo_device_owner',
+ 'mac_address': 'fa:16:3e:da:1d:46'
+ }]
+ call_args = mock.call(
+ mock.ANY, 'foo_network_id', 'foo_subnet_id',
+ dhcp=[{'ip_address': '1.2.3.4',
+ 'mac_address': 'fa:16:3e:da:1d:46'}],
+ meta=[]
+ )
+ self._test_notify_port_update(ports, 1, call_args)
+
+ def test_notify_ports_update_ports_with_no_fixed_ips(self):
+ ports = [{
+ 'fixed_ips': [],
+ 'device_id': 'foo_device_id',
+ 'device_owner': 'foo_device_owner',
+ 'mac_address': 'fa:16:3e:da:1d:46'
+ }]
+ call_args = mock.call(
+ mock.ANY, 'foo_network_id', 'foo_subnet_id', dhcp=[], meta=[])
+ self._test_notify_port_update(ports, 1, call_args)
+
+ def test_notify_ports_update_ports_with_no_fixed_ips_and_no_device(self):
+ ports = [{
+ 'fixed_ips': [],
+ 'device_id': '',
+ 'device_owner': 'foo_device_owner',
+ 'mac_address': 'fa:16:3e:da:1d:46'
+ }]
+ call_args = mock.call(
+ mock.ANY, 'foo_network_id', 'foo_subnet_id', dhcp=[], meta=[])
+ self._test_notify_port_update(ports, 0, call_args)
+
+ def test_notify_ports_update_with_special_ports(self):
+ ports = [{'fixed_ips': [],
+ 'device_id': '',
+ 'device_owner': 'network:dhcp',
+ 'mac_address': 'fa:16:3e:da:1d:46'},
+ {'fixed_ips': [{'subnet_id': 'foo_subnet_id',
+ 'ip_address': '1.2.3.4'}],
+ 'device_id': 'foo_device_id',
+ 'device_owner': 'network:router_gateway',
+ 'mac_address': 'fa:16:3e:da:1d:46'}]
+ call_args = mock.call(
+ mock.ANY, 'foo_network_id', 'foo_subnet_id', dhcp=[], meta=[])
+ self._test_notify_port_update(ports, 0, call_args)
+
+ def test_notify_ports_update_many_ports(self):
+ ports = [{'fixed_ips': [],
+ 'device_id': '',
+ 'device_owner': 'foo_device_owner',
+ 'mac_address': 'fa:16:3e:da:1d:46'},
+ {'fixed_ips': [{'subnet_id': 'foo_subnet_id',
+ 'ip_address': '1.2.3.4'}],
+ 'device_id': 'foo_device_id',
+ 'device_owner': 'foo_device_owner',
+ 'mac_address': 'fa:16:3e:da:1d:46'}]
+ call_args = mock.call(
+ mock.ANY, 'foo_network_id', 'foo_subnet_id',
+ dhcp=[{'ip_address': '1.2.3.4',
+ 'mac_address': 'fa:16:3e:da:1d:46'}],
+ meta=[{'instance_id': 'foo_device_id',
+ 'ip_address': '1.2.3.4'}])
+ self._test_notify_port_update(ports, 1, call_args)
+
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
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)
+
+
+class MetadataTestCase(base.BaseTestCase):
+
+ def setUp(self):
+ super(MetadataTestCase, self).setUp()
+ self.plugin = mock.Mock()
+ self.plugin.lsn_manager = mock.Mock()
+
+ def _test_handle_port_metadata_access_special_owners(
+ self, owner, dev_id='foo_device_id', ips=None):
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': owner,
+ 'device_id': dev_id,
+ 'fixed_ips': ips or []
+ }
+ nvp.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY)
+ self.assertFalse(
+ self.plugin.lsn_manager.lsn_port_meta_host_add.call_count)
+ self.assertFalse(
+ self.plugin.lsn_manager.lsn_port_meta_host_remove.call_count)
+
+ def test_handle_port_metadata_access_external_network(self):
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': 'foo_device_owner',
+ 'device_id': 'foo_device_id',
+ 'network_id': 'foo_network_id',
+ 'fixed_ips': [{'subnet_id': 'foo_subnet'}]
+ }
+ self.plugin.get_network.return_value = {'router:external': True}
+ nvp.handle_port_metadata_access(self.plugin, mock.ANY, port, mock.ANY)
+ self.assertFalse(
+ self.plugin.lsn_manager.lsn_port_meta_host_add.call_count)
+ self.assertFalse(
+ self.plugin.lsn_manager.lsn_port_meta_host_remove.call_count)
+
+ def test_handle_port_metadata_access_dhcp_port(self):
+ self._test_handle_port_metadata_access_special_owners(
+ 'network:dhcp', [{'subnet_id': 'foo_subnet'}])
+
+ def test_handle_port_metadata_access_router_port(self):
+ self._test_handle_port_metadata_access_special_owners(
+ 'network:router_interface', [{'subnet_id': 'foo_subnet'}])
+
+ def test_handle_port_metadata_access_no_device_id(self):
+ self._test_handle_port_metadata_access_special_owners(
+ 'network:dhcp', '')
+
+ def test_handle_port_metadata_access_no_fixed_ips(self):
+ self._test_handle_port_metadata_access_special_owners(
+ 'foo', 'foo', None)
+
+ def _test_handle_port_metadata_access(self, is_delete, raise_exc=False):
+ port = {
+ 'id': 'foo_port_id',
+ 'device_owner': 'foo_device_id',
+ 'network_id': 'foo_network_id',
+ 'device_id': 'foo_device_id',
+ 'tenant_id': 'foo_tenant_id',
+ 'fixed_ips': [
+ {'subnet_id': 'foo_subnet_id', 'ip_address': '1.2.3.4'}
+ ]
+ }
+ meta = {
+ 'instance_id': port['device_id'],
+ 'tenant_id': port['tenant_id'],
+ 'ip_address': port['fixed_ips'][0]['ip_address']
+ }
+ self.plugin.get_network.return_value = {'router:external': False}
+ if is_delete:
+ mock_func = self.plugin.lsn_manager.lsn_port_meta_host_remove
+ else:
+ mock_func = self.plugin.lsn_manager.lsn_port_meta_host_add
+ if raise_exc:
+ mock_func.side_effect = p_exc.PortConfigurationError(
+ lsn_id='foo_lsn_id', net_id='foo_net_id', port_id=None)
+ with mock.patch.object(nvp.db_base_plugin_v2.NeutronDbPluginV2,
+ 'delete_port') as d:
+ self.assertRaises(p_exc.PortConfigurationError,
+ nvp.handle_port_metadata_access,
+ self.plugin, mock.ANY, port,
+ is_delete=is_delete)
+ if not is_delete:
+ d.assert_called_once_with(mock.ANY, mock.ANY, port['id'])
+ else:
+ self.assertFalse(d.call_count)
+ else:
+ nvp.handle_port_metadata_access(
+ self.plugin, mock.ANY, port, is_delete=is_delete)
+ mock_func.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY, meta)
+
+ def test_handle_port_metadata_access_on_delete_true(self):
+ self._test_handle_port_metadata_access(True)
+
+ def test_handle_port_metadata_access_on_delete_false(self):
+ self._test_handle_port_metadata_access(False)
+
+ def test_handle_port_metadata_access_on_delete_true_raise(self):
+ self._test_handle_port_metadata_access(True, raise_exc=True)
+
+ def test_handle_port_metadata_access_on_delete_false_raise(self):
+ self._test_handle_port_metadata_access(False, raise_exc=True)
+
+ def _test_handle_router_metadata_access(
+ self, is_port_found, raise_exc=False):
+ subnet = {
+ 'id': 'foo_subnet_id',
+ 'network_id': 'foo_network_id'
+ }
+ interface = {
+ 'subnet_id': subnet['id'],
+ 'port_id': 'foo_port_id'
+ }
+ mock_func = self.plugin.lsn_manager.lsn_metadata_configure
+ if not is_port_found:
+ self.plugin.get_port.side_effect = n_exc.NotFound
+ if raise_exc:
+ with mock.patch.object(nvp.l3_db.L3_NAT_db_mixin,
+ 'remove_router_interface') as d:
+ mock_func.side_effect = p_exc.NvpPluginException(err_msg='')
+ self.assertRaises(p_exc.NvpPluginException,
+ nvp.handle_router_metadata_access,
+ self.plugin, mock.ANY, 'foo_router_id',
+ interface)
+ d.assert_called_once_with(mock.ANY, mock.ANY, 'foo_router_id',
+ interface)
+ else:
+ nvp.handle_router_metadata_access(
+ self.plugin, mock.ANY, 'foo_router_id', interface)
+ mock_func.assert_called_once_with(
+ mock.ANY, subnet['id'], is_port_found)
+
+ def test_handle_router_metadata_access_add_interface(self):
+ self._test_handle_router_metadata_access(True)
+
+ def test_handle_router_metadata_access_delete_interface(self):
+ self._test_handle_router_metadata_access(False)
+
+ def test_handle_router_metadata_access_raise_error_on_add(self):
+ self._test_handle_router_metadata_access(True, raise_exc=True)
+
+ def test_handle_router_metadata_access_raise_error_on_delete(self):
+ self._test_handle_router_metadata_access(True, raise_exc=False)
"DELETE",
"/ws.v1/lservices-node/%s" % lsn_id, cluster=self.cluster)
+ def _test_lsn_port_host_entries_update(self, lsn_type, hosts_data):
+ lsn_id = 'foo_lsn_id'
+ lsn_port_id = 'foo_lsn_port_id'
+ lsnlib.lsn_port_host_entries_update(
+ self.cluster, lsn_id, lsn_port_id, lsn_type, hosts_data)
+ self.mock_request.assert_called_once_with(
+ 'PUT',
+ '/ws.v1/lservices-node/%s/lport/%s/%s' % (lsn_id,
+ lsn_port_id,
+ lsn_type),
+ json.dumps({'hosts': hosts_data}),
+ cluster=self.cluster)
+
+ def test_lsn_port_dhcp_entries_update(self):
+ hosts_data = [{"ip_address": "11.22.33.44",
+ "mac_address": "aa:bb:cc:dd:ee:ff"},
+ {"ip_address": "44.33.22.11",
+ "mac_address": "ff:ee:dd:cc:bb:aa"}]
+ self._test_lsn_port_host_entries_update("dhcp", hosts_data)
+
+ def test_lsn_port_metadata_entries_update(self):
+ hosts_data = [{"ip_address": "11.22.33.44",
+ "device_id": "foo_vm_uuid"}]
+ self._test_lsn_port_host_entries_update("metadata-proxy", hosts_data)
+
def test_lsn_port_create(self):
port_data = {
"ip_address": "1.2.3.0/24",
self._test_lsn_port_dhcp_configure(
lsn_id, lsn_port_id, is_enabled, opts)
+ def _test_lsn_metadata_configure(
+ self, lsn_id, is_enabled, opts, expected_opts):
+ lsnlib.lsn_metadata_configure(
+ self.cluster, lsn_id, is_enabled, opts)
+ lsn_obj = {"enabled": is_enabled}
+ lsn_obj.update(expected_opts)
+ self.mock_request.assert_has_calls([
+ mock.call("PUT",
+ "/ws.v1/lservices-node/%s/metadata-proxy" % lsn_id,
+ json.dumps(lsn_obj),
+ cluster=self.cluster),
+ ])
+
+ def test_lsn_port_metadata_configure_empty_secret(self):
+ lsn_id = "foo_lsn_id"
+ is_enabled = True
+ opts = {
+ "metadata_server_ip": "1.2.3.4",
+ "metadata_server_port": "8775"
+ }
+ expected_opts = {
+ "metadata_server_ip": "1.2.3.4",
+ "metadata_server_port": "8775",
+ "misc_options": []
+ }
+ self._test_lsn_metadata_configure(
+ lsn_id, is_enabled, opts, expected_opts)
+
+ def test_lsn_metadata_configure_with_secret(self):
+ lsn_id = "foo_lsn_id"
+ is_enabled = True
+ opts = {
+ "metadata_server_ip": "1.2.3.4",
+ "metadata_server_port": "8775",
+ "metadata_proxy_shared_secret": "foo_secret"
+ }
+ expected_opts = {
+ "metadata_server_ip": "1.2.3.4",
+ "metadata_server_port": "8775",
+ "misc_options": ["metadata_proxy_shared_secret=foo_secret"]
+ }
+ self._test_lsn_metadata_configure(
+ lsn_id, is_enabled, opts, expected_opts)
+
def _test_lsn_port_host_action(
self, lsn_port_action_func, extra_action, action, host):
lsn_id = "foo_lsn_id"
}
self._test_lsn_port_host_action(
lsnlib.lsn_port_dhcp_host_remove, "dhcp", "remove_host", host)
+
+ def test_lsn_port_metadata_host_add(self):
+ host = {
+ "ip_address": "1.2.3.4",
+ "instance_id": "foo_instance_id"
+ }
+ self._test_lsn_port_host_action(lsnlib.lsn_port_metadata_host_add,
+ "metadata-proxy", "add_host", host)
+
+ def test_lsn_port_metadata_host_remove(self):
+ host = {
+ "ip_address": "1.2.3.4",
+ "instance_id": "foo_instance_id"
+ }
+ self._test_lsn_port_host_action(lsnlib.lsn_port_metadata_host_remove,
+ "metadata-proxy", "remove_host", host)