Commit 7cd2f953 by Kirill Smelkov

helloworld: Turn it into real web-service

Implement a simple "hello world" web server in Python and hook it into
SlapOS infrastructure, so that newcomers can see how to implement
SlapOS services:

    - start service from under etc/service/
    - add promise for service monitoring
    - publish connection url

TODO IPv4 frontend

For now the service is only accessible via IPv6, because SlapOS provides
an unique ipv6-address to every computer partition, and provided IPv4
addresses have non-global scope and can be used only for organizing
internal subservices.

The way we handle IPv4 publish - is through explicit additional IPv4
frontend - TODO add example how to do it.

TODO monitoring

Currently in SlapOS for a service in order to be monitorable, the
service needs to explicitly extend from monitoring stack:

TODO - add example how to do it.

Cc: Jérome Perrin <>
parent 85e6a1da
#!{{ python_executable }}
"""Simple web-server that says "Hello World" for every path
hello-web [--logfile <logfile>] <bind-ip> <bind-port> ...
import sys
import time
import argparse
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from socket import AF_INET6
class WebHello(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200) # ok
self.send_header("Content-type", "text/plain")
print >>self.wfile, \
"Hello %s at `%s` ; %s" % (
' '.join(self.server.webhello_argv) or 'world',
self.path, time.asctime())
class HTTPServerV6(HTTPServer):
address_family = AF_INET6
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--logfile', dest='logfile')
parser.add_argument('bind_port', type=int)
parser.add_argument('argv_extra', metavar='...', nargs=argparse.REMAINDER)
args = parser.parse_args()
# HTTPServer logs to sys.stderr - override it if we have --logfile
if args.logfile:
f = open(args.logfile, 'a', buffering=1)
sys.stderr = f
print >>sys.stderr, '* %s Hello-Web starting at %s' % (
time.asctime(), (args.bind_ip, args.bind_port))
# TODO autodetect ipv6/ipv4
httpd = HTTPServerV6( (args.bind_ip, args.bind_port), WebHello)
httpd.webhello_argv = args.argv_extra
if __name__ == '__main__':
......@@ -7,6 +7,7 @@
parts =
# Define egg directories to be the one from Software Release
......@@ -58,23 +59,48 @@ promise = $${:etc}/promise/
# Path of the log directory used by our service (see [hello-world])
log = $${:var}/log
# Create a simple shell script that will only output your name if you
# specified it as instance parameter.
# Usually, of course, we use more useful commands, like web servers.
# Create a simple web server that says "hello <>" to the web.
# helloworld service is listening on:
# - global IPv6 address, and
# - fixed port
# NOTE because every computer partition is allocated its own global IPv6
# address, it is ok to fix the port - different hello-world instances will have
# different IPv6 addresses and they all will be accessible at the same time.
ipv6 = $${instance-parameter:ipv6-random}
port = 7777
# full URL - for convenience
url = http://[$${:ipv6}]:$${:port}
# the service will log here
logfile = $${directory:log}/hello-world.log
# Actual script that starts the service:
# This recipe will try to "exec" the command-line after separating parameters.
recipe = slapos.cookbook:wrapper
# Notice that there is only one $ at ${dash:location}, it is because it comes from the Software Release buildout profile.
command-line = ${dash:location}/bin/dash -c 'echo "Hello $${}, it is $(date). If I were a TCP server, I would be listening on [$${instance-parameter:ipv6-random}]" > $${directory:log}/log.log; sleep 1000000;'
command-line =
${hello-web-bin:rendered} --logfile $${hello-world:logfile}
$${:ipv6} $${:port} $${}
# Put this shell script in the "etc/service" directory. Each executable of this
# repository will be started and monitored by supervisord. If a service
# exits/crashes, it will trigger a "bang" and cause a re-run of the instance.
wrapper-path = $${directory:service}/hello-world
# promise, that checks that hello-world service is alive
recipe = slapos.cookbook:check_port_listening
path = $${directory:promise}/hello-world
hostname= $${hello-world:ipv6}
port = $${hello-world:port}
# Publish all the parameters needed for the user to connect to the instance.
# It can be anything: URL(s), password(s), or arbitrary parameters.
# Here we'll just echo back the entered name as instance parameter
recipe = slapos.cookbook:publish
name = Hello $${}!
url = $${hello-world:url}
......@@ -6,9 +6,10 @@ extends =
# Extend here component profiles, like openssl, apache, mariadb, curl...
# Or/and extend a stack (lamp, tomcat) that does most of the work for you
# In this example we only need the dash binary to run a simple "hello world"
# shell script.
# In this example we don't need anything more than python which is provided by
# above stack/slapos.cfg
# ../../component/component1/buildout.cfg
# ../../component/component2/buildout.cfg
parts =
# Call installation of slapos.cookbook egg defined in stack/slapos.cfg (needed
......@@ -18,6 +19,10 @@ parts =
# instance
# "build" python program (install + correct shebang for our python)
# Download (buildout profile used to deployment of instance),
# replace all ${foo:bar} parameters by real values, and change $${foo:bar} to
# ${foo:bar}
......@@ -26,5 +31,23 @@ recipe = slapos.recipe.template
url = ${:_profile_base_location_}/
output = ${buildout:directory}/instance.cfg
# MD5 checksum can be skipped for development (easier to develop), but must be filled for production
md5sum = bf5b6e4e16fc47bf959fa7db4e93aac7
md5sum = 968bea0fc81dc604a874c53648b7d13f
mode = 0644
# install hello-web with correct python_executable
recipe = slapos.recipe.template:jinja2
filename = hello-web
md5sum = da4a93ff679d40c6682859476dcf4ce0
template = ${:_profile_base_location_}/${:filename}.in
rendered = ${buildout:bin-directory}/${:filename}
mode = 0755
# XXX python_executable should be ${${buildout:python}:executable}
# but buildout cannot support such indirection.
# in real-cases, python software is usually installed with zc.recipe.egg
# which cares about correctly specifiing python interpreter for
# entry-points automatically.
context =
raw python_executable ${buildout:executable}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment