Original repo:
https://github.com/TakeshiTseng/ryu-dynamic-loader

We developed a ryu application as a plugin and use this plugin to
control ryu application manager(AppManager).

This plugin allow users to install/uninstall ryu application
dynamically without restart ryu-manager.

To use this plugin, start ryu application with dal_plugin

$ ryu-manager ryu.app.dal_plugin ryu.controller.ofp_handler

And start ryu-cli
$ ryu-cli

Now command line provide 3 commands:
1. list: list all available ryu application from ryu.app module
2. install: install a ryu application by app id
3. uninstall: uninstall a ryu application by app id

Contributors:
@TakeshiTseng : develop plugin and cli
@John-Lin : add error handling and prompt

-- 
Yi Tseng (a.k.a Takeshi)
Taiwan National Chiao Tung University
Department of Computer Science
W2CNLab

http://blog.takeshi.tw
From 68360c99d4f0d1ffff6692466629098c023e2ea6 Mon Sep 17 00:00:00 2001
From: Takeshi <a86487...@gmail.com>
Date: Thu, 20 Aug 2015 15:53:26 +0800
Subject: [PATCH] Add dynamic application loader for ryu

Original repo:
https://github.com/TakeshiTseng/ryu-dynamic-loader

We developed a ryu application as a plugin and use this plugin to
control ryu application manager(AppManager).

This plugin allow users to install/uninstall ryu application
dynamically without restart ryu-manager.

To use this plugin, start ryu application with dal_plugin

$ ryu-manager ryu.app.dal_plugin ryu.controller.ofp_handler

And start ryu-cli
$ ryu-cli

Now command line provide 3 commands:
1. list: list all available ryu application from ryu.app module
2. install: install a ryu application by app id
3. uninstall: uninstall a ryu application by app id

Contributors:
@TakeshiTseng : develop plugin and cli
@John-Lin : add error handling and prompt

Signed-off-by: Takeshi <a86487...@gmail.com>
Signed-off-by: John-Lin <linton...@gmail.com>
---
 bin/ryu-cli           |  19 +++++
 ryu/app/dal_plugin.py | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++
 ryu/cmd/ryu_cli.py    | 174 +++++++++++++++++++++++++++++++++++++++++++
 ryu/lib/dal_lib.py    |  63 ++++++++++++++++
 setup.cfg             |   1 +
 5 files changed, 460 insertions(+)
 create mode 100755 bin/ryu-cli
 create mode 100644 ryu/app/dal_plugin.py
 create mode 100755 ryu/cmd/ryu_cli.py
 create mode 100644 ryu/lib/dal_lib.py

