dragonflow/dragonflow/controller/df_base_app.py

302 lines
11 KiB
Python

# Copyright (c) 2015 OpenStack Foundation.
# 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.
import eventlet
from oslo_log import log
from ryu.app.ofctl import api as ofctl_api
from ryu.lib.packet import arp
from ryu.lib.packet import ethernet
from ryu.lib.packet import packet
from ryu.ofproto import ether
from dragonflow._i18n import _
from dragonflow.controller.common import constants
from dragonflow.controller.common import cookies
from dragonflow.db import db_store
# TODO(heshan) This timeout constant should be configured in cfg file
DEFAULT_GET_FLOWS_TIMEOUT = 20
LOG = log.getLogger(__name__)
class DFlowApp(object):
def __init__(self, api, switch_backend=None, nb_api=None,
neutron_server_notifier=None):
self.api = api
self.db_store = db_store.get_instance()
self.switch_backend = switch_backend
self.nb_api = nb_api
self.neutron_server_notifier = neutron_server_notifier
# Though there is nothing to initialize in super class, call it
# will make the multi-inheritence work.
super(DFlowApp, self).__init__()
self._register_events()
@property
def vswitch_api(self):
return self.switch_backend.vswitch_api
def _register_events(self):
'''Iterate all methods we decorated with @register_event and register
them to the requested models.
'''
for attr_name in dir(self):
try:
attr = getattr(self, attr_name)
# NOTE (dimak) list() is needed here because sometimes we have
# attributes that are mocks (during tests), who will have the
# _register_events attribute (and any other attribute too).
# list(mock.Mock()) yields an empty list so this works out
# with little effort.
args = list(attr._register_events)
except (AttributeError, TypeError):
# * AttributeError is OK because we stumbled upon an attribute
# with no _register_events
# * TypeError is fine too because we have an attribute that has
# _register_events but its not iterable (for instance we have
# a _register_events method on an object we hold).
continue
for model, event in args:
model.register(event, attr)
@property
def datapath(self):
return self.api.datapath
@property
def dfdp(self):
return self.switch_backend.datapath
@property
def parser(self):
return self.datapath.ofproto_parser
@property
def ofproto(self):
return self.datapath.ofproto
@property
def local_ports(self):
return self.datapath.local_ports
def add_flow_go_to_table(self, table, priority, goto_table_id,
datapath=None, match=None):
if datapath is None:
datapath = self.datapath
if table < goto_table_id:
inst = [
datapath.ofproto_parser.OFPInstructionGotoTable(goto_table_id)
]
self.mod_flow(datapath, inst=inst, table_id=table,
priority=priority, match=match)
else:
actions = [
self.parser.NXActionResubmitTable(table_id=goto_table_id)
]
self.mod_flow(datapath, actions=actions, table_id=table,
priority=priority, match=match)
def mod_flow(self, datapath=None, cookie=0, cookie_mask=0, table_id=0,
command=None, idle_timeout=0, hard_timeout=0,
priority=0xff, buffer_id=0xffffffff, match=None,
actions=None, inst_type=None, out_port=None,
out_group=None, flags=0, inst=None):
if datapath is None:
datapath = self.datapath
if command is None:
command = datapath.ofproto.OFPFC_ADD
if inst is None:
if inst_type is None:
inst_type = datapath.ofproto.OFPIT_APPLY_ACTIONS
inst = []
if actions is not None:
inst = [datapath.ofproto_parser.OFPInstructionActions(
inst_type, actions)]
if out_port is None:
out_port = datapath.ofproto.OFPP_ANY
if out_group is None:
out_group = datapath.ofproto.OFPG_ANY
cookie, cookie_mask = cookies.apply_global_cookie_modifiers(
cookie, cookie_mask, self)
message = datapath.ofproto_parser.OFPFlowMod(datapath, cookie,
cookie_mask,
table_id, command,
idle_timeout,
hard_timeout,
priority,
buffer_id,
out_port,
out_group,
flags,
match,
inst)
datapath.send_msg(message)
def get_flows(self, datapath=None, table_id=None, timeout=None):
if datapath is None:
datapath = self.datapath
if table_id is None:
table_id = datapath.ofproto.OFPTT_ALL
if not timeout:
timeout = DEFAULT_GET_FLOWS_TIMEOUT
parser = datapath.ofproto_parser
msg = parser.OFPFlowStatsRequest(datapath, table_id=table_id)
try:
with eventlet.timeout.Timeout(seconds=timeout):
replies = ofctl_api.send_msg(
self.api,
msg,
reply_cls=parser.OFPFlowStatsReply,
reply_multi=True)
except BaseException:
LOG.exception("Failed to get flows")
return []
if replies is None:
LOG.error("No reply for get flows")
return []
flows = [body for reply in replies for body in reply.body]
LOG.debug("Got the following flows: %s", flows)
return flows
def add_group(self, group_id, group_type, buckets, replace=False):
"""Add an entry to the groups table:
:param group_id: ID for the new group
:param group_type: Type of the new group, one of ofproto.OFPGT_*
:param buckets: List of parser.OFPBucket objects that define
group's actions.
"""
if replace:
self.del_group(
group_id=group_id,
group_type=group_type,
)
self._mod_group(
command=self.ofproto.OFPGC_ADD,
group_id=group_id,
group_type=group_type,
buckets=buckets,
)
def del_group(self, group_id, group_type):
"""Delete an entry from the groups table
:param group_id: ID of the group to delete.
To delete all groups use ofproto.OFPG_ALL.
:param group_type: Type of the group to delete.
"""
self._mod_group(
command=self.ofproto.OFPGC_DELETE,
group_id=group_id,
group_type=group_type,
)
def _mod_group(self, command, group_id, group_type, buckets=None):
"""Convenince function that sends a group modification message"""
self.datapath.send_msg(
self.parser.OFPGroupMod(
datapath=self.datapath,
command=command,
group_id=group_id,
type_=group_type,
buckets=buckets,
)
)
def dispatch_packet(self, pkt, unique_key):
self.reinject_packet(
pkt,
table_id=constants.INGRESS_DISPATCH_TABLE,
actions=[
self.parser.OFPActionSetField(reg7=unique_key),
]
)
def reinject_packet(self, pkt, table_id=None, actions=None):
datapath = self.datapath
ofproto = datapath.ofproto
parser = self.parser
actions = actions or []
if table_id is not None:
actions.append(parser.NXActionResubmitTable(table_id=table_id))
datapath.send_msg(
parser.OFPPacketOut(
datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions,
data=pkt,
),
)
def send_arp_request(self, src_mac, src_ip, dst_ip, port_key):
arp_request_pkt = packet.Packet()
arp_request_pkt.add_protocol(ethernet.ethernet(
ethertype=ether.ETH_TYPE_ARP,
src=src_mac))
arp_request_pkt.add_protocol(arp.arp(
src_mac=src_mac,
src_ip=src_ip,
dst_ip=dst_ip))
self.dispatch_packet(arp_request_pkt, port_key)
def register_local_cookie_bits(self, name, length):
cookies.register_cookie_bits(name, length,
True, self.__class__.__name__)
def get_local_cookie(self, name, value, old_cookie=0, old_mask=0):
return cookies.get_cookie(name, value,
old_cookie=old_cookie, old_mask=old_mask,
is_local=True,
app_name=self.__class__.__name__)
def register_event(model, event):
'''The decorator marks the method to be registered to the specified event
:param model: Model holding the event
:type model: Class
:param event: Event name that method well be registerd to
:type event: String
'''
if event not in model.get_events():
raise RuntimeError(
_('{0} is not an event of {1}').format(event, model))
def decorator(func):
if not hasattr(func, '_register_events'):
func._register_events = []
func._register_events.append((model, event))
return func
return decorator