diff --git a/.github/workflows/tests-unit.yml b/.github/workflows/tests-unit.yml new file mode 100644 index 00000000..866f151f --- /dev/null +++ b/.github/workflows/tests-unit.yml @@ -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 diff --git a/.renovaterc.json b/.renovaterc.json new file mode 100644 index 00000000..e1e004b7 --- /dev/null +++ b/.renovaterc.json @@ -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" + ] +} diff --git a/.stickler.yml b/.stickler.yml new file mode 100644 index 00000000..7fcbba89 --- /dev/null +++ b/.stickler.yml @@ -0,0 +1,9 @@ +--- +linters: + flake8: + python: 3 + max-line-length: 120 + pep8: + python: 3 + max-line-length: 120 + py3k: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 712ffd12..00000000 --- a/.travis.yml +++ /dev/null @@ -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 diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index f57d074f..c2ce5ca3 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -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: diff --git a/debian/control b/debian/control index 0d1c1cc5..fd5af74a 100644 --- a/debian/control +++ b/debian/control @@ -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, diff --git a/doc/source/writing_ryu_app.rst b/doc/source/writing_ryu_app.rst index a4d1830a..79d7528e 100644 --- a/doc/source/writing_ryu_app.rst +++ b/doc/source/writing_ryu_app.rst @@ -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) diff --git a/pip-requirements.txt b/pip-requirements.txt new file mode 100644 index 00000000..a9a3cb75 --- /dev/null +++ b/pip-requirements.txt @@ -0,0 +1 @@ +pip==20.3.4 diff --git a/ryu/app/wsgi.py b/ryu/app/wsgi.py index 0b98df94..9c171e4f 100644 --- a/ryu/app/wsgi.py +++ b/ryu/app/wsgi.py @@ -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 diff --git a/ryu/controller/controller.py b/ryu/controller/controller.py index b3d2d35b..1e86ed56 100644 --- a/ryu/controller/controller.py +++ b/ryu/controller/controller.py @@ -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) diff --git a/ryu/controller/handler.py b/ryu/controller/handler.py index cda27bcc..c4f13283 100644 --- a/ryu/controller/handler.py +++ b/ryu/controller/handler.py @@ -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 diff --git a/ryu/controller/mac_to_port.py b/ryu/controller/mac_to_port.py index 98472a2f..12b08bf3 100644 --- a/ryu/controller/mac_to_port.py +++ b/ryu/controller/mac_to_port.py @@ -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): diff --git a/ryu/controller/ofp_api.py b/ryu/controller/ofp_api.py index 7a8f5f49..4b9ee737 100644 --- a/ryu/controller/ofp_api.py +++ b/ryu/controller/ofp_api.py @@ -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) diff --git a/ryu/lib/hub.py b/ryu/lib/hub.py index e847f656..cac989a5 100644 --- a/ryu/lib/hub.py +++ b/ryu/lib/hub.py @@ -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 diff --git a/ryu/lib/ovs/bridge.py b/ryu/lib/ovs/bridge.py index f86e9ae0..1bfb9c07 100644 --- a/ryu/lib/ovs/bridge.py +++ b/ryu/lib/ovs/bridge.py @@ -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): """ diff --git a/ryu/lib/packet/cfm.py b/ryu/lib/packet/cfm.py index 0e8c2cf1..cbf9999a 100644 --- a/ryu/lib/packet/cfm.py +++ b/ryu/lib/packet/cfm.py @@ -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 diff --git a/ryu/lib/packet/icmpv6.py b/ryu/lib/packet/icmpv6.py index a391c087..0c228382 100644 --- a/ryu/lib/packet/icmpv6.py +++ b/ryu/lib/packet/icmpv6.py @@ -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 diff --git a/ryu/lib/rpc.py b/ryu/lib/rpc.py index 7db0ebeb..f74f8846 100644 --- a/ryu/lib/rpc.py +++ b/ryu/lib/rpc.py @@ -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): diff --git a/ryu/ofproto/nx_actions.py b/ryu/ofproto/nx_actions.py index bdb36406..f3f28bfe 100644 --- a/ryu/ofproto/nx_actions.py +++ b/ryu/ofproto/nx_actions.py @@ -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 diff --git a/ryu/ofproto/ofproto_v1_3_parser.py b/ryu/ofproto/ofproto_v1_3_parser.py index 0324c82b..34c49a3c 100644 --- a/ryu/ofproto/ofproto_v1_3_parser.py +++ b/ryu/ofproto/ofproto_v1_3_parser.py @@ -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): diff --git a/ryu/services/protocols/bgp/net_ctrl.py b/ryu/services/protocols/bgp/net_ctrl.py index 92a8e71e..5c79d3f8 100644 --- a/ryu/services/protocols/bgp/net_ctrl.py +++ b/ryu/services/protocols/bgp/net_ctrl.py @@ -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 diff --git a/ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh b/ryu/tests/integrated/common/install_docker_test_pkg_for_github_actions.sh similarity index 100% rename from ryu/tests/integrated/common/install_docker_test_pkg_for_travis.sh rename to ryu/tests/integrated/common/install_docker_test_pkg_for_github_actions.sh diff --git a/ryu/tests/unit/controller/test_controller.py b/ryu/tests/unit/controller/test_controller.py index 45c659c9..ce09ac7e 100644 --- a/ryu/tests/unit/controller/test_controller.py +++ b/ryu/tests/unit/controller/test_controller.py @@ -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() diff --git a/ryu/tests/unit/lib/ovs/test_vsctl.py b/ryu/tests/unit/lib/ovs/test_vsctl.py index 84698eec..92e00f62 100644 --- a/ryu/tests/unit/lib/ovs/test_vsctl.py +++ b/ryu/tests/unit/lib/ovs/test_vsctl.py @@ -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): diff --git a/ryu/tests/unit/lib/test_ofctl_action_match.py b/ryu/tests/unit/lib/test_ofctl_action_match.py index 11e27f78..9e9f2e46 100644 --- a/ryu/tests/unit/lib/test_ofctl_action_match.py +++ b/ryu/tests/unit/lib/test_ofctl_action_match.py @@ -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) diff --git a/ryu/tests/unit/test_requirements.py b/ryu/tests/unit/test_requirements.py deleted file mode 100644 index c842a0f0..00000000 --- a/ryu/tests/unit/test_requirements.py +++ /dev/null @@ -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 diff --git a/setup.cfg b/setup.cfg index c3757abb..e1ff80cf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/setup.py b/setup.py index cf2a404d..fee79eb1 100644 --- a/setup.py +++ b/setup.py @@ -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 diff --git a/tools/install_venv.py b/tools/install_venv.py index 29639801..13a5bc17 100644 --- a/tools/install_venv.py +++ b/tools/install_venv.py @@ -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): diff --git a/tools/pip-requires b/tools/pip-requires index 56020060..714cb3a6 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -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 diff --git a/tools/test-requires b/tools/test-requires index e0503381..dfef2215 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -4,4 +4,5 @@ mock nose pycodestyle pylint +pytype formencode diff --git a/tox.ini b/tox.ini index 65136233..04ebe1c1 100644 --- a/tox.ini +++ b/tox.ini @@ -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