ソースを参照

Split API-Hour daemon in two parts: aiohttp.web and yocto_http

Ludovic Gasc (GMLudo) 10 年 前
コミット
f663b7258e
29 ファイル変更743 行追加25 行削除
  1. 2 2
      frameworks/Python/AsyncIO/aiohttp.web/etc/hello/api_hour/gunicorn_conf.py
  2. 1 4
      frameworks/Python/AsyncIO/aiohttp.web/hello/__init__.py
  3. 1 12
      frameworks/Python/AsyncIO/aiohttp.web/hello/utils/__init__.py
  4. 6 6
      frameworks/Python/AsyncIO/benchmark_config.json
  5. 1 1
      frameworks/Python/AsyncIO/install.sh
  6. 0 0
      frameworks/Python/AsyncIO/requirements.txt
  7. 64 0
      frameworks/Python/AsyncIO/yocto_http/.gitignore
  8. 13 0
      frameworks/Python/AsyncIO/yocto_http/LICENSE
  9. 22 0
      frameworks/Python/AsyncIO/yocto_http/README.rst
  10. 3 0
      frameworks/Python/AsyncIO/yocto_http/etc/default/hello
  11. 15 0
      frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/gunicorn_conf.py
  12. 71 0
      frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/logging.ini
  13. 14 0
      frameworks/Python/AsyncIO/yocto_http/etc/hello/main/main.yaml
  14. 79 0
      frameworks/Python/AsyncIO/yocto_http/etc/init.d/hello
  15. 7 0
      frameworks/Python/AsyncIO/yocto_http/etc/logrotate.d/hello
  16. 5 0
      frameworks/Python/AsyncIO/yocto_http/etc/monit/conf.d/hello
  17. 126 0
      frameworks/Python/AsyncIO/yocto_http/etc/rsyslog.conf
  18. 61 0
      frameworks/Python/AsyncIO/yocto_http/hello/__init__.py
  19. 1 0
      frameworks/Python/AsyncIO/yocto_http/hello/endpoints/__init__.py
  20. 86 0
      frameworks/Python/AsyncIO/yocto_http/hello/endpoints/world.py
  21. 0 0
      frameworks/Python/AsyncIO/yocto_http/hello/servers/__init__.py
  22. 0 0
      frameworks/Python/AsyncIO/yocto_http/hello/servers/yocto_http.py
  23. 14 0
      frameworks/Python/AsyncIO/yocto_http/hello/services/__init__.py
  24. 45 0
      frameworks/Python/AsyncIO/yocto_http/hello/services/redis.py
  25. 54 0
      frameworks/Python/AsyncIO/yocto_http/hello/services/world.py
  26. 20 0
      frameworks/Python/AsyncIO/yocto_http/hello/templates/fortunes.html.j2
  27. 14 0
      frameworks/Python/AsyncIO/yocto_http/hello/utils/__init__.py
  28. 9 0
      frameworks/Python/AsyncIO/yocto_http/setup.sh
  29. 9 0
      frameworks/Python/AsyncIO/yocto_http/source_code

+ 2 - 2
frameworks/Python/AsyncIO/aiohttp.web/etc/hello/api_hour/gunicorn_conf.py

@@ -7,9 +7,9 @@ workers = multiprocessing.cpu_count() * 2
 if _is_travis:
     workers = 2
 
-bind = ['0.0.0.0:8008', '0.0.0.0:8009', '0.0.0.0:8011']
+bind = ['0.0.0.0:8080']
 keepalive = 120
 errorlog = '-'
 pidfile = 'api_hour.pid'
 pythonpath = 'hello'
-backlog = 10240000
+backlog = 10240000

+ 1 - 4
frameworks/Python/AsyncIO/aiohttp.web/hello/__init__.py

@@ -12,7 +12,6 @@ import aiohttp_jinja2
 import api_hour
 
 from . import endpoints
-from . import servers
 
 LOG = logging.getLogger(__name__)
 
@@ -41,9 +40,7 @@ class Container(api_hour.Container):
                                                   debug=False,
                                                   keep_alive=0,
                                                   access_log=None,
-                                                  access_log_format=self.worker.cfg.access_log_format),
-                servers.yocto_http.YoctoHttpJson,
-                servers.yocto_http.YoctoHttpText]
+                                                  access_log_format=self.worker.cfg.access_log_format)]
 
     @asyncio.coroutine
     def start(self):

