Add 'apps/arv-web/' from commit 'f9732ad8460d013c2f28363655d0d1b91894dca5'
authorPeter Amstutz <peter.amstutz@curoverse.com>
Fri, 16 Jan 2015 19:05:48 +0000 (14:05 -0500)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Fri, 16 Jan 2015 19:05:48 +0000 (14:05 -0500)
git-subtree-dir: apps/arv-web
git-subtree-mainline: b97ac7f96234cbbb491bdbaade840ab50802f357
git-subtree-split: f9732ad8460d013c2f28363655d0d1b91894dca5

16 files changed:
1  2 
apps/arv-web/Dockerfile
apps/arv-web/README
apps/arv-web/apache2_foreground.sh
apps/arv-web/apache2_vhost
apps/arv-web/arv-web.py
apps/arv-web/sample-cgi-app/public/.htaccess
apps/arv-web/sample-cgi-app/public/index.cgi
apps/arv-web/sample-cgi-app/tmp/.keepkeep
apps/arv-web/sample-rack-app/config.ru
apps/arv-web/sample-rack-app/public/.keepkeep
apps/arv-web/sample-rack-app/tmp/.keepkeep
apps/arv-web/sample-static-page/public/index.html
apps/arv-web/sample-static-page/tmp/.keepkeep
apps/arv-web/sample-wsgi-app/passenger_wsgi.py
apps/arv-web/sample-wsgi-app/public/.keepkeep
apps/arv-web/sample-wsgi-app/tmp/.keepkeep

diff --combined apps/arv-web/Dockerfile
index 0000000000000000000000000000000000000000,d0a6abe8299bf66dc630327972ecc38ec99eb5d3..d0a6abe8299bf66dc630327972ecc38ec99eb5d3
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,27 +1,27 @@@
+ FROM arvados/base
+ MAINTAINER Peter Amstutz <peter.amstutz@curoverse.com>
+ 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 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..826092d447b14b04df32da905dd13f5179b8df80
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -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 .
index 0000000000000000000000000000000000000000,fc6028ea8391b3445e13adc5de4439fe7207ec2d..fc6028ea8391b3445e13adc5de4439fe7207ec2d
mode 000000,100755..100755
--- /dev/null
@@@ -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
index 0000000000000000000000000000000000000000,dad4a9b57a98d640066b9b517ab4e7c7b1c570da..dad4a9b57a98d640066b9b517ab4e7c7b1c570da
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,23 +1,23 @@@
+ <VirtualHost *:80>
+   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
+   <Directory /mnt/public>
+     Options Indexes IncludesNoExec
+     Options -MultiViews
+     AllowOverride All
+     Order allow,deny
+     Allow from all
+   </Directory>
+ </VirtualHost>
diff --combined apps/arv-web/arv-web.py
index 0000000000000000000000000000000000000000,63d760d2f124158c27e94ff83e6732882ff80026..63d760d2f124158c27e94ff83e6732882ff80026
mode 000000,100644..100644
--- /dev/null
@@@ -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)
index 0000000000000000000000000000000000000000,e5145bd37df5a893d6571c8c33c452b70cca71f6..e5145bd37df5a893d6571c8c33c452b70cca71f6
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,3 +1,3 @@@
+ Options +ExecCGI
+ AddHandler cgi-script .cgi
+ DirectoryIndex index.cgi
index 0000000000000000000000000000000000000000,57bc2a9a019d2dff45c08cb4263bf7cb1d1b6605..57bc2a9a019d2dff45c08cb4263bf7cb1d1b6605
mode 000000,100755..100755
--- /dev/null
@@@ -1,0 -1,4 +1,4 @@@
+ #!/usr/bin/perl
+ print "Content-type: text/html\n\n";
+ print "Hello world from perl!";
index 0000000000000000000000000000000000000000,e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,84bb0da9948c7a3c7cad563e603b6d5754d9c866..84bb0da9948c7a3c7cad563e603b6d5754d9c866
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,4 +1,4 @@@
+ app = proc do |env|
+     [200, { "Content-Type" => "text/html" }, ["hello <b>world</b> from ruby"]]
+ end
+ run app
index 0000000000000000000000000000000000000000,e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,a2e485c5d833d22a9a989032a7dc1685fe71f1b5..a2e485c5d833d22a9a989032a7dc1685fe71f1b5
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,6 +1,6 @@@
+ <html>
+   <head><title>arv-web sample</title></head>
+   <body>
+     <p>Hello world static page</p>
+   </body>
+ </html>
index 0000000000000000000000000000000000000000,e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,ea918f0cbf769218fe9c8b9bd78981b81b095ce6..ea918f0cbf769218fe9c8b9bd78981b81b095ce6
mode 000000,100644..100644
--- /dev/null
@@@ -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"]
index 0000000000000000000000000000000000000000,e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
mode 000000,100644..100644
--- /dev/null
index 0000000000000000000000000000000000000000,e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
mode 000000,100644..100644
--- /dev/null