Currently there is no notion of group/set of RyuApps which makes sense as
a whole. Thus it is difficult to run a correct set of RyuApps.
So introduce the notion of bundle, a group of RyuApps and subprocesses.
And allow ryu-manager to run bundle.
This simplifies command line of ryu-manager.
For example
./bin/ryu-manager ryu/bundle/os-gre.json
instead of
./bin/ryu-manager ryu/app/gre_tunnel.py \
ryu/app/quantum_adapter.py \
ryu/app/rest.py ryu/app/rest_conf_switch.py \
ryu/app/rest_tunnel.py \
ryu/app/tunnel_port_updater.py \
ryu/app/rest_quantum.py
Signed-off-by: Isaku Yamahata <[email protected]>
---
ryu/base/app_manager.py | 219 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 197 insertions(+), 22 deletions(-)
diff --git a/ryu/base/app_manager.py b/ryu/base/app_manager.py
index 9709657..75bceee 100644
--- a/ryu/base/app_manager.py
+++ b/ryu/base/app_manager.py
@@ -16,8 +16,12 @@
import inspect
import itertools
+import json
import logging
+import subprocess
+import traceback
+from ryu import exception as ryu_exc
from ryu import utils
from ryu.controller.handler import register_instance
from ryu.controller.controller import Datapath
@@ -29,6 +33,35 @@ LOG = logging.getLogger('ryu.base.app_manager')
SERVICE_BRICKS = {}
+def _load_cls(parent_clses, name):
+ try:
+ mod = utils.import_module(name)
+ except ImportError:
+ try:
+ cls = utils.import_class(name)
+ except ImportError, e:
+ LOG.debug('ImportError %s %s', e, traceback.format_exc())
+ return None
+ if not issubclass(cls, parent_clses):
+ return None
+ return cls
+
+ clses = inspect.getmembers(
+ mod, lambda cls: (inspect.isclass(cls) and
+ issubclass(cls, parent_clses)))
+ if clses:
+ return clses[0][1]
+ return None
+
+
+def _load_bundle_cls(name):
+ return _load_cls(RyuBundle, name)
+
+
+def _load_app_cls(name):
+ return _load_cls(RyuApp, name)
+
+
def lookup_service_brick(name):
return SERVICE_BRICKS.get(name)
@@ -136,6 +169,133 @@ class RyuApp(object):
pass
+class RyuSubProcessApp(RyuApp):
+ _ARGS = []
+
+ @classmethod
+ def create_cls(cls, name, args):
+ # name might be unicode
+ return type(str(name), (cls,), {'_ARGS': args})
+
+ def __init__(self, *args, **kwargs):
+ assert self.__class__ != RyuSubProcessApp
+ super(RyuSubProcessApp, self).__init__(*args, **kwargs)
+ self.pipe = None
+ self.start()
+
+ def start(self):
+ LOG.debug('starting %s', self._ARGS)
+ self.pipe = subprocess.Popen(self._ARGS, close_fds=True)
+
+ def stop(self):
+ self.pipe.kill()
+ self.pipe.wait()
+ self.pipe = None
+
+ def close(self):
+ self.stop()
+
+
+class BadFormat(ryu_exc.RyuException):
+ message = 'bad config foramt'
+
+
+class RyuBundle(object):
+ _PARSERS = {}
+
+ @staticmethod
+ def register_parser(suffix):
+ def _register_parser(cls):
+ assert suffix not in RyuBundle._PARSERS
+ RyuBundle._PARSERS[suffix] = cls
+ return cls
+ return _register_parser
+
+ _RYU_APPS = {}
+ _RYU_SUBPROCS = {}
+ NAME = None
+
+ @staticmethod
+ def read_config(config_path):
+ LOG.debug('read_config %s %s', config_path, RyuBundle._PARSERS)
+ for suffix, cls in RyuBundle._PARSERS.items():
+ if not config_path.endswith(suffix):
+ continue
+ LOG.debug('read_config %s %s', suffix, cls)
+ f = file(config_path, 'r')
+ bundle = cls()
+ try:
+ bundle.parser(f)
+ except (ValueError, BadFormat) as e:
+ LOG.debug('parse %s %s', e, traceback.format_exc())
+ continue
+ return bundle
+
+ return None
+
+ def __init__(self):
+ super(RyuBundle, self).__init__()
+
+ def parser(self, config):
+ raise NotImplementedError('sub class must implement this '
+ 'or never get called')
+
+ def load_apps(self):
+ # return dict of {'name': cls}
+ dict_ = {}
+ for name, cls in self._RYU_APPS.items():
+ if not inspect.isclass(cls):
+ cls_ = _load_app_cls(cls)
+ if cls_ is None:
+ raise ImportError('cls %s can not be loaded' % cls)
+ cls = cls_
+
+ dict_[name] = cls
+
+ for name, args in self._RYU_SUBPROCS.iteritems():
+ cls = RyuSubProcessApp.create_cls(name, args)
+ dict_[name] = cls
+
+ return dict_
+
+
[email protected]_parser('.json')
+class RyuBundleJson(RyuBundle):
+ def parser(self, f):
+ buf = f.read()
+ conf = json.loads(buf)
+ if not isinstance(conf, dict):
+ raise BadFormat(json.dumps(conf))
+
+ name = conf.get('name')
+ LOG.debug('RyuBundleJson %s', name)
+ if not isinstance(name, basestring):
+ raise BadFormat(json.dumps(name))
+
+ ryu_apps = conf.get('ryu_apps', {})
+ LOG.debug('ryu_apps %s', ryu_apps)
+ if not isinstance(ryu_apps, dict):
+ raise BadFormat(json.dumps(ryu_apps))
+ for value in ryu_apps.values():
+ if not isinstance(value, basestring):
+ raise BadFormat(json.dumps(value))
+
+ ryu_subprocs = conf.get('ryu_subprocs', {})
+ LOG.debug('ryu_subprocs %s', ryu_subprocs)
+ if not isinstance(ryu_subprocs, dict):
+ raise BadFormat(json.dumps(ryu_subprocs))
+ for value in ryu_subprocs.values():
+ if not isinstance(value, list):
+ raise BadFormat(json.dumps(value))
+ for arg in value:
+ if not isinstance(arg, basestring):
+ raise BadFormat(json.dumps(arg))
+
+ self.NAME = name
+ self._RYU_APPS = ryu_apps
+ self._RYU_SUBPROCS = ryu_subprocs
+
+
class AppManager(object):
def __init__(self):
self.applications_cls = {}
@@ -143,34 +303,49 @@ class AppManager(object):
self.contexts_cls = {}
self.contexts = {}
- def load_app(self, name):
- mod = utils.import_module(name)
- clses = inspect.getmembers(mod, lambda cls: (inspect.isclass(cls) and
- issubclass(cls, RyuApp)))
- if clses:
- return clses[0][1]
- return None
+ def _setup_app(self, app_cls_name, app_cls):
+ # for now, only single instance of a given module
+ # Do we need to support multiple instances?
+ # Yes, maybe for slicing.
+ assert app_cls_name not in self.applications_cls
+ self.applications_cls[app_cls_name] = app_cls
- def load_apps(self, app_lists):
- for app_cls_name in itertools.chain.from_iterable([app_list.split(',')
- for app_list
- in app_lists]):
- LOG.info('loading app %s', app_cls_name)
+ for key, context_cls in app_cls.context_iteritems():
+ cls = self.contexts_cls.setdefault(key, context_cls)
+ assert cls == context_cls
- # for now, only single instance of a given module
- # Do we need to support multiple instances?
- # Yes, maybe for slicing.
- assert app_cls_name not in self.applications_cls
+ def load_apps(self, app_lists):
+ bundles = {}
+ bundle_loaded_apps = {}
+ for cls_name in itertools.chain.from_iterable([app_list.split(',')
+ for app_list
+ in app_lists]):
+ LOG.info('loading app %s', cls_name)
+
+ bundle = RyuBundle.read_config(cls_name)
+ if bundle is None:
+ cls = _load_bundle_cls(cls_name)
+ if cls is not None:
+ bundle = cls()
+
+ if bundle is not None:
+ assert bundle.NAME not in bundles
+ bundles[bundle.NAME] = bundle
+ for app_name, app_cls in bundle.load_apps().items():
+ bundle_loaded_apps.setdefault(app_name, app_cls)
+ assert bundle_loaded_apps[app_name] == app_cls
+ continue
- cls = self.load_app(app_cls_name)
- if cls is None:
+ cls = _load_app_cls(cls_name)
+ if cls is not None:
+ self._setup_app(cls_name, cls)
continue
- self.applications_cls[app_cls_name] = cls
+ LOG.error('can not load %s', cls_name)
- for key, context_cls in cls.context_iteritems():
- cls = self.contexts_cls.setdefault(key, context_cls)
- assert cls == context_cls
+ LOG.debug('bundle_loaded_apps %s', bundle_loaded_apps)
+ for cls_name, cls in bundle_loaded_apps.iteritems():
+ self._setup_app(cls_name, cls)
def create_contexts(self):
for key, cls in self.contexts_cls.items():
--
1.7.10.4
------------------------------------------------------------------------------
Try New Relic Now & We'll Send You this Cool Shirt
New Relic is the only SaaS-based application performance monitoring service
that delivers powerful full stack analytics. Optimize and monitor your
browser, app, & servers with just a few lines of code. Try New Relic
and get this awesome Nerd Life shirt! http://p.sf.net/sfu/newrelic_d2d_may
_______________________________________________
Ryu-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/ryu-devel