+ 1 - 12
frameworks/Python/AsyncIO/aiohttp.web/hello/utils/__init__.py

@@ -1,14 +1,3 @@
 import logging
-from wsgiref.handlers import format_date_time
 
-LOG = logging.getLogger(__name__)
-
-def generate_http_response(payload, content_type='application/json'):
-    return ("""HTTP/1.1 200 OK
-CONTENT-TYPE: %s
-CONTENT-LENGTH: %s
-CONNECTION: keep-alive
-DATE: %s
-SERVER: yocto_http/0.0.1
-
-%s""" % (content_type, len(payload), format_date_time(None), payload)).encode('utf-8')
+LOG = logging.getLogger(__name__)

+ 6 - 6
frameworks/Python/AsyncIO/benchmark_config.json

@@ -9,7 +9,7 @@
       "fortune_url": "/fortunes",
       "update_url": "/updates?queries=",
       "plaintext_url": "/plaintext",
-      "port": 8008,
+      "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
       "database": "Postgres",
@@ -29,7 +29,7 @@
       "query_url": "/queries_redis?queries=",
       "fortune_url": "/fortunes_redis",
       "update_url": "/updates_redis?queries=",
-      "port": 8008,
+      "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
       "database": "Redis",
@@ -44,9 +44,9 @@
       "notes": "Python 3 + API-Hour + AsyncIO + aiohttp.web + Redis"
     },
     "json": {
-      "setup_file": "aiohttp.web/setup",
+      "setup_file": "yocto_http/setup",
       "json_url": "/json",
-      "port": 8009,
+      "port": 8080,
       "approach": "Stripped",
       "classification": "Platform",
       "database": "None",
@@ -61,9 +61,9 @@
       "notes": "Python 3 + API-Hour + AsyncIO"
     },
     "plaintext": {
-      "setup_file": "aiohttp.web/setup",
+      "setup_file": "yocto_http/setup",
       "plaintext_url": "/plaintext",
-      "port": 8011,
+      "port": 8084,
       "approach": "Stripped",
       "classification": "Platform",
       "database": "None",

+ 1 - 1
frameworks/Python/AsyncIO/install.sh

@@ -10,4 +10,4 @@ export PIP_DOWNLOAD_CACHE=$IROOT/.pip_cache
 
 fw_depends python3
 
-$IROOT/py3/bin/pip3 install --install-option="--prefix=${IROOT}/py3" -r $TROOT/aiohttp.web/requirements.txt
+$IROOT/py3/bin/pip3 install --install-option="--prefix=${IROOT}/py3" -r $TROOT/requirements.txt

+ 0 - 0
frameworks/Python/AsyncIO/aiohttp.web/requirements.txt → frameworks/Python/AsyncIO/requirements.txt


+ 64 - 0
frameworks/Python/AsyncIO/yocto_http/.gitignore

@@ -0,0 +1,64 @@
+# Created by .gitignore support plugin (hsz.mobi)
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# PyCharm/IDEA files
+.idea
+*.iml
+
+# Virtualenvs
+venv/
+pyvenv/

+ 13 - 0
frameworks/Python/AsyncIO/yocto_http/LICENSE

@@ -0,0 +1,13 @@
+Copyright [2015] [Eyepea Dev Team]
+
+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.

+ 22 - 0
frameworks/Python/AsyncIO/yocto_http/README.rst

@@ -0,0 +1,22 @@
+hello
+=====
+
+Install
+-------
+
+#. Follow pythonz install doc
+#. pythonz install 3.4.2
+#. cd /opt
+#. Git clone your app here
+#. cd /opt/hello/
+#. /usr/local/pythonz/pythons/CPython-3.4.2/bin/pyvenv pyvenv
+#. . pyvenv/bin/activate
+#. pip install -r requirements.txt
+#. cd /etc/init.d/ && ln -s /opt/hello/etc/init.d/hello
+#. cd /etc/default/ && ln -s /opt/hello/etc/default/hello
+#. cd /etc/monit/conf.d/ && ln -s /opt/hello/etc/monit/conf.d/hello
+#. update-rc.d hello defaults
+#. cp -a /opt/hello/etc/hello /etc/
+#. Adapt rsyslog and lograte
+#. service hello start
+#. service monit restart

+ 3 - 0
frameworks/Python/AsyncIO/yocto_http/etc/default/hello

@@ -0,0 +1,3 @@
+# Start the daemon by default, let the user disable it.
+START_DAEMON=yes
+DAEMON_ARGS="--config_dir=/etc/hello"

+ 15 - 0
frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/gunicorn_conf.py

@@ -0,0 +1,15 @@
+import multiprocessing
+import os
+
+_is_travis = os.environ.get('TRAVIS') == 'true'
+
+workers = multiprocessing.cpu_count() * 2
+if _is_travis:
+    workers = 2
+
+bind = ['0.0.0.0:8080', '0.0.0.0:8084']
+keepalive = 120
+errorlog = '-'
+pidfile = 'api_hour.pid'
+pythonpath = 'hello'
+backlog = 10240000

+ 71 - 0
frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/logging.ini

@@ -0,0 +1,71 @@
+[formatters]
+keys=detailed,simple
+
+[handlers]
+keys=console,syslog,smtp
+
+[loggers]
+keys=root,warnings,asyncio,gunicorn,aiohttp,api_hour,hello
+
+[formatter_simple]
+format=%(name)s:%(levelname)s %(asctime)s %(module)s.py => %(message)s
+
+[formatter_detailed]
+format=%(name)s:%(levelname)s %(asctime)s %(module)s.py:%(lineno)d => %(message)s
+
+[handler_console]
+class=StreamHandler
+args=(sys.stdout,)
+formatter=detailed
+
+[handler_syslog]
+class=handlers.SysLogHandler
+args=('/dev/log', handlers.SysLogHandler.LOG_LOCAL6)
+formatter=detailed
+
+[handler_smtp]
+class=handlers.SMTPHandler
+level=WARN
+args=('127.0.0.1', '[email protected]', ['[email protected]'], 'hello error on server: hello')
+formatter=detailed
+
+[logger_root]
+level=ERROR
+handlers=console
+
+# You can add smtp in handlers list to receive e-mail alerts
+[logger_warnings]
+level=WARN
+handlers=console
+qualname=py.warnings
+propagate=0
+
+[logger_asyncio]
+level=WARN
+handlers=console
+qualname=asyncio
+propagate=0
+
+[logger_gunicorn]
+level=WARN
+handlers=console
+qualname=gunicorn
+propagate=0
+
+[logger_aiohttp]
+level=WARN
+handlers=console
+qualname=aiohttp
+propagate=0
+
+[logger_api_hour]
+level=WARN
+handlers=console
+qualname=api_hour
+propagate=0
+
+[logger_hello]
+level=WARN
+handlers=console
+qualname=hello
+propagate=0

+ 14 - 0
frameworks/Python/AsyncIO/yocto_http/etc/hello/main/main.yaml

@@ -0,0 +1,14 @@
+---
+engines:
+  pg:
+    host: 127.0.0.1
+    port: 5432
+    dbname: hello_world
+    user: benchmarkdbuser
+    password: benchmarkdbpass
+    minsize: 22
+    maxsize: 22
+  redis:
+    host: 127.0.0.1
+    port: 6379
+    poolsize: 40

+ 79 - 0
frameworks/Python/AsyncIO/yocto_http/etc/init.d/hello

@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+### BEGIN INIT INFO
+# Provides:          hello
+# Required-Start:    $local_fs $remote_fs $network $syslog
+# Required-Stop:     $local_fs $remote_fs $network $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Startup daemon script for hello
+### END INIT INFO
+#
+# Author: Ludovic Gasc <[email protected]>
+set -e
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
+DAEMONNAME=hello
+RUNDIR=/run/$DAEMONNAME
+DAEMON=/opt/hello/${DAEMONNAME}_cli
+PIDFILE=/run/lock/${DAEMONNAME}_0
+DAEMON_ARGS=""
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Create RUNDIR if it doesn't exist
+[ -d "$RUNDIR" ] || mkdir -p "$RUNDIR"
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+[ -f /etc/default/rcS ] && . /etc/default/rcS
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+case "$1" in
+  start)
+    log_daemon_msg "Starting" "$DAEMONNAME"
+    if start-stop-daemon -b --start --pidfile $PIDFILE --startas $DAEMON -- $DAEMON_ARGS;
+    then
+        log_end_msg 0
+    else
+        log_end_msg 1
+    fi
+    ;;
+  stop)
+    log_daemon_msg "Stopping" "$DAEMONNAME"
+    if start-stop-daemon --stop --retry 5 --pidfile $PIDFILE;
+     then
+         log_end_msg 0
+     else
+         log_end_msg 1
+     fi
+    ;;
+  reload|force-reload)
+    log_daemon_msg "Reloading" "$DAEMONNAME"
+    if start-stop-daemon --stop --signal 1 --pidfile $PIDFILE --startas $DAEMON;
+     then
+         log_end_msg 0
+     else
+         log_end_msg 1
+     fi
+    ;;
+  restart)
+    $0 stop
+    $0 start
+    ;;
+  status)
+    status_of_proc -p $PIDFILE "$DAEMON" $DAEMONNAME && exit 0 || exit $?
+    ;;
+  *)
+    echo "Usage: $0 {start|stop|reload|force-reload|restart|status}"
+    exit 1
+    ;;
+esac
+
+exit 0

+ 7 - 0
frameworks/Python/AsyncIO/yocto_http/etc/logrotate.d/hello

@@ -0,0 +1,7 @@
+/var/log/hello/hello.log {
+    daily
+    rotate 15
+    compress
+    copytruncate
+    missingok
+}

+ 5 - 0
frameworks/Python/AsyncIO/yocto_http/etc/monit/conf.d/hello

@@ -0,0 +1,5 @@
+check process cache_updater with pidfile /run/lock/hello_0
+	group hello
+	start program = "/etc/init.d/hello start"
+	stop program = "/etc/init.d/hello stop"
+	if 5 restarts within 5 cycles then timeout

+ 126 - 0
frameworks/Python/AsyncIO/yocto_http/etc/rsyslog.conf

@@ -0,0 +1,126 @@
+#  /etc/rsyslog.conf	Configuration file for rsyslog.
+#
+#			For more information see
+#			/usr/share/doc/rsyslog-doc/html/rsyslog_conf.html
+
+
+#################
+#### MODULES ####
+#################
+
+$ModLoad imuxsock # provides support for local system logging
+$ModLoad imklog   # provides kernel logging support
+#$ModLoad immark  # provides --MARK-- message capability
+$SystemLogRateLimitInterval 1
+$SystemLogRateLimitBurst 1000
+
+# provides UDP syslog reception
+#$ModLoad imudp
+#$UDPServerRun 514
+
+# provides TCP syslog reception
+#$ModLoad imtcp
+#$InputTCPServerRun 514
+
+
+###########################
+#### GLOBAL DIRECTIVES ####
+###########################
+
+#
+# Use traditional timestamp format.
+# To enable high precision timestamps, comment out the following line.
+#
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+
+#
+# Set the default permissions for all log files.
+#
+$FileOwner root
+$FileGroup adm
+$FileCreateMode 0640
+$DirCreateMode 0755
+$Umask 0022
+
+#
+# Where to place spool and state files
+#
+$WorkDirectory /var/spool/rsyslog
+
+#
+# Include all config files in /etc/rsyslog.d/
+#
+$IncludeConfig /etc/rsyslog.d/*.conf
+
+
+###############
+#### RULES ####
+###############
+
+#
+# First some standard log files.  Log by facility.
+#
+auth,authpriv.*			/var/log/auth.log
+*.*;auth,authpriv.none;\
+    local6.none                 -/var/log/syslog
+#cron.*				/var/log/cron.log
+daemon.*			-/var/log/daemon.log
+kern.*				-/var/log/kern.log
+lpr.*				-/var/log/lpr.log
+mail.*				-/var/log/mail.log
+user.*				-/var/log/user.log
+
+local6.*                        /var/log/hello/hello.log
+
+#
+# Logging for the mail system.  Split it up so that
+# it is easy to write scripts to parse these files.
+#
+mail.info			-/var/log/mail.info
+mail.warn			-/var/log/mail.warn
+mail.err			/var/log/mail.err
+
+#
+# Logging for INN news system.
+#
+news.crit			/var/log/news/news.crit
+news.err			/var/log/news/news.err
+news.notice			-/var/log/news/news.notice
+
+#
+# Some "catch-all" log files.
+#
+*.=debug;\
+	auth,authpriv.none;\
+	news.none;mail.none;local7.none	-/var/log/debug
+*.=info;*.=notice;*.=warn;\
+	auth,authpriv.none;\
+	cron,daemon.none;\
+	mail,news.none;local7.none		-/var/log/messages
+
+#
+# Emergencies are sent to everybody logged in.
+#
+*.emerg				:omusrmsg:*
+
+#
+# I like to have messages displayed on the console, but only on a virtual
+# console I usually leave idle.
+#
+#daemon,mail.*;\
+#	news.=crit;news.=err;news.=notice;\
+#	*.=debug;*.=info;\
+#	*.=notice;*.=warn	/dev/tty8
+
+# The named pipe /dev/xconsole is for the `xconsole' utility.  To use it,
+# you must invoke `xconsole' with the `-file' option:
+# 
+#    $ xconsole -file /dev/xconsole [...]
+#
+# NOTE: adjust the list below, or you'll go crazy if you have a reasonably
+#      busy site..
+#
+daemon.*;mail.*;\
+	news.err;\
+	*.=debug;*.=info;\
+	*.=notice;*.=warn	|/dev/xconsole

+ 61 - 0
frameworks/Python/AsyncIO/yocto_http/hello/__init__.py

@@ -0,0 +1,61 @@
+import logging
+import asyncio
+import os
+
+import aiopg
+import psycopg2.extras
+import asyncio_redis
+from asyncio_redis.protocol import HiRedisProtocol
+import api_hour
+
+from . import endpoints
+from . import servers
+
+LOG = logging.getLogger(__name__)
+
+
+class Container(api_hour.Container):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+
+    def make_servers(self):
+        return [servers.yocto_http.YoctoHttpJson,
+                servers.yocto_http.YoctoHttpText]
+
+    @asyncio.coroutine
+    def start(self):
+        yield from super().start()
+        LOG.info('Starting engines...')
+        self.engines['pg'] = self.loop.create_task(aiopg.create_pool(host=os.environ.get('DBHOST', self.config['engines']['pg']['host']),
+                                                                     port=int(self.config['engines']['pg']['port']),
+                                                                     sslmode='disable',
+                                                                     dbname=self.config['engines']['pg']['dbname'],
+                                                                     user=self.config['engines']['pg']['user'],
+                                                                     password=self.config['engines']['pg']['password'],
+                                                                     cursor_factory=psycopg2.extras.RealDictCursor,
+                                                                     minsize=int(self.config['engines']['pg']['minsize']),
+                                                                     maxsize=int(self.config['engines']['pg']['maxsize']),
+                                                                     loop=self.loop))
+        yield from asyncio.wait([self.engines['pg']], return_when=asyncio.ALL_COMPLETED)
+        self.engines['redis'] = yield from asyncio_redis.Pool.create(host=self.config['engines']['redis']['host'],
+                                                                     port=self.config['engines']['redis']['port'],
+                                                                     poolsize=self.config['engines']['redis']['poolsize'],
+                                                                     loop=self.loop,
+                                                                     protocol_class=HiRedisProtocol)
+
+        LOG.info('All engines ready !')
+
+    @asyncio.coroutine
+    def stop(self):
+        LOG.info('Stopping engines...')
+        if 'pg' in self.engines:
+            if self.engines['pg'].done():
+                self.engines['pg'].result().terminate()
+                yield from self.engines['pg'].result().wait_closed()
+            else:
+                yield from self.engines['pg'].cancel()
+        if 'redis' in self.engines:
+            self.engines['redis'].close()
+            yield from asyncio.sleep(1) # wait redis close connection
+        LOG.info('All engines stopped !')
+        yield from super().stop()

+ 1 - 0
frameworks/Python/AsyncIO/yocto_http/hello/endpoints/__init__.py

@@ -0,0 +1 @@
+from . import world

+ 86 - 0
frameworks/Python/AsyncIO/yocto_http/hello/endpoints/world.py

@@ -0,0 +1,86 @@
+import logging
+import asyncio
+
+from aiohttp.web import Response
+from api_hour.plugins.aiohttp import JSON
+import aiohttp_jinja2
+
+from ..services import queries_number
+from ..services.world import get_random_record, get_random_records, update_random_records, get_fortunes
+from ..services import redis
+
+LOG = logging.getLogger(__name__)
+
[email protected]
+def json(request):
+    """Test type 1: JSON serialization"""
+    return JSON({'message': 'Hello, World!'})
+
[email protected]
+def db(request):
+    """Test type 2: Single database query"""
+    container = request.app.ah_container
+
+    return JSON((yield from get_random_record(container)))
+
[email protected]
+def db_redis(request):
+    """Test type 2: Single database query"""
+    container = request.app.ah_container
+
+    return JSON((yield from redis.get_random_record(container)))
+
[email protected]
+def queries(request):
+    """Test type 3: Multiple database queries"""
+    container = request.app.ah_container
+    limit = queries_number(request.GET.get('queries', 1))
+
+    return JSON((yield from get_random_records(container, limit)))
+
[email protected]
+def queries_redis(request):
+    """Test type 3: Multiple database queries"""
+    container = request.app.ah_container
+    limit = queries_number(request.GET.get('queries', 1))
+
+    return JSON((yield from redis.get_random_records(container, limit)))
+
[email protected]
+def fortunes(request):
+    """Test type 4: Fortunes"""
+    container = request.app.ah_container
+
+    return aiohttp_jinja2.render_template('fortunes.html.j2',
+                                          request,
+                                          {'fortunes': (yield from get_fortunes(container))})
+
[email protected]
+def fortunes_redis(request):
+    """Test type 4: Fortunes"""
+    container = request.app.ah_container
+
+    return aiohttp_jinja2.render_template('fortunes.html.j2',
+                                          request,
+                                          {'fortunes': (yield from redis.get_fortunes(container))})
+
[email protected]
+def updates(request):
+    """Test type 5: Database updates"""
+    container = request.app.ah_container
+    limit = queries_number(request.GET.get('queries', 1))
+
+    return JSON((yield from update_random_records(container, limit)))
+
[email protected]
+def updates_redis(request):
+    """Test type 5: Database updates"""
+    container = request.app.ah_container
+    limit = queries_number(request.GET.get('queries', 1))
+
+    return JSON((yield from redis.update_random_records(container, limit)))
+
[email protected]
+def plaintext(request):
+    """Test type 6: Plaintext"""
+    return Response(text='Hello, World!')

+ 0 - 0
frameworks/Python/AsyncIO/aiohttp.web/hello/servers/__init__.py → frameworks/Python/AsyncIO/yocto_http/hello/servers/__init__.py


+ 0 - 0
frameworks/Python/AsyncIO/aiohttp.web/hello/servers/yocto_http.py → frameworks/Python/AsyncIO/yocto_http/hello/servers/yocto_http.py


+ 14 - 0
frameworks/Python/AsyncIO/yocto_http/hello/services/__init__.py

@@ -0,0 +1,14 @@
+from . import world
+
+# get from Django hello application
+def queries_number(number):
+    try:
+        queries = int(number)
+    except Exception:
+        queries = 1
+    if queries < 1:
+        queries = 1
+    if queries > 500:
+        queries = 500
+    return queries
+

+ 45 - 0
frameworks/Python/AsyncIO/yocto_http/hello/services/redis.py

@@ -0,0 +1,45 @@
+from operator import itemgetter
+import asyncio
+from random import randint
+
+
[email protected]
+def get_random_record(container):
+    idx = randint(1, 10000)
+    random_number = yield from container.engines['redis'].get('world:%i' % idx)
+    return {'Id': idx, 'RandomNumber': random_number}
+
[email protected]
+def get_random_records(container, limit):
+    results = []
+    for i in range(limit):
+        idx = randint(1, 10000)
+        random_number = yield from container.engines['redis'].get('world:%i' % idx)
+        results.append({'Id': idx, 'RandomNumber': random_number})
+
+    return results
+
[email protected]
+def update_random_records(container, limit):
+    results = []
+    for i in range(limit):
+        idx = randint(1, 10000)
+        random_number = yield from container.engines['redis'].get('world:%i' % idx)
+        yield from container.engines['redis'].set('world:%i' % idx, str(randint(1, 10000)))
+        results.append({'Id': idx, 'RandomNumber': random_number})
+    return results
+
[email protected]
+def get_fortunes(container):
+    results = []
+    list_reply = yield from container.engines['redis'].lrange('fortunes')
+    fortunes = yield from list_reply.aslist()
+    i = 1
+    for fortune in fortunes:
+        results.append({'id': i, 'message': fortune})
+        i += 1
+
+    results.append({'id': 0, 'message': 'Additional fortune added at request time.'})
+    results.sort(key=itemgetter('message'))
+
+    return results

+ 54 - 0
frameworks/Python/AsyncIO/yocto_http/hello/services/world.py

@@ -0,0 +1,54 @@
+import asyncio
+from random import randint
+from operator import itemgetter
+
[email protected]
+def get_random_record(container):
+    pg = yield from container.engines['pg']
+
+    with (yield from pg.cursor()) as cur:
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            world = yield from cur.fetchone()
+    return world
+
[email protected]
+def get_random_records(container, limit):
+    pg = yield from container.engines['pg']
+    results = []
+    with (yield from pg.cursor()) as cur:
+        for i in range(limit):
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            results.append((yield from cur.fetchone()))
+
+    return results
+
[email protected]
+def update_random_records(container, limit):
+    results = []
+    pg = yield from container.engines['pg']
+
+    with (yield from pg.cursor()) as cur:
+        for i in range(limit):
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            world = yield from cur.fetchone()
+            yield from cur.execute('UPDATE world SET randomnumber=%(random_number)s WHERE id=%(idx)s',
+                                   {'random_number': randint(1, 10000), 'idx': world['Id']})
+            results.append(world)
+    return results
+
[email protected]
+def get_fortunes(container):
+    pg = yield from container.engines['pg']
+
+    with (yield from pg.cursor()) as cur:
+        yield from cur.execute('SELECT * FROM fortune')
+        fortunes = yield from cur.fetchall()
+
+    fortunes.append({'id': 0, 'message': 'Additional fortune added at request time.'})
+
+    fortunes.sort(key=itemgetter('message'))
+
+    return fortunes

+ 20 - 0
frameworks/Python/AsyncIO/yocto_http/hello/templates/fortunes.html.j2

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Fortunes</title>
+</head>
+<body>
+<table>
+    <tr>
+        <th>id</th>
+        <th>message</th>
+    </tr>
+    {% for fortune in fortunes %}
+        <tr>
+            <td>{{ fortune['id']|e }}</td>
+            <td>{{ fortune['message']|e }}</td>
+        </tr>
+    {% endfor %}
+</table>
+</body>
+</html>

+ 14 - 0
frameworks/Python/AsyncIO/yocto_http/hello/utils/__init__.py

@@ -0,0 +1,14 @@
+import logging
+from wsgiref.handlers import format_date_time
+
+LOG = logging.getLogger(__name__)
+
+def generate_http_response(payload, content_type='application/json'):
+    return ("""HTTP/1.1 200 OK
+CONTENT-TYPE: %s
+CONTENT-LENGTH: %s
+CONNECTION: keep-alive
+DATE: %s
+SERVER: yocto_http/0.0.1
+
+%s""" % (content_type, len(payload), format_date_time(None), payload)).encode('utf-8')

+ 9 - 0
frameworks/Python/AsyncIO/yocto_http/setup.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+export PY3_ROOT=$IROOT/py3
+export PY3=$PY3_ROOT/bin/python
+export PY3_PIP=$PY3_ROOT/bin/pip3
+export PY3_API_HOUR=$PY3_ROOT/bin/api_hour
+
+cd $TROOT/yocto_http
+$PY3_API_HOUR -ac hello:Container &

+ 9 - 0
frameworks/Python/AsyncIO/yocto_http/source_code

@@ -0,0 +1,9 @@
+./etc/hello/main/main.yaml
+./etc/hello/api_hour/logging.ini
+./etc/hello/api_hour/gunicorn_conf.py
+./hello/__init__.py
+./hello/hello/endpoints/__init__.py
+./hello/hello/endpoints/world.py
+./hello/hello/services/__init__.py
+./hello/hello/services/world.py
+./hello/hello/utils/__init__.py