l2pop fdb flows for HA router ports
This patch makes L3 HA failover not depended on neutron components (during failover). All HA agents(active and backup) call update_device_up/down after wiring the ports. But l2pop driver is called for only active agent as port binding in DB reflects active agent. Then l2pop creates unicast and multicast flows for active agent. On failover, flows to new active agent is created. For this to happen - all of database, messaging server, neutron-server and destination L3 agent should be active during failover. This creates two issues - 1) When any of the above resources(i.e neutron-server, .. ) are dead, flows between new master and other agents won't be created and L3 Ha failover is not working. In same scenario, L3 Ha failover will work if l2pop is disabled. 2) Packet loss during failover is higher as above neutron resources interact multiple times, so will take time to create l2 flows. In this change, we allow plugin to notify l2pop when update_device_up/down is called by backup agents also. Then l2pop will create flood flows to all HA agents(both active and slave). L2pop won't create unicast flow for this port, instead unicast flow is created by learning action of table 10 when keepalived sends GARP after assigning ip address to master router's qr-xx port. As flood flows are already created and unicast flow is dynamically added, L3 HA failover is not depended on l2pop. This solves two isses 1) with L3 HA + l2pop, failover will work even if any of above agents or processes dead. 2) Reduce failover time as we are not depending on neutron to create flows during failover. We use L3HARouterAgentPortBinding table for getting all HA agents of a router port. HA router port on slave agent is also considered for l2pop distributed_active_network_ports and agent_network_active_port_count Closes-bug: #1522980 Closes-bug: #1602614 Change-Id: Ie1f5289390b3ff3f7f3ed7ffc8f6a8258ee8662e
This commit is contained in:
parent
dc6e83771c
commit
26d8702b9d
|
@ -35,8 +35,10 @@ from neutron.api.v2 import attributes
|
||||||
from neutron.common import constants as n_const
|
from neutron.common import constants as n_const
|
||||||
from neutron.common import utils as n_utils
|
from neutron.common import utils as n_utils
|
||||||
from neutron.db import agents_db
|
from neutron.db import agents_db
|
||||||
|
from neutron.db import api as db_api
|
||||||
from neutron.db.availability_zone import router as router_az_db
|
from neutron.db.availability_zone import router as router_az_db
|
||||||
from neutron.db import common_db_mixin
|
from neutron.db import common_db_mixin
|
||||||
|
from neutron.db import l3_attrs_db
|
||||||
from neutron.db import l3_db
|
from neutron.db import l3_db
|
||||||
from neutron.db import l3_dvr_db
|
from neutron.db import l3_dvr_db
|
||||||
from neutron.db.l3_dvr_db import is_distributed_router
|
from neutron.db.l3_dvr_db import is_distributed_router
|
||||||
|
@ -788,3 +790,17 @@ def is_ha_router(router):
|
||||||
if validators.is_attr_set(requested_router_type):
|
if validators.is_attr_set(requested_router_type):
|
||||||
return requested_router_type
|
return requested_router_type
|
||||||
return cfg.CONF.l3_ha
|
return cfg.CONF.l3_ha
|
||||||
|
|
||||||
|
|
||||||
|
def is_ha_router_port(device_owner, router_id):
|
||||||
|
session = db_api.get_session()
|
||||||
|
if device_owner == constants.DEVICE_OWNER_HA_REPLICATED_INT:
|
||||||
|
return True
|
||||||
|
elif device_owner == constants.DEVICE_OWNER_ROUTER_SNAT:
|
||||||
|
query = session.query(l3_attrs_db.RouterExtraAttributes)
|
||||||
|
query = query.filter_by(ha=True)
|
||||||
|
query = query.filter(l3_attrs_db.RouterExtraAttributes.router_id ==
|
||||||
|
router_id)
|
||||||
|
return bool(query.limit(1).count())
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
|
@ -18,10 +18,15 @@ from oslo_serialization import jsonutils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
from neutron.db import agents_db
|
from neutron.db import agents_db
|
||||||
|
from neutron.db import l3_hamode_db
|
||||||
from neutron.db import models_v2
|
from neutron.db import models_v2
|
||||||
from neutron.plugins.ml2 import models as ml2_models
|
from neutron.plugins.ml2 import models as ml2_models
|
||||||
|
|
||||||
|
|
||||||
|
HA_ROUTER_PORTS = (const.DEVICE_OWNER_HA_REPLICATED_INT,
|
||||||
|
const.DEVICE_OWNER_ROUTER_SNAT)
|
||||||
|
|
||||||
|
|
||||||
def get_agent_ip_by_host(session, agent_host):
|
def get_agent_ip_by_host(session, agent_host):
|
||||||
agent = get_agent_by_host(session, agent_host)
|
agent = get_agent_by_host(session, agent_host)
|
||||||
if agent:
|
if agent:
|
||||||
|
@ -70,15 +75,33 @@ def _get_active_network_ports(session, network_id):
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
|
||||||
|
def _ha_router_interfaces_on_network_query(session, network_id):
|
||||||
|
query = session.query(models_v2.Port)
|
||||||
|
query = query.join(l3_hamode_db.L3HARouterAgentPortBinding,
|
||||||
|
l3_hamode_db.L3HARouterAgentPortBinding.router_id ==
|
||||||
|
models_v2.Port.device_id)
|
||||||
|
return query.filter(
|
||||||
|
models_v2.Port.network_id == network_id,
|
||||||
|
models_v2.Port.device_owner.in_(HA_ROUTER_PORTS))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_ha_router_interface_ids(session, network_id):
|
||||||
|
query = _ha_router_interfaces_on_network_query(session, network_id)
|
||||||
|
return query.from_self(models_v2.Port.id).distinct()
|
||||||
|
|
||||||
|
|
||||||
def get_nondistributed_active_network_ports(session, network_id):
|
def get_nondistributed_active_network_ports(session, network_id):
|
||||||
query = _get_active_network_ports(session, network_id)
|
query = _get_active_network_ports(session, network_id)
|
||||||
|
# Exclude DVR and HA router interfaces
|
||||||
query = query.filter(models_v2.Port.device_owner !=
|
query = query.filter(models_v2.Port.device_owner !=
|
||||||
const.DEVICE_OWNER_DVR_INTERFACE)
|
const.DEVICE_OWNER_DVR_INTERFACE)
|
||||||
|
ha_iface_ids_query = _get_ha_router_interface_ids(session, network_id)
|
||||||
|
query = query.filter(models_v2.Port.id.notin_(ha_iface_ids_query))
|
||||||
return [(bind, agent) for bind, agent in query.all()
|
return [(bind, agent) for bind, agent in query.all()
|
||||||
if get_agent_ip(agent)]
|
if get_agent_ip(agent)]
|
||||||
|
|
||||||
|
|
||||||
def get_distributed_active_network_ports(session, network_id):
|
def get_dvr_active_network_ports(session, network_id):
|
||||||
with session.begin(subtransactions=True):
|
with session.begin(subtransactions=True):
|
||||||
query = session.query(ml2_models.DistributedPortBinding,
|
query = session.query(ml2_models.DistributedPortBinding,
|
||||||
agents_db.Agent)
|
agents_db.Agent)
|
||||||
|
@ -94,6 +117,44 @@ def get_distributed_active_network_ports(session, network_id):
|
||||||
if get_agent_ip(agent)]
|
if get_agent_ip(agent)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_distributed_active_network_ports(session, network_id):
|
||||||
|
return (get_dvr_active_network_ports(session, network_id) +
|
||||||
|
get_ha_active_network_ports(session, network_id))
|
||||||
|
|
||||||
|
|
||||||
|
def get_ha_active_network_ports(session, network_id):
|
||||||
|
agents = get_ha_agents(session, network_id=network_id)
|
||||||
|
return [(None, agent) for agent in agents]
|
||||||
|
|
||||||
|
|
||||||
|
def get_ha_agents(session, network_id=None, router_id=None):
|
||||||
|
query = session.query(agents_db.Agent.host).distinct()
|
||||||
|
query = query.join(l3_hamode_db.L3HARouterAgentPortBinding,
|
||||||
|
l3_hamode_db.L3HARouterAgentPortBinding.l3_agent_id ==
|
||||||
|
agents_db.Agent.id)
|
||||||
|
if router_id:
|
||||||
|
query = query.filter(
|
||||||
|
l3_hamode_db.L3HARouterAgentPortBinding.router_id == router_id)
|
||||||
|
elif network_id:
|
||||||
|
query = query.join(models_v2.Port, models_v2.Port.device_id ==
|
||||||
|
l3_hamode_db.L3HARouterAgentPortBinding.router_id)
|
||||||
|
query = query.filter(models_v2.Port.network_id == network_id,
|
||||||
|
models_v2.Port.status == const.PORT_STATUS_ACTIVE,
|
||||||
|
models_v2.Port.device_owner.in_(HA_ROUTER_PORTS))
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
# L3HARouterAgentPortBinding will have l3 agent ids of hosting agents.
|
||||||
|
# But we need l2 agent(for tunneling ip) while creating FDB entries.
|
||||||
|
agents_query = session.query(agents_db.Agent)
|
||||||
|
agents_query = agents_query.filter(agents_db.Agent.host.in_(query))
|
||||||
|
return [agent for agent in agents_query
|
||||||
|
if get_agent_ip(agent)]
|
||||||
|
|
||||||
|
|
||||||
|
def get_ha_agents_by_router_id(session, router_id):
|
||||||
|
return get_ha_agents(session, router_id=router_id)
|
||||||
|
|
||||||
|
|
||||||
def get_agent_network_active_port_count(session, agent_host,
|
def get_agent_network_active_port_count(session, agent_host,
|
||||||
network_id):
|
network_id):
|
||||||
with session.begin(subtransactions=True):
|
with session.begin(subtransactions=True):
|
||||||
|
@ -105,6 +166,12 @@ def get_agent_network_active_port_count(session, agent_host,
|
||||||
models_v2.Port.device_owner !=
|
models_v2.Port.device_owner !=
|
||||||
const.DEVICE_OWNER_DVR_INTERFACE,
|
const.DEVICE_OWNER_DVR_INTERFACE,
|
||||||
ml2_models.PortBinding.host == agent_host)
|
ml2_models.PortBinding.host == agent_host)
|
||||||
|
|
||||||
|
ha_iface_ids_query = _get_ha_router_interface_ids(session, network_id)
|
||||||
|
query1 = query1.filter(models_v2.Port.id.notin_(ha_iface_ids_query))
|
||||||
|
ha_port_count = get_ha_router_active_port_count(
|
||||||
|
session, agent_host, network_id)
|
||||||
|
|
||||||
query2 = query.join(ml2_models.DistributedPortBinding)
|
query2 = query.join(ml2_models.DistributedPortBinding)
|
||||||
query2 = query2.filter(models_v2.Port.network_id == network_id,
|
query2 = query2.filter(models_v2.Port.network_id == network_id,
|
||||||
ml2_models.DistributedPortBinding.status ==
|
ml2_models.DistributedPortBinding.status ==
|
||||||
|
@ -113,4 +180,13 @@ def get_agent_network_active_port_count(session, agent_host,
|
||||||
const.DEVICE_OWNER_DVR_INTERFACE,
|
const.DEVICE_OWNER_DVR_INTERFACE,
|
||||||
ml2_models.DistributedPortBinding.host ==
|
ml2_models.DistributedPortBinding.host ==
|
||||||
agent_host)
|
agent_host)
|
||||||
return (query1.count() + query2.count())
|
return (query1.count() + query2.count() + ha_port_count)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ha_router_active_port_count(session, agent_host, network_id):
|
||||||
|
# Return num of HA router interfaces on the given network and host
|
||||||
|
query = _ha_router_interfaces_on_network_query(session, network_id)
|
||||||
|
query = query.filter(models_v2.Port.status == const.PORT_STATUS_ACTIVE)
|
||||||
|
query = query.join(agents_db.Agent)
|
||||||
|
query = query.filter(agents_db.Agent.host == agent_host)
|
||||||
|
return query.count()
|
||||||
|
|
|
@ -21,6 +21,9 @@ from oslo_log import log as logging
|
||||||
from neutron._i18n import _, _LW
|
from neutron._i18n import _, _LW
|
||||||
from neutron import context as n_context
|
from neutron import context as n_context
|
||||||
from neutron.db import api as db_api
|
from neutron.db import api as db_api
|
||||||
|
from neutron.db import l3_hamode_db
|
||||||
|
from neutron import manager
|
||||||
|
from neutron.plugins.common import constants as service_constants
|
||||||
from neutron.plugins.ml2 import driver_api as api
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
from neutron.plugins.ml2.drivers.l2pop import config # noqa
|
from neutron.plugins.ml2.drivers.l2pop import config # noqa
|
||||||
from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db
|
from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db
|
||||||
|
@ -52,11 +55,30 @@ class L2populationMechanismDriver(api.MechanismDriver):
|
||||||
"""L2population driver vlan transparency support."""
|
"""L2population driver vlan transparency support."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _get_ha_port_agents_fdb(
|
||||||
|
self, session, network_id, router_id):
|
||||||
|
other_fdb_ports = {}
|
||||||
|
for agent in l2pop_db.get_ha_agents_by_router_id(session, router_id):
|
||||||
|
agent_active_ports = l2pop_db.get_agent_network_active_port_count(
|
||||||
|
session, agent.host, network_id)
|
||||||
|
if agent_active_ports == 0:
|
||||||
|
ip = l2pop_db.get_agent_ip(agent)
|
||||||
|
other_fdb_ports[ip] = [const.FLOODING_ENTRY]
|
||||||
|
|
||||||
|
return other_fdb_ports
|
||||||
|
|
||||||
def delete_port_postcommit(self, context):
|
def delete_port_postcommit(self, context):
|
||||||
port = context.current
|
port = context.current
|
||||||
agent_host = context.host
|
agent_host = context.host
|
||||||
fdb_entries = self._get_agent_fdb(context.bottom_bound_segment,
|
fdb_entries = self._get_agent_fdb(context.bottom_bound_segment,
|
||||||
port, agent_host)
|
port, agent_host)
|
||||||
|
session = db_api.get_session()
|
||||||
|
if port['device_owner'] in l2pop_db.HA_ROUTER_PORTS:
|
||||||
|
network_id = port['network_id']
|
||||||
|
other_fdb_ports = self._get_ha_port_agents_fdb(
|
||||||
|
session, network_id, port['device_id'])
|
||||||
|
fdb_entries[network_id]['ports'] = other_fdb_ports
|
||||||
|
|
||||||
self.L2populationAgentNotify.remove_fdb_entries(self.rpc_ctx,
|
self.L2populationAgentNotify.remove_fdb_entries(self.rpc_ctx,
|
||||||
fdb_entries)
|
fdb_entries)
|
||||||
|
|
||||||
|
@ -125,13 +147,15 @@ class L2populationMechanismDriver(api.MechanismDriver):
|
||||||
def update_port_postcommit(self, context):
|
def update_port_postcommit(self, context):
|
||||||
port = context.current
|
port = context.current
|
||||||
orig = context.original
|
orig = context.original
|
||||||
|
if l3_hamode_db.is_ha_router_port(port['device_owner'],
|
||||||
|
port['device_id']):
|
||||||
|
return
|
||||||
diff_ips = self._get_diff_ips(orig, port)
|
diff_ips = self._get_diff_ips(orig, port)
|
||||||
if diff_ips:
|
if diff_ips:
|
||||||
self._fixed_ips_changed(context, orig, port, diff_ips)
|
self._fixed_ips_changed(context, orig, port, diff_ips)
|
||||||
if port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
|
if port['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
|
||||||
if context.status == const.PORT_STATUS_ACTIVE:
|
if context.status == const.PORT_STATUS_ACTIVE:
|
||||||
self._update_port_up(context)
|
self.update_port_up(context)
|
||||||
if context.status == const.PORT_STATUS_DOWN:
|
if context.status == const.PORT_STATUS_DOWN:
|
||||||
agent_host = context.host
|
agent_host = context.host
|
||||||
fdb_entries = self._get_agent_fdb(
|
fdb_entries = self._get_agent_fdb(
|
||||||
|
@ -150,7 +174,7 @@ class L2populationMechanismDriver(api.MechanismDriver):
|
||||||
self.rpc_ctx, fdb_entries)
|
self.rpc_ctx, fdb_entries)
|
||||||
elif context.status != context.original_status:
|
elif context.status != context.original_status:
|
||||||
if context.status == const.PORT_STATUS_ACTIVE:
|
if context.status == const.PORT_STATUS_ACTIVE:
|
||||||
self._update_port_up(context)
|
self.update_port_up(context)
|
||||||
elif context.status == const.PORT_STATUS_DOWN:
|
elif context.status == const.PORT_STATUS_DOWN:
|
||||||
fdb_entries = self._get_agent_fdb(
|
fdb_entries = self._get_agent_fdb(
|
||||||
context.bottom_bound_segment, port, context.host)
|
context.bottom_bound_segment, port, context.host)
|
||||||
|
@ -209,7 +233,24 @@ class L2populationMechanismDriver(api.MechanismDriver):
|
||||||
|
|
||||||
return agents
|
return agents
|
||||||
|
|
||||||
def _update_port_up(self, context):
|
def update_port_down(self, context):
|
||||||
|
port = context.current
|
||||||
|
agent_host = context.host
|
||||||
|
l3plugin = manager.NeutronManager.get_service_plugins().get(
|
||||||
|
service_constants.L3_ROUTER_NAT)
|
||||||
|
# when agent transitions to backup, don't remove flood flows
|
||||||
|
if agent_host and l3plugin and getattr(
|
||||||
|
l3plugin, "list_router_ids_on_host", None):
|
||||||
|
admin_context = n_context.get_admin_context()
|
||||||
|
if l3plugin.list_router_ids_on_host(
|
||||||
|
admin_context, agent_host, [port['device_id']]):
|
||||||
|
return
|
||||||
|
fdb_entries = self._get_agent_fdb(
|
||||||
|
context.bottom_bound_segment, port, agent_host)
|
||||||
|
self.L2populationAgentNotify.remove_fdb_entries(
|
||||||
|
self.rpc_ctx, fdb_entries)
|
||||||
|
|
||||||
|
def update_port_up(self, context):
|
||||||
port = context.current
|
port = context.current
|
||||||
agent_host = context.host
|
agent_host = context.host
|
||||||
session = db_api.get_session()
|
session = db_api.get_session()
|
||||||
|
@ -249,7 +290,9 @@ class L2populationMechanismDriver(api.MechanismDriver):
|
||||||
self.rpc_ctx, agent_fdb_entries, agent_host)
|
self.rpc_ctx, agent_fdb_entries, agent_host)
|
||||||
|
|
||||||
# Notify other agents to add fdb rule for current port
|
# Notify other agents to add fdb rule for current port
|
||||||
if port['device_owner'] != const.DEVICE_OWNER_DVR_INTERFACE:
|
if (port['device_owner'] != const.DEVICE_OWNER_DVR_INTERFACE and
|
||||||
|
not l3_hamode_db.is_ha_router_port(port['device_owner'],
|
||||||
|
port['device_id'])):
|
||||||
other_fdb_ports[agent_ip] += self._get_port_fdb_entries(port)
|
other_fdb_ports[agent_ip] += self._get_port_fdb_entries(port)
|
||||||
|
|
||||||
self.L2populationAgentNotify.add_fdb_entries(self.rpc_ctx,
|
self.L2populationAgentNotify.add_fdb_entries(self.rpc_ctx,
|
||||||
|
@ -278,7 +321,9 @@ class L2populationMechanismDriver(api.MechanismDriver):
|
||||||
other_fdb_entries[network_id]['ports'][agent_ip].append(
|
other_fdb_entries[network_id]['ports'][agent_ip].append(
|
||||||
const.FLOODING_ENTRY)
|
const.FLOODING_ENTRY)
|
||||||
# Notify other agents to remove fdb rules for current port
|
# Notify other agents to remove fdb rules for current port
|
||||||
if port['device_owner'] != const.DEVICE_OWNER_DVR_INTERFACE:
|
if (port['device_owner'] != const.DEVICE_OWNER_DVR_INTERFACE and
|
||||||
|
not l3_hamode_db.is_ha_router_port(port['device_owner'],
|
||||||
|
port['device_id'])):
|
||||||
fdb_entries = self._get_port_fdb_entries(port)
|
fdb_entries = self._get_port_fdb_entries(port)
|
||||||
other_fdb_entries[network_id]['ports'][agent_ip] += fdb_entries
|
other_fdb_entries[network_id]['ports'][agent_ip] += fdb_entries
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc
|
||||||
from neutron.callbacks import resources
|
from neutron.callbacks import resources
|
||||||
from neutron.common import rpc as n_rpc
|
from neutron.common import rpc as n_rpc
|
||||||
from neutron.common import topics
|
from neutron.common import topics
|
||||||
|
from neutron.db import l3_hamode_db
|
||||||
from neutron.db import provisioning_blocks
|
from neutron.db import provisioning_blocks
|
||||||
from neutron.extensions import portbindings
|
from neutron.extensions import portbindings
|
||||||
from neutron.extensions import portsecurity as psec
|
from neutron.extensions import portsecurity as psec
|
||||||
|
@ -182,16 +183,18 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
|
||||||
LOG.debug("Device %(device)s not bound to the"
|
LOG.debug("Device %(device)s not bound to the"
|
||||||
" agent host %(host)s",
|
" agent host %(host)s",
|
||||||
{'device': device, 'host': host})
|
{'device': device, 'host': host})
|
||||||
return {'device': device,
|
else:
|
||||||
'exists': port_exists}
|
try:
|
||||||
|
port_exists = bool(plugin.update_port_status(
|
||||||
try:
|
rpc_context, port_id, n_const.PORT_STATUS_DOWN, host))
|
||||||
port_exists = bool(plugin.update_port_status(
|
except exc.StaleDataError:
|
||||||
rpc_context, port_id, n_const.PORT_STATUS_DOWN, host))
|
port_exists = False
|
||||||
except exc.StaleDataError:
|
LOG.debug("delete_port and update_device_down are being "
|
||||||
port_exists = False
|
"executed concurrently. Ignoring StaleDataError.")
|
||||||
LOG.debug("delete_port and update_device_down are being executed "
|
return {'device': device,
|
||||||
"concurrently. Ignoring StaleDataError.")
|
'exists': port_exists}
|
||||||
|
self.notify_ha_port_status(port_id, rpc_context,
|
||||||
|
n_const.PORT_STATUS_DOWN, host)
|
||||||
|
|
||||||
return {'device': device,
|
return {'device': device,
|
||||||
'exists': port_exists}
|
'exists': port_exists}
|
||||||
|
@ -217,11 +220,19 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
|
||||||
port = plugin._get_port(rpc_context, port_id)
|
port = plugin._get_port(rpc_context, port_id)
|
||||||
except exceptions.PortNotFound:
|
except exceptions.PortNotFound:
|
||||||
LOG.debug("Port %s not found, will not notify nova.", port_id)
|
LOG.debug("Port %s not found, will not notify nova.", port_id)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
if port.device_owner.startswith(
|
if port.device_owner.startswith(
|
||||||
n_const.DEVICE_OWNER_COMPUTE_PREFIX):
|
n_const.DEVICE_OWNER_COMPUTE_PREFIX):
|
||||||
plugin.nova_notifier.notify_port_active_direct(port)
|
plugin.nova_notifier.notify_port_active_direct(port)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
self.update_port_status_to_active(port, rpc_context, port_id, host)
|
||||||
|
self.notify_ha_port_status(port_id, rpc_context,
|
||||||
|
n_const.PORT_STATUS_ACTIVE, host, port=port)
|
||||||
|
|
||||||
|
def update_port_status_to_active(self, port, rpc_context, port_id, host):
|
||||||
|
plugin = manager.NeutronManager.get_plugin()
|
||||||
if port and port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE:
|
if port and port['device_owner'] == n_const.DEVICE_OWNER_DVR_INTERFACE:
|
||||||
# NOTE(kevinbenton): we have to special case DVR ports because of
|
# NOTE(kevinbenton): we have to special case DVR ports because of
|
||||||
# the special multi-binding status update logic they have that
|
# the special multi-binding status update logic they have that
|
||||||
|
@ -241,6 +252,29 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin):
|
||||||
rpc_context, port['id'], resources.PORT,
|
rpc_context, port['id'], resources.PORT,
|
||||||
provisioning_blocks.L2_AGENT_ENTITY)
|
provisioning_blocks.L2_AGENT_ENTITY)
|
||||||
|
|
||||||
|
def notify_ha_port_status(self, port_id, rpc_context,
|
||||||
|
status, host, port=None):
|
||||||
|
plugin = manager.NeutronManager.get_plugin()
|
||||||
|
l2pop_driver = plugin.mechanism_manager.mech_drivers.get(
|
||||||
|
'l2population')
|
||||||
|
if not l2pop_driver:
|
||||||
|
return
|
||||||
|
if not port:
|
||||||
|
port = ml2_db.get_port(rpc_context.session, port_id)
|
||||||
|
if not port:
|
||||||
|
return
|
||||||
|
is_ha_port = l3_hamode_db.is_ha_router_port(port['device_owner'],
|
||||||
|
port['device_id'])
|
||||||
|
if is_ha_port:
|
||||||
|
port_context = plugin.get_bound_port_context(
|
||||||
|
rpc_context, port_id)
|
||||||
|
port_context.current['status'] = status
|
||||||
|
port_context.current[portbindings.HOST_ID] = host
|
||||||
|
if status == n_const.PORT_STATUS_ACTIVE:
|
||||||
|
l2pop_driver.obj.update_port_up(port_context)
|
||||||
|
else:
|
||||||
|
l2pop_driver.obj.update_port_down(port_context)
|
||||||
|
|
||||||
def update_device_list(self, rpc_context, **kwargs):
|
def update_device_list(self, rpc_context, **kwargs):
|
||||||
devices_up = []
|
devices_up = []
|
||||||
failed_devices_up = []
|
failed_devices_up = []
|
||||||
|
|
|
@ -1165,6 +1165,39 @@ class L3HAModeDbTestCase(L3HATestFramework):
|
||||||
port = self._get_first_interface(router['id'])
|
port = self._get_first_interface(router['id'])
|
||||||
self.assertEqual(self.agent1['host'], port[portbindings.HOST_ID])
|
self.assertEqual(self.agent1['host'], port[portbindings.HOST_ID])
|
||||||
|
|
||||||
|
def test_is_ha_router_port(self):
|
||||||
|
network_id = self._create_network(self.core_plugin, self.admin_ctx)
|
||||||
|
subnet = self._create_subnet(self.core_plugin, self.admin_ctx,
|
||||||
|
network_id)
|
||||||
|
interface_info = {'subnet_id': subnet['id']}
|
||||||
|
|
||||||
|
router = self._create_router()
|
||||||
|
self.plugin.add_router_interface(self.admin_ctx,
|
||||||
|
router['id'],
|
||||||
|
interface_info)
|
||||||
|
port = self._get_first_interface(router['id'])
|
||||||
|
self.assertTrue(l3_hamode_db.is_ha_router_port(
|
||||||
|
port['device_owner'], port['device_id']))
|
||||||
|
|
||||||
|
def test_is_ha_router_port_for_normal_port(self):
|
||||||
|
network_id = self._create_network(self.core_plugin, self.admin_ctx)
|
||||||
|
subnet = self._create_subnet(self.core_plugin, self.admin_ctx,
|
||||||
|
network_id)
|
||||||
|
interface_info = {'subnet_id': subnet['id']}
|
||||||
|
|
||||||
|
router = self._create_router(ha=False)
|
||||||
|
self.plugin.add_router_interface(self.admin_ctx,
|
||||||
|
router['id'],
|
||||||
|
interface_info)
|
||||||
|
device_filter = {'device_id': [router['id']],
|
||||||
|
'device_owner':
|
||||||
|
[constants.DEVICE_OWNER_ROUTER_INTF]}
|
||||||
|
port = self.core_plugin.get_ports(
|
||||||
|
self.admin_ctx, filters=device_filter)[0]
|
||||||
|
|
||||||
|
self.assertFalse(l3_hamode_db.is_ha_router_port(
|
||||||
|
port['device_owner'], port['device_id']))
|
||||||
|
|
||||||
|
|
||||||
class L3HAUserTestCase(L3HATestFramework):
|
class L3HAUserTestCase(L3HATestFramework):
|
||||||
|
|
||||||
|
|
|
@ -13,23 +13,73 @@
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from neutron_lib import constants
|
from neutron_lib import constants
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from neutron.common import constants as n_const
|
||||||
|
from neutron.common import utils
|
||||||
from neutron import context
|
from neutron import context
|
||||||
|
from neutron.db import l3_attrs_db
|
||||||
|
from neutron.db import l3_db
|
||||||
|
from neutron.db import l3_hamode_db
|
||||||
from neutron.db import models_v2
|
from neutron.db import models_v2
|
||||||
from neutron.extensions import portbindings
|
from neutron.extensions import portbindings
|
||||||
from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db
|
from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db
|
||||||
from neutron.plugins.ml2 import models
|
from neutron.plugins.ml2 import models
|
||||||
from neutron.tests.common import helpers
|
from neutron.tests.common import helpers
|
||||||
|
from neutron.tests import tools
|
||||||
from neutron.tests.unit import testlib_api
|
from neutron.tests.unit import testlib_api
|
||||||
|
|
||||||
|
HOST = helpers.HOST
|
||||||
|
HOST_2 = 'HOST_2'
|
||||||
|
HOST_3 = 'HOST_3'
|
||||||
|
HOST_2_TUNNELING_IP = '20.0.0.2'
|
||||||
|
HOST_3_TUNNELING_IP = '20.0.0.3'
|
||||||
|
TEST_ROUTER_ID = 'router_id'
|
||||||
|
TEST_NETWORK_ID = 'network_id'
|
||||||
|
TEST_HA_NETWORK_ID = 'ha_network_id'
|
||||||
|
|
||||||
|
|
||||||
class TestL2PopulationDBTestCase(testlib_api.SqlTestCase):
|
class TestL2PopulationDBTestCase(testlib_api.SqlTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestL2PopulationDBTestCase, self).setUp()
|
super(TestL2PopulationDBTestCase, self).setUp()
|
||||||
self.ctx = context.get_admin_context()
|
self.ctx = context.get_admin_context()
|
||||||
|
self._create_network()
|
||||||
|
|
||||||
|
def _create_network(self, network_id=TEST_NETWORK_ID):
|
||||||
|
with self.ctx.session.begin(subtransactions=True):
|
||||||
|
self.ctx.session.add(models_v2.Network(id=network_id))
|
||||||
|
|
||||||
|
def _create_router(self, distributed=True, ha=False):
|
||||||
|
with self.ctx.session.begin(subtransactions=True):
|
||||||
|
self.ctx.session.add(l3_db.Router(id=TEST_ROUTER_ID))
|
||||||
|
self.ctx.session.add(l3_attrs_db.RouterExtraAttributes(
|
||||||
|
router_id=TEST_ROUTER_ID, distributed=distributed, ha=ha))
|
||||||
|
|
||||||
|
def _create_ha_router(self, distributed=False):
|
||||||
|
helpers.register_l3_agent(HOST_2)
|
||||||
|
helpers.register_ovs_agent(HOST_2, tunneling_ip=HOST_2_TUNNELING_IP)
|
||||||
|
# Register l3 agent on host3, which doesn't host any HA router.
|
||||||
|
# Tests should test that host3 is not a HA agent host.
|
||||||
|
helpers.register_l3_agent(HOST_3)
|
||||||
|
helpers.register_ovs_agent(HOST_3, tunneling_ip=HOST_3_TUNNELING_IP)
|
||||||
|
with self.ctx.session.begin(subtransactions=True):
|
||||||
|
self.ctx.session.add(models_v2.Network(id=TEST_HA_NETWORK_ID))
|
||||||
|
self._create_router(distributed=distributed, ha=True)
|
||||||
|
for state, host in [(n_const.HA_ROUTER_STATE_ACTIVE, HOST),
|
||||||
|
(n_const.HA_ROUTER_STATE_STANDBY, HOST_2)]:
|
||||||
|
self._setup_port_binding(
|
||||||
|
network_id=TEST_HA_NETWORK_ID,
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_HA_INTF,
|
||||||
|
device_id=TEST_ROUTER_ID,
|
||||||
|
host_state=state,
|
||||||
|
host=host)
|
||||||
|
|
||||||
|
def get_l3_agent_by_host(self, agent_host):
|
||||||
|
plugin = helpers.FakePlugin()
|
||||||
|
return plugin._get_agent_by_type_and_host(
|
||||||
|
self.ctx, constants.AGENT_TYPE_L3, agent_host)
|
||||||
|
|
||||||
def test_get_agent_by_host(self):
|
def test_get_agent_by_host(self):
|
||||||
# Register a L2 agent + A bunch of other agents on the same host
|
|
||||||
helpers.register_l3_agent()
|
helpers.register_l3_agent()
|
||||||
helpers.register_dhcp_agent()
|
helpers.register_dhcp_agent()
|
||||||
helpers.register_ovs_agent()
|
helpers.register_ovs_agent()
|
||||||
|
@ -38,58 +88,70 @@ class TestL2PopulationDBTestCase(testlib_api.SqlTestCase):
|
||||||
self.assertEqual(constants.AGENT_TYPE_OVS, agent.agent_type)
|
self.assertEqual(constants.AGENT_TYPE_OVS, agent.agent_type)
|
||||||
|
|
||||||
def test_get_agent_by_host_no_candidate(self):
|
def test_get_agent_by_host_no_candidate(self):
|
||||||
# Register a bunch of non-L2 agents on the same host
|
|
||||||
helpers.register_l3_agent()
|
helpers.register_l3_agent()
|
||||||
helpers.register_dhcp_agent()
|
helpers.register_dhcp_agent()
|
||||||
agent = l2pop_db.get_agent_by_host(
|
agent = l2pop_db.get_agent_by_host(
|
||||||
self.ctx.session, helpers.HOST)
|
self.ctx.session, helpers.HOST)
|
||||||
self.assertIsNone(agent)
|
self.assertIsNone(agent)
|
||||||
|
|
||||||
def _setup_port_binding(self, network_id='network_id', dvr=True):
|
def _setup_port_binding(self, **kwargs):
|
||||||
with self.ctx.session.begin(subtransactions=True):
|
with self.ctx.session.begin(subtransactions=True):
|
||||||
self.ctx.session.add(models_v2.Network(id=network_id))
|
mac = utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
|
||||||
device_owner = constants.DEVICE_OWNER_DVR_INTERFACE if dvr else ''
|
port_id = uuidutils.generate_uuid()
|
||||||
|
network_id = kwargs.get('network_id', TEST_NETWORK_ID)
|
||||||
|
device_owner = kwargs.get('device_owner', '')
|
||||||
|
device_id = kwargs.get('device_id', '')
|
||||||
|
host = kwargs.get('host', helpers.HOST)
|
||||||
|
|
||||||
self.ctx.session.add(models_v2.Port(
|
self.ctx.session.add(models_v2.Port(
|
||||||
id='port_id',
|
id=port_id, network_id=network_id, mac_address=mac,
|
||||||
network_id=network_id,
|
admin_state_up=True, status=constants.PORT_STATUS_ACTIVE,
|
||||||
mac_address='00:11:22:33:44:55',
|
device_id=device_id, device_owner=device_owner))
|
||||||
admin_state_up=True,
|
|
||||||
status=constants.PORT_STATUS_ACTIVE,
|
port_binding_cls = models.PortBinding
|
||||||
device_id='',
|
binding_kwarg = {'port_id': port_id,
|
||||||
device_owner=device_owner))
|
'host': host,
|
||||||
port_binding_cls = (models.DistributedPortBinding if dvr
|
'vif_type': portbindings.VIF_TYPE_UNBOUND,
|
||||||
else models.PortBinding)
|
'vnic_type': portbindings.VNIC_NORMAL}
|
||||||
binding_kwarg = {
|
|
||||||
'port_id': 'port_id',
|
if device_owner == constants.DEVICE_OWNER_DVR_INTERFACE:
|
||||||
'host': helpers.HOST,
|
port_binding_cls = models.DistributedPortBinding
|
||||||
'vif_type': portbindings.VIF_TYPE_UNBOUND,
|
binding_kwarg['router_id'] = TEST_ROUTER_ID
|
||||||
'vnic_type': portbindings.VNIC_NORMAL
|
|
||||||
}
|
|
||||||
if dvr:
|
|
||||||
binding_kwarg['router_id'] = 'router_id'
|
|
||||||
binding_kwarg['status'] = constants.PORT_STATUS_DOWN
|
binding_kwarg['status'] = constants.PORT_STATUS_DOWN
|
||||||
|
|
||||||
self.ctx.session.add(port_binding_cls(**binding_kwarg))
|
self.ctx.session.add(port_binding_cls(**binding_kwarg))
|
||||||
|
|
||||||
|
if network_id == TEST_HA_NETWORK_ID:
|
||||||
|
agent = self.get_l3_agent_by_host(host)
|
||||||
|
haport_bindings_cls = l3_hamode_db.L3HARouterAgentPortBinding
|
||||||
|
habinding_kwarg = {'port_id': port_id,
|
||||||
|
'router_id': device_id,
|
||||||
|
'l3_agent_id': agent['id'],
|
||||||
|
'state': kwargs.get('host_state',
|
||||||
|
n_const.HA_ROUTER_STATE_ACTIVE)}
|
||||||
|
self.ctx.session.add(haport_bindings_cls(**habinding_kwarg))
|
||||||
|
|
||||||
def test_get_distributed_active_network_ports(self):
|
def test_get_distributed_active_network_ports(self):
|
||||||
self._setup_port_binding()
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_DVR_INTERFACE)
|
||||||
# Register a L2 agent + A bunch of other agents on the same host
|
# Register a L2 agent + A bunch of other agents on the same host
|
||||||
helpers.register_l3_agent()
|
helpers.register_l3_agent()
|
||||||
helpers.register_dhcp_agent()
|
helpers.register_dhcp_agent()
|
||||||
helpers.register_ovs_agent()
|
helpers.register_ovs_agent()
|
||||||
tunnel_network_ports = l2pop_db.get_distributed_active_network_ports(
|
tunnel_network_ports = l2pop_db.get_distributed_active_network_ports(
|
||||||
self.ctx.session, 'network_id')
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
self.assertEqual(1, len(tunnel_network_ports))
|
self.assertEqual(1, len(tunnel_network_ports))
|
||||||
_, agent = tunnel_network_ports[0]
|
_, agent = tunnel_network_ports[0]
|
||||||
self.assertEqual(constants.AGENT_TYPE_OVS, agent.agent_type)
|
self.assertEqual(constants.AGENT_TYPE_OVS, agent.agent_type)
|
||||||
|
|
||||||
def test_get_distributed_active_network_ports_no_candidate(self):
|
def test_get_distributed_active_network_ports_no_candidate(self):
|
||||||
self._setup_port_binding()
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_DVR_INTERFACE)
|
||||||
# Register a bunch of non-L2 agents on the same host
|
# Register a bunch of non-L2 agents on the same host
|
||||||
helpers.register_l3_agent()
|
helpers.register_l3_agent()
|
||||||
helpers.register_dhcp_agent()
|
helpers.register_dhcp_agent()
|
||||||
tunnel_network_ports = l2pop_db.get_distributed_active_network_ports(
|
tunnel_network_ports = l2pop_db.get_distributed_active_network_ports(
|
||||||
self.ctx.session, 'network_id')
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
self.assertEqual(0, len(tunnel_network_ports))
|
self.assertEqual(0, len(tunnel_network_ports))
|
||||||
|
|
||||||
def test_get_nondistributed_active_network_ports(self):
|
def test_get_nondistributed_active_network_ports(self):
|
||||||
|
@ -99,7 +161,7 @@ class TestL2PopulationDBTestCase(testlib_api.SqlTestCase):
|
||||||
helpers.register_dhcp_agent()
|
helpers.register_dhcp_agent()
|
||||||
helpers.register_ovs_agent()
|
helpers.register_ovs_agent()
|
||||||
fdb_network_ports = l2pop_db.get_nondistributed_active_network_ports(
|
fdb_network_ports = l2pop_db.get_nondistributed_active_network_ports(
|
||||||
self.ctx.session, 'network_id')
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
self.assertEqual(1, len(fdb_network_ports))
|
self.assertEqual(1, len(fdb_network_ports))
|
||||||
_, agent = fdb_network_ports[0]
|
_, agent = fdb_network_ports[0]
|
||||||
self.assertEqual(constants.AGENT_TYPE_OVS, agent.agent_type)
|
self.assertEqual(constants.AGENT_TYPE_OVS, agent.agent_type)
|
||||||
|
@ -110,5 +172,116 @@ class TestL2PopulationDBTestCase(testlib_api.SqlTestCase):
|
||||||
helpers.register_l3_agent()
|
helpers.register_l3_agent()
|
||||||
helpers.register_dhcp_agent()
|
helpers.register_dhcp_agent()
|
||||||
fdb_network_ports = l2pop_db.get_nondistributed_active_network_ports(
|
fdb_network_ports = l2pop_db.get_nondistributed_active_network_ports(
|
||||||
self.ctx.session, 'network_id')
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
self.assertEqual(0, len(fdb_network_ports))
|
self.assertEqual(0, len(fdb_network_ports))
|
||||||
|
|
||||||
|
def test__get_ha_router_interface_ids_with_ha_dvr_snat_port(self):
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
helpers.register_l3_agent()
|
||||||
|
helpers.register_ovs_agent()
|
||||||
|
self._create_ha_router()
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
ha_iface_ids = l2pop_db._get_ha_router_interface_ids(
|
||||||
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(1, len(list(ha_iface_ids)))
|
||||||
|
|
||||||
|
def test__get_ha_router_interface_ids_with_ha_replicated_port(self):
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
helpers.register_l3_agent()
|
||||||
|
helpers.register_ovs_agent()
|
||||||
|
self._create_ha_router()
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_HA_REPLICATED_INT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
ha_iface_ids = l2pop_db._get_ha_router_interface_ids(
|
||||||
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(1, len(list(ha_iface_ids)))
|
||||||
|
|
||||||
|
def test__get_ha_router_interface_ids_with_no_ha_port(self):
|
||||||
|
self._create_router()
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
ha_iface_ids = l2pop_db._get_ha_router_interface_ids(
|
||||||
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(0, len(list(ha_iface_ids)))
|
||||||
|
|
||||||
|
def test_active_network_ports_with_dvr_snat_port(self):
|
||||||
|
# Test to get agent hosting dvr snat port
|
||||||
|
helpers.register_l3_agent()
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
helpers.register_ovs_agent()
|
||||||
|
# create DVR router
|
||||||
|
self._create_router()
|
||||||
|
# setup DVR snat port
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
fdb_network_ports = l2pop_db.get_nondistributed_active_network_ports(
|
||||||
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(1, len(fdb_network_ports))
|
||||||
|
|
||||||
|
def test_active_network_ports_with_ha_dvr_snat_port(self):
|
||||||
|
# test to get HA agents hosting HA+DVR snat port
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
helpers.register_l3_agent()
|
||||||
|
helpers.register_ovs_agent()
|
||||||
|
# create HA+DVR router
|
||||||
|
self._create_ha_router()
|
||||||
|
# setup HA snat port
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
fdb_network_ports = l2pop_db.get_nondistributed_active_network_ports(
|
||||||
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(0, len(fdb_network_ports))
|
||||||
|
ha_ports = l2pop_db.get_ha_active_network_ports(
|
||||||
|
self.ctx.session, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(2, len(ha_ports))
|
||||||
|
|
||||||
|
def test_active_port_count_with_dvr_snat_port(self):
|
||||||
|
helpers.register_l3_agent()
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
helpers.register_ovs_agent()
|
||||||
|
self._create_router()
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
port_count = l2pop_db.get_agent_network_active_port_count(
|
||||||
|
self.ctx.session, HOST, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(1, port_count)
|
||||||
|
port_count = l2pop_db.get_agent_network_active_port_count(
|
||||||
|
self.ctx.session, HOST_2, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(0, port_count)
|
||||||
|
|
||||||
|
def test_active_port_count_with_ha_dvr_snat_port(self):
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
helpers.register_l3_agent()
|
||||||
|
helpers.register_ovs_agent()
|
||||||
|
self._create_ha_router()
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
port_count = l2pop_db.get_agent_network_active_port_count(
|
||||||
|
self.ctx.session, HOST, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(1, port_count)
|
||||||
|
port_count = l2pop_db.get_agent_network_active_port_count(
|
||||||
|
self.ctx.session, HOST_2, TEST_NETWORK_ID)
|
||||||
|
self.assertEqual(1, port_count)
|
||||||
|
|
||||||
|
def test_get_ha_agents_by_router_id(self):
|
||||||
|
helpers.register_dhcp_agent()
|
||||||
|
helpers.register_l3_agent()
|
||||||
|
helpers.register_ovs_agent()
|
||||||
|
self._create_ha_router()
|
||||||
|
self._setup_port_binding(
|
||||||
|
device_owner=constants.DEVICE_OWNER_ROUTER_SNAT,
|
||||||
|
device_id=TEST_ROUTER_ID)
|
||||||
|
agents = l2pop_db.get_ha_agents_by_router_id(
|
||||||
|
self.ctx.session, TEST_ROUTER_ID)
|
||||||
|
ha_agents = [agent.host for agent in agents]
|
||||||
|
self.assertEqual(tools.UnorderedList([HOST, HOST_2]), ha_agents)
|
||||||
|
|
|
@ -19,11 +19,18 @@ from neutron_lib import exceptions
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
|
from neutron.api.v2 import attributes
|
||||||
|
from neutron.common import constants as n_const
|
||||||
from neutron.common import topics
|
from neutron.common import topics
|
||||||
from neutron import context
|
from neutron import context
|
||||||
|
from neutron.db import agents_db
|
||||||
|
from neutron.db import common_db_mixin
|
||||||
|
from neutron.db import l3_agentschedulers_db
|
||||||
|
from neutron.db import l3_hamode_db
|
||||||
from neutron.extensions import portbindings
|
from neutron.extensions import portbindings
|
||||||
from neutron.extensions import providernet as pnet
|
from neutron.extensions import providernet as pnet
|
||||||
from neutron import manager
|
from neutron import manager
|
||||||
|
from neutron.plugins.common import constants as service_constants
|
||||||
from neutron.plugins.ml2 import driver_context
|
from neutron.plugins.ml2 import driver_context
|
||||||
from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db
|
from neutron.plugins.ml2.drivers.l2pop import db as l2pop_db
|
||||||
from neutron.plugins.ml2.drivers.l2pop import mech_driver as l2pop_mech_driver
|
from neutron.plugins.ml2.drivers.l2pop import mech_driver as l2pop_mech_driver
|
||||||
|
@ -31,6 +38,7 @@ from neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc
|
||||||
from neutron.plugins.ml2.drivers.l2pop.rpc_manager import l2population_rpc
|
from neutron.plugins.ml2.drivers.l2pop.rpc_manager import l2population_rpc
|
||||||
from neutron.plugins.ml2 import managers
|
from neutron.plugins.ml2 import managers
|
||||||
from neutron.plugins.ml2 import rpc
|
from neutron.plugins.ml2 import rpc
|
||||||
|
from neutron.scheduler import l3_agent_scheduler
|
||||||
from neutron.tests import base
|
from neutron.tests import base
|
||||||
from neutron.tests.common import helpers
|
from neutron.tests.common import helpers
|
||||||
from neutron.tests.unit.plugins.ml2 import test_plugin
|
from neutron.tests.unit.plugins.ml2 import test_plugin
|
||||||
|
@ -40,12 +48,20 @@ HOST_2 = HOST + '_2'
|
||||||
HOST_3 = HOST + '_3'
|
HOST_3 = HOST + '_3'
|
||||||
HOST_4 = HOST + '_4'
|
HOST_4 = HOST + '_4'
|
||||||
HOST_5 = HOST + '_5'
|
HOST_5 = HOST + '_5'
|
||||||
|
TEST_ROUTER_ID = 'router_id'
|
||||||
|
|
||||||
|
|
||||||
NOTIFIER = 'neutron.plugins.ml2.rpc.AgentNotifierApi'
|
NOTIFIER = 'neutron.plugins.ml2.rpc.AgentNotifierApi'
|
||||||
DEVICE_OWNER_COMPUTE = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake'
|
DEVICE_OWNER_COMPUTE = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake'
|
||||||
|
|
||||||
|
|
||||||
|
class FakeL3PluginWithAgents(common_db_mixin.CommonDbMixin,
|
||||||
|
l3_hamode_db.L3_HA_NAT_db_mixin,
|
||||||
|
l3_agentschedulers_db.L3AgentSchedulerDbMixin,
|
||||||
|
agents_db.AgentDbMixin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase):
|
class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase):
|
||||||
_mechanism_drivers = ['openvswitch', 'fake_agent', 'l2population']
|
_mechanism_drivers = ['openvswitch', 'fake_agent', 'l2population']
|
||||||
|
|
||||||
|
@ -101,6 +117,18 @@ class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase):
|
||||||
uptime_patch = mock.patch(uptime, return_value=190)
|
uptime_patch = mock.patch(uptime, return_value=190)
|
||||||
uptime_patch.start()
|
uptime_patch.start()
|
||||||
|
|
||||||
|
def _setup_l3(self):
|
||||||
|
notif_p = mock.patch.object(l3_hamode_db.L3_HA_NAT_db_mixin,
|
||||||
|
'_notify_ha_interfaces_updated')
|
||||||
|
self.notif_m = notif_p.start()
|
||||||
|
self.plugin = FakeL3PluginWithAgents()
|
||||||
|
self._register_ml2_agents()
|
||||||
|
self._register_l3_agents()
|
||||||
|
|
||||||
|
def _register_l3_agents(self):
|
||||||
|
self.agent1 = helpers.register_l3_agent(host=HOST)
|
||||||
|
self.agent2 = helpers.register_l3_agent(host=HOST_2)
|
||||||
|
|
||||||
def _register_ml2_agents(self):
|
def _register_ml2_agents(self):
|
||||||
helpers.register_ovs_agent(host=HOST, tunneling_ip='20.0.0.1')
|
helpers.register_ovs_agent(host=HOST, tunneling_ip='20.0.0.1')
|
||||||
helpers.register_ovs_agent(host=HOST_2, tunneling_ip='20.0.0.2')
|
helpers.register_ovs_agent(host=HOST_2, tunneling_ip='20.0.0.2')
|
||||||
|
@ -167,6 +195,216 @@ class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase):
|
||||||
result = jsonutils.loads(jsonutils.dumps(payload))
|
result = jsonutils.loads(jsonutils.dumps(payload))
|
||||||
self.assertEqual(entry, result['netuuid']['ports']['1'][0])
|
self.assertEqual(entry, result['netuuid']['ports']['1'][0])
|
||||||
|
|
||||||
|
def _create_router(self, ha=True, tenant_id='tenant1',
|
||||||
|
distributed=None, ctx=None):
|
||||||
|
if ctx is None:
|
||||||
|
ctx = self.adminContext
|
||||||
|
ctx.tenant_id = tenant_id
|
||||||
|
router = {'name': TEST_ROUTER_ID, 'admin_state_up': True,
|
||||||
|
'tenant_id': ctx.tenant_id}
|
||||||
|
if ha is not None:
|
||||||
|
router['ha'] = ha
|
||||||
|
if distributed is not None:
|
||||||
|
router['distributed'] = distributed
|
||||||
|
return self.plugin.create_router(ctx, {'router': router})
|
||||||
|
|
||||||
|
def _bind_router(self, router_id):
|
||||||
|
with self.adminContext.session.begin(subtransactions=True):
|
||||||
|
scheduler = l3_agent_scheduler.ChanceScheduler()
|
||||||
|
filters = {'agent_type': [constants.AGENT_TYPE_L3]}
|
||||||
|
agents_db = self.plugin.get_agents_db(self.adminContext,
|
||||||
|
filters=filters)
|
||||||
|
scheduler._bind_ha_router_to_agents(
|
||||||
|
self.plugin,
|
||||||
|
self.adminContext,
|
||||||
|
router_id,
|
||||||
|
agents_db)
|
||||||
|
self._bind_ha_network_ports(router_id)
|
||||||
|
|
||||||
|
def _bind_ha_network_ports(self, router_id):
|
||||||
|
port_bindings = self.plugin.get_ha_router_port_bindings(
|
||||||
|
self.adminContext, [router_id])
|
||||||
|
plugin = manager.NeutronManager.get_plugin()
|
||||||
|
|
||||||
|
for port_binding in port_bindings:
|
||||||
|
filters = {'id': [port_binding.port_id]}
|
||||||
|
port = plugin.get_ports(self.adminContext, filters=filters)[0]
|
||||||
|
if port_binding.l3_agent_id == self.agent1['id']:
|
||||||
|
port[portbindings.HOST_ID] = self.agent1['host']
|
||||||
|
else:
|
||||||
|
port[portbindings.HOST_ID] = self.agent2['host']
|
||||||
|
plugin.update_port(self.adminContext, port['id'],
|
||||||
|
{attributes.PORT: port})
|
||||||
|
|
||||||
|
def _get_first_interface(self, net_id, router_id):
|
||||||
|
plugin = manager.NeutronManager.get_plugin()
|
||||||
|
device_filter = {'device_id': [router_id],
|
||||||
|
'device_owner':
|
||||||
|
[constants.DEVICE_OWNER_HA_REPLICATED_INT]}
|
||||||
|
return plugin.get_ports(self.adminContext, filters=device_filter)[0]
|
||||||
|
|
||||||
|
def _add_router_interface(self, subnet, router, host):
|
||||||
|
interface_info = {'subnet_id': subnet['id']}
|
||||||
|
self.plugin.add_router_interface(self.adminContext,
|
||||||
|
router['id'], interface_info)
|
||||||
|
self.plugin.update_routers_states(
|
||||||
|
self.adminContext,
|
||||||
|
{router['id']: n_const.HA_ROUTER_STATE_ACTIVE}, host)
|
||||||
|
|
||||||
|
port = self._get_first_interface(subnet['network_id'], router['id'])
|
||||||
|
|
||||||
|
self.mock_cast.reset_mock()
|
||||||
|
self.mock_fanout.reset_mock()
|
||||||
|
self.callbacks.update_device_up(self.adminContext, agent_id=host,
|
||||||
|
device=port['id'], host=host)
|
||||||
|
return port
|
||||||
|
|
||||||
|
def _create_ha_router(self):
|
||||||
|
self._setup_l3()
|
||||||
|
router = self._create_router()
|
||||||
|
self._bind_router(router['id'])
|
||||||
|
return router
|
||||||
|
|
||||||
|
def _verify_remove_fdb(self, expected, agent_id, device, host=None):
|
||||||
|
self.mock_fanout.reset_mock()
|
||||||
|
self.callbacks.update_device_down(self.adminContext, agent_id=host,
|
||||||
|
device=device, host=host)
|
||||||
|
self.mock_fanout.assert_called_with(
|
||||||
|
mock.ANY, 'remove_fdb_entries', expected)
|
||||||
|
|
||||||
|
def test_other_agents_get_flood_entries_for_ha_agents(self):
|
||||||
|
# First HA router port is added on HOST and HOST2, then network port
|
||||||
|
# is added on HOST4.
|
||||||
|
# HOST4 should get flood entries for HOST1 and HOST2
|
||||||
|
router = self._create_ha_router()
|
||||||
|
service_plugins = manager.NeutronManager.get_service_plugins()
|
||||||
|
service_plugins[service_constants.L3_ROUTER_NAT] = self.plugin
|
||||||
|
with self.subnet(network=self._network, enable_dhcp=False) as snet, \
|
||||||
|
mock.patch('neutron.manager.NeutronManager.get_service_plugins',
|
||||||
|
return_value=service_plugins):
|
||||||
|
subnet = snet['subnet']
|
||||||
|
port = self._add_router_interface(subnet, router, HOST)
|
||||||
|
|
||||||
|
host_arg = {portbindings.HOST_ID: HOST_4, 'admin_state_up': True}
|
||||||
|
with self.port(subnet=snet,
|
||||||
|
device_owner=DEVICE_OWNER_COMPUTE,
|
||||||
|
arg_list=(portbindings.HOST_ID,),
|
||||||
|
**host_arg) as port1:
|
||||||
|
p1 = port1['port']
|
||||||
|
device1 = 'tap' + p1['id']
|
||||||
|
|
||||||
|
self.mock_cast.reset_mock()
|
||||||
|
self.mock_fanout.reset_mock()
|
||||||
|
self.callbacks.update_device_up(
|
||||||
|
self.adminContext, agent_id=HOST_4, device=device1)
|
||||||
|
|
||||||
|
cast_expected = {
|
||||||
|
port['network_id']: {
|
||||||
|
'ports': {'20.0.0.1': [constants.FLOODING_ENTRY],
|
||||||
|
'20.0.0.2': [constants.FLOODING_ENTRY]},
|
||||||
|
'network_type': 'vxlan', 'segment_id': 1}}
|
||||||
|
self.assertEqual(1, self.mock_cast.call_count)
|
||||||
|
self.mock_cast.assert_called_with(
|
||||||
|
mock.ANY, 'add_fdb_entries', cast_expected, HOST_4)
|
||||||
|
|
||||||
|
def test_delete_ha_port(self):
|
||||||
|
# First network port is added on HOST, and then HA router port
|
||||||
|
# is added on HOST and HOST2.
|
||||||
|
# Remove_fdb should carry flood entry of only HOST2 and not HOST
|
||||||
|
router = self._create_ha_router()
|
||||||
|
|
||||||
|
service_plugins = manager.NeutronManager.get_service_plugins()
|
||||||
|
service_plugins[service_constants.L3_ROUTER_NAT] = self.plugin
|
||||||
|
with self.subnet(network=self._network, enable_dhcp=False) as snet, \
|
||||||
|
mock.patch('neutron.manager.NeutronManager.get_service_plugins',
|
||||||
|
return_value=service_plugins):
|
||||||
|
host_arg = {portbindings.HOST_ID: HOST, 'admin_state_up': True}
|
||||||
|
with self.port(subnet=snet,
|
||||||
|
device_owner=DEVICE_OWNER_COMPUTE,
|
||||||
|
arg_list=(portbindings.HOST_ID,),
|
||||||
|
**host_arg) as port1:
|
||||||
|
p1 = port1['port']
|
||||||
|
device1 = 'tap' + p1['id']
|
||||||
|
self.callbacks.update_device_up(self.adminContext,
|
||||||
|
agent_id=HOST, device=device1)
|
||||||
|
|
||||||
|
subnet = snet['subnet']
|
||||||
|
port = self._add_router_interface(subnet, router, HOST)
|
||||||
|
|
||||||
|
expected = {port['network_id']:
|
||||||
|
{'ports': {'20.0.0.2': [constants.FLOODING_ENTRY]},
|
||||||
|
'network_type': 'vxlan', 'segment_id': 1}}
|
||||||
|
|
||||||
|
self.mock_fanout.reset_mock()
|
||||||
|
interface_info = {'subnet_id': subnet['id']}
|
||||||
|
self.plugin.remove_router_interface(self.adminContext,
|
||||||
|
router['id'], interface_info)
|
||||||
|
self.mock_fanout.assert_called_with(
|
||||||
|
mock.ANY, 'remove_fdb_entries', expected)
|
||||||
|
|
||||||
|
def test_ha_agents_get_other_fdb(self):
|
||||||
|
# First network port is added on HOST4, then HA router port is
|
||||||
|
# added on HOST and HOST2.
|
||||||
|
# Both HA agents should create tunnels to HOST4 and among themselves.
|
||||||
|
# Both HA agents should be notified to other agents.
|
||||||
|
router = self._create_ha_router()
|
||||||
|
|
||||||
|
service_plugins = manager.NeutronManager.get_service_plugins()
|
||||||
|
service_plugins[service_constants.L3_ROUTER_NAT] = self.plugin
|
||||||
|
with self.subnet(network=self._network, enable_dhcp=False) as snet, \
|
||||||
|
mock.patch('neutron.manager.NeutronManager.get_service_plugins',
|
||||||
|
return_value=service_plugins):
|
||||||
|
host_arg = {portbindings.HOST_ID: HOST_4, 'admin_state_up': True}
|
||||||
|
with self.port(subnet=snet,
|
||||||
|
device_owner=DEVICE_OWNER_COMPUTE,
|
||||||
|
arg_list=(portbindings.HOST_ID,),
|
||||||
|
**host_arg) as port1:
|
||||||
|
p1 = port1['port']
|
||||||
|
device1 = 'tap' + p1['id']
|
||||||
|
self.callbacks.update_device_up(
|
||||||
|
self.adminContext, agent_id=HOST_4, device=device1)
|
||||||
|
p1_ips = [p['ip_address'] for p in p1['fixed_ips']]
|
||||||
|
|
||||||
|
subnet = snet['subnet']
|
||||||
|
port = self._add_router_interface(subnet, router, HOST)
|
||||||
|
fanout_expected = {port['network_id']: {
|
||||||
|
'ports': {'20.0.0.1': [constants.FLOODING_ENTRY]},
|
||||||
|
'network_type': 'vxlan', 'segment_id': 1}}
|
||||||
|
|
||||||
|
cast_expected_host = {port['network_id']: {
|
||||||
|
'ports': {
|
||||||
|
'20.0.0.4': [constants.FLOODING_ENTRY,
|
||||||
|
l2pop_rpc.PortInfo(p1['mac_address'],
|
||||||
|
p1_ips[0])],
|
||||||
|
'20.0.0.2': [constants.FLOODING_ENTRY]},
|
||||||
|
'network_type': 'vxlan', 'segment_id': 1}}
|
||||||
|
self.mock_cast.assert_called_with(
|
||||||
|
mock.ANY, 'add_fdb_entries', cast_expected_host, HOST)
|
||||||
|
self.mock_fanout.assert_called_with(
|
||||||
|
mock.ANY, 'add_fdb_entries', fanout_expected)
|
||||||
|
|
||||||
|
self.mock_cast.reset_mock()
|
||||||
|
self.mock_fanout.reset_mock()
|
||||||
|
|
||||||
|
self.callbacks.update_device_up(
|
||||||
|
self.adminContext, agent_id=HOST_2,
|
||||||
|
device=port['id'], host=HOST_2)
|
||||||
|
|
||||||
|
cast_expected_host2 = {port['network_id']: {
|
||||||
|
'ports': {
|
||||||
|
'20.0.0.4': [constants.FLOODING_ENTRY,
|
||||||
|
l2pop_rpc.PortInfo(p1['mac_address'],
|
||||||
|
p1_ips[0])],
|
||||||
|
'20.0.0.1': [constants.FLOODING_ENTRY]},
|
||||||
|
'network_type': 'vxlan', 'segment_id': 1}}
|
||||||
|
fanout_expected = {port['network_id']: {
|
||||||
|
'ports': {'20.0.0.2': [constants.FLOODING_ENTRY]},
|
||||||
|
'network_type': 'vxlan', 'segment_id': 1}}
|
||||||
|
self.mock_cast.assert_called_with(
|
||||||
|
mock.ANY, 'add_fdb_entries', cast_expected_host2, HOST_2)
|
||||||
|
self.mock_fanout.assert_called_with(
|
||||||
|
mock.ANY, 'add_fdb_entries', fanout_expected)
|
||||||
|
|
||||||
def test_fdb_add_called(self):
|
def test_fdb_add_called(self):
|
||||||
self._register_ml2_agents()
|
self._register_ml2_agents()
|
||||||
|
|
||||||
|
@ -842,12 +1080,15 @@ class TestL2PopulationRpcTestCase(test_plugin.Ml2PluginV2TestCase):
|
||||||
l2pop_mech = l2pop_mech_driver.L2populationMechanismDriver()
|
l2pop_mech = l2pop_mech_driver.L2populationMechanismDriver()
|
||||||
l2pop_mech.L2PopulationAgentNotify = mock.Mock()
|
l2pop_mech.L2PopulationAgentNotify = mock.Mock()
|
||||||
l2pop_mech.rpc_ctx = mock.Mock()
|
l2pop_mech.rpc_ctx = mock.Mock()
|
||||||
|
port = {'device_owner': ''}
|
||||||
|
context = mock.Mock()
|
||||||
|
context.current = port
|
||||||
with mock.patch.object(l2pop_mech,
|
with mock.patch.object(l2pop_mech,
|
||||||
'_get_agent_fdb',
|
'_get_agent_fdb',
|
||||||
return_value=None) as upd_port_down,\
|
return_value=None) as upd_port_down,\
|
||||||
mock.patch.object(l2pop_mech.L2PopulationAgentNotify,
|
mock.patch.object(l2pop_mech.L2PopulationAgentNotify,
|
||||||
'remove_fdb_entries'):
|
'remove_fdb_entries'):
|
||||||
l2pop_mech.delete_port_postcommit(mock.Mock())
|
l2pop_mech.delete_port_postcommit(context)
|
||||||
self.assertTrue(upd_port_down.called)
|
self.assertTrue(upd_port_down.called)
|
||||||
|
|
||||||
def test_delete_unbound_port(self):
|
def test_delete_unbound_port(self):
|
||||||
|
|
|
@ -59,7 +59,8 @@ class RpcCallbacksTestCase(base.BaseTestCase):
|
||||||
'host': host
|
'host': host
|
||||||
}
|
}
|
||||||
with mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin'
|
with mock.patch('neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||||
'._device_to_port_id'):
|
'._device_to_port_id'),\
|
||||||
|
mock.patch.object(self.callbacks, 'notify_ha_port_status'):
|
||||||
with mock.patch('neutron.db.provisioning_blocks.'
|
with mock.patch('neutron.db.provisioning_blocks.'
|
||||||
'provisioning_complete') as pc:
|
'provisioning_complete') as pc:
|
||||||
self.callbacks.update_device_up(mock.Mock(), **kwargs)
|
self.callbacks.update_device_up(mock.Mock(), **kwargs)
|
||||||
|
@ -212,6 +213,7 @@ class RpcCallbacksTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
def _test_update_device_not_bound_to_host(self, func):
|
def _test_update_device_not_bound_to_host(self, func):
|
||||||
self.plugin.port_bound_to_host.return_value = False
|
self.plugin.port_bound_to_host.return_value = False
|
||||||
|
self.callbacks.notify_ha_port_status = mock.Mock()
|
||||||
self.plugin._device_to_port_id.return_value = 'fake_port_id'
|
self.plugin._device_to_port_id.return_value = 'fake_port_id'
|
||||||
res = func(mock.Mock(), device='fake_device', host='fake_host')
|
res = func(mock.Mock(), device='fake_device', host='fake_host')
|
||||||
self.plugin.port_bound_to_host.assert_called_once_with(mock.ANY,
|
self.plugin.port_bound_to_host.assert_called_once_with(mock.ANY,
|
||||||
|
@ -234,6 +236,7 @@ class RpcCallbacksTestCase(base.BaseTestCase):
|
||||||
|
|
||||||
def test_update_device_down_call_update_port_status(self):
|
def test_update_device_down_call_update_port_status(self):
|
||||||
self.plugin.update_port_status.return_value = False
|
self.plugin.update_port_status.return_value = False
|
||||||
|
self.callbacks.notify_ha_port_status = mock.Mock()
|
||||||
self.plugin._device_to_port_id.return_value = 'fake_port_id'
|
self.plugin._device_to_port_id.return_value = 'fake_port_id'
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
{'device': 'fake_device', 'exists': False},
|
{'device': 'fake_device', 'exists': False},
|
||||||
|
|
Loading…
Reference in New Issue