Very sorry about the delay,

On Thu, 20 Aug 2015 15:59:25 +0800
Yi Tseng <a86487...@gmail.com> wrote:

> 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.

dynamic loading and unloading is always tricky (we do the similar
inside VRRP app though). Do you have any use case?


> 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

I prefer to avoid installing a new command. How about integrating the
existing 'ryu' command (ryu/cmd/ryu_base.py)?


> 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