Merge "Add support for NSX/NVP Metadata services"

This commit is contained in:
Jenkins 2013-12-18 21:53:48 +00:00 committed by Gerrit Code Review
commit 458a19a780
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)