8ac9e0b2e0
Highlights: - Deprecate br-tun. - Reduce the use of OFPP_NORMAL action. Use mac address info obtained from get_device_details and l2-pop to reduce flooding. - Use OpenFlow metadata instead of "internal" VLANs. Now tenant networks are VLAN transparent. Implements: blueprint ofagent-port-monitor Implements: blueprint ofagent-merge-bridges Change-Id: I21ee1c6d141863182b487e10c7bfe911b1a472ab
408 lines
15 KiB
Python
408 lines
15 KiB
Python
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
|
# Copyright (C) 2014 YAMAMOTO Takashi <yamamoto at valinux co jp>
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""
|
|
OpenFlow1.3 flow table for OFAgent
|
|
|
|
* requirements
|
|
** plain OpenFlow 1.3. no vendor extensions.
|
|
|
|
* legends
|
|
xxx: network id (agent internal use)
|
|
yyy: segment id (vlan id, gre key, ...)
|
|
a,b,c: tunnel port (tun_ofports, map[net_id].tun_ofports)
|
|
i,j,k: vm port (map[net_id].vif_ports[vif_id].ofport)
|
|
x,y,z: physical port (int_ofports)
|
|
N: tunnel type (0 for TYPE_GRE, 1 for TYPE_xxx, ...)
|
|
iii: unknown ip address
|
|
uuu: unicast l2 address
|
|
|
|
* tables (in order)
|
|
CHECK_IN_PORT
|
|
TUNNEL_IN+N
|
|
PHYS_IN
|
|
LOCAL_IN
|
|
ARP_PASSTHROUGH
|
|
ARP_RESPONDER
|
|
TUNNEL_OUT
|
|
LOCAL_OUT
|
|
PHYS_OUT
|
|
TUNNEL_FLOOD+N
|
|
PHYS_FLOOD
|
|
LOCAL_FLOOD
|
|
|
|
* CHECK_IN_PORT
|
|
|
|
for each vm ports:
|
|
// check_in_port_add_local_port, check_in_port_delete_port
|
|
in_port=i, write_metadata(LOCAL|xxx),goto(LOCAL_IN)
|
|
TYPE_GRE
|
|
for each tunnel ports:
|
|
// check_in_port_add_tunnel_port, check_in_port_delete_port
|
|
in_port=a, goto(TUNNEL_IN+N)
|
|
TYPE_VLAN
|
|
for each networks ports:
|
|
// provision_tenant_physnet, reclaim_tenant_physnet
|
|
in_port=x,vlan_vid=present|yyy, write_metadata(xxx),goto(PHYS_IN)
|
|
TYPE_FLAT
|
|
// provision_tenant_physnet, reclaim_tenant_physnet
|
|
in_port=x, write_metadata(xxx),goto(PHYS_IN)
|
|
default drop
|
|
|
|
* TUNNEL_IN+N (per tunnel types) tunnel -> network
|
|
|
|
for each networks:
|
|
// provision_tenant_tunnel, reclaim_tenant_tunnel
|
|
tun_id=yyy, write_metadata(xxx),goto(TUNNEL_OUT)
|
|
|
|
default drop
|
|
|
|
* PHYS_IN
|
|
default goto(TUNNEL_OUT)
|
|
|
|
* LOCAL_IN
|
|
default goto(next_table)
|
|
|
|
* ARP_PASSTHROUGH
|
|
for each unknown tpa:
|
|
// arp_passthrough
|
|
arp,arp_op=request,metadata=xxx,tpa=iii, idle_timeout=5, goto(TUNNEL_OUT)
|
|
default goto(next_table)
|
|
|
|
* ARP_RESPONDER
|
|
arp,arp_op=request, output:controller
|
|
default goto(next_table)
|
|
|
|
* TUNNEL_OUT
|
|
TYPE_GRE
|
|
// !FLOODING_ENTRY
|
|
// install_tunnel_output, delete_tunnel_output
|
|
metadata=LOCAL|xxx,eth_dst=uuu set_tunnel(yyy),output:a
|
|
|
|
default goto(next table)
|
|
|
|
* LOCAL_OUT
|
|
for each known destinations:
|
|
// local_out_add_port, local_out_delete_port
|
|
metadata=xxx,eth_dst=uuu output:i
|
|
default goto(next table)
|
|
|
|
* PHYS_OUT
|
|
|
|
NOTE(yamamoto): currently this table is always empty.
|
|
|
|
default goto(next table)
|
|
|
|
* TUNNEL_FLOOD+N. (per tunnel types)
|
|
|
|
network -> tunnel/vlan
|
|
output to tunnel/physical ports
|
|
"next table" might be LOCAL_OUT
|
|
TYPE_GRE
|
|
for each networks:
|
|
// FLOODING_ENTRY
|
|
// install_tunnel_output, delete_tunnel_output
|
|
metadata=LOCAL|xxx, set_tunnel(yyy),output:a,b,c,goto(next table)
|
|
|
|
default goto(next table)
|
|
|
|
* PHYS_FLOOD
|
|
|
|
TYPE_VLAN
|
|
for each networks:
|
|
// provision_tenant_physnet, reclaim_tenant_physnet
|
|
metadata=LOCAL|xxx, push_vlan:0x8100,set_field:present|yyy->vlan_vid,
|
|
output:x,pop_vlan,goto(next table)
|
|
TYPE_FLAT
|
|
for each networks:
|
|
// provision_tenant_physnet, reclaim_tenant_physnet
|
|
metadata=LOCAL|xxx, output:x,goto(next table)
|
|
|
|
default goto(next table)
|
|
|
|
* LOCAL_FLOOD
|
|
|
|
for each networks:
|
|
// local_flood_update, local_flood_delete
|
|
metadata=xxx, output:i,j,k
|
|
or
|
|
metadata=xxx,eth_dst=broadcast, output:i,j,k
|
|
|
|
default drop
|
|
|
|
* references
|
|
** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
|
|
*** we use metadata instead of "internal" VLANs
|
|
*** we don't want to use NX learn action
|
|
"""
|
|
|
|
from ryu.lib.packet import arp
|
|
from ryu.ofproto import ether
|
|
|
|
from neutron.plugins.common import constants as p_const
|
|
import neutron.plugins.ofagent.agent.metadata as meta
|
|
from neutron.plugins.ofagent.agent import ofswitch
|
|
from neutron.plugins.ofagent.agent import tables
|
|
|
|
|
|
class OFAgentIntegrationBridge(ofswitch.OpenFlowSwitch):
|
|
"""ofagent br-int specific logic."""
|
|
|
|
def setup_default_table(self):
|
|
self.delete_flows()
|
|
|
|
self.install_default_drop(tables.CHECK_IN_PORT)
|
|
|
|
for t in tables.TUNNEL_IN.values():
|
|
self.install_default_drop(t)
|
|
self.install_default_goto(tables.PHYS_IN, tables.TUNNEL_OUT)
|
|
self.install_default_goto_next(tables.LOCAL_IN)
|
|
self.install_default_goto_next(tables.ARP_PASSTHROUGH)
|
|
self.install_arp_responder(tables.ARP_RESPONDER)
|
|
|
|
self.install_default_goto_next(tables.TUNNEL_OUT)
|
|
self.install_default_goto_next(tables.LOCAL_OUT)
|
|
self.install_default_goto_next(tables.PHYS_OUT)
|
|
|
|
for t in tables.TUNNEL_FLOOD.values():
|
|
self.install_default_goto_next(t)
|
|
self.install_default_goto_next(tables.PHYS_FLOOD)
|
|
self.install_default_drop(tables.LOCAL_FLOOD)
|
|
|
|
def install_arp_responder(self, table_id):
|
|
(dp, ofp, ofpp) = self._get_dp()
|
|
match = ofpp.OFPMatch(eth_type=ether.ETH_TYPE_ARP,
|
|
arp_op=arp.ARP_REQUEST)
|
|
actions = [ofpp.OFPActionOutput(ofp.OFPP_CONTROLLER)]
|
|
instructions = [
|
|
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=table_id,
|
|
priority=1,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
self.install_default_goto_next(table_id)
|
|
|
|
def install_tunnel_output(self, table_id,
|
|
network, segmentation_id,
|
|
ports, goto_next, **additional_matches):
|
|
(dp, ofp, ofpp) = self._get_dp()
|
|
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network, meta.LOCAL),
|
|
**additional_matches)
|
|
actions = [ofpp.OFPActionSetField(tunnel_id=segmentation_id)]
|
|
actions += [ofpp.OFPActionOutput(port=p) for p in ports]
|
|
instructions = [
|
|
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
|
]
|
|
if goto_next:
|
|
instructions += [
|
|
ofpp.OFPInstructionGotoTable(table_id=table_id + 1),
|
|
]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=table_id,
|
|
priority=1,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
|
|
def delete_tunnel_output(self, table_id,
|
|
network, **additional_matches):
|
|
(dp, _ofp, ofpp) = self._get_dp()
|
|
self.delete_flows(table_id=table_id,
|
|
metadata=meta.mk_metadata(network, meta.LOCAL),
|
|
**additional_matches)
|
|
|
|
def provision_tenant_tunnel(self, network_type, network, segmentation_id):
|
|
(dp, _ofp, ofpp) = self._get_dp()
|
|
match = ofpp.OFPMatch(tunnel_id=segmentation_id)
|
|
metadata = meta.mk_metadata(network)
|
|
instructions = [
|
|
ofpp.OFPInstructionWriteMetadata(metadata=metadata[0],
|
|
metadata_mask=metadata[1]),
|
|
ofpp.OFPInstructionGotoTable(table_id=tables.TUNNEL_OUT),
|
|
]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=tables.TUNNEL_IN[network_type],
|
|
priority=1,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
|
|
def reclaim_tenant_tunnel(self, network_type, network, segmentation_id):
|
|
table_id = tables.TUNNEL_IN[network_type]
|
|
self.delete_flows(table_id=table_id, tunnel_id=segmentation_id)
|
|
|
|
def provision_tenant_physnet(self, network_type, network,
|
|
segmentation_id, phys_port):
|
|
"""for vlan and flat."""
|
|
assert(network_type in [p_const.TYPE_VLAN, p_const.TYPE_FLAT])
|
|
(dp, ofp, ofpp) = self._get_dp()
|
|
|
|
# inbound
|
|
metadata = meta.mk_metadata(network)
|
|
instructions = [
|
|
ofpp.OFPInstructionWriteMetadata(metadata=metadata[0],
|
|
metadata_mask=metadata[1])
|
|
]
|
|
if network_type == p_const.TYPE_VLAN:
|
|
vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
|
|
match = ofpp.OFPMatch(in_port=phys_port, vlan_vid=vlan_vid)
|
|
actions = [ofpp.OFPActionPopVlan()]
|
|
instructions += [ofpp.OFPInstructionActions(
|
|
ofp.OFPIT_APPLY_ACTIONS, actions)]
|
|
else:
|
|
match = ofpp.OFPMatch(in_port=phys_port)
|
|
instructions += [ofpp.OFPInstructionGotoTable(table_id=tables.PHYS_IN)]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
priority=1,
|
|
table_id=tables.CHECK_IN_PORT,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
|
|
# outbound
|
|
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network, meta.LOCAL))
|
|
if network_type == p_const.TYPE_VLAN:
|
|
actions = [
|
|
ofpp.OFPActionPushVlan(),
|
|
ofpp.OFPActionSetField(vlan_vid=vlan_vid),
|
|
]
|
|
else:
|
|
actions = []
|
|
actions += [ofpp.OFPActionOutput(port=phys_port)]
|
|
if network_type == p_const.TYPE_VLAN:
|
|
actions += [ofpp.OFPActionPopVlan()]
|
|
instructions = [
|
|
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
|
ofpp.OFPInstructionGotoTable(table_id=tables.PHYS_FLOOD + 1),
|
|
]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
priority=1,
|
|
table_id=tables.PHYS_FLOOD,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
|
|
def reclaim_tenant_physnet(self, network_type, network,
|
|
segmentation_id, phys_port):
|
|
(_dp, ofp, _ofpp) = self._get_dp()
|
|
vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
|
|
if network_type == p_const.TYPE_VLAN:
|
|
self.delete_flows(table_id=tables.CHECK_IN_PORT,
|
|
in_port=phys_port, vlan_vid=vlan_vid)
|
|
else:
|
|
self.delete_flows(table_id=tables.CHECK_IN_PORT,
|
|
in_port=phys_port)
|
|
self.delete_flows(table_id=tables.PHYS_FLOOD,
|
|
metadata=meta.mk_metadata(network))
|
|
|
|
def check_in_port_add_tunnel_port(self, network_type, port):
|
|
(dp, _ofp, ofpp) = self._get_dp()
|
|
match = ofpp.OFPMatch(in_port=port)
|
|
instructions = [
|
|
ofpp.OFPInstructionGotoTable(
|
|
table_id=tables.TUNNEL_IN[network_type])
|
|
]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=tables.CHECK_IN_PORT,
|
|
priority=1,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
|
|
def check_in_port_add_local_port(self, network, port):
|
|
(dp, ofp, ofpp) = self._get_dp()
|
|
match = ofpp.OFPMatch(in_port=port)
|
|
metadata = meta.mk_metadata(network, meta.LOCAL)
|
|
instructions = [
|
|
ofpp.OFPInstructionWriteMetadata(metadata=metadata[0],
|
|
metadata_mask=metadata[1]),
|
|
ofpp.OFPInstructionGotoTable(table_id=tables.LOCAL_IN),
|
|
]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=tables.CHECK_IN_PORT,
|
|
priority=1,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
|
|
def check_in_port_delete_port(self, port):
|
|
self.delete_flows(table_id=tables.CHECK_IN_PORT, in_port=port)
|
|
|
|
def local_flood_update(self, network, ports, flood_unicast):
|
|
(dp, ofp, ofpp) = self._get_dp()
|
|
match_all = ofpp.OFPMatch(metadata=meta.mk_metadata(network))
|
|
match_multicast = ofpp.OFPMatch(metadata=meta.mk_metadata(network),
|
|
eth_dst=('01:00:00:00:00:00',
|
|
'01:00:00:00:00:00'))
|
|
if flood_unicast:
|
|
match_add = match_all
|
|
match_del = match_multicast
|
|
else:
|
|
match_add = match_multicast
|
|
match_del = match_all
|
|
actions = [ofpp.OFPActionOutput(port=p) for p in ports]
|
|
instructions = [
|
|
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
|
]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=tables.LOCAL_FLOOD,
|
|
priority=1,
|
|
match=match_add,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
self.delete_flows(table_id=tables.LOCAL_FLOOD, strict=True,
|
|
priority=1, match=match_del)
|
|
|
|
def local_flood_delete(self, network):
|
|
self.delete_flows(table_id=tables.LOCAL_FLOOD,
|
|
metadata=meta.mk_metadata(network))
|
|
|
|
def local_out_add_port(self, network, port, mac):
|
|
(dp, ofp, ofpp) = self._get_dp()
|
|
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network), eth_dst=mac)
|
|
actions = [ofpp.OFPActionOutput(port=port)]
|
|
instructions = [
|
|
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
|
]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=tables.LOCAL_OUT,
|
|
priority=1,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|
|
|
|
def local_out_delete_port(self, network, mac):
|
|
self.delete_flows(table_id=tables.LOCAL_OUT,
|
|
metadata=meta.mk_metadata(network), eth_dst=mac)
|
|
|
|
def arp_passthrough(self, network, tpa):
|
|
(dp, ofp, ofpp) = self._get_dp()
|
|
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network),
|
|
eth_type=ether.ETH_TYPE_ARP,
|
|
arp_op=arp.ARP_REQUEST,
|
|
arp_tpa=tpa)
|
|
instructions = [
|
|
ofpp.OFPInstructionGotoTable(table_id=tables.TUNNEL_OUT)]
|
|
msg = ofpp.OFPFlowMod(dp,
|
|
table_id=tables.ARP_PASSTHROUGH,
|
|
priority=1,
|
|
idle_timeout=5,
|
|
match=match,
|
|
instructions=instructions)
|
|
self._send_msg(msg)
|