diff --git a/bin/ryu-cli b/bin/ryu-cli
new file mode 100755
index 0000000..61ccfde
--- /dev/null
+++ b/bin/ryu-cli
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2011, 2012 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.
+
+from ryu.cmd.ryu_cli import main
+main()
diff --git a/ryu/app/dal_plugin.py b/ryu/app/dal_plugin.py
new file mode 100644
index 0000000..8be1573
--- /dev/null
+++ b/ryu/app/dal_plugin.py
@@ -0,0 +1,203 @@
+# -*- codeing: utf-8 -*-
+import logging
+import pkgutil
+import inspect
+
+from ryu import app as ryu_app
+from ryu.lib import hub
+from ryu.lib.dal_lib import DLController
+from ryu.app.wsgi import WSGIApplication
+from ryu.base import app_manager
+from ryu.base.app_manager import RyuApp, AppManager
+from ryu.controller.handler import MAIN_DISPATCHER
+from ryu.controller.handler import set_ev_cls
+
+
+LOG = logging.getLogger('DynamicLoader')
+
+def deep_import(mod_name):
+    mod = __import__(mod_name)
+    components = mod_name.split('.')
+    for comp in components[1:]:
+        mod = getattr(mod, comp)
+    return mod
+
+class DynamicLoader(RyuApp):
+
+    def __init__(self, *args, **kwargs):
+        super(DynamicLoader, self).__init__(*args, **kwargs)
+        self.ryu_mgr = AppManager.get_instance()
+        self.available_app = []
+        self.init_apps()
+        wsgi = self.create_wsgi_app('0.0.0.0', 5566)
+        mapper = wsgi.mapper
+        wsgi.registory['DLController'] = self
+
+        self.init_mapper(mapper)
+
+    def create_wsgi_app(self, host, port):
+        wsgi = WSGIApplication()
+        webapp = hub.WSGIServer((host, port), wsgi)
+        hub.spawn(webapp.serve_forever)
+        return wsgi
+
+
+    def init_apps(self):
+        # init all available apps
+        for _, name, is_pkg in pkgutil.walk_packages(ryu_app.__path__):
+            LOG.debug(
+                'Find %s : %s',
+                'package' if is_pkg else 'module',
+                name)
+
+            if is_pkg:
+                continue
+
+            try:
+                _app_module = deep_import('ryu.app.' + name)
+
+                for _attr_name in dir(_app_module):
+                    _attr = getattr(_app_module, _attr_name)
+
+                    if inspect.isclass(_attr) and _attr.__bases__[0] == RyuApp:
+                        LOG.debug('\tFind ryu app : %s.%s',
+                            _attr.__module__,
+                            _attr.__name__)
+                        _full_name = '%s.%s' % (_attr.__module__, _attr.__name__)
+                        self.available_app.append((_full_name, _attr))
+
+            except ImportError:
+                LOG.debug('Import Error')
+
+    def init_mapper(self, mapper):
+        mapper.connect('list', '/list', controller=DLController,
+                       action='list_all_apps',
+                       conditions=dict(method=['GET']))
+
+        mapper.connect('list', '/install', controller=DLController,
+                       action='install_app',
+                       conditions=dict(method=['POST']))
+
+        mapper.connect('list', '/uninstall', controller=DLController,
+                       action='uninstall_app',
+                       conditions=dict(method=['POST']))
+
+    def create_context(self, key, cls):
+        context = None
+
+        if issubclass(cls, RyuApp):
+            context = self.ryu_mgr._instantiate(None, cls)
+        else:
+            context = cls()
+
+        LOG.info('creating context %s', key)
+
+        if key in self.ryu_mgr.contexts:
+            return None
+
+        self.ryu_mgr.contexts.setdefault(key, context)
+        return context
+
+
+    def list_all_apps(self):
+        res = []
+        installed_apps = self.ryu_mgr.applications
+
+        for app_info in self.available_app:
+            _cls = app_info[1]
+            installed_apps_cls =\
+                [obj.__class__ for obj in installed_apps.values()]
+
+            if _cls in installed_apps_cls:
+                res.append({'name': app_info[0], 'installed': True})
+
+            else:
+                res.append({'name': app_info[0], 'installed': False})
+
+        return res
+
+
+    def install_app(self, app_id):
+        try:
+            app_cls = self.available_app[app_id][1]
+            app_contexts = app_cls._CONTEXTS
+            installed_apps = self.ryu_mgr.applications
+            installed_apps_cls =\
+                [obj.__class__ for obj in installed_apps.values()]
+
+            if app_cls in installed_apps_cls:
+                # app was installed
+                LOG.debug('Application already installed')
+                return
+            new_contexts = []
+
+            for k in app_contexts:
+                context_cls = app_contexts[k]
+                ctx = self.create_context(k, context_cls)
+
+                if ctx and issubclass(context_cls, RyuApp):
+                    new_contexts.append(ctx)
+
+            app = self.ryu_mgr.instantiate(app_cls, **self.ryu_mgr.contexts)
+            new_contexts.append(app)
+
+            for ctx in new_contexts:
+                t = ctx.start()
+                # t should be join to some where?
+
+        except IndexError:
+            LOG.debug('Can\'t find application with id %d', app_id)
+            ex = IndexError('Can\'t find application with id %d' % (app_id, ))
+            raise ex
+
+        except ValueError:
+            LOG.debug('ryu-app-id must be number')
+
+        except Exception, ex:
+            LOG.debug('Import error for id: %d', ex.app_id)
+            raise ex
+
+
+    def uninstall_app(self, app_id):
+        app_info = self.available_app[app_id]
+        # TODO: such dirty, fix it!
+        app_name = app_info[0].split('.')[-1]
+        if app_name not in self.ryu_mgr.applications:
+            raise ValueError('Can\'t find application')
+
+        app = self.ryu_mgr.applications[app_name]
+        self.ryu_mgr.uninstantiate(app_name)
+        app.stop()
+
+        # after we stoped application, chack it context
+        app_cls = app_info[1]
+        app_contexts = app_cls._CONTEXTS
+        installed_apps = self.ryu_mgr.applications
+        installed_apps_cls =\
+            [obj.__class__ for obj in installed_apps.values()]
+
+        for ctx_name in app_contexts:
+            for app_cls in installed_apps_cls:
+                if ctx_name in app_cls._CONTEXTS:
+                    break;
+
+            else:
+                # remove this context
+                ctx_cls = app_contexts[ctx_name]
+                ctx = self.ryu_mgr.contexts[ctx_name]
+                if issubclass(ctx_cls, RyuApp):
+                    ctx.stop()
+
+                if ctx.name in self.ryu_mgr.applications:
+                    del self.ryu_mgr.applications[ctx.name]
+
+                if ctx_name in self.ryu_mgr.contexts:
+                    del self.ryu_mgr.contexts[ctx_name]
+
+                if ctx.name in app_manager.SERVICE_BRICKS:
+                    del app_manager.SERVICE_BRICKS[ctx.name]
+
+                ctx.logger.info('Uninstall app %s successfully', ctx.name)
+
+                # handler hacking, remove all stream handler to avoid it log many times!
+                ctx.logger.handlers = []
diff --git a/ryu/cmd/ryu_cli.py b/ryu/cmd/ryu_cli.py
new file mode 100755
index 0000000..bf371e3
--- /dev/null
+++ b/ryu/cmd/ryu_cli.py
@@ -0,0 +1,174 @@
+#! /usr/bin/env python
+# -*- codeing: utf-8 -*-
+from __future__ import print_function
+
+import cmd
+import six
+import json
+
+if six.PY2:
+    import urllib2 as urllib
+
+else:
+    import urllib3 as urllib
+
+CLI_BASE_URL = 'http://127.0.0.1:5566'
+CLI_LIST_PATH = '/list'
+CLI_INSTALL_PATH = '/install'
+CLI_UNINSTALL_PATH = '/uninstall'
+
+def http_get(url):
+    '''
+    do http GET method
+    return python dictionary data
+
+    param:
+        url: url for http GET, string type
+    '''
+    result = None
+
+    if six.PY2:
+        try:
+            response = urllib.urlopen(url)
+        except urllib.URLError as e:
+            return e.reason
+        result = json.load(response)
+
+    else:
+        http = urllib.PoolManager()
+        response = http.request('GET', url)
+        result = json.loads(response.data.decode('utf8'))
+
+    return result
+
+def http_post(url, req_body):
+    '''
+    do http POST method
+    return python dictionary data
+
+    param:
+        url: url for http GET, string type
+        req_body: data to send, string type
+    '''
+    result = None
+
+    if six.PY2:
+        try:
+            response = urllib.urlopen(url, data=req_body)
+        except urllib.URLError as e:
+            return e.reason
+        result = json.load(response)
+
+    else:
+        http = urllib.PoolManager()
+        response = http.urlopen('POST', url, body=req_body)
+        result = json.load(response)
+
+    return result
+
+class DlCli(cmd.Cmd):
+    """
+    Ryu dynamic loader command line
+    """
+    intro = 'Welcome to the Ryu CLI. Type help or ? to list commands.\n'
+    prompt = '(ryu-cli) '
+
+    def do_list(self, line):
+        '''
+        List all available applications.
+        '''
+
+        app_list = http_get(CLI_BASE_URL + CLI_LIST_PATH)
+
+        if not type(app_list) == list:
+            print(app_list)
+            return False
+
+        app_id = 0
+
+        for app_info in app_list:
+            print('[%02d] %s' % (app_id, app_info['name']), end='')
+
+            if app_info['installed']:
+                print(' [\033[92minstalled\033[0m]')
+            else:
+                print('')
+
+            app_id += 1
+
+
+    def do_install(self, line):
+        '''
+        Install ryu application
+        Usage:
+            install [app_id]
+        '''
+        try:
+            app_id = int(line)
+        except ValueError:
+            print('Application id must be integer')
+            return
+
+        req_body = json.dumps({'app_id':app_id})
+        result = http_post(CLI_BASE_URL + CLI_INSTALL_PATH, req_body)
+
+        if not type(result) == dict:
+            print(result)
+            return
+
+        if result['result'] == 'ok':
+            print('Install successfully')
+
+        else:
+            print(result['details'])
+
+
+    def do_uninstall(self, line):
+        '''
+        Uninstall ryu application
+        Usage:
+            uninstall [app_id]
+        '''
+        try:
+            app_id = int(line)
+        except ValueError:
+            print('Application id must be integer')
+            return
+
+        req_body = json.dumps({'app_id':app_id})
+        result = http_post(CLI_BASE_URL + CLI_UNINSTALL_PATH, req_body)
+
+        if not type(result) == dict:
+            print(result)
+            return
+
+        if result['result'] == 'ok':
+            print('Uninstall successfully')
+
+        else:
+            print(result['details'])
+
+    def do_exit(self, line):
+        '''
+        Exit from command line
+        '''
+        return True
+
+    def do_EOF(self, line):
+        '''
+        Use ctrl + D to exit
+        '''
+        return True
+
+if __name__ == '__main__':
+    '''
+    Usage:
+        ./cli [Base url]
+
+        Base url: RESTful API server base url, default is http://127.0.0.1:5566
+    '''
+    import sys
+    if len(sys.argv) >= 2:
+        CLI_BASE_URL = sys.argv[1]
+
+    DlCli().cmdloop()
diff --git a/ryu/lib/dal_lib.py b/ryu/lib/dal_lib.py
new file mode 100644
index 0000000..2ca84d3
--- /dev/null
+++ b/ryu/lib/dal_lib.py
@@ -0,0 +1,63 @@
+# -*- codeing: utf-8 -*-
+import logging
+import json
+from webob import Response
+from ryu.app.wsgi import ControllerBase
+from ryu.app.wsgi import WSGIApplication
+
+# REST command template
+def rest_command(func):
+    def _rest_command(*args, **kwargs):
+        try:
+            msg = func(*args, **kwargs)
+            return Response(content_type='application/json',
+                            body=json.dumps(msg))
+
+        except SyntaxError as e:
+            details = e.msg
+        except (ValueError, NameError) as e:
+            details = e.message
+        except IndexError as e:
+            details = e.args[0]
+
+        msg = {'result': 'failure',
+               'details': details}
+        return Response(body=json.dumps(msg))
+
+    return _rest_command
+
+LOG = logging.getLogger('DLController')
+class DLController(ControllerBase):
+
+    def __init__(self, req, link, data, **config):
+        super(DLController, self).__init__(req, link, data, **config)
+        self.ryu_app = data
+
+    @rest_command
+    def list_all_apps(self, req, **_kwargs):
+        return self.ryu_app.list_all_apps()
+
+
+    @rest_command
+    def install_app(self, req, **_kwargs):
+        body = json.loads(req.body)
+        app_id = int(body['app_id'])
+
+        if app_id < 0:
+            e = ValueError('app id must grater than 0')
+            raise e
+
+        self.ryu_app.install_app(app_id)
+        return {'result': 'ok'}
+
+    @rest_command
+    def uninstall_app(self, req, **_kwargs):
+        body = json.loads(req.body)
+        app_id = int(body['app_id'])
+
+        if app_id < 0:
+            e = ValueError('app id must grater than 0')
+            raise e
+
+        self.ryu_app.uninstall_app(app_id)
+        return {'result': 'ok'}
diff --git a/setup.cfg b/setup.cfg
index 626e0f8..af91500 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -53,3 +53,4 @@ setup-hooks =
 console_scripts =
     ryu-manager = ryu.cmd.manager:main
     ryu = ryu.cmd.ryu_base:main
+    ryu-cli = ryu.cmd.ryu_cli:main
--
2.3.2 (Apple Git-55)
------------------------------------------------------------------------------
_______________________________________________
Ryu-devel mailing list
Ryu-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ryu-devel

Reply via email to