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:
parent
314aaabddb
commit
5d7e467631
@ -169,3 +169,15 @@
|
|||||||
|
|
||||||
# Default DHCP lease time
|
# Default DHCP lease time
|
||||||
# default_lease_time = 43200
|
# 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 =
|
||||||
|
@ -1552,7 +1552,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
|||||||
# router, but if it does, it should not happen within a
|
# router, but if it does, it should not happen within a
|
||||||
# transaction, and it should be restored on rollback
|
# transaction, and it should be restored on rollback
|
||||||
self.handle_router_metadata_access(
|
self.handle_router_metadata_access(
|
||||||
context, router_id, do_create=False)
|
context, router_id, interface=None)
|
||||||
# Pre-delete checks
|
# Pre-delete checks
|
||||||
# NOTE(salv-orlando): These checks will be repeated anyway when
|
# NOTE(salv-orlando): These checks will be repeated anyway when
|
||||||
# calling the superclass. This is wasteful, but is the simplest
|
# 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'
|
# Ensure the NVP logical router has a connection to a 'metadata access'
|
||||||
# network (with a proxy listening on its DHCP port), by creating it
|
# network (with a proxy listening on its DHCP port), by creating it
|
||||||
# if needed.
|
# 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 "
|
LOG.debug(_("Add_router_interface completed for subnet:%(subnet_id)s "
|
||||||
"and router:%(router_id)s"),
|
"and router:%(router_id)s"),
|
||||||
{'subnet_id': subnet_id, 'router_id': router_id})
|
{'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'
|
# Ensure the connection to the 'metadata access network'
|
||||||
# is removed (with the network) if this the last subnet
|
# is removed (with the network) if this the last subnet
|
||||||
# on the router
|
# on the router
|
||||||
self.handle_router_metadata_access(context, router_id)
|
self.handle_router_metadata_access(
|
||||||
|
context, router_id, interface=info)
|
||||||
try:
|
try:
|
||||||
if not subnet:
|
if not subnet:
|
||||||
subnet = self._get_subnet(context, subnet_id)
|
subnet = self._get_subnet(context, subnet_id)
|
||||||
|
@ -22,6 +22,8 @@ from neutron.api.v2 import attributes as attr
|
|||||||
from neutron.common import constants as const
|
from neutron.common import constants as const
|
||||||
from neutron.common import exceptions as n_exc
|
from neutron.common import exceptions as n_exc
|
||||||
from neutron.db import db_base_plugin_v2
|
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.openstack.common import log as logging
|
||||||
from neutron.plugins.nicira.common import exceptions as p_exc
|
from neutron.plugins.nicira.common import exceptions as p_exc
|
||||||
from neutron.plugins.nicira.nsxlib import lsn as lsn_api
|
from neutron.plugins.nicira.nsxlib import lsn as lsn_api
|
||||||
@ -29,7 +31,17 @@ from neutron.plugins.nicira import nvplib
|
|||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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 = [
|
dhcp_opts = [
|
||||||
cfg.ListOpt('extra_domain_name_servers',
|
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):
|
def register_dhcp_opts(config):
|
||||||
config.CONF.register_opts(dhcp_opts, "NVP_DHCP")
|
config.CONF.register_opts(dhcp_opts, "NVP_DHCP")
|
||||||
|
|
||||||
|
|
||||||
|
def register_metadata_opts(config):
|
||||||
|
config.CONF.register_opts(metadata_opts, "NVP_METADATA")
|
||||||
|
|
||||||
|
|
||||||
class LsnManager(object):
|
class LsnManager(object):
|
||||||
"""Manage LSN entities associated with networks."""
|
"""Manage LSN entities associated with networks."""
|
||||||
|
|
||||||
@ -161,6 +190,24 @@ class LsnManager(object):
|
|||||||
context, network_id, mac_address, raise_on_err=False)
|
context, network_id, mac_address, raise_on_err=False)
|
||||||
if lsn_port_id:
|
if lsn_port_id:
|
||||||
self.lsn_port_delete(context, lsn_id, 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(
|
def lsn_port_dhcp_setup(
|
||||||
self, context, network_id, port_id, port_data, subnet_config=None):
|
self, context, network_id, port_id, port_data, subnet_config=None):
|
||||||
@ -187,6 +234,36 @@ class LsnManager(object):
|
|||||||
else:
|
else:
|
||||||
return (lsn_id, lsn_port_id)
|
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):
|
def lsn_port_dhcp_configure(self, context, lsn_id, lsn_port_id, subnet):
|
||||||
"""Enable/disable dhcp services with the given config options."""
|
"""Enable/disable dhcp services with the given config options."""
|
||||||
is_enabled = subnet["enable_dhcp"]
|
is_enabled = subnet["enable_dhcp"]
|
||||||
@ -214,6 +291,36 @@ class LsnManager(object):
|
|||||||
LOG.error(err_msg)
|
LOG.error(err_msg)
|
||||||
raise p_exc.NvpPluginException(err_msg=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):
|
def _lsn_port_host_conf(self, context, network_id, subnet_id, data, hdlr):
|
||||||
lsn_id = None
|
lsn_id = None
|
||||||
lsn_port_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)
|
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):
|
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,
|
self._lsn_port_host_conf(context, network_id, subnet_id, host,
|
||||||
lsn_api.lsn_port_dhcp_host_add)
|
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,
|
self._lsn_port_host_conf(context, network_id, subnet_id, host,
|
||||||
lsn_api.lsn_port_dhcp_host_remove)
|
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):
|
class DhcpAgentNotifyAPI(object):
|
||||||
|
|
||||||
@ -251,6 +386,33 @@ class DhcpAgentNotifyAPI(object):
|
|||||||
[resource, action, _e] = methodname.split('.')
|
[resource, action, _e] = methodname.split('.')
|
||||||
if resource == 'subnet':
|
if resource == 'subnet':
|
||||||
self._handle_subnet_dhcp_access[action](context, data['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):
|
def _subnet_create(self, context, subnet, clean_on_err=True):
|
||||||
if subnet['enable_dhcp']:
|
if subnet['enable_dhcp']:
|
||||||
@ -268,7 +430,7 @@ class DhcpAgentNotifyAPI(object):
|
|||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
# This will end up calling handle_port_dhcp_access
|
# 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})
|
self.plugin.create_port(context, {'port': dhcp_port})
|
||||||
except p_exc.PortConfigurationError as e:
|
except p_exc.PortConfigurationError as e:
|
||||||
err_msg = (_("Error while creating subnet %(cidr)s for "
|
err_msg = (_("Error while creating subnet %(cidr)s for "
|
||||||
@ -292,7 +454,13 @@ class DhcpAgentNotifyAPI(object):
|
|||||||
context, lsn_id, lsn_port_id, subnet)
|
context, lsn_id, lsn_port_id, subnet)
|
||||||
except p_exc.LsnPortNotFound:
|
except p_exc.LsnPortNotFound:
|
||||||
# It's possible that the subnet was created with dhcp off;
|
# 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
|
# accordingly
|
||||||
filters = dict(network_id=[network_id],
|
filters = dict(network_id=[network_id],
|
||||||
device_owner=[const.DEVICE_OWNER_DHCP])
|
device_owner=[const.DEVICE_OWNER_DHCP])
|
||||||
@ -313,10 +481,15 @@ class DhcpAgentNotifyAPI(object):
|
|||||||
ports = self.plugin.get_ports(context, filters=filters)
|
ports = self.plugin.get_ports(context, filters=filters)
|
||||||
if ports:
|
if ports:
|
||||||
# This will end up calling handle_port_dhcp_access
|
# 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'])
|
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):
|
def check_services_requirements(cluster):
|
||||||
ver = cluster.api_client.get_nvp_version()
|
ver = cluster.api_client.get_nvp_version()
|
||||||
# It sounds like 4.1 is the first one where DHCP in NSX/NVP
|
# 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
|
# do something only if there are IP's and dhcp is enabled
|
||||||
subnet_id = port["fixed_ips"][0]['subnet_id']
|
subnet_id = port["fixed_ips"][0]['subnet_id']
|
||||||
if not plugin.get_subnet(context, subnet_id)['enable_dhcp']:
|
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
|
return
|
||||||
host_data = {
|
host_data = {
|
||||||
"mac_address": port["mac_address"],
|
"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'])
|
LOG.info(_("DHCP for port %s configured successfully"), port['id'])
|
||||||
|
|
||||||
|
|
||||||
def handle_port_metadata_access(context, port, is_delete=False):
|
def handle_port_metadata_access(plugin, context, port, is_delete=False):
|
||||||
# TODO(armando-migliaccio)
|
if is_user_port(port, check_dev_id=True):
|
||||||
LOG.info('%s port with data %s' % (is_delete, port))
|
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):
|
def handle_router_metadata_access(plugin, context, router_id, interface=None):
|
||||||
# TODO(armando-migliaccio)
|
LOG.info(_("Handle metadata access via router: %(r)s and "
|
||||||
LOG.info('%s router %s' % (do_create, router_id))
|
"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)
|
||||||
|
@ -80,7 +80,7 @@ def handle_port_dhcp_access(plugin, context, port_data, action):
|
|||||||
_notify_rpc_agent(context, {'subnet': subnet}, 'subnet.update.end')
|
_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
|
if (cfg.CONF.NVP.metadata_mode == config.MetadataModes.INDIRECT and
|
||||||
port.get('device_owner') == const.DEVICE_OWNER_DHCP):
|
port.get('device_owner') == const.DEVICE_OWNER_DHCP):
|
||||||
if port.get('fixed_ips', []) or is_delete:
|
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)
|
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:
|
if cfg.CONF.NVP.metadata_mode != config.MetadataModes.DIRECT:
|
||||||
LOG.debug(_("Metadata access network is disabled"))
|
LOG.debug(_("Metadata access network is disabled"))
|
||||||
return
|
return
|
||||||
@ -128,7 +128,7 @@ def handle_router_metadata_access(plugin, context, router_id, do_create=True):
|
|||||||
plugin, ctx_elevated, filters=device_filter)
|
plugin, ctx_elevated, filters=device_filter)
|
||||||
try:
|
try:
|
||||||
if ports:
|
if ports:
|
||||||
if (do_create and
|
if (interface and
|
||||||
not _find_metadata_port(plugin, ctx_elevated, ports)):
|
not _find_metadata_port(plugin, ctx_elevated, ports)):
|
||||||
_create_metadata_access_network(
|
_create_metadata_access_network(
|
||||||
plugin, ctx_elevated, router_id)
|
plugin, ctx_elevated, router_id)
|
||||||
|
@ -77,6 +77,7 @@ class DhcpMetadataAccess(object):
|
|||||||
self.supported_extension_aliases.remove(
|
self.supported_extension_aliases.remove(
|
||||||
"dhcp_agent_scheduler")
|
"dhcp_agent_scheduler")
|
||||||
nvp_svc.register_dhcp_opts(cfg)
|
nvp_svc.register_dhcp_opts(cfg)
|
||||||
|
nvp_svc.register_metadata_opts(cfg)
|
||||||
self.lsn_manager = nvp_svc.LsnManager(self)
|
self.lsn_manager = nvp_svc.LsnManager(self)
|
||||||
self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
|
self.agent_notifiers[const.AGENT_TYPE_DHCP] = (
|
||||||
nvp_svc.DhcpAgentNotifyAPI(self, self.lsn_manager))
|
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)
|
self.handle_port_dhcp_access_delegate(self, context, port_data, action)
|
||||||
|
|
||||||
def handle_port_metadata_access(self, context, port, is_delete=False):
|
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,
|
def handle_router_metadata_access(self, context,
|
||||||
router_id, do_create=True):
|
router_id, interface=None):
|
||||||
self.handle_metadata_access_delegate(self, context,
|
self.handle_metadata_access_delegate(self, context,
|
||||||
router_id, do_create)
|
router_id, interface)
|
||||||
|
@ -33,6 +33,7 @@ HTTP_PUT = "PUT"
|
|||||||
SERVICECLUSTER_RESOURCE = "service-cluster"
|
SERVICECLUSTER_RESOURCE = "service-cluster"
|
||||||
LSERVICESNODE_RESOURCE = "lservices-node"
|
LSERVICESNODE_RESOURCE = "lservices-node"
|
||||||
LSERVICESNODEPORT_RESOURCE = "lport/%s" % LSERVICESNODE_RESOURCE
|
LSERVICESNODEPORT_RESOURCE = "lport/%s" % LSERVICESNODE_RESOURCE
|
||||||
|
SUPPORTED_METADATA_OPTIONS = ['metadata_proxy_shared_secret']
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
@ -82,6 +83,18 @@ def lsn_delete(cluster, lsn_id):
|
|||||||
cluster=cluster)
|
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):
|
def lsn_port_create(cluster, lsn_id, port_data):
|
||||||
port_obj = {
|
port_obj = {
|
||||||
"ip_address": port_data["ip_address"],
|
"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)
|
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(
|
def _lsn_port_configure_action(
|
||||||
cluster, lsn_id, lsn_port_id, action, is_enabled, obj):
|
cluster, lsn_id, lsn_port_id, action, is_enabled, obj):
|
||||||
do_request(HTTP_PUT,
|
do_request(HTTP_PUT,
|
||||||
@ -179,6 +204,22 @@ def lsn_port_dhcp_configure(
|
|||||||
cluster, lsn_id, lsn_port_id, 'dhcp', is_enabled, dhcp_obj)
|
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(
|
def _lsn_port_host_action(
|
||||||
cluster, lsn_id, lsn_port_id, host_obj, extra_action, action):
|
cluster, lsn_id, lsn_port_id, host_obj, extra_action, action):
|
||||||
do_request(HTTP_POST,
|
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):
|
def lsn_port_dhcp_host_remove(cluster, lsn_id, lsn_port_id, host_data):
|
||||||
_lsn_port_host_action(
|
_lsn_port_host_action(
|
||||||
cluster, lsn_id, lsn_port_id, host_data, 'dhcp', 'remove_host')
|
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')
|
||||||
|
@ -36,10 +36,12 @@ class LsnManagerTestCase(base.BaseTestCase):
|
|||||||
self.lsn_id = 'foo_lsn_id'
|
self.lsn_id = 'foo_lsn_id'
|
||||||
self.mac = 'aa:bb:cc:dd:ee:ff'
|
self.mac = 'aa:bb:cc:dd:ee:ff'
|
||||||
self.lsn_port_id = 'foo_lsn_port_id'
|
self.lsn_port_id = 'foo_lsn_port_id'
|
||||||
|
self.tenant_id = 'foo_tenant_id'
|
||||||
self.manager = nvp.LsnManager(mock.Mock())
|
self.manager = nvp.LsnManager(mock.Mock())
|
||||||
self.mock_lsn_api_p = mock.patch.object(nvp, 'lsn_api')
|
self.mock_lsn_api_p = mock.patch.object(nvp, 'lsn_api')
|
||||||
self.mock_lsn_api = self.mock_lsn_api_p.start()
|
self.mock_lsn_api = self.mock_lsn_api_p.start()
|
||||||
nvp.register_dhcp_opts(cfg)
|
nvp.register_dhcp_opts(cfg)
|
||||||
|
nvp.register_metadata_opts(cfg)
|
||||||
self.addCleanup(cfg.CONF.reset)
|
self.addCleanup(cfg.CONF.reset)
|
||||||
self.addCleanup(self.mock_lsn_api_p.stop)
|
self.addCleanup(self.mock_lsn_api_p.stop)
|
||||||
|
|
||||||
@ -290,6 +292,86 @@ class LsnManagerTestCase(base.BaseTestCase):
|
|||||||
self._test_lsn_port_dhcp_configure_with_subnet(
|
self._test_lsn_port_dhcp_configure_with_subnet(
|
||||||
expected, routes=['8.8.8.8', '9.9.9.9'])
|
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):
|
def _test_lsn_port_dispose_with_values(self, lsn_id, lsn_port_id, count):
|
||||||
with mock.patch.object(self.manager,
|
with mock.patch.object(self.manager,
|
||||||
'lsn_port_get_by_mac',
|
'lsn_port_get_by_mac',
|
||||||
@ -302,6 +384,17 @@ class LsnManagerTestCase(base.BaseTestCase):
|
|||||||
self._test_lsn_port_dispose_with_values(
|
self._test_lsn_port_dispose_with_values(
|
||||||
self.lsn_id, self.lsn_port_id, 1)
|
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):
|
def test_lsn_port_dispose_lsn_not_found(self):
|
||||||
self._test_lsn_port_dispose_with_values(None, None, 0)
|
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.manager._lsn_port_host_conf, mock.ANY,
|
||||||
self.net_id, self.sub_id, mock.ANY, mock.Mock())
|
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):
|
class DhcpAgentNotifyAPITestCase(base.BaseTestCase):
|
||||||
|
|
||||||
@ -343,6 +463,107 @@ class DhcpAgentNotifyAPITestCase(base.BaseTestCase):
|
|||||||
self.plugin = self.notifier.plugin
|
self.plugin = self.notifier.plugin
|
||||||
self.lsn_manager = self.notifier.lsn_manager
|
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):
|
def _test_notify_subnet_action(self, action):
|
||||||
with mock.patch.object(self.notifier, '_subnet_%s' % action) as f:
|
with mock.patch.object(self.notifier, '_subnet_%s' % action) as f:
|
||||||
self.notifier._handle_subnet_dhcp_access[action] = 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):
|
def test_handle_delete_user_port_no_fixed_ips(self):
|
||||||
self._test_handle_user_port_no_fixed_ips(
|
self._test_handle_user_port_no_fixed_ips(
|
||||||
'delete_port', self.plugin.lsn_manager.lsn_port_dhcp_host_remove)
|
'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)
|
||||||
|
@ -112,6 +112,31 @@ class LSNTestCase(base.BaseTestCase):
|
|||||||
"DELETE",
|
"DELETE",
|
||||||
"/ws.v1/lservices-node/%s" % lsn_id, cluster=self.cluster)
|
"/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):
|
def test_lsn_port_create(self):
|
||||||
port_data = {
|
port_data = {
|
||||||
"ip_address": "1.2.3.0/24",
|
"ip_address": "1.2.3.0/24",
|
||||||
@ -230,6 +255,50 @@ class LSNTestCase(base.BaseTestCase):
|
|||||||
self._test_lsn_port_dhcp_configure(
|
self._test_lsn_port_dhcp_configure(
|
||||||
lsn_id, lsn_port_id, is_enabled, opts)
|
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(
|
def _test_lsn_port_host_action(
|
||||||
self, lsn_port_action_func, extra_action, action, host):
|
self, lsn_port_action_func, extra_action, action, host):
|
||||||
lsn_id = "foo_lsn_id"
|
lsn_id = "foo_lsn_id"
|
||||||
@ -256,3 +325,19 @@ class LSNTestCase(base.BaseTestCase):
|
|||||||
}
|
}
|
||||||
self._test_lsn_port_host_action(
|
self._test_lsn_port_host_action(
|
||||||
lsnlib.lsn_port_dhcp_host_remove, "dhcp", "remove_host", host)
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user