From: Peter Amstutz Date: Fri, 16 Jan 2015 19:05:48 +0000 (-0500) Subject: Add 'apps/arv-web/' from commit 'f9732ad8460d013c2f28363655d0d1b91894dca5' X-Git-Tag: 1.1.0~1814^2~17 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/964ab3dd90ff1508efc0c77378cde2b3a4da1029?hp=-c Add 'apps/arv-web/' from commit 'f9732ad8460d013c2f28363655d0d1b91894dca5' git-subtree-dir: apps/arv-web git-subtree-mainline: b97ac7f96234cbbb491bdbaade840ab50802f357 git-subtree-split: f9732ad8460d013c2f28363655d0d1b91894dca5 --- 964ab3dd90ff1508efc0c77378cde2b3a4da1029 diff --combined apps/arv-web/Dockerfile index 0000000000,d0a6abe829..d0a6abe829 mode 000000,100644..100644 --- a/apps/arv-web/Dockerfile +++ b/apps/arv-web/Dockerfile @@@ -1,0 -1,27 +1,27 @@@ + FROM arvados/base + MAINTAINER Peter Amstutz + + RUN apt-get update -qq + RUN apt-get install -qqy \ + apt-utils git curl procps apache2-mpm-worker \ + libcurl4-openssl-dev apache2-threaded-dev \ + libapr1-dev libaprutil1-dev + + RUN cd /usr/src/arvados/services/api && \ + /usr/local/rvm/bin/rvm-exec default bundle exec passenger-install-apache2-module --auto --languages ruby,python + + RUN cd /usr/src/arvados/services/api && \ + /usr/local/rvm/bin/rvm-exec default bundle exec passenger-install-apache2-module --snippet > /etc/apache2/conf.d/passenger + + ADD apache2_foreground.sh /etc/apache2/foreground.sh + + ADD apache2_vhost /etc/apache2/sites-available/arv-web + RUN \ + mkdir /var/run/apache2 && \ + a2dissite default && \ + a2ensite arv-web && \ + a2enmod rewrite + + EXPOSE 80 + + CMD ["/etc/apache2/foreground.sh"] diff --combined apps/arv-web/README index 0000000000,0000000000..826092d447 new file mode 100644 --- /dev/null +++ b/apps/arv-web/README @@@ -1,0 -1,0 +1,28 @@@ ++Run a web service from Arvados. ++ ++usage: arv-web.py [-h] --project PROJECT [--port PORT] --image IMAGE ++ ++optional arguments: ++ -h, --help show this help message and exit ++ --project PROJECT Project to watch ++ --port PORT Local bind port ++ --image IMAGE Docker image to run ++ ++ ++This queries an Arvados project and FUSE mounts the most recently modified ++collection into a temporary directory. It then runs the supplied Docker image ++with the collection bind mounted to /mnt inside the container. ++ ++When a new collection is added to the project, or an existing project is ++updated, it will detect the change, it will stop the running Docker container, ++unmount the old collection, mount the new most recently modified collection, ++and restart the Docker container with the new mount. ++ ++The supplied Dockerfile builds a Docker image that runs Apache with /mnt as the ++DocumentRoot. It is configured to run web applications based on Python WSGI, ++Ruby Rack, CGI, to serve static HTML, or simply browse the contents of the ++/public subdirectory of the collection using Apache's default index pages. ++ ++To build the Docker image: ++ ++$ docker build -t arvados/arv-web . diff --combined apps/arv-web/apache2_foreground.sh index 0000000000,fc6028ea83..fc6028ea83 mode 000000,100755..100755 --- a/apps/arv-web/apache2_foreground.sh +++ b/apps/arv-web/apache2_foreground.sh @@@ -1,0 -1,7 +1,7 @@@ + #! /bin/bash + + read pid cmd state ppid pgrp session tty_nr tpgid rest < /proc/self/stat + trap "kill -TERM -$pgrp; exit" EXIT TERM KILL SIGKILL SIGTERM SIGQUIT + + source /etc/apache2/envvars + /usr/sbin/apache2 -D FOREGROUND diff --combined apps/arv-web/apache2_vhost index 0000000000,dad4a9b57a..dad4a9b57a mode 000000,100644..100644 --- a/apps/arv-web/apache2_vhost +++ b/apps/arv-web/apache2_vhost @@@ -1,0 -1,23 +1,23 @@@ + + + ServerName arv-web + ServerAdmin sysadmin@curoverse.com + + # Index file and Document Root (where the public files are located) + DirectoryIndex index.html + DocumentRoot /mnt/public + RackBaseURI / + + LogLevel warn + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + Options Indexes IncludesNoExec + Options -MultiViews + AllowOverride All + Order allow,deny + Allow from all + + + diff --combined apps/arv-web/arv-web.py index 0000000000,63d760d2f1..63d760d2f1 mode 000000,100644..100644 --- a/apps/arv-web/arv-web.py +++ b/apps/arv-web/arv-web.py @@@ -1,0 -1,126 +1,126 @@@ + import arvados + import subprocess + from arvados_fuse import Operations, SafeApi, CollectionDirectory + import tempfile + import os + import llfuse + import threading + import Queue + import argparse + import logging + import signal + import sys + + logging.basicConfig(level=logging.INFO) + + parser = argparse.ArgumentParser() + parser.add_argument('--project', type=str, required=True, help="Project to watch") + parser.add_argument('--port', type=int, default=8080, help="Local bind port") + parser.add_argument('--image', type=str, required=True, help="Docker image to run") + + args = parser.parse_args() + + api = SafeApi(arvados.config) + project = args.project + docker_image = args.image + port = args.port + evqueue = Queue.Queue() + + def run_fuse_mount(collection): + global api + + mountdir = tempfile.mkdtemp() + + operations = Operations(os.getuid(), os.getgid(), "utf-8", True) + operations.inodes.add_entry(CollectionDirectory(llfuse.ROOT_INODE, operations.inodes, api, 2, collection)) + + # Initialize the fuse connection + llfuse.init(operations, mountdir, ['allow_other']) + + t = threading.Thread(None, lambda: llfuse.main()) + t.start() + + # wait until the driver is finished initializing + operations.initlock.wait() + + return mountdir + + def on_message(ev): + global project + global evqueue + + if 'event_type' in ev: + old_attr = None + if 'old_attributes' in ev['properties'] and ev['properties']['old_attributes']: + old_attr = ev['properties']['old_attributes'] + if project not in (ev['properties']['new_attributes']['owner_uuid'], + old_attr['owner_uuid'] if old_attr else None): + return + + et = ev['event_type'] + if ev['event_type'] == 'update' and ev['properties']['new_attributes']['owner_uuid'] != ev['properties']['old_attributes']['owner_uuid']: + if args.project == ev['properties']['new_attributes']['owner_uuid']: + et = 'add' + else: + et = 'remove' + + evqueue.put((project, et, ev['object_uuid'])) + + collection = api.collections().list(filters=[["owner_uuid", "=", project]], + limit=1, + order='modified_at desc').execute()['items'][0]['uuid'] + + ws = arvados.events.subscribe(api, [["object_uuid", "is_a", "arvados#collection"]], on_message) + + signal.signal(signal.SIGTERM, lambda signal, frame: sys.exit(0)) + + loop = True + cid = None + while loop: + logging.info("Mounting %s" % collection) + mountdir = run_fuse_mount(collection) + try: + logging.info("Starting docker container") + cid = subprocess.check_output(["docker", "run", + "--detach=true", + "--publish=%i:80" % (port), + "--volume=%s:/mnt:ro" % mountdir, + docker_image]) + cid = cid.rstrip() + logging.info("Container id is %s" % cid) + + logging.info("Waiting for events") + running = True + while running: + try: + eq = evqueue.get(True, 1) + logging.info("%s %s" % (eq[1], eq[2])) + newcollection = collection + if eq[1] in ('add', 'update', 'create'): + newcollection = eq[2] + elif eq[1] == 'remove': + newcollection = api.collections().list(filters=[["owner_uuid", "=", project]], + limit=1, + order='modified_at desc').execute()['items'][0]['uuid'] + if newcollection != collection: + logging.info("restarting web service") + collection = newcollection + running = False + except Queue.Empty: + pass + except (KeyboardInterrupt): + logging.info("Got keyboard interrupt") + ws.close() + loop = False + except Exception as e: + logging.exception(str(e)) + ws.close() + loop = False + finally: + if cid: + logging.info("Stopping docker container") + cid = subprocess.call(["docker", "stop", cid]) + + logging.info("Unmounting") + subprocess.call(["fusermount", "-u", "-z", mountdir]) + os.rmdir(mountdir) diff --combined apps/arv-web/sample-cgi-app/public/.htaccess index 0000000000,e5145bd37d..e5145bd37d mode 000000,100644..100644 --- a/apps/arv-web/sample-cgi-app/public/.htaccess +++ b/apps/arv-web/sample-cgi-app/public/.htaccess @@@ -1,0 -1,3 +1,3 @@@ + Options +ExecCGI + AddHandler cgi-script .cgi + DirectoryIndex index.cgi diff --combined apps/arv-web/sample-cgi-app/public/index.cgi index 0000000000,57bc2a9a01..57bc2a9a01 mode 000000,100755..100755 --- a/apps/arv-web/sample-cgi-app/public/index.cgi +++ b/apps/arv-web/sample-cgi-app/public/index.cgi @@@ -1,0 -1,4 +1,4 @@@ + #!/usr/bin/perl + + print "Content-type: text/html\n\n"; + print "Hello world from perl!"; diff --combined apps/arv-web/sample-cgi-app/tmp/.keepkeep index 0000000000,e69de29bb2..e69de29bb2 mode 000000,100644..100644 --- a/apps/arv-web/sample-cgi-app/tmp/.keepkeep +++ b/apps/arv-web/sample-cgi-app/tmp/.keepkeep diff --combined apps/arv-web/sample-rack-app/config.ru index 0000000000,84bb0da994..84bb0da994 mode 000000,100644..100644 --- a/apps/arv-web/sample-rack-app/config.ru +++ b/apps/arv-web/sample-rack-app/config.ru @@@ -1,0 -1,4 +1,4 @@@ + app = proc do |env| + [200, { "Content-Type" => "text/html" }, ["hello world from ruby"]] + end + run app diff --combined apps/arv-web/sample-rack-app/public/.keepkeep index 0000000000,e69de29bb2..e69de29bb2 mode 000000,100644..100644 --- a/apps/arv-web/sample-rack-app/public/.keepkeep +++ b/apps/arv-web/sample-rack-app/public/.keepkeep diff --combined apps/arv-web/sample-rack-app/tmp/.keepkeep index 0000000000,e69de29bb2..e69de29bb2 mode 000000,100644..100644 --- a/apps/arv-web/sample-rack-app/tmp/.keepkeep +++ b/apps/arv-web/sample-rack-app/tmp/.keepkeep diff --combined apps/arv-web/sample-static-page/public/index.html index 0000000000,a2e485c5d8..a2e485c5d8 mode 000000,100644..100644 --- a/apps/arv-web/sample-static-page/public/index.html +++ b/apps/arv-web/sample-static-page/public/index.html @@@ -1,0 -1,6 +1,6 @@@ + + arv-web sample + +

