dragonflow/dragonflow/controller/ryu_base_app.py

206 lines
8.0 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 time
from oslo_config import cfg
from oslo_log import log
from ryu.controller import handler
from ryu.controller import ofp_event
from ryu.controller import ofp_handler
from ryu.ofproto import ofproto_common
from ryu.ofproto import ofproto_parser
from ryu.ofproto import ofproto_v1_3
from ryu import utils
from dragonflow.common import profiler as df_profiler
from dragonflow.controller.common import constants
from dragonflow.controller import dispatcher
LOG = log.getLogger(__name__)
class RyuDFAdapter(ofp_handler.OFPHandler):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
OF_AUTO_PORT_DESC_STATS_REQ_VER = 0x04
def __init__(self, vswitch_api, nb_api,
db_change_callback,
neutron_server_notifier=None):
super(RyuDFAdapter, self).__init__()
self.dispatcher = dispatcher.AppDispatcher(cfg.CONF.df.apps_list)
self.vswitch_api = vswitch_api
self.nb_api = nb_api
self.neutron_server_notifier = neutron_server_notifier
self._datapath = None
self.table_handlers = {}
self.first_connect = True
self.db_change_callback = db_change_callback
@property
def datapath(self):
return self._datapath
def start(self):
super(RyuDFAdapter, self).start()
self.load(self,
vswitch_api=self.vswitch_api,
nb_api=self.nb_api,
neutron_server_notifier=self.neutron_server_notifier)
self.wait_until_ready()
def load(self, *args, **kwargs):
self.dispatcher.load(*args, **kwargs)
def is_ready(self):
return self.datapath is not None
def wait_until_ready(self):
while not self.is_ready():
time.sleep(3)
def register_table_handler(self, table_id, handler):
if table_id in self.table_handlers:
raise RuntimeError(
_(
'Cannot register handler {new_handler} for table {table},'
'occupied by {existing_handler}'
).format(
table=table_id,
new_handler=handler,
existing_handler=self.table_handlers[table_id],
),
)
self.table_handlers[table_id] = handler
def unregister_table_handler(self, table_id, handler):
self.table_handlers.pop(table_id, None)
def notify_ovs_sync_finished(self):
self.dispatcher.dispatch('ovs_sync_finished')
def notify_ovs_sync_started(self):
self.dispatcher.dispatch('ovs_sync_started')
@handler.set_ev_handler(ofp_event.EventOFPSwitchFeatures,
handler.CONFIG_DISPATCHER)
def switch_features_handler(self, ev):
# TODO(oanson) is there a better way to get the datapath?
self._datapath = ev.msg.datapath
super(RyuDFAdapter, self).switch_features_handler(ev)
version = self.datapath.ofproto.OFP_VERSION
if version < RyuDFAdapter.OF_AUTO_PORT_DESC_STATS_REQ_VER:
# Otherwise, this is done automatically by OFPHandler
self._send_port_desc_stats_request(self.datapath)
self.get_sw_async_msg_config()
self.dispatcher.dispatch('switch_features_handler', ev)
if not self.first_connect:
# For reconnecting to the ryu controller, df needs a full sync
# in case any resource added during the disconnection.
self.db_change_callback(None, None,
constants.CONTROLLER_REINITIALIZE,
None)
self.first_connect = False
self.vswitch_api.initialize(self.db_change_callback)
def _send_port_desc_stats_request(self, datapath):
ofp_parser = datapath.ofproto_parser
req = ofp_parser.OFPPortDescStatsRequest(datapath, 0)
datapath.send_msg(req)
@handler.set_ev_handler(ofp_event.EventOFPPortDescStatsReply,
handler.MAIN_DISPATCHER)
def port_desc_stats_reply_handler(self, ev):
self.dispatcher.dispatch('port_desc_stats_reply_handler', ev)
@handler.set_ev_handler(ofp_event.EventOFPPacketIn,
handler.MAIN_DISPATCHER)
def OF_packet_in_handler(self, event):
msg = event.msg
table_id = msg.table_id
if table_id in self.table_handlers:
handler = self.table_handlers[table_id]
with df_profiler.profiler_context('packet_in',
info={"func": handler.__name__}):
handler(event)
else:
LOG.info("No handler for table id %(table)s with message "
"%(msg)", {'table': table_id, 'msg': msg})
@handler.set_ev_handler(ofp_event.EventOFPErrorMsg,
handler.MAIN_DISPATCHER)
def OF_error_msg_handler(self, event):
msg = event.msg
try:
(version, msg_type, msg_len, xid) = ofproto_parser.header(msg.data)
ryu_msg = ofproto_parser.msg(
self._datapath, version, msg_type,
msg_len - ofproto_common.OFP_HEADER_SIZE, xid, msg.data)
LOG.error('OFPErrorMsg received: %s', ryu_msg)
except Exception:
LOG.error('Unrecognized OFPErrorMsg received: '
'type=0x%(type)02x code=0x%(code)02x '
'message=%(msg)s',
{'type': msg.type, 'code': msg.code,
'msg': utils.hex_array(msg.data)})
@handler.set_ev_cls(ofp_event.EventOFPGetAsyncReply,
handler.MAIN_DISPATCHER)
def get_async_reply_handler(self, event):
msg = event.msg
LOG.debug('OFPGetAsyncReply received: packet_in_mask=0x%08x:0x%08x '
'port_status_mask=0x%08x:0x%08x '
'flow_removed_mask=0x%08x:0x%08x',
msg.packet_in_mask[0], msg.packet_in_mask[1],
msg.port_status_mask[0], msg.port_status_mask[1],
msg.flow_removed_mask[0], msg.flow_removed_mask[1])
self.set_sw_async_msg_config_for_ttl(msg)
def get_sw_async_msg_config(self):
"""Get the configuration of current switch"""
ofp_parser = self._datapath.ofproto_parser
req = ofp_parser.OFPGetAsyncRequest(self._datapath)
self._datapath.send_msg(req)
def set_sw_async_msg_config_for_ttl(self, cur_config):
"""Configure switch for TTL
Configure the switch to packet-in TTL invalid packets to controller.
Note that this method only works in OFP 1.3, however, this ryu app
claims that it only supports ofproto_v1_3.OFP_VERSION. So, no check
will be made here.
"""
dp = self._datapath
parser = dp.ofproto_parser
ofproto = dp.ofproto
if cur_config.packet_in_mask[0] & 1 << ofproto.OFPR_INVALID_TTL != 0:
LOG.info('SW config for TTL error packet in has already '
'been set')
return
packet_in_mask = (cur_config.packet_in_mask[0] |
1 << ofproto.OFPR_INVALID_TTL)
m = parser.OFPSetAsync(
dp, [packet_in_mask, cur_config.packet_in_mask[1]],
[cur_config.port_status_mask[0], cur_config.port_status_mask[1]],
[cur_config.flow_removed_mask[0], cur_config.flow_removed_mask[1]])
dp.send_msg(m)
LOG.info('Set SW config for TTL error packet in.')