mirror of
https://github.com/faucetsdn/ryu.git
synced 2026-05-09 06:16:10 +02:00
Compare commits
No commits in common. "master" and "v3.25" have entirely different histories.
25
.github/workflows/tests-unit.yml
vendored
25
.github/workflows/tests-unit.yml
vendored
@ -1,25 +0,0 @@
|
||||
name: Unit tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.5, 3.6, 3.7, 3.8, 3.9]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade -r pip-requirements.txt
|
||||
pip install tox tox-gh-actions coveralls
|
||||
bash ryu/tests/integrated/common/install_docker_test_pkg_for_github_actions.sh
|
||||
- name: Test with tox
|
||||
run: NOSE_VERBOSE=0 tox
|
||||
@ -10,6 +10,6 @@
|
||||
# W0614: Unused import %s from wildcard import
|
||||
# R0801: Similar lines in %s files
|
||||
disable=C0111,W0511,W0142,E0602,C0103,E1101,R0903,W0614,R0801
|
||||
msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
|
||||
output-format=parseable
|
||||
reports=yes
|
||||
files-output=no
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
version: 2
|
||||
build:
|
||||
image: latest
|
||||
python:
|
||||
version: 3.6
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
sphinx:
|
||||
configuration: doc/source/conf.py
|
||||
formats: all
|
||||
@ -1,15 +0,0 @@
|
||||
{
|
||||
"separateMajorMinor": false,
|
||||
"schedule": [
|
||||
"after 10pm every weekday",
|
||||
"before 5am every weekday",
|
||||
"every weekend"
|
||||
],
|
||||
"timezone": "Pacific/Auckland",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":prHourlyLimit1",
|
||||
":preserveSemverRanges",
|
||||
"docker:enableMajor"
|
||||
]
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
---
|
||||
linters:
|
||||
flake8:
|
||||
python: 3
|
||||
max-line-length: 120
|
||||
pep8:
|
||||
python: 3
|
||||
max-line-length: 120
|
||||
py3k:
|
||||
17
.travis.yml
Normal file
17
.travis.yml
Normal file
@ -0,0 +1,17 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
|
||||
env:
|
||||
- TOX_ENV=py26
|
||||
- TOX_ENV=py27
|
||||
- TOX_ENV=py34
|
||||
- TOX_ENV=pep8
|
||||
|
||||
install:
|
||||
- "pip install tox"
|
||||
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
||||
|
||||
sudo: false
|
||||
@ -5,59 +5,46 @@ How to Get Your Change Into Ryu
|
||||
Submitting a change
|
||||
===================
|
||||
|
||||
To send patches to ryu, please make a
|
||||
`pull request <https://github.com/faucetsdn/ryu>`_ on GitHub.
|
||||
Send patches to ryu-devel@lists.sourceforge.net. Please don't use 'pull
|
||||
request' on github. We expect you to send a patch in Linux kernel
|
||||
development style. If you are not familiar with it, please read the
|
||||
following document:
|
||||
|
||||
Please check your changes with autopep8, pycodestyle(pep8) and running
|
||||
unit tests to make sure that they don't break the existing features.
|
||||
The following command does all for you.
|
||||
https://www.kernel.org/doc/Documentation/SubmittingPatches
|
||||
|
||||
.. code-block:: bash
|
||||
Please check your changes with pep8 and run unittests to make sure
|
||||
that they don't break the existing features. The following command
|
||||
does both for you:
|
||||
|
||||
# Install dependencies of tests
|
||||
$ pip install -r tools/test-requires
|
||||
fujita@rose:~/git/ryu$ ./run_tests.sh
|
||||
|
||||
# Execute autopep8
|
||||
# Also, it is convenient to add settings of your editor or IDE for
|
||||
# applying autopep8 automatically.
|
||||
$ autopep8 --recursive --in-place ryu/
|
||||
|
||||
# Execute unit tests and pycodestyle(pep8)
|
||||
$ ./run_tests.sh
|
||||
|
||||
Of course, you are encouraged to add unit tests when you add new
|
||||
Of course, you are encouraged to add unittests when you add new
|
||||
features (it's not a must though).
|
||||
|
||||
Python version and libraries
|
||||
============================
|
||||
* Python 3.5, 3.6, 3.7, 3.8, 3.9:
|
||||
* Python 2.6+
|
||||
As RHEL 6 adopted python 2.6, features only for 2.7+ should be avoided.
|
||||
|
||||
Ryu supports multiple Python versions. CI tests on GitHub Actions is running
|
||||
on these versions.
|
||||
|
||||
* standard library + widely used library:
|
||||
|
||||
Basically widely used == OpenStack adopted.
|
||||
As usual there are exceptions. Or python binding library for other
|
||||
* standard library + widely used library
|
||||
Basically widely used == OpenStack adopted
|
||||
As usual there are exceptions. gevents. Or python binding library for other
|
||||
component.
|
||||
|
||||
Coding style guide
|
||||
==================
|
||||
* pep8:
|
||||
|
||||
* pep8
|
||||
As python is used, PEP8 is would be hopefully mandatory for
|
||||
https://www.python.org/dev/peps/pep-0008/
|
||||
|
||||
* pylint:
|
||||
http://www.python.org/dev/peps/pep-0008/
|
||||
|
||||
* pylint
|
||||
Although pylint is useful for finding bugs, but pylint score not very
|
||||
important for now because we're still at early development stage.
|
||||
https://www.pylint.org/
|
||||
|
||||
* Google python style guide is very helpful:
|
||||
http://google.github.io/styleguide/pyguide.html
|
||||
* Google python style guide is very helpful
|
||||
http://google-styleguide.googlecode.com/svn/trunk/pyguide.html
|
||||
|
||||
* Guidelines derived from Guido's Recommendations:
|
||||
Guidelines derived from Guido's Recommendations
|
||||
|
||||
============================= ================= ========
|
||||
Type Public Internal
|
||||
@ -75,11 +62,10 @@ Coding style guide
|
||||
Local Variables lower_with_under
|
||||
============================= ================= ========
|
||||
|
||||
* OpenStack Nova style guide:
|
||||
* OpenStack Nova style guide
|
||||
https://github.com/openstack/nova/blob/master/HACKING.rst
|
||||
|
||||
* JSON files:
|
||||
|
||||
* JSON files
|
||||
Ryu source tree has JSON files under ryu/tests/unit/ofproto/json.
|
||||
They are used by unit tests. To make patches easier to read,
|
||||
they are normalized using tools/normalize_json.py. Please re-run
|
||||
|
||||
42
README.rst
42
README.rst
@ -1,13 +1,8 @@
|
||||
**PLEASE READ: RYU NOT CURRENTLY MAINTAINED**
|
||||
|
||||
* The Ryu project needs new maintainers - please file an issue if you are able to assist.
|
||||
* see OpenStack's os-ken (`<https://github.com/openstack/os-ken>`_) for a maintained Ryu alternative.
|
||||
|
||||
What's Ryu
|
||||
==========
|
||||
Ryu is a component-based software defined networking framework.
|
||||
|
||||
Ryu provides software components with well defined API's that make it
|
||||
Ryu provides software components with well defined API that make it
|
||||
easy for developers to create new network management and control
|
||||
applications. Ryu supports various protocols for managing network
|
||||
devices, such as OpenFlow, Netconf, OF-config, etc. About OpenFlow,
|
||||
@ -25,11 +20,14 @@ Installing Ryu is quite easy::
|
||||
|
||||
If you prefer to install Ryu from the source code::
|
||||
|
||||
% git clone https://github.com/faucetsdn/ryu.git
|
||||
% cd ryu; pip install .
|
||||
% git clone git://github.com/osrg/ryu.git
|
||||
% cd ryu; python ./setup.py install
|
||||
|
||||
If you want to use Ryu with `OpenStack <http://openstack.org/>`_,
|
||||
please refer `networking-ofagent project <https://github.com/stackforge/networking-ofagent>`_.
|
||||
|
||||
If you want to write your Ryu application, have a look at
|
||||
`Writing ryu application <http://ryu.readthedocs.io/en/latest/writing_ryu_app.html>`_ document.
|
||||
`Writing ryu application <http://ryu.readthedocs.org/en/latest/writing_ryu_app.html>`_ document.
|
||||
After writing your application, just type::
|
||||
|
||||
% ryu-manager yourapp.py
|
||||
@ -38,33 +36,21 @@ After writing your application, just type::
|
||||
Optional Requirements
|
||||
=====================
|
||||
|
||||
Some functions of ryu require extra packages:
|
||||
Some functionalities of ryu requires extra packages:
|
||||
|
||||
- OF-Config requires lxml and ncclient
|
||||
- OF-Config requires lxml
|
||||
- NETCONF requires paramiko
|
||||
- BGP speaker (SSH console) requires paramiko
|
||||
- Zebra protocol service (database) requires SQLAlchemy
|
||||
- BGP speaker (ssh console) requires paramiko
|
||||
|
||||
If you want to use these functions, please install the requirements::
|
||||
If you want to use the functionalities, please install requirements::
|
||||
|
||||
% pip install -r tools/optional-requires
|
||||
|
||||
Please refer to tools/optional-requires for details.
|
||||
|
||||
|
||||
Prerequisites
|
||||
=============
|
||||
If you got some error messages at the installation stage, please confirm
|
||||
dependencies for building the required Python packages.
|
||||
|
||||
On Ubuntu(16.04 LTS or later)::
|
||||
|
||||
% apt install gcc python-dev libffi-dev libssl-dev libxml2-dev libxslt1-dev zlib1g-dev
|
||||
% pip install lxml
|
||||
% pip install paramiko
|
||||
|
||||
|
||||
Support
|
||||
=======
|
||||
Ryu Official site is `<https://ryu-sdn.org/>`_.
|
||||
Ryu Official site is `<http://osrg.github.io/ryu/>`_.
|
||||
|
||||
If you have any
|
||||
questions, suggestions, and patches, the mailing list is available at
|
||||
|
||||
10
debian/control
vendored
10
debian/control
vendored
@ -6,7 +6,7 @@ Build-Depends: debhelper (>= 9.0.0), python-all (>= 2.6), python-sphinx
|
||||
Build-Depends-Indep:
|
||||
python-eventlet,
|
||||
python-lxml,
|
||||
python-msgpack (>= 0.4.0),
|
||||
python-msgpack (>= 0.3.0),
|
||||
python-netaddr,
|
||||
python-oslo.config (>= 1:1.2.0),
|
||||
python-paramiko,
|
||||
@ -17,9 +17,9 @@ Build-Depends-Indep:
|
||||
python-pip,
|
||||
python-pbr
|
||||
Standards-Version: 3.9.5
|
||||
Homepage: https://ryu-sdn.org
|
||||
Vcs-Git: git://github.com/faucetsdn/ryu.git
|
||||
Vcs-Browser: https://github.com/faucetsdn/ryu
|
||||
Homepage: http://osrg.github.io/ryu/
|
||||
Vcs-Git: git://github.com/osrg/ryu.git
|
||||
Vcs-Browser: http://github.com/osrg/ryu
|
||||
XS-Python-Version: >= 2.6
|
||||
|
||||
Package: python-ryu
|
||||
@ -28,7 +28,7 @@ Section: python
|
||||
Depends:
|
||||
python-eventlet,
|
||||
python-lxml,
|
||||
python-msgpack (>= 0.4.0),
|
||||
python-msgpack (>= 0.3.0),
|
||||
python-netaddr,
|
||||
python-oslo.config (>= 1:1.2.0),
|
||||
python-paramiko,
|
||||
|
||||
2
debian/copyright
vendored
2
debian/copyright
vendored
@ -1,6 +1,6 @@
|
||||
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: ryu
|
||||
Source: http://github.com/faucetsdn/ryu
|
||||
Source: http://github.com/osrg/ryu
|
||||
|
||||
Files: *
|
||||
Copyright: 2014 Ryu Project Team <ryu-devel@lists.sourceforge.net>
|
||||
|
||||
@ -11,5 +11,3 @@ Others provide some functionalities to other Ryu applications.
|
||||
|
||||
app/ofctl.rst
|
||||
app/ofctl_rest.rst
|
||||
app/rest_vtep.rst
|
||||
app/bgp_application.rst
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
**************************************
|
||||
ryu.services.protocols.bgp.application
|
||||
**************************************
|
||||
|
||||
.. automodule:: ryu.services.protocols.bgp.application
|
||||
:members:
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,12 +0,0 @@
|
||||
*****************
|
||||
ryu.app.rest_vtep
|
||||
*****************
|
||||
|
||||
.. automodule:: ryu.app.rest_vtep
|
||||
|
||||
REST API
|
||||
========
|
||||
|
||||
.. autoclass:: ryu.app.rest_vtep.RestVtepController
|
||||
:members:
|
||||
:member-order: bysource
|
||||
@ -74,14 +74,6 @@ ryu.ofproto.ofproto_v1_4_parser
|
||||
-------------------------------
|
||||
.. automodule:: ryu.ofproto.ofproto_v1_4_parser
|
||||
|
||||
ryu.ofproto.ofproto_v1_5
|
||||
------------------------
|
||||
.. automodule:: ryu.ofproto.ofproto_v1_5
|
||||
|
||||
ryu.ofproto.ofproto_v1_5_parser
|
||||
-------------------------------
|
||||
.. automodule:: ryu.ofproto.ofproto_v1_5_parser
|
||||
|
||||
|
||||
Ryu applications
|
||||
================
|
||||
@ -94,6 +86,42 @@ ryu.app.simple_switch
|
||||
---------------------
|
||||
.. automodule:: ryu.app.simple_switch
|
||||
|
||||
ryu.app.simple_isolation
|
||||
------------------------
|
||||
.. automodule:: ryu.app.simple_isolation
|
||||
|
||||
ryu.app.simple_vlan
|
||||
-------------------
|
||||
.. automodule:: ryu.app.simple_vlan
|
||||
|
||||
ryu.app.gre_tunnel
|
||||
------------------
|
||||
.. automodule:: ryu.app.gre_tunnel
|
||||
|
||||
ryu.app.tunnel_port_updater
|
||||
---------------------------
|
||||
.. automodule:: ryu.app.tunnel_port_updater
|
||||
|
||||
ryu.app.quantum_adapter
|
||||
-----------------------
|
||||
.. automodule:: ryu.app.quantum_adapter
|
||||
|
||||
ryu.app.rest
|
||||
------------
|
||||
.. automodule:: ryu.app.rest
|
||||
|
||||
ryu.app.rest_conf_switch
|
||||
------------------------
|
||||
.. automodule:: ryu.app.rest_conf_switch
|
||||
|
||||
ryu.app.rest_quantum
|
||||
--------------------
|
||||
.. automodule:: ryu.app.rest_quantum
|
||||
|
||||
ryu.app.rest_tunnel
|
||||
-------------------
|
||||
.. automodule:: ryu.app.rest_tunnel
|
||||
|
||||
ryu.topology
|
||||
------------
|
||||
.. automodule:: ryu.topology
|
||||
|
||||
@ -94,7 +94,7 @@ pygments_style = 'sphinx'
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme = 'haiku'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
@ -132,7 +132,6 @@ html_static_path = ['_static']
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
# (Deprecated since version 1.6)
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
@ -10,5 +10,4 @@ Writing Your Ryu Application
|
||||
ryu_app_api.rst
|
||||
library.rst
|
||||
ofproto_ref.rst
|
||||
nicira_ext_ref.rst
|
||||
api_ref.rst
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
[parsers]
|
||||
smart_quotes: false
|
||||
@ -16,6 +16,7 @@ Contents:
|
||||
developing.rst
|
||||
configuration.rst
|
||||
tests.rst
|
||||
using_with_openstack.rst
|
||||
snort_integrate.rst
|
||||
app.rst
|
||||
|
||||
|
||||
@ -9,10 +9,7 @@ Ryu provides some useful library for your network applications.
|
||||
|
||||
library_packet.rst
|
||||
library_packet_ref.rst
|
||||
library_pcap.rst
|
||||
library_of_config.rst
|
||||
library_bgp_speaker.rst
|
||||
library_bgp_speaker_ref.rst
|
||||
library_mrt.rst
|
||||
library_ovsdb_manager.rst
|
||||
library_ovsdb.rst
|
||||
|
||||
@ -6,8 +6,8 @@ Introduction
|
||||
============
|
||||
|
||||
Ryu BGP speaker library helps you to enable your code to speak BGP
|
||||
protocol. The library supports IPv4, IPv4 MPLS-labeled VPN, IPv6
|
||||
MPLS-labeled VPN and L2VPN EVPN address families.
|
||||
protocol. The library supports ipv4, ipv4 vpn, and ipv6 vpn address
|
||||
families.
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
****************
|
||||
MRT file library
|
||||
****************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Ryu MRT file library helps you to read/write MRT
|
||||
(Multi-Threaded Routing Toolkit) Routing Information Export Format
|
||||
[`RFC6396`_].
|
||||
|
||||
.. _RFC6396: https://tools.ietf.org/html/rfc6396
|
||||
|
||||
Reading MRT file
|
||||
================
|
||||
|
||||
For loading the routing information contained in MRT files, you can use
|
||||
mrtlib.Reader.
|
||||
|
||||
.. autoclass:: ryu.lib.mrtlib.Reader
|
||||
|
||||
Writing MRT file
|
||||
================
|
||||
|
||||
For dumping the routing information which your RyuApp generated, you can use
|
||||
mrtlib.Writer.
|
||||
|
||||
.. autoclass:: ryu.lib.mrtlib.Writer
|
||||
@ -1,76 +0,0 @@
|
||||
*************
|
||||
OVSDB library
|
||||
*************
|
||||
|
||||
Path: ``ryu.lib.ovs``
|
||||
|
||||
Similar to the :doc:`library_ovsdb_manager`, this library enables your
|
||||
application to speak the OVSDB protocol (RFC7047_), but differ from the
|
||||
:doc:`library_ovsdb_manager`, this library will initiate connections from
|
||||
controller side as ovs-vsctl_ command does.
|
||||
Please make sure that your devices are listening on either the Unix domain
|
||||
socket or TCP/SSL port before calling the APIs of this library.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Show current configuration
|
||||
$ ovs-vsctl get-manager
|
||||
|
||||
# Set TCP listen address
|
||||
$ ovs-vsctl set-manager "ptcp:6640"
|
||||
|
||||
See manpage of ovs-vsctl_ command for more details.
|
||||
|
||||
.. _RFC7047: https://tools.ietf.org/html/rfc7047
|
||||
.. _ovs-vsctl: http://openvswitch.org/support/dist-docs/ovs-vsctl.8.txt
|
||||
|
||||
Basic Usage
|
||||
===========
|
||||
|
||||
1. Instantiate :py:mod:`ryu.lib.ovs.vsctl.VSCtl`.
|
||||
|
||||
2. Construct commands with :py:mod:`ryu.lib.ovs.vsctl.VSCtlCommand`.
|
||||
The syntax is almost the same as ovs-vsctl_ command.
|
||||
|
||||
3. Execute commands via :py:mod:`ryu.lib.ovs.vsctl.VSCtl.run_command`.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ryu.lib.ovs import vsctl
|
||||
|
||||
OVSDB_ADDR = 'tcp:127.0.0.1:6640'
|
||||
ovs_vsctl = vsctl.VSCtl(OVSDB_ADDR)
|
||||
|
||||
# Equivalent to
|
||||
# $ ovs-vsctl show
|
||||
command = vsctl.VSCtlCommand('show')
|
||||
ovs_vsctl.run_command([command])
|
||||
print(command)
|
||||
# >>> VSCtlCommand(args=[],command='show',options=[],result='830d781f-c3c8-4b4f-837e-106e1b33d058\n ovs_version: "2.8.90"\n')
|
||||
|
||||
# Equivalent to
|
||||
# $ ovs-vsctl list Port s1-eth1
|
||||
command = vsctl.VSCtlCommand('list', ('Port', 's1-eth1'))
|
||||
ovs_vsctl.run_command([command])
|
||||
print(command)
|
||||
# >>> VSCtlCommand(args=('Port', 's1-eth1'),command='list',options=[],result=[<ovs.db.idl.Row object at 0x7f525fb682e8>])
|
||||
print(command.result[0].name)
|
||||
# >>> s1-eth1
|
||||
|
||||
API Reference
|
||||
=============
|
||||
|
||||
ryu.lib.ovs.vsctl
|
||||
-----------------
|
||||
|
||||
.. automodule:: ryu.lib.ovs.vsctl
|
||||
:members:
|
||||
|
||||
ryu.lib.ovs.bridge
|
||||
------------------
|
||||
|
||||
.. automodule:: ryu.lib.ovs.bridge
|
||||
:members:
|
||||
@ -2,8 +2,6 @@
|
||||
OVSDB Manager library
|
||||
*********************
|
||||
|
||||
Path: ``ryu.services.protocols.ovsdb``
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
@ -11,47 +9,17 @@ Ryu OVSDB Manager library allows your code to interact with devices
|
||||
speaking the OVSDB protocol. This enables your code to perform remote
|
||||
management of the devices and react to topology changes on them.
|
||||
|
||||
Please note this library will spawn a server listening on the port 6640 (the
|
||||
IANA registered for OVSDB protocol), but does not initiate connections from
|
||||
controller side.
|
||||
Then, to make your devices connect to Ryu, you need to tell the controller IP
|
||||
address and port to your devices.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Show current configuration
|
||||
$ ovs-vsctl get-manager
|
||||
|
||||
# Set manager (controller) address
|
||||
$ ovs-vsctl set-manager "tcp:127.0.0.1:6640"
|
||||
|
||||
# If you want to specify IPv6 address, wrap ip with brackets
|
||||
$ ovs-vsctl set-manager "tcp:[::1]:6640"
|
||||
|
||||
Also this library identifies the devices by "system-id" which should be unique,
|
||||
persistent identifier among all devices connecting to a single controller.
|
||||
Please make sure "system-id" is configured before connecting.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Show current configuration
|
||||
$ ovs-vsctl get Open_vSwitch . external_ids:system-id
|
||||
|
||||
# Set system-id manually
|
||||
$ ovs-vsctl set Open_vSwitch . external_ids:system-id=<SYSTEM-ID>
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
The following logs all new OVSDB connections in "handle_new_ovsdb_connection"
|
||||
and also provides the API "create_port" for creating a port on a bridge.
|
||||
The following logs all new OVSDB connections and allows creating a port
|
||||
on a bridge.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import uuid
|
||||
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.services.protocols.ovsdb import api as ovsdb
|
||||
from ryu.services.protocols.ovsdb import event as ovsdb_event
|
||||
|
||||
@ -60,22 +28,16 @@ and also provides the API "create_port" for creating a port on a bridge.
|
||||
@set_ev_cls(ovsdb_event.EventNewOVSDBConnection)
|
||||
def handle_new_ovsdb_connection(self, ev):
|
||||
system_id = ev.system_id
|
||||
address = ev.client.address
|
||||
self.logger.info(
|
||||
'New OVSDB connection from system-id=%s, address=%s',
|
||||
system_id, address)
|
||||
self.logger.info('New OVSDB connection from system id %s',
|
||||
systemd_id)
|
||||
|
||||
# Example: If device has bridge "s1", add port "s1-eth99"
|
||||
if ovsdb.bridge_exists(self, system_id, "s1"):
|
||||
self.create_port(system_id, "s1", "s1-eth99")
|
||||
|
||||
def create_port(self, system_id, bridge_name, name):
|
||||
def create_port(self, systemd_id, bridge_name, name):
|
||||
new_iface_uuid = uuid.uuid4()
|
||||
new_port_uuid = uuid.uuid4()
|
||||
|
||||
bridge = ovsdb.row_by_name(self, system_id, bridge_name)
|
||||
|
||||
def _create_port(tables, insert):
|
||||
bridge = ovsdb.row_by_name(self, system_id, bridge_name)
|
||||
|
||||
iface = insert(tables['Interface'], new_iface_uuid)
|
||||
iface.name = name
|
||||
iface.type = 'internal'
|
||||
@ -84,9 +46,9 @@ and also provides the API "create_port" for creating a port on a bridge.
|
||||
port.name = name
|
||||
port.interfaces = [iface]
|
||||
|
||||
bridge.ports = bridge.ports + [port]
|
||||
brdige.ports = bridfe.ports + [port]
|
||||
|
||||
return new_port_uuid, new_iface_uuid
|
||||
return (new_port_uuid, new_iface_uuid)
|
||||
|
||||
req = ovsdb_event.EventModifyRequest(system_id, _create_port)
|
||||
rep = self.send_request(req)
|
||||
@ -96,4 +58,4 @@ and also provides the API "create_port" for creating a port on a bridge.
|
||||
name, bridge, rep.status)
|
||||
return None
|
||||
|
||||
return rep.insert_uuids[new_port_uuid]
|
||||
return reply.insert_uuid[new_port_uuid]
|
||||
|
||||
@ -14,15 +14,119 @@ Stream Parser class
|
||||
.. automodule:: ryu.lib.packet.stream_parser
|
||||
:members:
|
||||
|
||||
List of the sub-classes:
|
||||
|
||||
- :py:mod:`ryu.lib.packet.bgp.StreamParser`
|
||||
.. autoclass:: ryu.lib.packet.bgp.StreamParser
|
||||
:members:
|
||||
|
||||
Protocol Header classes
|
||||
=======================
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
.. automodule:: ryu.lib.packet.packet_base
|
||||
:members:
|
||||
|
||||
library_packet_ref/packet_base
|
||||
library_packet_ref/*
|
||||
.. automodule:: ryu.lib.packet.ethernet
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.vlan
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.pbb
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.mpls
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.arp
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.ipv4
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.icmp
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.ipv6
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.icmpv6
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.cfm
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.tcp
|
||||
:members:
|
||||
|
||||
.. automodule:: ryu.lib.packet.udp
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.dhcp.dhcp
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.dhcp.options
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.dhcp.option
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.vrrp.vrrp
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.vrrp.vrrpv2
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.vrrp.vrrpv3
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.slow.slow
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.slow.lacp
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.llc.llc
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.llc.ControlFormatI
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.llc.ControlFormatS
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.llc.ControlFormatU
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.bpdu.bpdu
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bpdu.ConfigurationBPDUs
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bpdu.TopologyChangeNotificationBPDUs
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bpdu.RstBPDUs
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.igmp.igmp
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.igmp.igmpv3_query
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.igmp.igmpv3_report
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.igmp.igmpv3_report_group
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.bgp.BGPMessage
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bgp.BGPOpen
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bgp.BGPUpdate
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bgp.BGPKeepAlive
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bgp.BGPNotification
|
||||
:members:
|
||||
.. automodule:: ryu.lib.packet.sctp
|
||||
:members:
|
||||
|
||||
.. autoclass:: ryu.lib.packet.bfd.bfd
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bfd.SimplePassword
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bfd.KeyedMD5
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bfd.MeticulousKeyedMD5
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bfd.KeyedSHA1
|
||||
:members:
|
||||
.. autoclass:: ryu.lib.packet.bfd.MeticulousKeyedSHA1
|
||||
:members:
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
ARP
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.arp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
*****************
|
||||
Packet Base Class
|
||||
*****************
|
||||
|
||||
.. automodule:: ryu.lib.packet.packet_base
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
BFD
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.bfd
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
BGP
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.bgp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
BMP
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.bmp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
BPDU
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.bpdu
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
CFM
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.cfm
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
DHCP
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.dhcp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
*****
|
||||
DHCP6
|
||||
*****
|
||||
|
||||
.. automodule:: ryu.lib.packet.dhcp6
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
********
|
||||
Ethernet
|
||||
********
|
||||
|
||||
.. automodule:: ryu.lib.packet.ethernet
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
******
|
||||
Geneve
|
||||
******
|
||||
|
||||
.. automodule:: ryu.lib.packet.geneve
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
GRE
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.gre
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
ICMP
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.icmp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
******
|
||||
ICMPv6
|
||||
******
|
||||
|
||||
.. automodule:: ryu.lib.packet.icmpv6
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
IGMP
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.igmp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
IPv4
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.ipv4
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
IPv6
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.ipv6
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
LLC
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.llc
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
LLDP
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.lldp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
MPLS
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.mpls
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
********
|
||||
OpenFlow
|
||||
********
|
||||
|
||||
.. automodule:: ryu.lib.packet.openflow
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
OSPF
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.ospf
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
PBB
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.pbb
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
SCTP
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.sctp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
Slow
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.slow
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
TCP
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.tcp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
***
|
||||
UDP
|
||||
***
|
||||
|
||||
.. automodule:: ryu.lib.packet.udp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
VLAN
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.vlan
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
****
|
||||
VRRP
|
||||
****
|
||||
|
||||
.. automodule:: ryu.lib.packet.vrrp
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
*****
|
||||
VXLAN
|
||||
*****
|
||||
|
||||
.. automodule:: ryu.lib.packet.vxlan
|
||||
:members:
|
||||
@ -1,6 +0,0 @@
|
||||
*****
|
||||
Zebra
|
||||
*****
|
||||
|
||||
.. automodule:: ryu.lib.packet.zebra
|
||||
:members:
|
||||
@ -1,27 +0,0 @@
|
||||
*****************
|
||||
PCAP file library
|
||||
*****************
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Ryu PCAP file library helps you to read/write PCAP file which file
|
||||
format are described in `The Wireshark Wiki`_.
|
||||
|
||||
.. _The Wireshark Wiki: https://wiki.wireshark.org/Development/LibpcapFileFormat
|
||||
|
||||
Reading PCAP file
|
||||
=================
|
||||
|
||||
For loading the packet data containing in PCAP files, you can use
|
||||
pcaplib.Reader.
|
||||
|
||||
.. autoclass:: ryu.lib.pcaplib.Reader
|
||||
|
||||
Writing PCAP file
|
||||
=================
|
||||
|
||||
For dumping the packet data which your RyuApp received, you can use
|
||||
pcaplib.Writer.
|
||||
|
||||
.. autoclass:: ryu.lib.pcaplib.Writer
|
||||
@ -1,69 +0,0 @@
|
||||
***************************
|
||||
Nicira Extension Structures
|
||||
***************************
|
||||
|
||||
.. _nx_actions_structures:
|
||||
|
||||
Nicira Extension Actions Structures
|
||||
===================================
|
||||
|
||||
The followings shows the supported NXAction classes only in OpenFlow1.0
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. py:currentmodule:: ryu.ofproto.ofproto_v1_0_parser
|
||||
|
||||
.. autoclass:: NXActionSetQueue
|
||||
.. autoclass:: NXActionDecTtl
|
||||
.. autoclass:: NXActionPushMpls
|
||||
.. autoclass:: NXActionPopMpls
|
||||
.. autoclass:: NXActionSetMplsTtl
|
||||
.. autoclass:: NXActionDecMplsTtl
|
||||
.. autoclass:: NXActionSetMplsLabel
|
||||
.. autoclass:: NXActionSetMplsTc
|
||||
|
||||
The followings shows the supported NXAction classes in OpenFlow1.0 or later
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. py:currentmodule:: ryu.ofproto.ofproto_v1_3_parser
|
||||
|
||||
.. autoclass:: NXActionPopQueue
|
||||
.. autoclass:: NXActionRegLoad
|
||||
.. autoclass:: NXActionRegLoad2
|
||||
.. autoclass:: NXActionNote
|
||||
.. autoclass:: NXActionSetTunnel
|
||||
.. autoclass:: NXActionSetTunnel64
|
||||
.. autoclass:: NXActionRegMove
|
||||
.. autoclass:: NXActionResubmit
|
||||
.. autoclass:: NXActionResubmitTable
|
||||
.. autoclass:: NXActionOutputReg
|
||||
.. autoclass:: NXActionOutputReg2
|
||||
.. autoclass:: NXActionLearn
|
||||
.. autoclass:: NXActionExit
|
||||
.. autoclass:: NXActionController
|
||||
.. autoclass:: NXActionController2
|
||||
.. autoclass:: NXActionDecTtlCntIds
|
||||
.. autoclass:: NXActionStackPush
|
||||
.. autoclass:: NXActionStackPop
|
||||
.. autoclass:: NXActionSample
|
||||
.. autoclass:: NXActionSample2
|
||||
.. autoclass:: NXActionFinTimeout
|
||||
.. autoclass:: NXActionConjunction
|
||||
.. autoclass:: NXActionMultipath
|
||||
.. autoclass:: NXActionBundle
|
||||
.. autoclass:: NXActionBundleLoad
|
||||
.. autoclass:: NXActionCT
|
||||
.. autoclass:: NXActionNAT
|
||||
.. autoclass:: NXActionOutputTrunc
|
||||
.. autoclass:: NXActionDecNshTtl
|
||||
.. autoclass:: NXFlowSpecMatch
|
||||
.. autoclass:: NXFlowSpecLoad
|
||||
.. autoclass:: NXFlowSpecOutput
|
||||
.. autofunction:: ryu.ofproto.nicira_ext.ofs_nbits
|
||||
|
||||
.. _nx_match_structures:
|
||||
|
||||
Nicira Extended Match Structures
|
||||
================================
|
||||
|
||||
.. automodule:: ryu.ofproto.nicira_ext
|
||||
|
||||
@ -8,8 +8,6 @@ OpenFlow protocol API Reference
|
||||
:maxdepth: 3
|
||||
|
||||
ofproto_base.rst
|
||||
ofproto_v1_0_ref.rst
|
||||
ofproto_v1_2_ref.rst
|
||||
ofproto_v1_3_ref.rst
|
||||
ofproto_v1_4_ref.rst
|
||||
ofproto_v1_5_ref.rst
|
||||
|
||||
@ -1,294 +0,0 @@
|
||||
*************************************
|
||||
OpenFlow v1.0 Messages and Structures
|
||||
*************************************
|
||||
|
||||
.. py:currentmodule:: ryu.ofproto.ofproto_v1_0_parser
|
||||
|
||||
Controller-to-Switch Messages
|
||||
=============================
|
||||
|
||||
Handshake
|
||||
---------
|
||||
|
||||
.. autoclass:: OFPFeaturesRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-5-features_request.packet.json
|
||||
|
||||
.. autoclass:: OFPSwitchFeatures
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-6-ofp_switch_features.packet.json
|
||||
|
||||
Switch Configuration
|
||||
--------------------
|
||||
|
||||
.. autoclass:: OFPSetConfig
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-7-ofp_set_config.packet.json
|
||||
|
||||
.. autoclass:: OFPGetConfigRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-8-ofp_get_config_request.packet.json
|
||||
|
||||
.. autoclass:: OFPGetConfigReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-9-ofp_get_config_reply.packet.json
|
||||
|
||||
Modify State Messages
|
||||
---------------------
|
||||
|
||||
.. autoclass:: OFPFlowMod
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-2-ofp_flow_mod.packet.json
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-3-ofp_flow_mod.packet.json
|
||||
|
||||
.. autoclass:: OFPPortMod
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-22-ofp_port_mod.packet.json
|
||||
|
||||
Queue Configuration Messages
|
||||
----------------------------
|
||||
|
||||
.. autoclass:: OFPQueueGetConfigRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-35-ofp_queue_get_config_request.packet.json
|
||||
|
||||
.. autoclass:: OFPQueueGetConfigReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-36-ofp_queue_get_config_reply.packet.json
|
||||
|
||||
Read State Messages
|
||||
-------------------
|
||||
|
||||
.. autoclass:: OFPDescStatsRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-24-ofp_desc_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPDescStatsReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-0-ofp_desc_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowStatsRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-11-ofp_flow_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowStatsReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-12-ofp_flow_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPAggregateStatsRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-25-ofp_aggregate_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPAggregateStatsReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-26-ofp_aggregate_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPTableStatsRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-27-ofp_table_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPTableStatsReply
|
||||
|
||||
.. XXX commented out because it's too long
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-28-ofp_table_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPPortStatsRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-29-ofp_port_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPPortStatsReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-30-ofp_port_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPQueueStatsRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-37-ofp_queue_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPQueueStatsReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-38-ofp_queue_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPVendorStatsRequest
|
||||
.. autoclass:: OFPVendorStatsReply
|
||||
|
||||
Send Packet Message
|
||||
-------------------
|
||||
|
||||
.. autoclass:: OFPPacketOut
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-1-ofp_packet_out.packet.json
|
||||
|
||||
Barrier Message
|
||||
---------------
|
||||
|
||||
.. autoclass:: OFPBarrierRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-17-ofp_barrier_request.packet.json
|
||||
|
||||
.. autoclass:: OFPBarrierReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-18-ofp_barrier_reply.packet.json
|
||||
|
||||
|
||||
Asynchronous Messages
|
||||
=====================
|
||||
|
||||
Packet-In Message
|
||||
-----------------
|
||||
|
||||
.. autoclass:: OFPPacketIn
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-4-ofp_packet_in.packet.json
|
||||
|
||||
Flow Removed Message
|
||||
--------------------
|
||||
|
||||
.. autoclass:: OFPFlowRemoved
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-40-ofp_flow_removed.packet.json
|
||||
|
||||
Port Status Message
|
||||
-------------------
|
||||
|
||||
.. autoclass:: OFPPortStatus
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-39-ofp_port_status.packet.json
|
||||
|
||||
Error Message
|
||||
-------------
|
||||
|
||||
.. autoclass:: OFPErrorMsg
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-15-ofp_error_msg.packet.json
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-41-ofp_error_msg_vendor.packet.json
|
||||
|
||||
Symmetric Messages
|
||||
==================
|
||||
|
||||
Hello
|
||||
-----
|
||||
|
||||
.. autoclass:: OFPHello
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-10-ofp_hello.packet.json
|
||||
|
||||
Echo Request
|
||||
------------
|
||||
|
||||
.. autoclass:: OFPEchoRequest
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-13-ofp_echo_request.packet.json
|
||||
|
||||
|
||||
Echo Reply
|
||||
----------
|
||||
|
||||
.. autoclass:: OFPEchoReply
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-14-ofp_echo_reply.packet.json
|
||||
|
||||
|
||||
Vendor
|
||||
------------
|
||||
|
||||
.. autoclass:: OFPVendor
|
||||
|
||||
.. JSON Example:
|
||||
..
|
||||
.. .. literalinclude:: ../../ryu/tests/unit/ofproto/json/of10/1-16-ofp_vendor.packet.json
|
||||
|
||||
Port Structures
|
||||
===============
|
||||
|
||||
.. autoclass:: OFPPhyPort
|
||||
|
||||
|
||||
Flow Match Structure
|
||||
====================
|
||||
|
||||
.. autoclass:: OFPMatch
|
||||
|
||||
|
||||
Action Structures
|
||||
=================
|
||||
|
||||
.. autoclass:: OFPActionHeader
|
||||
.. autoclass:: OFPAction
|
||||
.. autoclass:: OFPActionOutput
|
||||
.. autoclass:: OFPActionVlanVid
|
||||
.. autoclass:: OFPActionVlanPcp
|
||||
.. autoclass:: OFPActionStripVlan
|
||||
.. autoclass:: OFPActionDlAddr
|
||||
.. autoclass:: OFPActionSetDlSrc
|
||||
.. autoclass:: OFPActionSetDlDst
|
||||
.. autoclass:: OFPActionNwAddr
|
||||
.. autoclass:: OFPActionSetNwSrc
|
||||
.. autoclass:: OFPActionSetNwDst
|
||||
.. autoclass:: OFPActionSetNwTos
|
||||
.. autoclass:: OFPActionTpPort
|
||||
.. autoclass:: OFPActionSetTpSrc
|
||||
.. autoclass:: OFPActionSetTpDst
|
||||
.. autoclass:: OFPActionEnqueue
|
||||
.. autoclass:: OFPActionVendor
|
||||
@ -312,11 +312,6 @@ Experimenter
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of12/3-16-ofp_experimenter.packet.json
|
||||
|
||||
Port Structures
|
||||
===============
|
||||
|
||||
.. autoclass:: OFPPort
|
||||
|
||||
|
||||
Flow Match Structure
|
||||
====================
|
||||
|
||||
@ -396,11 +396,6 @@ Experimenter
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of13/4-16-ofp_experimenter.packet.json
|
||||
|
||||
Port Structures
|
||||
===============
|
||||
|
||||
.. autoclass:: OFPPort
|
||||
|
||||
|
||||
Flow Match Structure
|
||||
====================
|
||||
|
||||
@ -355,7 +355,7 @@ Bundle Messages
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of14/5-70-ofp_bundle_add_msg.packet.json
|
||||
|
||||
|
||||
Set Asynchronous Configuration Message
|
||||
--------------------------------------
|
||||
|
||||
@ -435,8 +435,8 @@ Request Forward Message
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of14/5-71-ofp_requestforward.packet.json
|
||||
|
||||
|
||||
|
||||
|
||||
Symmetric Messages
|
||||
==================
|
||||
|
||||
@ -487,11 +487,6 @@ Experimenter
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of14/5-16-ofp_experimenter.packet.json
|
||||
|
||||
Port Structures
|
||||
===============
|
||||
|
||||
.. autoclass:: OFPPort
|
||||
|
||||
|
||||
Flow Match Structure
|
||||
====================
|
||||
|
||||
@ -1,577 +0,0 @@
|
||||
*************************************
|
||||
OpenFlow v1.5 Messages and Structures
|
||||
*************************************
|
||||
|
||||
.. py:currentmodule:: ryu.ofproto.ofproto_v1_5_parser
|
||||
|
||||
Controller-to-Switch Messages
|
||||
=============================
|
||||
|
||||
Handshake
|
||||
---------
|
||||
|
||||
.. autoclass:: OFPFeaturesRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-features_request.packet.json
|
||||
|
||||
.. autoclass:: OFPSwitchFeatures
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-features_reply.packet.json
|
||||
|
||||
Switch Configuration
|
||||
--------------------
|
||||
|
||||
.. autoclass:: OFPSetConfig
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-set_config.packet.json
|
||||
|
||||
.. autoclass:: OFPGetConfigRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-get_config_request.packet.json
|
||||
|
||||
.. autoclass:: OFPGetConfigReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-get_config_reply.packet.json
|
||||
|
||||
Modify State Messages
|
||||
---------------------
|
||||
|
||||
.. autoclass:: OFPTableMod
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_mod.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowMod
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_mod.packet.json
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_mod_conjunction.packet.json
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_mod_match_conj.packet.json
|
||||
|
||||
.. autoclass:: OFPGroupMod
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-group_mod.packet.json
|
||||
|
||||
.. autoclass:: OFPPortMod
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-port_mod.packet.json
|
||||
|
||||
.. autoclass:: OFPMeterMod
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-meter_mod.packet.json
|
||||
|
||||
Multipart Messages
|
||||
------------------
|
||||
|
||||
.. autoclass:: OFPDescStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-desc_request.packet.json
|
||||
|
||||
.. autoclass:: OFPDescStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-desc_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowDescStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_desc_request.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowDescStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_desc_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPAggregateStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-aggregate_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPAggregateStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-aggregate_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPPortStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-port_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPPortStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-port_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPPortDescStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-port_desc_request.packet.json
|
||||
|
||||
.. autoclass:: OFPPortDescStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-port_desc_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPQueueStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-queue_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPQueueStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-queue_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPQueueDescStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-queue_desc_request.packet.json
|
||||
|
||||
.. autoclass:: OFPQueueDescStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-queue_desc_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPGroupStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-group_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPGroupStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-group_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPGroupDescStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-group_desc_request.packet.json
|
||||
|
||||
.. autoclass:: OFPGroupDescStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-group_desc_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPGroupFeaturesStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-group_features_request.packet.json
|
||||
|
||||
.. autoclass:: OFPGroupFeaturesStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-group_features_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPMeterStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-meter_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPMeterStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-meter_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPMeterDescStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-meter_desc_request.packet.json
|
||||
|
||||
.. autoclass:: OFPMeterDescStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-meter_desc_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPMeterFeaturesStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-meter_features_request.packet.json
|
||||
|
||||
.. autoclass:: OFPMeterFeaturesStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-meter_features_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPControllerStatusStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-controller_status_request.packet.json
|
||||
|
||||
.. autoclass:: OFPControllerStatusStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-controller_status_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPTableStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_stats_request.packet.json
|
||||
|
||||
.. autoclass:: OFPTableStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_stats_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPTableDescStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_desc_request.packet.json
|
||||
|
||||
.. autoclass:: OFPTableDescStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_desc_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPTableFeaturesStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_features_request.packet.json
|
||||
|
||||
.. autoclass:: OFPTableFeaturesStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_features_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowMonitorRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_monitor_request.packet.json
|
||||
|
||||
.. autoclass:: OFPFlowMonitorReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_monitor_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPBundleFeaturesStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-bundle_features_request.packet.json
|
||||
|
||||
.. autoclass:: OFPBundleFeaturesStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-bundle_features_reply.packet.json
|
||||
|
||||
.. autoclass:: OFPExperimenterStatsRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-experimenter_request.packet.json
|
||||
|
||||
.. autoclass:: OFPExperimenterStatsReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-experimenter_reply.packet.json
|
||||
|
||||
Packet-Out Message
|
||||
------------------
|
||||
|
||||
.. autoclass:: OFPPacketOut
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-packet_out.packet.json
|
||||
|
||||
Barrier Message
|
||||
---------------
|
||||
|
||||
.. autoclass:: OFPBarrierRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-barrier_request.packet.json
|
||||
|
||||
.. autoclass:: OFPBarrierReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-barrier_reply.packet.json
|
||||
|
||||
Role Request Message
|
||||
--------------------
|
||||
|
||||
.. autoclass:: OFPRoleRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-role_request.packet.json
|
||||
|
||||
.. autoclass:: OFPRoleReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-role_reply.packet.json
|
||||
|
||||
Bundle Messages
|
||||
---------------
|
||||
|
||||
.. autoclass:: OFPBundleCtrlMsg
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-bundle_ctrl.packet.json
|
||||
|
||||
.. autoclass:: OFPBundleAddMsg
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-bundle_add.packet.json
|
||||
|
||||
Set Asynchronous Configuration Message
|
||||
--------------------------------------
|
||||
|
||||
.. autoclass:: OFPSetAsync
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-set_async.packet.json
|
||||
|
||||
.. autoclass:: OFPGetAsyncRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-get_async_request.packet.json
|
||||
|
||||
.. autoclass:: OFPGetAsyncReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-get_async_reply.packet.json
|
||||
|
||||
Asynchronous Messages
|
||||
=====================
|
||||
|
||||
Packet-In Message
|
||||
-----------------
|
||||
|
||||
.. autoclass:: OFPPacketIn
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-packet_in.packet.json
|
||||
|
||||
Flow Removed Message
|
||||
--------------------
|
||||
|
||||
.. autoclass:: OFPFlowRemoved
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-flow_removed.packet.json
|
||||
|
||||
Port Status Message
|
||||
-------------------
|
||||
|
||||
.. autoclass:: OFPPortStatus
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-port_status.packet.json
|
||||
|
||||
Controller Role Status Message
|
||||
------------------------------
|
||||
|
||||
.. autoclass:: OFPRoleStatus
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-role_status.packet.json
|
||||
|
||||
Table Status Message
|
||||
--------------------
|
||||
|
||||
.. autoclass:: OFPTableStatus
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-table_status.packet.json
|
||||
|
||||
Request Forward Message
|
||||
-----------------------
|
||||
|
||||
.. autoclass:: OFPRequestForward
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-requestforward.packet.json
|
||||
|
||||
Controller Status Message
|
||||
-------------------------
|
||||
|
||||
.. autoclass:: OFPControllerStatus
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-controller_status.packet.json
|
||||
|
||||
Symmetric Messages
|
||||
==================
|
||||
|
||||
Hello
|
||||
-----
|
||||
|
||||
.. autoclass:: OFPHello
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-hello.packet.json
|
||||
|
||||
.. autoclass:: OFPHelloElemVersionBitmap
|
||||
|
||||
Echo Request
|
||||
------------
|
||||
|
||||
.. autoclass:: OFPEchoRequest
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-echo_request.packet.json
|
||||
|
||||
Echo Reply
|
||||
----------
|
||||
|
||||
.. autoclass:: OFPEchoReply
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-echo_reply.packet.json
|
||||
|
||||
Error Message
|
||||
-------------
|
||||
|
||||
.. autoclass:: OFPErrorMsg
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-error_msg.packet.json
|
||||
|
||||
Experimenter
|
||||
------------
|
||||
|
||||
.. autoclass:: OFPExperimenter
|
||||
|
||||
JSON Example:
|
||||
|
||||
.. literalinclude:: ../../ryu/tests/unit/ofproto/json/of15/libofproto-OFP15-error_msg_experimenter.packet.json
|
||||
|
||||
Port Structures
|
||||
===============
|
||||
|
||||
.. autoclass:: OFPPort
|
||||
|
||||
Flow Match Structure
|
||||
====================
|
||||
|
||||
.. autoclass:: OFPMatch
|
||||
|
||||
Flow Stats Structures
|
||||
=====================
|
||||
|
||||
.. autoclass:: OFPStats
|
||||
|
||||
Flow Instruction Structures
|
||||
===========================
|
||||
|
||||
.. autoclass:: OFPInstructionGotoTable
|
||||
.. autoclass:: OFPInstructionWriteMetadata
|
||||
.. autoclass:: OFPInstructionActions
|
||||
.. autoclass:: OFPInstructionStatTrigger
|
||||
|
||||
Action Structures
|
||||
=================
|
||||
|
||||
.. autoclass:: OFPActionOutput
|
||||
.. autoclass:: OFPActionCopyTtlOut
|
||||
.. autoclass:: OFPActionCopyTtlIn
|
||||
.. autoclass:: OFPActionSetMplsTtl
|
||||
.. autoclass:: OFPActionDecMplsTtl
|
||||
.. autoclass:: OFPActionPushVlan
|
||||
.. autoclass:: OFPActionPopVlan
|
||||
.. autoclass:: OFPActionPushMpls
|
||||
.. autoclass:: OFPActionPopMpls
|
||||
.. autoclass:: OFPActionSetQueue
|
||||
.. autoclass:: OFPActionGroup
|
||||
.. autoclass:: OFPActionSetNwTtl
|
||||
.. autoclass:: OFPActionDecNwTtl
|
||||
.. autoclass:: OFPActionSetField
|
||||
.. autoclass:: OFPActionPushPbb
|
||||
.. autoclass:: OFPActionPopPbb
|
||||
.. autoclass:: OFPActionCopyField
|
||||
.. autoclass:: OFPActionMeter
|
||||
.. autoclass:: OFPActionExperimenter
|
||||
|
||||
Controller Status Structure
|
||||
===========================
|
||||
|
||||
.. autoclass:: OFPControllerStatusStats
|
||||
|
||||
@ -11,7 +11,7 @@ Threads, events, and event queues
|
||||
Ryu applications are single-threaded entities which implement
|
||||
various functionalities in Ryu. Events are messages between them.
|
||||
|
||||
Ryu applications send asynchronous events to each other.
|
||||
Ryu applications send asynchronous events each other.
|
||||
Besides that, there are some Ryu-internal event sources which
|
||||
are not Ryu applications. One of examples of such event sources
|
||||
is OpenFlow controller.
|
||||
@ -22,11 +22,11 @@ between Ryu applications.
|
||||
Each Ryu application has a receive queue for events.
|
||||
The queue is FIFO and preserves the order of events.
|
||||
Each Ryu application has a thread for event processing.
|
||||
The thread keeps draining the receive queue by dequeueing an event
|
||||
The thread keep draining the receive queue by dequeueing an event
|
||||
and calling the appropritate event handler for the event type.
|
||||
Because the event handler is called in the context of
|
||||
the event processing thread, it should be careful when blocking.
|
||||
While an event handler is blocked, no further events for
|
||||
the event processing thread, it should be careful for blocking.
|
||||
I.e. while an event handler is blocked, no further events for
|
||||
the Ryu application will be processed.
|
||||
|
||||
There are kinds of events which are used to implement synchronous
|
||||
@ -82,10 +82,20 @@ For example, EventOFPPacketIn for packet-in message.
|
||||
The OpenFlow controller part of Ryu automatically decodes OpenFlow messages
|
||||
received from switches and send these events to Ryu applications which
|
||||
expressed an interest using ryu.controller.handler.set_ev_cls.
|
||||
OpenFlow event classes are subclasses of the following class.
|
||||
OpenFlow event classes have at least the following attributes.
|
||||
|
||||
.. autoclass:: ryu.controller.ofp_event.EventOFPMsgBase
|
||||
.. tabularcolumns:: |l|L|
|
||||
|
||||
============ =============================================================
|
||||
Attribute Description
|
||||
============ =============================================================
|
||||
msg An object which describes the corresponding OpenFlow message.
|
||||
msg.datapath A ryu.controller.controller.Datapath instance which describes
|
||||
an OpenFlow switch from which we received this OpenFlow message.
|
||||
============ =============================================================
|
||||
|
||||
The msg object has some more additional members whose values are extracted
|
||||
from the original OpenFlow message.
|
||||
See :ref:`ofproto_ref` for more info about OpenFlow messages.
|
||||
|
||||
ryu.base.app_manager.RyuApp
|
||||
@ -93,87 +103,267 @@ ryu.base.app_manager.RyuApp
|
||||
|
||||
See :ref:`api_ref`.
|
||||
|
||||
ryu.controller.handler.set_ev_cls
|
||||
=================================
|
||||
ryu.controller.handler.set_ev_cls(ev_cls, dispatchers=None)
|
||||
===========================================================
|
||||
|
||||
.. autofunction:: ryu.controller.handler.set_ev_cls
|
||||
A decorator for Ryu application to declare an event handler.
|
||||
Decorated method will become an event handler.
|
||||
ev_cls is an event class whose instances this RyuApp wants to receive.
|
||||
dispatchers argument specifies one of the following negotiation phases
|
||||
(or a list of them) for which events should be generated for this handler.
|
||||
Note that, in case an event changes the phase, the phase before the change
|
||||
is used to check the interest.
|
||||
|
||||
.. tabularcolumns:: |l|L|
|
||||
|
||||
=========================================== ==================================
|
||||
Negotiation phase Description
|
||||
=========================================== ==================================
|
||||
ryu.controller.handler.HANDSHAKE_DISPATCHER Sending and waiting for hello
|
||||
message
|
||||
ryu.controller.handler.CONFIG_DISPATCHER Version negotiated and sent
|
||||
features-request message
|
||||
ryu.controller.handler.MAIN_DISPATCHER Switch-features message received
|
||||
and sent set-config message
|
||||
ryu.controller.handler.DEAD_DISPATCHER Disconnect from the peer. Or
|
||||
disconnecting due to some
|
||||
unrecoverable errors.
|
||||
=========================================== ==================================
|
||||
|
||||
ryu.controller.controller.Datapath
|
||||
==================================
|
||||
|
||||
.. autoclass:: ryu.controller.controller.Datapath
|
||||
A class to describe an OpenFlow switch connected to this controller.
|
||||
An instance has the following attributes.
|
||||
|
||||
.. tabularcolumns:: |l|L|
|
||||
|
||||
====================================== =======================================
|
||||
Attribute Description
|
||||
====================================== =======================================
|
||||
id 64-bit OpenFlow Datapath ID.
|
||||
Only available for
|
||||
ryu.controller.handler.MAIN_DISPATCHER
|
||||
phase.
|
||||
ofproto A module which exports OpenFlow
|
||||
definitions, mainly constants appeared
|
||||
in the specification, for the
|
||||
negotiated OpenFlow version. For
|
||||
example, ryu.ofproto.ofproto_v1_0 for
|
||||
OpenFlow 1.0.
|
||||
ofproto_parser A module which exports OpenFlow wire
|
||||
message encoder and decoder for the
|
||||
negotiated OpenFlow version. For
|
||||
example, ryu.ofproto.ofproto_v1_0_parser
|
||||
for OpenFlow 1.0.
|
||||
ofproto_parser.OFPxxxx(datapath, ....) A callable to prepare an OpenFlow
|
||||
message for the given switch. It can
|
||||
be sent with Datapath.send_msg later.
|
||||
xxxx is a name of the message. For
|
||||
example OFPFlowMod for flow-mod
|
||||
message. Arguemnts depend on the
|
||||
message.
|
||||
set_xid(self, msg) Generate an OpenFlow XID and put it
|
||||
in msg.xid.
|
||||
send_msg(self, msg) Queue an OpenFlow message to send to
|
||||
the corresponding switch. If msg.xid
|
||||
is None, set_xid is automatically
|
||||
called on the message before queueing.
|
||||
send_packet_out deprecated
|
||||
send_flow_mod deprecated
|
||||
send_flow_del deprecated
|
||||
send_delete_all_flows deprecated
|
||||
send_barrier Queue an OpenFlow barrier message to
|
||||
send to the switch.
|
||||
send_nxt_set_flow_format deprecated
|
||||
is_reserved_port deprecated
|
||||
====================================== =======================================
|
||||
|
||||
ryu.controller.event.EventBase
|
||||
==============================
|
||||
|
||||
.. autoclass:: ryu.controller.event.EventBase
|
||||
The base of all event classes.
|
||||
A Ryu application can define its own event type by creating a subclass.
|
||||
|
||||
ryu.controller.event.EventRequestBase
|
||||
=====================================
|
||||
|
||||
.. autoclass:: ryu.controller.event.EventRequestBase
|
||||
The base class for synchronous request for RyuApp.send_request.
|
||||
|
||||
ryu.controller.event.EventReplyBase
|
||||
===================================
|
||||
|
||||
.. autoclass:: ryu.controller.event.EventReplyBase
|
||||
The base class for synchronous request reply for RyuApp.send_reply.
|
||||
|
||||
ryu.controller.ofp_event.EventOFPStateChange
|
||||
============================================
|
||||
|
||||
.. autoclass:: ryu.controller.ofp_event.EventOFPStateChange
|
||||
An event class for negotiation phase change notification.
|
||||
An instance of this class is sent to observer after changing
|
||||
the negotiation phase.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
ryu.controller.ofp_event.EventOFPPortStateChange
|
||||
================================================
|
||||
|
||||
.. autoclass:: ryu.controller.ofp_event.EventOFPPortStateChange
|
||||
========= ====================================================================
|
||||
Attribute Description
|
||||
========= ====================================================================
|
||||
datapath ryu.controller.controller.Datapath instance of the switch
|
||||
========= ====================================================================
|
||||
|
||||
ryu.controller.dpset.EventDP
|
||||
============================
|
||||
|
||||
.. autoclass:: ryu.controller.dpset.EventDP
|
||||
An event class to notify connect/disconnect of a switch.
|
||||
For OpenFlow switches, one can get the same notification by observing
|
||||
ryu.controller.ofp_event.EventOFPStateChange.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
========= ====================================================================
|
||||
Attribute Description
|
||||
========= ====================================================================
|
||||
dp A ryu.controller.controller.Datapath instance of the switch
|
||||
enter True when the switch connected to our controller. False for
|
||||
disconnect.
|
||||
========= ====================================================================
|
||||
|
||||
ryu.controller.dpset.EventPortAdd
|
||||
=================================
|
||||
|
||||
.. autoclass:: ryu.controller.dpset.EventPortAdd
|
||||
An event class for switch port status notification.
|
||||
This event is generated when a new port is added to a switch.
|
||||
For OpenFlow switches, one can get the same notification by observing
|
||||
ryu.controller.ofp_event.EventOFPPortStatus.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
========= ====================================================================
|
||||
Attribute Description
|
||||
========= ====================================================================
|
||||
dp A ryu.controller.controller.Datapath instance of the switch
|
||||
port port number
|
||||
========= ====================================================================
|
||||
|
||||
ryu.controller.dpset.EventPortDelete
|
||||
====================================
|
||||
|
||||
.. autoclass:: ryu.controller.dpset.EventPortDelete
|
||||
An event class for switch port status notification.
|
||||
This event is generated when a port is removed from a switch.
|
||||
For OpenFlow switches, one can get the same notification by observing
|
||||
ryu.controller.ofp_event.EventOFPPortStatus.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
========= ====================================================================
|
||||
Attribute Description
|
||||
========= ====================================================================
|
||||
dp A ryu.controller.controller.Datapath instance of the switch
|
||||
port port number
|
||||
========= ====================================================================
|
||||
|
||||
ryu.controller.dpset.EventPortModify
|
||||
====================================
|
||||
|
||||
.. autoclass:: ryu.controller.dpset.EventPortModify
|
||||
An event class for switch port status notification.
|
||||
This event is generated when some attribute of a port is changed.
|
||||
For OpenFlow switches, one can get the same notification by observing
|
||||
ryu.controller.ofp_event.EventOFPPortStatus.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
========= ====================================================================
|
||||
Attribute Description
|
||||
========= ====================================================================
|
||||
dp A ryu.controller.controller.Datapath instance of the switch
|
||||
port port number
|
||||
========= ====================================================================
|
||||
|
||||
ryu.controller.network.EventNetworkPort
|
||||
=======================================
|
||||
|
||||
.. autoclass:: ryu.controller.network.EventNetworkPort
|
||||
An event class for notification of port arrival and deperture.
|
||||
This event is generated when a port is introduced to or removed from a network
|
||||
by the REST API.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
========== ===================================================================
|
||||
Attribute Description
|
||||
========== ===================================================================
|
||||
network_id Network ID
|
||||
dpid OpenFlow Datapath ID of the switch to which the port belongs.
|
||||
port_no OpenFlow port number of the port
|
||||
add_del True for adding a port. False for removing a port.
|
||||
========== ===================================================================
|
||||
|
||||
ryu.controller.network.EventNetworkDel
|
||||
======================================
|
||||
|
||||
.. autoclass:: ryu.controller.network.EventNetworkDel
|
||||
An event class for network deletion.
|
||||
This event is generated when a network is deleted by the REST API.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
========== ===================================================================
|
||||
Attribute Description
|
||||
========== ===================================================================
|
||||
network_id Network ID
|
||||
========== ===================================================================
|
||||
|
||||
ryu.controller.network.EventMacAddress
|
||||
======================================
|
||||
|
||||
.. autoclass:: ryu.controller.network.EventMacAddress
|
||||
An event class for end-point MAC address registration.
|
||||
This event is generated when a end-point MAC address is updated
|
||||
by the REST API.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
=========== ==================================================================
|
||||
Attribute Description
|
||||
=========== ==================================================================
|
||||
network_id Network ID
|
||||
dpid OpenFlow Datapath ID of the switch to which the port belongs.
|
||||
port_no OpenFlow port number of the port
|
||||
mac_address The old MAC address of the port if add_del is False. Otherwise
|
||||
the new MAC address.
|
||||
add_del False if this event is a result of a port removal. Otherwise
|
||||
True.
|
||||
=========== ==================================================================
|
||||
|
||||
ryu.controller.tunnels.EventTunnelKeyAdd
|
||||
========================================
|
||||
|
||||
.. autoclass:: ryu.controller.tunnels.EventTunnelKeyAdd
|
||||
An event class for tunnel key registration.
|
||||
This event is generated when a tunnel key is registered or updated
|
||||
by the REST API.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
=========== ==================================================================
|
||||
Attribute Description
|
||||
=========== ==================================================================
|
||||
network_id Network ID
|
||||
tunnel_key Tunnel Key
|
||||
=========== ==================================================================
|
||||
|
||||
ryu.controller.tunnels.EventTunnelKeyDel
|
||||
========================================
|
||||
|
||||
.. autoclass:: ryu.controller.tunnels.EventTunnelKeyDel
|
||||
An event class for tunnel key registration.
|
||||
This event is generated when a tunnel key is removed by the REST API.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
=========== ==================================================================
|
||||
Attribute Description
|
||||
=========== ==================================================================
|
||||
network_id Network ID
|
||||
tunnel_key Tunnel Key
|
||||
=========== ==================================================================
|
||||
|
||||
ryu.controller.tunnels.EventTunnelPort
|
||||
======================================
|
||||
|
||||
.. autoclass:: ryu.controller.tunnels.EventTunnelPort
|
||||
An event class for tunnel port registration.
|
||||
This event is generated when a tunnel port is added or removed by the REST API.
|
||||
An instance has at least the following attributes.
|
||||
|
||||
=========== ==================================================================
|
||||
Attribute Description
|
||||
=========== ==================================================================
|
||||
dpid OpenFlow Datapath ID
|
||||
port_no OpenFlow port number
|
||||
remote_dpid OpenFlow port number of the tunnel peer
|
||||
add_del True for adding a tunnel. False for removal.
|
||||
=========== ==================================================================
|
||||
|
||||
@ -19,7 +19,7 @@ The test procedure
|
||||
* run LINC switch
|
||||
* run Ryu test_of_config app
|
||||
|
||||
For getting/installing Ryu itself, please refer to https://ryu-sdn.org/
|
||||
For getting/installing Ryu itself, please refer to http://osrg.github.io/ryu/
|
||||
|
||||
|
||||
Install Erlang environment
|
||||
|
||||
@ -4,17 +4,10 @@
|
||||
Using Ryu Network Operating System with OpenStack as OpenFlow controller
|
||||
************************************************************************
|
||||
|
||||
.. CAUTION::
|
||||
|
||||
The Ryu plugin and OFAgent described in the following is deprecated,
|
||||
because Ryu is officially integrated into Open vSwitch agent with
|
||||
"of_interface = native" mode.
|
||||
|
||||
|
||||
Ryu cooperates with OpenStack using Quantum Ryu plugin. The plugin is
|
||||
available in the official Quantum releases.
|
||||
|
||||
For more information, please visit https://github.com/faucetsdn/ryu/wiki/OpenStack .
|
||||
For more information, please visit http://github.com/osrg/ryu/wiki/OpenStack .
|
||||
We described instructions of the installation / configuration of OpenStack
|
||||
with Ryu, and we provide pre-configured VM image to be able to easily try
|
||||
OpenStack with Ryu.
|
||||
|
||||
@ -5,21 +5,21 @@ The First Application
|
||||
Whetting Your Appetite
|
||||
======================
|
||||
|
||||
If you want to manage network gear (switches, routers, etc) your
|
||||
own way, you just need to write your own Ryu application. Your application
|
||||
tells Ryu how you want to manage the gear. Then Ryu configures the
|
||||
gear by using OpenFlow protocol, etc.
|
||||
If you want to manage the network gears (switches, routers, etc) at
|
||||
your way, you need to write your Ryu application. Your application
|
||||
tells Ryu how you want to manage the gears. Then Ryu configures the
|
||||
gears by using OpenFlow protocol, etc.
|
||||
|
||||
Writing Ryu applications is easy. They're just Python scripts.
|
||||
Writing Ryu application is easy. It's just Python scripts.
|
||||
|
||||
|
||||
Start Writing
|
||||
=============
|
||||
|
||||
Here we show a Ryu application that makes an OpenFlow switch work as a dumb
|
||||
We show a Ryu application that make OpenFlow switches work as a dumb
|
||||
layer 2 switch.
|
||||
|
||||
Open a text editor and create a new file with the following content:
|
||||
Open a text editor creating a new file with the following content:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -29,9 +29,9 @@ Open a text editor and create a new file with the following content:
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(L2Switch, self).__init__(*args, **kwargs)
|
||||
|
||||
Ryu applications are just Python scripts so you can save the file with
|
||||
any name, any extension, and any place you want. Let's name the file
|
||||
'l2.py' in your home directory.
|
||||
Ryu application is just a Python script so you can save the file with
|
||||
any name, extensions, and any place you want. Let's name the file
|
||||
'l2.py' at your home directory.
|
||||
|
||||
This application does nothing useful yet, however it's a complete Ryu
|
||||
application. In fact, you can run this Ryu application::
|
||||
@ -41,10 +41,10 @@ application. In fact, you can run this Ryu application::
|
||||
instantiating app /Users/fujita/l2.py
|
||||
|
||||
|
||||
All you have to do is define a new subclass of RyuApp to run
|
||||
All you have to do is defining needs a new subclass of RyuApp to run
|
||||
your Python script as a Ryu application.
|
||||
|
||||
Next let's add some functionality that sends a received packet to all
|
||||
Next let's add the functionality of sending a received packet to all
|
||||
the ports.
|
||||
|
||||
.. code-block:: python
|
||||
@ -53,11 +53,8 @@ the ports.
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import MAIN_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.ofproto import ofproto_v1_0
|
||||
|
||||
class L2Switch(app_manager.RyuApp):
|
||||
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(L2Switch, self).__init__(*args, **kwargs)
|
||||
|
||||
@ -67,31 +64,26 @@ the ports.
|
||||
dp = msg.datapath
|
||||
ofp = dp.ofproto
|
||||
ofp_parser = dp.ofproto_parser
|
||||
|
||||
|
||||
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
|
||||
|
||||
data = None
|
||||
if msg.buffer_id == ofp.OFP_NO_BUFFER:
|
||||
data = msg.data
|
||||
|
||||
out = ofp_parser.OFPPacketOut(
|
||||
datapath=dp, buffer_id=msg.buffer_id, in_port=msg.in_port,
|
||||
actions=actions, data = data)
|
||||
actions=actions)
|
||||
dp.send_msg(out)
|
||||
|
||||
|
||||
A new method 'packet_in_handler' is added to the L2Switch class. This is
|
||||
called when Ryu receives an OpenFlow packet_in message. The trick is the
|
||||
A new method 'packet_in_handler' is added to L2Switch class. This is
|
||||
called when Ryu receives an OpenFlow packet_in message. The trick is
|
||||
'set_ev_cls' decorator. This decorator tells Ryu when the decorated
|
||||
function should be called.
|
||||
|
||||
The first argument of the decorator indicates which type of event this
|
||||
function should be called for. As you might expect, every time Ryu gets a
|
||||
The first argument of the decorator indicates an event that makes
|
||||
function called. As you expect easily, every time Ryu gets a
|
||||
packet_in message, this function is called.
|
||||
|
||||
The second argument indicates the state of the switch. You probably
|
||||
The second argument indicates the state of the switch. Probably, you
|
||||
want to ignore packet_in messages before the negotiation between Ryu
|
||||
and the switch is finished. Using 'MAIN_DISPATCHER' as the second
|
||||
and the switch finishes. Using 'MAIN_DISPATCHER' as the second
|
||||
argument means this function is called only after the negotiation
|
||||
completes.
|
||||
|
||||
@ -108,24 +100,24 @@ Ready for the second half.
|
||||
|
||||
* OFPActionOutput class is used with a packet_out message to specify a
|
||||
switch port that you want to send the packet out of. This
|
||||
application uses the OFPP_FLOOD flag to indicate that the packet should
|
||||
be sent out on all ports.
|
||||
application need a switch to send out of all the ports so OFPP_FLOOD
|
||||
constant is used.
|
||||
|
||||
* OFPPacketOut class is used to build a packet_out message.
|
||||
|
||||
* If you call Datapath class's send_msg method with a OpenFlow message
|
||||
class object, Ryu builds and sends the on-wire data format to the switch.
|
||||
class object, Ryu builds and send the on-wire data format to the switch.
|
||||
|
||||
|
||||
There, you finished implementing your first Ryu application. You are ready to
|
||||
run a Ryu application that does something useful.
|
||||
Here, you finished implementing your first Ryu application. You are ready to
|
||||
run this Ryu application that does something useful.
|
||||
|
||||
|
||||
Is a dumb L2 switch is too dumb? You want to implement a learning L2
|
||||
A dumb l2 switch is too dumb? You want to implement a learning l2
|
||||
switch? Move to `the next step
|
||||
<https://github.com/faucetsdn/ryu/blob/master/ryu/app/simple_switch.py>`_. You
|
||||
<https://github.com/osrg/ryu/blob/master/ryu/app/simple_switch.py>`_. You
|
||||
can learn from the existing Ryu applications at `ryu/app
|
||||
<https://github.com/faucetsdn/ryu/blob/master/ryu/app/>`_ directory and
|
||||
<https://github.com/osrg/ryu/blob/master/ryu/app/>`_ directory and
|
||||
`integrated tests
|
||||
<https://github.com/faucetsdn/ryu/blob/master/ryu/tests/integrated/>`_
|
||||
<https://github.com/osrg/ryu/blob/master/ryu/tests/integrated/>`_
|
||||
directory.
|
||||
|
||||
@ -1 +0,0 @@
|
||||
pip==20.3.4
|
||||
56
run_tests.sh
56
run_tests.sh
@ -8,16 +8,16 @@ usage() {
|
||||
echo "Usage: $0 [OPTION]..."
|
||||
echo "Run Ryu's test suite(s)"
|
||||
echo ""
|
||||
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
||||
echo " -c, --coverage Generate coverage report"
|
||||
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
||||
echo " -p, --pycodestyle, --pep8 Just run pycodestyle(pep8)"
|
||||
echo " -P, --no-pycodestyle, --no-pep8 Don't run pycodestyle(pep8)"
|
||||
echo " -l, --pylint Just run pylint"
|
||||
echo " -i, --integrated Run integrated test"
|
||||
echo " -v, --verbose Run verbose pylint analysis"
|
||||
echo " -h, --help Print this usage message"
|
||||
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
|
||||
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
|
||||
echo " -c, --coverage Generate coverage report"
|
||||
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
|
||||
echo " -p, --pep8 Just run pep8"
|
||||
echo " -P, --no-pep8 Don't run pep8"
|
||||
echo " -l, --pylint Just run pylint"
|
||||
echo " -i, --integrated Run integrated test"
|
||||
echo " -v, --verbose Run verbose pylint analysis"
|
||||
echo " -h, --help Print this usage message"
|
||||
echo ""
|
||||
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
|
||||
echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
|
||||
@ -31,8 +31,8 @@ process_option() {
|
||||
-V|--virtual-env) always_venv=1; never_venv=0;;
|
||||
-N|--no-virtual-env) always_venv=0; never_venv=1;;
|
||||
-f|--force) force=1;;
|
||||
-p|--pycodestyle|--pep8) just_pycodestyle=1; never_venv=1; always_venv=0;;
|
||||
-P|--no-pycodestyle|--no-pep8) no_pycodestyle=1;;
|
||||
-p|--pep8) just_pep8=1; never_venv=1; always_venv=0;;
|
||||
-P|--no-pep8) no_pep8=1;;
|
||||
-l|--pylint) just_pylint=1;;
|
||||
-i|--integrated) integrated=1;;
|
||||
-c|--coverage) coverage=1;;
|
||||
@ -46,8 +46,8 @@ venv=.venv
|
||||
with_venv=tools/with_venv.sh
|
||||
always_venv=0
|
||||
never_venv=0
|
||||
just_pycodestyle=0
|
||||
no_pycodestyle=0
|
||||
just_pep8=0
|
||||
no_pep8=0
|
||||
just_pylint=0
|
||||
integrated=0
|
||||
force=0
|
||||
@ -103,26 +103,20 @@ run_pylint() {
|
||||
export PYTHONPATH=$OLD_PYTHONPATH
|
||||
}
|
||||
|
||||
run_pycodestyle() {
|
||||
PYCODESTYLE=$(which pycodestyle || which pep8)
|
||||
if [ -z "${PYCODESTYLE}" ]
|
||||
then
|
||||
echo "Please install pycodestyle or pep8"
|
||||
return 1
|
||||
fi
|
||||
echo "Running $(basename ${PYCODESTYLE}) ..."
|
||||
run_pep8() {
|
||||
echo "Running pep8 ..."
|
||||
|
||||
PYCODESTYLE_OPTIONS="--repeat --show-source"
|
||||
PYCODESTYLE_INCLUDE="ryu setup*.py"
|
||||
PYCODESTYLE_LOG=pycodestyle.log
|
||||
${wrapper} ${PYCODESTYLE} $PYCODESTYLE_OPTIONS $PYCODESTYLE_INCLUDE | tee $PYCODESTYLE_LOG
|
||||
PEP8_OPTIONS="--repeat --show-source"
|
||||
PEP8_INCLUDE="ryu setup*.py"
|
||||
PEP8_LOG=pep8.log
|
||||
${wrapper} pep8 $PEP8_OPTIONS $PEP8_INCLUDE | tee $PEP8_LOG
|
||||
}
|
||||
|
||||
run_integrated() {
|
||||
echo "Running integrated test ..."
|
||||
|
||||
INTEGRATED_TEST_RUNNER="./ryu/tests/integrated/run_tests_with_ovs12.py"
|
||||
sudo PYTHONPATH=. nosetests -s $INTEGRATED_TEST_RUNNER
|
||||
sudo PYTHONPATH=. nosetests -s $INTEGRATED_TEST_RUNNER
|
||||
}
|
||||
#NOSETESTS="nosetests $noseopts $noseargs"
|
||||
NOSETESTS="${PYTHON} ./ryu/tests/run_tests.py $noseopts $noseargs"
|
||||
@ -167,8 +161,8 @@ if [ $coverage -eq 1 ]; then
|
||||
${wrapper} coverage erase
|
||||
fi
|
||||
|
||||
if [ $just_pycodestyle -eq 1 ]; then
|
||||
run_pycodestyle
|
||||
if [ $just_pep8 -eq 1 ]; then
|
||||
run_pep8
|
||||
exit
|
||||
fi
|
||||
if [ $just_pylint -eq 1 ]; then
|
||||
@ -183,8 +177,8 @@ fi
|
||||
|
||||
run_tests
|
||||
RV=$?
|
||||
if [ $no_pycodestyle -eq 0 ]; then
|
||||
run_pycodestyle
|
||||
if [ $no_pep8 -eq 0 ]; then
|
||||
run_pep8
|
||||
fi
|
||||
|
||||
if [ $coverage -eq 1 ]; then
|
||||
|
||||
@ -14,5 +14,5 @@
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
version_info = (4, 34)
|
||||
version_info = (3, 25)
|
||||
version = '.'.join(map(str, version_info))
|
||||
|
||||
286
ryu/app/client.py
Normal file
286
ryu/app/client.py
Normal file
@ -0,0 +1,286 @@
|
||||
# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
|
||||
# Copyright (C) 2011, 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.
|
||||
|
||||
# This is a client library for Ryu REST API. (ryu.app.rest_quantum etc)
|
||||
# This module is *not* used by ryu-manager.
|
||||
# Imported and used by OpenStack Ryu plug-in and agent.
|
||||
|
||||
from six.moves import http_client
|
||||
import json
|
||||
from six.moves import urllib_parse
|
||||
|
||||
|
||||
def ignore_http_not_found(func):
|
||||
"""
|
||||
Ignore http not found(404) with Ryu client library.
|
||||
Ryu client raises httplib.HTTPException with an error in args[0]
|
||||
"""
|
||||
try:
|
||||
func()
|
||||
except http_client.HTTPException as e:
|
||||
res = e.args[0]
|
||||
if res.status != http_client.NOT_FOUND:
|
||||
raise
|
||||
|
||||
|
||||
class RyuClientBase(object):
|
||||
def __init__(self, version, address):
|
||||
super(RyuClientBase, self).__init__()
|
||||
self.version = version
|
||||
res = urllib_parse.SplitResult('', address, '', '', '')
|
||||
self.host = res.hostname
|
||||
self.port = res.port
|
||||
self.url_prefix = '/' + self.version + '/'
|
||||
|
||||
def _do_request(self, method, action, body=None):
|
||||
conn = http_client.HTTPConnection(self.host, self.port)
|
||||
url = self.url_prefix + action
|
||||
headers = {}
|
||||
if body is not None:
|
||||
body = json.dumps(body)
|
||||
headers['Content-Type'] = 'application/json'
|
||||
conn.request(method, url, body, headers)
|
||||
res = conn.getresponse()
|
||||
if res.status in (http_client.OK,
|
||||
http_client.CREATED,
|
||||
http_client.ACCEPTED,
|
||||
http_client.NO_CONTENT):
|
||||
return res
|
||||
|
||||
raise http_client.HTTPException(
|
||||
res, 'code %d reason %s' % (res.status, res.reason),
|
||||
res.getheaders(), res.read())
|
||||
|
||||
def _do_request_read(self, method, action):
|
||||
res = self._do_request(method, action)
|
||||
return res.read()
|
||||
|
||||
|
||||
class OFPClientV1_0(RyuClientBase):
|
||||
version = 'v1.0'
|
||||
|
||||
# /networks/{network_id}/{dpid}_{port}/macs/{mac_address}
|
||||
path_networks = 'networks'
|
||||
path_network = path_networks + '/%s'
|
||||
path_port = path_network + '/%s_%s'
|
||||
path_macs = path_port + '/macs'
|
||||
path_mac = path_macs + '/%s'
|
||||
|
||||
def __init__(self, address):
|
||||
super(OFPClientV1_0, self).__init__(OFPClientV1_0.version, address)
|
||||
|
||||
def get_networks(self):
|
||||
return self._do_request_read('GET', self.path_networks)
|
||||
|
||||
def create_network(self, network_id):
|
||||
self._do_request('POST', self.path_network % network_id)
|
||||
|
||||
def update_network(self, network_id):
|
||||
self._do_request('PUT', self.path_network % network_id)
|
||||
|
||||
def delete_network(self, network_id):
|
||||
self._do_request('DELETE', self.path_network % network_id)
|
||||
|
||||
def get_ports(self, network_id):
|
||||
return self._do_request_read('GET', self.path_network % network_id)
|
||||
|
||||
def create_port(self, network_id, dpid, port):
|
||||
self._do_request('POST', self.path_port % (network_id, dpid, port))
|
||||
|
||||
def update_port(self, network_id, dpid, port):
|
||||
self._do_request('PUT', self.path_port % (network_id, dpid, port))
|
||||
|
||||
def delete_port(self, network_id, dpid, port):
|
||||
self._do_request('DELETE', self.path_port % (network_id, dpid, port))
|
||||
|
||||
def list_macs(self, network_id, dpid, port):
|
||||
return self._do_request_read('GET',
|
||||
self.path_macs % (network_id, dpid, port))
|
||||
|
||||
def create_mac(self, network_id, dpid, port, mac_address):
|
||||
self._do_request('POST', self.path_mac % (network_id, dpid, port,
|
||||
mac_address))
|
||||
|
||||
def update_mac(self, network_id, dpid, port, mac_address):
|
||||
self._do_request('PUT', self.path_mac % (network_id, dpid, port,
|
||||
mac_address))
|
||||
|
||||
|
||||
OFPClient = OFPClientV1_0
|
||||
|
||||
|
||||
class TunnelClientV1_0(RyuClientBase):
|
||||
version = 'v1.0'
|
||||
|
||||
# /tunnels/networks/{network-id}/key/{tunnel_key}
|
||||
# /tunnels/switches/{dpid}/ports/{port-id}/{remote_dpip}
|
||||
path_tunnels = 'tunnels'
|
||||
path_key = path_tunnels + '/networks/%(network_id)s/key'
|
||||
path_tunnel_key = path_key + '/%(tunnel_key)s'
|
||||
path_ports = path_tunnels + '/switches/%(dpid)s/ports'
|
||||
path_port = path_ports + '/%(port_no)s'
|
||||
path_remote_dpid = path_port + '/%(remote_dpid)s'
|
||||
|
||||
def __init__(self, address):
|
||||
super(TunnelClientV1_0, self).__init__(self.version, address)
|
||||
|
||||
def get_tunnel_key(self, network_id):
|
||||
return self._do_request_read('GET', self.path_key % locals())
|
||||
|
||||
def delete_tunnel_key(self, network_id):
|
||||
return self._do_request_read('DELETE', self.path_key % locals())
|
||||
|
||||
def create_tunnel_key(self, network_id, tunnel_key):
|
||||
self._do_request('POST', self.path_tunnel_key % locals())
|
||||
|
||||
def update_tunnel_key(self, network_id, tunnel_key):
|
||||
self._do_request('PUT', self.path_tunnel_key % locals())
|
||||
|
||||
def list_ports(self, dpid):
|
||||
return self._do_request_read('GET', self.path_ports % locals())
|
||||
|
||||
def delete_port(self, dpid, port_no):
|
||||
return self._do_request_read('DELETE', self.path_port % locals())
|
||||
|
||||
def get_remote_dpid(self, dpid, port_no):
|
||||
return self._do_request_read('GET', self.path_port % locals())
|
||||
|
||||
def create_remote_dpid(self, dpid, port_no, remote_dpid):
|
||||
self._do_request('POST', self.path_remote_dpid % locals())
|
||||
|
||||
def update_remote_dpid(self, dpid, port_no, remote_dpid):
|
||||
self._do_request('PUT', self.path_remote_dpid % locals())
|
||||
|
||||
|
||||
TunnelClient = TunnelClientV1_0
|
||||
|
||||
|
||||
class SwitchConfClientV1_0(RyuClientBase):
|
||||
version = 'v1.0'
|
||||
|
||||
# /conf/switches
|
||||
# /conf/switches/<dpid>
|
||||
# /conf/switches/<dpid>/<key>
|
||||
path_conf_switches = 'conf/switches'
|
||||
path_switch = path_conf_switches + '/%(dpid)s'
|
||||
path_key = path_switch + '/%(key)s'
|
||||
|
||||
def __init__(self, address):
|
||||
super(SwitchConfClientV1_0, self).__init__(self.version, address)
|
||||
|
||||
def list_switches(self):
|
||||
return self._do_request_read('GET', self.path_conf_switches)
|
||||
|
||||
def delete_switch(self, dpid):
|
||||
self._do_request('DELETE', self.path_switch % locals())
|
||||
|
||||
def list_keys(self, dpid):
|
||||
return self._do_request_read('GET', self.path_switch % locals())
|
||||
|
||||
def set_key(self, dpid, key, value):
|
||||
self._do_request('PUT', self.path_key % locals(), value)
|
||||
|
||||
def get_key(self, dpid, key):
|
||||
return self._do_request_read('GET', self.path_key % locals())
|
||||
|
||||
def delete_key(self, dpid, key):
|
||||
self._do_request('DELETE', self.path_key % locals())
|
||||
|
||||
|
||||
SwitchConfClient = SwitchConfClientV1_0
|
||||
|
||||
|
||||
class QuantumIfaceClientV1_0(RyuClientBase):
|
||||
version = 'v1.0'
|
||||
|
||||
# /quantum/ports
|
||||
# /quantum/ports/{iface_id}
|
||||
# /quantum/ports/{iface_id}/keys/
|
||||
# /quantum/ports/{iface_id}/keys/{key}/{value}
|
||||
path_quantum_ports = 'quantum/ports'
|
||||
path_iface_id = path_quantum_ports + '/%(iface_id)s'
|
||||
path_keys = path_iface_id + '/keys'
|
||||
path_key = path_keys + '/%(key)s'
|
||||
path_value = path_key + '/%(value)s'
|
||||
|
||||
def __init__(self, address):
|
||||
super(QuantumIfaceClientV1_0, self).__init__(self.version, address)
|
||||
|
||||
def list_ifaces(self):
|
||||
return self._do_request_read('GET', self.path_quantum_ports)
|
||||
|
||||
def delete_iface(self, iface_id):
|
||||
self._do_request('DELETE', self.path_iface_id % locals())
|
||||
|
||||
def list_keys(self, iface_id):
|
||||
return self._do_request_read('GET', self.path_keys % locals())
|
||||
|
||||
def get_key(self, iface_id, key):
|
||||
return self._do_request_read('GET', self.path_key % locals())
|
||||
|
||||
def create_key(self, iface_id, key, value):
|
||||
self._do_request('POST', self.path_value % locals())
|
||||
|
||||
def update_key(self, iface_id, key, value):
|
||||
self._do_request('PUT', self.path_value % locals())
|
||||
|
||||
# for convenience
|
||||
def get_network_id(self, iface_id):
|
||||
return self.get_key(iface_id, 'network_id')
|
||||
|
||||
def create_network_id(self, iface_id, network_id):
|
||||
self.create_key(iface_id, 'network_id', network_id)
|
||||
|
||||
def update_network_id(self, iface_id, network_id):
|
||||
self.update_key(iface_id, 'network_id', network_id)
|
||||
|
||||
|
||||
QuantumIfaceClient = QuantumIfaceClientV1_0
|
||||
NeutronIfaceClient = QuantumIfaceClient # project rename quantum -> neutron
|
||||
|
||||
|
||||
class TopologyClientV1_0(RyuClientBase):
|
||||
version = 'v1.0'
|
||||
|
||||
# /topology/switches
|
||||
# /topology/switches/{dpid}
|
||||
# /topology/links
|
||||
# /topology/links/{dpid}
|
||||
_path_switches = 'topology/switches'
|
||||
_path_links = 'topology/links'
|
||||
|
||||
def __init__(self, address):
|
||||
super(TopologyClientV1_0, self).__init__(self.version, address)
|
||||
|
||||
# dpid: string representation (see ryu.lib.dpid)
|
||||
# if None, get all
|
||||
def list_switches(self, dpid=None):
|
||||
uri = self._path_switches
|
||||
if dpid:
|
||||
uri += '/%s' % (dpid)
|
||||
|
||||
return self._do_request('GET', uri)
|
||||
|
||||
# dpid: string representation (see ryu.lib.dpid)
|
||||
# if None, get all
|
||||
def list_links(self, dpid=None):
|
||||
uri = self._path_links
|
||||
if dpid:
|
||||
uri += '/%s' % (dpid)
|
||||
return self._do_request('GET', uri)
|
||||
|
||||
|
||||
TopologyClient = TopologyClientV1_0
|
||||
@ -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 ryu.base import app_manager
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.ofproto import ofproto_v1_3
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.lib.packet import ethernet
|
||||
|
||||
|
||||
class ExampleSwitch13(app_manager.RyuApp):
|
||||
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)
|
||||
980
ryu/app/gre_tunnel.py
Normal file
980
ryu/app/gre_tunnel.py
Normal file
@ -0,0 +1,980 @@
|
||||
# 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 updates flow table for OpenStack integration.
|
||||
# Despite of the name, this module isn't GRE specific and
|
||||
# should work for VXLAN etc as well.
|
||||
|
||||
"""
|
||||
Flow table updater for OpenStack integration. Despite of the name, this
|
||||
isn't GRE specific.
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
from ryu import exception as ryu_exc
|
||||
from ryu.app.rest_nw_id import (NW_ID_VPORT_GRE,
|
||||
RESERVED_NETWORK_IDS)
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import (dpset,
|
||||
event,
|
||||
handler,
|
||||
network,
|
||||
ofp_event,
|
||||
tunnels)
|
||||
from ryu.ofproto import nx_match
|
||||
from ryu.lib import dpid as dpid_lib
|
||||
from ryu.lib import mac
|
||||
|
||||
|
||||
def _is_reserved_port(ofproto, port_no):
|
||||
return port_no > ofproto.OFPP_MAX
|
||||
|
||||
|
||||
def _link_is_up(dpset_, dp, port_no):
|
||||
try:
|
||||
state = dpset_.get_port(dp.id, port_no).state
|
||||
return not (state & dp.ofproto.OFPPS_LINK_DOWN)
|
||||
except ryu_exc.PortNotFound:
|
||||
return False
|
||||
|
||||
|
||||
class PortSet(app_manager.RyuApp):
|
||||
|
||||
# Those events are higher level events than events of network tenant,
|
||||
# tunnel ports as the race conditions are masked.
|
||||
# Add event is generated only when all necessary informations are gathered,
|
||||
# Del event is generated when any one of the informations are deleted.
|
||||
#
|
||||
# Example: ports for VMs
|
||||
# there is a race condition between ofp port add/del event and
|
||||
# register network_id for the port.
|
||||
|
||||
class EventTunnelKeyDel(event.EventBase):
|
||||
def __init__(self, tunnel_key):
|
||||
super(PortSet.EventTunnelKeyDel, self).__init__()
|
||||
self.tunnel_key = tunnel_key
|
||||
|
||||
class EventPortBase(event.EventBase):
|
||||
def __init__(self, dpid, port_no):
|
||||
super(PortSet.EventPortBase, self).__init__()
|
||||
self.dpid = dpid
|
||||
self.port_no = port_no
|
||||
|
||||
class EventVMPort(EventPortBase):
|
||||
def __init__(self, network_id, tunnel_key,
|
||||
dpid, port_no, mac_address, add_del):
|
||||
super(PortSet.EventVMPort, self).__init__(dpid, port_no)
|
||||
self.network_id = network_id
|
||||
self.tunnel_key = tunnel_key
|
||||
self.mac_address = mac_address
|
||||
self.add_del = add_del
|
||||
|
||||
def __str__(self):
|
||||
return ('EventVMPort<dpid %s port_no %d '
|
||||
'network_id %s tunnel_key %s mac %s add_del %s>' %
|
||||
(dpid_lib.dpid_to_str(self.dpid), self.port_no,
|
||||
self.network_id, self.tunnel_key,
|
||||
mac.haddr_to_str(self.mac_address), self.add_del))
|
||||
|
||||
class EventTunnelPort(EventPortBase):
|
||||
def __init__(self, dpid, port_no, remote_dpid, add_del):
|
||||
super(PortSet.EventTunnelPort, self).__init__(dpid, port_no)
|
||||
self.remote_dpid = remote_dpid
|
||||
self.add_del = add_del
|
||||
|
||||
def __str__(self):
|
||||
return ('EventTunnelPort<dpid %s port_no %d remote_dpid %s '
|
||||
'add_del %s>' %
|
||||
(dpid_lib.dpid_to_str(self.dpid), self.port_no,
|
||||
dpid_lib.dpid_to_str(self.remote_dpid), self.add_del))
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(PortSet, self).__init__()
|
||||
self.nw = kwargs['network']
|
||||
self.tunnels = kwargs['tunnels']
|
||||
self.dpset = kwargs['dpset']
|
||||
app_manager.register_app(self)
|
||||
|
||||
def _check_link_state(self, dp, port_no, add_del):
|
||||
if add_del:
|
||||
# When adding port, the link should be UP.
|
||||
return _link_is_up(self.dpset, dp, port_no)
|
||||
else:
|
||||
# When deleting port, the link status isn't cared.
|
||||
return True
|
||||
|
||||
# Tunnel port
|
||||
# of connecting: self.dpids by (dpid, port_no)
|
||||
# datapath: connected: EventDP event
|
||||
# port status: UP: port add/delete/modify event
|
||||
# remote dpid: self.tunnels by (dpid, port_no): tunnel port add/del even
|
||||
def _tunnel_port_handler(self, dpid, port_no, add_del):
|
||||
dp = self.dpset.get(dpid)
|
||||
if dp is None:
|
||||
return
|
||||
if not self._check_link_state(dp, port_no, add_del):
|
||||
return
|
||||
try:
|
||||
remote_dpid = self.tunnels.get_remote_dpid(dpid, port_no)
|
||||
except ryu_exc.PortNotFound:
|
||||
return
|
||||
|
||||
self.send_event_to_observers(self.EventTunnelPort(dpid, port_no,
|
||||
remote_dpid, add_del))
|
||||
|
||||
# VM port
|
||||
# of connection: self.dpids by (dpid, port_no)
|
||||
# datapath: connected: EventDP event
|
||||
# port status: UP: Port add/delete/modify event
|
||||
# network_id: self.nw by (dpid, port_no): network port add/del event
|
||||
# mac_address: self.nw by (dpid, port_no): mac address add/del event
|
||||
# tunnel key: from self.tunnels by network_id: tunnel key add/del event
|
||||
def _vm_port_handler(self, dpid, port_no,
|
||||
network_id, mac_address, add_del):
|
||||
if network_id in RESERVED_NETWORK_IDS:
|
||||
return
|
||||
if mac_address is None:
|
||||
return
|
||||
dp = self.dpset.get(dpid)
|
||||
if dp is None:
|
||||
return
|
||||
if _is_reserved_port(dp.ofproto, port_no):
|
||||
return
|
||||
if not self._check_link_state(dp, port_no, add_del):
|
||||
return
|
||||
try:
|
||||
tunnel_key = self.tunnels.get_key(network_id)
|
||||
except tunnels.TunnelKeyNotFound:
|
||||
return
|
||||
|
||||
self.send_event_to_observers(self.EventVMPort(network_id, tunnel_key,
|
||||
dpid, port_no, mac_address, add_del))
|
||||
|
||||
def _vm_port_mac_handler(self, dpid, port_no, network_id, add_del):
|
||||
if network_id == NW_ID_VPORT_GRE:
|
||||
self._tunnel_port_handler(dpid, port_no, add_del)
|
||||
return
|
||||
|
||||
try:
|
||||
mac_address = self.nw.get_mac(dpid, port_no)
|
||||
except ryu_exc.PortNotFound:
|
||||
return
|
||||
self._vm_port_handler(dpid, port_no, network_id, mac_address,
|
||||
add_del)
|
||||
|
||||
def _port_handler(self, dpid, port_no, add_del):
|
||||
"""
|
||||
:type add_del: bool
|
||||
:param add_del: True for add, False for del
|
||||
"""
|
||||
try:
|
||||
port = self.nw.get_port(dpid, port_no)
|
||||
except ryu_exc.PortNotFound:
|
||||
return
|
||||
|
||||
if port.network_id is None:
|
||||
return
|
||||
|
||||
if port.network_id == NW_ID_VPORT_GRE:
|
||||
self._tunnel_port_handler(dpid, port_no, add_del)
|
||||
return
|
||||
|
||||
self._vm_port_handler(dpid, port_no, port.network_id,
|
||||
port.mac_address, add_del)
|
||||
|
||||
def _tunnel_key_del(self, tunnel_key):
|
||||
self.send_event_to_observers(self.EventTunnelKeyDel(tunnel_key))
|
||||
|
||||
# nw: network del
|
||||
# port add/del (vm/tunnel port)
|
||||
# mac address add/del(only vm port)
|
||||
# tunnels: tunnel key add/del
|
||||
# tunnel port add/del
|
||||
# dpset: eventdp
|
||||
# port add/delete/modify
|
||||
|
||||
@handler.set_ev_cls(network.EventNetworkDel)
|
||||
def network_del_handler(self, ev):
|
||||
network_id = ev.network_id
|
||||
if network_id in RESERVED_NETWORK_IDS:
|
||||
return
|
||||
try:
|
||||
tunnel_key = self.tunnels.get_key(network_id)
|
||||
except tunnels.TunnelKeyNotFound:
|
||||
return
|
||||
self._tunnel_key_del(tunnel_key)
|
||||
|
||||
@handler.set_ev_cls(network.EventNetworkPort)
|
||||
def network_port_handler(self, ev):
|
||||
self._vm_port_mac_handler(ev.dpid, ev.port_no, ev.network_id,
|
||||
ev.add_del)
|
||||
|
||||
@handler.set_ev_cls(network.EventMacAddress)
|
||||
def network_mac_address_handler(self, ev):
|
||||
self._vm_port_handler(ev.dpid, ev.port_no, ev.network_id,
|
||||
ev.mac_address, ev.add_del)
|
||||
|
||||
@handler.set_ev_cls(tunnels.EventTunnelKeyAdd)
|
||||
def tunnel_key_add_handler(self, ev):
|
||||
network_id = ev.network_id
|
||||
for (dpid, port_no) in self.nw.list_ports_noraise(network_id):
|
||||
self._vm_port_mac_handler(dpid, port_no, network_id, True)
|
||||
|
||||
@handler.set_ev_cls(tunnels.EventTunnelKeyDel)
|
||||
def tunnel_key_del_handler(self, ev):
|
||||
network_id = ev.network_id
|
||||
for (dpid, port_no) in self.nw.list_ports_noraise(network_id):
|
||||
self._vm_port_mac_handler(dpid, port_no, network_id, False)
|
||||
if self.nw.has_network(network_id):
|
||||
self._tunnel_key_del(ev.tunnel_key)
|
||||
|
||||
@handler.set_ev_cls(tunnels.EventTunnelPort)
|
||||
def tunnel_port_handler(self, ev):
|
||||
self._port_handler(ev.dpid, ev.port_no, ev.add_del)
|
||||
|
||||
@handler.set_ev_cls(dpset.EventDP)
|
||||
def dp_handler(self, ev):
|
||||
self.send_event_to_observers(ev)
|
||||
enter_leave = ev.enter
|
||||
if not enter_leave:
|
||||
# TODO:XXX
|
||||
# What to do on datapath disconnection?
|
||||
self.logger.debug('dp disconnection ev:%s', ev)
|
||||
|
||||
dpid = ev.dp.id
|
||||
ports = set(port.port_no for port in ev.ports)
|
||||
ports.update(port.port_no for port in self.nw.get_ports(dpid))
|
||||
for port_no in ports:
|
||||
self._port_handler(dpid, port_no, enter_leave)
|
||||
|
||||
@handler.set_ev_cls(dpset.EventPortAdd)
|
||||
def port_add_handler(self, ev):
|
||||
self._port_handler(ev.dp.id, ev.port.port_no, True)
|
||||
|
||||
@handler.set_ev_cls(dpset.EventPortDelete)
|
||||
def port_del_handler(self, ev):
|
||||
self._port_handler(ev.dp.id, ev.port.port_no, False)
|
||||
|
||||
@handler.set_ev_cls(dpset.EventPortModify)
|
||||
def port_modify_handler(self, ev):
|
||||
# We don't know LINK status has been changed.
|
||||
# So VM/TUNNEL port event can be triggered many times.
|
||||
dp = ev.dp
|
||||
port = ev.port
|
||||
self._port_handler(dp.id, port.port_no,
|
||||
not (port.state & dp.ofproto.OFPPS_LINK_DOWN))
|
||||
|
||||
@handler.set_ev_cls(ofp_event.EventOFPPacketIn)
|
||||
def packet_in_handler(self, ev):
|
||||
# for debug
|
||||
self.send_event_to_observers(ev)
|
||||
|
||||
|
||||
def cls_rule(in_port=None, tun_id=None, dl_src=None, dl_dst=None):
|
||||
"""Convenience function to initialize nx_match.ClsRule()"""
|
||||
rule = nx_match.ClsRule()
|
||||
if in_port is not None:
|
||||
rule.set_in_port(in_port)
|
||||
if tun_id is not None:
|
||||
rule.set_tun_id(tun_id)
|
||||
if dl_src is not None:
|
||||
rule.set_dl_src(dl_src)
|
||||
if dl_dst is not None:
|
||||
rule.set_dl_dst(dl_dst)
|
||||
return rule
|
||||
|
||||
|
||||
class GRETunnel(app_manager.RyuApp):
|
||||
"""
|
||||
app for L2/L3 with gre tunneling
|
||||
|
||||
PORTS
|
||||
VM-port: the port which is connected to VM instance
|
||||
TUNNEL-port: the ovs GRE vport
|
||||
|
||||
TABLES: multi tables is used
|
||||
SRC_TABLE:
|
||||
This table is firstly used to match packets.
|
||||
by in_port, determine which port the packet comes VM-port or
|
||||
TUNNEL-port.
|
||||
If the packet came from VM-port, set tunnel id based on which network
|
||||
the VM belongs to, and send the packet to the tunnel out table.
|
||||
If the packet came from TUNNEL-port and its tunnel id is known to this
|
||||
switch, send the packet to local out table. Otherwise drop it.
|
||||
|
||||
TUNNEL_OUT_TABLE:
|
||||
This table looks at tunnel id and dl_dst, send the packet to tunnel
|
||||
ports if necessary. And then, sends the packet to LOCAL_OUT_TABLE.
|
||||
By matching the packet with tunnel_id and dl_dst, determine which
|
||||
tunnel port the packet is send to.
|
||||
|
||||
LOCAL_OUT_TABLE:
|
||||
This table looks at tunnel id and dl_dst, send the packet to local
|
||||
VM ports if necessary. Otherwise drop the packet.
|
||||
|
||||
|
||||
The packet from vm port traverses as
|
||||
SRC_TABLE -> TUNNEL_OUT_TABLE -> LOCAL_OUT_TABLE
|
||||
|
||||
The packet from tunnel port traverses as
|
||||
SRC_TABLE -> LOCAL_OUT_TABLE
|
||||
|
||||
|
||||
The packet from vm port:
|
||||
SRC_TABLE
|
||||
match action
|
||||
in_port(VM) & dl_src set_tunnel & goto TUNNEL_OUT_TABLE
|
||||
in_port(VM) drop (catch-all drop rule)
|
||||
|
||||
in_port(TUNNEL) & tun_id goto LOCAL_OUT_TABLE
|
||||
in_port(TUNNEL) drop (catch-all drop rule)
|
||||
|
||||
TUNNEL_OUT_TABLE
|
||||
match action
|
||||
tun_id & dl_dst out tunnel port & goto LOCAL_OUT_TABLE
|
||||
(unicast or broadcast)
|
||||
tun_id goto LOCAL_OUT_TABLE (catch-all rule)
|
||||
|
||||
LOCAL_OUT_TABLE
|
||||
tun_id & dl_dst output(VM) (unicast or broadcast)
|
||||
tun_id drop (catch-all drop rule)
|
||||
|
||||
NOTE:
|
||||
adding/deleting flow entries should be done carefully in certain order
|
||||
such that packet in event should not be triggered.
|
||||
"""
|
||||
_CONTEXTS = {
|
||||
'network': network.Network,
|
||||
'dpset': dpset.DPSet,
|
||||
'tunnels': tunnels.Tunnels,
|
||||
}
|
||||
|
||||
DEFAULT_COOKIE = 0 # cookie isn't used. Just set 0
|
||||
|
||||
# Tables
|
||||
SRC_TABLE = 0
|
||||
TUNNEL_OUT_TABLE = 1
|
||||
LOCAL_OUT_TABLE = 2
|
||||
FLOW_TABLES = [SRC_TABLE, TUNNEL_OUT_TABLE, LOCAL_OUT_TABLE]
|
||||
|
||||
# Priorities. The only inequality is important.
|
||||
# '/ 2' is used just for easy looking instead of '- 1'.
|
||||
# 0x7ffff vs 0x4000
|
||||
TABLE_DEFAULT_PRPIRITY = 32768 # = ofproto.OFP_DEFAULT_PRIORITY
|
||||
|
||||
# SRC_TABLE for VM-port
|
||||
SRC_PRI_MAC = TABLE_DEFAULT_PRPIRITY
|
||||
SRC_PRI_DROP = TABLE_DEFAULT_PRPIRITY / 2
|
||||
# SRC_TABLE for TUNNEL-port
|
||||
SRC_PRI_TUNNEL_PASS = TABLE_DEFAULT_PRPIRITY
|
||||
SRC_PRI_TUNNEL_DROP = TABLE_DEFAULT_PRPIRITY / 2
|
||||
|
||||
# TUNNEL_OUT_TABLE
|
||||
TUNNEL_OUT_PRI_MAC = TABLE_DEFAULT_PRPIRITY
|
||||
TUNNEL_OUT_PRI_BROADCAST = TABLE_DEFAULT_PRPIRITY / 2
|
||||
TUNNEL_OUT_PRI_PASS = TABLE_DEFAULT_PRPIRITY / 4
|
||||
TUNNEL_OUT_PRI_DROP = TABLE_DEFAULT_PRPIRITY / 8
|
||||
|
||||
# LOCAL_OUT_TABLE
|
||||
LOCAL_OUT_PRI_MAC = TABLE_DEFAULT_PRPIRITY
|
||||
LOCAL_OUT_PRI_BROADCAST = TABLE_DEFAULT_PRPIRITY / 2
|
||||
LOCAL_OUT_PRI_DROP = TABLE_DEFAULT_PRPIRITY / 4
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(GRETunnel, self).__init__(*args, **kwargs)
|
||||
self.nw = kwargs['network']
|
||||
self.dpset = kwargs['dpset']
|
||||
self.tunnels = kwargs['tunnels']
|
||||
|
||||
self.port_set = PortSet(**kwargs)
|
||||
map(lambda ev_cls: self.port_set.register_observer(ev_cls, self.name),
|
||||
[dpset.EventDP, PortSet.EventTunnelKeyDel, PortSet.EventVMPort,
|
||||
PortSet.EventTunnelPort, ofp_event.EventOFPPacketIn])
|
||||
|
||||
def start(self):
|
||||
super(GRETunnel, self).start()
|
||||
self.port_set.start()
|
||||
|
||||
def stop(self):
|
||||
app_mgr = app_manager.get_instance()
|
||||
app_mgr.uninstantiate(self.port_set)
|
||||
self.port_set = None
|
||||
super(GRETunnel, self).stop()
|
||||
|
||||
# TODO: track active vm/tunnel ports
|
||||
|
||||
@handler.set_ev_handler(dpset.EventDP)
|
||||
def dp_handler(self, ev):
|
||||
if not ev.enter:
|
||||
return
|
||||
|
||||
# enable nicira extension
|
||||
# TODO:XXX error handling
|
||||
dp = ev.dp
|
||||
ofproto = dp.ofproto
|
||||
|
||||
dp.send_nxt_set_flow_format(ofproto.NXFF_NXM)
|
||||
flow_mod_table_id = dp.ofproto_parser.NXTFlowModTableId(dp, 1)
|
||||
dp.send_msg(flow_mod_table_id)
|
||||
dp.send_barrier()
|
||||
|
||||
# delete all flows in all tables
|
||||
# current controller.handlers takes care of only table = 0
|
||||
for table in self.FLOW_TABLES:
|
||||
rule = cls_rule()
|
||||
self.send_flow_del(dp, rule, table, ofproto.OFPFC_DELETE,
|
||||
None, None)
|
||||
dp.send_barrier()
|
||||
|
||||
@staticmethod
|
||||
def _make_command(table, command):
|
||||
return table << 8 | command
|
||||
|
||||
def send_flow_mod(self, dp, rule, table, command, priority, actions):
|
||||
command = self._make_command(table, command)
|
||||
dp.send_flow_mod(rule=rule, cookie=self.DEFAULT_COOKIE,
|
||||
command=command, idle_timeout=0,
|
||||
hard_timeout=0, priority=priority, actions=actions)
|
||||
|
||||
def send_flow_del(self, dp, rule, table, command, priority, out_port):
|
||||
command = self._make_command(table, command)
|
||||
dp.send_flow_mod(rule=rule, cookie=self.DEFAULT_COOKIE,
|
||||
command=command, idle_timeout=0,
|
||||
hard_timeout=0, priority=priority, out_port=out_port)
|
||||
|
||||
def _list_tunnel_port(self, dp, remote_dpids):
|
||||
dpid = dp.id
|
||||
tunnel_ports = []
|
||||
for other_dpid in remote_dpids:
|
||||
if other_dpid == dpid:
|
||||
continue
|
||||
other_dp = self.dpset.get(other_dpid)
|
||||
if other_dp is None:
|
||||
continue
|
||||
try:
|
||||
port_no = self.tunnels.get_port(dpid, other_dpid)
|
||||
except ryu_exc.PortNotFound:
|
||||
continue
|
||||
if not self._link_is_up(dp, port_no):
|
||||
continue
|
||||
tunnel_ports.append(port_no)
|
||||
|
||||
return tunnel_ports
|
||||
|
||||
def _link_is_up(self, dp, port_no):
|
||||
return _link_is_up(self.dpset, dp, port_no)
|
||||
|
||||
def _port_is_active(self, network_id, dp, nw_port):
|
||||
return (nw_port.network_id == network_id and
|
||||
nw_port.mac_address is not None and
|
||||
self._link_is_up(dp, nw_port.port_no))
|
||||
|
||||
def _tunnel_port_with_mac(self, remote_dp, dpid, network_id, port_no,
|
||||
mac_address):
|
||||
tunnel_ports = []
|
||||
ports = self.nw.get_ports_with_mac(network_id, mac_address).copy()
|
||||
ports.discard((dpid, port_no))
|
||||
assert len(ports) <= 1
|
||||
for port in ports:
|
||||
try:
|
||||
tunnel_port_no = self.tunnels.get_port(remote_dp.id, port.dpid)
|
||||
except ryu_exc.PortNotFound:
|
||||
pass
|
||||
else:
|
||||
if self._link_is_up(remote_dp, tunnel_port_no):
|
||||
tunnel_ports.append(tunnel_port_no)
|
||||
|
||||
assert len(tunnel_ports) <= 1
|
||||
return tunnel_ports
|
||||
|
||||
def _vm_port_add(self, ev):
|
||||
dpid = ev.dpid
|
||||
dp = self.dpset.get(dpid)
|
||||
assert dp is not None
|
||||
ofproto = dp.ofproto
|
||||
ofproto_parser = dp.ofproto_parser
|
||||
mac_address = ev.mac_address
|
||||
network_id = ev.network_id
|
||||
tunnel_key = ev.tunnel_key
|
||||
remote_dpids = self.nw.get_dpids(network_id)
|
||||
remote_dpids.remove(dpid)
|
||||
|
||||
# LOCAL_OUT_TABLE: unicast
|
||||
# live-migration: there can be two ports with same mac_address
|
||||
ports = self.nw.get_ports(dpid, network_id, mac_address)
|
||||
assert ev.port_no in [port.port_no for port in ports]
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address)
|
||||
actions = [ofproto_parser.OFPActionOutput(port.port_no)
|
||||
for port in ports if self._link_is_up(dp, port.port_no)]
|
||||
self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, ofproto.OFPFC_ADD,
|
||||
self.LOCAL_OUT_PRI_MAC, actions)
|
||||
|
||||
# LOCAL_OUT_TABLE: broad cast
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST)
|
||||
actions = []
|
||||
for port in self.nw.get_ports(dpid):
|
||||
if not self._port_is_active(network_id, dp, port):
|
||||
continue
|
||||
actions.append(ofproto_parser.OFPActionOutput(port.port_no))
|
||||
|
||||
first_instance = (len(actions) == 1)
|
||||
assert actions
|
||||
if first_instance:
|
||||
command = ofproto.OFPFC_ADD
|
||||
else:
|
||||
command = ofproto.OFPFC_MODIFY_STRICT
|
||||
self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE, command,
|
||||
self.LOCAL_OUT_PRI_BROADCAST, actions)
|
||||
|
||||
# LOCAL_OUT_TABLE: multicast TODO:XXX
|
||||
|
||||
# LOCAL_OUT_TABLE: catch-all drop
|
||||
if first_instance:
|
||||
rule = cls_rule(tun_id=tunnel_key)
|
||||
self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE,
|
||||
ofproto.OFPFC_ADD, self.LOCAL_OUT_PRI_DROP, [])
|
||||
|
||||
# TUNNEL_OUT_TABLE: unicast
|
||||
mac_to_ports = collections.defaultdict(set)
|
||||
for remote_dpid in remote_dpids:
|
||||
remote_dp = self.dpset.get(remote_dpid)
|
||||
if remote_dp is None:
|
||||
continue
|
||||
try:
|
||||
tunnel_port_no = self.tunnels.get_port(dpid, remote_dpid)
|
||||
except ryu_exc.PortNotFound:
|
||||
continue
|
||||
if not self._link_is_up(dp, tunnel_port_no):
|
||||
continue
|
||||
|
||||
for port in self.nw.get_ports(remote_dpid):
|
||||
if not self._port_is_active(network_id, remote_dp, port):
|
||||
continue
|
||||
# TUNNEL_OUT_TABLE: unicast
|
||||
# live-migration: there can be more than one tunnel-ports that
|
||||
# have a given mac address
|
||||
mac_to_ports[port.mac_address].add(tunnel_port_no)
|
||||
|
||||
if first_instance:
|
||||
# SRC_TABLE: TUNNEL-port: resubmit to LOAL_OUT_TABLE
|
||||
rule = cls_rule(in_port=tunnel_port_no, tun_id=tunnel_key)
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE)
|
||||
actions = [resubmit_table]
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE,
|
||||
ofproto.OFPFC_ADD, self.SRC_PRI_TUNNEL_PASS,
|
||||
actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: unicast
|
||||
for remote_mac_address, tunnel_ports in mac_to_ports.items():
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=remote_mac_address)
|
||||
outputs = [ofproto_parser.OFPActionOutput(tunnel_port_no)
|
||||
for tunnel_port_no in tunnel_ports]
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE)
|
||||
actions = outputs + [resubmit_table]
|
||||
self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_MAC,
|
||||
actions)
|
||||
|
||||
if first_instance:
|
||||
# TUNNEL_OUT_TABLE: catch-all(resubmit to LOCAL_OUT_TABLE)
|
||||
rule = cls_rule(tun_id=tunnel_key)
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE)
|
||||
actions = [resubmit_table]
|
||||
self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
ofproto.OFPFC_ADD,
|
||||
self.TUNNEL_OUT_PRI_PASS, actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: broadcast
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST)
|
||||
actions = [ofproto_parser.OFPActionOutput(tunnel_port_no)
|
||||
for tunnel_port_no
|
||||
in self._list_tunnel_port(dp, remote_dpids)]
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE)
|
||||
actions.append(resubmit_table)
|
||||
self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
ofproto.OFPFC_ADD,
|
||||
self.TUNNEL_OUT_PRI_BROADCAST, actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: multicast TODO:XXX
|
||||
|
||||
# SRC_TABLE: VM-port unicast
|
||||
dp.send_barrier()
|
||||
rule = cls_rule(in_port=ev.port_no, dl_src=mac_address)
|
||||
set_tunnel = ofproto_parser.NXActionSetTunnel(tunnel_key)
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.TUNNEL_OUT_TABLE)
|
||||
actions = [set_tunnel, resubmit_table]
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD,
|
||||
self.SRC_PRI_MAC, actions)
|
||||
|
||||
# SRC_TABLE: VM-port catch-call drop
|
||||
rule = cls_rule(in_port=ev.port_no)
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD,
|
||||
self.SRC_PRI_DROP, [])
|
||||
|
||||
# remote dp
|
||||
for remote_dpid in remote_dpids:
|
||||
remote_dp = self.dpset.get(remote_dpid)
|
||||
if remote_dp is None:
|
||||
continue
|
||||
try:
|
||||
tunnel_port_no = self.tunnels.get_port(remote_dpid, dpid)
|
||||
except ryu_exc.PortNotFound:
|
||||
continue
|
||||
if not self._link_is_up(remote_dp, tunnel_port_no):
|
||||
continue
|
||||
|
||||
remote_ofproto = remote_dp.ofproto
|
||||
remote_ofproto_parser = remote_dp.ofproto_parser
|
||||
|
||||
# TUNNEL_OUT_TABLE: unicast
|
||||
# live-migration: there can be another port that has
|
||||
# same mac address
|
||||
tunnel_ports = self._tunnel_port_with_mac(remote_dp, dpid,
|
||||
network_id, ev.port_no,
|
||||
mac_address)
|
||||
tunnel_ports.append(tunnel_port_no)
|
||||
|
||||
rule = cls_rule(tun_id=ev.tunnel_key, dl_dst=mac_address)
|
||||
outputs = [remote_ofproto_parser.OFPActionOutput(port_no)
|
||||
for port_no in tunnel_ports]
|
||||
resubmit_table = remote_ofproto_parser.NXActionResubmitTable(
|
||||
in_port=remote_ofproto.OFPP_IN_PORT,
|
||||
table=self.LOCAL_OUT_TABLE)
|
||||
actions = outputs + [resubmit_table]
|
||||
self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
remote_ofproto.OFPFC_ADD,
|
||||
self.TUNNEL_OUT_PRI_MAC, actions)
|
||||
|
||||
if not first_instance:
|
||||
continue
|
||||
|
||||
# SRC_TABLE: TUNNEL-port
|
||||
rule = cls_rule(in_port=tunnel_port_no, tun_id=ev.tunnel_key)
|
||||
resubmit_table = remote_ofproto_parser.NXActionResubmitTable(
|
||||
in_port=remote_ofproto.OFPP_IN_PORT,
|
||||
table=self.LOCAL_OUT_TABLE)
|
||||
actions = [resubmit_table]
|
||||
self.send_flow_mod(remote_dp, rule, self.SRC_TABLE,
|
||||
remote_ofproto.OFPFC_ADD,
|
||||
self.SRC_PRI_TUNNEL_PASS, actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: broadcast
|
||||
rule = cls_rule(tun_id=ev.tunnel_key, dl_dst=mac.BROADCAST)
|
||||
tunnel_ports = self._list_tunnel_port(remote_dp, remote_dpids)
|
||||
if tunnel_port_no not in tunnel_ports:
|
||||
tunnel_ports.append(tunnel_port_no)
|
||||
actions = [remote_ofproto_parser.OFPActionOutput(port_no)
|
||||
for port_no in tunnel_ports]
|
||||
if len(actions) == 1:
|
||||
command = remote_dp.ofproto.OFPFC_ADD
|
||||
else:
|
||||
command = remote_dp.ofproto.OFPFC_MODIFY_STRICT
|
||||
resubmit_table = remote_ofproto_parser.NXActionResubmitTable(
|
||||
in_port=remote_ofproto.OFPP_IN_PORT,
|
||||
table=self.LOCAL_OUT_TABLE)
|
||||
actions.append(resubmit_table)
|
||||
self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
command, self.TUNNEL_OUT_PRI_BROADCAST, actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: multicast TODO:XXX
|
||||
|
||||
def _vm_port_del(self, ev):
|
||||
dpid = ev.dpid
|
||||
dp = self.dpset.get(dpid)
|
||||
assert dp is not None
|
||||
ofproto = dp.ofproto
|
||||
ofproto_parser = dp.ofproto_parser
|
||||
mac_address = ev.mac_address
|
||||
network_id = ev.network_id
|
||||
tunnel_key = ev.tunnel_key
|
||||
|
||||
local_ports = []
|
||||
for port in self.nw.get_ports(dpid):
|
||||
if port.port_no == ev.port_no:
|
||||
continue
|
||||
if not self._port_is_active(network_id, dp, port):
|
||||
continue
|
||||
local_ports.append(port.port_no)
|
||||
|
||||
last_instance = not local_ports
|
||||
|
||||
# SRC_TABLE: VM-port unicast and catch-call
|
||||
rule = cls_rule(in_port=ev.port_no)
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_DELETE,
|
||||
ofproto.OFP_DEFAULT_PRIORITY,
|
||||
[]) # priority is ignored
|
||||
|
||||
if last_instance:
|
||||
# SRC_TABLE: TUNNEL-port: all tunnel matching
|
||||
rule = cls_rule(tun_id=tunnel_key)
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE,
|
||||
ofproto.OFPFC_DELETE,
|
||||
ofproto.OFP_DEFAULT_PRIORITY,
|
||||
[]) # priority is ignored
|
||||
|
||||
# TUNNEL_OUT_TABLE: (tun_id & dl_dst) and tun_id
|
||||
rule = cls_rule(tun_id=tunnel_key)
|
||||
self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
ofproto.OFPFC_DELETE,
|
||||
ofproto.OFP_DEFAULT_PRIORITY,
|
||||
[]) # priority is ignored
|
||||
|
||||
# LOCAL_OUT: tun_id catch-all drop rule
|
||||
rule = cls_rule(tun_id=tunnel_key)
|
||||
self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE,
|
||||
ofproto.OFPFC_DELETE,
|
||||
ofproto.OFP_DEFAULT_PRIORITY,
|
||||
[]) # priority is ignored
|
||||
else:
|
||||
# LOCAL_OUT_TABLE: unicast
|
||||
# live-migration: there can be two ports with same mac_address
|
||||
ports = self.nw.get_ports(dpid, network_id, mac_address)
|
||||
port_nos = [port.port_no for port in ports
|
||||
if (port.port_no != ev.port_no and
|
||||
self._link_is_up(dp, port.port_no))]
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address)
|
||||
if port_nos:
|
||||
assert len(ports) == 1
|
||||
actions = [ofproto_parser.OFPActionOutput(port_no)
|
||||
for port_no in port_nos]
|
||||
self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE,
|
||||
ofproto.OFPFC_MODIFY_STRICT,
|
||||
self.LOCAL_OUT_PRI_MAC, actions)
|
||||
else:
|
||||
self.send_flow_del(dp, rule, self.LOCAL_OUT_TABLE,
|
||||
ofproto.OFPFC_DELETE_STRICT,
|
||||
self.LOCAL_OUT_PRI_MAC, ev.port_no)
|
||||
|
||||
# LOCAL_OUT_TABLE: broadcast
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST)
|
||||
actions = [ofproto_parser.OFPActionOutput(port_no)
|
||||
for port_no in local_ports]
|
||||
self.send_flow_mod(dp, rule, self.LOCAL_OUT_TABLE,
|
||||
ofproto.OFPFC_MODIFY_STRICT,
|
||||
self.LOCAL_OUT_PRI_BROADCAST, actions)
|
||||
|
||||
# LOCAL_OUT_TABLE: multicast TODO:XXX
|
||||
|
||||
# remote dp
|
||||
remote_dpids = self.nw.get_dpids(ev.network_id)
|
||||
if dpid in remote_dpids:
|
||||
remote_dpids.remove(dpid)
|
||||
for remote_dpid in remote_dpids:
|
||||
remote_dp = self.dpset.get(remote_dpid)
|
||||
if remote_dp is None:
|
||||
continue
|
||||
try:
|
||||
tunnel_port_no = self.tunnels.get_port(remote_dpid, dpid)
|
||||
except ryu_exc.PortNotFound:
|
||||
continue
|
||||
if not self._link_is_up(remote_dp, tunnel_port_no):
|
||||
continue
|
||||
|
||||
remote_ofproto = remote_dp.ofproto
|
||||
remote_ofproto_parser = remote_dp.ofproto_parser
|
||||
|
||||
if last_instance:
|
||||
# SRC_TABLE: TUNNEL-port
|
||||
rule = cls_rule(in_port=tunnel_port_no, tun_id=tunnel_key)
|
||||
self.send_flow_del(remote_dp, rule, self.SRC_TABLE,
|
||||
remote_ofproto.OFPFC_DELETE_STRICT,
|
||||
self.SRC_PRI_TUNNEL_PASS, None)
|
||||
|
||||
# SRC_TABLE: TUNNEL-port catch-call drop rule
|
||||
rule = cls_rule(in_port=tunnel_port_no)
|
||||
self.send_flow_del(remote_dp, rule, self.SRC_TABLE,
|
||||
remote_ofproto.OFPFC_DELETE_STRICT,
|
||||
self.SRC_PRI_TUNNEL_DROP, None)
|
||||
|
||||
# TUNNEL_OUT_TABLE: broadcast
|
||||
# tunnel_ports.remove(tunnel_port_no)
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST)
|
||||
tunnel_ports = self._list_tunnel_port(remote_dp,
|
||||
remote_dpids)
|
||||
assert tunnel_port_no not in tunnel_ports
|
||||
actions = [remote_ofproto_parser.OFPActionOutput(port_no)
|
||||
for port_no in tunnel_ports]
|
||||
if not actions:
|
||||
command = remote_dp.ofproto.OFPFC_DELETE_STRICT
|
||||
else:
|
||||
command = remote_dp.ofproto.OFPFC_MODIFY_STRICT
|
||||
resubmit_table = \
|
||||
remote_ofproto_parser.NXActionResubmitTable(
|
||||
in_port=remote_ofproto.OFPP_IN_PORT,
|
||||
table=self.LOCAL_OUT_TABLE)
|
||||
actions.append(resubmit_table)
|
||||
self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
command, self.TUNNEL_OUT_PRI_BROADCAST,
|
||||
actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: unicast
|
||||
# live-migration: there can be more than one (dpid, port_no)
|
||||
# with a given mac address
|
||||
tunnel_ports = self._tunnel_port_with_mac(remote_dp, dpid,
|
||||
network_id, ev.port_no,
|
||||
mac_address)
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac_address)
|
||||
if tunnel_ports:
|
||||
outputs = [remote_ofproto_parser.OFPActionOutput(port_no)
|
||||
for port_no in tunnel_ports]
|
||||
resubmit_table = remote_ofproto_parser.NXActionResubmitTable(
|
||||
in_port=remote_ofproto.OFPP_IN_PORT,
|
||||
table=self.LOCAL_OUT_TABLE)
|
||||
actions = outputs + [resubmit_table]
|
||||
self.send_flow_mod(remote_dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
remote_ofproto.OFPFC_ADD,
|
||||
self.TUNNEL_OUT_PRI_MAC, actions)
|
||||
else:
|
||||
self.send_flow_del(remote_dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
remote_ofproto.OFPFC_DELETE_STRICT,
|
||||
self.TUNNEL_OUT_PRI_MAC, tunnel_port_no)
|
||||
|
||||
# TODO:XXX multicast
|
||||
|
||||
def _get_vm_ports(self, dpid):
|
||||
ports = collections.defaultdict(list)
|
||||
for port in self.nw.get_ports(dpid):
|
||||
if port.network_id in RESERVED_NETWORK_IDS:
|
||||
continue
|
||||
ports[port.network_id].append(port)
|
||||
return ports
|
||||
|
||||
def _tunnel_port_add(self, ev):
|
||||
dpid = ev.dpid
|
||||
dp = self.dpset.get(dpid)
|
||||
ofproto = dp.ofproto
|
||||
ofproto_parser = dp.ofproto_parser
|
||||
remote_dpid = ev.remote_dpid
|
||||
|
||||
local_ports = self._get_vm_ports(dpid)
|
||||
remote_ports = self._get_vm_ports(remote_dpid)
|
||||
|
||||
# SRC_TABLE: TUNNEL-port catch-call drop rule
|
||||
# ingress flow from this tunnel port: remote -> tunnel port
|
||||
# drop if unknown tunnel_key
|
||||
rule = cls_rule(in_port=ev.port_no)
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD,
|
||||
self.SRC_PRI_TUNNEL_DROP, [])
|
||||
|
||||
# SRC_TABLE: TUNNEL-port: pass if known tunnel_key
|
||||
for network_id in local_ports:
|
||||
try:
|
||||
tunnel_key = self.tunnels.get_key(network_id)
|
||||
except tunnels.TunnelKeyNotFound:
|
||||
continue
|
||||
if network_id not in remote_ports:
|
||||
continue
|
||||
|
||||
rule = cls_rule(in_port=ev.port_no, tun_id=tunnel_key)
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE)
|
||||
actions = [resubmit_table]
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE, ofproto.OFPFC_ADD,
|
||||
self.SRC_PRI_TUNNEL_PASS, actions)
|
||||
|
||||
# egress flow into this tunnel port: vm port -> tunnel port -> remote
|
||||
for network_id in local_ports:
|
||||
try:
|
||||
tunnel_key = self.tunnels.get_key(network_id)
|
||||
except tunnels.TunnelKeyNotFound:
|
||||
continue
|
||||
ports = remote_ports.get(network_id)
|
||||
if ports is None:
|
||||
continue
|
||||
|
||||
# TUNNEL_OUT_TABLE: unicast
|
||||
for port in ports:
|
||||
if port.mac_address is None:
|
||||
continue
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=port.mac_address)
|
||||
output = ofproto_parser.OFPActionOutput(ev.port_no)
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE)
|
||||
actions = [output, resubmit_table]
|
||||
self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
ofproto.OFPFC_ADD, self.TUNNEL_OUT_PRI_MAC,
|
||||
actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: broadcast
|
||||
remote_dpids = self.nw.get_dpids(network_id)
|
||||
remote_dpids.remove(dpid)
|
||||
|
||||
rule = cls_rule(tun_id=tunnel_key, dl_dst=mac.BROADCAST)
|
||||
tunnel_ports = self._list_tunnel_port(dp, remote_dpids)
|
||||
if ev.port_no not in tunnel_ports:
|
||||
tunnel_ports.append(ev.port_no)
|
||||
actions = [ofproto_parser.OFPActionOutput(port_no)
|
||||
for port_no in tunnel_ports]
|
||||
resubmit_table = ofproto_parser.NXActionResubmitTable(
|
||||
in_port=ofproto.OFPP_IN_PORT, table=self.LOCAL_OUT_TABLE)
|
||||
actions.append(resubmit_table)
|
||||
if len(tunnel_ports) == 1:
|
||||
command = ofproto.OFPFC_ADD
|
||||
else:
|
||||
command = ofproto.OFPFC_MODIFY_STRICT
|
||||
self.send_flow_mod(dp, rule, self.TUNNEL_OUT_TABLE,
|
||||
command, self.TUNNEL_OUT_PRI_BROADCAST, actions)
|
||||
|
||||
# TUNNEL_OUT_TABLE: multicast TODO:XXX
|
||||
|
||||
def _tunnel_port_del(self, ev):
|
||||
# almost nothing to do because all flow related to this tunnel port
|
||||
# should be handled by self._vm_port_del() as tunnel port deletion
|
||||
# follows vm port deletion.
|
||||
# the tunnel port is deleted if and only if no instance of same
|
||||
# tenants resides in both nodes of tunnel end points.
|
||||
self.logger.debug('tunnel_port_del %s', ev)
|
||||
dp = self.dpset.get(ev.dpid)
|
||||
|
||||
# SRC_TABLE: TUNNEL-port catch-all drop rule
|
||||
rule = cls_rule(in_port=ev.port_no)
|
||||
self.send_flow_mod(dp, rule, self.SRC_TABLE,
|
||||
dp.ofproto.OFPFC_DELETE_STRICT,
|
||||
self.SRC_PRI_TUNNEL_DROP, [])
|
||||
|
||||
@handler.set_ev_handler(PortSet.EventTunnelKeyDel)
|
||||
def tunnel_key_del_handler(self, ev):
|
||||
self.logger.debug('tunnel_key_del ev %s', ev)
|
||||
|
||||
@handler.set_ev_handler(PortSet.EventVMPort)
|
||||
def vm_port_handler(self, ev):
|
||||
self.logger.debug('vm_port ev %s', ev)
|
||||
if ev.add_del:
|
||||
self._vm_port_add(ev)
|
||||
else:
|
||||
self._vm_port_del(ev)
|
||||
|
||||
@handler.set_ev_handler(PortSet.EventTunnelPort)
|
||||
def tunnel_port_handler(self, ev):
|
||||
self.logger.debug('tunnel_port ev %s', ev)
|
||||
if ev.add_del:
|
||||
self._tunnel_port_add(ev)
|
||||
else:
|
||||
self._tunnel_port_del(ev)
|
||||
|
||||
@handler.set_ev_handler(ofp_event.EventOFPPacketIn)
|
||||
def packet_in_handler(self, ev):
|
||||
# for debug
|
||||
msg = ev.msg
|
||||
self.logger.debug('packet in ev %s msg %s', ev, ev.msg)
|
||||
if msg.buffer_id != msg.datapath.ofproto.OFP_NO_BUFFER:
|
||||
msg.datapath.send_packet_out(msg.buffer_id, msg.in_port, [])
|
||||
@ -56,7 +56,7 @@ class GUIServerController(ControllerBase):
|
||||
path = "%s/html/" % PATH
|
||||
self.static_app = DirectoryApp(path)
|
||||
|
||||
@route('topology', '/{filename:[^/]*}')
|
||||
@route('topology', '/{filename:.*}')
|
||||
def static_handler(self, req, **kwargs):
|
||||
if kwargs['filename']:
|
||||
req.path_info = kwargs['filename']
|
||||
|
||||
@ -16,37 +16,22 @@
|
||||
|
||||
# client for ryu.app.ofctl.service
|
||||
|
||||
import numbers
|
||||
|
||||
from ryu.base import app_manager
|
||||
from . import event
|
||||
|
||||
|
||||
def get_datapath(app, dpid=None):
|
||||
def get_datapath(app, dpid):
|
||||
"""
|
||||
Get datapath object by dpid.
|
||||
|
||||
:param app: Client RyuApp instance
|
||||
:param dpid: Datapath ID (int type) or None to get all datapath objects
|
||||
:param dpid: Datapath-id (in integer)
|
||||
|
||||
Returns a object of datapath, a list of datapath objects when no dpid
|
||||
given or None when error.
|
||||
|
||||
Raises an exception if any of the given values is invalid.
|
||||
|
||||
Example::
|
||||
|
||||
# ...(snip)...
|
||||
import ryu.app.ofctl.api as ofctl_api
|
||||
|
||||
|
||||
class MyApp(app_manager.RyuApp):
|
||||
|
||||
def _my_handler(self, ev):
|
||||
# Get all datapath objects
|
||||
result = ofctl_api.get_datapath(self)
|
||||
|
||||
# Get the datapath object which has the given dpid
|
||||
result = ofctl_api.get_datapath(self, dpid=1)
|
||||
Returns None on error.
|
||||
"""
|
||||
assert isinstance(dpid, numbers.Integral)
|
||||
return app.send_request(event.GetDatapathRequest(dpid=dpid))()
|
||||
|
||||
|
||||
@ -70,19 +55,12 @@ def send_msg(app, msg, reply_cls=None, reply_multi=False):
|
||||
|
||||
Example::
|
||||
|
||||
# ...(snip)...
|
||||
import ryu.app.ofctl.api as ofctl_api
|
||||
import ryu.app.ofctl.api as api
|
||||
|
||||
|
||||
class MyApp(app_manager.RyuApp):
|
||||
|
||||
def _my_handler(self, ev):
|
||||
# ...(snip)...
|
||||
msg = parser.OFPPortDescStatsRequest(datapath=datapath)
|
||||
result = ofctl_api.send_msg(
|
||||
self, msg,
|
||||
reply_cls=parser.OFPPortDescStatsReply,
|
||||
reply_multi=True)
|
||||
msg = parser.OFPPortDescStatsRequest(datapath=datapath)
|
||||
result = api.send_msg(self, msg,
|
||||
reply_cls=parser.OFPPortDescStatsReply,
|
||||
reply_multi=True)
|
||||
"""
|
||||
return app.send_request(event.SendMsgRequest(msg=msg,
|
||||
reply_cls=reply_cls,
|
||||
|
||||
@ -33,8 +33,8 @@ class _ReplyBase(event.EventReplyBase):
|
||||
# get datapath
|
||||
|
||||
class GetDatapathRequest(_RequestBase):
|
||||
def __init__(self, dpid=None):
|
||||
assert dpid is None or isinstance(dpid, numbers.Integral)
|
||||
def __init__(self, dpid):
|
||||
assert isinstance(dpid, numbers.Integral)
|
||||
super(GetDatapathRequest, self).__init__()
|
||||
self.dpid = dpid
|
||||
|
||||
|
||||
@ -64,22 +64,6 @@ class OfctlService(app_manager.RyuApp):
|
||||
self.unobserve_event(ev_cls)
|
||||
self.logger.debug('ofctl: stop observing %s', ev_cls)
|
||||
|
||||
def _cancel(self, info, barrier_xid, exception):
|
||||
xid = info.barriers.pop(barrier_xid)
|
||||
req = info.xids.pop(xid)
|
||||
msg = req.msg
|
||||
datapath = msg.datapath
|
||||
parser = datapath.ofproto_parser
|
||||
is_barrier = isinstance(msg, parser.OFPBarrierRequest)
|
||||
|
||||
info.results.pop(xid)
|
||||
|
||||
if not is_barrier and req.reply_cls is not None:
|
||||
self._unobserve_msg(req.reply_cls)
|
||||
|
||||
self.logger.error('failed to send message <%s>', req.msg)
|
||||
self.reply_to_request(req, event.Reply(exception=exception))
|
||||
|
||||
@staticmethod
|
||||
def _is_error(msg):
|
||||
return (ofp_event.ofp_msg_to_ev_cls(type(msg)) ==
|
||||
@ -95,11 +79,6 @@ class OfctlService(app_manager.RyuApp):
|
||||
self.logger.debug('add dpid %s datapath %s new_info %s old_info %s',
|
||||
id, datapath, new_info, old_info)
|
||||
self._switches[id] = new_info
|
||||
if old_info:
|
||||
old_info.datapath.close()
|
||||
for xid in list(old_info.barriers):
|
||||
self._cancel(
|
||||
old_info, xid, exception.InvalidDatapath(result=id))
|
||||
|
||||
@set_ev_cls(ofp_event.EventOFPStateChange, DEAD_DISPATCHER)
|
||||
def _handle_dead(self, ev):
|
||||
@ -115,25 +94,23 @@ class OfctlService(app_manager.RyuApp):
|
||||
if info.datapath is datapath:
|
||||
self.logger.debug('forget info %s', info)
|
||||
self._switches.pop(id)
|
||||
for xid in list(info.barriers):
|
||||
self._cancel(info, xid, exception.InvalidDatapath(result=id))
|
||||
|
||||
@set_ev_cls(event.GetDatapathRequest, MAIN_DISPATCHER)
|
||||
def _handle_get_datapath(self, req):
|
||||
result = None
|
||||
if req.dpid is None:
|
||||
result = [v.datapath for v in self._switches.values()]
|
||||
else:
|
||||
if req.dpid in self._switches:
|
||||
result = self._switches[req.dpid].datapath
|
||||
self.reply_to_request(req, event.Reply(result=result))
|
||||
id = req.dpid
|
||||
assert isinstance(id, numbers.Integral)
|
||||
try:
|
||||
datapath = self._switches[id].datapath
|
||||
except KeyError:
|
||||
datapath = None
|
||||
self.logger.debug('dpid %s -> datapath %s', id, datapath)
|
||||
rep = event.Reply(result=datapath)
|
||||
self.reply_to_request(req, rep)
|
||||
|
||||
@set_ev_cls(event.SendMsgRequest, MAIN_DISPATCHER)
|
||||
def _handle_send_msg(self, req):
|
||||
msg = req.msg
|
||||
datapath = msg.datapath
|
||||
parser = datapath.ofproto_parser
|
||||
is_barrier = isinstance(msg, parser.OFPBarrierRequest)
|
||||
|
||||
try:
|
||||
si = self._switches[datapath.id]
|
||||
@ -144,40 +121,28 @@ class OfctlService(app_manager.RyuApp):
|
||||
self.reply_to_request(req, rep)
|
||||
return
|
||||
|
||||
def _store_xid(xid, barrier_xid):
|
||||
assert xid not in si.results
|
||||
assert xid not in si.xids
|
||||
assert barrier_xid not in si.barriers
|
||||
si.results[xid] = []
|
||||
si.xids[xid] = req
|
||||
si.barriers[barrier_xid] = xid
|
||||
if req.reply_cls is not None:
|
||||
self._observe_msg(req.reply_cls)
|
||||
|
||||
if is_barrier:
|
||||
barrier = msg
|
||||
datapath.set_xid(barrier)
|
||||
_store_xid(barrier.xid, barrier.xid)
|
||||
else:
|
||||
if req.reply_cls is not None:
|
||||
self._observe_msg(req.reply_cls)
|
||||
datapath.set_xid(msg)
|
||||
barrier = datapath.ofproto_parser.OFPBarrierRequest(datapath)
|
||||
datapath.set_xid(barrier)
|
||||
_store_xid(msg.xid, barrier.xid)
|
||||
if not datapath.send_msg(msg):
|
||||
return self._cancel(
|
||||
si, barrier.xid,
|
||||
exception.InvalidDatapath(result=datapath.id))
|
||||
datapath.set_xid(msg)
|
||||
xid = msg.xid
|
||||
barrier = datapath.ofproto_parser.OFPBarrierRequest(datapath)
|
||||
datapath.set_xid(barrier)
|
||||
barrier_xid = barrier.xid
|
||||
assert xid not in si.results
|
||||
assert xid not in si.xids
|
||||
assert barrier_xid not in si.barriers
|
||||
si.results[xid] = []
|
||||
si.xids[xid] = req
|
||||
si.barriers[barrier_xid] = xid
|
||||
|
||||
if not datapath.send_msg(barrier):
|
||||
return self._cancel(
|
||||
si, barrier.xid,
|
||||
exception.InvalidDatapath(result=datapath.id))
|
||||
datapath.send_msg(msg)
|
||||
datapath.send_msg(barrier)
|
||||
|
||||
@set_ev_cls(ofp_event.EventOFPBarrierReply, MAIN_DISPATCHER)
|
||||
def _handle_barrier(self, ev):
|
||||
msg = ev.msg
|
||||
datapath = msg.datapath
|
||||
parser = datapath.ofproto_parser
|
||||
try:
|
||||
si = self._switches[datapath.id]
|
||||
except KeyError:
|
||||
@ -190,12 +155,9 @@ class OfctlService(app_manager.RyuApp):
|
||||
return
|
||||
result = si.results.pop(xid)
|
||||
req = si.xids.pop(xid)
|
||||
is_barrier = isinstance(req.msg, parser.OFPBarrierRequest)
|
||||
if req.reply_cls is not None and not is_barrier:
|
||||
if req.reply_cls is not None:
|
||||
self._unobserve_msg(req.reply_cls)
|
||||
if is_barrier and req.reply_cls == parser.OFPBarrierReply:
|
||||
rep = event.Reply(result=ev.msg)
|
||||
elif any(self._is_error(r) for r in result):
|
||||
if any(self._is_error(r) for r in result):
|
||||
rep = event.Reply(exception=exception.OFError(result=result))
|
||||
elif req.reply_multi:
|
||||
rep = event.Reply(result=result)
|
||||
@ -223,7 +185,7 @@ class OfctlService(app_manager.RyuApp):
|
||||
self.logger.error('unknown error xid %s', msg.xid)
|
||||
return
|
||||
if ((not isinstance(ev, ofp_event.EventOFPErrorMsg)) and
|
||||
(req.reply_cls is None or not isinstance(ev.msg, req.reply_cls))):
|
||||
(req.reply_cls is None or not isinstance(ev.msg, req.reply_cls))):
|
||||
self.logger.error('unexpected reply %s for xid %s', ev, msg.xid)
|
||||
return
|
||||
try:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
446
ryu/app/quantum_adapter.py
Normal file
446
ryu/app/quantum_adapter.py
Normal file
@ -0,0 +1,446 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Listen OpenFlow port status change notifications from switches. Consult
|
||||
ovsdb to retrieve the corresponding port uuid. Notify relevant parties,
|
||||
including quantum (via Ryu plug-in) and Ryu applications. (via Ryu Events)
|
||||
"""
|
||||
|
||||
import traceback
|
||||
|
||||
try:
|
||||
from neutronclient import client as q_client
|
||||
from neutronclient.common import exceptions as q_exc
|
||||
from neutronclient.common.exceptions import (NeutronClientException as
|
||||
client_exc)
|
||||
from neutronclient.v2_0 import client as q_clientv2
|
||||
except ImportError:
|
||||
from quantumclient import client as q_client
|
||||
from quantumclient.common import exceptions as q_exc
|
||||
from quantumclient.common.exceptions import (QuantumClientException as
|
||||
client_exc)
|
||||
from quantumclient.v2_0 import client as q_clientv2
|
||||
|
||||
from ryu.app import conf_switch_key as cs_key
|
||||
from ryu.app import rest_nw_id
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import (conf_switch,
|
||||
dpset,
|
||||
handler,
|
||||
network)
|
||||
from ryu import exception as ryu_exc
|
||||
from ryu.lib import dpid as dpid_lib
|
||||
from ryu.lib import mac as mac_lib
|
||||
from ryu.lib import quantum_ifaces
|
||||
from ryu.lib.ovs import bridge
|
||||
from ryu.lib.quantum_ifaces import QuantumIfaces
|
||||
|
||||
|
||||
def _get_auth_token(CONF, logger):
|
||||
httpclient = q_client.HTTPClient(
|
||||
username=CONF.neutron_admin_username,
|
||||
tenant_name=CONF.neutron_admin_tenant_name,
|
||||
password=CONF.neutron_admin_password,
|
||||
auth_url=CONF.neutron_admin_auth_url,
|
||||
timeout=CONF.neutron_url_timeout,
|
||||
auth_strategy=CONF.neutron_auth_strategy)
|
||||
try:
|
||||
httpclient.authenticate()
|
||||
except (q_exc.Unauthorized, q_exc.Forbidden, q_exc.EndpointNotFound) as e:
|
||||
logger.error("authentication failure: %s", e)
|
||||
return None
|
||||
# logger.debug("_get_auth_token: token=%s", httpclient.auth_token)
|
||||
return httpclient.auth_token
|
||||
|
||||
|
||||
def _get_quantum_client(CONF, token):
|
||||
if token:
|
||||
my_client = q_clientv2.Client(
|
||||
endpoint_url=CONF.neutron_url,
|
||||
token=token, timeout=CONF.neutron_url_timeout)
|
||||
else:
|
||||
my_client = q_clientv2.Client(
|
||||
endpoint_url=CONF.neutron_url,
|
||||
auth_strategy=None, timeout=CONF.neutron_url_timeout)
|
||||
return my_client
|
||||
|
||||
|
||||
class OVSPort(object):
|
||||
PORT_ERROR = -1
|
||||
PORT_UNKNOWN = 0
|
||||
PORT_GATEWAY = 1
|
||||
PORT_VETH_GATEWAY = 2
|
||||
PORT_GUEST = 3
|
||||
PORT_TUNNEL = 4
|
||||
|
||||
# extra-ids: 'attached-mac', 'iface-id', 'iface-status', 'vm-uuid'
|
||||
def __init__(self, ofport, port_name):
|
||||
super(OVSPort, self).__init__()
|
||||
self.ofport = ofport
|
||||
self.name = port_name
|
||||
self.type = None
|
||||
self.ext_ids = {}
|
||||
self.options = {}
|
||||
|
||||
def update(self, port):
|
||||
self.__dict__.update((key, port[key]) for key
|
||||
in ['name', 'ofport', 'type']
|
||||
if key in port)
|
||||
if 'external_ids' in port:
|
||||
self.ext_ids = dict(port['external_ids'])
|
||||
if 'options' in port:
|
||||
self.options = dict(port['options'])
|
||||
|
||||
def get_port_type(self):
|
||||
if not isinstance(self.ofport, int):
|
||||
return self.PORT_ERROR
|
||||
if self.type == 'internal' and 'iface-id' in self.ext_ids:
|
||||
return self.PORT_GATEWAY
|
||||
if self.type == '' and 'iface-id' in self.ext_ids:
|
||||
return self.PORT_VETH_GATEWAY
|
||||
if (self.type == 'gre' and 'local_ip' in self.options and
|
||||
'remote_ip' in self.options):
|
||||
return self.PORT_TUNNEL
|
||||
if self.type == '' and 'vm-uuid' in self.ext_ids:
|
||||
return self.PORT_GUEST
|
||||
return self.PORT_UNKNOWN
|
||||
|
||||
def __str__(self):
|
||||
return "type=%s ofport=%s name=%s, ext_ids=%s options=%s" % (
|
||||
self.type, self.ofport, self.name, self.ext_ids, self.options)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (other is not None and
|
||||
self.ofport == other.ofport and
|
||||
self.type == other.type and
|
||||
self.ext_ids == other.ext_ids and
|
||||
self.options == other.options)
|
||||
|
||||
|
||||
class OVSSwitch(object):
|
||||
def __init__(self, CONF, dpid, nw, ifaces, logger):
|
||||
# TODO: clean up
|
||||
self.CONF = CONF
|
||||
self.dpid = dpid
|
||||
self.network_api = nw
|
||||
self.ifaces = ifaces
|
||||
self.logger = logger
|
||||
self._q_api = None # lazy initialization
|
||||
self.ctrl_addr = self.CONF.neutron_controller_addr
|
||||
if not self.ctrl_addr:
|
||||
raise ValueError('option neutron_controler_addr must be speicfied')
|
||||
|
||||
self.ovsdb_addr = None
|
||||
self.tunnel_ip = None
|
||||
|
||||
self.ovs_bridge = None
|
||||
self.ports = {} # port_no -> OVSPort
|
||||
|
||||
super(OVSSwitch, self).__init__()
|
||||
|
||||
@property
|
||||
def q_api(self):
|
||||
if self._q_api is None:
|
||||
token = None
|
||||
if self.CONF.neutron_auth_strategy:
|
||||
token = _get_auth_token(self.CONF, self.logger)
|
||||
self._q_api = _get_quantum_client(self.CONF, token)
|
||||
return self._q_api
|
||||
|
||||
def set_ovsdb_addr(self, dpid, ovsdb_addr):
|
||||
# easy check if the address format valid
|
||||
self.logger.debug('set_ovsdb_addr dpid %s ovsdb_addr %s',
|
||||
dpid_lib.dpid_to_str(dpid), ovsdb_addr)
|
||||
_proto, _host, _port = ovsdb_addr.split(':')
|
||||
|
||||
old_address = self.ovsdb_addr
|
||||
if old_address == ovsdb_addr:
|
||||
return
|
||||
if ovsdb_addr is None:
|
||||
# TODO: clean up this ovs switch
|
||||
if self.ovs_bridge:
|
||||
self.ovs_bridge.del_controller()
|
||||
self.ovs_bridge = None
|
||||
return
|
||||
self.ovsdb_addr = ovsdb_addr
|
||||
if self.ovs_bridge is None:
|
||||
self.logger.debug('ovsdb: adding ports %s', self.ports)
|
||||
ovs_bridge = bridge.OVSBridge(self.CONF, dpid, ovsdb_addr)
|
||||
self.ovs_bridge = ovs_bridge
|
||||
ovs_bridge.init()
|
||||
# TODO: for multi-controller
|
||||
# not overwrite controllers, but append this controller
|
||||
ovs_bridge.set_controller([self.ctrl_addr])
|
||||
for port in self.ports.values():
|
||||
self.logger.debug('adding port %s', port)
|
||||
self.update_port(port.ofport, port.name, True)
|
||||
|
||||
def _update_external_port(self, port, add=True):
|
||||
if add:
|
||||
self.network_api.update_port(rest_nw_id.NW_ID_EXTERNAL,
|
||||
self.dpid, port.ofport)
|
||||
else:
|
||||
self.network_api.remove_port(rest_nw_id.NW_ID_EXTERNAL,
|
||||
self.dpid, port.ofport)
|
||||
|
||||
def _update_vif_port(self, port, add=True):
|
||||
# When ovs port is updated, the corresponding network id may or
|
||||
# may not exist because the order between the notification of
|
||||
# ovs port deletion via OVSDB protocol and the notification
|
||||
# network id/port deletion via REST from quantum plugin
|
||||
# isn't deterministic.
|
||||
self.logger.debug("_update_vif_port: %s %s", port, add)
|
||||
iface_id = port.ext_ids.get('iface-id')
|
||||
if iface_id is None:
|
||||
return
|
||||
try:
|
||||
network_id = self.ifaces.get_key(iface_id,
|
||||
QuantumIfaces.KEY_NETWORK_ID)
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
if not add:
|
||||
try:
|
||||
self.network_api.remove_port(network_id,
|
||||
self.dpid, port.ofport)
|
||||
except (network.NetworkNotFound, ryu_exc.PortNotFound) as e:
|
||||
self.logger.debug('remove_port %s', traceback.format_exc())
|
||||
ports = self.ifaces.get_key(iface_id, QuantumIfaces.KEY_PORTS)
|
||||
other_ovs_ports = None
|
||||
for p in ports:
|
||||
dpid = p.get(QuantumIfaces.SUBKEY_DATAPATH_ID)
|
||||
if dpid is None:
|
||||
continue
|
||||
if dpid != self.dpid:
|
||||
continue
|
||||
|
||||
other_ovs_ports = self.ifaces.del_key(iface_id,
|
||||
QuantumIfaces.KEY_PORTS,
|
||||
p)
|
||||
if other_ovs_ports:
|
||||
# When live-migration, one of the two OVS ports is deleted.
|
||||
return
|
||||
|
||||
port_data = {
|
||||
'status': 'DOWN'
|
||||
}
|
||||
body = {'port': port_data}
|
||||
# self.logger.debug("port-body = %s", body)
|
||||
|
||||
try:
|
||||
self.q_api.update_port(port.ext_ids['iface-id'], body)
|
||||
except (q_exc.ConnectionFailed, client_exc) as e:
|
||||
self.logger.error("quantum update port failed: %s", e)
|
||||
# TODO: When authentication failure occurred,
|
||||
# it should get auth token again
|
||||
return
|
||||
|
||||
# update {network, port, mac}
|
||||
try:
|
||||
self.network_api.update_network(network_id)
|
||||
self.network_api.update_port(network_id, self.dpid, port.ofport)
|
||||
mac = port.ext_ids.get('attached-mac')
|
||||
if mac:
|
||||
self.network_api.update_mac(network_id, self.dpid, port.ofport,
|
||||
mac_lib.haddr_to_bin(mac))
|
||||
except (network.NetworkNotFound, ryu_exc.PortNotFound) as e:
|
||||
self.logger.debug('update network/port/mac %s',
|
||||
traceback.format_exc())
|
||||
|
||||
def update_port(self, port_no, port_name, add):
|
||||
self.logger.debug('update_port port_no %d %s %s', port_no, port_name,
|
||||
add)
|
||||
assert port_name is not None
|
||||
old_port = self.ports.get(port_no)
|
||||
if not add:
|
||||
new_port = None
|
||||
self.ports.pop(port_no, None)
|
||||
else:
|
||||
new_port = OVSPort(port_no, port_name)
|
||||
if self.ovs_bridge:
|
||||
port_cfg = self.ovs_bridge.get_quantum_ports(port_name)
|
||||
if port_cfg:
|
||||
if 'ofport' not in port_cfg or not port_cfg['ofport']:
|
||||
port_cfg['ofport'] = port_no
|
||||
elif port_cfg['ofport'] != port_no:
|
||||
self.logger.warn('inconsistent port_no: %d port_cfg '
|
||||
'%s', port_no, port_cfg)
|
||||
return
|
||||
if port_cfg['name'] != port_name:
|
||||
self.logger.warn('inconsistent port_name: %s '
|
||||
'port_cfg %s', port_name, port_cfg)
|
||||
return
|
||||
new_port.update(port_cfg)
|
||||
|
||||
self.ports[port_no] = new_port
|
||||
iface_id = new_port.ext_ids.get('iface-id')
|
||||
if iface_id:
|
||||
p = {QuantumIfaces.SUBKEY_DATAPATH_ID: self.dpid,
|
||||
QuantumIfaces.SUBKEY_OFPORT: port_no,
|
||||
QuantumIfaces.SUBKEY_NAME: port_name}
|
||||
self.ifaces.update_key(iface_id, QuantumIfaces.KEY_PORTS, p)
|
||||
|
||||
if old_port == new_port:
|
||||
return
|
||||
|
||||
if not new_port:
|
||||
port_type = old_port.get_port_type()
|
||||
if port_type == OVSPort.PORT_ERROR:
|
||||
return
|
||||
elif port_type == OVSPort.PORT_UNKNOWN:
|
||||
# self.logger.info("delete external port: %s", old_port)
|
||||
self._update_external_port(old_port, add=False)
|
||||
else:
|
||||
# self.logger.info("delete port: %s", old_port)
|
||||
if port_type != OVSPort.PORT_TUNNEL:
|
||||
self._update_vif_port(old_port, add=False)
|
||||
return
|
||||
|
||||
if new_port.ofport == -1:
|
||||
return
|
||||
if not old_port or old_port.ofport == -1:
|
||||
port_type = new_port.get_port_type()
|
||||
if port_type == OVSPort.PORT_ERROR:
|
||||
return
|
||||
elif port_type == OVSPort.PORT_UNKNOWN:
|
||||
# self.logger.info("create external port: %s", new_port)
|
||||
self._update_external_port(new_port)
|
||||
else:
|
||||
# self.logger.info("create port: %s", new_port)
|
||||
if port_type != OVSPort.PORT_TUNNEL:
|
||||
self._update_vif_port(new_port)
|
||||
return
|
||||
if new_port.get_port_type() in (OVSPort.PORT_GUEST,
|
||||
OVSPort.PORT_GATEWAY,
|
||||
OVSPort.PORT_VETH_GATEWAY):
|
||||
# self.logger.info("update port: %s", new_port)
|
||||
self._update_vif_port(new_port)
|
||||
|
||||
|
||||
class QuantumAdapter(app_manager.RyuApp):
|
||||
_CONTEXTS = {
|
||||
'conf_switch': conf_switch.ConfSwitchSet,
|
||||
'network': network.Network,
|
||||
'quantum_ifaces': quantum_ifaces.QuantumIfaces,
|
||||
}
|
||||
|
||||
def __init__(self, *_args, **kwargs):
|
||||
super(QuantumAdapter, self).__init__()
|
||||
|
||||
self.cs = kwargs['conf_switch']
|
||||
self.nw = kwargs['network']
|
||||
self.ifaces = kwargs['quantum_ifaces']
|
||||
self.dps = {}
|
||||
|
||||
for network_id in rest_nw_id.RESERVED_NETWORK_IDS:
|
||||
if network_id == rest_nw_id.NW_ID_UNKNOWN:
|
||||
continue
|
||||
self.nw.update_network(network_id)
|
||||
|
||||
def _get_ovs_switch(self, dpid, create=True):
|
||||
ovs_switch = self.dps.get(dpid)
|
||||
if not ovs_switch:
|
||||
if create:
|
||||
ovs_switch = OVSSwitch(self.CONF, dpid, self.nw, self.ifaces,
|
||||
self.logger)
|
||||
self.dps[dpid] = ovs_switch
|
||||
else:
|
||||
self.logger.debug('ovs switch %s is already known', dpid)
|
||||
return ovs_switch
|
||||
|
||||
def _port_handler(self, dpid, port_no, port_name, add):
|
||||
ovs_switch = self._get_ovs_switch(dpid)
|
||||
if ovs_switch:
|
||||
ovs_switch.update_port(port_no, port_name, add)
|
||||
else:
|
||||
self.logger.warn('unknown ovs switch %s %s %s %s\n',
|
||||
dpid, port_no, port_name, add)
|
||||
|
||||
@handler.set_ev_cls(dpset.EventDP)
|
||||
def dp_handler(self, ev):
|
||||
dpid = ev.dp.id
|
||||
ovs_switch = self._get_ovs_switch(dpid)
|
||||
if not ovs_switch:
|
||||
return
|
||||
|
||||
if ev.enter:
|
||||
for port in ev.ports:
|
||||
ovs_switch.update_port(port.port_no, port.name, True)
|
||||
else:
|
||||
# When dp leaving, we don't delete ports because OF connection
|
||||
# can be disconnected for some reason.
|
||||
# TODO: configuration needed to tell that this dp is really
|
||||
# removed.
|
||||
self.dps.pop(dpid, None)
|
||||
|
||||
@handler.set_ev_cls(dpset.EventPortAdd)
|
||||
def port_add_handler(self, ev):
|
||||
port = ev.port
|
||||
name = port.name.rstrip('\0')
|
||||
self._port_handler(ev.dp.id, port.port_no, name, True)
|
||||
|
||||
@handler.set_ev_cls(dpset.EventPortDelete)
|
||||
def port_del_handler(self, ev):
|
||||
port = ev.port
|
||||
name = port.name.rstrip('\0')
|
||||
self._port_handler(ev.dp.id, port.port_no, name, False)
|
||||
|
||||
def _conf_switch_set_ovsdb_addr(self, dpid, value):
|
||||
ovs_switch = self._get_ovs_switch(dpid)
|
||||
ovs_switch.set_ovsdb_addr(dpid, value)
|
||||
|
||||
def _conf_switch_del_ovsdb_addr(self, dpid):
|
||||
ovs_switch = self._get_ovs_switch(dpid, False)
|
||||
if ovs_switch:
|
||||
ovs_switch.set_ovsdb_addr(dpid, None)
|
||||
|
||||
@handler.set_ev_cls(conf_switch.EventConfSwitchSet)
|
||||
def conf_switch_set_handler(self, ev):
|
||||
self.logger.debug("conf_switch set: %s", ev)
|
||||
if ev.key == cs_key.OVSDB_ADDR:
|
||||
self._conf_switch_set_ovsdb_addr(ev.dpid, ev.value)
|
||||
else:
|
||||
self.logger.debug("unknown event: %s", ev)
|
||||
|
||||
@handler.set_ev_cls(conf_switch.EventConfSwitchDel)
|
||||
def conf_switch_del_handler(self, ev):
|
||||
self.logger.debug("conf_switch del: %s", ev)
|
||||
if ev.key == cs_key.OVSDB_ADDR:
|
||||
self._conf_switch_del_ovsdb_addr(ev.dpid)
|
||||
else:
|
||||
self.logger.debug("unknown event: %s", ev)
|
||||
|
||||
@handler.set_ev_cls(quantum_ifaces.EventQuantumIfaceSet)
|
||||
def quantum_iface_set_handler(self, ev):
|
||||
if ev.key != quantum_ifaces.QuantumIfaces.KEY_NETWORK_ID:
|
||||
# self.logger.debug("unknown key %s", ev.key)
|
||||
return
|
||||
iface_id = ev.iface_id
|
||||
try:
|
||||
ports = self.ifaces.get_key(iface_id, QuantumIfaces.KEY_PORTS)
|
||||
except KeyError:
|
||||
return
|
||||
for p in ports:
|
||||
try:
|
||||
dpid = p[QuantumIfaces.SUBKEY_DATAPATH_ID]
|
||||
ofport = p[QuantumIfaces.SUBKEY_OFPORT]
|
||||
port_name = p[QuantumIfaces.SUBKEY_NAME]
|
||||
except KeyError:
|
||||
continue
|
||||
ovs_switch = self._get_ovs_switch(dpid, False)
|
||||
if ovs_switch:
|
||||
ovs_switch.update_port(ofport, port_name, True)
|
||||
270
ryu/app/rest.py
Normal file
270
ryu/app/rest.py
Normal file
@ -0,0 +1,270 @@
|
||||
# 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 basic set of REST API.
|
||||
|
||||
- Network registration
|
||||
- End-point port management
|
||||
- OpenFlow port number
|
||||
- MAC address (for anti-spoofing)
|
||||
|
||||
Used by OpenStack Ryu plug-in.
|
||||
"""
|
||||
|
||||
import json
|
||||
from webob import Response
|
||||
|
||||
from ryu.app import wsgi as app_wsgi
|
||||
from ryu.app.wsgi import ControllerBase, WSGIApplication
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import network
|
||||
from ryu.exception import NetworkNotFound, NetworkAlreadyExist
|
||||
from ryu.exception import PortNotFound, PortAlreadyExist
|
||||
from ryu.lib import dpid as dpid_lib
|
||||
from ryu.lib import mac as mac_lib
|
||||
|
||||
# TODO:XXX
|
||||
# define db interface and store those information into db
|
||||
|
||||
# REST API
|
||||
|
||||
# get the list of networks
|
||||
# GET /v1.0/networks/
|
||||
#
|
||||
# register a new network.
|
||||
# Fail if the network is already registered.
|
||||
# POST /v1.0/networks/{network-id}
|
||||
#
|
||||
# update a new network.
|
||||
# Success as nop even if the network is already registered.
|
||||
#
|
||||
# PUT /v1.0/networks/{network-id}
|
||||
#
|
||||
# remove a network
|
||||
# DELETE /v1.0/networks/{network-id}
|
||||
#
|
||||
# get the list of sets of dpid and port
|
||||
# GET /v1.0/networks/{network-id}/
|
||||
#
|
||||
# register a new set of dpid and port
|
||||
# Fail if the port is already registered.
|
||||
# POST /v1.0/networks/{network-id}/{dpid}_{port-id}
|
||||
#
|
||||
# update a new set of dpid and port
|
||||
# Success as nop even if same port already registered
|
||||
# PUT /v1.0/networks/{network-id}/{dpid}_{port-id}
|
||||
#
|
||||
# remove a set of dpid and port
|
||||
# DELETE /v1.0/networks/{network-id}/{dpid}_{port-id}
|
||||
#
|
||||
# get the list of mac addresses of dpid and port
|
||||
# GET /v1.0/networks/{network-id}/{dpid}_{port-id}/macs/
|
||||
#
|
||||
# register a new mac address for dpid and port
|
||||
# Fail if mac address is already registered or the mac address is used
|
||||
# for other ports of the same network-id
|
||||
# POST /v1.0/networks/{network-id}/{dpid}_{port-id}/macs/{mac}
|
||||
#
|
||||
# update a new mac address for dpid and port
|
||||
# Success as nop even if same mac address is already registered.
|
||||
# For now, changing mac address is not allows as it fails.
|
||||
# PUT /v1.0/networks/{network-id}/{dpid}_{port-id}/macs/{mac}
|
||||
#
|
||||
# For now DELETE /v1.0/networks/{network-id}/{dpid}_{port-id}/macs/{mac}
|
||||
# is not supported. mac address is released when port is deleted.
|
||||
#
|
||||
|
||||
|
||||
class NetworkController(ControllerBase):
|
||||
def __init__(self, req, link, data, **config):
|
||||
super(NetworkController, self).__init__(req, link, data, **config)
|
||||
self.nw = data
|
||||
|
||||
def create(self, req, network_id, **_kwargs):
|
||||
try:
|
||||
self.nw.create_network(network_id)
|
||||
except NetworkAlreadyExist:
|
||||
return Response(status=409)
|
||||
else:
|
||||
return Response(status=200)
|
||||
|
||||
def update(self, req, network_id, **_kwargs):
|
||||
self.nw.update_network(network_id)
|
||||
return Response(status=200)
|
||||
|
||||
def lists(self, req, **_kwargs):
|
||||
body = json.dumps(self.nw.list_networks())
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def delete(self, req, network_id, **_kwargs):
|
||||
try:
|
||||
self.nw.remove_network(network_id)
|
||||
except NetworkNotFound:
|
||||
return Response(status=404)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
|
||||
class PortController(ControllerBase):
|
||||
def __init__(self, req, link, data, **config):
|
||||
super(PortController, self).__init__(req, link, data, **config)
|
||||
self.nw = data
|
||||
|
||||
def create(self, req, network_id, dpid, port_id, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
try:
|
||||
self.nw.create_port(network_id, dpid, port_id)
|
||||
except NetworkNotFound:
|
||||
return Response(status=404)
|
||||
except PortAlreadyExist:
|
||||
return Response(status=409)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def update(self, req, network_id, dpid, port_id, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
try:
|
||||
self.nw.update_port(network_id, dpid, port_id)
|
||||
except NetworkNotFound:
|
||||
return Response(status=404)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def lists(self, req, network_id, **_kwargs):
|
||||
try:
|
||||
body = json.dumps(self.nw.list_ports(network_id))
|
||||
except NetworkNotFound:
|
||||
return Response(status=404)
|
||||
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def delete(self, req, network_id, dpid, port_id, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
try:
|
||||
self.nw.remove_port(network_id, dpid, port_id)
|
||||
except (NetworkNotFound, PortNotFound):
|
||||
return Response(status=404)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
|
||||
class MacController(ControllerBase):
|
||||
def __init__(self, req, link, data, **config):
|
||||
super(MacController, self).__init__(req, link, data, **config)
|
||||
self.nw = data
|
||||
|
||||
def create(self, _req, network_id, dpid, port_id, mac_addr, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
mac_addr = mac_lib.haddr_to_bin(mac_addr)
|
||||
try:
|
||||
self.nw.create_mac(network_id, dpid, port_id, mac_addr)
|
||||
except PortNotFound:
|
||||
return Response(status=404)
|
||||
except network.MacAddressAlreadyExist:
|
||||
return Response(status=409)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def update(self, _req, network_id, dpid, port_id, mac_addr, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
mac_addr = mac_lib.haddr_to_bin(mac_addr)
|
||||
try:
|
||||
self.nw.update_mac(network_id, dpid, port_id, mac_addr)
|
||||
except PortNotFound:
|
||||
return Response(status=404)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def lists(self, _req, network_id, dpid, port_id, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
try:
|
||||
body = json.dumps([mac_lib.haddr_to_str(mac_addr) for mac_addr in
|
||||
self.nw.list_mac(dpid, port_id)])
|
||||
except PortNotFound:
|
||||
return Response(status=404)
|
||||
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
|
||||
class RestAPI(app_manager.RyuApp):
|
||||
_CONTEXTS = {
|
||||
'network': network.Network,
|
||||
'wsgi': WSGIApplication
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RestAPI, self).__init__(*args, **kwargs)
|
||||
self.nw = kwargs['network']
|
||||
wsgi = kwargs['wsgi']
|
||||
mapper = wsgi.mapper
|
||||
|
||||
wsgi.registory['NetworkController'] = self.nw
|
||||
route_name = 'networks'
|
||||
uri = '/v1.0/networks'
|
||||
mapper.connect(route_name, uri,
|
||||
controller=NetworkController, action='lists',
|
||||
conditions=dict(method=['GET', 'HEAD']))
|
||||
|
||||
uri += '/{network_id}'
|
||||
s = mapper.submapper(controller=NetworkController)
|
||||
s.connect(route_name, uri, action='create',
|
||||
conditions=dict(method=['POST']))
|
||||
s.connect(route_name, uri, action='update',
|
||||
conditions=dict(method=['PUT']))
|
||||
s.connect(route_name, uri, action='delete',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
wsgi.registory['PortController'] = self.nw
|
||||
route_name = 'ports'
|
||||
mapper.connect(route_name, uri,
|
||||
controller=PortController, action='lists',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
uri += '/{dpid}_{port_id}'
|
||||
requirements = {'dpid': dpid_lib.DPID_PATTERN,
|
||||
'port_id': app_wsgi.DIGIT_PATTERN}
|
||||
s = mapper.submapper(controller=PortController,
|
||||
requirements=requirements)
|
||||
s.connect(route_name, uri, action='create',
|
||||
conditions=dict(method=['POST']))
|
||||
s.connect(route_name, uri, action='update',
|
||||
conditions=dict(method=['PUT']))
|
||||
s.connect(route_name, uri, action='delete',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
wsgi.registory['MacController'] = self.nw
|
||||
route_name = 'macs'
|
||||
uri += '/macs'
|
||||
mapper.connect(route_name, uri,
|
||||
controller=MacController, action='lists',
|
||||
conditions=dict(method=['GET']),
|
||||
requirements=requirements)
|
||||
|
||||
uri += '/{mac_addr}'
|
||||
requirements['mac_addr'] = mac_lib.HADDR_PATTERN
|
||||
s = mapper.submapper(controller=MacController,
|
||||
requirements=requirements)
|
||||
s.connect(route_name, uri, action='create',
|
||||
conditions=dict(method=['POST']))
|
||||
s.connect(route_name, uri, action='update',
|
||||
conditions=dict(method=['PUT']))
|
||||
@ -21,12 +21,12 @@ This module provides a set of REST API for switch configuration.
|
||||
Used by OpenStack Ryu agent.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from six.moves import http_client
|
||||
import json
|
||||
import logging
|
||||
from webob import Response
|
||||
|
||||
from ryu.app.wsgi import ControllerBase
|
||||
from ryu.app.wsgi import Response
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import conf_switch
|
||||
from ryu.lib import dpid as dpid_lib
|
||||
@ -111,11 +111,7 @@ class ConfSwitchController(ControllerBase):
|
||||
|
||||
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)
|
||||
val = json.loads(req.body)
|
||||
self.conf_switch.set_key(dpid, key, val)
|
||||
return None
|
||||
|
||||
|
||||
@ -17,8 +17,9 @@
|
||||
import logging
|
||||
import json
|
||||
|
||||
from webob import Response
|
||||
|
||||
from ryu.app.wsgi import ControllerBase
|
||||
from ryu.app.wsgi import Response
|
||||
from ryu.app.wsgi import WSGIApplication
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import ofp_event
|
||||
@ -491,8 +492,8 @@ class FirewallController(ControllerBase):
|
||||
|
||||
def _set_rule(self, req, switchid, vlan_id=VLANID_NONE):
|
||||
try:
|
||||
rule = req.json if req.body else {}
|
||||
except ValueError:
|
||||
rule = json.loads(req.body)
|
||||
except SyntaxError:
|
||||
FirewallController._LOGGER.debug('invalid syntax %s', req.body)
|
||||
return Response(status=400)
|
||||
|
||||
@ -515,8 +516,8 @@ class FirewallController(ControllerBase):
|
||||
|
||||
def _delete_rule(self, req, switchid, vlan_id=VLANID_NONE):
|
||||
try:
|
||||
ruleid = req.json if req.body else {}
|
||||
except ValueError:
|
||||
ruleid = json.loads(req.body)
|
||||
except SyntaxError:
|
||||
FirewallController._LOGGER.debug('invalid syntax %s', req.body)
|
||||
return Response(status=400)
|
||||
|
||||
@ -678,7 +679,8 @@ class Firewall(object):
|
||||
|
||||
def _set_log_status(self, is_enable, waiters):
|
||||
if is_enable:
|
||||
actions = Action.to_openflow({REST_ACTION: REST_ACTION_PACKETIN})
|
||||
actions = Action.to_openflow(self.dp,
|
||||
{REST_ACTION: REST_ACTION_PACKETIN})
|
||||
details = 'Log collection started.'
|
||||
else:
|
||||
actions = []
|
||||
@ -720,7 +722,7 @@ class Firewall(object):
|
||||
priority = ARP_FLOW_PRIORITY
|
||||
match = {REST_DL_TYPE: ether.ETH_TYPE_ARP}
|
||||
action = {REST_ACTION: REST_ACTION_ALLOW}
|
||||
actions = Action.to_openflow(action)
|
||||
actions = Action.to_openflow(self.dp, action)
|
||||
flow = self._to_of_flow(cookie=cookie, priority=priority,
|
||||
match=match, actions=actions)
|
||||
|
||||
@ -752,7 +754,7 @@ class Firewall(object):
|
||||
result = self.get_log_status(waiters)
|
||||
if result[REST_LOG_STATUS] == REST_STATUS_ENABLE:
|
||||
rest[REST_ACTION] = REST_ACTION_PACKETIN
|
||||
actions = Action.to_openflow(rest)
|
||||
actions = Action.to_openflow(self.dp, rest)
|
||||
flow = self._to_of_flow(cookie=cookie, priority=priority,
|
||||
match=match, actions=actions)
|
||||
|
||||
@ -879,7 +881,7 @@ class Firewall(object):
|
||||
rule = {REST_RULE_ID: ruleid}
|
||||
rule.update({REST_PRIORITY: flow[REST_PRIORITY]})
|
||||
rule.update(Match.to_rest(flow))
|
||||
rule.update(Action.to_rest(flow))
|
||||
rule.update(Action.to_rest(self.dp, flow))
|
||||
return rule
|
||||
|
||||
|
||||
@ -1077,17 +1079,19 @@ class Match(object):
|
||||
class Action(object):
|
||||
|
||||
@staticmethod
|
||||
def to_openflow(rest):
|
||||
def to_openflow(dp, rest):
|
||||
value = rest.get(REST_ACTION, REST_ACTION_ALLOW)
|
||||
|
||||
if value == REST_ACTION_ALLOW:
|
||||
out_port = dp.ofproto.OFPP_NORMAL
|
||||
action = [{'type': 'OUTPUT',
|
||||
'port': 'NORMAL'}]
|
||||
'port': out_port}]
|
||||
elif value == REST_ACTION_DENY:
|
||||
action = []
|
||||
elif value == REST_ACTION_PACKETIN:
|
||||
out_port = dp.ofproto.OFPP_CONTROLLER
|
||||
action = [{'type': 'OUTPUT',
|
||||
'port': 'CONTROLLER',
|
||||
'port': out_port,
|
||||
'max_len': 128}]
|
||||
else:
|
||||
raise ValueError('Invalid action type.')
|
||||
@ -1095,9 +1099,9 @@ class Action(object):
|
||||
return action
|
||||
|
||||
@staticmethod
|
||||
def to_rest(openflow):
|
||||
def to_rest(dp, openflow):
|
||||
if REST_ACTION in openflow:
|
||||
action_allow = 'OUTPUT:NORMAL'
|
||||
action_allow = 'OUTPUT:%d' % dp.ofproto.OFPP_NORMAL
|
||||
if openflow[REST_ACTION] == [action_allow]:
|
||||
action = {REST_ACTION: REST_ACTION_ALLOW}
|
||||
else:
|
||||
|
||||
41
ryu/app/rest_nw_id.py
Normal file
41
ryu/app/rest_nw_id.py
Normal file
@ -0,0 +1,41 @@
|
||||
# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
|
||||
# Copyright (C) 2011, 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.
|
||||
|
||||
|
||||
NW_ID_EXTERNAL = '__NW_ID_EXTERNAL__'
|
||||
NW_ID_RESERVED = '__NW_ID_RESERVED__'
|
||||
NW_ID_VPORT_GRE = '__NW_ID_VPORT_GRE__'
|
||||
NW_ID_UNKNOWN = '__NW_ID_UNKNOWN__'
|
||||
|
||||
RESERVED_NETWORK_IDS = (
|
||||
NW_ID_EXTERNAL,
|
||||
NW_ID_RESERVED,
|
||||
NW_ID_VPORT_GRE,
|
||||
NW_ID_UNKNOWN,
|
||||
)
|
||||
|
||||
# tunnel type
|
||||
_TUNNEL_TYPE_TO_NETWORK_ID = {
|
||||
'gre': NW_ID_VPORT_GRE,
|
||||
}
|
||||
|
||||
|
||||
def tunnel_type_to_network_id(tunnel_type):
|
||||
return _TUNNEL_TYPE_TO_NETWORK_ID[tunnel_type.lower()]
|
||||
|
||||
# PORT_TYPE_VM = 'guestvm'
|
||||
# PORT_TYPE_GW = 'gateway'
|
||||
# PORT_TYPE_EXTERNAL = 'external'
|
||||
@ -18,11 +18,10 @@ import logging
|
||||
import json
|
||||
import re
|
||||
|
||||
from webob import Response
|
||||
|
||||
from ryu.app import conf_switch_key as cs_key
|
||||
from ryu.app.wsgi import ControllerBase
|
||||
from ryu.app.wsgi import Response
|
||||
from ryu.app.wsgi import route
|
||||
from ryu.app.wsgi import WSGIApplication
|
||||
from ryu.app.wsgi import ControllerBase, WSGIApplication, route
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import conf_switch
|
||||
from ryu.controller import ofp_event
|
||||
@ -425,8 +424,7 @@ class QoSController(ControllerBase):
|
||||
@staticmethod
|
||||
def delete_ovsdb_addr(dpid):
|
||||
ofs = QoSController._OFS_LIST.get(dpid, None)
|
||||
if ofs is not None:
|
||||
ofs.set_ovsdb_addr(dpid, None)
|
||||
ofs.set_ovsdb_addr(dpid, None)
|
||||
|
||||
@route('qos_switch', BASE_URL + '/queue/{switchid}',
|
||||
methods=['GET'], requirements=REQUIREMENTS)
|
||||
@ -508,8 +506,8 @@ class QoSController(ControllerBase):
|
||||
|
||||
def _access_switch(self, req, switchid, vlan_id, func, waiters):
|
||||
try:
|
||||
rest = req.json if req.body else {}
|
||||
except ValueError:
|
||||
rest = json.loads(req.body) if req.body else {}
|
||||
except SyntaxError:
|
||||
QoSController._LOGGER.debug('invalid syntax %s', req.body)
|
||||
return Response(status=400)
|
||||
|
||||
@ -558,22 +556,6 @@ class QoS(object):
|
||||
self.vlan_list[VLANID_NONE] = 0 # for VLAN=None
|
||||
self.dp = dp
|
||||
self.version = dp.ofproto.OFP_VERSION
|
||||
# Dictionary of port name to Queue config.
|
||||
# e.g.)
|
||||
# self.queue_list = {
|
||||
# "s1-eth1": {
|
||||
# "0": {
|
||||
# "config": {
|
||||
# "max-rate": "600000"
|
||||
# }
|
||||
# },
|
||||
# "1": {
|
||||
# "config": {
|
||||
# "min-rate": "900000"
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
self.queue_list = {}
|
||||
self.CONF = CONF
|
||||
self.ovsdb_addr = None
|
||||
@ -601,22 +583,25 @@ class QoS(object):
|
||||
self.ofctl.mod_flow_entry(self.dp, flow, cmd)
|
||||
|
||||
def set_ovsdb_addr(self, dpid, ovsdb_addr):
|
||||
# easy check if the address format valid
|
||||
_proto, _host, _port = ovsdb_addr.split(':')
|
||||
|
||||
old_address = self.ovsdb_addr
|
||||
if old_address == ovsdb_addr:
|
||||
return
|
||||
elif ovsdb_addr is None:
|
||||
# Determine deleting OVSDB address was requested.
|
||||
if ovsdb_addr is None:
|
||||
if self.ovs_bridge:
|
||||
self.ovs_bridge.del_controller()
|
||||
self.ovs_bridge = None
|
||||
return
|
||||
|
||||
ovs_bridge = bridge.OVSBridge(self.CONF, dpid, ovsdb_addr)
|
||||
try:
|
||||
ovs_bridge.init()
|
||||
except:
|
||||
raise ValueError('ovsdb addr is not available.')
|
||||
self.ovsdb_addr = ovsdb_addr
|
||||
self.ovs_bridge = ovs_bridge
|
||||
if self.ovs_bridge is None:
|
||||
ovs_bridge = bridge.OVSBridge(self.CONF, dpid, ovsdb_addr)
|
||||
self.ovs_bridge = ovs_bridge
|
||||
try:
|
||||
ovs_bridge.init()
|
||||
except:
|
||||
raise ValueError('ovsdb addr is not available.')
|
||||
|
||||
def _update_vlan_list(self, vlan_list):
|
||||
for vlan_id in self.vlan_list.keys():
|
||||
@ -679,15 +664,7 @@ class QoS(object):
|
||||
'details': 'ovs_bridge is not exists'}
|
||||
return REST_COMMAND_RESULT, msg
|
||||
|
||||
port_name = rest.get(REST_PORT_NAME, None)
|
||||
vif_ports = self.ovs_bridge.get_port_name_list()
|
||||
|
||||
if port_name is not None:
|
||||
if port_name not in vif_ports:
|
||||
raise ValueError('%s port is not exists' % port_name)
|
||||
vif_ports = [port_name]
|
||||
|
||||
queue_list = {}
|
||||
self.queue_list.clear()
|
||||
queue_type = rest.get(REST_QUEUE_TYPE, 'linux-htb')
|
||||
parent_max_rate = rest.get(REST_QUEUE_MAX_RATE, None)
|
||||
queues = rest.get(REST_QUEUES, [])
|
||||
@ -705,9 +682,17 @@ class QoS(object):
|
||||
config['min-rate'] = min_rate
|
||||
if len(config):
|
||||
queue_config.append(config)
|
||||
queue_list[queue_id] = {'config': config}
|
||||
self.queue_list[queue_id] = {'config': config}
|
||||
queue_id += 1
|
||||
|
||||
port_name = rest.get(REST_PORT_NAME, None)
|
||||
vif_ports = self.ovs_bridge.get_port_name_list()
|
||||
|
||||
if port_name is not None:
|
||||
if port_name not in vif_ports:
|
||||
raise ValueError('%s port is not exists' % port_name)
|
||||
vif_ports = [port_name]
|
||||
|
||||
for port_name in vif_ports:
|
||||
try:
|
||||
self.ovs_bridge.set_qos(port_name, type=queue_type,
|
||||
@ -715,10 +700,9 @@ class QoS(object):
|
||||
queues=queue_config)
|
||||
except Exception as msg:
|
||||
raise ValueError(msg)
|
||||
self.queue_list[port_name] = queue_list
|
||||
|
||||
msg = {'result': 'success',
|
||||
'details': queue_list}
|
||||
'details': self.queue_list}
|
||||
|
||||
return REST_COMMAND_RESULT, msg
|
||||
|
||||
@ -733,9 +717,9 @@ class QoS(object):
|
||||
|
||||
@rest_command
|
||||
def delete_queue(self, rest, vlan_id):
|
||||
self.queue_list.clear()
|
||||
if self._delete_queue():
|
||||
msg = 'success'
|
||||
self.queue_list.clear()
|
||||
else:
|
||||
msg = 'failure'
|
||||
|
||||
@ -1143,20 +1127,20 @@ class Match(object):
|
||||
class Action(object):
|
||||
|
||||
@staticmethod
|
||||
def to_rest(flow):
|
||||
if REST_ACTION in flow:
|
||||
def to_rest(openflow):
|
||||
if REST_ACTION in openflow:
|
||||
actions = []
|
||||
for act in flow[REST_ACTION]:
|
||||
field_value = re.search(r'SET_FIELD: \{ip_dscp:(\d+)', act)
|
||||
for action in openflow[REST_ACTION]:
|
||||
field_value = re.search('SET_FIELD: {ip_dscp:(\d+)', action)
|
||||
if field_value:
|
||||
actions.append({REST_ACTION_MARK: field_value.group(1)})
|
||||
meter_value = re.search(r'METER:(\d+)', act)
|
||||
meter_value = re.search('METER:(\d+)', action)
|
||||
if meter_value:
|
||||
actions.append({REST_ACTION_METER: meter_value.group(1)})
|
||||
queue_value = re.search(r'SET_QUEUE:(\d+)', act)
|
||||
queue_value = re.search('SET_QUEUE:(\d+)', action)
|
||||
if queue_value:
|
||||
actions.append({REST_ACTION_QUEUE: queue_value.group(1)})
|
||||
action = {REST_ACTION: actions}
|
||||
action = {REST_ACTION: actions}
|
||||
else:
|
||||
action = {REST_ACTION: 'Unknown action type.'}
|
||||
|
||||
|
||||
136
ryu/app/rest_quantum.py
Normal file
136
ryu/app/rest_quantum.py
Normal file
@ -0,0 +1,136 @@
|
||||
# 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 dedicated to OpenStack Ryu plug-in.
|
||||
|
||||
- Interface (uuid in ovsdb) registration
|
||||
- Maintain interface association to a network
|
||||
|
||||
Used by OpenStack Ryu plug-in.
|
||||
"""
|
||||
|
||||
import json
|
||||
from webob import Response
|
||||
|
||||
from ryu.base import app_manager
|
||||
from ryu.app.wsgi import (ControllerBase,
|
||||
WSGIApplication)
|
||||
from ryu.lib import quantum_ifaces
|
||||
|
||||
# REST API for openstack quantum
|
||||
# get the list of iface-ids
|
||||
# GET /v1.0/quantum/ports/
|
||||
#
|
||||
# register the iface_id
|
||||
# POST /v1.0/quantum/ports/{iface_id}
|
||||
#
|
||||
# unregister iface_id
|
||||
# DELETE /v1.0/quantum/ports/{iface_id}
|
||||
#
|
||||
# associate network_id with iface_id
|
||||
# GET /v1.0/quantum/ports/{iface_id}/network_id
|
||||
#
|
||||
# associate network_id with iface_id
|
||||
# POST /v1.0/quantum/ports/{iface_id}/network_id/{network_id}
|
||||
#
|
||||
# update network_id with iface_id
|
||||
# PUT /v1.0/quantum/ports/{iface_id}/network_id/{network_id}
|
||||
|
||||
|
||||
class QuantumController(ControllerBase):
|
||||
def __init__(self, req, link, data, **config):
|
||||
super(QuantumController, self).__init__(req, link, data, **config)
|
||||
self.ifaces = data
|
||||
|
||||
def list_ifaces(self, _req, **_kwargs):
|
||||
body = json.dumps(list(self.ifaces.keys()))
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def delete_iface(self, _req, iface_id, **_kwargs):
|
||||
self.ifaces.unregister(iface_id)
|
||||
return Response(status=200)
|
||||
|
||||
def list_keys(self, _req, iface_id, **_kwargs):
|
||||
try:
|
||||
keys = self.ifaces.list_keys(iface_id)
|
||||
except KeyError:
|
||||
return Response(status=404)
|
||||
body = json.dumps(keys)
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def get_key(self, _req, iface_id, key, **_kwargs):
|
||||
try:
|
||||
value = self.ifaces.get_key(iface_id, key)
|
||||
except KeyError:
|
||||
return Response(status=404)
|
||||
body = json.dumps(value)
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def create_value(self, _req, iface_id, key, value, **_kwargs):
|
||||
try:
|
||||
self.ifaces.set_key(iface_id, key, value)
|
||||
except ValueError:
|
||||
return Response(status=404)
|
||||
return Response(status=200)
|
||||
|
||||
def update_value(self, _req, iface_id, key, value, **_kwargs):
|
||||
try:
|
||||
self.ifaces.update_key(iface_id, key, value)
|
||||
except ValueError:
|
||||
return Response(status=404)
|
||||
return Response(status=200)
|
||||
|
||||
|
||||
class QuantumIfaceAPI(app_manager.RyuApp):
|
||||
_CONTEXTS = {
|
||||
'quantum_ifaces': quantum_ifaces.QuantumIfaces,
|
||||
'wsgi': WSGIApplication,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(QuantumIfaceAPI, self).__init__(*args, **kwargs)
|
||||
self.ifaces = kwargs['quantum_ifaces']
|
||||
wsgi = kwargs['wsgi']
|
||||
mapper = wsgi.mapper
|
||||
|
||||
controller = QuantumController
|
||||
wsgi.registory[controller.__name__] = self.ifaces
|
||||
route_name = 'quantum_ifaces'
|
||||
uri = '/v1.0/quantum'
|
||||
|
||||
ports_uri = uri + '/ports'
|
||||
s = mapper.submapper(controller=controller)
|
||||
s.connect(route_name, ports_uri, action='list_ifaces',
|
||||
conditions=dict(method=['GET', 'HEAD']))
|
||||
|
||||
iface_uri = ports_uri + '/{iface_id}'
|
||||
s.connect(route_name, iface_uri, action='delete_iface',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
keys_uri = iface_uri + '/keys'
|
||||
s.connect(route_name, keys_uri, action='list_keys',
|
||||
conditions=dict(method=['GET', 'HEAD']))
|
||||
|
||||
key_uri = keys_uri + '/{key}'
|
||||
s.connect(route_name, key_uri, action='get_key',
|
||||
conditions=dict(method=['GET', 'HEAD']))
|
||||
|
||||
value_uri = keys_uri + '/{key}/{value}'
|
||||
s.connect(route_name, value_uri, action='create_value',
|
||||
conditions=dict(method=['POST']))
|
||||
s.connect(route_name, value_uri, action='update_value',
|
||||
conditions=dict(method=['PUT']))
|
||||
@ -20,9 +20,9 @@ import socket
|
||||
import struct
|
||||
|
||||
import json
|
||||
from webob import Response
|
||||
|
||||
from ryu.app.wsgi import ControllerBase
|
||||
from ryu.app.wsgi import Response
|
||||
from ryu.app.wsgi import WSGIApplication
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import dpset
|
||||
@ -40,7 +40,6 @@ from ryu.lib.packet import ethernet
|
||||
from ryu.lib.packet import icmp
|
||||
from ryu.lib.packet import ipv4
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.lib.packet import packet_base
|
||||
from ryu.lib.packet import tcp
|
||||
from ryu.lib.packet import udp
|
||||
from ryu.lib.packet import vlan
|
||||
@ -377,45 +376,42 @@ class RouterController(ControllerBase):
|
||||
@rest_command
|
||||
def get_data(self, req, switch_id, **_kwargs):
|
||||
return self._access_router(switch_id, VLANID_NONE,
|
||||
'get_data', req)
|
||||
'get_data', req.body)
|
||||
|
||||
# GET /router/{switch_id}/{vlan_id}
|
||||
@rest_command
|
||||
def get_vlan_data(self, req, switch_id, vlan_id, **_kwargs):
|
||||
return self._access_router(switch_id, vlan_id,
|
||||
'get_data', req)
|
||||
'get_data', req.body)
|
||||
|
||||
# POST /router/{switch_id}
|
||||
@rest_command
|
||||
def set_data(self, req, switch_id, **_kwargs):
|
||||
return self._access_router(switch_id, VLANID_NONE,
|
||||
'set_data', req)
|
||||
'set_data', req.body)
|
||||
|
||||
# POST /router/{switch_id}/{vlan_id}
|
||||
@rest_command
|
||||
def set_vlan_data(self, req, switch_id, vlan_id, **_kwargs):
|
||||
return self._access_router(switch_id, vlan_id,
|
||||
'set_data', req)
|
||||
'set_data', req.body)
|
||||
|
||||
# DELETE /router/{switch_id}
|
||||
@rest_command
|
||||
def delete_data(self, req, switch_id, **_kwargs):
|
||||
return self._access_router(switch_id, VLANID_NONE,
|
||||
'delete_data', req)
|
||||
'delete_data', req.body)
|
||||
|
||||
# DELETE /router/{switch_id}/{vlan_id}
|
||||
@rest_command
|
||||
def delete_vlan_data(self, req, switch_id, vlan_id, **_kwargs):
|
||||
return self._access_router(switch_id, vlan_id,
|
||||
'delete_data', req)
|
||||
'delete_data', req.body)
|
||||
|
||||
def _access_router(self, switch_id, vlan_id, func, req):
|
||||
def _access_router(self, switch_id, vlan_id, func, rest_param):
|
||||
rest_message = []
|
||||
routers = self._get_router(switch_id)
|
||||
try:
|
||||
param = req.json if req.body else {}
|
||||
except ValueError:
|
||||
raise SyntaxError('invalid syntax %s', req.body)
|
||||
param = json.loads(rest_param) if rest_param else {}
|
||||
for router in routers.values():
|
||||
function = getattr(router, func)
|
||||
data = function(vlan_id, param, self.waiters)
|
||||
@ -570,8 +566,7 @@ class Router(dict):
|
||||
# TODO: Packet library convert to string
|
||||
# self.logger.debug('Packet in = %s', str(pkt), self.sw_id)
|
||||
header_list = dict((p.protocol_name, p)
|
||||
for p in pkt.protocols
|
||||
if isinstance(p, packet_base.PacketBase))
|
||||
for p in pkt.protocols if type(p) != str)
|
||||
if header_list:
|
||||
# Check vlan-tag
|
||||
vlan_id = VLANID_NONE
|
||||
@ -1010,14 +1005,14 @@ class VlanRouter(object):
|
||||
else:
|
||||
if header_list[ARP].opcode == arp.ARP_REQUEST:
|
||||
# ARP request to router port -> send ARP reply
|
||||
src_mac = self.port_data[in_port].mac
|
||||
dst_mac = header_list[ARP].src_mac
|
||||
src_mac = header_list[ARP].src_mac
|
||||
dst_mac = self.port_data[in_port].mac
|
||||
arp_target_mac = dst_mac
|
||||
output = in_port
|
||||
in_port = self.ofctl.dp.ofproto.OFPP_CONTROLLER
|
||||
|
||||
self.ofctl.send_arp(arp.ARP_REPLY, self.vlan_id,
|
||||
src_mac, dst_mac, dst_ip, src_ip,
|
||||
dst_mac, src_mac, dst_ip, src_ip,
|
||||
arp_target_mac, in_port, output)
|
||||
|
||||
log_msg = 'Receive ARP request from [%s] to router port [%s].'
|
||||
@ -1528,37 +1523,18 @@ class OfCtl(object):
|
||||
eth = protocol_list[ETHERNET]
|
||||
e = ethernet.ethernet(eth.src, eth.dst, ether_proto)
|
||||
|
||||
ip = protocol_list[IPV4]
|
||||
|
||||
if icmp_data is None and msg_data is not None:
|
||||
# RFC 4884 says that we should send "at least 128 octets"
|
||||
# if we are using the ICMP Extension Structure.
|
||||
# We're not using the extension structure, but let's send
|
||||
# up to 128 bytes of the original msg_data.
|
||||
#
|
||||
# RFC 4884 also states that the length field is interpreted in
|
||||
# 32 bit units, so the length calculated in bytes needs to first
|
||||
# be divided by 4, then increased by 1 if the modulus is non-zero.
|
||||
#
|
||||
# Finally, RFC 4884 says, if we're specifying the length, we MUST
|
||||
# zero pad to the next 32 bit boundary.
|
||||
end_of_data = offset + len(ip) + 128
|
||||
ip_datagram = bytearray()
|
||||
ip_datagram += msg_data[offset:end_of_data]
|
||||
data_len = int(len(ip_datagram) / 4)
|
||||
length_modulus = int(len(ip_datagram) % 4)
|
||||
if length_modulus:
|
||||
data_len += 1
|
||||
ip_datagram += bytearray([0] * (4 - length_modulus))
|
||||
ip_datagram = msg_data[offset:]
|
||||
if icmp_type == icmp.ICMP_DEST_UNREACH:
|
||||
icmp_data = icmp.dest_unreach(data_len=data_len,
|
||||
icmp_data = icmp.dest_unreach(data_len=len(ip_datagram),
|
||||
data=ip_datagram)
|
||||
elif icmp_type == icmp.ICMP_TIME_EXCEEDED:
|
||||
icmp_data = icmp.TimeExceeded(data_len=data_len,
|
||||
icmp_data = icmp.TimeExceeded(data_len=len(ip_datagram),
|
||||
data=ip_datagram)
|
||||
|
||||
ic = icmp.icmp(icmp_type, icmp_code, csum, data=icmp_data)
|
||||
|
||||
ip = protocol_list[IPV4]
|
||||
if src_ip is None:
|
||||
src_ip = ip.dst
|
||||
ip_total_length = ip.header_length * 4 + ic._MIN_LEN
|
||||
|
||||
@ -14,11 +14,9 @@
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
from webob import Response
|
||||
|
||||
from ryu.app.wsgi import ControllerBase
|
||||
from ryu.app.wsgi import Response
|
||||
from ryu.app.wsgi import route
|
||||
from ryu.app.wsgi import WSGIApplication
|
||||
from ryu.app.wsgi import ControllerBase, WSGIApplication, route
|
||||
from ryu.base import app_manager
|
||||
from ryu.lib import dpid as dpid_lib
|
||||
from ryu.topology.api import get_switch, get_link, get_host
|
||||
|
||||
218
ryu/app/rest_tunnel.py
Normal file
218
ryu/app/rest_tunnel.py
Normal file
@ -0,0 +1,218 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Provide a set of REST API for tunnel key management. Used by OpenStack
|
||||
Ryu plug-in.
|
||||
|
||||
- Tunnel key registration for a network
|
||||
- Manage switches and their ports which are used to establish a tunnel
|
||||
"""
|
||||
|
||||
import json
|
||||
from webob import Response
|
||||
|
||||
from ryu.app import wsgi as app_wsgi
|
||||
from ryu.app.wsgi import ControllerBase, WSGIApplication
|
||||
from ryu.base import app_manager
|
||||
from ryu.controller import network
|
||||
from ryu.controller import tunnels
|
||||
import ryu.exception as ryu_exc
|
||||
from ryu.lib import dpid as dpid_lib
|
||||
|
||||
|
||||
# REST API for tunneling
|
||||
#
|
||||
# register tunnel key of this network
|
||||
# Fail if the key is already registered
|
||||
# POST /v1.0/tunnels/networks/{network-id}/key/{tunnel_key}
|
||||
#
|
||||
# register tunnel key of this network
|
||||
# Success as nop even if the same key is already registered
|
||||
# PUT /v1.0/tunnels/networks/{network-id}/key/{tunnel_key}
|
||||
#
|
||||
# return allocated tunnel key of this network
|
||||
# GET /v1.0/tunnels/networks/{network-id}/key
|
||||
#
|
||||
# get the ports of dpid that are used for tunneling
|
||||
# GET /v1.0/tunnels/switches/{dpid}/ports
|
||||
#
|
||||
# get the dpid of the other end of tunnel
|
||||
# GET /v1.0/tunnels/switches/{dpid}/ports/{port-id}/
|
||||
#
|
||||
# register the dpid of the other end of tunnel
|
||||
# Fail if the dpid is already registered
|
||||
# POST /v1.0/tunnels/switches/{dpid}/ports/{port-id}/{remote_dpid}
|
||||
#
|
||||
# register the dpid of the other end of tunnel
|
||||
# Success as nop even if the dpid is already registered
|
||||
# PUT /v1.0/tunnels/switches/{dpid}/ports/{port-id}/{remote_dpid}
|
||||
|
||||
|
||||
class TunnelKeyController(ControllerBase):
|
||||
def __init__(self, req, link, data, **config):
|
||||
super(TunnelKeyController, self).__init__(req, link, data, **config)
|
||||
self.tunnels = data
|
||||
|
||||
def create(self, _req, network_id, tunnel_key, **_kwargs):
|
||||
tunnel_key = int(tunnel_key)
|
||||
try:
|
||||
self.tunnels.register_key(network_id, tunnel_key)
|
||||
except (ryu_exc.NetworkAlreadyExist, tunnels.TunnelKeyAlreadyExist):
|
||||
return Response(status=409)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def update(self, _req, network_id, tunnel_key, **_kwargs):
|
||||
tunnel_key = int(tunnel_key)
|
||||
try:
|
||||
self.tunnels.update_key(network_id, tunnel_key)
|
||||
except (ryu_exc.NetworkAlreadyExist, tunnels.TunnelKeyAlreadyExist):
|
||||
return Response(status=409)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def lists(self, _req, network_id, **_kwargs):
|
||||
try:
|
||||
tunnel_key = self.tunnels.get_key(network_id)
|
||||
except tunnels.TunnelKeyNotFound:
|
||||
return Response(status=404)
|
||||
body = json.dumps(tunnel_key)
|
||||
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def delete(self, _req, network_id, **_kwargs):
|
||||
try:
|
||||
self.tunnels.delete_key(network_id)
|
||||
except (ryu_exc.NetworkNotFound, tunnels.TunnelKeyNotFound):
|
||||
return Response(status=404)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
|
||||
class TunnelPortController(ControllerBase):
|
||||
def __init__(self, req, link, data, **config):
|
||||
super(TunnelPortController, self).__init__(req, link, data, **config)
|
||||
self.tunnels = data
|
||||
|
||||
def create(self, _req, dpid, port_id, remote_dpid, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
remote_dpid = dpid_lib.str_to_dpid(remote_dpid)
|
||||
try:
|
||||
self.tunnels.register_port(dpid, port_id, remote_dpid)
|
||||
except ryu_exc.PortAlreadyExist:
|
||||
return Response(status=409)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def update(self, _req, dpid, port_id, remote_dpid, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
remote_dpid = dpid_lib.str_to_dpid(remote_dpid)
|
||||
try:
|
||||
self.tunnels.update_port(dpid, port_id, remote_dpid)
|
||||
except tunnels.RemoteDPIDAlreadyExist:
|
||||
return Response(status=409)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
def lists(self, _req, dpid, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
ports = self.tunnels.list_ports(dpid)
|
||||
body = json.dumps(ports)
|
||||
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def get(self, _req, dpid, port_id, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
try:
|
||||
remote_dpid = self.tunnels.get_remote_dpid(dpid, port_id)
|
||||
except ryu_exc.PortNotFound:
|
||||
return Response(status=404)
|
||||
body = json.dumps(dpid_lib.dpid_to_str(remote_dpid))
|
||||
|
||||
return Response(content_type='application/json', body=body)
|
||||
|
||||
def delete(self, _req, dpid, port_id, **_kwargs):
|
||||
dpid = dpid_lib.str_to_dpid(dpid)
|
||||
port_id = int(port_id)
|
||||
try:
|
||||
self.tunnels.delete_port(dpid, port_id)
|
||||
except ryu_exc.PortNotFound:
|
||||
return Response(status=404)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
|
||||
class TunnelAPI(app_manager.RyuApp):
|
||||
_CONTEXTS = {
|
||||
'network': network.Network,
|
||||
'tunnels': tunnels.Tunnels,
|
||||
'wsgi': WSGIApplication
|
||||
}
|
||||
|
||||
def __init__(self, *_args, **kwargs):
|
||||
super(TunnelAPI, self).__init__()
|
||||
self.nw = kwargs['network']
|
||||
self.tunnels = kwargs['tunnels']
|
||||
wsgi = kwargs['wsgi']
|
||||
mapper = wsgi.mapper
|
||||
|
||||
controller = TunnelKeyController
|
||||
wsgi.registory[controller.__name__] = self.tunnels
|
||||
route_name = 'tunnel_key'
|
||||
uri = '/v1.0/tunnels'
|
||||
key_uri = uri + '/networks/{network_id}/key'
|
||||
s = mapper.submapper(controller=controller)
|
||||
s.connect(route_name, key_uri, action='lists',
|
||||
conditions=dict(method=['GET', 'HEAD']))
|
||||
s.connect(route_name, key_uri, action='delete',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
key_uri += '/{tunnel_key}'
|
||||
requirements = {route_name: app_wsgi.DIGIT_PATTERN}
|
||||
s = mapper.submapper(controller=controller, requirements=requirements)
|
||||
s.connect(route_name, key_uri, action='create',
|
||||
conditions=dict(method=['POST']))
|
||||
s.connect(route_name, key_uri, action='update',
|
||||
conditions=dict(method=['PUT']))
|
||||
|
||||
controller = TunnelPortController
|
||||
wsgi.registory[controller.__name__] = self.tunnels
|
||||
route_name = 'tunnel_port'
|
||||
sw_uri = uri + '/switches/{dpid}/ports'
|
||||
requirements = {'dpid': dpid_lib.DPID_PATTERN}
|
||||
mapper.connect(route_name, sw_uri, controller=controller,
|
||||
action='lists', conditions=dict(method=['GET', 'HEAD']),
|
||||
requirements=requirements)
|
||||
|
||||
sw_uri += '/{port_id}'
|
||||
requirements['port_id'] = app_wsgi.DIGIT_PATTERN
|
||||
s = mapper.submapper(controller=controller, requirements=requirements)
|
||||
mapper.connect(route_name, sw_uri, action='get',
|
||||
conditions=dict(method=['GET', 'HEAD']))
|
||||
mapper.connect(route_name, sw_uri, action='delete',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
sw_uri += '/{remote_dpid}'
|
||||
requirements['remote_dpid'] = dpid_lib.DPID_PATTERN
|
||||
s = mapper.submapper(controller=controller, requirements=requirements)
|
||||
mapper.connect(route_name, sw_uri, action='create',
|
||||
conditions=dict(method=['POST']))
|
||||
mapper.connect(route_name, sw_uri, action='update',
|
||||
conditions=dict(method=['PUT']))
|
||||
1842
ryu/app/rest_vtep.py
1842
ryu/app/rest_vtep.py
File diff suppressed because it is too large
Load Diff
351
ryu/app/simple_isolation.py
Normal file
351
ryu/app/simple_isolation.py
Normal file
@ -0,0 +1,351 @@
|
||||
# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
|
||||
# Copyright (C) 2011, 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.
|
||||
|
||||
"""
|
||||
MAC address based isolation logic.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import struct
|
||||
|
||||
from ryu.app.rest_nw_id import NW_ID_UNKNOWN, NW_ID_EXTERNAL
|
||||
from ryu.base import app_manager
|
||||
from ryu.exception import MacAddressDuplicated
|
||||
from ryu.exception import PortUnknown
|
||||
from ryu.controller import dpset
|
||||
from ryu.controller import mac_to_network
|
||||
from ryu.controller import mac_to_port
|
||||
from ryu.controller import network
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import MAIN_DISPATCHER
|
||||
from ryu.controller.handler import CONFIG_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.ofproto import nx_match
|
||||
from ryu.lib.mac import haddr_to_str
|
||||
from ryu.lib import mac
|
||||
|
||||
|
||||
class SimpleIsolation(app_manager.RyuApp):
|
||||
_CONTEXTS = {
|
||||
'network': network.Network,
|
||||
'dpset': dpset.DPSet,
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SimpleIsolation, self).__init__(*args, **kwargs)
|
||||
self.nw = kwargs['network']
|
||||
self.dpset = kwargs['dpset']
|
||||
self.mac2port = mac_to_port.MacToPortTable()
|
||||
self.mac2net = mac_to_network.MacToNetwork(self.nw)
|
||||
|
||||
@set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
|
||||
def switch_features_handler(self, ev):
|
||||
msg = ev.msg
|
||||
datapath = msg.datapath
|
||||
|
||||
datapath.send_delete_all_flows()
|
||||
datapath.send_barrier()
|
||||
|
||||
self.mac2port.dpid_add(ev.msg.datapath_id)
|
||||
self.nw.add_datapath(ev.msg)
|
||||
|
||||
@staticmethod
|
||||
def _modflow_and_send_packet(msg, src, dst, actions):
|
||||
datapath = msg.datapath
|
||||
ofproto = datapath.ofproto
|
||||
|
||||
#
|
||||
# install flow and then send packet
|
||||
#
|
||||
rule = nx_match.ClsRule()
|
||||
rule.set_in_port(msg.in_port)
|
||||
rule.set_dl_dst(dst)
|
||||
rule.set_dl_src(src)
|
||||
datapath.send_flow_mod(
|
||||
rule=rule, cookie=0, command=datapath.ofproto.OFPFC_ADD,
|
||||
idle_timeout=0, hard_timeout=0,
|
||||
priority=ofproto.OFP_DEFAULT_PRIORITY,
|
||||
buffer_id=ofproto.OFP_NO_BUFFER, out_port=ofproto.OFPP_NONE,
|
||||
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
|
||||
|
||||
datapath.send_packet_out(msg.buffer_id, msg.in_port, actions)
|
||||
|
||||
def _forward_to_nw_id(self, msg, src, dst, nw_id, out_port):
|
||||
assert out_port is not None
|
||||
datapath = msg.datapath
|
||||
|
||||
if not self.nw.same_network(datapath.id, nw_id, out_port,
|
||||
NW_ID_EXTERNAL):
|
||||
self.logger.debug('packet is blocked src %s dst %s '
|
||||
'from %d to %d on datapath %d',
|
||||
haddr_to_str(src), haddr_to_str(dst),
|
||||
msg.in_port, out_port, datapath.id)
|
||||
return
|
||||
|
||||
self.logger.debug("learned dpid %s in_port %d out_port "
|
||||
"%d src %s dst %s",
|
||||
datapath.id, msg.in_port, out_port,
|
||||
haddr_to_str(src), haddr_to_str(dst))
|
||||
actions = [datapath.ofproto_parser.OFPActionOutput(out_port)]
|
||||
self._modflow_and_send_packet(msg, src, dst, actions)
|
||||
|
||||
def _flood_to_nw_id(self, msg, src, dst, nw_id):
|
||||
datapath = msg.datapath
|
||||
actions = []
|
||||
self.logger.debug("dpid %s in_port %d src %s dst %s ports %s",
|
||||
datapath.id, msg.in_port,
|
||||
haddr_to_str(src), haddr_to_str(dst),
|
||||
list(self.nw.dpids.get(datapath.id, {}).items()))
|
||||
for port_no in self.nw.filter_ports(datapath.id, msg.in_port,
|
||||
nw_id, NW_ID_EXTERNAL):
|
||||
self.logger.debug("port_no %s", port_no)
|
||||
actions.append(datapath.ofproto_parser.OFPActionOutput(port_no))
|
||||
self._modflow_and_send_packet(msg, src, dst, actions)
|
||||
|
||||
def _learned_mac_or_flood_to_nw_id(self, msg, src, dst,
|
||||
dst_nw_id, out_port):
|
||||
if out_port is not None:
|
||||
self._forward_to_nw_id(msg, src, dst, dst_nw_id, out_port)
|
||||
else:
|
||||
self._flood_to_nw_id(msg, src, dst, dst_nw_id)
|
||||
|
||||
def _modflow_and_drop_packet(self, msg, src, dst):
|
||||
self._modflow_and_send_packet(msg, src, dst, [])
|
||||
|
||||
def _drop_packet(self, msg):
|
||||
datapath = msg.datapath
|
||||
datapath.send_packet_out(msg.buffer_id, msg.in_port, [])
|
||||
|
||||
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
||||
def packet_in_handler(self, ev):
|
||||
# self.logger.debug('packet in ev %s msg %s', ev, ev.msg)
|
||||
msg = ev.msg
|
||||
datapath = msg.datapath
|
||||
ofproto = datapath.ofproto
|
||||
|
||||
dst, src, _eth_type = struct.unpack_from('!6s6sH', buffer(msg.data), 0)
|
||||
|
||||
try:
|
||||
port_nw_id = self.nw.get_network(datapath.id, msg.in_port)
|
||||
except PortUnknown:
|
||||
port_nw_id = NW_ID_UNKNOWN
|
||||
|
||||
if port_nw_id != NW_ID_UNKNOWN:
|
||||
# Here it is assumed that the
|
||||
# (port <-> network id)/(mac <-> network id) relationship
|
||||
# is stable once the port is created. The port will be destroyed
|
||||
# before assigning new network id to the given port.
|
||||
# This is correct nova-network/nova-compute.
|
||||
try:
|
||||
# allow external -> known nw id change
|
||||
self.mac2net.add_mac(src, port_nw_id, NW_ID_EXTERNAL)
|
||||
except MacAddressDuplicated:
|
||||
self.logger.warn('mac address %s is already in use.'
|
||||
' So (dpid %s, port %s) can not use it',
|
||||
haddr_to_str(src), datapath.id, msg.in_port)
|
||||
#
|
||||
# should we install drop action pro-actively for future?
|
||||
#
|
||||
self._drop_packet(msg)
|
||||
return
|
||||
|
||||
old_port = self.mac2port.port_add(datapath.id, msg.in_port, src)
|
||||
if old_port is not None and old_port != msg.in_port:
|
||||
# We really overwrite already learned mac address.
|
||||
# So discard already installed stale flow entry which conflicts
|
||||
# new port.
|
||||
rule = nx_match.ClsRule()
|
||||
rule.set_dl_dst(src)
|
||||
datapath.send_flow_mod(rule=rule,
|
||||
cookie=0,
|
||||
command=ofproto.OFPFC_DELETE,
|
||||
idle_timeout=0,
|
||||
hard_timeout=0,
|
||||
priority=ofproto.OFP_DEFAULT_PRIORITY,
|
||||
out_port=old_port)
|
||||
|
||||
# to make sure the old flow entries are purged.
|
||||
datapath.send_barrier()
|
||||
|
||||
src_nw_id = self.mac2net.get_network(src, NW_ID_UNKNOWN)
|
||||
dst_nw_id = self.mac2net.get_network(dst, NW_ID_UNKNOWN)
|
||||
|
||||
# we handle multicast packet as same as broadcast
|
||||
broadcast = (dst == mac.BROADCAST) or mac.is_multicast(dst)
|
||||
out_port = self.mac2port.port_get(datapath.id, dst)
|
||||
|
||||
#
|
||||
# there are several combinations:
|
||||
# in_port: known nw_id, external, unknown nw,
|
||||
# src mac: known nw_id, external, unknown nw,
|
||||
# dst mac: known nw_id, external, unknown nw, and broadcast/multicast
|
||||
# where known nw_id: is quantum network id
|
||||
# external: means that these ports are connected to outside
|
||||
# unknown nw: means that we don't know this port is bounded to
|
||||
# specific nw_id or external
|
||||
# broadcast: the destination mac address is broadcast address
|
||||
# (or multicast address)
|
||||
#
|
||||
# Can the following logic be refined/shortened?
|
||||
#
|
||||
|
||||
# When NW_ID_UNKNOWN is found, registering ports might be delayed.
|
||||
# So just drop only this packet and not install flow entry.
|
||||
# It is expected that when next packet arrives, the port is registers
|
||||
# with some network id
|
||||
|
||||
if port_nw_id != NW_ID_EXTERNAL and port_nw_id != NW_ID_UNKNOWN:
|
||||
if broadcast:
|
||||
# flood to all ports of external or src_nw_id
|
||||
self._flood_to_nw_id(msg, src, dst, src_nw_id)
|
||||
elif src_nw_id == NW_ID_EXTERNAL:
|
||||
self._modflow_and_drop_packet(msg, src, dst)
|
||||
return
|
||||
elif src_nw_id == NW_ID_UNKNOWN:
|
||||
self._drop_packet(msg)
|
||||
return
|
||||
else:
|
||||
# src_nw_id != NW_ID_EXTERNAL and src_nw_id != NW_ID_UNKNOWN:
|
||||
#
|
||||
# try learned mac check if the port is net_id
|
||||
# or
|
||||
# flood to all ports of external or src_nw_id
|
||||
self._learned_mac_or_flood_to_nw_id(msg, src, dst,
|
||||
src_nw_id, out_port)
|
||||
|
||||
elif port_nw_id == NW_ID_EXTERNAL:
|
||||
if src_nw_id != NW_ID_EXTERNAL and src_nw_id != NW_ID_UNKNOWN:
|
||||
if broadcast:
|
||||
# flood to all ports of external or src_nw_id
|
||||
self._flood_to_nw_id(msg, src, dst, src_nw_id)
|
||||
elif (dst_nw_id != NW_ID_EXTERNAL and
|
||||
dst_nw_id != NW_ID_UNKNOWN):
|
||||
if src_nw_id == dst_nw_id:
|
||||
# try learned mac
|
||||
# check if the port is external or same net_id
|
||||
# or
|
||||
# flood to all ports of external or src_nw_id
|
||||
self._learned_mac_or_flood_to_nw_id(msg, src, dst,
|
||||
src_nw_id,
|
||||
out_port)
|
||||
else:
|
||||
# should not occur?
|
||||
self.logger.debug("should this case happen?")
|
||||
self._drop_packet(msg)
|
||||
elif dst_nw_id == NW_ID_EXTERNAL:
|
||||
# try learned mac
|
||||
# or
|
||||
# flood to all ports of external or src_nw_id
|
||||
self._learned_mac_or_flood_to_nw_id(msg, src, dst,
|
||||
src_nw_id, out_port)
|
||||
else:
|
||||
assert dst_nw_id == NW_ID_UNKNOWN
|
||||
self.logger.debug("Unknown dst_nw_id")
|
||||
self._drop_packet(msg)
|
||||
elif src_nw_id == NW_ID_EXTERNAL:
|
||||
self._modflow_and_drop_packet(msg, src, dst)
|
||||
else:
|
||||
# should not occur?
|
||||
assert src_nw_id == NW_ID_UNKNOWN
|
||||
self._drop_packet(msg)
|
||||
else:
|
||||
# drop packets
|
||||
assert port_nw_id == NW_ID_UNKNOWN
|
||||
self._drop_packet(msg)
|
||||
# self.logger.debug("Unknown port_nw_id")
|
||||
|
||||
def _port_add(self, ev):
|
||||
#
|
||||
# delete flows entries that matches with
|
||||
# dl_dst == broadcast/multicast
|
||||
# and dl_src = network id if network id of this port is known
|
||||
# to send broadcast packet to this newly added port.
|
||||
#
|
||||
# Openflow v1.0 doesn't support masked match of dl_dst,
|
||||
# so delete all flow entries. It's inefficient, though.
|
||||
#
|
||||
msg = ev.msg
|
||||
datapath = msg.datapath
|
||||
|
||||
datapath.send_delete_all_flows()
|
||||
datapath.send_barrier()
|
||||
self.nw.port_added(datapath, msg.desc.port_no)
|
||||
|
||||
def _port_del(self, ev):
|
||||
# free mac addresses associated to this VM port,
|
||||
# and delete related flow entries for later reuse of mac address
|
||||
|
||||
dps_needs_barrier = set()
|
||||
|
||||
msg = ev.msg
|
||||
datapath = msg.datapath
|
||||
datapath_id = datapath.id
|
||||
port_no = msg.desc.port_no
|
||||
|
||||
rule = nx_match.ClsRule()
|
||||
rule.set_in_port(port_no)
|
||||
datapath.send_flow_del(rule=rule, cookie=0)
|
||||
|
||||
rule = nx_match.ClsRule()
|
||||
datapath.send_flow_del(rule=rule, cookie=0, out_port=port_no)
|
||||
dps_needs_barrier.add(datapath)
|
||||
|
||||
try:
|
||||
port_nw_id = self.nw.get_network(datapath_id, port_no)
|
||||
except PortUnknown:
|
||||
# race condition between rest api delete port
|
||||
# and openflow port deletion ofp_event
|
||||
pass
|
||||
else:
|
||||
if port_nw_id in (NW_ID_UNKNOWN, NW_ID_EXTERNAL):
|
||||
datapath.send_barrier()
|
||||
return
|
||||
|
||||
for mac_ in self.mac2port.mac_list(datapath_id, port_no):
|
||||
for (_dpid, dp) in self.dpset.get_all():
|
||||
if self.mac2port.port_get(dp.id, mac_) is None:
|
||||
continue
|
||||
|
||||
rule = nx_match.ClsRule()
|
||||
rule.set_dl_src(mac_)
|
||||
dp.send_flow_del(rule=rule, cookie=0)
|
||||
|
||||
rule = nx_match.ClsRule()
|
||||
rule.set_dl_dst(mac_)
|
||||
dp.send_flow_del(rule=rule, cookie=0)
|
||||
dps_needs_barrier.add(dp)
|
||||
|
||||
self.mac2port.mac_del(dp.id, mac_)
|
||||
|
||||
self.mac2net.del_mac(mac_)
|
||||
|
||||
self.nw.port_deleted(datapath.id, port_no)
|
||||
|
||||
for dp in dps_needs_barrier:
|
||||
dp.send_barrier()
|
||||
|
||||
@set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
|
||||
def port_status_handler(self, ev):
|
||||
msg = ev.msg
|
||||
reason = msg.reason
|
||||
ofproto = msg.datapath.ofproto
|
||||
|
||||
if reason == ofproto.OFPPR_ADD:
|
||||
self._port_add(ev)
|
||||
elif reason == ofproto.OFPPR_DELETE:
|
||||
self._port_del(ev)
|
||||
else:
|
||||
assert reason == ofproto.OFPPR_MODIFY
|
||||
@ -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 ryu.app import simple_switch_13
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import MAIN_DISPATCHER, DEAD_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.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)
|
||||
@ -36,12 +36,11 @@ class SimpleSwitch(app_manager.RyuApp):
|
||||
super(SimpleSwitch, self).__init__(*args, **kwargs)
|
||||
self.mac_to_port = {}
|
||||
|
||||
def add_flow(self, datapath, in_port, dst, src, actions):
|
||||
def add_flow(self, datapath, in_port, dst, 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))
|
||||
in_port=in_port, dl_dst=haddr_to_bin(dst))
|
||||
|
||||
mod = datapath.ofproto_parser.OFPFlowMod(
|
||||
datapath=datapath, match=match, cookie=0,
|
||||
@ -82,7 +81,7 @@ class SimpleSwitch(app_manager.RyuApp):
|
||||
|
||||
# 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)
|
||||
self.add_flow(datapath, msg.in_port, dst, actions)
|
||||
|
||||
data = None
|
||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
||||
|
||||
@ -30,12 +30,11 @@ class SimpleSwitch12(app_manager.RyuApp):
|
||||
super(SimpleSwitch12, self).__init__(*args, **kwargs)
|
||||
self.mac_to_port = {}
|
||||
|
||||
def add_flow(self, datapath, port, dst, src, actions):
|
||||
def add_flow(self, datapath, port, dst, actions):
|
||||
ofproto = datapath.ofproto
|
||||
|
||||
match = datapath.ofproto_parser.OFPMatch(in_port=port,
|
||||
eth_dst=dst,
|
||||
eth_src=src)
|
||||
eth_dst=dst)
|
||||
inst = [datapath.ofproto_parser.OFPInstructionActions(
|
||||
ofproto.OFPIT_APPLY_ACTIONS, actions)]
|
||||
|
||||
@ -81,7 +80,7 @@ class SimpleSwitch12(app_manager.RyuApp):
|
||||
|
||||
# install a flow to avoid packet_in next time
|
||||
if out_port != ofproto.OFPP_FLOOD:
|
||||
self.add_flow(datapath, in_port, dst, src, actions)
|
||||
self.add_flow(datapath, in_port, dst, actions)
|
||||
|
||||
data = None
|
||||
if msg.buffer_id == ofproto.OFP_NO_BUFFER:
|
||||
|
||||
@ -85,7 +85,7 @@ class SimpleSwitch13(app_manager.RyuApp):
|
||||
dst = eth.dst
|
||||
src = eth.src
|
||||
|
||||
dpid = format(datapath.id, "d").zfill(16)
|
||||
dpid = datapath.id
|
||||
self.mac_to_port.setdefault(dpid, {})
|
||||
|
||||
self.logger.info("packet in %s %s %s %s", dpid, src, dst, in_port)
|
||||
@ -102,7 +102,7 @@ class SimpleSwitch13(app_manager.RyuApp):
|
||||
|
||||
# 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)
|
||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
||||
# 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:
|
||||
|
||||
@ -93,7 +93,7 @@ class SimpleSwitch14(app_manager.RyuApp):
|
||||
|
||||
# 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)
|
||||
match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
|
||||
self.add_flow(datapath, 1, match, actions)
|
||||
|
||||
data = None
|
||||
|
||||
@ -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 ryu.base import app_manager
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.ofproto import ofproto_v1_5
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.lib.packet import ethernet
|
||||
from ryu.lib.packet import ether_types
|
||||
|
||||
|
||||
class SimpleSwitch15(app_manager.RyuApp):
|
||||
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,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 ryu.base import app_manager
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import CONFIG_DISPATCHER
|
||||
from ryu.controller.handler import MAIN_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.ofproto import ofproto_v1_3
|
||||
from ryu.lib import igmplib
|
||||
from ryu.lib.dpid import str_to_dpid
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.lib.packet import ethernet
|
||||
from ryu.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,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 ryu.base import app_manager
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import CONFIG_DISPATCHER
|
||||
from ryu.controller.handler import MAIN_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.ofproto import ofproto_v1_3
|
||||
from ryu.lib import lacplib
|
||||
from ryu.lib.dpid import str_to_dpid
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.lib.packet import ethernet
|
||||
from ryu.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 ryu.app import simple_switch_13
|
||||
from ryu.controller import ofp_event
|
||||
from ryu.controller.handler import CONFIG_DISPATCHER
|
||||
from ryu.controller.handler import set_ev_cls
|
||||
from ryu.app.wsgi import ControllerBase
|
||||
from ryu.app.wsgi import Response
|
||||
from ryu.app.wsgi import route
|
||||
from ryu.app.wsgi import WSGIApplication
|
||||
from ryu.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 = 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', text=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 = 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', text=body)
|
||||
except Exception as e:
|
||||
return Response(status=500)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user