Hello world static page

+ + diff --combined apps/arv-web/sample-static-page/tmp/.keepkeep index 0000000000,e69de29bb2..e69de29bb2 mode 000000,100644..100644 --- a/apps/arv-web/sample-static-page/tmp/.keepkeep +++ b/apps/arv-web/sample-static-page/tmp/.keepkeep diff --combined apps/arv-web/sample-wsgi-app/passenger_wsgi.py index 0000000000,ea918f0cbf..ea918f0cbf mode 000000,100644..100644 --- a/apps/arv-web/sample-wsgi-app/passenger_wsgi.py +++ b/apps/arv-web/sample-wsgi-app/passenger_wsgi.py @@@ -1,0 -1,3 +1,3 @@@ + def application(environ, start_response): + start_response('200 OK', [('Content-Type', 'text/plain')]) + return [b"hello world from python!\n"] diff --combined apps/arv-web/sample-wsgi-app/public/.keepkeep index 0000000000,e69de29bb2..e69de29bb2 mode 000000,100644..100644 --- a/apps/arv-web/sample-wsgi-app/public/.keepkeep +++ b/apps/arv-web/sample-wsgi-app/public/.keepkeep diff --combined apps/arv-web/sample-wsgi-app/tmp/.keepkeep index 0000000000,e69de29bb2..e69de29bb2 mode 000000,100644..100644 --- a/apps/arv-web/sample-wsgi-app/tmp/.keepkeep +++ b/apps/arv-web/sample-wsgi-app/tmp/.keepkeep