6e3eccb453
Signed-off-by: IWASE Yusuke <iwase.yusuke0@gmail.com> Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
334 lines
13 KiB
Python
334 lines
13 KiB
Python
# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation.
|
|
#
|
|
# 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.
|
|
|
|
import logging
|
|
|
|
from ryu.base import app_manager
|
|
from ryu.controller import event
|
|
from ryu.controller import ofp_event
|
|
from ryu.controller.handler import MAIN_DISPATCHER
|
|
from ryu.controller.handler import set_ev_cls
|
|
from ryu.ofproto import ether
|
|
from ryu.ofproto import ofproto_v1_0
|
|
from ryu.ofproto import ofproto_v1_2
|
|
from ryu.ofproto import ofproto_v1_3
|
|
from ryu.lib import addrconv
|
|
from ryu.lib.dpid import dpid_to_str
|
|
from ryu.lib.packet import packet
|
|
from ryu.lib.packet import ethernet
|
|
from ryu.lib.packet import slow
|
|
|
|
|
|
class EventPacketIn(event.EventBase):
|
|
"""a PacketIn event class using except LACP."""
|
|
|
|
def __init__(self, msg):
|
|
"""initialization."""
|
|
super(EventPacketIn, self).__init__()
|
|
self.msg = msg
|
|
|
|
|
|
class EventSlaveStateChanged(event.EventBase):
|
|
"""a event class that notifies the changes of the statuses of the
|
|
slave i/fs."""
|
|
|
|
def __init__(self, datapath, port, enabled):
|
|
"""initialization."""
|
|
super(EventSlaveStateChanged, self).__init__()
|
|
self.datapath = datapath
|
|
self.port = port
|
|
self.enabled = enabled
|
|
|
|
|
|
class LacpLib(app_manager.RyuApp):
|
|
"""LACP exchange library. this works only in a PASSIVE mode."""
|
|
|
|
# -------------------------------------------------------------------
|
|
# PUBLIC METHODS
|
|
# -------------------------------------------------------------------
|
|
def __init__(self):
|
|
"""initialization."""
|
|
super(LacpLib, self).__init__()
|
|
self.name = 'lacplib'
|
|
self._bonds = []
|
|
self._add_flow = {
|
|
ofproto_v1_0.OFP_VERSION: self._add_flow_v1_0,
|
|
ofproto_v1_2.OFP_VERSION: self._add_flow_v1_2,
|
|
ofproto_v1_3.OFP_VERSION: self._add_flow_v1_2,
|
|
}
|
|
self._set_logger()
|
|
|
|
def add(self, dpid, ports):
|
|
"""add a setting of a bonding i/f.
|
|
'add' method takes the corresponding args in this order.
|
|
|
|
========= =====================================================
|
|
Attribute Description
|
|
========= =====================================================
|
|
dpid datapath id.
|
|
|
|
ports a list of integer values that means the ports face
|
|
with the slave i/fs.
|
|
========= =====================================================
|
|
|
|
if you want to use multi LAG, call 'add' method more than once.
|
|
"""
|
|
assert isinstance(ports, list)
|
|
assert len(ports) >= 2
|
|
ifs = {}
|
|
for port in ports:
|
|
ifs[port] = {'enabled': False, 'timeout': 0}
|
|
bond = {dpid: ifs}
|
|
self._bonds.append(bond)
|
|
|
|
# -------------------------------------------------------------------
|
|
# PUBLIC METHODS ( EVENT HANDLERS )
|
|
# -------------------------------------------------------------------
|
|
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
def packet_in_handler(self, evt):
|
|
"""PacketIn event handler. when the received packet was LACP,
|
|
proceed it. otherwise, send a event."""
|
|
req_pkt = packet.Packet(evt.msg.data)
|
|
if slow.lacp in req_pkt:
|
|
(req_lacp, ) = req_pkt.get_protocols(slow.lacp)
|
|
(req_eth, ) = req_pkt.get_protocols(ethernet.ethernet)
|
|
self._do_lacp(req_lacp, req_eth.src, evt.msg)
|
|
else:
|
|
self.send_event_to_observers(EventPacketIn(evt.msg))
|
|
|
|
@set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER)
|
|
def flow_removed_handler(self, evt):
|
|
"""FlowRemoved event handler. when the removed flow entry was
|
|
for LACP, set the status of the slave i/f to disabled, and
|
|
send a event."""
|
|
msg = evt.msg
|
|
datapath = msg.datapath
|
|
ofproto = datapath.ofproto
|
|
dpid = datapath.id
|
|
match = msg.match
|
|
if ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
|
|
port = match.in_port
|
|
dl_type = match.dl_type
|
|
else:
|
|
port = match['in_port']
|
|
dl_type = match['eth_type']
|
|
if ether.ETH_TYPE_SLOW != dl_type:
|
|
return
|
|
self.logger.info(
|
|
"SW=%s PORT=%d LACP exchange timeout has occurred.",
|
|
dpid_to_str(dpid), port)
|
|
self._set_slave_enabled(dpid, port, False)
|
|
self._set_slave_timeout(dpid, port, 0)
|
|
self.send_event_to_observers(
|
|
EventSlaveStateChanged(datapath, port, False))
|
|
|
|
# -------------------------------------------------------------------
|
|
# PRIVATE METHODS ( RELATED TO LACP )
|
|
# -------------------------------------------------------------------
|
|
def _do_lacp(self, req_lacp, src, msg):
|
|
"""packet-in process when the received packet is LACP."""
|
|
datapath = msg.datapath
|
|
dpid = datapath.id
|
|
ofproto = datapath.ofproto
|
|
parser = datapath.ofproto_parser
|
|
if ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
|
|
port = msg.in_port
|
|
else:
|
|
port = msg.match['in_port']
|
|
|
|
self.logger.info("SW=%s PORT=%d LACP received.",
|
|
dpid_to_str(dpid), port)
|
|
self.logger.debug(str(req_lacp))
|
|
|
|
# when LACP arrived at disabled port, update the status of
|
|
# the slave i/f to enabled, and send a event.
|
|
if not self._get_slave_enabled(dpid, port):
|
|
self.logger.info(
|
|
"SW=%s PORT=%d the slave i/f has just been up.",
|
|
dpid_to_str(dpid), port)
|
|
self._set_slave_enabled(dpid, port, True)
|
|
self.send_event_to_observers(
|
|
EventSlaveStateChanged(datapath, port, True))
|
|
|
|
# set the idle_timeout time using the actor state of the
|
|
# received packet.
|
|
if req_lacp.LACP_STATE_SHORT_TIMEOUT == \
|
|
req_lacp.actor_state_timeout:
|
|
idle_timeout = req_lacp.SHORT_TIMEOUT_TIME
|
|
else:
|
|
idle_timeout = req_lacp.LONG_TIMEOUT_TIME
|
|
|
|
# when the timeout time has changed, update the timeout time of
|
|
# the slave i/f and re-enter a flow entry for the packet from
|
|
# the slave i/f with idle_timeout.
|
|
if idle_timeout != self._get_slave_timeout(dpid, port):
|
|
self.logger.info(
|
|
"SW=%s PORT=%d the timeout time has changed.",
|
|
dpid_to_str(dpid), port)
|
|
self._set_slave_timeout(dpid, port, idle_timeout)
|
|
func = self._add_flow.get(ofproto.OFP_VERSION)
|
|
assert func
|
|
func(src, port, idle_timeout, datapath)
|
|
|
|
# create a response packet.
|
|
res_pkt = self._create_response(datapath, port, req_lacp)
|
|
|
|
# packet-out the response packet.
|
|
out_port = ofproto.OFPP_IN_PORT
|
|
actions = [parser.OFPActionOutput(out_port)]
|
|
out = datapath.ofproto_parser.OFPPacketOut(
|
|
datapath=datapath, buffer_id=ofproto.OFP_NO_BUFFER,
|
|
data=res_pkt.data, in_port=port, actions=actions)
|
|
datapath.send_msg(out)
|
|
|
|
def _create_response(self, datapath, port, req):
|
|
"""create a packet including LACP."""
|
|
src = datapath.ports[port].hw_addr
|
|
res_ether = ethernet.ethernet(
|
|
slow.SLOW_PROTOCOL_MULTICAST, src, ether.ETH_TYPE_SLOW)
|
|
res_lacp = self._create_lacp(datapath, port, req)
|
|
res_pkt = packet.Packet()
|
|
res_pkt.add_protocol(res_ether)
|
|
res_pkt.add_protocol(res_lacp)
|
|
res_pkt.serialize()
|
|
return res_pkt
|
|
|
|
def _create_lacp(self, datapath, port, req):
|
|
"""create a LACP packet."""
|
|
actor_system = datapath.ports[datapath.ofproto.OFPP_LOCAL].hw_addr
|
|
res = slow.lacp(
|
|
actor_system_priority=0xffff,
|
|
actor_system=actor_system,
|
|
actor_key=req.actor_key,
|
|
actor_port_priority=0xff,
|
|
actor_port=port,
|
|
actor_state_activity=req.LACP_STATE_PASSIVE,
|
|
actor_state_timeout=req.actor_state_timeout,
|
|
actor_state_aggregation=req.actor_state_aggregation,
|
|
actor_state_synchronization=req.actor_state_synchronization,
|
|
actor_state_collecting=req.actor_state_collecting,
|
|
actor_state_distributing=req.actor_state_distributing,
|
|
actor_state_defaulted=req.LACP_STATE_OPERATIONAL_PARTNER,
|
|
actor_state_expired=req.LACP_STATE_NOT_EXPIRED,
|
|
partner_system_priority=req.actor_system_priority,
|
|
partner_system=req.actor_system,
|
|
partner_key=req.actor_key,
|
|
partner_port_priority=req.actor_port_priority,
|
|
partner_port=req.actor_port,
|
|
partner_state_activity=req.actor_state_activity,
|
|
partner_state_timeout=req.actor_state_timeout,
|
|
partner_state_aggregation=req.actor_state_aggregation,
|
|
partner_state_synchronization=req.actor_state_synchronization,
|
|
partner_state_collecting=req.actor_state_collecting,
|
|
partner_state_distributing=req.actor_state_distributing,
|
|
partner_state_defaulted=req.actor_state_defaulted,
|
|
partner_state_expired=req.actor_state_expired,
|
|
collector_max_delay=0)
|
|
self.logger.info("SW=%s PORT=%d LACP sent.",
|
|
dpid_to_str(datapath.id), port)
|
|
self.logger.debug(str(res))
|
|
return res
|
|
|
|
def _get_slave_enabled(self, dpid, port):
|
|
"""get whether a slave i/f at some port of some datapath is
|
|
enable or not."""
|
|
slave = self._get_slave(dpid, port)
|
|
if slave:
|
|
return slave['enabled']
|
|
else:
|
|
return False
|
|
|
|
def _set_slave_enabled(self, dpid, port, enabled):
|
|
"""set whether a slave i/f at some port of some datapath is
|
|
enable or not."""
|
|
slave = self._get_slave(dpid, port)
|
|
if slave:
|
|
slave['enabled'] = enabled
|
|
|
|
def _get_slave_timeout(self, dpid, port):
|
|
"""get the timeout time at some port of some datapath."""
|
|
slave = self._get_slave(dpid, port)
|
|
if slave:
|
|
return slave['timeout']
|
|
else:
|
|
return 0
|
|
|
|
def _set_slave_timeout(self, dpid, port, timeout):
|
|
"""set the timeout time at some port of some datapath."""
|
|
slave = self._get_slave(dpid, port)
|
|
if slave:
|
|
slave['timeout'] = timeout
|
|
|
|
def _get_slave(self, dpid, port):
|
|
"""get slave i/f at some port of some datapath."""
|
|
result = None
|
|
for bond in self._bonds:
|
|
if dpid in bond:
|
|
if port in bond[dpid]:
|
|
result = bond[dpid][port]
|
|
break
|
|
return result
|
|
|
|
# -------------------------------------------------------------------
|
|
# PRIVATE METHODS ( RELATED TO OPEN FLOW PROTOCOL )
|
|
# -------------------------------------------------------------------
|
|
def _add_flow_v1_0(self, src, port, timeout, datapath):
|
|
"""enter a flow entry for the packet from the slave i/f
|
|
with idle_timeout. for OpenFlow ver1.0."""
|
|
ofproto = datapath.ofproto
|
|
parser = datapath.ofproto_parser
|
|
|
|
match = parser.OFPMatch(
|
|
in_port=port, dl_src=addrconv.mac.text_to_bin(src),
|
|
dl_type=ether.ETH_TYPE_SLOW)
|
|
actions = [parser.OFPActionOutput(
|
|
ofproto.OFPP_CONTROLLER, 65535)]
|
|
mod = parser.OFPFlowMod(
|
|
datapath=datapath, match=match, cookie=0,
|
|
command=ofproto.OFPFC_ADD, idle_timeout=timeout,
|
|
priority=65535, flags=ofproto.OFPFF_SEND_FLOW_REM,
|
|
actions=actions)
|
|
datapath.send_msg(mod)
|
|
|
|
def _add_flow_v1_2(self, src, port, timeout, datapath):
|
|
"""enter a flow entry for the packet from the slave i/f
|
|
with idle_timeout. for OpenFlow ver1.2 and ver1.3."""
|
|
ofproto = datapath.ofproto
|
|
parser = datapath.ofproto_parser
|
|
|
|
match = parser.OFPMatch(
|
|
in_port=port, eth_src=src, eth_type=ether.ETH_TYPE_SLOW)
|
|
actions = [parser.OFPActionOutput(
|
|
ofproto.OFPP_CONTROLLER, ofproto.OFPCML_MAX)]
|
|
inst = [parser.OFPInstructionActions(
|
|
ofproto.OFPIT_APPLY_ACTIONS, actions)]
|
|
mod = parser.OFPFlowMod(
|
|
datapath=datapath, command=ofproto.OFPFC_ADD,
|
|
idle_timeout=timeout, priority=65535,
|
|
flags=ofproto.OFPFF_SEND_FLOW_REM, match=match,
|
|
instructions=inst)
|
|
datapath.send_msg(mod)
|
|
|
|
# -------------------------------------------------------------------
|
|
# PRIVATE METHODS ( OTHERS )
|
|
# -------------------------------------------------------------------
|
|
def _set_logger(self):
|
|
"""change log format."""
|
|
self.logger.propagate = False
|
|
hdl = logging.StreamHandler()
|
|
fmt_str = '[LACP][%(levelname)s] %(message)s'
|
|
hdl.setFormatter(logging.Formatter(fmt_str))
|
|
self.logger.addHandler(hdl)
|