Add support for NSX/NVP Metadata services

This is a feature patch (2 of 3) that adds support for
Metadata services provided by the NSX (aka NVP) platform.

It also implements the handling of port events so that
dhcp and metadata configuration in NSX/NVP is updated
if port attributes such as fixed_ips and device_id are
updated.

Partial-implements blueprint nsx-integrated-services

Change-Id: Id2b9125b49c0e15e717605ec6ba3dea5d32ee755
This commit is contained in:
armando-migliaccio 2013-09-26 09:06:36 -07:00 committed by armando-migliaccio
parent 03bea60c9f
commit 8f3a54f047
8 changed files with 752 additions and 21 deletions

View File

@ -169,3 +169,15 @@
# 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 =

View File

@ -1552,7 +1552,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# 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
@ -1654,7 +1654,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# 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})
@ -1698,7 +1699,8 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
# 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)

View File

@ -22,6 +22,8 @@ from neutron.api.v2 import attributes as attr
from neutron.common import constants as const
from neutron.common import exceptions as n_exc
from neutron.db import db_base_plugin_v2
from neutron.db import l3_db
from neutron.extensions import external_net
from neutron.openstack.common import log as logging
from neutron.plugins.nicira.common import exceptions as p_exc
from neutron.plugins.nicira.nsxlib import lsn as lsn_api
@ -29,7 +31,17 @@ from neutron.plugins.nicira import nvplib
LOG = logging.getLogger(__name__)
# A unique MAC to quickly identify the LSN port used for metadata services
# when dhcp on the subnet is off. Inspired by leet-speak for 'metadata'.
METADATA_MAC = "fa:15:73:74:d4:74"
METADATA_PORT_ID = 'metadata:id'
METADATA_PORT_NAME = 'metadata:name'
METADATA_DEVICE_ID = 'metadata:device'
META_CONF = 'metadata-proxy'
DHCP_CONF = 'dhcp'
SPECIAL_OWNERS = (const.DEVICE_OWNER_DHCP,
const.DEVICE_OWNER_ROUTER_GW,
l3_db.DEVICE_OWNER_ROUTER_INTF)
dhcp_opts = [
cfg.ListOpt('extra_domain_name_servers',
@ -44,10 +56,27 @@ dhcp_opts = [
]
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."""
@ -161,6 +190,24 @@ class LsnManager(object):
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):
@ -187,6 +234,36 @@ class LsnManager(object):
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"]
@ -214,6 +291,36 @@ class LsnManager(object):
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
@ -228,7 +335,7 @@ class LsnManager(object):
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)
@ -237,6 +344,34 @@ class LsnManager(object):
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):
@ -251,6 +386,33 @@ 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']:
@ -268,7 +430,7 @@ class DhcpAgentNotifyAPI(object):
}
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 "
@ -292,7 +454,13 @@ class DhcpAgentNotifyAPI(object):
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])
@ -313,10 +481,15 @@ class DhcpAgentNotifyAPI(object):
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
@ -374,7 +547,8 @@ def handle_port_dhcp_access(plugin, context, port, action):
# 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"],
@ -395,11 +569,50 @@ def handle_port_dhcp_access(plugin, context, port, action):
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)

View File

@ -80,7 +80,7 @@ def handle_port_dhcp_access(plugin, context, port_data, action):
_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:
@ -112,7 +112,7 @@ def handle_port_metadata_access(context, port, is_delete=False):
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
@ -128,7 +128,7 @@ def handle_router_metadata_access(plugin, context, router_id, do_create=True):
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)

View File

@ -77,6 +77,7 @@ class DhcpMetadataAccess(object):
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))
@ -106,9 +107,10 @@ class DhcpMetadataAccess(object):
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)

View File

@ -33,6 +33,7 @@ HTTP_PUT = "PUT"
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__)
@ -82,6 +83,18 @@ def lsn_delete(cluster, lsn_id):
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"],
@ -151,6 +164,18 @@ def lsn_port_plug_network(cluster, lsn_id, lsn_port_id, lswitch_port_id):
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,
@ -179,6 +204,22 @@ def lsn_port_dhcp_configure(
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,
@ -199,3 +240,13 @@ def lsn_port_dhcp_host_add(cluster, lsn_id, lsn_port_id, host_data):
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')

View File

@ -36,10 +36,12 @@ class LsnManagerTestCase(base.BaseTestCase):
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)
@ -290,6 +292,86 @@ class LsnManagerTestCase(base.BaseTestCase):
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',
@ -302,6 +384,17 @@ class LsnManagerTestCase(base.BaseTestCase):
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)
@ -334,6 +427,33 @@ class LsnManagerTestCase(base.BaseTestCase):
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):
@ -343,6 +463,107 @@ 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
@ -631,3 +852,148 @@ class DhcpTestCase(base.BaseTestCase):
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)

View File

@ -112,6 +112,31 @@ class LSNTestCase(base.BaseTestCase):
"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",
@ -230,6 +255,50 @@ class LSNTestCase(base.BaseTestCase):
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"
@ -256,3 +325,19 @@ class LSNTestCase(base.BaseTestCase):
}
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)