Remove os_ken/app and os_ken/services/protocols/bgp/api/jsonrpc modules
The os_ken/services/protocols/bgp/api/jsonrpc.py module was using os_ken/app and os_ken app is the only one which was using tinyrpc lib. Modules under os_ken/app except os_ken/app/ofctl are not used in neutron and neutron-dynamic-routing so lets drop them from the code. With that we can also drop tinyrpc from the requirements file at all. Task: #41912 Story: #2008648 Change-Id: Ic35d1f7ee4112bc5cf16fee3d828534ded26ce7f
This commit is contained in:
parent
28b0341e48
commit
cd4926b235
@ -10,6 +10,3 @@ Others provide some functionalities to other OS-Ken applications.
|
|||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
app/ofctl.rst
|
app/ofctl.rst
|
||||||
app/ofctl_rest.rst
|
|
||||||
app/rest_vtep.rst
|
|
||||||
app/bgp_application.rst
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
*****************************************
|
|
||||||
os_ken.services.protocols.bgp.application
|
|
||||||
*****************************************
|
|
||||||
|
|
||||||
.. automodule:: os_ken.services.protocols.bgp.application
|
|
||||||
:members:
|
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +0,0 @@
|
|||||||
********************
|
|
||||||
os_ken.app.rest_vtep
|
|
||||||
********************
|
|
||||||
|
|
||||||
.. automodule:: os_ken.app.rest_vtep
|
|
||||||
|
|
||||||
REST API
|
|
||||||
========
|
|
||||||
|
|
||||||
.. autoclass:: os_ken.app.rest_vtep.RestVtepController
|
|
||||||
:members:
|
|
||||||
:member-order: bysource
|
|
@ -86,14 +86,6 @@ os_ken.ofproto.ofproto_v1_5_parser
|
|||||||
OS-Ken applications
|
OS-Ken applications
|
||||||
===================
|
===================
|
||||||
|
|
||||||
os_ken.app.cbench
|
|
||||||
-----------------
|
|
||||||
.. automodule:: os_ken.app.cbench
|
|
||||||
|
|
||||||
os_ken.app.simple_switch
|
|
||||||
------------------------
|
|
||||||
.. automodule:: os_ken.app.simple_switch
|
|
||||||
|
|
||||||
os_ken.topology
|
os_ken.topology
|
||||||
---------------
|
---------------
|
||||||
.. automodule:: os_ken.topology
|
.. automodule:: os_ken.topology
|
||||||
|
@ -6,5 +6,4 @@ Configuration
|
|||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
tls.rst
|
tls.rst
|
||||||
gui.rst
|
|
||||||
|
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
***************
|
|
||||||
Topology Viewer
|
|
||||||
***************
|
|
||||||
|
|
||||||
os_ken.app.gui_topology.gui_topology provides topology visualization.
|
|
||||||
|
|
||||||
This depends on following os_ken applications.
|
|
||||||
|
|
||||||
======================== =================================================
|
|
||||||
os_ken.app.rest_topology Get node and link data.
|
|
||||||
os_ken.app.ws_topology Being notified change of link up/down.
|
|
||||||
os_ken.app.ofctl_rest Get flows of datapaths.
|
|
||||||
======================== =================================================
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
Run mininet (or join your real environment)::
|
|
||||||
|
|
||||||
$ sudo mn --controller remote --topo tree,depth=3
|
|
||||||
|
|
||||||
Run GUI application::
|
|
||||||
|
|
||||||
$ PYTHONPATH=. ./bin/os_ken run --observe-links os_ken/app/gui_topology/gui_topology.py
|
|
||||||
|
|
||||||
Access http://<ip address of os_ken host>:8080 with your web browser.
|
|
||||||
|
|
||||||
Screenshot
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. image:: gui.png
|
|
||||||
:width: 640 px
|
|
||||||
|
|
@ -42,27 +42,12 @@ It can be configured by passing configuration file like::
|
|||||||
|
|
||||||
osken-manager [generic/application specific options...]
|
osken-manager [generic/application specific options...]
|
||||||
|
|
||||||
At the moment applications including the following ones are available
|
|
||||||
(And more to come as OS-Ken evolves.)
|
|
||||||
|
|
||||||
* os_ken.app.simple_isolation.SimpleIsolation
|
|
||||||
* os_ken.app.rest.RestAPI
|
|
||||||
* os_ken.app.simple_bridge.SimpleSwitch
|
|
||||||
|
|
||||||
The generic available is as follows::
|
The generic available is as follows::
|
||||||
|
|
||||||
--app-lists: application module name to run;
|
--app-lists: application module name to run;
|
||||||
repeat this option to specify a list of values
|
repeat this option to specify a list of values
|
||||||
--help: show help
|
--help: show help
|
||||||
|
|
||||||
The options for REST server::
|
|
||||||
|
|
||||||
--wsapi-host: webapp listen host
|
|
||||||
(default: '')
|
|
||||||
--wsapi-port: webapp listen port
|
|
||||||
(default: '8080')
|
|
||||||
(an integer)
|
|
||||||
|
|
||||||
The options for openflow controller::
|
The options for openflow controller::
|
||||||
|
|
||||||
--ofp-listen-host: openflow listen host
|
--ofp-listen-host: openflow listen host
|
||||||
@ -96,38 +81,3 @@ The option for oslo.config.cfg::
|
|||||||
individual options are over-ridden. The set is parsed after the file(s),
|
individual options are over-ridden. The set is parsed after the file(s),
|
||||||
if any, specified via --config-file, hence over-ridden options in the
|
if any, specified via --config-file, hence over-ridden options in the
|
||||||
directory take precedence.
|
directory take precedence.
|
||||||
|
|
||||||
|
|
||||||
Invoking Example
|
|
||||||
================
|
|
||||||
The example is as follows::
|
|
||||||
|
|
||||||
% osken-manager --wsapi-port 8081 --verbose --app-lists os_ken.app.simple_isolation,os_ken.app.rest
|
|
||||||
loading app os_ken.app.simple_isolation
|
|
||||||
loading app os_ken.app.rest
|
|
||||||
loading app os_ken.controller.ofp_handler
|
|
||||||
creating context dpset
|
|
||||||
creating context wsgi
|
|
||||||
creating context network
|
|
||||||
instantiating app os_ken.app.simple_isolation
|
|
||||||
instantiating app os_ken.app.rest
|
|
||||||
instantiating app os_ken.controller.ofp_handler
|
|
||||||
BRICK dpset
|
|
||||||
CONSUMES EventOFPStateChange
|
|
||||||
CONSUMES EventOFPPortStatus
|
|
||||||
CONSUMES EventOFPSwitchFeatures
|
|
||||||
BRICK ofp_event
|
|
||||||
PROVIDES EventOFPStateChange TO ['dpset']
|
|
||||||
PROVIDES EventOFPPortStatus TO ['dpset', 'SimpleIsolation']
|
|
||||||
PROVIDES EventOFPPacketIn TO ['SimpleIsolation']
|
|
||||||
PROVIDES EventOFPSwitchFeatures TO ['dpset', 'SimpleIsolation']
|
|
||||||
CONSUMES EventOFPEchoRequest
|
|
||||||
CONSUMES EventOFPErrorMsg
|
|
||||||
CONSUMES EventOFPSwitchFeatures
|
|
||||||
CONSUMES EventOFPHello
|
|
||||||
BRICK network
|
|
||||||
BRICK RestAPI
|
|
||||||
BRICK SimpleIsolation
|
|
||||||
CONSUMES EventOFPPacketIn
|
|
||||||
CONSUMES EventOFPPortStatus
|
|
||||||
CONSUMES EventOFPSwitchFeatures
|
|
||||||
|
@ -176,15 +176,3 @@ Starting LINC OpenFlow switch
|
|||||||
Then run LINC::
|
Then run LINC::
|
||||||
|
|
||||||
# rel/linc/bin/linc console
|
# rel/linc/bin/linc console
|
||||||
|
|
||||||
|
|
||||||
Run OS-Ken test_of_config app
|
|
||||||
=============================
|
|
||||||
|
|
||||||
Run test_of_config app::
|
|
||||||
|
|
||||||
# osken-manager --verbose os_ken.tests.integrated.test_of_config os_ken.app.rest
|
|
||||||
|
|
||||||
If you don't install os_ken and are working in the git repo directly::
|
|
||||||
|
|
||||||
# osken-manager --verbose os_ken.tests.integrated.test_of_config os_ken.app.rest
|
|
||||||
|
@ -126,7 +126,6 @@ testrepository==0.0.18
|
|||||||
testresources==2.0.0
|
testresources==2.0.0
|
||||||
testscenarios==0.4
|
testscenarios==0.4
|
||||||
testtools==2.2.0
|
testtools==2.2.0
|
||||||
tinyrpc==0.6
|
|
||||||
traceback2==1.4.0
|
traceback2==1.4.0
|
||||||
unittest2==1.1.0
|
unittest2==1.1.0
|
||||||
vine==1.1.4
|
vine==1.1.4
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
# Copyright (C) 2014 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 os
|
|
||||||
import socket
|
|
||||||
import time
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
|
|
||||||
from os_ken.lib import hub
|
|
||||||
from os_ken.lib.hub import StreamServer
|
|
||||||
from os_ken.lib.packet import bmp
|
|
||||||
|
|
||||||
|
|
||||||
class BMPStation(app_manager.OSKenApp):
|
|
||||||
def __init__(self):
|
|
||||||
super(BMPStation, self).__init__()
|
|
||||||
self.name = 'bmpstation'
|
|
||||||
self.server_host = os.environ.get('OSKEN_BMP_SERVER_HOST', '0.0.0.0')
|
|
||||||
self.server_port = int(os.environ.get('OSKEN_BMP_SERVER_PORT', 11019))
|
|
||||||
output_file = os.environ.get('OSKEN_BMP_OUTPUT_FILE', 'os_ken_bmp.log')
|
|
||||||
failed_dump = os.environ.get('OSKEN_BMP_FAILED_DUMP',
|
|
||||||
'os_ken_bmp_failed.dump')
|
|
||||||
|
|
||||||
self.output_fd = open(output_file, 'w')
|
|
||||||
self.failed_dump_fd = open(failed_dump, 'w')
|
|
||||||
|
|
||||||
self.failed_pkt_count = 0
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
super(BMPStation, self).start()
|
|
||||||
self.logger.debug("listening on %s:%s", self.server_host,
|
|
||||||
self.server_port)
|
|
||||||
|
|
||||||
return hub.spawn(StreamServer((self.server_host, self.server_port),
|
|
||||||
self.loop).serve_forever)
|
|
||||||
|
|
||||||
def loop(self, sock, addr):
|
|
||||||
self.logger.debug("BMP client connected, ip=%s, port=%s", addr[0],
|
|
||||||
addr[1])
|
|
||||||
is_active = True
|
|
||||||
buf = bytearray()
|
|
||||||
required_len = bmp.BMPMessage._HDR_LEN
|
|
||||||
|
|
||||||
while is_active:
|
|
||||||
ret = sock.recv(required_len)
|
|
||||||
if len(ret) == 0:
|
|
||||||
is_active = False
|
|
||||||
break
|
|
||||||
buf += ret
|
|
||||||
while len(buf) >= required_len:
|
|
||||||
version, len_, _ = bmp.BMPMessage.parse_header(buf)
|
|
||||||
if version != bmp.VERSION:
|
|
||||||
self.logger.error("unsupported bmp version: %d", version)
|
|
||||||
is_active = False
|
|
||||||
break
|
|
||||||
|
|
||||||
required_len = len_
|
|
||||||
if len(buf) < required_len:
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
msg, rest = bmp.BMPMessage.parser(buf)
|
|
||||||
except Exception as e:
|
|
||||||
pkt = buf[:len_]
|
|
||||||
self.failed_dump_fd.write(pkt)
|
|
||||||
self.failed_dump_fd.flush()
|
|
||||||
buf = buf[len_:]
|
|
||||||
self.failed_pkt_count += 1
|
|
||||||
self.logger.error("failed to parse: %s"
|
|
||||||
" (total fail count: %d)",
|
|
||||||
e, self.failed_pkt_count)
|
|
||||||
else:
|
|
||||||
t = time.strftime("%Y %b %d %H:%M:%S", time.localtime())
|
|
||||||
self.logger.debug("%s | %s | %s\n", t, addr[0], msg)
|
|
||||||
self.output_fd.write("%s | %s | %s\n\n" % (t, addr[0],
|
|
||||||
msg))
|
|
||||||
self.output_fd.flush()
|
|
||||||
buf = rest
|
|
||||||
|
|
||||||
required_len = bmp.BMPMessage._HDR_LEN
|
|
||||||
|
|
||||||
self.logger.debug("BMP client disconnected, ip=%s, port=%s", addr[0],
|
|
||||||
addr[1])
|
|
||||||
|
|
||||||
sock.close()
|
|
@ -1,50 +0,0 @@
|
|||||||
# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
|
|
||||||
# Copyright (C) 2012 Isaku Yamahata <yamahata at valinux co jp>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
A dumb OpenFlow 1.0 responder for benchmarking the controller framework.
|
|
||||||
Intended to be used with oflops cbench.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_0
|
|
||||||
|
|
||||||
|
|
||||||
class Cbench(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(Cbench, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
match = datapath.ofproto_parser.OFPMatch(
|
|
||||||
ofproto_v1_0.OFPFW_ALL, 0, 0, 0,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
||||||
|
|
||||||
mod = datapath.ofproto_parser.OFPFlowMod(
|
|
||||||
datapath, match=match, cookie=0, command=ofproto.OFPFC_ADD,
|
|
||||||
idle_timeout=0, hard_timeout=0,
|
|
||||||
priority=ofproto.OFP_DEFAULT_PRIORITY,
|
|
||||||
flags=0, actions=None)
|
|
||||||
datapath.send_msg(mod)
|
|
@ -1,18 +0,0 @@
|
|||||||
# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
|
|
||||||
# Copyright (C) 2012 Isaku Yamahata <yamahata at private email ne jp>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
OVSDB_ADDR = 'ovsdb_addr' # value <method>:<ip>[:<port>]
|
|
||||||
OVS_TUNNEL_ADDR = 'ovs_tunnel_addr' # ip address of tunnel
|
|
@ -1,101 +0,0 @@
|
|||||||
# Copyright (C) 2016 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
|
|
||||||
|
|
||||||
class ExampleSwitch13(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(ExampleSwitch13, self).__init__(*args, **kwargs)
|
|
||||||
# initialize mac address table.
|
|
||||||
self.mac_to_port = {}
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
|
||||||
def switch_features_handler(self, ev):
|
|
||||||
datapath = ev.msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
# install the table-miss flow entry.
|
|
||||||
match = parser.OFPMatch()
|
|
||||||
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
|
|
||||||
ofproto.OFPCML_NO_BUFFER)]
|
|
||||||
self.add_flow(datapath, 0, match, actions)
|
|
||||||
|
|
||||||
def add_flow(self, datapath, priority, match, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
# construct flow_mod message and send it.
|
|
||||||
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
|
|
||||||
actions)]
|
|
||||||
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
|
|
||||||
match=match, instructions=inst)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
# get Datapath ID to identify OpenFlow switches.
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
# analyse the received packets using the packet library.
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth_pkt = pkt.get_protocol(ethernet.ethernet)
|
|
||||||
dst = eth_pkt.dst
|
|
||||||
src = eth_pkt.src
|
|
||||||
|
|
||||||
# get the received port number from packet_in message.
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
# if the destination mac address is already learned,
|
|
||||||
# decide which port to output the packet, otherwise FLOOD.
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
# construct action list.
|
|
||||||
actions = [parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time.
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
# construct packet_out message and send it.
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath,
|
|
||||||
buffer_id=ofproto.OFP_NO_BUFFER,
|
|
||||||
in_port=in_port, actions=actions,
|
|
||||||
data=msg.data)
|
|
||||||
datapath.send_msg(out)
|
|
@ -1,68 +0,0 @@
|
|||||||
# Copyright (C) 2014 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Usage example
|
|
||||||
|
|
||||||
1. Join switches (use your favorite method):
|
|
||||||
$ sudo mn --controller remote --topo tree,depth=3
|
|
||||||
|
|
||||||
2. Run this application:
|
|
||||||
$ PYTHONPATH=. ./bin/os_ken run \
|
|
||||||
--observe-links os_ken/app/gui_topology/gui_topology.py
|
|
||||||
|
|
||||||
3. Access http://<ip address of os_ken host>:8080 with your web browser.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from webob.static import DirectoryApp
|
|
||||||
|
|
||||||
from os_ken.app.wsgi import ControllerBase, WSGIApplication, route
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
|
|
||||||
|
|
||||||
PATH = os.path.dirname(__file__)
|
|
||||||
|
|
||||||
|
|
||||||
# Serving static files
|
|
||||||
class GUIServerApp(app_manager.OSKenApp):
|
|
||||||
_CONTEXTS = {
|
|
||||||
'wsgi': WSGIApplication,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(GUIServerApp, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
wsgi.register(GUIServerController)
|
|
||||||
|
|
||||||
|
|
||||||
class GUIServerController(ControllerBase):
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(GUIServerController, self).__init__(req, link, data, **config)
|
|
||||||
path = "%s/html/" % PATH
|
|
||||||
self.static_app = DirectoryApp(path)
|
|
||||||
|
|
||||||
@route('topology', '/{filename:[^/]*}')
|
|
||||||
def static_handler(self, req, **kwargs):
|
|
||||||
if kwargs['filename']:
|
|
||||||
req.path_info = kwargs['filename']
|
|
||||||
return self.static_app(req)
|
|
||||||
|
|
||||||
|
|
||||||
app_manager.require_app('os_ken.app.rest_topology')
|
|
||||||
app_manager.require_app('os_ken.app.ws_topology')
|
|
||||||
app_manager.require_app('os_ken.app.ofctl_rest')
|
|
@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<link rel="stylesheet" type="text/css" href="./osken.topology.css">
|
|
||||||
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>OSKen Topology Viewer</h1>
|
|
||||||
<script src="./osken.topology.js" charset="utf-8"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,30 +0,0 @@
|
|||||||
#topology {
|
|
||||||
border: 1px solid #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node {
|
|
||||||
}
|
|
||||||
|
|
||||||
.node.fixed {
|
|
||||||
fill: #C0C0C0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node text {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
stroke: #090909;
|
|
||||||
stroke-opacity: .6;
|
|
||||||
stroke-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port circle {
|
|
||||||
stroke: black;
|
|
||||||
fill: #C5F9F9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.port text {
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,281 +0,0 @@
|
|||||||
var CONF = {
|
|
||||||
image: {
|
|
||||||
width: 50,
|
|
||||||
height: 40
|
|
||||||
},
|
|
||||||
force: {
|
|
||||||
width: 960,
|
|
||||||
height: 500,
|
|
||||||
dist: 200,
|
|
||||||
charge: -600
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var ws = new WebSocket("ws://" + location.host + "/v1.0/topology/ws");
|
|
||||||
ws.onmessage = function(event) {
|
|
||||||
var data = JSON.parse(event.data);
|
|
||||||
|
|
||||||
var result = rpc[data.method](data.params);
|
|
||||||
|
|
||||||
var ret = {"id": data.id, "jsonrpc": "2.0", "result": result};
|
|
||||||
this.send(JSON.stringify(ret));
|
|
||||||
}
|
|
||||||
|
|
||||||
function trim_zero(obj) {
|
|
||||||
return String(obj).replace(/^0+/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function dpid_to_int(dpid) {
|
|
||||||
return Number("0x" + dpid);
|
|
||||||
}
|
|
||||||
|
|
||||||
var elem = {
|
|
||||||
force: d3.layout.force()
|
|
||||||
.size([CONF.force.width, CONF.force.height])
|
|
||||||
.charge(CONF.force.charge)
|
|
||||||
.linkDistance(CONF.force.dist)
|
|
||||||
.on("tick", _tick),
|
|
||||||
svg: d3.select("body").append("svg")
|
|
||||||
.attr("id", "topology")
|
|
||||||
.attr("width", CONF.force.width)
|
|
||||||
.attr("height", CONF.force.height),
|
|
||||||
console: d3.select("body").append("div")
|
|
||||||
.attr("id", "console")
|
|
||||||
.attr("width", CONF.force.width)
|
|
||||||
};
|
|
||||||
function _tick() {
|
|
||||||
elem.link.attr("x1", function(d) { return d.source.x; })
|
|
||||||
.attr("y1", function(d) { return d.source.y; })
|
|
||||||
.attr("x2", function(d) { return d.target.x; })
|
|
||||||
.attr("y2", function(d) { return d.target.y; });
|
|
||||||
|
|
||||||
elem.node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
|
|
||||||
|
|
||||||
elem.port.attr("transform", function(d) {
|
|
||||||
var p = topo.get_port_point(d);
|
|
||||||
return "translate(" + p.x + "," + p.y + ")";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
elem.drag = elem.force.drag().on("dragstart", _dragstart);
|
|
||||||
function _dragstart(d) {
|
|
||||||
var dpid = dpid_to_int(d.dpid)
|
|
||||||
d3.json("/stats/flow/" + dpid, function(e, data) {
|
|
||||||
flows = data[dpid];
|
|
||||||
console.log(flows);
|
|
||||||
elem.console.selectAll("ul").remove();
|
|
||||||
li = elem.console.append("ul")
|
|
||||||
.selectAll("li");
|
|
||||||
li.data(flows).enter().append("li")
|
|
||||||
.text(function (d) { return JSON.stringify(d, null, " "); });
|
|
||||||
});
|
|
||||||
d3.select(this).classed("fixed", d.fixed = true);
|
|
||||||
}
|
|
||||||
elem.node = elem.svg.selectAll(".node");
|
|
||||||
elem.link = elem.svg.selectAll(".link");
|
|
||||||
elem.port = elem.svg.selectAll(".port");
|
|
||||||
elem.update = function () {
|
|
||||||
this.force
|
|
||||||
.nodes(topo.nodes)
|
|
||||||
.links(topo.links)
|
|
||||||
.start();
|
|
||||||
|
|
||||||
this.link = this.link.data(topo.links);
|
|
||||||
this.link.exit().remove();
|
|
||||||
this.link.enter().append("line")
|
|
||||||
.attr("class", "link");
|
|
||||||
|
|
||||||
this.node = this.node.data(topo.nodes);
|
|
||||||
this.node.exit().remove();
|
|
||||||
var nodeEnter = this.node.enter().append("g")
|
|
||||||
.attr("class", "node")
|
|
||||||
.on("dblclick", function(d) { d3.select(this).classed("fixed", d.fixed = false); })
|
|
||||||
.call(this.drag);
|
|
||||||
nodeEnter.append("image")
|
|
||||||
.attr("xlink:href", "./router.svg")
|
|
||||||
.attr("x", -CONF.image.width/2)
|
|
||||||
.attr("y", -CONF.image.height/2)
|
|
||||||
.attr("width", CONF.image.width)
|
|
||||||
.attr("height", CONF.image.height);
|
|
||||||
nodeEnter.append("text")
|
|
||||||
.attr("dx", -CONF.image.width/2)
|
|
||||||
.attr("dy", CONF.image.height-10)
|
|
||||||
.text(function(d) { return "dpid: " + trim_zero(d.dpid); });
|
|
||||||
|
|
||||||
var ports = topo.get_ports();
|
|
||||||
this.port.remove();
|
|
||||||
this.port = this.svg.selectAll(".port").data(ports);
|
|
||||||
var portEnter = this.port.enter().append("g")
|
|
||||||
.attr("class", "port");
|
|
||||||
portEnter.append("circle")
|
|
||||||
.attr("r", 8);
|
|
||||||
portEnter.append("text")
|
|
||||||
.attr("dx", -3)
|
|
||||||
.attr("dy", 3)
|
|
||||||
.text(function(d) { return trim_zero(d.port_no); });
|
|
||||||
};
|
|
||||||
|
|
||||||
function is_valid_link(link) {
|
|
||||||
return (link.src.dpid < link.dst.dpid)
|
|
||||||
}
|
|
||||||
|
|
||||||
var topo = {
|
|
||||||
nodes: [],
|
|
||||||
links: [],
|
|
||||||
node_index: {}, // dpid -> index of nodes array
|
|
||||||
initialize: function (data) {
|
|
||||||
this.add_nodes(data.switches);
|
|
||||||
this.add_links(data.links);
|
|
||||||
},
|
|
||||||
add_nodes: function (nodes) {
|
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
|
||||||
this.nodes.push(nodes[i]);
|
|
||||||
}
|
|
||||||
this.refresh_node_index();
|
|
||||||
},
|
|
||||||
add_links: function (links) {
|
|
||||||
for (var i = 0; i < links.length; i++) {
|
|
||||||
if (!is_valid_link(links[i])) continue;
|
|
||||||
console.log("add link: " + JSON.stringify(links[i]));
|
|
||||||
|
|
||||||
var src_dpid = links[i].src.dpid;
|
|
||||||
var dst_dpid = links[i].dst.dpid;
|
|
||||||
var src_index = this.node_index[src_dpid];
|
|
||||||
var dst_index = this.node_index[dst_dpid];
|
|
||||||
var link = {
|
|
||||||
source: src_index,
|
|
||||||
target: dst_index,
|
|
||||||
port: {
|
|
||||||
src: links[i].src,
|
|
||||||
dst: links[i].dst
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.links.push(link);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
delete_nodes: function (nodes) {
|
|
||||||
for (var i = 0; i < nodes.length; i++) {
|
|
||||||
console.log("delete switch: " + JSON.stringify(nodes[i]));
|
|
||||||
|
|
||||||
node_index = this.get_node_index(nodes[i]);
|
|
||||||
this.nodes.splice(node_index, 1);
|
|
||||||
}
|
|
||||||
this.refresh_node_index();
|
|
||||||
},
|
|
||||||
delete_links: function (links) {
|
|
||||||
for (var i = 0; i < links.length; i++) {
|
|
||||||
if (!is_valid_link(links[i])) continue;
|
|
||||||
console.log("delete link: " + JSON.stringify(links[i]));
|
|
||||||
|
|
||||||
link_index = this.get_link_index(links[i]);
|
|
||||||
this.links.splice(link_index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get_node_index: function (node) {
|
|
||||||
for (var i = 0; i < this.nodes.length; i++) {
|
|
||||||
if (node.dpid == this.nodes[i].dpid) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
get_link_index: function (link) {
|
|
||||||
for (var i = 0; i < this.links.length; i++) {
|
|
||||||
if (link.src.dpid == this.links[i].port.src.dpid &&
|
|
||||||
link.src.port_no == this.links[i].port.src.port_no &&
|
|
||||||
link.dst.dpid == this.links[i].port.dst.dpid &&
|
|
||||||
link.dst.port_no == this.links[i].port.dst.port_no) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
get_ports: function () {
|
|
||||||
var ports = [];
|
|
||||||
var pushed = {};
|
|
||||||
for (var i = 0; i < this.links.length; i++) {
|
|
||||||
function _push(p, dir) {
|
|
||||||
key = p.dpid + ":" + p.port_no;
|
|
||||||
if (key in pushed) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pushed[key] = true;
|
|
||||||
p.link_idx = i;
|
|
||||||
p.link_dir = dir;
|
|
||||||
return ports.push(p);
|
|
||||||
}
|
|
||||||
_push(this.links[i].port.src, "source");
|
|
||||||
_push(this.links[i].port.dst, "target");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ports;
|
|
||||||
},
|
|
||||||
get_port_point: function (d) {
|
|
||||||
var weight = 0.88;
|
|
||||||
|
|
||||||
var link = this.links[d.link_idx];
|
|
||||||
var x1 = link.source.x;
|
|
||||||
var y1 = link.source.y;
|
|
||||||
var x2 = link.target.x;
|
|
||||||
var y2 = link.target.y;
|
|
||||||
|
|
||||||
if (d.link_dir == "target") weight = 1.0 - weight;
|
|
||||||
|
|
||||||
var x = x1 * weight + x2 * (1.0 - weight);
|
|
||||||
var y = y1 * weight + y2 * (1.0 - weight);
|
|
||||||
|
|
||||||
return {x: x, y: y};
|
|
||||||
},
|
|
||||||
refresh_node_index: function(){
|
|
||||||
this.node_index = {};
|
|
||||||
for (var i = 0; i < this.nodes.length; i++) {
|
|
||||||
this.node_index[this.nodes[i].dpid] = i;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var rpc = {
|
|
||||||
event_switch_enter: function (params) {
|
|
||||||
var switches = [];
|
|
||||||
for(var i=0; i < params.length; i++){
|
|
||||||
switches.push({"dpid":params[i].dpid,"ports":params[i].ports});
|
|
||||||
}
|
|
||||||
topo.add_nodes(switches);
|
|
||||||
elem.update();
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
event_switch_leave: function (params) {
|
|
||||||
var switches = [];
|
|
||||||
for(var i=0; i < params.length; i++){
|
|
||||||
switches.push({"dpid":params[i].dpid,"ports":params[i].ports});
|
|
||||||
}
|
|
||||||
topo.delete_nodes(switches);
|
|
||||||
elem.update();
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
event_link_add: function (links) {
|
|
||||||
topo.add_links(links);
|
|
||||||
elem.update();
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
event_link_delete: function (links) {
|
|
||||||
topo.delete_links(links);
|
|
||||||
elem.update();
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function initialize_topology() {
|
|
||||||
d3.json("/v1.0/topology/switches", function(error, switches) {
|
|
||||||
d3.json("/v1.0/topology/links", function(error, links) {
|
|
||||||
topo.initialize({switches: switches, links: links});
|
|
||||||
elem.update();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
initialize_topology();
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
@ -1,25 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="38pt" height="26pt" viewBox="0 0 38 26" version="1.1">
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip1">
|
|
||||||
<path d="M 0.0585938 0.820312 L 37 0.820312 L 37 25.820312 L 0.0585938 25.820312 L 0.0585938 0.820312 Z M 0.0585938 0.820312 "/>
|
|
||||||
</clipPath>
|
|
||||||
<clipPath id="clip2">
|
|
||||||
<path d="M 0.0585938 0.820312 L 37 0.820312 L 37 25.820312 L 0.0585938 25.820312 L 0.0585938 0.820312 Z M 0.0585938 0.820312 "/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g id="surface0">
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0.784314%,42.352941%,60.784314%);fill-opacity:1;" d="M 36.984375 8.171875 C 36.984375 12.121094 28.75 15.324219 18.59375 15.324219 C 8.433594 15.324219 0.199219 12.121094 0.199219 8.171875 L 0.199219 18.648438 C 0.199219 22.597656 8.433594 25.800781 18.59375 25.800781 C 28.75 25.800781 36.984375 22.597656 36.984375 18.648438 L 36.984375 8.171875 "/>
|
|
||||||
<g clip-path="url(#clip1)" clip-rule="nonzero">
|
|
||||||
<path style="fill:none;stroke-width:0.4;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 36.984375 17.828125 C 36.984375 13.878906 28.75 10.675781 18.59375 10.675781 C 8.433594 10.675781 0.199219 13.878906 0.199219 17.828125 L 0.199219 7.351562 C 0.199219 3.402344 8.433594 0.199219 18.59375 0.199219 C 28.75 0.199219 36.984375 3.402344 36.984375 7.351562 L 36.984375 17.828125 Z M 36.984375 17.828125 " transform="matrix(1,0,0,-1,0,26)"/>
|
|
||||||
</g>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0.784314%,42.352941%,60.784314%);fill-opacity:1;" d="M 18.59375 15.324219 C 28.75 15.324219 36.984375 12.121094 36.984375 8.171875 C 36.984375 4.222656 28.75 1.019531 18.59375 1.019531 C 8.433594 1.019531 0.199219 4.222656 0.199219 8.171875 C 0.199219 12.121094 8.433594 15.324219 18.59375 15.324219 "/>
|
|
||||||
<g clip-path="url(#clip2)" clip-rule="nonzero">
|
|
||||||
<path style="fill:none;stroke-width:0.4;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 18.59375 10.675781 C 28.75 10.675781 36.984375 13.878906 36.984375 17.828125 C 36.984375 21.777344 28.75 24.980469 18.59375 24.980469 C 8.433594 24.980469 0.199219 21.777344 0.199219 17.828125 C 0.199219 13.878906 8.433594 10.675781 18.59375 10.675781 Z M 18.59375 10.675781 " transform="matrix(1,0,0,-1,0,26)"/>
|
|
||||||
</g>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14.394531 5.375 L 15.910156 7.652344 L 10.167969 8.980469 L 11.425781 7.9375 L 2.550781 6.417969 L 4.773438 4.75 L 13.339844 6.199219 L 14.394531 5.375 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 22.472656 10.898438 L 21.4375 8.550781 L 26.617188 7.515625 L 25.71875 8.320312 L 34.351562 9.796875 L 32.277344 11.453125 L 23.699219 9.839844 L 22.472656 10.898438 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 19.640625 4.132812 L 25.441406 2.542969 L 25.511719 5.027344 L 24.058594 4.753906 L 21.230469 7.101562 L 18.527344 6.707031 L 21.449219 4.410156 L 19.640625 4.132812 "/>
|
|
||||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 17.152344 13.039062 L 11.628906 14.074219 L 11.421875 11.519531 L 13.011719 11.867188 L 16.050781 9.269531 L 18.742188 9.726562 L 15.496094 12.558594 L 17.152344 13.039062 "/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 3.4 KiB |
@ -1,778 +0,0 @@
|
|||||||
# Copyright (C) 2012 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
|
|
||||||
import json
|
|
||||||
import ast
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller import dpset
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.exception import OSKenException
|
|
||||||
from os_ken.ofproto import ofproto_v1_0
|
|
||||||
from os_ken.ofproto import ofproto_v1_2
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.ofproto import ofproto_v1_4
|
|
||||||
from os_ken.ofproto import ofproto_v1_5
|
|
||||||
from os_ken.lib import ofctl_v1_0
|
|
||||||
from os_ken.lib import ofctl_v1_2
|
|
||||||
from os_ken.lib import ofctl_v1_3
|
|
||||||
from os_ken.lib import ofctl_v1_4
|
|
||||||
from os_ken.lib import ofctl_v1_5
|
|
||||||
from os_ken.app.wsgi import ControllerBase
|
|
||||||
from os_ken.app.wsgi import Response
|
|
||||||
from os_ken.app.wsgi import WSGIApplication
|
|
||||||
|
|
||||||
LOG = logging.getLogger('os_ken.app.ofctl_rest')
|
|
||||||
|
|
||||||
# supported ofctl versions in this restful app
|
|
||||||
supported_ofctl = {
|
|
||||||
ofproto_v1_0.OFP_VERSION: ofctl_v1_0,
|
|
||||||
ofproto_v1_2.OFP_VERSION: ofctl_v1_2,
|
|
||||||
ofproto_v1_3.OFP_VERSION: ofctl_v1_3,
|
|
||||||
ofproto_v1_4.OFP_VERSION: ofctl_v1_4,
|
|
||||||
ofproto_v1_5.OFP_VERSION: ofctl_v1_5,
|
|
||||||
}
|
|
||||||
|
|
||||||
# REST API
|
|
||||||
#
|
|
||||||
|
|
||||||
# Retrieve the switch stats
|
|
||||||
#
|
|
||||||
# get the list of all switches
|
|
||||||
# GET /stats/switches
|
|
||||||
#
|
|
||||||
# get the desc stats of the switch
|
|
||||||
# GET /stats/desc/<dpid>
|
|
||||||
#
|
|
||||||
# get flows desc stats of the switch
|
|
||||||
# GET /stats/flowdesc/<dpid>
|
|
||||||
#
|
|
||||||
# get flows desc stats of the switch filtered by the fields
|
|
||||||
# POST /stats/flowdesc/<dpid>
|
|
||||||
#
|
|
||||||
# get flows stats of the switch
|
|
||||||
# GET /stats/flow/<dpid>
|
|
||||||
#
|
|
||||||
# get flows stats of the switch filtered by the fields
|
|
||||||
# POST /stats/flow/<dpid>
|
|
||||||
#
|
|
||||||
# get aggregate flows stats of the switch
|
|
||||||
# GET /stats/aggregateflow/<dpid>
|
|
||||||
#
|
|
||||||
# get aggregate flows stats of the switch filtered by the fields
|
|
||||||
# POST /stats/aggregateflow/<dpid>
|
|
||||||
#
|
|
||||||
# get table stats of the switch
|
|
||||||
# GET /stats/table/<dpid>
|
|
||||||
#
|
|
||||||
# get table features stats of the switch
|
|
||||||
# GET /stats/tablefeatures/<dpid>
|
|
||||||
#
|
|
||||||
# get ports stats of the switch
|
|
||||||
# GET /stats/port/<dpid>[/<port>]
|
|
||||||
# Note: Specification of port number is optional
|
|
||||||
#
|
|
||||||
# get queues stats of the switch
|
|
||||||
# GET /stats/queue/<dpid>[/<port>[/<queue_id>]]
|
|
||||||
# Note: Specification of port number and queue id are optional
|
|
||||||
# If you want to omitting the port number and setting the queue id,
|
|
||||||
# please specify the keyword "ALL" to the port number
|
|
||||||
# e.g. GET /stats/queue/1/ALL/1
|
|
||||||
#
|
|
||||||
# get queues config stats of the switch
|
|
||||||
# GET /stats/queueconfig/<dpid>[/<port>]
|
|
||||||
# Note: Specification of port number is optional
|
|
||||||
#
|
|
||||||
# get queues desc stats of the switch
|
|
||||||
# GET /stats/queuedesc/<dpid>[/<port>[/<queue_id>]]
|
|
||||||
# Note: Specification of port number and queue id are optional
|
|
||||||
# If you want to omitting the port number and setting the queue id,
|
|
||||||
# please specify the keyword "ALL" to the port number
|
|
||||||
# e.g. GET /stats/queuedesc/1/ALL/1
|
|
||||||
#
|
|
||||||
# get meter features stats of the switch
|
|
||||||
# GET /stats/meterfeatures/<dpid>
|
|
||||||
#
|
|
||||||
# get meter config stats of the switch
|
|
||||||
# GET /stats/meterconfig/<dpid>[/<meter_id>]
|
|
||||||
# Note: Specification of meter id is optional
|
|
||||||
#
|
|
||||||
# get meter desc stats of the switch
|
|
||||||
# GET /stats/meterdesc/<dpid>[/<meter_id>]
|
|
||||||
# Note: Specification of meter id is optional
|
|
||||||
#
|
|
||||||
# get meters stats of the switch
|
|
||||||
# GET /stats/meter/<dpid>[/<meter_id>]
|
|
||||||
# Note: Specification of meter id is optional
|
|
||||||
#
|
|
||||||
# get group features stats of the switch
|
|
||||||
# GET /stats/groupfeatures/<dpid>
|
|
||||||
#
|
|
||||||
# get groups desc stats of the switch
|
|
||||||
# GET /stats/groupdesc/<dpid>[/<group_id>]
|
|
||||||
# Note: Specification of group id is optional (OpenFlow 1.5 or later)
|
|
||||||
#
|
|
||||||
# get groups stats of the switch
|
|
||||||
# GET /stats/group/<dpid>[/<group_id>]
|
|
||||||
# Note: Specification of group id is optional
|
|
||||||
#
|
|
||||||
# get ports description of the switch
|
|
||||||
# GET /stats/portdesc/<dpid>[/<port_no>]
|
|
||||||
# Note: Specification of port number is optional (OpenFlow 1.5 or later)
|
|
||||||
|
|
||||||
# Update the switch stats
|
|
||||||
#
|
|
||||||
# add a flow entry
|
|
||||||
# POST /stats/flowentry/add
|
|
||||||
#
|
|
||||||
# modify all matching flow entries
|
|
||||||
# POST /stats/flowentry/modify
|
|
||||||
#
|
|
||||||
# modify flow entry strictly matching wildcards and priority
|
|
||||||
# POST /stats/flowentry/modify_strict
|
|
||||||
#
|
|
||||||
# delete all matching flow entries
|
|
||||||
# POST /stats/flowentry/delete
|
|
||||||
#
|
|
||||||
# delete flow entry strictly matching wildcards and priority
|
|
||||||
# POST /stats/flowentry/delete_strict
|
|
||||||
#
|
|
||||||
# delete all flow entries of the switch
|
|
||||||
# DELETE /stats/flowentry/clear/<dpid>
|
|
||||||
#
|
|
||||||
# add a meter entry
|
|
||||||
# POST /stats/meterentry/add
|
|
||||||
#
|
|
||||||
# modify a meter entry
|
|
||||||
# POST /stats/meterentry/modify
|
|
||||||
#
|
|
||||||
# delete a meter entry
|
|
||||||
# POST /stats/meterentry/delete
|
|
||||||
#
|
|
||||||
# add a group entry
|
|
||||||
# POST /stats/groupentry/add
|
|
||||||
#
|
|
||||||
# modify a group entry
|
|
||||||
# POST /stats/groupentry/modify
|
|
||||||
#
|
|
||||||
# delete a group entry
|
|
||||||
# POST /stats/groupentry/delete
|
|
||||||
#
|
|
||||||
# modify behavior of the physical port
|
|
||||||
# POST /stats/portdesc/modify
|
|
||||||
#
|
|
||||||
# modify role of controller
|
|
||||||
# POST /stats/role
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# send a experimeter message
|
|
||||||
# POST /stats/experimenter/<dpid>
|
|
||||||
|
|
||||||
|
|
||||||
class CommandNotFoundError(OSKenException):
|
|
||||||
message = 'No such command : %(cmd)s'
|
|
||||||
|
|
||||||
|
|
||||||
class PortNotFoundError(OSKenException):
|
|
||||||
message = 'No such port info: %(port_no)s'
|
|
||||||
|
|
||||||
|
|
||||||
def stats_method(method):
|
|
||||||
def wrapper(self, req, dpid, *args, **kwargs):
|
|
||||||
# Get datapath instance from DPSet
|
|
||||||
try:
|
|
||||||
dp = self.dpset.get(int(str(dpid), 0))
|
|
||||||
except ValueError:
|
|
||||||
LOG.exception('Invalid dpid: %s', dpid)
|
|
||||||
return Response(status=400)
|
|
||||||
if dp is None:
|
|
||||||
LOG.error('No such Datapath: %s', dpid)
|
|
||||||
return Response(status=404)
|
|
||||||
|
|
||||||
# Get lib/ofctl_* module
|
|
||||||
try:
|
|
||||||
ofctl = supported_ofctl.get(dp.ofproto.OFP_VERSION)
|
|
||||||
except KeyError:
|
|
||||||
LOG.exception('Unsupported OF version: %s',
|
|
||||||
dp.ofproto.OFP_VERSION)
|
|
||||||
return Response(status=501)
|
|
||||||
|
|
||||||
# Invoke StatsController method
|
|
||||||
try:
|
|
||||||
ret = method(self, req, dp, ofctl, *args, **kwargs)
|
|
||||||
return Response(content_type='application/json',
|
|
||||||
body=json.dumps(ret))
|
|
||||||
except ValueError:
|
|
||||||
LOG.exception('Invalid syntax: %s', req.body)
|
|
||||||
return Response(status=400)
|
|
||||||
except AttributeError:
|
|
||||||
LOG.exception('Unsupported OF request in this version: %s',
|
|
||||||
dp.ofproto.OFP_VERSION)
|
|
||||||
return Response(status=501)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def command_method(method):
|
|
||||||
def wrapper(self, req, *args, **kwargs):
|
|
||||||
# Parse request json body
|
|
||||||
try:
|
|
||||||
if req.body:
|
|
||||||
# We use ast.literal_eval() to parse request json body
|
|
||||||
# instead of json.loads().
|
|
||||||
# Because we need to parse binary format body
|
|
||||||
# in send_experimenter().
|
|
||||||
body = ast.literal_eval(req.body.decode('utf-8'))
|
|
||||||
else:
|
|
||||||
body = {}
|
|
||||||
except SyntaxError:
|
|
||||||
LOG.exception('Invalid syntax: %s', req.body)
|
|
||||||
return Response(status=400)
|
|
||||||
|
|
||||||
# Get datapath_id from request parameters
|
|
||||||
dpid = body.get('dpid', None)
|
|
||||||
if not dpid:
|
|
||||||
try:
|
|
||||||
dpid = kwargs.pop('dpid')
|
|
||||||
except KeyError:
|
|
||||||
LOG.exception('Cannot get dpid from request parameters')
|
|
||||||
return Response(status=400)
|
|
||||||
|
|
||||||
# Get datapath instance from DPSet
|
|
||||||
try:
|
|
||||||
dp = self.dpset.get(int(str(dpid), 0))
|
|
||||||
except ValueError:
|
|
||||||
LOG.exception('Invalid dpid: %s', dpid)
|
|
||||||
return Response(status=400)
|
|
||||||
if dp is None:
|
|
||||||
LOG.error('No such Datapath: %s', dpid)
|
|
||||||
return Response(status=404)
|
|
||||||
|
|
||||||
# Get lib/ofctl_* module
|
|
||||||
try:
|
|
||||||
ofctl = supported_ofctl.get(dp.ofproto.OFP_VERSION)
|
|
||||||
except KeyError:
|
|
||||||
LOG.exception('Unsupported OF version: version=%s',
|
|
||||||
dp.ofproto.OFP_VERSION)
|
|
||||||
return Response(status=501)
|
|
||||||
|
|
||||||
# Invoke StatsController method
|
|
||||||
try:
|
|
||||||
method(self, req, dp, ofctl, body, *args, **kwargs)
|
|
||||||
return Response(status=200)
|
|
||||||
except ValueError:
|
|
||||||
LOG.exception('Invalid syntax: %s', req.body)
|
|
||||||
return Response(status=400)
|
|
||||||
except AttributeError:
|
|
||||||
LOG.exception('Unsupported OF request in this version: %s',
|
|
||||||
dp.ofproto.OFP_VERSION)
|
|
||||||
return Response(status=501)
|
|
||||||
except CommandNotFoundError as e:
|
|
||||||
LOG.exception(e.message)
|
|
||||||
return Response(status=404)
|
|
||||||
except PortNotFoundError as e:
|
|
||||||
LOG.exception(e.message)
|
|
||||||
return Response(status=404)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class StatsController(ControllerBase):
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(StatsController, self).__init__(req, link, data, **config)
|
|
||||||
self.dpset = data['dpset']
|
|
||||||
self.waiters = data['waiters']
|
|
||||||
|
|
||||||
def get_dpids(self, req, **_kwargs):
|
|
||||||
dps = list(self.dpset.dps.keys())
|
|
||||||
body = json.dumps(dps)
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_desc_stats(self, req, dp, ofctl, **kwargs):
|
|
||||||
return ofctl.get_desc_stats(dp, self.waiters)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_flow_desc(self, req, dp, ofctl, **kwargs):
|
|
||||||
flow = req.json if req.body else {}
|
|
||||||
return ofctl.get_flow_desc(dp, self.waiters, flow)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_flow_stats(self, req, dp, ofctl, **kwargs):
|
|
||||||
flow = req.json if req.body else {}
|
|
||||||
return ofctl.get_flow_stats(dp, self.waiters, flow)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_aggregate_flow_stats(self, req, dp, ofctl, **kwargs):
|
|
||||||
flow = req.json if req.body else {}
|
|
||||||
return ofctl.get_aggregate_flow_stats(dp, self.waiters, flow)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_table_stats(self, req, dp, ofctl, **kwargs):
|
|
||||||
return ofctl.get_table_stats(dp, self.waiters)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_table_features(self, req, dp, ofctl, **kwargs):
|
|
||||||
return ofctl.get_table_features(dp, self.waiters)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_port_stats(self, req, dp, ofctl, port=None, **kwargs):
|
|
||||||
if port == "ALL":
|
|
||||||
port = None
|
|
||||||
|
|
||||||
return ofctl.get_port_stats(dp, self.waiters, port)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_queue_stats(self, req, dp, ofctl,
|
|
||||||
port=None, queue_id=None, **kwargs):
|
|
||||||
if port == "ALL":
|
|
||||||
port = None
|
|
||||||
|
|
||||||
if queue_id == "ALL":
|
|
||||||
queue_id = None
|
|
||||||
|
|
||||||
return ofctl.get_queue_stats(dp, self.waiters, port, queue_id)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_queue_config(self, req, dp, ofctl, port=None, **kwargs):
|
|
||||||
if port == "ALL":
|
|
||||||
port = None
|
|
||||||
|
|
||||||
return ofctl.get_queue_config(dp, self.waiters, port)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_queue_desc(self, req, dp, ofctl,
|
|
||||||
port=None, queue=None, **_kwargs):
|
|
||||||
if port == "ALL":
|
|
||||||
port = None
|
|
||||||
|
|
||||||
if queue == "ALL":
|
|
||||||
queue = None
|
|
||||||
|
|
||||||
return ofctl.get_queue_desc(dp, self.waiters, port, queue)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_meter_features(self, req, dp, ofctl, **kwargs):
|
|
||||||
return ofctl.get_meter_features(dp, self.waiters)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_meter_config(self, req, dp, ofctl, meter_id=None, **kwargs):
|
|
||||||
if meter_id == "ALL":
|
|
||||||
meter_id = None
|
|
||||||
|
|
||||||
return ofctl.get_meter_config(dp, self.waiters, meter_id)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_meter_desc(self, req, dp, ofctl, meter_id=None, **kwargs):
|
|
||||||
if meter_id == "ALL":
|
|
||||||
meter_id = None
|
|
||||||
|
|
||||||
return ofctl.get_meter_desc(dp, self.waiters, meter_id)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_meter_stats(self, req, dp, ofctl, meter_id=None, **kwargs):
|
|
||||||
if meter_id == "ALL":
|
|
||||||
meter_id = None
|
|
||||||
|
|
||||||
return ofctl.get_meter_stats(dp, self.waiters, meter_id)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_group_features(self, req, dp, ofctl, **kwargs):
|
|
||||||
return ofctl.get_group_features(dp, self.waiters)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_group_desc(self, req, dp, ofctl, group_id=None, **kwargs):
|
|
||||||
if dp.ofproto.OFP_VERSION < ofproto_v1_5.OFP_VERSION:
|
|
||||||
return ofctl.get_group_desc(dp, self.waiters)
|
|
||||||
else:
|
|
||||||
return ofctl.get_group_desc(dp, self.waiters, group_id)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_group_stats(self, req, dp, ofctl, group_id=None, **kwargs):
|
|
||||||
if group_id == "ALL":
|
|
||||||
group_id = None
|
|
||||||
|
|
||||||
return ofctl.get_group_stats(dp, self.waiters, group_id)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_port_desc(self, req, dp, ofctl, port_no=None, **kwargs):
|
|
||||||
if dp.ofproto.OFP_VERSION < ofproto_v1_5.OFP_VERSION:
|
|
||||||
return ofctl.get_port_desc(dp, self.waiters)
|
|
||||||
else:
|
|
||||||
return ofctl.get_port_desc(dp, self.waiters, port_no)
|
|
||||||
|
|
||||||
@stats_method
|
|
||||||
def get_role(self, req, dp, ofctl, **kwargs):
|
|
||||||
return ofctl.get_role(dp, self.waiters)
|
|
||||||
|
|
||||||
@command_method
|
|
||||||
def mod_flow_entry(self, req, dp, ofctl, flow, cmd, **kwargs):
|
|
||||||
cmd_convert = {
|
|
||||||
'add': dp.ofproto.OFPFC_ADD,
|
|
||||||
'modify': dp.ofproto.OFPFC_MODIFY,
|
|
||||||
'modify_strict': dp.ofproto.OFPFC_MODIFY_STRICT,
|
|
||||||
'delete': dp.ofproto.OFPFC_DELETE,
|
|
||||||
'delete_strict': dp.ofproto.OFPFC_DELETE_STRICT,
|
|
||||||
}
|
|
||||||
mod_cmd = cmd_convert.get(cmd, None)
|
|
||||||
if mod_cmd is None:
|
|
||||||
raise CommandNotFoundError(cmd=cmd)
|
|
||||||
|
|
||||||
ofctl.mod_flow_entry(dp, flow, mod_cmd)
|
|
||||||
|
|
||||||
@command_method
|
|
||||||
def delete_flow_entry(self, req, dp, ofctl, flow, **kwargs):
|
|
||||||
if ofproto_v1_0.OFP_VERSION == dp.ofproto.OFP_VERSION:
|
|
||||||
flow = {}
|
|
||||||
else:
|
|
||||||
flow = {'table_id': dp.ofproto.OFPTT_ALL}
|
|
||||||
|
|
||||||
ofctl.mod_flow_entry(dp, flow, dp.ofproto.OFPFC_DELETE)
|
|
||||||
|
|
||||||
@command_method
|
|
||||||
def mod_meter_entry(self, req, dp, ofctl, meter, cmd, **kwargs):
|
|
||||||
cmd_convert = {
|
|
||||||
'add': dp.ofproto.OFPMC_ADD,
|
|
||||||
'modify': dp.ofproto.OFPMC_MODIFY,
|
|
||||||
'delete': dp.ofproto.OFPMC_DELETE,
|
|
||||||
}
|
|
||||||
mod_cmd = cmd_convert.get(cmd, None)
|
|
||||||
if mod_cmd is None:
|
|
||||||
raise CommandNotFoundError(cmd=cmd)
|
|
||||||
|
|
||||||
ofctl.mod_meter_entry(dp, meter, mod_cmd)
|
|
||||||
|
|
||||||
@command_method
|
|
||||||
def mod_group_entry(self, req, dp, ofctl, group, cmd, **kwargs):
|
|
||||||
cmd_convert = {
|
|
||||||
'add': dp.ofproto.OFPGC_ADD,
|
|
||||||
'modify': dp.ofproto.OFPGC_MODIFY,
|
|
||||||
'delete': dp.ofproto.OFPGC_DELETE,
|
|
||||||
}
|
|
||||||
mod_cmd = cmd_convert.get(cmd, None)
|
|
||||||
if mod_cmd is None:
|
|
||||||
raise CommandNotFoundError(cmd=cmd)
|
|
||||||
|
|
||||||
ofctl.mod_group_entry(dp, group, mod_cmd)
|
|
||||||
|
|
||||||
@command_method
|
|
||||||
def mod_port_behavior(self, req, dp, ofctl, port_config, cmd, **kwargs):
|
|
||||||
port_no = port_config.get('port_no', None)
|
|
||||||
port_no = int(str(port_no), 0)
|
|
||||||
|
|
||||||
port_info = self.dpset.port_state[int(dp.id)].get(port_no)
|
|
||||||
if port_info:
|
|
||||||
port_config.setdefault('hw_addr', port_info.hw_addr)
|
|
||||||
if dp.ofproto.OFP_VERSION < ofproto_v1_4.OFP_VERSION:
|
|
||||||
port_config.setdefault('advertise', port_info.advertised)
|
|
||||||
else:
|
|
||||||
port_config.setdefault('properties', port_info.properties)
|
|
||||||
else:
|
|
||||||
raise PortNotFoundError(port_no=port_no)
|
|
||||||
|
|
||||||
if cmd != 'modify':
|
|
||||||
raise CommandNotFoundError(cmd=cmd)
|
|
||||||
|
|
||||||
ofctl.mod_port_behavior(dp, port_config)
|
|
||||||
|
|
||||||
@command_method
|
|
||||||
def send_experimenter(self, req, dp, ofctl, exp, **kwargs):
|
|
||||||
ofctl.send_experimenter(dp, exp)
|
|
||||||
|
|
||||||
@command_method
|
|
||||||
def set_role(self, req, dp, ofctl, role, **kwargs):
|
|
||||||
ofctl.set_role(dp, role)
|
|
||||||
|
|
||||||
|
|
||||||
class RestStatsApi(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION,
|
|
||||||
ofproto_v1_2.OFP_VERSION,
|
|
||||||
ofproto_v1_3.OFP_VERSION,
|
|
||||||
ofproto_v1_4.OFP_VERSION,
|
|
||||||
ofproto_v1_5.OFP_VERSION]
|
|
||||||
_CONTEXTS = {
|
|
||||||
'dpset': dpset.DPSet,
|
|
||||||
'wsgi': WSGIApplication
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(RestStatsApi, self).__init__(*args, **kwargs)
|
|
||||||
self.dpset = kwargs['dpset']
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
self.waiters = {}
|
|
||||||
self.data = {}
|
|
||||||
self.data['dpset'] = self.dpset
|
|
||||||
self.data['waiters'] = self.waiters
|
|
||||||
mapper = wsgi.mapper
|
|
||||||
|
|
||||||
wsgi.registory['StatsController'] = self.data
|
|
||||||
path = '/stats'
|
|
||||||
uri = path + '/switches'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_dpids',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/desc/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_desc_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/flowdesc/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_flow_stats',
|
|
||||||
conditions=dict(method=['GET', 'POST']))
|
|
||||||
|
|
||||||
uri = path + '/flow/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_flow_stats',
|
|
||||||
conditions=dict(method=['GET', 'POST']))
|
|
||||||
|
|
||||||
uri = path + '/aggregateflow/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController,
|
|
||||||
action='get_aggregate_flow_stats',
|
|
||||||
conditions=dict(method=['GET', 'POST']))
|
|
||||||
|
|
||||||
uri = path + '/table/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_table_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/tablefeatures/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_table_features',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/port/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_port_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/port/{dpid}/{port}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_port_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queue/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queue/{dpid}/{port}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queue/{dpid}/{port}/{queue_id}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queueconfig/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_config',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queueconfig/{dpid}/{port}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_config',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queuedesc/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queuedesc/{dpid}/{port}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/queuedesc/{dpid}/{port}/{queue}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_queue_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/meterfeatures/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_meter_features',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/meterconfig/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_meter_config',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/meterconfig/{dpid}/{meter_id}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_meter_config',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/meterdesc/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_meter_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/meterdesc/{dpid}/{meter_id}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_meter_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/meter/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_meter_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/meter/{dpid}/{meter_id}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_meter_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/groupfeatures/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_group_features',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/groupdesc/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_group_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/groupdesc/{dpid}/{group_id}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_group_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/group/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_group_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/group/{dpid}/{group_id}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_group_stats',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/portdesc/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_port_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/portdesc/{dpid}/{port_no}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_port_desc',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/role/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='get_role',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri = path + '/flowentry/{cmd}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='mod_flow_entry',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
|
|
||||||
uri = path + '/flowentry/clear/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='delete_flow_entry',
|
|
||||||
conditions=dict(method=['DELETE']))
|
|
||||||
|
|
||||||
uri = path + '/meterentry/{cmd}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='mod_meter_entry',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
|
|
||||||
uri = path + '/groupentry/{cmd}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='mod_group_entry',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
|
|
||||||
uri = path + '/portdesc/{cmd}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='mod_port_behavior',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
|
|
||||||
uri = path + '/experimenter/{dpid}'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='send_experimenter',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
|
|
||||||
uri = path + '/role'
|
|
||||||
mapper.connect('stats', uri,
|
|
||||||
controller=StatsController, action='set_role',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
|
|
||||||
@set_ev_cls([ofp_event.EventOFPStatsReply,
|
|
||||||
ofp_event.EventOFPDescStatsReply,
|
|
||||||
ofp_event.EventOFPFlowStatsReply,
|
|
||||||
ofp_event.EventOFPAggregateStatsReply,
|
|
||||||
ofp_event.EventOFPTableStatsReply,
|
|
||||||
ofp_event.EventOFPTableFeaturesStatsReply,
|
|
||||||
ofp_event.EventOFPPortStatsReply,
|
|
||||||
ofp_event.EventOFPQueueStatsReply,
|
|
||||||
ofp_event.EventOFPQueueDescStatsReply,
|
|
||||||
ofp_event.EventOFPMeterStatsReply,
|
|
||||||
ofp_event.EventOFPMeterFeaturesStatsReply,
|
|
||||||
ofp_event.EventOFPMeterConfigStatsReply,
|
|
||||||
ofp_event.EventOFPGroupStatsReply,
|
|
||||||
ofp_event.EventOFPGroupFeaturesStatsReply,
|
|
||||||
ofp_event.EventOFPGroupDescStatsReply,
|
|
||||||
ofp_event.EventOFPPortDescStatsReply
|
|
||||||
], MAIN_DISPATCHER)
|
|
||||||
def stats_reply_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
dp = msg.datapath
|
|
||||||
|
|
||||||
if dp.id not in self.waiters:
|
|
||||||
return
|
|
||||||
if msg.xid not in self.waiters[dp.id]:
|
|
||||||
return
|
|
||||||
lock, msgs = self.waiters[dp.id][msg.xid]
|
|
||||||
msgs.append(msg)
|
|
||||||
|
|
||||||
flags = 0
|
|
||||||
if dp.ofproto.OFP_VERSION == ofproto_v1_0.OFP_VERSION:
|
|
||||||
flags = dp.ofproto.OFPSF_REPLY_MORE
|
|
||||||
elif dp.ofproto.OFP_VERSION == ofproto_v1_2.OFP_VERSION:
|
|
||||||
flags = dp.ofproto.OFPSF_REPLY_MORE
|
|
||||||
elif dp.ofproto.OFP_VERSION >= ofproto_v1_3.OFP_VERSION:
|
|
||||||
flags = dp.ofproto.OFPMPF_REPLY_MORE
|
|
||||||
|
|
||||||
if msg.flags & flags:
|
|
||||||
return
|
|
||||||
del self.waiters[dp.id][msg.xid]
|
|
||||||
lock.set()
|
|
||||||
|
|
||||||
@set_ev_cls([ofp_event.EventOFPSwitchFeatures,
|
|
||||||
ofp_event.EventOFPQueueGetConfigReply,
|
|
||||||
ofp_event.EventOFPRoleReply,
|
|
||||||
], MAIN_DISPATCHER)
|
|
||||||
def features_reply_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
dp = msg.datapath
|
|
||||||
|
|
||||||
if dp.id not in self.waiters:
|
|
||||||
return
|
|
||||||
if msg.xid not in self.waiters[dp.id]:
|
|
||||||
return
|
|
||||||
lock, msgs = self.waiters[dp.id][msg.xid]
|
|
||||||
msgs.append(msg)
|
|
||||||
|
|
||||||
del self.waiters[dp.id][msg.xid]
|
|
||||||
lock.set()
|
|
@ -1,179 +0,0 @@
|
|||||||
# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
|
|
||||||
# Copyright (C) 2012 Isaku Yamahata <yamahata at private email ne jp>
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
This module provides a set of REST API for switch configuration.
|
|
||||||
- Per-switch Key-Value store
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from six.moves import http_client
|
|
||||||
|
|
||||||
from os_ken.app.wsgi import ControllerBase
|
|
||||||
from os_ken.app.wsgi import Response
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import conf_switch
|
|
||||||
from os_ken.lib import dpid as dpid_lib
|
|
||||||
|
|
||||||
|
|
||||||
# REST API for switch configuration
|
|
||||||
#
|
|
||||||
# get all the switches
|
|
||||||
# GET /v1.0/conf/switches
|
|
||||||
#
|
|
||||||
# get all the configuration keys of a switch
|
|
||||||
# GET /v1.0/conf/switches/<dpid>
|
|
||||||
#
|
|
||||||
# delete all the configuration of a switch
|
|
||||||
# DELETE /v1.0/conf/switches/<dpid>
|
|
||||||
#
|
|
||||||
# set the <key> configuration of a switch
|
|
||||||
# PUT /v1.0/conf/switches/<dpid>/<key>
|
|
||||||
#
|
|
||||||
# get the <key> configuration of a switch
|
|
||||||
# GET /v1.0/conf/switches/<dpid>/<key>
|
|
||||||
#
|
|
||||||
# delete the <key> configuration of a switch
|
|
||||||
# DELETE /v1.0/conf/switches/<dpid>/<key>
|
|
||||||
#
|
|
||||||
# where
|
|
||||||
# <dpid>: datapath id in 16 hex
|
|
||||||
|
|
||||||
|
|
||||||
class ConfSwitchController(ControllerBase):
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(ConfSwitchController, self).__init__(req, link, data, **config)
|
|
||||||
self.conf_switch = data
|
|
||||||
|
|
||||||
def list_switches(self, _req, **_kwargs):
|
|
||||||
dpids = self.conf_switch.dpids()
|
|
||||||
body = json.dumps([dpid_lib.dpid_to_str(dpid) for dpid in dpids])
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _do_switch(dpid, func, ret_func):
|
|
||||||
dpid = dpid_lib.str_to_dpid(dpid)
|
|
||||||
try:
|
|
||||||
ret = func(dpid)
|
|
||||||
except KeyError:
|
|
||||||
return Response(status=http_client.NOT_FOUND,
|
|
||||||
body='no dpid is found %s' %
|
|
||||||
dpid_lib.dpid_to_str(dpid))
|
|
||||||
|
|
||||||
return ret_func(ret)
|
|
||||||
|
|
||||||
def delete_switch(self, _req, dpid, **_kwargs):
|
|
||||||
def _delete_switch(dpid):
|
|
||||||
self.conf_switch.del_dpid(dpid)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _ret(_ret):
|
|
||||||
return Response(status=http_client.ACCEPTED)
|
|
||||||
|
|
||||||
return self._do_switch(dpid, _delete_switch, _ret)
|
|
||||||
|
|
||||||
def list_keys(self, _req, dpid, **_kwargs):
|
|
||||||
def _list_keys(dpid):
|
|
||||||
return self.conf_switch.keys(dpid)
|
|
||||||
|
|
||||||
def _ret(keys):
|
|
||||||
body = json.dumps(keys)
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
||||||
|
|
||||||
return self._do_switch(dpid, _list_keys, _ret)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _do_key(dpid, key, func, ret_func):
|
|
||||||
dpid = dpid_lib.str_to_dpid(dpid)
|
|
||||||
try:
|
|
||||||
ret = func(dpid, key)
|
|
||||||
except KeyError:
|
|
||||||
return Response(status=http_client.NOT_FOUND,
|
|
||||||
body='no dpid/key is found %s %s' %
|
|
||||||
(dpid_lib.dpid_to_str(dpid), key))
|
|
||||||
return ret_func(ret)
|
|
||||||
|
|
||||||
def set_key(self, req, dpid, key, **_kwargs):
|
|
||||||
def _set_val(dpid, key):
|
|
||||||
try:
|
|
||||||
val = req.json if req.body else {}
|
|
||||||
except ValueError:
|
|
||||||
return Response(status=http_client.BAD_REQUEST,
|
|
||||||
body='invalid syntax %s' % req.body)
|
|
||||||
self.conf_switch.set_key(dpid, key, val)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _ret(_ret):
|
|
||||||
return Response(status=http_client.CREATED)
|
|
||||||
|
|
||||||
return self._do_key(dpid, key, _set_val, _ret)
|
|
||||||
|
|
||||||
def get_key(self, _req, dpid, key, **_kwargs):
|
|
||||||
def _get_key(dpid, key):
|
|
||||||
return self.conf_switch.get_key(dpid, key)
|
|
||||||
|
|
||||||
def _ret(val):
|
|
||||||
return Response(content_type='application/json',
|
|
||||||
body=json.dumps(val))
|
|
||||||
|
|
||||||
return self._do_key(dpid, key, _get_key, _ret)
|
|
||||||
|
|
||||||
def delete_key(self, _req, dpid, key, **_kwargs):
|
|
||||||
def _delete_key(dpid, key):
|
|
||||||
self.conf_switch.del_key(dpid, key)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _ret(_ret):
|
|
||||||
return Response()
|
|
||||||
|
|
||||||
return self._do_key(dpid, key, _delete_key, _ret)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfSwitchAPI(app_manager.OSKenApp):
|
|
||||||
_CONTEXTS = {
|
|
||||||
'conf_switch': conf_switch.ConfSwitchSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(ConfSwitchAPI, self).__init__(*args, **kwargs)
|
|
||||||
self.conf_switch = kwargs['conf_switch']
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
mapper = wsgi.mapper
|
|
||||||
|
|
||||||
controller = ConfSwitchController
|
|
||||||
wsgi.registory[controller.__name__] = self.conf_switch
|
|
||||||
route_name = 'conf_switch'
|
|
||||||
uri = '/v1.0/conf/switches'
|
|
||||||
mapper.connect(route_name, uri, controller=controller,
|
|
||||||
action='list_switches',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri += '/{dpid}'
|
|
||||||
requirements = {'dpid': dpid_lib.DPID_PATTERN}
|
|
||||||
s = mapper.submapper(controller=controller, requirements=requirements)
|
|
||||||
s.connect(route_name, uri, action='delete_switch',
|
|
||||||
conditions=dict(method=['DELETE']))
|
|
||||||
s.connect(route_name, uri, action='list_keys',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
|
|
||||||
uri += '/{key}'
|
|
||||||
s.connect(route_name, uri, action='set_key',
|
|
||||||
conditions=dict(method=['PUT']))
|
|
||||||
s.connect(route_name, uri, action='get_key',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
s.connect(route_name, uri, action='delete_key',
|
|
||||||
conditions=dict(method=['DELETE']))
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,119 +0,0 @@
|
|||||||
# 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 json
|
|
||||||
|
|
||||||
from os_ken.app.wsgi import ControllerBase
|
|
||||||
from os_ken.app.wsgi import Response
|
|
||||||
from os_ken.app.wsgi import route
|
|
||||||
from os_ken.app.wsgi import WSGIApplication
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.lib import dpid as dpid_lib
|
|
||||||
from os_ken.topology.api import get_switch, get_link, get_host
|
|
||||||
|
|
||||||
# REST API for switch configuration
|
|
||||||
#
|
|
||||||
# get all the switches
|
|
||||||
# GET /v1.0/topology/switches
|
|
||||||
#
|
|
||||||
# get the switch
|
|
||||||
# GET /v1.0/topology/switches/<dpid>
|
|
||||||
#
|
|
||||||
# get all the links
|
|
||||||
# GET /v1.0/topology/links
|
|
||||||
#
|
|
||||||
# get the links of a switch
|
|
||||||
# GET /v1.0/topology/links/<dpid>
|
|
||||||
#
|
|
||||||
# get all the hosts
|
|
||||||
# GET /v1.0/topology/hosts
|
|
||||||
#
|
|
||||||
# get the hosts of a switch
|
|
||||||
# GET /v1.0/topology/hosts/<dpid>
|
|
||||||
#
|
|
||||||
# where
|
|
||||||
# <dpid>: datapath id in 16 hex
|
|
||||||
|
|
||||||
|
|
||||||
class TopologyAPI(app_manager.OSKenApp):
|
|
||||||
_CONTEXTS = {
|
|
||||||
'wsgi': WSGIApplication
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(TopologyAPI, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
wsgi.register(TopologyController, {'topology_api_app': self})
|
|
||||||
|
|
||||||
|
|
||||||
class TopologyController(ControllerBase):
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(TopologyController, self).__init__(req, link, data, **config)
|
|
||||||
self.topology_api_app = data['topology_api_app']
|
|
||||||
|
|
||||||
@route('topology', '/v1.0/topology/switches',
|
|
||||||
methods=['GET'])
|
|
||||||
def list_switches(self, req, **kwargs):
|
|
||||||
return self._switches(req, **kwargs)
|
|
||||||
|
|
||||||
@route('topology', '/v1.0/topology/switches/{dpid}',
|
|
||||||
methods=['GET'], requirements={'dpid': dpid_lib.DPID_PATTERN})
|
|
||||||
def get_switch(self, req, **kwargs):
|
|
||||||
return self._switches(req, **kwargs)
|
|
||||||
|
|
||||||
@route('topology', '/v1.0/topology/links',
|
|
||||||
methods=['GET'])
|
|
||||||
def list_links(self, req, **kwargs):
|
|
||||||
return self._links(req, **kwargs)
|
|
||||||
|
|
||||||
@route('topology', '/v1.0/topology/links/{dpid}',
|
|
||||||
methods=['GET'], requirements={'dpid': dpid_lib.DPID_PATTERN})
|
|
||||||
def get_links(self, req, **kwargs):
|
|
||||||
return self._links(req, **kwargs)
|
|
||||||
|
|
||||||
@route('topology', '/v1.0/topology/hosts',
|
|
||||||
methods=['GET'])
|
|
||||||
def list_hosts(self, req, **kwargs):
|
|
||||||
return self._hosts(req, **kwargs)
|
|
||||||
|
|
||||||
@route('topology', '/v1.0/topology/hosts/{dpid}',
|
|
||||||
methods=['GET'], requirements={'dpid': dpid_lib.DPID_PATTERN})
|
|
||||||
def get_hosts(self, req, **kwargs):
|
|
||||||
return self._hosts(req, **kwargs)
|
|
||||||
|
|
||||||
def _switches(self, req, **kwargs):
|
|
||||||
dpid = None
|
|
||||||
if 'dpid' in kwargs:
|
|
||||||
dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
|
|
||||||
switches = get_switch(self.topology_api_app, dpid)
|
|
||||||
body = json.dumps([switch.to_dict() for switch in switches])
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
||||||
|
|
||||||
def _links(self, req, **kwargs):
|
|
||||||
dpid = None
|
|
||||||
if 'dpid' in kwargs:
|
|
||||||
dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
|
|
||||||
links = get_link(self.topology_api_app, dpid)
|
|
||||||
body = json.dumps([link.to_dict() for link in links])
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
||||||
|
|
||||||
def _hosts(self, req, **kwargs):
|
|
||||||
dpid = None
|
|
||||||
if 'dpid' in kwargs:
|
|
||||||
dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
|
|
||||||
hosts = get_host(self.topology_api_app, dpid)
|
|
||||||
body = json.dumps([host.to_dict() for host in hosts])
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
File diff suppressed because it is too large
Load Diff
@ -1,95 +0,0 @@
|
|||||||
# Copyright (C) 2016 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.
|
|
||||||
|
|
||||||
from operator import attrgetter
|
|
||||||
|
|
||||||
from os_ken.app import simple_switch_13
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.lib import hub
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleMonitor13(simple_switch_13.SimpleSwitch13):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleMonitor13, self).__init__(*args, **kwargs)
|
|
||||||
self.datapaths = {}
|
|
||||||
self.monitor_thread = hub.spawn(self._monitor)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPStateChange,
|
|
||||||
[MAIN_DISPATCHER, DEAD_DISPATCHER])
|
|
||||||
def _state_change_handler(self, ev):
|
|
||||||
datapath = ev.datapath
|
|
||||||
if ev.state == MAIN_DISPATCHER:
|
|
||||||
if datapath.id not in self.datapaths:
|
|
||||||
self.logger.debug('register datapath: %016x', datapath.id)
|
|
||||||
self.datapaths[datapath.id] = datapath
|
|
||||||
elif ev.state == DEAD_DISPATCHER:
|
|
||||||
if datapath.id in self.datapaths:
|
|
||||||
self.logger.debug('unregister datapath: %016x', datapath.id)
|
|
||||||
del self.datapaths[datapath.id]
|
|
||||||
|
|
||||||
def _monitor(self):
|
|
||||||
while True:
|
|
||||||
for dp in self.datapaths.values():
|
|
||||||
self._request_stats(dp)
|
|
||||||
hub.sleep(10)
|
|
||||||
|
|
||||||
def _request_stats(self, datapath):
|
|
||||||
self.logger.debug('send stats request: %016x', datapath.id)
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
req = parser.OFPFlowStatsRequest(datapath)
|
|
||||||
datapath.send_msg(req)
|
|
||||||
|
|
||||||
req = parser.OFPPortStatsRequest(datapath, 0, ofproto.OFPP_ANY)
|
|
||||||
datapath.send_msg(req)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPFlowStatsReply, MAIN_DISPATCHER)
|
|
||||||
def _flow_stats_reply_handler(self, ev):
|
|
||||||
body = ev.msg.body
|
|
||||||
|
|
||||||
self.logger.info('datapath '
|
|
||||||
'in-port eth-dst '
|
|
||||||
'out-port packets bytes')
|
|
||||||
self.logger.info('---------------- '
|
|
||||||
'-------- ----------------- '
|
|
||||||
'-------- -------- --------')
|
|
||||||
for stat in sorted([flow for flow in body if flow.priority == 1],
|
|
||||||
key=lambda flow: (flow.match['in_port'],
|
|
||||||
flow.match['eth_dst'])):
|
|
||||||
self.logger.info('%016x %8x %17s %8x %8d %8d',
|
|
||||||
ev.msg.datapath.id,
|
|
||||||
stat.match['in_port'], stat.match['eth_dst'],
|
|
||||||
stat.instructions[0].actions[0].port,
|
|
||||||
stat.packet_count, stat.byte_count)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPortStatsReply, MAIN_DISPATCHER)
|
|
||||||
def _port_stats_reply_handler(self, ev):
|
|
||||||
body = ev.msg.body
|
|
||||||
|
|
||||||
self.logger.info('datapath port '
|
|
||||||
'rx-pkts rx-bytes rx-error '
|
|
||||||
'tx-pkts tx-bytes tx-error')
|
|
||||||
self.logger.info('---------------- -------- '
|
|
||||||
'-------- -------- -------- '
|
|
||||||
'-------- -------- --------')
|
|
||||||
for stat in sorted(body, key=attrgetter('port_no')):
|
|
||||||
self.logger.info('%016x %8x %8d %8d %8d %8d %8d %8d',
|
|
||||||
ev.msg.datapath.id, stat.port_no,
|
|
||||||
stat.rx_packets, stat.rx_bytes, stat.rx_errors,
|
|
||||||
stat.tx_packets, stat.tx_bytes, stat.tx_errors)
|
|
@ -1,110 +0,0 @@
|
|||||||
# Copyright (C) 2011 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
An OpenFlow 1.0 L2 learning switch implementation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_0
|
|
||||||
from os_ken.lib.mac import haddr_to_bin
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.lib.packet import ether_types
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitch(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitch, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
|
|
||||||
def add_flow(self, datapath, in_port, dst, src, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
match = datapath.ofproto_parser.OFPMatch(
|
|
||||||
in_port=in_port,
|
|
||||||
dl_dst=haddr_to_bin(dst), dl_src=haddr_to_bin(src))
|
|
||||||
|
|
||||||
mod = datapath.ofproto_parser.OFPFlowMod(
|
|
||||||
datapath=datapath, match=match, cookie=0,
|
|
||||||
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
|
|
||||||
priority=ofproto.OFP_DEFAULT_PRIORITY,
|
|
||||||
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocol(ethernet.ethernet)
|
|
||||||
|
|
||||||
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
|
|
||||||
# ignore lldp packet
|
|
||||||
return
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, msg.in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = msg.in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
self.add_flow(datapath, msg.in_port, dst, src, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = datapath.ofproto_parser.OFPPacketOut(
|
|
||||||
datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
|
|
||||||
actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
|
|
||||||
def _port_status_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
reason = msg.reason
|
|
||||||
port_no = msg.desc.port_no
|
|
||||||
|
|
||||||
ofproto = msg.datapath.ofproto
|
|
||||||
if reason == ofproto.OFPPR_ADD:
|
|
||||||
self.logger.info("port added %s", port_no)
|
|
||||||
elif reason == ofproto.OFPPR_DELETE:
|
|
||||||
self.logger.info("port deleted %s", port_no)
|
|
||||||
elif reason == ofproto.OFPPR_MODIFY:
|
|
||||||
self.logger.info("port modified %s", port_no)
|
|
||||||
else:
|
|
||||||
self.logger.info("Illeagal port state %s %s", port_no, reason)
|
|
@ -1,93 +0,0 @@
|
|||||||
# Copyright (C) 2011 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_2
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.lib.packet import ether_types
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitch12(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_2.OFP_VERSION]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitch12, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
|
|
||||||
def add_flow(self, datapath, port, dst, src, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
match = datapath.ofproto_parser.OFPMatch(in_port=port,
|
|
||||||
eth_dst=dst,
|
|
||||||
eth_src=src)
|
|
||||||
inst = [datapath.ofproto_parser.OFPInstructionActions(
|
|
||||||
ofproto.OFPIT_APPLY_ACTIONS, actions)]
|
|
||||||
|
|
||||||
mod = datapath.ofproto_parser.OFPFlowMod(
|
|
||||||
datapath=datapath, cookie=0, cookie_mask=0, table_id=0,
|
|
||||||
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
|
|
||||||
priority=0, buffer_id=ofproto.OFP_NO_BUFFER,
|
|
||||||
out_port=ofproto.OFPP_ANY,
|
|
||||||
out_group=ofproto.OFPG_ANY,
|
|
||||||
flags=0, match=match, instructions=inst)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
|
|
||||||
# ignore lldp packet
|
|
||||||
return
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
self.add_flow(datapath, in_port, dst, src, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = datapath.ofproto_parser.OFPPacketOut(
|
|
||||||
datapath=datapath, buffer_id=msg.buffer_id, in_port=in_port,
|
|
||||||
actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
@ -1,119 +0,0 @@
|
|||||||
# Copyright (C) 2011 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.lib.packet import ether_types
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitch13(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitch13, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
|
||||||
def switch_features_handler(self, ev):
|
|
||||||
datapath = ev.msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
# install table-miss flow entry
|
|
||||||
#
|
|
||||||
# We specify NO BUFFER to max_len of the output action due to
|
|
||||||
# OVS bug. At this moment, if we specify a lesser number, e.g.,
|
|
||||||
# 128, OVS will send Packet-In with invalid buffer_id and
|
|
||||||
# truncated packet data. In that case, we cannot output packets
|
|
||||||
# correctly. The bug has been fixed in OVS v2.1.0.
|
|
||||||
match = parser.OFPMatch()
|
|
||||||
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
|
|
||||||
ofproto.OFPCML_NO_BUFFER)]
|
|
||||||
self.add_flow(datapath, 0, match, actions)
|
|
||||||
|
|
||||||
def add_flow(self, datapath, priority, match, actions, buffer_id=None):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
|
|
||||||
actions)]
|
|
||||||
if buffer_id:
|
|
||||||
mod = parser.OFPFlowMod(datapath=datapath, buffer_id=buffer_id,
|
|
||||||
priority=priority, match=match,
|
|
||||||
instructions=inst)
|
|
||||||
else:
|
|
||||||
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
|
|
||||||
match=match, instructions=inst)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
# If you hit this you might want to increase
|
|
||||||
# the "miss_send_length" of your switch
|
|
||||||
if ev.msg.msg_len < ev.msg.total_len:
|
|
||||||
self.logger.debug("packet truncated: only %s of %s bytes",
|
|
||||||
ev.msg.msg_len, ev.msg.total_len)
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
|
|
||||||
# ignore lldp packet
|
|
||||||
return
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
|
|
||||||
# verify if we have a valid buffer_id, if yes avoid to send both
|
|
||||||
# flow_mod & packet_out
|
|
||||||
if msg.buffer_id != ofproto.OFP_NO_BUFFER:
|
|
||||||
self.add_flow(datapath, 1, match, actions, msg.buffer_id)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
|
|
||||||
in_port=in_port, actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
@ -1,105 +0,0 @@
|
|||||||
# Copyright (C) 2011 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_4
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.lib.packet import ether_types
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitch14(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_4.OFP_VERSION]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitch14, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
|
||||||
def switch_features_handler(self, ev):
|
|
||||||
datapath = ev.msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
# install table-miss flow entry
|
|
||||||
#
|
|
||||||
# We specify NO BUFFER to max_len of the output action due to
|
|
||||||
# OVS bug. At this moment, if we specify a lesser number, e.g.,
|
|
||||||
# 128, OVS will send Packet-In with invalid buffer_id and
|
|
||||||
# truncated packet data. In that case, we cannot output packets
|
|
||||||
# correctly. The bug has been fixed in OVS v2.1.0.
|
|
||||||
match = parser.OFPMatch()
|
|
||||||
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
|
|
||||||
ofproto.OFPCML_NO_BUFFER)]
|
|
||||||
self.add_flow(datapath, 0, match, actions)
|
|
||||||
|
|
||||||
def add_flow(self, datapath, priority, match, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
|
|
||||||
actions)]
|
|
||||||
|
|
||||||
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
|
|
||||||
match=match, instructions=inst)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
|
|
||||||
# ignore lldp packet
|
|
||||||
return
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst, eth_src=src)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
|
|
||||||
in_port=in_port, actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
@ -1,107 +0,0 @@
|
|||||||
# Copyright (C) 2011 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_5
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.lib.packet import ether_types
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitch15(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_5.OFP_VERSION]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitch15, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
|
||||||
def switch_features_handler(self, ev):
|
|
||||||
datapath = ev.msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
# install table-miss flow entry
|
|
||||||
#
|
|
||||||
# We specify NO BUFFER to max_len of the output action due to
|
|
||||||
# OVS bug. At this moment, if we specify a lesser number, e.g.,
|
|
||||||
# 128, OVS will send Packet-In with invalid buffer_id and
|
|
||||||
# truncated packet data. In that case, we cannot output packets
|
|
||||||
# correctly. The bug has been fixed in OVS v2.1.0.
|
|
||||||
match = parser.OFPMatch()
|
|
||||||
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
|
|
||||||
ofproto.OFPCML_NO_BUFFER)]
|
|
||||||
self.add_flow(datapath, 0, match, actions)
|
|
||||||
|
|
||||||
def add_flow(self, datapath, priority, match, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
|
|
||||||
actions)]
|
|
||||||
|
|
||||||
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
|
|
||||||
match=match, instructions=inst)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
if eth.ethertype == ether_types.ETH_TYPE_LLDP:
|
|
||||||
# ignore lldp packet
|
|
||||||
return
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
match = parser.OFPMatch(in_port=in_port)
|
|
||||||
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
|
|
||||||
match=match, actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
@ -1,104 +0,0 @@
|
|||||||
# 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 struct
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_0
|
|
||||||
from os_ken.lib import addrconv
|
|
||||||
from os_ken.lib import igmplib
|
|
||||||
from os_ken.lib.dpid import str_to_dpid
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchIgmp(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
|
|
||||||
_CONTEXTS = {'igmplib': igmplib.IgmpLib}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchIgmp, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
self._snoop = kwargs['igmplib']
|
|
||||||
# if you want a switch to operate as a querier,
|
|
||||||
# set up as follows:
|
|
||||||
self._snoop.set_querier_mode(
|
|
||||||
dpid=str_to_dpid('0000000000000001'), server_port=2)
|
|
||||||
# dpid the datapath id that will operate as a querier.
|
|
||||||
# server_port a port number which connect to the multicast
|
|
||||||
# server.
|
|
||||||
#
|
|
||||||
# NOTE: you can set up only the one querier.
|
|
||||||
# when you called this method several times,
|
|
||||||
# only the last one becomes effective.
|
|
||||||
|
|
||||||
def add_flow(self, datapath, in_port, dst, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
match = parser.OFPMatch(in_port=in_port,
|
|
||||||
dl_dst=addrconv.mac.text_to_bin(dst))
|
|
||||||
mod = parser.OFPFlowMod(
|
|
||||||
datapath=datapath, match=match, cookie=0,
|
|
||||||
command=ofproto.OFPFC_ADD, actions=actions)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(igmplib.EventPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
(dst_, src_, _eth_type) = struct.unpack_from(
|
|
||||||
'!6s6sH', buffer(msg.data), 0)
|
|
||||||
src = addrconv.mac.bin_to_text(src_)
|
|
||||||
dst = addrconv.mac.bin_to_text(dst_)
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s",
|
|
||||||
dpid, src, dst, msg.in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = msg.in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
self.add_flow(datapath, msg.in_port, dst, actions)
|
|
||||||
|
|
||||||
out = datapath.ofproto_parser.OFPPacketOut(
|
|
||||||
datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
|
|
||||||
actions=actions)
|
|
||||||
datapath.send_msg(out)
|
|
||||||
|
|
||||||
@set_ev_cls(igmplib.EventMulticastGroupStateChanged,
|
|
||||||
MAIN_DISPATCHER)
|
|
||||||
def _status_changed(self, ev):
|
|
||||||
msg = {
|
|
||||||
igmplib.MG_GROUP_ADDED: 'Multicast Group Added',
|
|
||||||
igmplib.MG_MEMBER_CHANGED: 'Multicast Group Member Changed',
|
|
||||||
igmplib.MG_GROUP_REMOVED: 'Multicast Group Removed',
|
|
||||||
}
|
|
||||||
self.logger.info("%s: [%s] querier:[%s] hosts:%s",
|
|
||||||
msg.get(ev.reason), ev.address, ev.src,
|
|
||||||
ev.dsts)
|
|
@ -1,92 +0,0 @@
|
|||||||
# Copyright (C) 2016 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.lib import igmplib
|
|
||||||
from os_ken.lib.dpid import str_to_dpid
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.app import simple_switch_13
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchIgmp13(simple_switch_13.SimpleSwitch13):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
|
||||||
_CONTEXTS = {'igmplib': igmplib.IgmpLib}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchIgmp13, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
self._snoop = kwargs['igmplib']
|
|
||||||
self._snoop.set_querier_mode(
|
|
||||||
dpid=str_to_dpid('0000000000000001'), server_port=2)
|
|
||||||
|
|
||||||
@set_ev_cls(igmplib.EventPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
|
|
||||||
in_port=in_port, actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
||||||
|
|
||||||
@set_ev_cls(igmplib.EventMulticastGroupStateChanged,
|
|
||||||
MAIN_DISPATCHER)
|
|
||||||
def _status_changed(self, ev):
|
|
||||||
msg = {
|
|
||||||
igmplib.MG_GROUP_ADDED: 'Multicast Group Added',
|
|
||||||
igmplib.MG_MEMBER_CHANGED: 'Multicast Group Member Changed',
|
|
||||||
igmplib.MG_GROUP_REMOVED: 'Multicast Group Removed',
|
|
||||||
}
|
|
||||||
self.logger.info("%s: [%s] querier:[%s] hosts:%s",
|
|
||||||
msg.get(ev.reason), ev.address, ev.src,
|
|
||||||
ev.dsts)
|
|
@ -1,116 +0,0 @@
|
|||||||
# 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 struct
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_0
|
|
||||||
from os_ken.lib import addrconv
|
|
||||||
from os_ken.lib import lacplib
|
|
||||||
from os_ken.lib.dpid import str_to_dpid
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchLacp(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
|
|
||||||
_CONTEXTS = {'lacplib': lacplib.LacpLib}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchLacp, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
self._lacp = kwargs['lacplib']
|
|
||||||
# in this sample application, bonding i/fs of the switchs
|
|
||||||
# shall be set up as follows:
|
|
||||||
# - the port 1 and 2 of the datapath 1 face the slave i/fs.
|
|
||||||
# - the port 3, 4 and 5 of the datapath 1 face the others.
|
|
||||||
# - the port 1 and 2 of the datapath 2 face the others.
|
|
||||||
self._lacp.add(
|
|
||||||
dpid=str_to_dpid('0000000000000001'), ports=[1, 2])
|
|
||||||
self._lacp.add(
|
|
||||||
dpid=str_to_dpid('0000000000000001'), ports=[3, 4, 5])
|
|
||||||
self._lacp.add(
|
|
||||||
dpid=str_to_dpid('0000000000000002'), ports=[1, 2])
|
|
||||||
|
|
||||||
def add_flow(self, datapath, in_port, dst, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
match = parser.OFPMatch(in_port=in_port,
|
|
||||||
dl_dst=addrconv.mac.text_to_bin(dst))
|
|
||||||
mod = parser.OFPFlowMod(
|
|
||||||
datapath=datapath, match=match, cookie=0,
|
|
||||||
command=ofproto.OFPFC_ADD, actions=actions)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
def del_flow(self, datapath, dst):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
match = parser.OFPMatch(dl_dst=addrconv.mac.text_to_bin(dst))
|
|
||||||
mod = parser.OFPFlowMod(
|
|
||||||
datapath=datapath, match=match, cookie=0,
|
|
||||||
command=ofproto.OFPFC_DELETE)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(lacplib.EventPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
(dst_, src_, _eth_type) = struct.unpack_from(
|
|
||||||
'!6s6sH', buffer(msg.data), 0)
|
|
||||||
src = addrconv.mac.bin_to_text(src_)
|
|
||||||
dst = addrconv.mac.bin_to_text(dst_)
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s",
|
|
||||||
dpid, src, dst, msg.in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = msg.in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
self.add_flow(datapath, msg.in_port, dst, actions)
|
|
||||||
|
|
||||||
out = datapath.ofproto_parser.OFPPacketOut(
|
|
||||||
datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
|
|
||||||
actions=actions)
|
|
||||||
datapath.send_msg(out)
|
|
||||||
|
|
||||||
@set_ev_cls(lacplib.EventSlaveStateChanged, MAIN_DISPATCHER)
|
|
||||||
def _slave_state_changed_handler(self, ev):
|
|
||||||
datapath = ev.datapath
|
|
||||||
dpid = datapath.id
|
|
||||||
port_no = ev.port
|
|
||||||
enabled = ev.enabled
|
|
||||||
self.logger.info("slave state changed port: %d enabled: %s",
|
|
||||||
port_no, enabled)
|
|
||||||
if dpid in self.mac_to_port:
|
|
||||||
for mac in self.mac_to_port[dpid]:
|
|
||||||
self.del_flow(datapath, mac)
|
|
||||||
del self.mac_to_port[dpid]
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
@ -1,106 +0,0 @@
|
|||||||
# Copyright (C) 2016 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.lib import lacplib
|
|
||||||
from os_ken.lib.dpid import str_to_dpid
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.app import simple_switch_13
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchLacp13(simple_switch_13.SimpleSwitch13):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
|
||||||
_CONTEXTS = {'lacplib': lacplib.LacpLib}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchLacp13, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
self._lacp = kwargs['lacplib']
|
|
||||||
self._lacp.add(
|
|
||||||
dpid=str_to_dpid('0000000000000001'), ports=[1, 2])
|
|
||||||
|
|
||||||
def del_flow(self, datapath, match):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
mod = parser.OFPFlowMod(datapath=datapath,
|
|
||||||
command=ofproto.OFPFC_DELETE,
|
|
||||||
out_port=ofproto.OFPP_ANY,
|
|
||||||
out_group=ofproto.OFPG_ANY,
|
|
||||||
match=match)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(lacplib.EventPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
|
|
||||||
in_port=in_port, actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
||||||
|
|
||||||
@set_ev_cls(lacplib.EventSlaveStateChanged, MAIN_DISPATCHER)
|
|
||||||
def _slave_state_changed_handler(self, ev):
|
|
||||||
datapath = ev.datapath
|
|
||||||
dpid = datapath.id
|
|
||||||
port_no = ev.port
|
|
||||||
enabled = ev.enabled
|
|
||||||
self.logger.info("slave state changed port: %d enabled: %s",
|
|
||||||
port_no, enabled)
|
|
||||||
if dpid in self.mac_to_port:
|
|
||||||
for mac in self.mac_to_port[dpid]:
|
|
||||||
match = datapath.ofproto_parser.OFPMatch(eth_dst=mac)
|
|
||||||
self.del_flow(datapath, match)
|
|
||||||
del self.mac_to_port[dpid]
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
@ -1,116 +0,0 @@
|
|||||||
# Copyright (C) 2016 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 json
|
|
||||||
|
|
||||||
from os_ken.app import simple_switch_13
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.app.wsgi import ControllerBase
|
|
||||||
from os_ken.app.wsgi import Response
|
|
||||||
from os_ken.app.wsgi import route
|
|
||||||
from os_ken.app.wsgi import WSGIApplication
|
|
||||||
from os_ken.lib import dpid as dpid_lib
|
|
||||||
|
|
||||||
simple_switch_instance_name = 'simple_switch_api_app'
|
|
||||||
url = '/simpleswitch/mactable/{dpid}'
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchRest13(simple_switch_13.SimpleSwitch13):
|
|
||||||
|
|
||||||
_CONTEXTS = {'wsgi': WSGIApplication}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchRest13, self).__init__(*args, **kwargs)
|
|
||||||
self.switches = {}
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
wsgi.register(SimpleSwitchController,
|
|
||||||
{simple_switch_instance_name: self})
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
|
||||||
def switch_features_handler(self, ev):
|
|
||||||
super(SimpleSwitchRest13, self).switch_features_handler(ev)
|
|
||||||
datapath = ev.msg.datapath
|
|
||||||
self.switches[datapath.id] = datapath
|
|
||||||
self.mac_to_port.setdefault(datapath.id, {})
|
|
||||||
|
|
||||||
def set_mac_to_port(self, dpid, entry):
|
|
||||||
mac_table = self.mac_to_port.setdefault(dpid, {})
|
|
||||||
datapath = self.switches.get(dpid)
|
|
||||||
|
|
||||||
entry_port = entry['port']
|
|
||||||
entry_mac = entry['mac']
|
|
||||||
|
|
||||||
if datapath is not None:
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
if entry_port not in mac_table.values():
|
|
||||||
|
|
||||||
for mac, port in mac_table.items():
|
|
||||||
|
|
||||||
# from known device to new device
|
|
||||||
actions = [parser.OFPActionOutput(entry_port)]
|
|
||||||
match = parser.OFPMatch(in_port=port, eth_dst=entry_mac)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
# from new device to known device
|
|
||||||
actions = [parser.OFPActionOutput(port)]
|
|
||||||
match = parser.OFPMatch(in_port=entry_port, eth_dst=mac)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
mac_table.update({entry_mac: entry_port})
|
|
||||||
return mac_table
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchController(ControllerBase):
|
|
||||||
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(SimpleSwitchController, self).__init__(req, link, data, **config)
|
|
||||||
self.simple_switch_app = data[simple_switch_instance_name]
|
|
||||||
|
|
||||||
@route('simpleswitch', url, methods=['GET'],
|
|
||||||
requirements={'dpid': dpid_lib.DPID_PATTERN})
|
|
||||||
def list_mac_table(self, req, **kwargs):
|
|
||||||
|
|
||||||
simple_switch = self.simple_switch_app
|
|
||||||
dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
|
|
||||||
|
|
||||||
if dpid not in simple_switch.mac_to_port:
|
|
||||||
return Response(status=404)
|
|
||||||
|
|
||||||
mac_table = simple_switch.mac_to_port.get(dpid, {})
|
|
||||||
body = json.dumps(mac_table)
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
||||||
|
|
||||||
@route('simpleswitch', url, methods=['PUT'],
|
|
||||||
requirements={'dpid': dpid_lib.DPID_PATTERN})
|
|
||||||
def put_mac_table(self, req, **kwargs):
|
|
||||||
|
|
||||||
simple_switch = self.simple_switch_app
|
|
||||||
dpid = dpid_lib.str_to_dpid(kwargs['dpid'])
|
|
||||||
try:
|
|
||||||
new_entry = req.json if req.body else {}
|
|
||||||
except ValueError:
|
|
||||||
raise Response(status=400)
|
|
||||||
|
|
||||||
if dpid not in simple_switch.mac_to_port:
|
|
||||||
return Response(status=404)
|
|
||||||
|
|
||||||
try:
|
|
||||||
mac_table = simple_switch.set_mac_to_port(dpid, new_entry)
|
|
||||||
body = json.dumps(mac_table)
|
|
||||||
return Response(content_type='application/json', body=body)
|
|
||||||
except Exception as e:
|
|
||||||
return Response(status=500)
|
|
@ -1,144 +0,0 @@
|
|||||||
# 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 array
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.lib.packet import ipv4
|
|
||||||
from os_ken.lib.packet import icmp
|
|
||||||
from os_ken.lib import snortlib
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchSnort(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
|
||||||
_CONTEXTS = {'snortlib': snortlib.SnortLib}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchSnort, self).__init__(*args, **kwargs)
|
|
||||||
self.snort = kwargs['snortlib']
|
|
||||||
self.snort_port = 3
|
|
||||||
self.mac_to_port = {}
|
|
||||||
|
|
||||||
socket_config = {'unixsock': True}
|
|
||||||
|
|
||||||
self.snort.set_config(socket_config)
|
|
||||||
self.snort.start_socket_server()
|
|
||||||
|
|
||||||
def packet_print(self, pkt):
|
|
||||||
pkt = packet.Packet(array.array('B', pkt))
|
|
||||||
|
|
||||||
eth = pkt.get_protocol(ethernet.ethernet)
|
|
||||||
_ipv4 = pkt.get_protocol(ipv4.ipv4)
|
|
||||||
_icmp = pkt.get_protocol(icmp.icmp)
|
|
||||||
|
|
||||||
if _icmp:
|
|
||||||
self.logger.info("%r", _icmp)
|
|
||||||
|
|
||||||
if _ipv4:
|
|
||||||
self.logger.info("%r", _ipv4)
|
|
||||||
|
|
||||||
if eth:
|
|
||||||
self.logger.info("%r", eth)
|
|
||||||
|
|
||||||
# for p in pkt.protocols:
|
|
||||||
# if hasattr(p, 'protocol_name') is False:
|
|
||||||
# break
|
|
||||||
# print('p: %s' % p.protocol_name)
|
|
||||||
|
|
||||||
@set_ev_cls(snortlib.EventAlert, MAIN_DISPATCHER)
|
|
||||||
def _dump_alert(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
|
|
||||||
print('alertmsg: %s' % ''.join(msg.alertmsg))
|
|
||||||
|
|
||||||
self.packet_print(msg.pkt)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
|
||||||
def switch_features_handler(self, ev):
|
|
||||||
datapath = ev.msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
# install table-miss flow entry
|
|
||||||
#
|
|
||||||
# We specify NO BUFFER to max_len of the output action due to
|
|
||||||
# OVS bug. At this moment, if we specify a lesser number, e.g.,
|
|
||||||
# 128, OVS will send Packet-In with invalid buffer_id and
|
|
||||||
# truncated packet data. In that case, we cannot output packets
|
|
||||||
# correctly.
|
|
||||||
match = parser.OFPMatch()
|
|
||||||
actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
|
|
||||||
ofproto.OFPCML_NO_BUFFER)]
|
|
||||||
self.add_flow(datapath, 0, match, actions)
|
|
||||||
|
|
||||||
def add_flow(self, datapath, priority, match, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
|
|
||||||
actions)]
|
|
||||||
|
|
||||||
mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
|
|
||||||
match=match, instructions=inst)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
# self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [parser.OFPActionOutput(out_port),
|
|
||||||
parser.OFPActionOutput(self.snort_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
|
|
||||||
in_port=in_port, actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
@ -1,133 +0,0 @@
|
|||||||
# 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 struct
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller.handler import MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_0
|
|
||||||
from os_ken.lib import dpid as dpid_lib
|
|
||||||
from os_ken.lib import stplib
|
|
||||||
from os_ken.lib.mac import haddr_to_str
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchStp(app_manager.OSKenApp):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
|
|
||||||
_CONTEXTS = {'stplib': stplib.Stp}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchStp, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
self.stp = kwargs['stplib']
|
|
||||||
|
|
||||||
# Sample of stplib config.
|
|
||||||
# please refer to stplib.Stp.set_config() for details.
|
|
||||||
"""
|
|
||||||
config = {dpid_lib.str_to_dpid('0000000000000001'):
|
|
||||||
{'bridge': {'priority': 0x8000,
|
|
||||||
'max_age': 10},
|
|
||||||
'ports': {1: {'priority': 0x80},
|
|
||||||
2: {'priority': 0x90}}},
|
|
||||||
dpid_lib.str_to_dpid('0000000000000002'):
|
|
||||||
{'bridge': {'priority': 0x9000}}}
|
|
||||||
self.stp.set_config(config)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def add_flow(self, datapath, in_port, dst, actions):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
wildcards = ofproto_v1_0.OFPFW_ALL
|
|
||||||
wildcards &= ~ofproto_v1_0.OFPFW_IN_PORT
|
|
||||||
wildcards &= ~ofproto_v1_0.OFPFW_DL_DST
|
|
||||||
|
|
||||||
match = datapath.ofproto_parser.OFPMatch(
|
|
||||||
wildcards, in_port, 0, dst,
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
||||||
|
|
||||||
mod = datapath.ofproto_parser.OFPFlowMod(
|
|
||||||
datapath=datapath, match=match, cookie=0,
|
|
||||||
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
|
|
||||||
priority=ofproto.OFP_DEFAULT_PRIORITY,
|
|
||||||
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
def delete_flow(self, datapath):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
wildcards = ofproto_v1_0.OFPFW_ALL
|
|
||||||
match = datapath.ofproto_parser.OFPMatch(
|
|
||||||
wildcards, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
|
||||||
|
|
||||||
mod = datapath.ofproto_parser.OFPFlowMod(
|
|
||||||
datapath=datapath, match=match, cookie=0,
|
|
||||||
command=ofproto.OFPFC_DELETE)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(stplib.EventPacketIn, MAIN_DISPATCHER)
|
|
||||||
def packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
|
|
||||||
dst, src, _eth_type = struct.unpack_from('!6s6sH', buffer(msg.data), 0)
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.debug("packet in %s %s %s %s",
|
|
||||||
dpid, haddr_to_str(src), haddr_to_str(dst),
|
|
||||||
msg.in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = msg.in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
self.add_flow(datapath, msg.in_port, dst, actions)
|
|
||||||
|
|
||||||
out = datapath.ofproto_parser.OFPPacketOut(
|
|
||||||
datapath=datapath, buffer_id=msg.buffer_id, in_port=msg.in_port,
|
|
||||||
actions=actions)
|
|
||||||
datapath.send_msg(out)
|
|
||||||
|
|
||||||
@set_ev_cls(stplib.EventTopologyChange, MAIN_DISPATCHER)
|
|
||||||
def _topology_change_handler(self, ev):
|
|
||||||
dp = ev.dp
|
|
||||||
dpid_str = dpid_lib.dpid_to_str(dp.id)
|
|
||||||
msg = 'Receive topology change event. Flush MAC table.'
|
|
||||||
self.logger.debug("[dpid=%s] %s", dpid_str, msg)
|
|
||||||
|
|
||||||
if dp.id in self.mac_to_port:
|
|
||||||
del self.mac_to_port[dp.id]
|
|
||||||
self.delete_flow(dp)
|
|
||||||
|
|
||||||
@set_ev_cls(stplib.EventPortStateChange, MAIN_DISPATCHER)
|
|
||||||
def _port_state_change_handler(self, ev):
|
|
||||||
dpid_str = dpid_lib.dpid_to_str(ev.dp.id)
|
|
||||||
of_state = {stplib.PORT_STATE_DISABLE: 'DISABLE',
|
|
||||||
stplib.PORT_STATE_BLOCK: 'BLOCK',
|
|
||||||
stplib.PORT_STATE_LISTEN: 'LISTEN',
|
|
||||||
stplib.PORT_STATE_LEARN: 'LEARN',
|
|
||||||
stplib.PORT_STATE_FORWARD: 'FORWARD'}
|
|
||||||
self.logger.debug("[dpid=%s][port=%d] state=%s",
|
|
||||||
dpid_str, ev.port_no, of_state[ev.port_state])
|
|
@ -1,121 +0,0 @@
|
|||||||
# Copyright (C) 2016 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.
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.lib import dpid as dpid_lib
|
|
||||||
from os_ken.lib import stplib
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.lib.packet import ethernet
|
|
||||||
from os_ken.app import simple_switch_13
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitch13(simple_switch_13.SimpleSwitch13):
|
|
||||||
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
|
||||||
_CONTEXTS = {'stplib': stplib.Stp}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitch13, self).__init__(*args, **kwargs)
|
|
||||||
self.mac_to_port = {}
|
|
||||||
self.stp = kwargs['stplib']
|
|
||||||
|
|
||||||
# Sample of stplib config.
|
|
||||||
# please refer to stplib.Stp.set_config() for details.
|
|
||||||
config = {dpid_lib.str_to_dpid('0000000000000001'):
|
|
||||||
{'bridge': {'priority': 0x8000}},
|
|
||||||
dpid_lib.str_to_dpid('0000000000000002'):
|
|
||||||
{'bridge': {'priority': 0x9000}},
|
|
||||||
dpid_lib.str_to_dpid('0000000000000003'):
|
|
||||||
{'bridge': {'priority': 0xa000}}}
|
|
||||||
self.stp.set_config(config)
|
|
||||||
|
|
||||||
def delete_flow(self, datapath):
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
|
|
||||||
for dst in self.mac_to_port[datapath.id].keys():
|
|
||||||
match = parser.OFPMatch(eth_dst=dst)
|
|
||||||
mod = parser.OFPFlowMod(
|
|
||||||
datapath, command=ofproto.OFPFC_DELETE,
|
|
||||||
out_port=ofproto.OFPP_ANY, out_group=ofproto.OFPG_ANY,
|
|
||||||
priority=1, match=match)
|
|
||||||
datapath.send_msg(mod)
|
|
||||||
|
|
||||||
@set_ev_cls(stplib.EventPacketIn, MAIN_DISPATCHER)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
msg = ev.msg
|
|
||||||
datapath = msg.datapath
|
|
||||||
ofproto = datapath.ofproto
|
|
||||||
parser = datapath.ofproto_parser
|
|
||||||
in_port = msg.match['in_port']
|
|
||||||
|
|
||||||
pkt = packet.Packet(msg.data)
|
|
||||||
eth = pkt.get_protocols(ethernet.ethernet)[0]
|
|
||||||
|
|
||||||
dst = eth.dst
|
|
||||||
src = eth.src
|
|
||||||
|
|
||||||
dpid = datapath.id
|
|
||||||
self.mac_to_port.setdefault(dpid, {})
|
|
||||||
|
|
||||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
|
||||||
|
|
||||||
# learn a mac address to avoid FLOOD next time.
|
|
||||||
self.mac_to_port[dpid][src] = in_port
|
|
||||||
|
|
||||||
if dst in self.mac_to_port[dpid]:
|
|
||||||
out_port = self.mac_to_port[dpid][dst]
|
|
||||||
else:
|
|
||||||
out_port = ofproto.OFPP_FLOOD
|
|
||||||
|
|
||||||
actions = [parser.OFPActionOutput(out_port)]
|
|
||||||
|
|
||||||
# install a flow to avoid packet_in next time
|
|
||||||
if out_port != ofproto.OFPP_FLOOD:
|
|
||||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
|
||||||
self.add_flow(datapath, 1, match, actions)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
|
||||||
data = msg.data
|
|
||||||
|
|
||||||
out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
|
|
||||||
in_port=in_port, actions=actions, data=data)
|
|
||||||
datapath.send_msg(out)
|
|
||||||
|
|
||||||
@set_ev_cls(stplib.EventTopologyChange, MAIN_DISPATCHER)
|
|
||||||
def _topology_change_handler(self, ev):
|
|
||||||
dp = ev.dp
|
|
||||||
dpid_str = dpid_lib.dpid_to_str(dp.id)
|
|
||||||
msg = 'Receive topology change event. Flush MAC table.'
|
|
||||||
self.logger.debug("[dpid=%s] %s", dpid_str, msg)
|
|
||||||
|
|
||||||
if dp.id in self.mac_to_port:
|
|
||||||
self.delete_flow(dp)
|
|
||||||
del self.mac_to_port[dp.id]
|
|
||||||
|
|
||||||
@set_ev_cls(stplib.EventPortStateChange, MAIN_DISPATCHER)
|
|
||||||
def _port_state_change_handler(self, ev):
|
|
||||||
dpid_str = dpid_lib.dpid_to_str(ev.dp.id)
|
|
||||||
of_state = {stplib.PORT_STATE_DISABLE: 'DISABLE',
|
|
||||||
stplib.PORT_STATE_BLOCK: 'BLOCK',
|
|
||||||
stplib.PORT_STATE_LISTEN: 'LISTEN',
|
|
||||||
stplib.PORT_STATE_LEARN: 'LEARN',
|
|
||||||
stplib.PORT_STATE_FORWARD: 'FORWARD'}
|
|
||||||
self.logger.debug("[dpid=%s][port=%d] state=%s",
|
|
||||||
dpid_str, ev.port_no, of_state[ev.port_state])
|
|
@ -1,99 +0,0 @@
|
|||||||
# Copyright (C) 2014 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Usage example
|
|
||||||
|
|
||||||
Run this application:
|
|
||||||
$ PYTHONPATH=. ./bin/os_ken run --verbose os_ken.app.simple_switch_websocket_13
|
|
||||||
|
|
||||||
Install and run websocket client(in other terminal):
|
|
||||||
$ pip install websocket-client
|
|
||||||
$ wsdump.py ws://127.0.0.1:8080/simpleswitch/ws
|
|
||||||
< "ethernet(dst='ff:ff:ff:ff:ff:ff',ethertype=2054,src='32:1a:51:fb:91:77'), a
|
|
||||||
rp(dst_ip='10.0.0.2',dst_mac='00:00:00:00:00:00',hlen=6,hwtype=1,opcode=1,plen
|
|
||||||
=4,proto=2048,src_ip='10.0.0.1',src_mac='32:1a:51:fb:91:77')"
|
|
||||||
< "ethernet(dst='32:1a:51:fb:91:77',ethertype=2054,src='26:8c:15:0c:de:49'), a
|
|
||||||
rp(dst_ip='10.0.0.1',dst_mac='32:1a:51:fb:91:77',hlen=6,hwtype=1,opcode=2,plen
|
|
||||||
=4,proto=2048,src_ip='10.0.0.2',src_mac='26:8c:15:0c:de:49')"
|
|
||||||
< "ethernet(dst='26:8c:15:0c:de:49',ethertype=2048,src='32:1a:51:fb:91:77'), i
|
|
||||||
pv4(csum=9895,dst='10.0.0.2',flags=2,header_length=5,identification=0,offset=0
|
|
||||||
,option=None,proto=1,src='10.0.0.1',tos=0,total_length=84,ttl=64,version=4), i
|
|
||||||
cmp(code=0,csum=43748,data=echo(data='`\\xb9uS\\x00\\x00\\x00\\x00\\x7f\\'\\x0
|
|
||||||
1\\x00\\x00\\x00\\x00\\x00\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\
|
|
||||||
x1a\\x1b\\x1c\\x1d\\x1e\\x1f !\"#$%&\\'()*+,-./01234567',id=14355,seq=1),type=
|
|
||||||
8)"
|
|
||||||
|
|
||||||
Get arp table:
|
|
||||||
> {"jsonrpc": "2.0", "id": 1, "method": "get_arp_table", "params" : {}}
|
|
||||||
< {"jsonrpc": "2.0", "id": 1, "result": {"1": {"32:1a:51:fb:91:77": 1, "26:8c:
|
|
||||||
15:0c:de:49": 2}}}
|
|
||||||
"""
|
|
||||||
|
|
||||||
from os_ken.app import simple_switch_13
|
|
||||||
from os_ken.app.wsgi import ControllerBase
|
|
||||||
from os_ken.app.wsgi import rpc_public
|
|
||||||
from os_ken.app.wsgi import websocket
|
|
||||||
from os_ken.app.wsgi import WebSocketRPCServer
|
|
||||||
from os_ken.app.wsgi import WSGIApplication
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
|
|
||||||
|
|
||||||
simple_switch_instance_name = 'simple_switch_api_app'
|
|
||||||
url = '/simpleswitch/ws'
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchWebSocket13(simple_switch_13.SimpleSwitch13):
|
|
||||||
_CONTEXTS = {
|
|
||||||
'wsgi': WSGIApplication,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(SimpleSwitchWebSocket13, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
wsgi.register(
|
|
||||||
SimpleSwitchWebSocketController,
|
|
||||||
data={simple_switch_instance_name: self},
|
|
||||||
)
|
|
||||||
self._ws_manager = wsgi.websocketmanager
|
|
||||||
|
|
||||||
@set_ev_cls(ofp_event.EventOFPPacketIn)
|
|
||||||
def _packet_in_handler(self, ev):
|
|
||||||
super(SimpleSwitchWebSocket13, self)._packet_in_handler(ev)
|
|
||||||
|
|
||||||
pkt = packet.Packet(ev.msg.data)
|
|
||||||
self._ws_manager.broadcast(str(pkt))
|
|
||||||
|
|
||||||
@rpc_public
|
|
||||||
def get_arp_table(self):
|
|
||||||
return self.mac_to_port
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleSwitchWebSocketController(ControllerBase):
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(SimpleSwitchWebSocketController, self).__init__(
|
|
||||||
req, link, data, **config)
|
|
||||||
self.simple_switch_app = data[simple_switch_instance_name]
|
|
||||||
|
|
||||||
@websocket('simpleswitch', url)
|
|
||||||
def _websocket_handler(self, ws):
|
|
||||||
simple_switch = self.simple_switch_app
|
|
||||||
simple_switch.logger.debug('WebSocket connected: %s', ws)
|
|
||||||
rpc_server = WebSocketRPCServer(ws, simple_switch)
|
|
||||||
rpc_server.serve_forever()
|
|
||||||
simple_switch.logger.debug('WebSocket disconnected: %s', ws)
|
|
@ -1,120 +0,0 @@
|
|||||||
# Copyright (C) 2014 Stratosphere Inc.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Usage example
|
|
||||||
|
|
||||||
1. Run this application:
|
|
||||||
$ osken-manager --verbose --observe-links os_ken.app.ws_topology
|
|
||||||
|
|
||||||
2. Connect to this application by WebSocket (use your favorite client):
|
|
||||||
$ wscat -c ws://localhost:8080/v1.0/topology/ws
|
|
||||||
|
|
||||||
3. Join switches (use your favorite method):
|
|
||||||
$ sudo mn --controller=remote --topo linear,2
|
|
||||||
|
|
||||||
4. Topology change is notified:
|
|
||||||
< {"params": [{"ports": [{"hw_addr": "56:c7:08:12:bb:36", "name": "s1-eth1", "port_no": "00000001", "dpid": "0000000000000001"}, {"hw_addr": "de:b9:49:24:74:3f", "name": "s1-eth2", "port_no": "00000002", "dpid": "0000000000000001"}], "dpid": "0000000000000001"}], "jsonrpc": "2.0", "method": "event_switch_enter", "id": 1}
|
|
||||||
> {"id": 1, "jsonrpc": "2.0", "result": ""}
|
|
||||||
|
|
||||||
< {"params": [{"ports": [{"hw_addr": "56:c7:08:12:bb:36", "name": "s1-eth1", "port_no": "00000001", "dpid": "0000000000000001"}, {"hw_addr": "de:b9:49:24:74:3f", "name": "s1-eth2", "port_no": "00000002", "dpid": "0000000000000001"}], "dpid": "0000000000000001"}], "jsonrpc": "2.0", "method": "event_switch_leave", "id": 2}
|
|
||||||
> {"id": 2, "jsonrpc": "2.0", "result": ""}
|
|
||||||
...
|
|
||||||
""" # noqa
|
|
||||||
|
|
||||||
from socket import error as SocketError
|
|
||||||
from tinyrpc.exc import InvalidReplyError
|
|
||||||
|
|
||||||
|
|
||||||
from os_ken.app.wsgi import (
|
|
||||||
ControllerBase,
|
|
||||||
WSGIApplication,
|
|
||||||
websocket,
|
|
||||||
WebSocketRPCClient
|
|
||||||
)
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.topology import event, switches
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketTopology(app_manager.OSKenApp):
|
|
||||||
_CONTEXTS = {
|
|
||||||
'wsgi': WSGIApplication,
|
|
||||||
'switches': switches.Switches,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(WebSocketTopology, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
self.rpc_clients = []
|
|
||||||
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
wsgi.register(WebSocketTopologyController, {'app': self})
|
|
||||||
|
|
||||||
@set_ev_cls(event.EventSwitchEnter)
|
|
||||||
def _event_switch_enter_handler(self, ev):
|
|
||||||
msg = ev.switch.to_dict()
|
|
||||||
self._rpc_broadcall('event_switch_enter', msg)
|
|
||||||
|
|
||||||
@set_ev_cls(event.EventSwitchLeave)
|
|
||||||
def _event_switch_leave_handler(self, ev):
|
|
||||||
msg = ev.switch.to_dict()
|
|
||||||
self._rpc_broadcall('event_switch_leave', msg)
|
|
||||||
|
|
||||||
@set_ev_cls(event.EventLinkAdd)
|
|
||||||
def _event_link_add_handler(self, ev):
|
|
||||||
msg = ev.link.to_dict()
|
|
||||||
self._rpc_broadcall('event_link_add', msg)
|
|
||||||
|
|
||||||
@set_ev_cls(event.EventLinkDelete)
|
|
||||||
def _event_link_delete_handler(self, ev):
|
|
||||||
msg = ev.link.to_dict()
|
|
||||||
self._rpc_broadcall('event_link_delete', msg)
|
|
||||||
|
|
||||||
@set_ev_cls(event.EventHostAdd)
|
|
||||||
def _event_host_add_handler(self, ev):
|
|
||||||
msg = ev.host.to_dict()
|
|
||||||
self._rpc_broadcall('event_host_add', msg)
|
|
||||||
|
|
||||||
def _rpc_broadcall(self, func_name, msg):
|
|
||||||
disconnected_clients = []
|
|
||||||
for rpc_client in self.rpc_clients:
|
|
||||||
# NOTE: Although broadcasting is desired,
|
|
||||||
# RPCClient#get_proxy(one_way=True) does not work well
|
|
||||||
rpc_server = rpc_client.get_proxy()
|
|
||||||
try:
|
|
||||||
getattr(rpc_server, func_name)(msg)
|
|
||||||
except SocketError:
|
|
||||||
self.logger.debug('WebSocket disconnected: %s', rpc_client.ws)
|
|
||||||
disconnected_clients.append(rpc_client)
|
|
||||||
except InvalidReplyError as e:
|
|
||||||
self.logger.error(e)
|
|
||||||
|
|
||||||
for client in disconnected_clients:
|
|
||||||
self.rpc_clients.remove(client)
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketTopologyController(ControllerBase):
|
|
||||||
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(WebSocketTopologyController, self).__init__(
|
|
||||||
req, link, data, **config)
|
|
||||||
self.app = data['app']
|
|
||||||
|
|
||||||
@websocket('topology', '/v1.0/topology/ws')
|
|
||||||
def _websocket_handler(self, ws):
|
|
||||||
rpc_client = WebSocketRPCClient(ws)
|
|
||||||
self.app.rpc_clients.append(rpc_client)
|
|
||||||
rpc_client.serve_forever()
|
|
@ -1,336 +0,0 @@
|
|||||||
# Copyright (C) 2012 Nippon Telegraph and Telephone Corporation.
|
|
||||||
# Copyright (C) 2012 Isaku Yamahata <yamahata at private email ne jp>
|
|
||||||
#
|
|
||||||
# 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 inspect
|
|
||||||
from types import MethodType
|
|
||||||
|
|
||||||
from routes import Mapper
|
|
||||||
from routes.util import URLGenerator
|
|
||||||
import six
|
|
||||||
from tinyrpc.server import RPCServer
|
|
||||||
from tinyrpc.dispatch import RPCDispatcher
|
|
||||||
from tinyrpc.dispatch import public as rpc_public
|
|
||||||
from tinyrpc.protocols.jsonrpc import JSONRPCProtocol
|
|
||||||
from tinyrpc.transports import ServerTransport, ClientTransport
|
|
||||||
from tinyrpc.client import RPCClient
|
|
||||||
import webob.dec
|
|
||||||
import webob.exc
|
|
||||||
from webob.request import Request as webob_Request
|
|
||||||
from webob.response import Response as webob_Response
|
|
||||||
|
|
||||||
from os_ken import cfg
|
|
||||||
from os_ken.lib import hub
|
|
||||||
|
|
||||||
DEFAULT_WSGI_HOST = '0.0.0.0'
|
|
||||||
DEFAULT_WSGI_PORT = 8080
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_cli_opts([
|
|
||||||
cfg.StrOpt(
|
|
||||||
'wsapi-host', default=DEFAULT_WSGI_HOST,
|
|
||||||
help='webapp listen host (default %s)' % DEFAULT_WSGI_HOST),
|
|
||||||
cfg.IntOpt(
|
|
||||||
'wsapi-port', default=DEFAULT_WSGI_PORT,
|
|
||||||
help='webapp listen port (default %s)' % DEFAULT_WSGI_PORT),
|
|
||||||
])
|
|
||||||
|
|
||||||
HEX_PATTERN = r'0x[0-9a-z]+'
|
|
||||||
DIGIT_PATTERN = r'[1-9][0-9]*'
|
|
||||||
|
|
||||||
|
|
||||||
def route(name, path, methods=None, requirements=None):
|
|
||||||
def _route(controller_method):
|
|
||||||
controller_method.routing_info = {
|
|
||||||
'name': name,
|
|
||||||
'path': path,
|
|
||||||
'methods': methods,
|
|
||||||
'requirements': requirements,
|
|
||||||
}
|
|
||||||
return controller_method
|
|
||||||
return _route
|
|
||||||
|
|
||||||
|
|
||||||
class Request(webob_Request):
|
|
||||||
"""
|
|
||||||
Wrapper class for webob.request.Request.
|
|
||||||
|
|
||||||
The behavior of this class is the same as webob.request.Request
|
|
||||||
except for setting "charset" to "UTF-8" automatically.
|
|
||||||
"""
|
|
||||||
DEFAULT_CHARSET = "UTF-8"
|
|
||||||
|
|
||||||
def __init__(self, environ, charset=DEFAULT_CHARSET, *args, **kwargs):
|
|
||||||
super(Request, self).__init__(
|
|
||||||
environ, charset=charset, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class Response(webob_Response):
|
|
||||||
"""
|
|
||||||
Wrapper class for webob.response.Response.
|
|
||||||
|
|
||||||
The behavior of this class is the same as webob.response.Response
|
|
||||||
except for setting "charset" to "UTF-8" automatically.
|
|
||||||
"""
|
|
||||||
DEFAULT_CHARSET = "UTF-8"
|
|
||||||
|
|
||||||
def __init__(self, charset=DEFAULT_CHARSET, *args, **kwargs):
|
|
||||||
super(Response, self).__init__(charset=charset, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketRegistrationWrapper(object):
|
|
||||||
|
|
||||||
def __init__(self, func, controller):
|
|
||||||
self._controller = controller
|
|
||||||
self._controller_method = MethodType(func, controller)
|
|
||||||
|
|
||||||
def __call__(self, ws):
|
|
||||||
wsgi_application = self._controller.parent
|
|
||||||
ws_manager = wsgi_application.websocketmanager
|
|
||||||
ws_manager.add_connection(ws)
|
|
||||||
try:
|
|
||||||
self._controller_method(ws)
|
|
||||||
finally:
|
|
||||||
ws_manager.delete_connection(ws)
|
|
||||||
|
|
||||||
|
|
||||||
class _AlreadyHandledResponse(Response):
|
|
||||||
# XXX: Eventlet API should not be used directly.
|
|
||||||
from eventlet.wsgi import ALREADY_HANDLED
|
|
||||||
_ALREADY_HANDLED = ALREADY_HANDLED
|
|
||||||
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
return self._ALREADY_HANDLED
|
|
||||||
|
|
||||||
|
|
||||||
def websocket(name, path):
|
|
||||||
def _websocket(controller_func):
|
|
||||||
def __websocket(self, req, **_):
|
|
||||||
wrapper = WebSocketRegistrationWrapper(controller_func, self)
|
|
||||||
ws_wsgi = hub.WebSocketWSGI(wrapper)
|
|
||||||
ws_wsgi(req.environ, req.start_response)
|
|
||||||
# XXX: In order to prevent the writing to a already closed socket.
|
|
||||||
# This issue is caused by combined use:
|
|
||||||
# - webob.dec.wsgify()
|
|
||||||
# - eventlet.wsgi.HttpProtocol.handle_one_response()
|
|
||||||
return _AlreadyHandledResponse()
|
|
||||||
__websocket.routing_info = {
|
|
||||||
'name': name,
|
|
||||||
'path': path,
|
|
||||||
'methods': None,
|
|
||||||
'requirements': None,
|
|
||||||
}
|
|
||||||
return __websocket
|
|
||||||
return _websocket
|
|
||||||
|
|
||||||
|
|
||||||
class ControllerBase(object):
|
|
||||||
special_vars = ['action', 'controller']
|
|
||||||
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
self.req = req
|
|
||||||
self.link = link
|
|
||||||
self.data = data
|
|
||||||
self.parent = None
|
|
||||||
for name, value in config.items():
|
|
||||||
setattr(self, name, value)
|
|
||||||
|
|
||||||
def __call__(self, req):
|
|
||||||
action = self.req.urlvars.get('action', 'index')
|
|
||||||
if hasattr(self, '__before__'):
|
|
||||||
self.__before__()
|
|
||||||
|
|
||||||
kwargs = self.req.urlvars.copy()
|
|
||||||
for attr in self.special_vars:
|
|
||||||
if attr in kwargs:
|
|
||||||
del kwargs[attr]
|
|
||||||
|
|
||||||
return getattr(self, action)(req, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketDisconnectedError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketServerTransport(ServerTransport):
|
|
||||||
def __init__(self, ws):
|
|
||||||
self.ws = ws
|
|
||||||
|
|
||||||
def receive_message(self):
|
|
||||||
message = self.ws.wait()
|
|
||||||
if message is None:
|
|
||||||
raise WebSocketDisconnectedError()
|
|
||||||
context = None
|
|
||||||
return context, message
|
|
||||||
|
|
||||||
def send_reply(self, context, reply):
|
|
||||||
self.ws.send(six.text_type(reply))
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketRPCServer(RPCServer):
|
|
||||||
def __init__(self, ws, rpc_callback):
|
|
||||||
dispatcher = RPCDispatcher()
|
|
||||||
dispatcher.register_instance(rpc_callback)
|
|
||||||
super(WebSocketRPCServer, self).__init__(
|
|
||||||
WebSocketServerTransport(ws),
|
|
||||||
JSONRPCProtocol(),
|
|
||||||
dispatcher,
|
|
||||||
)
|
|
||||||
|
|
||||||
def serve_forever(self):
|
|
||||||
try:
|
|
||||||
super(WebSocketRPCServer, self).serve_forever()
|
|
||||||
except WebSocketDisconnectedError:
|
|
||||||
return
|
|
||||||
|
|
||||||
def _spawn(self, func, *args, **kwargs):
|
|
||||||
hub.spawn(func, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketClientTransport(ClientTransport):
|
|
||||||
|
|
||||||
def __init__(self, ws, queue):
|
|
||||||
self.ws = ws
|
|
||||||
self.queue = queue
|
|
||||||
|
|
||||||
def send_message(self, message, expect_reply=True):
|
|
||||||
self.ws.send(six.text_type(message))
|
|
||||||
|
|
||||||
if expect_reply:
|
|
||||||
return self.queue.get()
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketRPCClient(RPCClient):
|
|
||||||
|
|
||||||
def __init__(self, ws):
|
|
||||||
self.ws = ws
|
|
||||||
self.queue = hub.Queue()
|
|
||||||
super(WebSocketRPCClient, self).__init__(
|
|
||||||
JSONRPCProtocol(),
|
|
||||||
WebSocketClientTransport(ws, self.queue),
|
|
||||||
)
|
|
||||||
|
|
||||||
def serve_forever(self):
|
|
||||||
while True:
|
|
||||||
msg = self.ws.wait()
|
|
||||||
if msg is None:
|
|
||||||
break
|
|
||||||
self.queue.put(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class wsgify_hack(webob.dec.wsgify):
|
|
||||||
def __call__(self, environ, start_response):
|
|
||||||
self.kwargs['start_response'] = start_response
|
|
||||||
return super(wsgify_hack, self).__call__(environ, start_response)
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketManager(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._connections = []
|
|
||||||
|
|
||||||
def add_connection(self, ws):
|
|
||||||
self._connections.append(ws)
|
|
||||||
|
|
||||||
def delete_connection(self, ws):
|
|
||||||
self._connections.remove(ws)
|
|
||||||
|
|
||||||
def broadcast(self, msg):
|
|
||||||
for connection in self._connections:
|
|
||||||
connection.send(msg)
|
|
||||||
|
|
||||||
|
|
||||||
class WSGIApplication(object):
|
|
||||||
def __init__(self, **config):
|
|
||||||
self.config = config
|
|
||||||
self.mapper = Mapper()
|
|
||||||
self.registory = {}
|
|
||||||
self._wsmanager = WebSocketManager()
|
|
||||||
super(WSGIApplication, self).__init__()
|
|
||||||
|
|
||||||
def _match(self, req):
|
|
||||||
# Note: Invoke the new API, first. If the arguments unmatched,
|
|
||||||
# invoke the old API.
|
|
||||||
try:
|
|
||||||
return self.mapper.match(environ=req.environ)
|
|
||||||
except TypeError:
|
|
||||||
self.mapper.environ = req.environ
|
|
||||||
return self.mapper.match(req.path_info)
|
|
||||||
|
|
||||||
@wsgify_hack
|
|
||||||
def __call__(self, req, start_response):
|
|
||||||
match = self._match(req)
|
|
||||||
|
|
||||||
if not match:
|
|
||||||
return webob.exc.HTTPNotFound()
|
|
||||||
|
|
||||||
req.start_response = start_response
|
|
||||||
req.urlvars = match
|
|
||||||
link = URLGenerator(self.mapper, req.environ)
|
|
||||||
|
|
||||||
data = None
|
|
||||||
name = match['controller'].__name__
|
|
||||||
if name in self.registory:
|
|
||||||
data = self.registory[name]
|
|
||||||
|
|
||||||
controller = match['controller'](req, link, data, **self.config)
|
|
||||||
controller.parent = self
|
|
||||||
return controller(req)
|
|
||||||
|
|
||||||
def register(self, controller, data=None):
|
|
||||||
def _target_filter(attr):
|
|
||||||
if not inspect.ismethod(attr) and not inspect.isfunction(attr):
|
|
||||||
return False
|
|
||||||
if not hasattr(attr, 'routing_info'):
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
methods = inspect.getmembers(controller, _target_filter)
|
|
||||||
for method_name, method in methods:
|
|
||||||
routing_info = getattr(method, 'routing_info')
|
|
||||||
name = routing_info['name']
|
|
||||||
path = routing_info['path']
|
|
||||||
conditions = {}
|
|
||||||
if routing_info.get('methods'):
|
|
||||||
conditions['method'] = routing_info['methods']
|
|
||||||
requirements = routing_info.get('requirements') or {}
|
|
||||||
self.mapper.connect(name,
|
|
||||||
path,
|
|
||||||
controller=controller,
|
|
||||||
requirements=requirements,
|
|
||||||
action=method_name,
|
|
||||||
conditions=conditions)
|
|
||||||
if data:
|
|
||||||
self.registory[controller.__name__] = data
|
|
||||||
|
|
||||||
@property
|
|
||||||
def websocketmanager(self):
|
|
||||||
return self._wsmanager
|
|
||||||
|
|
||||||
|
|
||||||
class WSGIServer(hub.WSGIServer):
|
|
||||||
def __init__(self, application, **config):
|
|
||||||
super(WSGIServer, self).__init__((CONF.wsapi_host, CONF.wsapi_port),
|
|
||||||
application, **config)
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
self.serve_forever()
|
|
||||||
|
|
||||||
|
|
||||||
def start_service(app_mgr):
|
|
||||||
for instance in app_mgr.contexts.values():
|
|
||||||
if instance.__class__ == WSGIApplication:
|
|
||||||
return WSGIServer(instance)
|
|
||||||
|
|
||||||
return None
|
|
@ -32,7 +32,6 @@ import gc
|
|||||||
|
|
||||||
from os_ken import cfg
|
from os_ken import cfg
|
||||||
from os_ken import utils
|
from os_ken import utils
|
||||||
from os_ken.app import wsgi
|
|
||||||
from os_ken.controller.handler import register_instance, get_dependent_services
|
from os_ken.controller.handler import register_instance, get_dependent_services
|
||||||
from os_ken.controller.controller import Datapath
|
from os_ken.controller.controller import Datapath
|
||||||
from os_ken.controller import event
|
from os_ken.controller import event
|
||||||
@ -363,9 +362,6 @@ class AppManager(object):
|
|||||||
app_mgr.load_apps(app_lists)
|
app_mgr.load_apps(app_lists)
|
||||||
contexts = app_mgr.create_contexts()
|
contexts = app_mgr.create_contexts()
|
||||||
services = app_mgr.instantiate_apps(**contexts)
|
services = app_mgr.instantiate_apps(**contexts)
|
||||||
webapp = wsgi.start_service(app_mgr)
|
|
||||||
if webapp:
|
|
||||||
services.append(hub.spawn(webapp))
|
|
||||||
try:
|
try:
|
||||||
hub.joinall(services)
|
hub.joinall(services)
|
||||||
finally:
|
finally:
|
||||||
|
@ -30,7 +30,6 @@ log.early_init_log(logging.DEBUG)
|
|||||||
|
|
||||||
from os_ken import flags
|
from os_ken import flags
|
||||||
from os_ken import __version__ as version
|
from os_ken import __version__ as version
|
||||||
from os_ken.app import wsgi
|
|
||||||
from os_ken.base.app_manager import AppManager
|
from os_ken.base.app_manager import AppManager
|
||||||
from os_ken.controller import controller
|
from os_ken.controller import controller
|
||||||
from os_ken.topology import switches
|
from os_ken.topology import switches
|
||||||
@ -100,11 +99,6 @@ def main(args=None, prog=None):
|
|||||||
services = []
|
services = []
|
||||||
services.extend(app_mgr.instantiate_apps(**contexts))
|
services.extend(app_mgr.instantiate_apps(**contexts))
|
||||||
|
|
||||||
webapp = wsgi.start_service(app_mgr)
|
|
||||||
if webapp:
|
|
||||||
thr = hub.spawn(webapp)
|
|
||||||
services.append(thr)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hub.joinall(services)
|
hub.joinall(services)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
# Copyright (C) 2014 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.
|
|
||||||
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.lib import hub
|
|
||||||
from os_ken.app.wsgi import websocket, ControllerBase, WSGIApplication
|
|
||||||
from os_ken.app.wsgi import rpc_public, WebSocketRPCServer
|
|
||||||
from os_ken.services.protocols.bgp.api.base import call
|
|
||||||
from os_ken.services.protocols.bgp.api.base import PREFIX
|
|
||||||
from os_ken.services.protocols.bgp.rtconf.common import LOCAL_AS
|
|
||||||
from os_ken.services.protocols.bgp.rtconf.common import ROUTER_ID
|
|
||||||
from os_ken.services.protocols.bgp.rtconf import neighbors
|
|
||||||
|
|
||||||
bgp_instance_name = 'bgp_api_app'
|
|
||||||
url = '/bgp/ws'
|
|
||||||
|
|
||||||
|
|
||||||
class BgpWSJsonRpc(app_manager.OSKenApp):
|
|
||||||
_CONTEXTS = {
|
|
||||||
'wsgi': WSGIApplication,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(BgpWSJsonRpc, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
wsgi = kwargs['wsgi']
|
|
||||||
wsgi.register(
|
|
||||||
BgpWSJsonRpcController,
|
|
||||||
data={bgp_instance_name: self},
|
|
||||||
)
|
|
||||||
self._ws_manager = wsgi.websocketmanager
|
|
||||||
|
|
||||||
@rpc_public('core.start')
|
|
||||||
def _core_start(self, as_number=64512, router_id='10.0.0.1'):
|
|
||||||
common_settings = {}
|
|
||||||
common_settings[LOCAL_AS] = as_number
|
|
||||||
common_settings[ROUTER_ID] = str(router_id)
|
|
||||||
waiter = hub.Event()
|
|
||||||
call('core.start', waiter=waiter, **common_settings)
|
|
||||||
waiter.wait()
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@rpc_public('neighbor.create')
|
|
||||||
def _neighbor_create(self, ip_address='192.168.177.32',
|
|
||||||
remote_as=64513):
|
|
||||||
bgp_neighbor = {}
|
|
||||||
bgp_neighbor[neighbors.IP_ADDRESS] = str(ip_address)
|
|
||||||
bgp_neighbor[neighbors.REMOTE_AS] = remote_as
|
|
||||||
call('neighbor.create', **bgp_neighbor)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@rpc_public('network.add')
|
|
||||||
def _prefix_add(self, prefix='10.20.0.0/24'):
|
|
||||||
networks = {}
|
|
||||||
networks[PREFIX] = str(prefix)
|
|
||||||
call('network.add', **networks)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
@rpc_public('neighbors.get')
|
|
||||||
def _neighbors_get(self):
|
|
||||||
return call('neighbors.get')
|
|
||||||
|
|
||||||
@rpc_public('show.rib')
|
|
||||||
def _show_rib(self, family='ipv4'):
|
|
||||||
show = {}
|
|
||||||
show['params'] = ['rib', family]
|
|
||||||
return call('operator.show', **show)
|
|
||||||
|
|
||||||
|
|
||||||
class BgpWSJsonRpcController(ControllerBase):
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(BgpWSJsonRpcController, self).__init__(
|
|
||||||
req, link, data, **config)
|
|
||||||
self.bgp_api_app = data[bgp_instance_name]
|
|
||||||
|
|
||||||
@websocket('bgp', url)
|
|
||||||
def _websocket_handler(self, ws):
|
|
||||||
rpc_server = WebSocketRPCServer(ws, self.bgp_api_app)
|
|
||||||
rpc_server.serve_forever()
|
|
@ -1,101 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/switches"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/desc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/portdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/table/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"path": "/stats/flowentry/clear/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/portdesc/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1,
|
|
||||||
"port_no": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,150 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/switches"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/desc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/portdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queueconfig/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queueconfig/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupfeatures/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/table/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"path": "/stats/flowentry/clear/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/portdesc/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1,
|
|
||||||
"port_no": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/experimenter/1"
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,191 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/switches"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/desc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/portdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queueconfig/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queueconfig/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupfeatures/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meter/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meter/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterconfig/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterconfig/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterfeatures/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/table/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"path": "/stats/flowentry/clear/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/portdesc/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1,
|
|
||||||
"port_no": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/experimenter/1"
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,195 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/switches"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/desc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/portdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queuedesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queuedesc/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queuedesc/1/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupfeatures/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meter/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meter/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterconfig/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterconfig/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterfeatures/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/table/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"path": "/stats/flowentry/clear/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/portdesc/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1,
|
|
||||||
"port_no": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/experimenter/1"
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,203 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/switches"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/desc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/aggregateflow/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/port/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/portdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/portdesc/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queue/1/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queuedesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queuedesc/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/queuedesc/1/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/group/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupdesc/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/groupfeatures/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meter/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meter/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterdesc/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterdesc/1/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/meterfeatures/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"path": "/stats/table/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/modify_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/flowentry/delete_strict",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"path": "/stats/flowentry/clear/1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/groupentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/portdesc/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1,
|
|
||||||
"port_no": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/add",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/modify",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/meterentry/delete",
|
|
||||||
"body": {
|
|
||||||
"dpid": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"path": "/stats/experimenter/1"
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,136 +0,0 @@
|
|||||||
# Copyright (C) 2016 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.
|
|
||||||
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
from unittest import mock
|
|
||||||
|
|
||||||
from nose.tools import eq_
|
|
||||||
|
|
||||||
from os_ken.app import ofctl_rest
|
|
||||||
from os_ken.app.wsgi import Request
|
|
||||||
from os_ken.app.wsgi import WSGIApplication
|
|
||||||
from os_ken.controller.dpset import DPSet
|
|
||||||
from os_ken.ofproto import ofproto_protocol
|
|
||||||
from os_ken.ofproto import ofproto_v1_0
|
|
||||||
from os_ken.ofproto import ofproto_v1_2
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.ofproto import ofproto_v1_4
|
|
||||||
from os_ken.ofproto import ofproto_v1_5
|
|
||||||
from os_ken.tests import test_lib
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DummyDatapath(ofproto_protocol.ProtocolDesc):
|
|
||||||
|
|
||||||
def __init__(self, version):
|
|
||||||
super(DummyDatapath, self).__init__(version)
|
|
||||||
self.id = 1
|
|
||||||
_kw = {'port_no': 1, 'hw_addr': 'aa:bb:cc:dd:ee:ff',
|
|
||||||
'name': 's1-eth1', 'config': 1, 'state': 1}
|
|
||||||
# for OpenFlow 1.0
|
|
||||||
if version in [ofproto_v1_0.OFP_VERSION]:
|
|
||||||
_kw.update(
|
|
||||||
{'curr': 2112, 'advertised': 0, 'supported': 0, 'peer': 0})
|
|
||||||
port_info = self.ofproto_parser.OFPPhyPort(**_kw)
|
|
||||||
# for OpenFlow 1.2 or 1.3
|
|
||||||
elif version in [ofproto_v1_2.OFP_VERSION, ofproto_v1_3.OFP_VERSION]:
|
|
||||||
_kw.update(
|
|
||||||
{'curr': 2112, 'advertised': 0, 'supported': 0, 'peer': 0,
|
|
||||||
'curr_speed': 10000000, 'max_speed': 0})
|
|
||||||
port_info = self.ofproto_parser.OFPPort(**_kw)
|
|
||||||
# for OpenFlow 1.4+
|
|
||||||
else:
|
|
||||||
_kw.update({'properties': []})
|
|
||||||
port_info = self.ofproto_parser.OFPPort(**_kw)
|
|
||||||
self.ports = {1: port_info}
|
|
||||||
|
|
||||||
|
|
||||||
class Test_ofctl_rest(unittest.TestCase):
|
|
||||||
|
|
||||||
def _test(self, name, dp, method, path, body):
|
|
||||||
# print('processing %s ...' % name)
|
|
||||||
|
|
||||||
dpset = DPSet()
|
|
||||||
dpset._register(dp)
|
|
||||||
wsgi = WSGIApplication()
|
|
||||||
contexts = {
|
|
||||||
'dpset': dpset,
|
|
||||||
'wsgi': wsgi,
|
|
||||||
}
|
|
||||||
ofctl_rest.RestStatsApi(**contexts)
|
|
||||||
|
|
||||||
req = Request.blank(path)
|
|
||||||
req.body = json.dumps(body).encode('utf-8')
|
|
||||||
req.method = method
|
|
||||||
|
|
||||||
with mock.patch('os_ken.lib.ofctl_utils.send_stats_request'),\
|
|
||||||
mock.patch('os_ken.lib.ofctl_utils.send_msg'):
|
|
||||||
res = req.get_response(wsgi)
|
|
||||||
eq_(res.status, '200 OK')
|
|
||||||
|
|
||||||
|
|
||||||
def _add_tests():
|
|
||||||
_ofp_vers = {
|
|
||||||
'of10': ofproto_v1_0.OFP_VERSION,
|
|
||||||
'of12': ofproto_v1_2.OFP_VERSION,
|
|
||||||
'of13': ofproto_v1_3.OFP_VERSION,
|
|
||||||
'of14': ofproto_v1_4.OFP_VERSION,
|
|
||||||
'of15': ofproto_v1_5.OFP_VERSION,
|
|
||||||
}
|
|
||||||
|
|
||||||
this_dir = os.path.dirname(sys.modules[__name__].__file__)
|
|
||||||
ofctl_rest_json_dir = os.path.join(this_dir, 'ofctl_rest_json/')
|
|
||||||
|
|
||||||
for ofp_ver in _ofp_vers:
|
|
||||||
# read a json file
|
|
||||||
json_path = os.path.join(ofctl_rest_json_dir, ofp_ver + '.json')
|
|
||||||
if os.path.exists(json_path):
|
|
||||||
_test_cases = json.load(open(json_path))
|
|
||||||
else:
|
|
||||||
# print("Skip to load test cases for %s" % ofp_ver)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# add test
|
|
||||||
for test in _test_cases:
|
|
||||||
method = test['method']
|
|
||||||
path = test['path']
|
|
||||||
body = test.get('body', {})
|
|
||||||
|
|
||||||
name = 'test_ofctl_rest_' + method + '_' + ofp_ver + '_' + path
|
|
||||||
# print('adding %s ...' % name)
|
|
||||||
f = functools.partial(
|
|
||||||
Test_ofctl_rest._test,
|
|
||||||
name=name,
|
|
||||||
dp=DummyDatapath(_ofp_vers[ofp_ver]),
|
|
||||||
method=test['method'],
|
|
||||||
path=test['path'],
|
|
||||||
body=body
|
|
||||||
)
|
|
||||||
test_lib.add_method(Test_ofctl_rest, name, f)
|
|
||||||
|
|
||||||
|
|
||||||
_add_tests()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
@ -1,464 +0,0 @@
|
|||||||
# Copyright (C) 2014 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.
|
|
||||||
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
from nose.tools import *
|
|
||||||
|
|
||||||
import binascii
|
|
||||||
import inspect
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import math
|
|
||||||
import netaddr
|
|
||||||
import os
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from random import randint
|
|
||||||
|
|
||||||
from os_ken import cfg
|
|
||||||
|
|
||||||
# import all packet libraries.
|
|
||||||
PKT_LIB_PATH = 'os_ken.lib.packet'
|
|
||||||
for modname, moddef in sys.modules.items():
|
|
||||||
if not modname.startswith(PKT_LIB_PATH) or not moddef:
|
|
||||||
continue
|
|
||||||
for (clsname, clsdef, ) in inspect.getmembers(moddef):
|
|
||||||
if not inspect.isclass(clsdef):
|
|
||||||
continue
|
|
||||||
exec('from %s import %s' % (modname, clsname))
|
|
||||||
|
|
||||||
from os_ken.base import app_manager
|
|
||||||
from os_ken.controller import handler
|
|
||||||
from os_ken.controller import ofp_event
|
|
||||||
from os_ken.controller.handler import set_ev_cls
|
|
||||||
from os_ken.exception import OSKenException
|
|
||||||
from os_ken.lib import dpid as dpid_lib
|
|
||||||
from os_ken.lib import hub
|
|
||||||
from os_ken.lib import stringify
|
|
||||||
from os_ken.lib.packet import packet
|
|
||||||
from os_ken.ofproto import ofproto_protocol
|
|
||||||
from os_ken.ofproto import ofproto_v1_3
|
|
||||||
from os_ken.ofproto import ofproto_v1_3_parser
|
|
||||||
from os_ken.ofproto import ofproto_v1_4
|
|
||||||
|
|
||||||
from os_ken.tests.switch.tester import TestPatterns
|
|
||||||
from os_ken.tests.switch.tester import TestFile
|
|
||||||
from os_ken.tests.switch.tester import OfTester
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
|
|
||||||
LOG = logging.getLogger('test_tester')
|
|
||||||
|
|
||||||
SAMPLE_DESC = "action: 00_OUTPUT"
|
|
||||||
|
|
||||||
|
|
||||||
class Test_tester(unittest.TestCase):
|
|
||||||
|
|
||||||
""" Test case for tester
|
|
||||||
"""
|
|
||||||
|
|
||||||
# action/00_OUTPUT.json
|
|
||||||
|
|
||||||
test_json_1 = {
|
|
||||||
"description": "ethernet/ipv4/tcp-->'actions=output:2'",
|
|
||||||
"prerequisite": [
|
|
||||||
{
|
|
||||||
"OFPFlowMod": {
|
|
||||||
"table_id": 0,
|
|
||||||
"instructions": [
|
|
||||||
{
|
|
||||||
"OFPInstructionActions": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"OFPActionOutput": {
|
|
||||||
"port": "target_send_port_1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"ingress": [
|
|
||||||
"ethernet(dst='22:22:22:22:22:22', \
|
|
||||||
src='12:11:11:11:11:11', ethertype=2048)",
|
|
||||||
"ipv4(tos=32, proto=6, src='192.168.10.10', \
|
|
||||||
dst='192.168.20.20', ttl=64)",
|
|
||||||
"tcp(dst_port=2222, option=str('\\x00' * 4), \
|
|
||||||
src_port=11111)",
|
|
||||||
"'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x0\
|
|
||||||
8\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x1\
|
|
||||||
2\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1\
|
|
||||||
b\\x1c\\x1d\\x1e\\x1f'"
|
|
||||||
],
|
|
||||||
"egress":[
|
|
||||||
"ethernet(dst='22:22:22:22:22:22', \
|
|
||||||
src='12:11:11:11:11:11', ethertype=2048)",
|
|
||||||
"ipv4(tos=32, proto=6, src='192.168.10.10', \
|
|
||||||
dst='192.168.20.20', ttl=64)",
|
|
||||||
"tcp(dst_port=2222, option=str('\\x00' * 4), \
|
|
||||||
src_port=11111)",
|
|
||||||
"'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x0\
|
|
||||||
8\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x1\
|
|
||||||
2\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1\
|
|
||||||
b\\x1c\\x1d\\x1e\\x1f'"
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# group/00_ALL.json
|
|
||||||
|
|
||||||
test_json_2 = {
|
|
||||||
"description": "2Mbps(ethernet/ipv4/tcp)-->'in_port=1,\
|
|
||||||
actions=group:all(actions=output:2/actions=output:3)'",
|
|
||||||
"prerequisite": [
|
|
||||||
{
|
|
||||||
"OFPGroupMod": {
|
|
||||||
"group_id": 0,
|
|
||||||
"buckets": [
|
|
||||||
{
|
|
||||||
"OFPBucket": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"OFPActionOutput": {
|
|
||||||
"port": "target_send_port_1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"OFPBucket": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"OFPActionOutput": {
|
|
||||||
"port": "target_send_port_2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"OFPFlowMod": {
|
|
||||||
"match": {
|
|
||||||
"OFPMatch": {
|
|
||||||
"oxm_fields": [
|
|
||||||
{
|
|
||||||
"OXMTlv": {
|
|
||||||
"field": "in_port",
|
|
||||||
"value": "target_recv_port"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"instructions": [
|
|
||||||
{
|
|
||||||
"OFPInstructionActions": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"OFPActionGroup": {
|
|
||||||
"group_id": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"ingress": {
|
|
||||||
"packets": {
|
|
||||||
"data": [
|
|
||||||
"ethernet(dst='22:22:22:22:22:22', \
|
|
||||||
src='12:11:11:11:11:11', ethertype=2048)",
|
|
||||||
"ipv4(proto=6)",
|
|
||||||
"tcp()",
|
|
||||||
"str('\\x11' * (1500 - 54))"
|
|
||||||
],
|
|
||||||
"pktps":175,
|
|
||||||
"duration_time":30
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"egress":{
|
|
||||||
"throughput": [
|
|
||||||
{
|
|
||||||
"OFPMatch": {
|
|
||||||
"oxm_fields": [
|
|
||||||
{
|
|
||||||
"OXMTlv": {
|
|
||||||
"field": "in_port",
|
|
||||||
"value": "tester_recv_port_1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"kbps": 2000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"OFPMatch": {
|
|
||||||
"oxm_fields": [
|
|
||||||
{
|
|
||||||
"OXMTlv": {
|
|
||||||
"field": "in_port",
|
|
||||||
"value": "tester_recv_port_2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"kbps": 2000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# match/00_IN_PORT.json
|
|
||||||
|
|
||||||
test_json_3 = {
|
|
||||||
"description": "ethernet/ipv4/tcp-->'in_port=1,actions=output:2'",
|
|
||||||
"prerequisite": [
|
|
||||||
{
|
|
||||||
"OFPFlowMod": {
|
|
||||||
"table_id": 0,
|
|
||||||
"match": {
|
|
||||||
"OFPMatch": {
|
|
||||||
"oxm_fields": [
|
|
||||||
{
|
|
||||||
"OXMTlv": {
|
|
||||||
"field": "in_port",
|
|
||||||
"value": "target_recv_port"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"instructions": [
|
|
||||||
{
|
|
||||||
"OFPInstructionActions": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"OFPActionOutput": {
|
|
||||||
"port": "target_send_port_1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"ingress": [
|
|
||||||
"ethernet(dst='22:22:22:22:22:22', \
|
|
||||||
src='12:11:11:11:11:11', ethertype=2048)",
|
|
||||||
"ipv4(tos=32, proto=6, src='192.168.10.10', \
|
|
||||||
dst='192.168.20.20', ttl=64)",
|
|
||||||
"tcp(dst_port=2222, option=str('\\x00' * 4), \
|
|
||||||
src_port=11111)",
|
|
||||||
"'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x0\
|
|
||||||
8\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x1\
|
|
||||||
2\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1\
|
|
||||||
b\\x1c\\x1d\\x1e\\x1f'"
|
|
||||||
],
|
|
||||||
"egress":[
|
|
||||||
"ethernet(dst='22:22:22:22:22:22', \
|
|
||||||
src='12:11:11:11:11:11', ethertype=2048)",
|
|
||||||
"ipv4(tos=32, proto=6, src='192.168.10.10',\
|
|
||||||
dst='192.168.20.20', ttl=64)",
|
|
||||||
"tcp(dst_port=2222, option=str('\\x00' * 4), \
|
|
||||||
src_port=11111)",
|
|
||||||
"'\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x0\
|
|
||||||
8\\t\\n\\x0b\\x0c\\r\\x0e\\x0f\\x10\\x11\\x1\
|
|
||||||
2\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1\
|
|
||||||
b\\x1c\\x1d\\x1e\\x1f'"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# meter/01_DROP_00_KBPS_00_1M.json
|
|
||||||
|
|
||||||
test_json_4 = {
|
|
||||||
"description": "2Mbps(ethernet/ipv4/tcp)-->'in_port=1,\
|
|
||||||
actions=meter:1Mbps(drop),output:2'",
|
|
||||||
"prerequisite": [
|
|
||||||
{
|
|
||||||
"OFPMeterMod": {
|
|
||||||
"meter_id": 1,
|
|
||||||
"bands": [
|
|
||||||
{
|
|
||||||
"OFPMeterBandDrop": {
|
|
||||||
"rate": 1000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"OFPFlowMod": {
|
|
||||||
"match": {
|
|
||||||
"OFPMatch": {
|
|
||||||
"oxm_fields": [
|
|
||||||
{
|
|
||||||
"OXMTlv": {
|
|
||||||
"field": "in_port",
|
|
||||||
"value": "target_recv_port"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"instructions": [
|
|
||||||
{
|
|
||||||
"OFPInstructionMeter": {
|
|
||||||
"meter_id": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"OFPInstructionActions": {
|
|
||||||
"actions": [
|
|
||||||
{
|
|
||||||
"OFPActionOutput": {
|
|
||||||
"port": "target_send_port_1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tests": [
|
|
||||||
{
|
|
||||||
"ingress": {
|
|
||||||
"packets": {
|
|
||||||
"data": [
|
|
||||||
"ethernet(dst='22:22:22:22:22:22', \
|
|
||||||
src='12:11:11:11:11:11', ethertype=2048)",
|
|
||||||
"ipv4(proto=6)",
|
|
||||||
"tcp()",
|
|
||||||
"str('\\x11' * (1500 - 54))"
|
|
||||||
],
|
|
||||||
"pktps":175,
|
|
||||||
"duration_time":30
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"egress":{
|
|
||||||
"throughput": [
|
|
||||||
{
|
|
||||||
"OFPMatch": {
|
|
||||||
"oxm_fields": [
|
|
||||||
{
|
|
||||||
"OXMTlv": {
|
|
||||||
"field": "in_port",
|
|
||||||
"value": "tester_recv_port_1"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"kbps": 1000
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
OfTester.tester_ver = ofproto_v1_3.OFP_VERSION
|
|
||||||
OfTester.target_ver = ofproto_v1_3.OFP_VERSION
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test__normalize_test_json(self):
|
|
||||||
self.tests = TestPatterns(
|
|
||||||
"../switch/of13/action/00_OUTPUT.json",
|
|
||||||
logging.getLogger("test_tester"))
|
|
||||||
|
|
||||||
self.tests[SAMPLE_DESC]._normalize_test_json(Test_tester.test_json_1)
|
|
||||||
self.tests[SAMPLE_DESC]._normalize_test_json(Test_tester.test_json_2)
|
|
||||||
self.tests[SAMPLE_DESC]._normalize_test_json(Test_tester.test_json_3)
|
|
||||||
self.tests[SAMPLE_DESC]._normalize_test_json(Test_tester.test_json_4)
|
|
||||||
|
|
||||||
# action/00_OUTPUT.json
|
|
||||||
eq_(Test_tester.test_json_1["prerequisite"][0]["OFPFlowMod"][
|
|
||||||
"instructions"][0]["OFPInstructionActions"][
|
|
||||||
"actions"][0]["OFPActionOutput"]["port"],
|
|
||||||
CONF['test-switch']['target_send_port_1'])
|
|
||||||
|
|
||||||
# group/00_ALL.json
|
|
||||||
eq_(Test_tester.test_json_2["prerequisite"][1]["OFPFlowMod"][
|
|
||||||
"match"]["OFPMatch"]["oxm_fields"][0]["OXMTlv"]["value"],
|
|
||||||
CONF['test-switch']['target_recv_port'])
|
|
||||||
eq_(Test_tester.test_json_2["prerequisite"][0]["OFPGroupMod"][
|
|
||||||
"buckets"][0]["OFPBucket"]["actions"][0]["OFPActionOutput"][
|
|
||||||
"port"], CONF['test-switch']['target_send_port_1'])
|
|
||||||
eq_(Test_tester.test_json_2["prerequisite"][0]["OFPGroupMod"][
|
|
||||||
"buckets"][1]["OFPBucket"]["actions"][0]["OFPActionOutput"][
|
|
||||||
"port"], CONF['test-switch']['target_send_port_2'])
|
|
||||||
eq_(Test_tester.test_json_2["tests"][0]["egress"]["throughput"][
|
|
||||||
0]["OFPMatch"]["oxm_fields"][0]["OXMTlv"]["value"],
|
|
||||||
CONF['test-switch']['tester_recv_port_1'])
|
|
||||||
eq_(Test_tester.test_json_2["tests"][0]["egress"]["throughput"][
|
|
||||||
1]["OFPMatch"]["oxm_fields"][0]["OXMTlv"]["value"],
|
|
||||||
CONF['test-switch']['tester_recv_port_2'])
|
|
||||||
|
|
||||||
# match/00_IN_PORT.json
|
|
||||||
eq_(Test_tester.test_json_3["prerequisite"][0]["OFPFlowMod"][
|
|
||||||
"match"]["OFPMatch"]["oxm_fields"][0]["OXMTlv"]["value"],
|
|
||||||
CONF['test-switch']['target_recv_port'])
|
|
||||||
eq_(Test_tester.test_json_3["prerequisite"][0]["OFPFlowMod"][
|
|
||||||
"instructions"][0]["OFPInstructionActions"]["actions"][0][
|
|
||||||
"OFPActionOutput"]["port"], CONF['test-switch'][
|
|
||||||
'target_send_port_1'])
|
|
||||||
|
|
||||||
# meter/01_DROP_00_KBPS_00_1M.json
|
|
||||||
eq_(Test_tester.test_json_4["prerequisite"][1]["OFPFlowMod"][
|
|
||||||
"match"]["OFPMatch"]["oxm_fields"][0]["OXMTlv"]["value"],
|
|
||||||
CONF['test-switch']['target_recv_port'])
|
|
||||||
eq_(Test_tester.test_json_4["prerequisite"][1]["OFPFlowMod"][
|
|
||||||
"instructions"][1]["OFPInstructionActions"]["actions"][0][
|
|
||||||
"OFPActionOutput"]["port"],
|
|
||||||
CONF['test-switch']['target_send_port_1'])
|
|
||||||
eq_(Test_tester.test_json_4["tests"][0]["egress"]["throughput"][
|
|
||||||
0]["OFPMatch"]["oxm_fields"][0]["OXMTlv"]["value"],
|
|
||||||
CONF['test-switch']['tester_recv_port_1'])
|
|
@ -1,55 +0,0 @@
|
|||||||
# Copyright (C) 2013 Stratosphere Inc.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
from unittest import mock
|
|
||||||
from socket import error as SocketError
|
|
||||||
|
|
||||||
|
|
||||||
from os_ken.app.ws_topology import WebSocketTopology
|
|
||||||
|
|
||||||
|
|
||||||
class Test_ws_topology(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_when_sock_error(self):
|
|
||||||
args = {
|
|
||||||
'wsgi': mock.Mock(),
|
|
||||||
}
|
|
||||||
app = WebSocketTopology(**args)
|
|
||||||
|
|
||||||
rpc_client_mock1 = mock.Mock()
|
|
||||||
config = {
|
|
||||||
'get_proxy.return_value.event_link_add.side_effect': SocketError,
|
|
||||||
}
|
|
||||||
rpc_client_mock1.configure_mock(**config)
|
|
||||||
|
|
||||||
rpc_client_mock2 = mock.Mock()
|
|
||||||
|
|
||||||
app.rpc_clients = [
|
|
||||||
rpc_client_mock1,
|
|
||||||
rpc_client_mock2,
|
|
||||||
]
|
|
||||||
|
|
||||||
ev_mock = mock.Mock()
|
|
||||||
app._event_link_add_handler(ev_mock)
|
|
||||||
|
|
||||||
rpc_client_mock1.get_proxy.assert_called_once_with()
|
|
||||||
rpc_client_mock2.get_proxy.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
@ -1,104 +0,0 @@
|
|||||||
# Copyright (C) 2013 Stratosphere Inc.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import nose
|
|
||||||
from nose.tools import eq_
|
|
||||||
|
|
||||||
from os_ken.app.wsgi import ControllerBase
|
|
||||||
from os_ken.app.wsgi import WSGIApplication
|
|
||||||
from os_ken.app.wsgi import Response
|
|
||||||
from os_ken.app.wsgi import route
|
|
||||||
from os_ken.lib import dpid as dpidlib
|
|
||||||
|
|
||||||
LOG = logging.getLogger('test_wsgi')
|
|
||||||
|
|
||||||
|
|
||||||
class _TestController(ControllerBase):
|
|
||||||
|
|
||||||
def __init__(self, req, link, data, **config):
|
|
||||||
super(_TestController, self).__init__(req, link, data, **config)
|
|
||||||
eq_(data['test_param'], 'foo')
|
|
||||||
|
|
||||||
@route('test', '/test/{dpid}',
|
|
||||||
methods=['GET'], requirements={'dpid': dpidlib.DPID_PATTERN})
|
|
||||||
def test_get_dpid(self, req, dpid, **_kwargs):
|
|
||||||
return Response(status=200, body=dpid)
|
|
||||||
|
|
||||||
@route('test', '/test')
|
|
||||||
def test_root(self, req, **_kwargs):
|
|
||||||
return Response(status=200, body='root')
|
|
||||||
|
|
||||||
|
|
||||||
class Test_wsgi(unittest.TestCase):
|
|
||||||
|
|
||||||
""" Test case for wsgi
|
|
||||||
"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
controller_data = {
|
|
||||||
'test_param': 'foo'
|
|
||||||
}
|
|
||||||
self.wsgi_app = WSGIApplication()
|
|
||||||
self.wsgi_app.register(_TestController, controller_data)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_wsgi_decorator_ok(self):
|
|
||||||
r = self.wsgi_app({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/test/0123456789abcdef'},
|
|
||||||
lambda s, _: eq_(s, '200 OK'))
|
|
||||||
eq_(r[0], (b'0123456789abcdef'))
|
|
||||||
|
|
||||||
def test_wsgi_decorator_ng_path(self):
|
|
||||||
self.wsgi_app({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/'},
|
|
||||||
lambda s, _: eq_(s, '404 Not Found'))
|
|
||||||
|
|
||||||
def test_wsgi_decorator_ng_method(self):
|
|
||||||
# XXX: If response code is "405 Method Not Allowed", it is better.
|
|
||||||
self.wsgi_app({'REQUEST_METHOD': 'PUT',
|
|
||||||
'PATH_INFO': '/test/0123456789abcdef'},
|
|
||||||
lambda s, _: eq_(s, '404 Not Found'))
|
|
||||||
|
|
||||||
def test_wsgi_decorator_ng_requirements(self):
|
|
||||||
# XXX: If response code is "400 Bad Request", it is better.
|
|
||||||
self.wsgi_app({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/test/hogehoge'},
|
|
||||||
lambda s, _: eq_(s, '404 Not Found'))
|
|
||||||
|
|
||||||
def test_wsgi_decorator_ok_any_method(self):
|
|
||||||
self.wsgi_app({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/test'},
|
|
||||||
lambda s, _: eq_(s, '200 OK'))
|
|
||||||
self.wsgi_app({'REQUEST_METHOD': 'POST',
|
|
||||||
'PATH_INFO': '/test'},
|
|
||||||
lambda s, _: eq_(s, '200 OK'))
|
|
||||||
self.wsgi_app({'REQUEST_METHOD': 'PUT',
|
|
||||||
'PATH_INFO': '/test'},
|
|
||||||
lambda s, _: eq_(s, '200 OK'))
|
|
||||||
r = self.wsgi_app({'REQUEST_METHOD': 'DELETE',
|
|
||||||
'PATH_INFO': '/test'},
|
|
||||||
lambda s, _: eq_(s, '200 OK'))
|
|
||||||
eq_(r[0], b'root')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
nose.main(argv=['nosetests', '-s', '-v'], defaultTest=__file__)
|
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from nose.tools import ok_, eq_
|
from nose.tools import ok_, eq_
|
||||||
# from os_ken.app.simple_switch import SimpleSwitch
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from nose.tools import ok_, eq_
|
from nose.tools import ok_, eq_
|
||||||
# from os_ken.app.simple_switch import SimpleSwitch
|
|
||||||
|
|
||||||
|
|
||||||
class TestSample2(unittest.TestCase):
|
class TestSample2(unittest.TestCase):
|
||||||
|
@ -10,5 +10,4 @@ oslo.config>=5.1.0
|
|||||||
ovs>=2.8.0 # OVSDB
|
ovs>=2.8.0 # OVSDB
|
||||||
Routes>=2.3.1 # MIT
|
Routes>=2.3.1 # MIT
|
||||||
six>=1.10.0
|
six>=1.10.0
|
||||||
tinyrpc>=0.6 # RPC library, BGP speaker(net_cntl)
|
|
||||||
WebOb>=1.8.2 # wsgi
|
WebOb>=1.8.2 # wsgi
|
||||||
|
@ -8,5 +8,4 @@ oslo.config>=5.1.0
|
|||||||
ovs>=2.8.0 # OVSDB
|
ovs>=2.8.0 # OVSDB
|
||||||
Routes>=2.3.1 # wsgi
|
Routes>=2.3.1 # wsgi
|
||||||
six>=1.10.0
|
six>=1.10.0
|
||||||
tinyrpc # RPC library, BGP speaker(net_cntl)
|
|
||||||
WebOb>=1.8.2 # wsgi
|
WebOb>=1.8.2 # wsgi
|
||||||
|
Loading…
Reference in New Issue
Block a user