- if collection:
- if not args.image:
- docker_image = None
-
- # FUSE is asynchronous, so there is a race between
- # the directory being updated above and the kernel
- # cache being refreshed. This manifests as the
- # bizare behavior where os.path.exists() returns
- # True, but open() raises "file not found". The
- # workaround is to keep trying until the kernel
- # catches up.
- while not docker_image and os.path.exists(os.path.join(mountdir, "docker_image")):
- try:
- with open(os.path.join(mountdir, "docker_image")) as di:
- docker_image = di.read().strip()
- except IOError as e:
- pass
-
- if not docker_image:
- logger.error("Collection must contain a file 'docker_image' or must specify --image on the command line.")
-
- if docker_image and ((docker_image != prev_docker_image) or cid is None):
- if cid:
- logger.info("Stopping Docker container")
- subprocess.check_call(["docker", "stop", cid])
- cid = None
- docker_proc = None
-
- if docker_image:
- logger.info("Starting Docker container %s", docker_image)
- ciddir = tempfile.mkdtemp()
- cidfilepath = os.path.join(ciddir, "cidfile")
- docker_proc = subprocess.Popen(["docker", "run",
- "--cidfile=%s" % (cidfilepath),
- "--publish=%i:80" % (port),
- "--volume=%s:/mnt:ro" % mountdir,
- docker_image])
- cid = None
- while not cid and docker_proc.poll() is None:
- try:
- with open(cidfilepath) as cidfile:
- cid = cidfile.read().strip()
- except IOError:
- pass
- try:
- os.unlink(cidfilepath)
- os.rmdir(ciddir)
- except OSError:
- pass
-
- prev_docker_image = docker_image
- logger.info("Container id %s", cid)
- elif cid:
- logger.info("Sending refresh signal to container")
- # Send SIGHUP to all the processes inside the
- # container. By convention, services are expected
- # to reload their configuration. If they die
- # instead, that's okay, because then we'll just
- # start a new container.
- #
- # Getting the services inside the container to
- # refresh turned out to be really hard. Here are
- # some of the other things I tried:
- #
- # docker kill --signal=HUP # no effect
- # docker_proc.send_signal(signal.SIGHUP) # no effect
- # os.killpg(os.getpgid(docker_proc.pid), signal.SIGHUP) # docker-proxy dies as collatoral damage
- # docker exec apache2ctl restart # only works if service is using apache.
- # Sending HUP directly to the processes inside the container: permission denied
-
- subprocess.check_call(["docker", "exec", cid, "killall", "--regexp", ".*", "--signal", "HUP"])
- elif cid:
- logger.info("Stopping docker container")
- subprocess.check_call(["docker", "stop", cid])
- except subprocess.CalledProcessError:
- cid = None
-
- if not cid:
- logger.warning("No service running! Will wait for a new collection to appear in the project.")
- else:
- logger.info("Waiting for events")
-
- running = True
- loop = True
- while running:
- # Main run loop. Wait on project events, signals, or the
- # Docker container stopping.
-
- try:
- # Poll the queue with a 1 second timeout, if we have no
- # timeout the Python runtime doesn't have a chance to
- # process SIGINT or SIGTERM.
- eq = evqueue.get(True, 1)
- logger.info("%s %s", eq[1], eq[2])
- newcollection = collection
- if eq[1] in ('add', 'update', 'create'):
- newcollection = eq[2]
- elif eq[1] == 'remove':
- collections = api.collections().list(filters=[["owner_uuid", "=", project]],
- limit=1,
- order='modified_at desc').execute()['items']
- newcollection = collections[0]['uuid'] if len(collections) > 0 else None
- running = False
- except Queue.Empty:
- pass
-
- if docker_proc and docker_proc.poll() is not None:
- logger.warning("Service has terminated. Will try to restart.")
- cid = None
- docker_proc = None
- running = False
-
- except (KeyboardInterrupt):
- logger.info("Got keyboard interrupt")
- ws.close()
- loop = False
- except Exception as e:
- logger.exception(e)
- ws.close()
- loop = False
- finally:
- if cid:
- logger.info("Stopping docker container")
- subprocess.check_call(["docker", "stop", cid])
-
- if mountdir:
- logger.info("Unmounting")
- subprocess.call(["fusermount", "-u", "-z", mountdir])
- os.rmdir(mountdir)
+ self.run_docker()
+ self.wait_for_events()
+ except (KeyboardInterrupt):
+ logger.info("Got keyboard interrupt")
+ self.ws.close()
+ self.loop = False
+ except Exception as e:
+ logger.exception("Caught fatal exception, shutting down")
+ self.ws.close()
+ self.loop = False
+ finally:
+ self.stop_docker()
+
+ if self.mountdir:
+ logger.info("Unmounting")
+ subprocess.call(["fusermount", "-u", self.mountdir])
+ os.rmdir(self.mountdir)
+
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--project-uuid', type=str, required=True, help="Project uuid to watch")
+ parser.add_argument('--port', type=int, default=8080, help="Host port to listen on (default 8080)")
+ parser.add_argument('--image', type=str, help="Docker image to run")
+
+ args = parser.parse_args(argv)
+
+ signal.signal(signal.SIGTERM, lambda signal, frame: sys.exit(0))
+
+ try:
+ arvweb = ArvWeb(args.project_uuid, args.image, args.port)
+ arvweb.run()
+ except arvados.errors.ArgumentError as e:
+ logger.error(e)