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

Reply via email to