diff options
-rw-r--r-- | ryu/lib/stringify.py | 210 | ||||
-rw-r--r-- | ryu/ofproto/ofproto_parser.py | 50 |
2 files changed, 236 insertions, 24 deletions
diff --git a/ryu/lib/stringify.py b/ryu/lib/stringify.py new file mode 100644 index 00000000..3e1b41b9 --- /dev/null +++ b/ryu/lib/stringify.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python +# +# Copyright (C) 2013 Nippon Telegraph and Telephone Corporation. +# Copyright (C) 2013 YAMAMOTO Takashi <yamamoto 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. + +import base64 +import collections +import inspect + + +# Some arguments to __init__ is mungled in order to avoid name conflicts +# with builtin names. +# The standard mangling is to append '_' in order to avoid name clashes +# with reserved keywords. +# +# PEP8: +# Function and method arguments +# If a function argument's name clashes with a reserved keyword, +# it is generally better to append a single trailing underscore +# rather than use an abbreviation or spelling corruption. Thus +# class_ is better than clss. (Perhaps better is to avoid such +# clashes by using a synonym.) +# +# grep __init__ *.py | grep '[^_]_\>' showed that +# 'len', 'property', 'set', 'type' +# A bit more generic way is adopted +import __builtin__ +_RESERVED_KEYWORD = dir(__builtin__) + + +_mapdict = lambda f, d: dict([(k, f(v)) for k, v in d.items()]) +_mapdict_key = lambda f, d: dict([(f(k), v) for k, v in d.items()]) + + +class StringifyMixin(object): + _class_prefixes = [] + + def stringify_attrs(self): + """an override point for sub classes""" + return obj_python_attrs(self) + + def __str__(self): + buf = '' + sep = '' + for k, v in self.stringify_attrs(): + buf += sep + buf += "%s=%s" % (k, repr(v)) # repr() to escape binaries + sep = ',' + return self.__class__.__name__ + '(' + buf + ')' + __repr__ = __str__ # note: str(list) uses __repr__ for elements + + @classmethod + def _is_class(cls, dict_): + # we distinguish a dict like OFPSwitchFeatures.ports + # from OFPxxx classes using heuristics. + # exmples of OFP classes: + # {"OFPMatch": { ... }} + # {"MTIPv6SRC": { ... }} + assert isinstance(dict_, dict) + if len(dict_) != 1: + return False + k = dict_.keys()[0] + if not isinstance(k, (bytes, unicode)): + return False + for p in cls._class_prefixes: + if k.startswith(p): + return True + return False + + @classmethod + def _encode_value(cls, v, encode_string=base64.b64encode): + encode = lambda x: cls._encode_value(x, encode_string) + if isinstance(v, (bytes, unicode)): + json_value = encode_string(v) + elif isinstance(v, list): + json_value = map(encode, v) + elif isinstance(v, dict): + json_value = _mapdict(encode, v) + # while a python dict key can be any hashable object, + # a JSON object key should be a string. + json_value = _mapdict_key(str, json_value) + assert not cls._is_class(json_value) + else: + try: + json_value = v.to_jsondict() + except: + json_value = v + return json_value + + def to_jsondict(self, encode_string=base64.b64encode): + """returns an object to feed json.dumps() + """ + dict_ = {} + encode = lambda x: self._encode_value(x, encode_string) + for k, v in obj_attrs(self): + dict_[k] = encode(v) + return {self.__class__.__name__: dict_} + + @classmethod + def cls_from_jsondict_key(cls, k): + # find a class with the given name from our class' module. + import sys + mod = sys.modules[cls.__module__] + return getattr(mod, k) + + @classmethod + def obj_from_jsondict(cls, jsondict): + assert len(jsondict) == 1 + for k, v in jsondict.iteritems(): + obj_cls = cls.cls_from_jsondict_key(k) + return obj_cls.from_jsondict(v) + + @classmethod + def _decode_value(cls, json_value, decode_string=base64.b64decode): + decode = lambda x: cls._decode_value(x, decode_string) + if isinstance(json_value, (bytes, unicode)): + v = decode_string(json_value) + elif isinstance(json_value, list): + v = map(decode, json_value) + elif isinstance(json_value, dict): + if cls._is_class(json_value): + v = cls.obj_from_jsondict(json_value) + else: + v = _mapdict(decode, json_value) + # XXXhack + # try to restore integer keys used by OFPSwitchFeatures.ports. + try: + v = _mapdict_key(int, v) + except ValueError: + pass + else: + v = json_value + return v + + @staticmethod + def _restore_args(dict_): + def restore(k): + if k in _RESERVED_KEYWORD: + return k + '_' + return k + return _mapdict_key(restore, dict_) + + @classmethod + def from_jsondict(cls, dict_, decode_string=base64.b64decode, + **additional_args): + """create an instance from a result of json.loads() + """ + decode = lambda x: cls._decode_value(x, decode_string) + kwargs = cls._restore_args(_mapdict(decode, dict_)) + try: + return cls(**dict(kwargs, **additional_args)) + except TypeError: + #debug + print "CLS", cls + print "ARG", dict_ + print "KWARG", kwargs + raise + + +def obj_python_attrs(msg_): + """iterate object attributes for stringify purposes + """ + + # a special case for namedtuple which seems widely used in + # ofp parser implementations. + if hasattr(msg_, '_fields'): + for k in msg_._fields: + yield(k, getattr(msg_, k)) + return + base = getattr(msg_, '_base_attributes', []) + for k, v in inspect.getmembers(msg_): + if k.startswith('_'): + continue + if callable(v): + continue + if k in base: + continue + if hasattr(msg_.__class__, k): + continue + yield (k, v) + + +def obj_attrs(msg_): + """similar to obj_python_attrs() but deals with python reserved keywords + """ + + if isinstance(msg_, StringifyMixin): + iter = msg_.stringify_attrs() + else: + # probably called by msg_str_attr + iter = obj_python_attrs(msg_) + for k, v in iter: + if k.endswith('_') and k[:-1] in _RESERVED_KEYWORD: + # XXX currently only StringifyMixin has restoring logic + assert isinstance(msg_, StringifyMixin) + k = k[:-1] + yield (k, v) diff --git a/ryu/ofproto/ofproto_parser.py b/ryu/ofproto/ofproto_parser.py index 73a6d9e4..8cb04024 100644 --- a/ryu/ofproto/ofproto_parser.py +++ b/ryu/ofproto/ofproto_parser.py @@ -14,12 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +import collections import logging import struct +import sys import functools -import inspect from ryu import exception +from ryu.lib import stringify from . import ofproto_common @@ -61,16 +63,23 @@ def create_list_of_base_attributes(f): return wrapper -class StringifyMixin(object): - def __str__(self): - buf = '' - sep = '' - for k, v in ofp_attrs(self): - buf += sep - buf += "%s=%s" % (k, repr(v)) # repr() to escape binaries - sep = ',' - return self.__class__.__name__ + '(' + buf + ')' - __repr__ = __str__ # note: str(list) uses __repr__ for elements +def ofp_msg_from_jsondict(dp, jsondict): + parser = dp.ofproto_parser + assert len(jsondict) == 1 + for k, v in jsondict.iteritems(): + cls = getattr(parser, k) + assert issubclass(cls, MsgBase) + return cls.from_jsondict(v, datapath=dp) + + +class StringifyMixin(stringify.StringifyMixin): + _class_prefixes = ["OFP", "MT"] + + @classmethod + def cls_from_jsondict_key(cls, k): + obj_cls = super(StringifyMixin, cls).cls_from_jsondict_key(k) + assert not issubclass(obj_cls, MsgBase) + return obj_cls class MsgBase(StringifyMixin): @@ -161,23 +170,16 @@ def msg_pack_into(fmt, buf, offset, *args): struct.pack_into(fmt, buf, offset, *args) -def ofp_attrs(msg_): - base = getattr(msg_, '_base_attributes', []) - for k, v in inspect.getmembers(msg_): - if k.startswith('_'): - continue - if callable(v): - continue - if k in base: - continue - if hasattr(msg_.__class__, k): - continue - yield (k, v) +def namedtuple(typename, fields, **kwargs): + class _namedtuple(StringifyMixin, + collections.namedtuple(typename, fields, **kwargs)): + pass + return _namedtuple def msg_str_attr(msg_, buf, attr_list=None): if attr_list is None: - attr_list = ofp_attrs(msg_) + attr_list = stringify.obj_attrs(msg_) for attr in attr_list: val = getattr(msg_, attr, None) if val is not None: |