Redesign of Podder.

Podder now only listens to events from the docker
api. Docker event api will use a callback mechanism
to take action when a container is started or stopped.

Adding Dockerfile for podder and an entry into the
compose file.

Change-Id: Ib5735078a69eab3af6076af94bc697ab3e82a239
diff --git a/common/utils/dockerhelpers.py b/common/utils/dockerhelpers.py
index 6db4ebe..af23dbc 100644
--- a/common/utils/dockerhelpers.py
+++ b/common/utils/dockerhelpers.py
@@ -17,17 +17,30 @@
 """
 Some docker related convenience functions
 """
+from datetime import datetime
 
 import os
+import socket
 from structlog import get_logger
 
-from docker import Client
+from docker import Client, errors
 
 
 docker_socket = os.environ.get('DOCKER_SOCK', 'unix://tmp/docker.sock')
 log = get_logger()
 
 
+def remove_container(id, force=True):
+    try:
+        docker_cli = Client(base_url=docker_socket)
+        containers = docker_cli.remove_container(id, force=force)
+
+    except Exception, e:
+        log.exception('failed', e=e)
+        raise
+
+    return containers
+
 def get_my_containers_name():
     """
     Return the docker containers name in which this process is running.
@@ -50,9 +63,32 @@
 
     return name
 
+def get_all_running_containers():
+    try:
+        docker_cli = Client(base_url=docker_socket)
+        containers = docker_cli.containers()
+
+    except Exception, e:
+        log.exception('failed', e=e)
+        raise
+
+    return containers
+
+def inspect_container(id):
+    try:
+        docker_cli = Client(base_url=docker_socket)
+        info = docker_cli.inspect_container(id)
+
+    except Exception, e:
+        log.exception('failed', e=e)
+        raise
+
+    return info
+
+
 def create_host_config(volumes, ports):
     try:
-        port_bindings = { ports[0] : None }
+        port_bindings = ports
         binds = ['{0}:{1}'.format(k, v) for k, v in volumes.iteritems()]
         docker_cli = Client(base_url=docker_socket)
         host_config = docker_cli.create_host_config(binds=binds,
@@ -63,8 +99,15 @@
 
     return host_config
 
+def connect_container_to_network(container, net_id, links):
+    try:
+        docker_cli = Client(base_url=docker_socket)
+        docker_cli.connect_container_to_network(container, net_id, links=links)
+    except:
+        log.exception('Failed to connect container {} to network {}'.format(container, net_id))
+        raise
 
-def create_container_network(name, links):
+def create_networking_config(name, links):
     """
     Creates a container networks based on a set of containers.
     :param name: the network name
@@ -73,7 +116,6 @@
     """
     try:
         docker_cli = Client(base_url=docker_socket)
-        docker_cli.create_network(name)
         networking_config = docker_cli.create_networking_config({
             name : docker_cli.create_endpoint_config(links=links)
         })
@@ -83,8 +125,24 @@
 
     return networking_config
 
+def stop_container(container, timeout=10):
+    try:
+        docker_cli = Client(base_url=docker_socket)
+        docker_cli.stop(container, timeout=timeout)
+    except Exception, e:
+        log.exception('failed', e=e)
+        raise
 
-def start_container(args):
+def create_container(args):
+    try:
+        docker_cli = Client(base_url=docker_socket)
+        container = docker_cli.create_container(**args)
+    except Exception, e:
+        log.exception('failed', e=e)
+        raise
+    return container
+
+def start_container(container):
     """
     Starts a requested container with the appropriate configuration.
     :param args: contains arguments for container creation
@@ -93,10 +151,98 @@
     """
     try:
         docker_cli = Client(base_url=docker_socket)
-        container = docker_cli.create_container(**args)
         response = docker_cli.start(container=container.get('Id'))
     except Exception, e:
         log.exception('failed', e=e)
         raise
     return response
 
+class EventProcessor(object):
+    """
+    This class handles the api session and allows for it to
+    be terminated.
+    """
+    def __init__(self):
+        self.client = CustomClient(base_url=docker_socket)
+        self.events = self.client.events(decode=True)
+
+    def stop_listening(self):
+        """
+        Shuts down the socket.
+        :return: None
+        """
+        if self.events is not None:
+            sock = self.client._get_raw_response_socket(self.events.response)
+            sock.shutdown(socket.SHUT_RDWR)
+
+
+    def listen_for_events(self, handlers):
+        """
+        Listens to the docker event stream and applies the functions
+        in the passed handler to each event.
+
+        docker containers can report the following events:
+
+        attach, commit, copy, create, destroy, detach, die,
+        exec_create, exec_detach, exec_start, export,
+        health_status, kill, oom, pause, rename, resize,
+        restart, start, stop, top, unpause, update
+
+        :param handlers: a dict of functions
+        :return: None
+        """
+        if not handlers or len(handlers) == 0:
+            raise ValueError("Handlers cannot be empty")
+
+        for event in self.events:
+            for k in ['time', 'Time']:
+                if k in event:
+                    event[k] = datetime.fromtimestamp(event[k])
+            log.debug('docker event: {}'.format(event))
+
+            data = {}
+            i = get_id(event)
+            if i is not None:
+                try:
+                    if 'from' in event or 'From' in event:
+                        data = self.client.inspect_container(i)
+                    else:
+                        data = self.client.inspect_image(i)
+                    data[i] = data
+                except errors.NotFound:
+                    log.info('No data for container {}'.format(i))
+
+            status = get_status(event)
+            if status in handlers:
+                handlers[get_status(event)](event, data, handlers['podder_config'])
+            else:
+                log.debug("No handler for {}; skipping...".format(status))
+
+class CustomGenerator(object):
+    """
+    This is a custom ugly class that allows for the generator
+    to be (kind of) cleanly closed.
+    """
+    def __init__(self, stream, response, decode):
+        self.stream = stream
+        self.response = response
+        self.decode = decode
+
+    def __iter__(self):
+        for item in super(CustomClient, self.stream).\
+                _stream_helper(self.response, self.decode):
+            yield item
+
+class CustomClient(Client):
+    def _stream_helper(self, response, decode=False):
+        return CustomGenerator(self, response, decode)
+
+def get_status(event):
+    for k in ['status', 'Status']:
+        if k in event:
+            return event[k]
+
+def get_id(event):
+    for k in ['id', 'ID', 'Id']:
+        if k in event:
+            return event[k]
\ No newline at end of file