Add Local IP L2 extension flows
- setup local ARP responder - setup local ip translation flows (nat via conntrack) - setup local gARP blocker to prevent undesired Local IP ARP updates from other nodes (including real IP address owner) 2 new OF tables added for br-int: - LOCAL_EGRESS_TABLE - to save local ports vlans - LOCAL_IP_TABLE - for local ip handling Partial-Bug: #1930200 Change-Id: I49923958d1d602e3af4e02fadbec1b17798c49c8
This commit is contained in:
parent
1222962767
commit
b51d6958f3
|
@ -16,16 +16,21 @@
|
|||
import collections
|
||||
import sys
|
||||
|
||||
import netaddr
|
||||
from neutron_lib.agent import l2_extension
|
||||
from neutron_lib.callbacks import events as lib_events
|
||||
from neutron_lib.callbacks import registry as lib_registry
|
||||
from neutron_lib import context as lib_ctx
|
||||
from os_ken.lib.packet import ether_types
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.api.rpc.callbacks.consumer import registry
|
||||
from neutron.api.rpc.callbacks import events
|
||||
from neutron.api.rpc.callbacks import resources
|
||||
from neutron.api.rpc.handlers import resources_rpc
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent import (
|
||||
ovs_neutron_agent as ovs_agent)
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import (
|
||||
constants as ovs_constants)
|
||||
|
||||
|
@ -41,9 +46,15 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
'currently uses %(driver_type)s',
|
||||
{'driver_type': driver_type})
|
||||
sys.exit(1)
|
||||
if (cfg.CONF.SECURITYGROUP.enable_security_group and
|
||||
cfg.CONF.SECURITYGROUP.firewall_driver == 'openvswitch'):
|
||||
LOG.error('Local IP extension is not supported together with '
|
||||
'openvswitch firewall')
|
||||
sys.exit(1)
|
||||
|
||||
self.resource_rpc = resources_rpc.ResourcesPullRpcApi()
|
||||
self._register_rpc_consumers(connection)
|
||||
self.int_br = self.agent_api.request_int_br()
|
||||
|
||||
self.local_ip_updates = {
|
||||
'added': collections.defaultdict(dict),
|
||||
|
@ -61,8 +72,8 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
port_id = assoc.fixed_port_id
|
||||
lip_id = assoc.local_ip_id
|
||||
self.local_ip_updates['added'][port_id][lip_id] = assoc
|
||||
# No need to notify "port updated" here as on restart agent
|
||||
# handles all ports anyway
|
||||
# Notify agent about port update to handle Local IP flows
|
||||
self._notify_port_updated(context, port_id)
|
||||
|
||||
def consume_api(self, agent_api):
|
||||
"""Allows an extension to gain access to resources internal to the
|
||||
|
@ -115,14 +126,25 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
"""
|
||||
port_id = port['port_id']
|
||||
local_ip_updates = self._pop_local_ip_updates_for_port(port_id)
|
||||
|
||||
# if port doesn't yet have local vlan - issue port updated
|
||||
# notification to handle it on next agent loop, when port
|
||||
# should have it
|
||||
if ((local_ip_updates['added'] or local_ip_updates['deleted']) and
|
||||
not port.get('local_vlan')):
|
||||
LOG.debug("Port %s has no local VLAN assigned yet. "
|
||||
"Skipping Local IP handling till next iteration.")
|
||||
self._notify_port_updated(context, port['port_id'])
|
||||
return
|
||||
|
||||
for assoc in local_ip_updates['added'].values():
|
||||
LOG.info("Local IP added for port %s: %s",
|
||||
port_id, assoc.local_ip)
|
||||
# TBD
|
||||
self.add_local_ip_flows(port, assoc)
|
||||
for assoc in local_ip_updates['deleted'].values():
|
||||
LOG.info("Local IP deleted from port %s: %s",
|
||||
port_id, assoc.local_ip)
|
||||
# TBD
|
||||
self.delete_local_ip_flows(port, assoc)
|
||||
|
||||
def _pop_local_ip_updates_for_port(self, port_id):
|
||||
return {
|
||||
|
@ -130,6 +152,96 @@ class LocalIPAgentExtension(l2_extension.L2AgentExtension):
|
|||
'deleted': self.local_ip_updates['deleted'].pop(port_id, {})
|
||||
}
|
||||
|
||||
def add_local_ip_flows(self, port, assoc):
|
||||
local_ip_address = str(assoc.local_ip.local_ip_address)
|
||||
dest_mac = str(netaddr.EUI(port['mac_address'],
|
||||
dialect=ovs_agent._mac_mydialect))
|
||||
dest_ip = str(assoc.fixed_ip)
|
||||
vlan = port['local_vlan']
|
||||
self.setup_local_ip_translation(
|
||||
vlan=vlan, local_ip=local_ip_address,
|
||||
dest_ip=dest_ip, mac=port['mac_address'])
|
||||
self.int_br.install_arp_responder(
|
||||
vlan=vlan, ip=local_ip_address, mac=dest_mac,
|
||||
table_id=ovs_constants.LOCAL_IP_TABLE)
|
||||
self.int_br.install_garp_blocker(
|
||||
vlan=vlan, ip=local_ip_address)
|
||||
self.int_br.install_garp_blocker_exception(
|
||||
vlan=vlan, ip=local_ip_address, except_ip=dest_ip)
|
||||
|
||||
def delete_local_ip_flows(self, port, assoc):
|
||||
local_ip_address = str(assoc.local_ip.local_ip_address)
|
||||
dest_ip = str(assoc.fixed_ip)
|
||||
vlan = port['local_vlan']
|
||||
self.delete_local_ip_translation(
|
||||
vlan=vlan, local_ip=local_ip_address,
|
||||
dest_ip=dest_ip, mac=port['mac_address'])
|
||||
self.int_br.delete_arp_responder(
|
||||
vlan=vlan, ip=local_ip_address,
|
||||
table_id=ovs_constants.LOCAL_IP_TABLE)
|
||||
self.int_br.delete_garp_blocker(
|
||||
vlan=vlan, ip=local_ip_address)
|
||||
self.int_br.delete_garp_blocker_exception(
|
||||
vlan=vlan, ip=local_ip_address, except_ip=dest_ip)
|
||||
|
||||
def delete_port(self, context, port):
|
||||
self.local_ip_updates['added'].pop(port['port_id'], None)
|
||||
self.local_ip_updates['deleted'].pop(port['port_id'], None)
|
||||
|
||||
def setup_local_ip_translation(self, vlan, local_ip, dest_ip, mac):
|
||||
self.int_br.add_flow(
|
||||
table=ovs_constants.LOCAL_IP_TABLE,
|
||||
priority=10,
|
||||
nw_dst=local_ip,
|
||||
reg6=vlan,
|
||||
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
|
||||
actions='mod_dl_dst:{:s},'
|
||||
'ct(commit,table={:d},zone={:d},nat(dst={:s}))'.format(
|
||||
mac, ovs_constants.TRANSIENT_TABLE, vlan, dest_ip)
|
||||
)
|
||||
# avoid NAT to self and let VM with local IP access "true" IP owner
|
||||
self.int_br.add_flow(
|
||||
table=ovs_constants.LOCAL_IP_TABLE,
|
||||
priority=11,
|
||||
nw_src=dest_ip,
|
||||
nw_dst=local_ip,
|
||||
reg6=vlan,
|
||||
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
|
||||
actions='resubmit(,{:d})'.format(ovs_constants.TRANSIENT_TABLE)
|
||||
)
|
||||
self.int_br.add_flow(
|
||||
table=ovs_constants.LOCAL_IP_TABLE,
|
||||
priority=10,
|
||||
dl_src=mac,
|
||||
nw_src=dest_ip,
|
||||
reg6=vlan,
|
||||
ct_state="-trk",
|
||||
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
|
||||
actions='ct(table={:d},zone={:d},nat'.format(
|
||||
ovs_constants.TRANSIENT_TABLE, vlan)
|
||||
)
|
||||
|
||||
def delete_local_ip_translation(self, vlan, local_ip, dest_ip, mac):
|
||||
self.int_br.uninstall_flows(
|
||||
table_id=ovs_constants.LOCAL_IP_TABLE,
|
||||
priority=10,
|
||||
ipv4_dst=local_ip,
|
||||
reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_IP
|
||||
)
|
||||
self.int_br.uninstall_flows(
|
||||
table_id=ovs_constants.LOCAL_IP_TABLE,
|
||||
priority=11,
|
||||
ipv4_src=dest_ip,
|
||||
ipv4_dst=local_ip,
|
||||
reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_IP
|
||||
)
|
||||
self.int_br.uninstall_flows(
|
||||
table_id=ovs_constants.LOCAL_IP_TABLE,
|
||||
priority=10,
|
||||
eth_src=mac,
|
||||
ipv4_src=dest_ip,
|
||||
reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_IP
|
||||
)
|
||||
|
|
|
@ -57,6 +57,9 @@ ARP_SPOOF_TABLE = 24
|
|||
# Table for MAC spoof filtering
|
||||
MAC_SPOOF_TABLE = 25
|
||||
|
||||
LOCAL_EGRESS_TABLE = 30
|
||||
LOCAL_IP_TABLE = 31
|
||||
|
||||
# Table to decide whether further filtering is needed
|
||||
TRANSIENT_TABLE = 60
|
||||
LOCAL_MAC_DIRECT = 61
|
||||
|
@ -95,6 +98,8 @@ INT_BR_ALL_TABLES = (
|
|||
ARP_SPOOF_TABLE,
|
||||
MAC_SPOOF_TABLE,
|
||||
LOCAL_MAC_DIRECT,
|
||||
LOCAL_EGRESS_TABLE,
|
||||
LOCAL_IP_TABLE,
|
||||
TRANSIENT_TABLE,
|
||||
TRANSIENT_EGRESS_TABLE,
|
||||
BASE_EGRESS_TABLE,
|
||||
|
|
|
@ -65,6 +65,12 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
|
|||
self.install_normal(table_id=constants.TRANSIENT_EGRESS_TABLE,
|
||||
priority=3)
|
||||
|
||||
# Local IP defaults
|
||||
self.install_goto(dest_table_id=constants.TRANSIENT_TABLE,
|
||||
table_id=constants.LOCAL_EGRESS_TABLE)
|
||||
self.install_goto(dest_table_id=constants.TRANSIENT_TABLE,
|
||||
table_id=constants.LOCAL_IP_TABLE)
|
||||
|
||||
def init_dhcp(self, enable_openflow_dhcp=False, enable_dhcpv6=False):
|
||||
if not enable_openflow_dhcp:
|
||||
return
|
||||
|
@ -381,7 +387,7 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
|
|||
self.install_goto(
|
||||
table_id=constants.MAC_SPOOF_TABLE, priority=2,
|
||||
eth_src=address, in_port=port,
|
||||
dest_table_id=constants.TRANSIENT_TABLE)
|
||||
dest_table_id=constants.LOCAL_EGRESS_TABLE)
|
||||
# normalize so we can see if macs are the same
|
||||
mac_addresses = {netaddr.EUI(mac) for mac in mac_addresses}
|
||||
flows = self.dump_flows(constants.MAC_SPOOF_TABLE)
|
||||
|
@ -454,3 +460,68 @@ class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge,
|
|||
self.install_instructions(instructions, table_id=0,
|
||||
priority=65535, in_port=port, reg2=0,
|
||||
eth_type=0x86DD)
|
||||
|
||||
def setup_local_egress_flows(self, in_port, vlan):
|
||||
# Setting priority to 8 to give advantage to ARP/MAC spoofing rules
|
||||
self.install_goto(table_id=constants.LOCAL_SWITCHING,
|
||||
priority=8,
|
||||
in_port=in_port,
|
||||
dest_table_id=constants.LOCAL_EGRESS_TABLE)
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
actions = [ofpp.OFPActionSetField(reg6=vlan),
|
||||
ofpp.NXActionResubmitTable(
|
||||
in_port=in_port, table_id=constants.LOCAL_IP_TABLE)]
|
||||
instructions = [
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
||||
]
|
||||
self.install_instructions(instructions,
|
||||
table_id=constants.LOCAL_EGRESS_TABLE,
|
||||
priority=10, in_port=in_port)
|
||||
|
||||
@staticmethod
|
||||
def _arp_responder_match(ofp, ofpp, vlan, ip):
|
||||
return ofpp.OFPMatch(reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_ARP,
|
||||
arp_tpa=ip)
|
||||
|
||||
def _garp_blocker_match(self, vlan, ip):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
|
||||
eth_type=ether_types.ETH_TYPE_ARP,
|
||||
arp_spa=ip)
|
||||
|
||||
def install_garp_blocker(self, vlan, ip,
|
||||
table_id=constants.LOCAL_SWITCHING):
|
||||
match = self._garp_blocker_match(vlan, ip)
|
||||
self.install_drop(table_id=table_id,
|
||||
priority=10,
|
||||
match=match)
|
||||
|
||||
def delete_garp_blocker(self, vlan, ip,
|
||||
table_id=constants.LOCAL_SWITCHING):
|
||||
match = self._garp_blocker_match(vlan, ip)
|
||||
self.uninstall_flows(table_id=table_id,
|
||||
priority=10,
|
||||
match=match)
|
||||
|
||||
def _garp_blocker_exception_match(self, vlan, ip, except_ip):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
|
||||
eth_type=ether_types.ETH_TYPE_ARP,
|
||||
arp_spa=ip,
|
||||
arp_tpa=except_ip)
|
||||
|
||||
def install_garp_blocker_exception(self, vlan, ip, except_ip,
|
||||
table_id=constants.LOCAL_SWITCHING):
|
||||
match = self._garp_blocker_exception_match(vlan, ip, except_ip)
|
||||
self.install_goto(dest_table_id=constants.TRANSIENT_TABLE,
|
||||
table_id=table_id,
|
||||
priority=11,
|
||||
match=match)
|
||||
|
||||
def delete_garp_blocker_exception(self, vlan, ip, except_ip,
|
||||
table_id=constants.LOCAL_SWITCHING):
|
||||
match = self._garp_blocker_exception_match(vlan, ip, except_ip)
|
||||
self.uninstall_flows(table_id=table_id,
|
||||
priority=11,
|
||||
match=match)
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from os_ken.lib.packet import arp
|
||||
from os_ken.lib.packet import ether_types
|
||||
|
||||
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
|
||||
|
@ -204,28 +203,6 @@ class OVSTunnelBridge(ovs_bridge.OVSAgentBridge,
|
|||
eth_type=ether_types.ETH_TYPE_ARP,
|
||||
arp_tpa=ip)
|
||||
|
||||
def install_arp_responder(self, vlan, ip, mac):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match = self._arp_responder_match(ofp, ofpp, vlan, ip)
|
||||
actions = [ofpp.OFPActionSetField(arp_op=arp.ARP_REPLY),
|
||||
ofpp.NXActionRegMove(src_field='arp_sha',
|
||||
dst_field='arp_tha',
|
||||
n_bits=48),
|
||||
ofpp.NXActionRegMove(src_field='arp_spa',
|
||||
dst_field='arp_tpa',
|
||||
n_bits=32),
|
||||
ofpp.OFPActionSetField(arp_sha=mac),
|
||||
ofpp.OFPActionSetField(arp_spa=ip),
|
||||
ofpp.NXActionRegMove(src_field='eth_src',
|
||||
dst_field='eth_dst',
|
||||
n_bits=48),
|
||||
ofpp.OFPActionSetField(eth_src=mac),
|
||||
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0)]
|
||||
self.install_apply_actions(table_id=constants.ARP_RESPONDER,
|
||||
priority=1,
|
||||
match=match,
|
||||
actions=actions)
|
||||
|
||||
def delete_arp_responder(self, vlan, ip):
|
||||
(_dp, ofp, ofpp) = self._get_dp()
|
||||
if ip is None:
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from os_ken.lib.packet import arp
|
||||
from os_ken.lib.packet import ether_types
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import excutils
|
||||
|
||||
|
@ -96,3 +98,39 @@ class OVSAgentBridge(ofswitch.OpenFlowSwitchMixin,
|
|||
|
||||
def drop_port(self, in_port):
|
||||
self.install_drop(priority=2, in_port=in_port)
|
||||
|
||||
@staticmethod
|
||||
def _arp_responder_match(ofp, ofpp, vlan, ip):
|
||||
return ofpp.OFPMatch(eth_type=ether_types.ETH_TYPE_ARP,
|
||||
arp_tpa=ip)
|
||||
|
||||
def install_arp_responder(self, vlan, ip, mac,
|
||||
table_id=ovs_consts.ARP_RESPONDER):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match = self._arp_responder_match(ofp, ofpp, vlan, ip)
|
||||
actions = [ofpp.OFPActionSetField(arp_op=arp.ARP_REPLY),
|
||||
ofpp.NXActionRegMove(src_field='arp_sha',
|
||||
dst_field='arp_tha',
|
||||
n_bits=48),
|
||||
ofpp.NXActionRegMove(src_field='arp_spa',
|
||||
dst_field='arp_tpa',
|
||||
n_bits=32),
|
||||
ofpp.OFPActionSetField(arp_sha=mac),
|
||||
ofpp.OFPActionSetField(arp_spa=ip),
|
||||
ofpp.NXActionRegMove(src_field='eth_src',
|
||||
dst_field='eth_dst',
|
||||
n_bits=48),
|
||||
ofpp.OFPActionSetField(eth_src=mac),
|
||||
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0)]
|
||||
self.install_apply_actions(table_id=table_id,
|
||||
priority=1,
|
||||
match=match,
|
||||
actions=actions)
|
||||
|
||||
def delete_arp_responder(self, vlan, ip,
|
||||
table_id=ovs_consts.ARP_RESPONDER):
|
||||
(_dp, ofp, ofpp) = self._get_dp()
|
||||
match = self._arp_responder_match(ofp, ofpp, vlan, ip)
|
||||
self.uninstall_flows(table_id=table_id,
|
||||
priority=1,
|
||||
match=match)
|
||||
|
|
|
@ -159,6 +159,7 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
|||
ovs_conf = self.conf.OVS
|
||||
|
||||
self.enable_openflow_dhcp = 'dhcp' in self.ext_manager.names()
|
||||
self.enable_local_ips = 'local_ip' in self.ext_manager.names()
|
||||
|
||||
self.fullsync = False
|
||||
# init bridge classes with configured datapath type.
|
||||
|
@ -702,6 +703,12 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
|||
lvm = self.vlan_manager.get(network_id)
|
||||
return lvm.vlan
|
||||
|
||||
def _get_net_local_vlan_or_none(self, net_id):
|
||||
try:
|
||||
return self.vlan_manager.get(net_id).vlan
|
||||
except vlanmanager.MappingNotFound:
|
||||
return None
|
||||
|
||||
def _deferred_delete_direct_flows(self, ports):
|
||||
if not self.direct_for_non_openflow_firewall:
|
||||
return
|
||||
|
@ -1967,6 +1974,8 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
|||
LOG.info("Port %(device)s updated. Details: %(details)s",
|
||||
{'device': device, 'details': details})
|
||||
details['vif_port'] = port
|
||||
details['local_vlan'] = self._get_net_local_vlan_or_none(
|
||||
details['network_id'])
|
||||
need_binding = self.treat_vif_port(port, details['port_id'],
|
||||
details['network_id'],
|
||||
details['network_type'],
|
||||
|
@ -2182,10 +2191,21 @@ class OVSNeutronAgent(l2population_rpc.L2populationRpcCallBackTunnelMixin,
|
|||
dest_table_id=constants.TRANSIENT_EGRESS_TABLE)
|
||||
|
||||
def process_install_ports_egress_flows(self, ports):
|
||||
if self.direct_for_non_openflow_firewall:
|
||||
with self.int_br.deferred(full_ordered=True,
|
||||
use_bundle=True) as int_br:
|
||||
for port in ports:
|
||||
if self.enable_local_ips and port['device_owner'].startswith(
|
||||
n_const.DEVICE_OWNER_COMPUTE_PREFIX):
|
||||
try:
|
||||
lvm = self.vlan_manager.get(port['network_id'])
|
||||
self.int_br.setup_local_egress_flows(
|
||||
port['vif_port'].ofport, lvm.vlan)
|
||||
except Exception as err:
|
||||
LOG.warning("Failed to install egress flows "
|
||||
"for port %s, error: %s",
|
||||
port['port_id'], err)
|
||||
|
||||
if self.direct_for_non_openflow_firewall:
|
||||
try:
|
||||
self.install_accepted_egress_direct_flow(port, int_br)
|
||||
except Exception as err:
|
||||
|
|
|
@ -18,6 +18,7 @@ from unittest import mock
|
|||
from neutron_lib.callbacks import events as lib_events
|
||||
from neutron_lib.callbacks import registry as lib_registry
|
||||
from neutron_lib import context
|
||||
from os_ken.lib.packet import ether_types
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.agent.l2.extensions import local_ip as local_ip_ext
|
||||
|
@ -36,25 +37,25 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
|
|||
self.context = context.get_admin_context_without_session()
|
||||
self.local_ip_ext = local_ip_ext.LocalIPAgentExtension()
|
||||
|
||||
self.int_br = mock.Mock()
|
||||
self.tun_br = mock.Mock()
|
||||
self.plugin_rpc = mock.Mock()
|
||||
self.agent_api = ovs_ext_api.OVSAgentExtensionAPI(
|
||||
self.int_br,
|
||||
self.tun_br,
|
||||
int_br=mock.Mock(),
|
||||
tun_br=mock.Mock(),
|
||||
phys_brs=None,
|
||||
plugin_rpc=self.plugin_rpc)
|
||||
self.local_ip_ext.consume_api(self.agent_api)
|
||||
with mock.patch.object(
|
||||
self.local_ip_ext, '_pull_all_local_ip_associations'):
|
||||
self.local_ip_ext.initialize(mock.Mock(), 'ovs')
|
||||
self.int_br = self.local_ip_ext.int_br
|
||||
|
||||
def _generate_test_lip_associations(self, count=2):
|
||||
return [lip_obj.LocalIPAssociation(
|
||||
fixed_port_id=uuidutils.generate_uuid(),
|
||||
local_ip_id=uuidutils.generate_uuid(),
|
||||
local_ip=lip_obj.LocalIP()) for _ in range(count)
|
||||
]
|
||||
fixed_ip='10.0.0.10',
|
||||
local_ip=lip_obj.LocalIP(
|
||||
local_ip_address='172.16.0.10')) for _ in range(count)]
|
||||
|
||||
def test_pulling_lip_associations_on_init(self):
|
||||
res_rpc = mock.Mock()
|
||||
|
@ -124,16 +125,22 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
|
|||
def test_handle_port(self):
|
||||
lip_assocs = self.test_handle_updated_notification()
|
||||
for assoc in lip_assocs:
|
||||
port = {'port_id': assoc.fixed_port_id}
|
||||
with mock.patch.object(self.local_ip_ext,
|
||||
'add_local_ip_flows') as add_lip_flows:
|
||||
port = {'port_id': assoc.fixed_port_id, 'local_vlan': 1}
|
||||
self.local_ip_ext.handle_port(self.context, port)
|
||||
self.assertEqual({}, self.local_ip_ext.local_ip_updates[
|
||||
'added'][assoc.fixed_port_id])
|
||||
add_lip_flows.assert_called_once_with(port, assoc)
|
||||
self.test_handle_deleted_notification(lip_assocs)
|
||||
for assoc in lip_assocs:
|
||||
port = {'port_id': assoc.fixed_port_id}
|
||||
with mock.patch.object(self.local_ip_ext,
|
||||
'delete_local_ip_flows') as del_lip_flows:
|
||||
port = {'port_id': assoc.fixed_port_id, 'local_vlan': 1}
|
||||
self.local_ip_ext.handle_port(self.context, port)
|
||||
self.assertEqual({}, self.local_ip_ext.local_ip_updates[
|
||||
'deleted'][assoc.fixed_port_id])
|
||||
del_lip_flows.assert_called_once_with(port, assoc)
|
||||
|
||||
def test_delete_port(self):
|
||||
lip_assocs = self.test_handle_updated_notification()
|
||||
|
@ -143,3 +150,126 @@ class LocalIPAgentExtensionTestCase(base.BaseTestCase):
|
|||
|
||||
self.assertEqual({}, self.local_ip_ext.local_ip_updates['added'])
|
||||
self.assertEqual({}, self.local_ip_ext.local_ip_updates['added'])
|
||||
|
||||
def test_add_local_ip_flows(self):
|
||||
assoc = self._generate_test_lip_associations(1)[0]
|
||||
port = {'port_id': assoc.fixed_port_id,
|
||||
'mac_address': 'fa:16:3e:11:22:33',
|
||||
'local_vlan': 1234}
|
||||
with mock.patch.object(self.local_ip_ext,
|
||||
'setup_local_ip_translation') as set_lip_trans:
|
||||
self.local_ip_ext.add_local_ip_flows(port, assoc)
|
||||
set_lip_trans.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
local_ip=str(assoc.local_ip.local_ip_address),
|
||||
dest_ip=str(assoc.fixed_ip),
|
||||
mac=port['mac_address']
|
||||
)
|
||||
self.int_br.install_arp_responder.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
ip=str(assoc.local_ip.local_ip_address),
|
||||
mac=port['mac_address'], table_id=31)
|
||||
self.int_br.install_garp_blocker.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
ip=str(assoc.local_ip.local_ip_address))
|
||||
self.int_br.install_garp_blocker_exception.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
ip=str(assoc.local_ip.local_ip_address),
|
||||
except_ip=str(assoc.fixed_ip))
|
||||
|
||||
def test_delete_local_ip_flows(self):
|
||||
assoc = self._generate_test_lip_associations(1)[0]
|
||||
port = {'port_id': assoc.fixed_port_id,
|
||||
'mac_address': 'fa:16:3e:11:22:33',
|
||||
'local_vlan': 1234}
|
||||
with mock.patch.object(self.local_ip_ext,
|
||||
'delete_local_ip_translation') as del_lip_trans:
|
||||
self.local_ip_ext.delete_local_ip_flows(port, assoc)
|
||||
del_lip_trans.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
local_ip=str(assoc.local_ip.local_ip_address),
|
||||
dest_ip=str(assoc.fixed_ip),
|
||||
mac=port['mac_address']
|
||||
)
|
||||
self.int_br.delete_arp_responder.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
ip=str(assoc.local_ip.local_ip_address),
|
||||
table_id=31)
|
||||
self.int_br.delete_garp_blocker.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
ip=str(assoc.local_ip.local_ip_address))
|
||||
self.int_br.delete_garp_blocker_exception.assert_called_once_with(
|
||||
vlan=port['local_vlan'],
|
||||
ip=str(assoc.local_ip.local_ip_address),
|
||||
except_ip=str(assoc.fixed_ip))
|
||||
|
||||
def test_setup_local_ip_translation(self):
|
||||
vlan = 1234
|
||||
local_ip = '172.0.0.10'
|
||||
dest_ip = '10.0.0.10'
|
||||
mac = 'fa:16:3e:11:22:33'
|
||||
self.local_ip_ext.setup_local_ip_translation(
|
||||
vlan, local_ip, dest_ip, mac)
|
||||
|
||||
expected_calls = [
|
||||
mock.call(
|
||||
table=31,
|
||||
priority=10,
|
||||
nw_dst=local_ip,
|
||||
reg6=vlan,
|
||||
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
|
||||
actions='mod_dl_dst:{:s},'
|
||||
'ct(commit,table={:d},zone={:d},nat(dst={:s}))'.format(
|
||||
mac, 60, vlan, dest_ip)),
|
||||
mock.call(
|
||||
table=31,
|
||||
priority=11,
|
||||
nw_src=dest_ip,
|
||||
nw_dst=local_ip,
|
||||
reg6=vlan,
|
||||
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
|
||||
actions='resubmit(,{:d})'.format(60)),
|
||||
mock.call(
|
||||
table=31,
|
||||
priority=10,
|
||||
dl_src=mac,
|
||||
nw_src=dest_ip,
|
||||
reg6=vlan,
|
||||
ct_state="-trk",
|
||||
dl_type="0x{:04x}".format(ether_types.ETH_TYPE_IP),
|
||||
actions='ct(table={:d},zone={:d},nat'.format(60, vlan))
|
||||
]
|
||||
self.assertEqual(expected_calls, self.int_br.add_flow.mock_calls)
|
||||
|
||||
def test_delete_local_ip_translation(self):
|
||||
vlan = 1234
|
||||
local_ip = '172.0.0.10'
|
||||
dest_ip = '10.0.0.10'
|
||||
mac = 'fa:16:3e:11:22:33'
|
||||
self.local_ip_ext.delete_local_ip_translation(
|
||||
vlan, local_ip, dest_ip, mac)
|
||||
|
||||
expected_calls = [
|
||||
mock.call(
|
||||
table_id=31,
|
||||
priority=10,
|
||||
ipv4_dst=local_ip,
|
||||
reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_IP),
|
||||
mock.call(
|
||||
table_id=31,
|
||||
priority=11,
|
||||
ipv4_src=dest_ip,
|
||||
ipv4_dst=local_ip,
|
||||
reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_IP),
|
||||
mock.call(
|
||||
table_id=31,
|
||||
priority=10,
|
||||
eth_src=mac,
|
||||
ipv4_src=dest_ip,
|
||||
reg6=vlan,
|
||||
eth_type=ether_types.ETH_TYPE_IP)
|
||||
]
|
||||
self.assertEqual(
|
||||
expected_calls, self.int_br.uninstall_flows.mock_calls)
|
||||
|
|
|
@ -157,6 +157,55 @@ class OVSBridgeTestBase(ovs_test_base.OVSOSKenTestBase):
|
|||
add_protocols.assert_called_once_with(
|
||||
constants.OPENFLOW10, constants.OPENFLOW13)
|
||||
|
||||
def test_install_arp_responder(self):
|
||||
vlan = 3333
|
||||
ip = '192.0.2.1'
|
||||
mac = '08:60:6e:7f:74:e7'
|
||||
self.br.install_arp_responder(vlan=vlan, ip=ip, mac=mac)
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call._send_msg(ofpp.OFPFlowMod(dp,
|
||||
cookie=self.stamp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
|
||||
ofpp.OFPActionSetField(arp_op=2),
|
||||
ofpp.NXActionRegMove(
|
||||
dst_field='arp_tha',
|
||||
n_bits=48,
|
||||
src_field='arp_sha'),
|
||||
ofpp.NXActionRegMove(
|
||||
dst_field='arp_tpa',
|
||||
n_bits=32,
|
||||
src_field='arp_spa'),
|
||||
ofpp.OFPActionSetField(arp_sha=mac),
|
||||
ofpp.OFPActionSetField(arp_spa=ip),
|
||||
ofpp.NXActionRegMove(src_field='eth_src',
|
||||
dst_field='eth_dst',
|
||||
n_bits=48),
|
||||
ofpp.OFPActionSetField(eth_src=mac),
|
||||
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0),
|
||||
]),
|
||||
],
|
||||
match=self.br._arp_responder_match(ofp, ofpp, vlan, ip),
|
||||
priority=1,
|
||||
table_id=21),
|
||||
active_bundle=None),
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_delete_arp_responder(self):
|
||||
vlan = 3333
|
||||
ip = '192.0.2.1'
|
||||
self.br.delete_arp_responder(vlan=vlan, ip=ip)
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call.uninstall_flows(
|
||||
table_id=21,
|
||||
priority=1,
|
||||
match=self.br._arp_responder_match(ofp, ofpp, vlan, ip))
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
|
||||
class OVSDVRProcessTestMixin(object):
|
||||
def test_install_dvr_process_ipv4(self):
|
||||
|
|
|
@ -127,6 +127,24 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
|
|||
priority=3,
|
||||
table_id=constants.TRANSIENT_EGRESS_TABLE),
|
||||
active_bundle=None),
|
||||
call._send_msg(ofpp.OFPFlowMod(
|
||||
dp, cookie=self.stamp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=60),
|
||||
],
|
||||
match=ofpp.OFPMatch(),
|
||||
priority=0,
|
||||
table_id=30),
|
||||
active_bundle=None),
|
||||
call._send_msg(ofpp.OFPFlowMod(
|
||||
dp, cookie=self.stamp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=60),
|
||||
],
|
||||
match=ofpp.OFPMatch(),
|
||||
priority=0,
|
||||
table_id=31),
|
||||
active_bundle=None),
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
|
@ -671,3 +689,103 @@ class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
|
|||
active_bundle=None)
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_setup_local_egress_flows(self):
|
||||
in_port = 10
|
||||
vlan = 3333
|
||||
self.br.setup_local_egress_flows(in_port=in_port, vlan=vlan)
|
||||
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=30)],
|
||||
match=ofpp.OFPMatch(in_port=in_port),
|
||||
priority=8,
|
||||
table_id=0),
|
||||
active_bundle=None),
|
||||
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
|
||||
instructions=[ofpp.OFPInstructionActions(
|
||||
ofp.OFPIT_APPLY_ACTIONS,
|
||||
[ofpp.OFPActionSetField(reg6=vlan),
|
||||
ofpp.NXActionResubmitTable(in_port=in_port,
|
||||
table_id=31)])],
|
||||
match=ofpp.OFPMatch(in_port=in_port),
|
||||
priority=10, table_id=30),
|
||||
active_bundle=None)
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_install_garp_blocker(self):
|
||||
vlan = 2222
|
||||
ip = '192.0.0.10'
|
||||
self.br.install_garp_blocker(vlan, ip)
|
||||
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
|
||||
instructions=[],
|
||||
match=ofpp.OFPMatch(
|
||||
vlan_vid=vlan | ofp.OFPVID_PRESENT,
|
||||
eth_type=self.ether_types.ETH_TYPE_ARP,
|
||||
arp_spa=ip),
|
||||
priority=10,
|
||||
table_id=0),
|
||||
active_bundle=None)]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_delete_garp_blocker(self):
|
||||
vlan = 2222
|
||||
ip = '192.0.0.10'
|
||||
self.br.delete_garp_blocker(vlan, ip)
|
||||
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call.uninstall_flows(
|
||||
table_id=0,
|
||||
priority=10,
|
||||
match=ofpp.OFPMatch(
|
||||
vlan_vid=vlan | ofp.OFPVID_PRESENT,
|
||||
eth_type=self.ether_types.ETH_TYPE_ARP,
|
||||
arp_spa=ip))
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_install_garp_blocker_exception(self):
|
||||
vlan = 2222
|
||||
ip = '192.0.0.10'
|
||||
except_ip = '192.0.0.20'
|
||||
self.br.install_garp_blocker_exception(vlan, ip, except_ip)
|
||||
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call._send_msg(ofpp.OFPFlowMod(dp, cookie=self.stamp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=60)],
|
||||
match=ofpp.OFPMatch(
|
||||
vlan_vid=vlan | ofp.OFPVID_PRESENT,
|
||||
eth_type=self.ether_types.ETH_TYPE_ARP,
|
||||
arp_spa=ip, arp_tpa=except_ip),
|
||||
priority=11,
|
||||
table_id=0),
|
||||
active_bundle=None)
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_delete_garp_blocker_exception(self):
|
||||
vlan = 2222
|
||||
ip = '192.0.0.10'
|
||||
except_ip = '192.0.0.20'
|
||||
self.br.delete_garp_blocker_exception(vlan, ip, except_ip)
|
||||
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call.uninstall_flows(
|
||||
table_id=0,
|
||||
priority=11,
|
||||
match=ofpp.OFPMatch(
|
||||
vlan_vid=vlan | ofp.OFPVID_PRESENT,
|
||||
eth_type=self.ether_types.ETH_TYPE_ARP,
|
||||
arp_spa=ip, arp_tpa=except_ip))
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
|
|
@ -405,45 +405,6 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
|
|||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_install_arp_responder(self):
|
||||
vlan = 3333
|
||||
ip = '192.0.2.1'
|
||||
mac = '08:60:6e:7f:74:e7'
|
||||
self.br.install_arp_responder(vlan=vlan, ip=ip, mac=mac)
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
expected = [
|
||||
call._send_msg(ofpp.OFPFlowMod(dp,
|
||||
cookie=self.stamp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
|
||||
ofpp.OFPActionSetField(arp_op=self.arp.ARP_REPLY),
|
||||
ofpp.NXActionRegMove(
|
||||
dst_field='arp_tha',
|
||||
n_bits=48,
|
||||
src_field='arp_sha'),
|
||||
ofpp.NXActionRegMove(
|
||||
dst_field='arp_tpa',
|
||||
n_bits=32,
|
||||
src_field='arp_spa'),
|
||||
ofpp.OFPActionSetField(arp_sha=mac),
|
||||
ofpp.OFPActionSetField(arp_spa=ip),
|
||||
ofpp.NXActionRegMove(src_field='eth_src',
|
||||
dst_field='eth_dst',
|
||||
n_bits=48),
|
||||
ofpp.OFPActionSetField(eth_src=mac),
|
||||
ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0),
|
||||
]),
|
||||
],
|
||||
match=ofpp.OFPMatch(
|
||||
eth_type=self.ether_types.ETH_TYPE_ARP,
|
||||
arp_tpa=ip,
|
||||
vlan_vid=vlan | ofp.OFPVID_PRESENT),
|
||||
priority=1,
|
||||
table_id=21),
|
||||
active_bundle=None),
|
||||
]
|
||||
self.assertEqual(expected, self.mock.mock_calls)
|
||||
|
||||
def test_delete_arp_responder(self):
|
||||
vlan = 3333
|
||||
ip = '192.0.2.1'
|
||||
|
|
Loading…
Reference in New Issue