Merge branch 'master' into point-to-multipoint

This commit is contained in:
Josh Bailey 2021-08-18 12:53:50 +12:00 committed by GitHub
commit f3d8c366db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 269 additions and 230 deletions

25
.github/workflows/tests-unit.yml vendored Normal file
View File

@ -0,0 +1,25 @@
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

15
.renovaterc.json Normal file
View File

@ -0,0 +1,15 @@
{
"separateMajorMinor": false,
"schedule": [
"after 10pm every weekday",
"before 5am every weekday",
"every weekend"
],
"timezone": "Pacific/Auckland",
"extends": [
"config:base",
":prHourlyLimit1",
":preserveSemverRanges",
"docker:enableMajor"
]
}

9
.stickler.yml Normal file
View File

@ -0,0 +1,9 @@
---
linters:
flake8:
python: 3
max-line-length: 120
pep8:
python: 3
max-line-length: 120
py3k:

View File

@ -1,40 +0,0 @@
language: python
matrix:
include:
- python: 3.6
env: TOX_ENV=pycodestyle
- python: 3.6
env: TOX_ENV=py36
- python: 3.6
env: TOX_ENV=autopep8
- python: 2.7
env: TOX_ENV=pycodestyle
- python: 2.7
env: TOX_ENV=py27
- python: 3.5
env: TOX_ENV=py35
- python: 3.7-dev
env: TOX_ENV=py37
# This is disabled because of trouble running on travis CI.
# - python: pypy
# env: TOX_ENV=pypy
services:
- docker
sudo: required # Required to enable Docker service
install:
- pip install tox coveralls
- bash ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh
script:
- NOSE_VERBOSE=0 tox -e $TOX_ENV
after_success:
- coveralls

View File

@ -30,9 +30,9 @@ features (it's not a must though).
Python version and libraries
============================
* Python 2.7, 3.5, 3.6, 3.7:
* Python 3.5, 3.6, 3.7, 3.8, 3.9:
Ryu supports multiple Python version. CI tests on Travis-CI is running
Ryu supports multiple Python versions. CI tests on GitHub Actions is running
on these versions.
* standard library + widely used library:

4
debian/control vendored
View File

@ -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.3.0),
python-msgpack (>= 0.4.0),
python-netaddr,
python-oslo.config (>= 1:1.2.0),
python-paramiko,
@ -28,7 +28,7 @@ Section: python
Depends:
python-eventlet,
python-lxml,
python-msgpack (>= 0.3.0),
python-msgpack (>= 0.4.0),
python-netaddr,
python-oslo.config (>= 1:1.2.0),
python-paramiko,

View File

@ -67,11 +67,16 @@ 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)
actions=actions, data = data)
dp.send_msg(out)

1
pip-requirements.txt Normal file
View File

@ -0,0 +1 @@
pip==20.3.4

View File

@ -108,8 +108,15 @@ class WebSocketRegistrationWrapper(object):
class _AlreadyHandledResponse(Response):
# XXX: Eventlet API should not be used directly.
from eventlet.wsgi import ALREADY_HANDLED
_ALREADY_HANDLED = ALREADY_HANDLED
# https://github.com/benoitc/gunicorn/pull/2581
from packaging import version
import eventlet
if version.parse(eventlet.__version__) >= version.parse("0.30.3"):
import eventlet.wsgi
_ALREADY_HANDLED = getattr(eventlet.wsgi, "ALREADY_HANDLED", None)
else:
from eventlet.wsgi import ALREADY_HANDLED
_ALREADY_HANDLED = ALREADY_HANDLED
def __call__(self, environ, start_response):
return self._ALREADY_HANDLED

View File

