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
|
||||
|
||||
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.app.cbench
|
||||
-----------------
|
||||
.. automodule:: os_ken.app.cbench
|
||||
|
||||
os_ken.app.simple_switch
|
||||
------------------------
|
||||
.. automodule:: os_ken.app.simple_switch
|
||||
|
||||
os_ken.topology
|
||||
---------------
|
||||
.. automodule:: os_ken.topology
|
||||
|
@ -6,5 +6,4 @@ Configuration
|
||||
:maxdepth: 2
|
||||
|
||||
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...]
|
||||
|
||||
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::
|
||||
|
||||
--app-lists: application module name to run;
|
||||
repeat this option to specify a list of values
|
||||
--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::
|
||||
|
||||
--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),
|
||||
if any, specified via --config-file, hence over-ridden options in the
|
||||
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::
|
||||
|
||||
# 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
|
||||
testscenarios==0.4
|
||||
testtools==2.2.0
|
||||
tinyrpc==0.6
|
||||
traceback2==1.4.0
|
||||
unittest2==1.1.0
|
||||
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 utils
|
||||
from os_ken.app import wsgi
|
||||
from os_ken.controller.handler import register_instance, get_dependent_services
|
||||
from os_ken.controller.controller import Datapath
|
||||
from os_ken.controller import event
|
||||
@ -363,9 +362,6 @@ class AppManager(object):
|
||||
app_mgr.load_apps(app_lists)
|
||||
contexts = app_mgr.create_contexts()
|
||||
services = app_mgr.instantiate_apps(**contexts)
|
||||
webapp = wsgi.start_service(app_mgr)
|
||||
if webapp:
|
||||
services.append(hub.spawn(webapp))
|
||||
try:
|
||||
hub.joinall(services)
|
||||
finally:
|
||||
|
@ -30,7 +30,6 @@ log.early_init_log(logging.DEBUG)
|
||||
|
||||
from os_ken import flags
|
||||
from os_ken import __version__ as version
|
||||
from os_ken.app import wsgi
|
||||
from os_ken.base.app_manager import AppManager
|
||||
from os_ken.controller import controller
|
||||
from os_ken.topology import switches
|
||||
@ -100,11 +99,6 @@ def main(args=None, prog=None):
|
||||
services = []
|
||||
services.extend(app_mgr.instantiate_apps(**contexts))
|
||||
|
||||
webapp = wsgi.start_service(app_mgr)
|
||||
if webapp:
|
||||
thr = hub.spawn(webapp)
|
||||
services.append(thr)
|
||||
|
||||
try:
|
||||
hub.joinall(services)
|
||||
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
|
||||
from nose.tools import ok_, eq_
|
||||
# from os_ken.app.simple_switch import SimpleSwitch
|
||||
|
||||
import logging
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
import unittest
|
||||
from nose.tools import ok_, eq_
|
||||
# from os_ken.app.simple_switch import SimpleSwitch
|
||||
|
||||
|
||||
class TestSample2(unittest.TestCase):
|
||||
|
@ -10,5 +10,4 @@ oslo.config>=5.1.0
|
||||
ovs>=2.8.0 # OVSDB
|
||||
Routes>=2.3.1 # MIT
|
||||
six>=1.10.0
|
||||
tinyrpc>=0.6 # RPC library, BGP speaker(net_cntl)
|
||||
WebOb>=1.8.2 # wsgi
|
||||
|
@ -8,5 +8,4 @@ oslo.config>=5.1.0
|
||||
ovs>=2.8.0 # OVSDB
|
||||
Routes>=2.3.1 # wsgi
|
||||
six>=1.10.0
|
||||
tinyrpc # RPC library, BGP speaker(net_cntl)
|
||||
WebOb>=1.8.2 # wsgi
|
||||
|
Loading…
Reference in New Issue
Block a user