@ -67,6 +67,7 @@ CONF.register_cli_opts([
cfg.StrOpt('ctl-privkey', default=None, help='controller private key'),
cfg.StrOpt('ctl-cert', default=None, help='controller certificate'),
cfg.StrOpt('ca-certs', default=None, help='CA certificates'),
cfg.StrOpt('ciphers', default=None, help='list of ciphers to enable'),
cfg.ListOpt('ofp-switch-address-list', item_type=str, default=[],
help='list of IP address and port pairs (default empty). '
'e.g., "127.0.0.1:6653,[::1]:6653"'),
@ -182,6 +183,9 @@ class OpenFlowController(object):
# Restrict non-safe versions
ssl_args['ssl_ctx'].options |= ssl.OP_NO_SSLv3 | ssl.OP_NO_SSLv2
if CONF.ciphers is not None:
ssl_args['ciphers'] = CONF.ciphers
if CONF.ca_certs is not None:
server = StreamServer((CONF.ofp_listen_host,
ofp_ssl_listen_port),
@ -313,7 +317,8 @@ class Datapath(ofproto_protocol.ProtocolDesc):
self.state = state
ev = ofp_event.EventOFPStateChange(self)
ev.state = state
self.ofp_brick.send_event_to_observers(ev, state)
if self.ofp_brick is not None:
self.ofp_brick.send_event_to_observers(ev, state)
# Low level socket handling layer
@_deactivate
@ -358,16 +363,17 @@ class Datapath(ofproto_protocol.ProtocolDesc):
# LOG.debug('queue msg %s cls %s', msg, msg.__class__)
if msg:
ev = ofp_event.ofp_msg_to_ev(msg)
self.ofp_brick.send_event_to_observers(ev, self.state)
if self.ofp_brick is not None:
self.ofp_brick.send_event_to_observers(ev, self.state)
def dispatchers(x):
return x.callers[ev.__class__].dispatchers
def dispatchers(x):
return x.callers[ev.__class__].dispatchers
handlers = [handler for handler in
self.ofp_brick.get_handlers(ev) if
self.state in dispatchers(handler)]
for handler in handlers:
handler(ev)
handlers = [handler for handler in
self.ofp_brick.get_handlers(ev) if
self.state in dispatchers(handler)]
for handler in handlers:
handler(ev)
buf = buf[msg_len:]
buf_len = len(buf)

View File

@ -149,6 +149,7 @@ def register_service(service):
there are applications consuming OFP events.
"""
frame = inspect.currentframe()
m_name = frame.f_back.f_globals['__name__']
m = sys.modules[m_name]
m._SERVICE_NAME = service
if frame is not None:
m_name = frame.f_back.f_globals['__name__']
m = sys.modules[m_name]
m._SERVICE_NAME = service

View File

@ -50,7 +50,7 @@ class MacToPortTable(object):
return self.mac_to_port[dpid].get(mac)
def mac_list(self, dpid, port):
return [mac for (mac, port_) in self.mac_to_port.get(dpid).items()
return [mac for (mac, port_) in self.mac_to_port.get(dpid, {}).items()
if port_ == port]
def mac_del(self, dpid, mac):

View File

@ -47,7 +47,7 @@ def register_switch_address(addr, interval=None):
def _retry_loop():
# Delays registration if ofp_handler is not started yet
while True:
if ofp_handler.controller is not None:
if ofp_handler is not None and ofp_handler.controller is not None:
for a, i in _TMP_ADDRESSES.items():
ofp_handler.controller.spawn_client_loop(a, i)
hub.sleep(1)
@ -69,6 +69,6 @@ def unregister_switch_address(addr):
"""
ofp_handler = app_manager.lookup_service_brick(ofp_event.NAME)
# Do nothing if ofp_handler is not started yet
if ofp_handler.controller is None:
if ofp_handler is None or ofp_handler.controller is None:
return
ofp_handler.controller.stop_client_loop(addr)

View File

@ -127,21 +127,25 @@ if HUB_TYPE == 'eventlet':
self.server = eventlet.listen(listen_info)
if ssl_args:
def wrap_and_handle(sock, addr):
ssl_args.setdefault('server_side', True)
if 'ssl_ctx' in ssl_args:
ctx = ssl_args.pop('ssl_ctx')
ctx.load_cert_chain(ssl_args.pop('certfile'),
ssl_args.pop('keyfile'))
if 'cert_reqs' in ssl_args:
ctx.verify_mode = ssl_args.pop('cert_reqs')
if 'ca_certs' in ssl_args:
ctx.load_verify_locations(ssl_args.pop('ca_certs'))
ssl_args.setdefault('server_side', True)
if 'ssl_ctx' in ssl_args:
ctx = ssl_args.pop('ssl_ctx')
ctx.load_cert_chain(ssl_args.pop('certfile'),
ssl_args.pop('keyfile'))
if 'cert_reqs' in ssl_args:
ctx.verify_mode = ssl_args.pop('cert_reqs')
if 'ca_certs' in ssl_args:
ctx.load_verify_locations(ssl_args.pop('ca_certs'))
def wrap_and_handle_ctx(sock, addr):
handle(ctx.wrap_socket(sock, **ssl_args), addr)
else:
self.handle = wrap_and_handle_ctx
else:
def wrap_and_handle_ssl(sock, addr):
handle(ssl.wrap_socket(sock, **ssl_args), addr)
self.handle = wrap_and_handle
self.handle = wrap_and_handle_ssl
else:
self.handle = handle

View File

@ -114,7 +114,7 @@ class OVSBridge(object):
"""
def __init__(self, CONF, datapath_id, ovsdb_addr, timeout=None,
exception=None):
exception=None, br_name=None):
super(OVSBridge, self).__init__()
self.datapath_id = datapath_id
self.ovsdb_addr = ovsdb_addr
@ -122,7 +122,7 @@ class OVSBridge(object):
self.timeout = timeout or CONF.ovsdb_timeout
self.exception = exception
self.br_name = None
self.br_name = br_name
def run_command(self, commands):
"""

View File

@ -268,7 +268,7 @@ class cc_message(operation):
self._opcode = CFM_CC_MESSAGE
assert rdi in [0, 1]
self.rdi = rdi
assert interval is not 0
assert interval != 0
self.interval = interval
self.seq_num = seq_num
assert 1 <= mep_id <= 8191

View File

@ -623,6 +623,50 @@ class nd_option_pi(nd_option):
return six.binary_type(hdr)
@nd_router_advert.register_nd_option_type
class nd_option_mtu(nd_option):
"""ICMPv6 sub encoder/decoder class for Neighbor discovery
MTU Option. (RFC 4861)
This is used with ryu.lib.packet.icmpv6.nd_router_advert.
An instance has the following attributes at least.
Most of them are same to the on-wire counterparts but in host byte order.
__init__ takes the corresponding args in this order.
.. tabularcolumns:: |l|p{35em}|
============== ====================
Attribute Description
============== ====================
mtu MTU.
============== ====================
"""
_PACK_STR = '!BBHI'
_LEN = struct.calcsize(_PACK_STR)
_OPTION_LEN = _LEN // 8
@classmethod
def option_type(cls):
return ND_OPTION_MTU
def __init__(self, mtu=1500):
super(nd_option_mtu, self).__init__(self.option_type(), 0)
self.mtu = mtu
@classmethod
def parser(cls, buf, offset):
(_, _, _, mtu) = struct.unpack_from(cls._PACK_STR, buf, offset)
msg = cls(mtu)
return msg
def serialize(self):
buf = bytearray(struct.pack(
self._PACK_STR, self.option_type(), self._OPTION_LEN, 0, self.mtu))
return six.binary_type(buf)
@icmpv6.register_icmpv6_type(ICMPV6_ECHO_REPLY, ICMPV6_ECHO_REQUEST)
class echo(_ICMPv6Payload):
"""ICMPv6 sub encoder/decoder class for Echo Request and Echo Reply

View File

@ -40,8 +40,16 @@ class MessageEncoder(object):
def __init__(self):
super(MessageEncoder, self).__init__()
self._packer = msgpack.Packer(encoding='utf-8', use_bin_type=True)
self._unpacker = msgpack.Unpacker(encoding='utf-8')
if msgpack.version >= (1, 0, 0):
self._packer = msgpack.Packer()
# The strict_map_key=False option is required to use int keys in
# maps; it is disabled by default to prevent hash collision denial
# of service attacks (hashdos) in scenarios where an attacker can
# control the keys to be hashed.
self._unpacker = msgpack.Unpacker(strict_map_key=False)
else:
self._packer = msgpack.Packer(encoding='utf-8', use_bin_type=True)
self._unpacker = msgpack.Unpacker(encoding='utf-8')
self._next_msgid = 0
def _create_msgid(self):

View File

@ -788,7 +788,7 @@ def generate(ofp_name, ofpp_name):
Example::
actions += [parser.NXActionResubmit(in_port=8080,
actions += [parser.NXActionResubmitTable(in_port=8080,
table_id=10)]
"""
_subtype = nicira_ext.NXAST_RESUBMIT_TABLE

View File

@ -1754,7 +1754,7 @@ class OFPMatchField(StringifyMixin):
(value, mask) = struct.unpack_from(pack_str, buf, offset + 4)
else:
(value,) = struct.unpack_from(cls.pack_str, buf, offset + 4)
return cls(header, value, mask)
return cls(header, value, mask) # pytype: disable=wrong-arg-count
def serialize(self, buf, offset):
if ofproto.oxm_tlv_header_extract_hasmask(self.header):
@ -2773,8 +2773,9 @@ class OFPFlowMod(MsgBase):
try:
while offset < msg_len:
i = OFPInstruction.parser(buf, offset)
instructions.append(i)
offset += i.len
if i is not None:
instructions.append(i)
offset += i.len
except exception.OFPTruncatedMessage as e:
instructions.append(e.ofpmsg)
msg.instructions = instructions
@ -2805,7 +2806,9 @@ class OFPInstruction(StringifyMixin):
def parser(cls, buf, offset):
(type_, len_) = struct.unpack_from('!HH', buf, offset)
cls_ = cls._INSTRUCTION_TYPES.get(type_)
return cls_.parser(buf, offset)
if cls_ is not None:
return cls_.parser(buf, offset)
return None
@OFPInstruction.register_instruction_type([ofproto.OFPIT_GOTO_TABLE])
@ -3551,7 +3554,7 @@ class OFPActionExperimenter(OFPAction):
data = buf[(offset + ofproto.OFP_ACTION_EXPERIMENTER_HEADER_SIZE
): offset + len_]
if experimenter == ofproto_common.NX_EXPERIMENTER_ID:
obj = NXAction.parse(data) # noqa
obj = NXAction.parse(data) # pytype: disable=name-error # noqa
else:
obj = OFPActionExperimenterUnknown(experimenter, data)
obj.len = len_
@ -3932,22 +3935,23 @@ class OFPMultipartReply(MsgBase):
ofproto.OFP_MULTIPART_REPLY_PACK_STR, six.binary_type(buf),
ofproto.OFP_HEADER_SIZE)
stats_type_cls = cls._STATS_MSG_TYPES.get(type_)
msg = super(OFPMultipartReply, stats_type_cls).parser(
msg = super(OFPMultipartReply, stats_type_cls).parser( # pytype: disable=attribute-error
datapath, version, msg_type, msg_len, xid, buf)
msg.type = type_
msg.flags = flags
offset = ofproto.OFP_MULTIPART_REPLY_SIZE
body = []
while offset < msg_len:
b = stats_type_cls.cls_stats_body_cls.parser(msg.buf, offset)
body.append(b)
offset += b.length if hasattr(b, 'length') else b.len
if stats_type_cls is not None:
offset = ofproto.OFP_MULTIPART_REPLY_SIZE
body = []
while offset < msg_len:
b = stats_type_cls.cls_stats_body_cls.parser(msg.buf, offset)
body.append(b)
offset += b.length if hasattr(b, 'length') else b.len
if stats_type_cls.cls_body_single_struct:
msg.body = body[0]
else:
msg.body = body
if stats_type_cls.cls_body_single_struct:
msg.body = body[0]
else:
msg.body = body
return msg
@ -4577,12 +4581,13 @@ class OFPGroupStats(StringifyMixin):
group_stats = cls(*group)
group_stats.bucket_stats = []
total_len = group_stats.length + offset
offset += ofproto.OFP_GROUP_STATS_SIZE
while total_len > offset:
b = OFPBucketCounter.parser(buf, offset)
group_stats.bucket_stats.append(b)
offset += ofproto.OFP_BUCKET_COUNTER_SIZE
if group_stats.length is not None:
total_len = group_stats.length + offset
offset += ofproto.OFP_GROUP_STATS_SIZE
while total_len > offset:
b = OFPBucketCounter.parser(buf, offset)
group_stats.bucket_stats.append(b)
offset += ofproto.OFP_BUCKET_COUNTER_SIZE
return group_stats
@ -5770,7 +5775,7 @@ class ONFFlowMonitorRequest(StringifyMixin):
match_len = match.length
match_hdr_len = ofproto.OFP_MATCH_SIZE - 4 # exclude pad[4]
# strip ofp_match header and trailing padding
bin_match = bytes(bin_match)[match_hdr_len:match_len]
bin_match = bytearray(bin_match)[match_hdr_len:match_len]
self.match_len = len(bin_match)
buf = bytearray()
@ -5936,14 +5941,16 @@ class OFPQueueProp(OFPQueuePropHeader):
ofproto.OFP_QUEUE_PROP_HEADER_PACK_STR,
buf, offset)
cls_ = cls._QUEUE_PROP_PROPERTIES.get(property_)
p = cls_.parser(buf, offset + ofproto.OFP_QUEUE_PROP_HEADER_SIZE)
p.property = property_
p.len = len_
if property_ == ofproto.OFPQT_EXPERIMENTER:
rest = buf[offset + ofproto.OFP_QUEUE_PROP_EXPERIMENTER_SIZE:
offset + len_]
p.parse_experimenter_data(rest)
return p
if cls_ is not None:
p = cls_.parser(buf, offset + ofproto.OFP_QUEUE_PROP_HEADER_SIZE)
p.property = property_
p.len = len_
if property_ == ofproto.OFPQT_EXPERIMENTER:
rest = buf[offset + ofproto.OFP_QUEUE_PROP_EXPERIMENTER_SIZE:
offset + len_]
p.parse_experimenter_data(rest)
return p
return None
@OFPQueueProp.register_property(ofproto.OFPQT_MIN_RATE,
@ -6017,9 +6024,10 @@ class OFPPacketQueue(StringifyMixin):
properties = []
while length < len_:
queue_prop = OFPQueueProp.parser(buf, offset)
properties.append(queue_prop)
offset += queue_prop.len
length += queue_prop.len
if queue_prop is not None:
properties.append(queue_prop)
offset += queue_prop.len
length += queue_prop.len
o = cls(queue_id, port, properties)
o.len = len_
return o
@ -6342,6 +6350,10 @@ class OFPSetAsync(MsgBase):
self.flow_removed_mask[0], self.flow_removed_mask[1])
class OFPBundleProp(OFPPropBase):
_TYPES = {}
@_register_exp_type(ofproto_common.ONF_EXPERIMENTER_ID,
ofproto.ONF_ET_BUNDLE_CONTROL)
class ONFBundleCtrlMsg(OFPExperimenter):

View File

@ -101,8 +101,16 @@ class RpcSession(Activity):
def __init__(self, sock, outgoing_msg_sink_iter):
self.peer_name = str(sock.getpeername())
super(RpcSession, self).__init__(self.NAME_FMT % self.peer_name)
self._packer = msgpack.Packer(encoding='utf-8')
self._unpacker = msgpack.Unpacker(encoding='utf-8')
if msgpack.version >= (1, 0, 0):
self._packer = msgpack.Packer()
# The strict_map_key=False option is required to use int keys in
# maps; it is disabled by default to prevent hash collision denial
# of service attacks (hashdos) in scenarios where an attacker can
# control the keys to be hashed.
self._unpacker = msgpack.Unpacker(strict_map_key=False)
else:
self._packer = msgpack.Packer(encoding='utf-8', use_bin_type=True)
self._unpacker = msgpack.Unpacker(encoding='utf-8')
self._next_msgid = 0
self._socket = sock
self._outgoing_msg_sink_iter = outgoing_msg_sink_iter

View File

@ -191,6 +191,7 @@ class TestOpenFlowController(unittest.TestCase):
conf_mock.ofp_ssl_listen_port = port
conf_mock.ofp_listen_host = "127.0.0.1"
conf_mock.ca_certs = None
conf_mock.ciphers = None
conf_mock.ctl_cert = os.path.join(this_dir, 'cert.crt')
conf_mock.ctl_privkey = os.path.join(this_dir, 'cert.key')
c = controller.OpenFlowController()

View File

@ -35,7 +35,7 @@ LOG = logging.getLogger(__name__)
DOCKER_IMAGE_MININET = 'osrg/ryu-book'
OVSDB_MANAGER_ADDR = 'ptcp:6640'
OVSDB_SWITCH_ADDR = 'tcp:%s:6640'
OVSDB_SWITCH_ADDR = 'tcp:0.0.0.0:6640'
def setUpModule():
@ -93,7 +93,7 @@ class TestVSCtl(unittest.TestCase):
@classmethod
def _docker_run(cls, image):
return _run('docker run --privileged -t -d %s' % image)[0]
return _run('docker run --privileged -t -d -p 6640:6640 %s' % image)[0]
@classmethod
def _docker_stop(cls, container):
@ -124,7 +124,7 @@ class TestVSCtl(unittest.TestCase):
@classmethod
def _set_up_vsctl(cls):
cls.vsctl = vsctl.VSCtl(OVSDB_SWITCH_ADDR % cls.container_mn_ip)
cls.vsctl = vsctl.VSCtl(OVSDB_SWITCH_ADDR)
@classmethod
def setUpClass(cls):

View File

@ -413,7 +413,7 @@ class Test_ofctl(unittest.TestCase):
# without mask
eq_(eth, field_value)
return
elif key in['nw_src', 'nw_dst', 'arp_spa', 'arp_tpa']:
elif key in ['nw_src', 'nw_dst', 'arp_spa', 'arp_tpa']:
# IPv4 address
if test.ver == ofproto_v1_0.OFP_VERSION:
ipv4, mask = _to_match_ip(value)

View File

@ -1,85 +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 logging
import os
import sys
import unittest
import pkg_resources
from six.moves import urllib
from nose.tools import ok_
LOG = logging.getLogger(__name__)
MOD_DIR = os.path.dirname('file://' + sys.modules[__name__].__file__)
_RYU_REQUIREMENTS_FILES = [
'../../../tools/pip-requires',
'../../../tools/optional-requires',
]
RYU_REQUIREMENTS_FILES = [
os.path.join(MOD_DIR, f) for f in _RYU_REQUIREMENTS_FILES]
OPENSTACK_REQUIREMENTS_REPO = 'https://github.com/openstack/requirements'
OPENSTACK_REQUIREMENTS_URL = (
'https://github.com/openstack/requirements/raw/master/')
_OPENSTACK_REQUIREMENTS_FILES = [
'requirements.txt',
'global-requirements.txt',
]
OPENSTACK_REQUIREMENTS_FILES = [
urllib.parse.urljoin(OPENSTACK_REQUIREMENTS_URL, f)
for f in _OPENSTACK_REQUIREMENTS_FILES]
def _get_requirements(files):
requirements = {}
for f in files:
response = urllib.request.urlopen(f)
contents = response.read().decode('utf-8')
for r in pkg_resources.parse_requirements(contents):
requirements[r.name] = str(r)
return requirements
OPENSTACK_REQUIREMENTS = _get_requirements(OPENSTACK_REQUIREMENTS_FILES)
RYU_REQUIREMENTS = _get_requirements(RYU_REQUIREMENTS_FILES)
class TestRequirements(unittest.TestCase):
"""
Test whether the requirements of Ryu has no conflict with that
of other projects.
"""
def setUp(self):
pass
def tearDown(self):
pass
def test_with_openstack_requirements(self):
try:
for name, req in OPENSTACK_REQUIREMENTS.items():
if name in RYU_REQUIREMENTS:
ok_(pkg_resources.require(req))
except pkg_resources.VersionConflict as e:
LOG.exception(
'Some requirements of Ryu are conflicting with that of '
'OpenStack project: %s', OPENSTACK_REQUIREMENTS_REPO)
raise e

View File

@ -13,12 +13,12 @@ classifier =
Topic :: System :: Networking
Natural Language :: English
Programming Language :: Python
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Programming Language :: Python :: 3.4
Programming Language :: Python :: 3.5
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Operating System :: Unix
keywords =
openflow
@ -55,3 +55,13 @@ setup-hooks =
console_scripts =
ryu-manager = ryu.cmd.manager:main
ryu = ryu.cmd.ryu_base:main
[pytype]
inputs =
ryu/controller/
ryu/ofproto/ofproto_v1_3*
disable =
import-error
module-attr
keep-going =
1

View File

@ -14,12 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# a bug workaround. http://bugs.python.org/issue15881
try:
import multiprocessing
except ImportError:
pass
import setuptools
import ryu.hooks

View File

@ -70,29 +70,29 @@ def check_dependencies():
if not HAS_VIRTUALENV:
raise Exception('Virtualenv not found. ' + \
'Try installing python-virtualenv')
print 'done.'
print('done.') # pylint: disable=print-statement
def create_virtualenv(venv=VENV, install_pip=False):
"""Creates the virtual environment and installs PIP only into the
virtual environment
"""
print 'Creating venv...',
print('Creating venv...') # pylint: disable=print-statement
install = ['virtualenv', '-q', venv]
run_command(install)
print 'done.'
print 'Installing pip in virtualenv...',
print('done.') # pylint: disable=print-statement
print('Installing pip in virtualenv...') # pylint: disable=print-statement
if install_pip and \
not run_command(['tools/with_venv.sh', 'easy_install',
'pip>1.0']):
die("Failed to install pip.")
print 'done.'
print('done.') # pylint: disable=print-statement
def install_dependencies(venv=VENV):
print 'Installing dependencies with pip (this can take a while)...'
print('Installing dependencies with pip (this can take a while)...') # pylint: disable=print-statement
run_command(['tools/with_venv.sh', 'pip', 'install', '-r',
PIP_REQUIRES], redirect_output=False)
run_command(['tools/with_venv.sh', 'pip', 'install', '-r',
@ -126,7 +126,7 @@ def print_help():
Also, make test will automatically use the virtualenv.
"""
print help
print(help) # pylint: disable=print-statement
def main(argv):

View File

@ -1,12 +1,14 @@
-r ../pip-requirements.txt
# NOTE: OpenStack avoids some versions of eventlet, because of the
# following issue.
# https://github.com/eventlet/eventlet/issues/401
eventlet!=0.18.3,>=0.18.2,!=0.20.1,!=0.21.0,!=0.23.0
msgpack>=0.3.0 # RPC library, BGP speaker(net_cntl)
eventlet==0.31.1
msgpack>=0.4.0 # RPC library, BGP speaker(net_cntl)
netaddr
oslo.config>=2.5.0
ovs>=2.6.0 # OVSDB
packaging==20.9
routes # wsgi
six>=1.4.0
tinyrpc==0.9.4 # RPC library, BGP speaker(net_cntl)
tinyrpc==1.0.4 # RPC library, BGP speaker(net_cntl)
webob>=1.2 # wsgi

View File

@ -4,4 +4,5 @@ mock
nose
pycodestyle
pylint
pytype
formencode

23
tox.ini
View File

@ -1,5 +1,13 @@
[tox]
envlist = py27,py34,py35,py36,py37,pypy,pycodestyle,autopep8
envlist = py35,py36,py37,py38,py39,pypy,pycodestyle,autopep8,pytype
[gh-actions]
python =
3.5: py35
3.6: py36, pycodestyle, autopep8, pytype
3.7: py37
3.8: py38
3.9: py39
[testenv]
deps =
@ -20,11 +28,6 @@ commands =
commands =
python ryu/tests/integrated/run_test.py
[testenv:py27]
commands =
{[testenv]commands}
{[testenv:scenario]commands}
[testenv:py36]
commands =
{[testenv]commands}
@ -50,6 +53,14 @@ deps =
commands =
bash -c 'test -z "$(autopep8 --recursive --diff ryu/)"'
[testenv:pytype]
deps =
-U
--no-cache-dir
pytype
commands =
pytype --jobs 2
[pycodestyle]
exclude = pbr-*,.venv,.tox,.git,doc,dist,tools,vcsversion.py,.pyc,ryu/contrib
# W503: line break before binary operator