[node-patches] Change in ovirt-node[master]: [DRAFT] Add a docker plugin

fabiand at fedoraproject.org fabiand at fedoraproject.org
Wed Mar 5 09:20:40 UTC 2014


Fabian Deutsch has uploaded a new change for review.

Change subject: [DRAFT] Add a docker plugin
......................................................................

[DRAFT] Add a docker plugin

This plugin allows Node to act as a "docker-host". This means that
docker is run in "host mode", binding itself to a public port.

Binding to a public port is unsafe, because everyone can use it.

Once this is working well, we should bind only locally and allow access
via SSH.

This plugin
- adds docker-io to the image
- persists the correct dirs
- adds a page to display some basic informations
- requires systemd

Change-Id: I1021f347285ff5ea8e343003291e610b02a2f838
Signed-off-by: Fabian Deutsch <fabiand at fedoraproject.org>
---
M ovirt-node.spec.in
M src/ovirt/node/plugins.py
A src/ovirt/node/setup/docker/__init__.py
A src/ovirt/node/setup/docker/docker_page.py
M src/ovirt/node/utils/system.py
5 files changed, 302 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/ovirt-node refs/changes/70/25370/1

diff --git a/ovirt-node.spec.in b/ovirt-node.spec.in
index 970eef3..7b1c605 100644
--- a/ovirt-node.spec.in
+++ b/ovirt-node.spec.in
@@ -342,6 +342,45 @@
 %endif
 
 
+#
+# Docker plugin subpackage
+#
+%global docker_port 4243
+%package plugin-docker
+Summary:        Become a docker host %{product_family}
+Group:          Applications/System
+
+Requires:       docker-io
+BuildRequires:  systemd-units
+Requires:       systemd
+
+
+%description plugin-docker
+This package adds docker (https://www.docker.io/) to %{product_family}.
+Docker will be run in daemon mode and attach itself to the public port %{docker_port}.
+
+A client can connect to the docker host using:
+$ docker -H $DOCKER_ADDR:%{docker_port} info
+
+
+%post plugin-docker
+if [ $1 -eq 1 ] ; then
+    # Initial installation
+
+    # Create persisted dir
+    service docker stop || :
+    mv /var/lib/docker /data/
+    mkdir /var/lib/docker
+    echo -e "\n/data/docker /var/lib/docker none bind 0 0" >> /etc/fstab
+
+    # Bind docker to external port
+    sed -i '/^ExecStart/ s/$/ -H 0.0.0.0:%{docker_port}/' /usr/lib/systemd/system/docker.service
+
+    systemctl daemon-reload >/dev/null 2>&1 || :
+    systemctl enable docker.service >/dev/null 2>&1 || :
+fi
+
+
 %prep
 %setup -q
 
diff --git a/src/ovirt/node/plugins.py b/src/ovirt/node/plugins.py
index 282f5b5..94223c5 100644
--- a/src/ovirt/node/plugins.py
+++ b/src/ovirt/node/plugins.py
@@ -525,6 +525,9 @@
     def __str__(self):
         return "<UIElements %s>" % self._elements
 
+    def items(self):
+        return self._elements.items()
+
     class Group(list, base.Base):
         def __init__(self, uielements, paths):
             super(UIElements.Group, self).__init__()
diff --git a/src/ovirt/node/setup/docker/__init__.py b/src/ovirt/node/setup/docker/__init__.py
new file mode 100644
index 0000000..a748432
--- /dev/null
+++ b/src/ovirt/node/setup/docker/__init__.py
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# __init__.py - Copyright (C) 2014 Red Hat, Inc.
+# Written by Fabian Deutsch <fabiand at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+
+"""
+Docker Plugin
+"""
+import docker_page
+
+
+#
+# Magic function to register all plugins to be used
+#
+def createPlugins(application):
+    docker_page.Plugin(application)
diff --git a/src/ovirt/node/setup/docker/docker_page.py b/src/ovirt/node/setup/docker/docker_page.py
new file mode 100644
index 0000000..a2e436e
--- /dev/null
+++ b/src/ovirt/node/setup/docker/docker_page.py
@@ -0,0 +1,217 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# docker_page.py - Copyright (C) 2012 Red Hat, Inc.
+# Written by Fabian Deutsch <fabiand at redhat.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.  A copy of the GNU General Public License is
+# also available at http://www.gnu.org/copyleft/gpl.html.
+from ovirt.node import base, plugins, ui
+from ovirt.node.utils import process, system
+from subprocess import CalledProcessError
+import urllib
+import threading
+import time
+
+"""
+Docker Status
+"""
+
+
+class Plugin(plugins.NodePlugin):
+    _model = None
+    watcher = None
+
+    def __init__(self, app):
+        super(Plugin, self).__init__(app)
+        self._model = {}
+        self.watcher = DockerWatcher(self)
+        self.watcher.start()
+
+    def has_ui(self):
+        return True
+
+    def name(self):
+        return "Docker"
+
+    def rank(self):
+        return 15
+
+    def model(self):
+        d = Docker()
+        is_alive = d.is_alive()
+
+        model = {"docker.is_alive": "%s" % is_alive,
+                 "docker.status": "",
+                 "docker.info": "",
+                 }
+
+        return model
+
+    def validators(self):
+        return {}
+
+    def ui_content(self):
+        is_alive = Docker().is_alive()
+
+        DockerAsyncUpdate(self).start()
+
+        ws = [ui.Header("header[0]", "Docker"),
+              ui.KeywordLabel("docker.is_alive", "Is Alive: "),
+              ui.Divider("divider[0]"),
+              ui.KeywordLabel("docker.status", "Status:"),
+              ]
+
+        page = ui.Page("page", ws)
+        page.buttons = [ui.Button("action.images", "Images",
+                                  enabled=is_alive),
+                        ui.Button("action.containers", "Containers",
+                                  enabled=is_alive)]
+        self.widgets.add(page)
+        self.widgets.add(page.buttons)
+        return page
+
+    def on_change(self, changes):
+        pass
+
+    def on_merge(self, changes):
+        actions = {"action.images": ["images"],
+                   "action.containers": ["ps"],
+                   }
+        for key in changes:
+            if key in actions:
+                self._dialog = DockerTxtDialog(actions[key])
+                return self._dialog
+
+
+class DockerTxtDialog(ui.InfoDialog):
+    """A simple class to display results of docker subcomands in a dialog
+    """
+    def __init__(self, subcmd):
+        cmd, txt = Docker().docker(subcmd, with_cmd=True)
+        title = "$ " + " ".join(cmd)
+        super(DockerTxtDialog, self).__init__("dialog.docker.txt", title, txt)
+
+
+class DockerWatcher(threading.Thread):
+    """A thread which watches docker for status changes. If the status changes it updtes the UI
+    """
+    daemon = True
+
+    def __init__(self, plugin):
+        self.plugin = plugin
+        self.app = plugin.application
+        super(DockerWatcher, self).__init__()
+
+    def run(self):
+        try:
+            self._run()
+        except Exception as e:
+            self.app.logger.debug("DockerWatcher", exc_info=True)
+
+    def on_state_change(self):
+        DockerAsyncUpdate(self.plugin).run()
+
+    def _run(self):
+        d = Docker()
+        last_state = None
+        while True:
+            self.app.logger.debug("Checking docker livelyness")
+            time.sleep(1)
+            alive = d.is_alive()
+            has_changed = last_state != alive
+            if has_changed:
+                last_state = alive
+                self.on_state_change()
+
+
+class DockerAsyncUpdate(threading.Thread):
+    """A thread which gathers information from long running process calls
+    In a thread to keep the UI responsive
+    """
+    daemon = True
+
+    def __init__(self, plugin):
+        self.plugin = plugin
+        self.app = plugin.application
+        super(DockerAsyncUpdate, self).__init__()
+
+    def run(self):
+        try:
+            self._run()
+        except Exception as e:
+            self.app.logger.debug("DockerStatusThread", exc_info=True)
+
+    def _run(self):
+        d = Docker()
+        alive = d.is_alive()
+        self._update_ui("docker.is_alive", "%s" % alive)
+        self._update_ui("docker.status", d.status())
+        #self._update_ui("docker.info", d.info() if alive else "")
+        for n, w in self.plugin.widgets.items():
+            self.app.logger.debug("%s" % n)
+            if n.startswith("action."):
+                w.enabled(alive) 
+
+    def _update_ui(self, path, txt):
+        con = self.app.ui.thread_connection()
+        def update_ui(path=path, txt=txt):
+            self.plugin.widgets[path].value(txt)
+        con.call(lambda: update_ui())
+
+
+class Docker(base.Base):
+    """A wrapper around several docker related binaries
+    """
+    port = 4243
+
+    def docker(self, args=[], with_cmd=False):
+        """Wrap docker binary
+        """
+        assert type(args) is list
+        cmd = ["docker", "-H", ":%s" % self.port] + args
+        ret = process.check_output(cmd, stderr=process.PIPE)
+        if with_cmd:
+            ret = (cmd, ret)
+        return ret
+
+    def service(self, cmd):
+        """Wrap docker service
+        """
+        return system.service("docker", cmd)
+
+    def logs(self):
+        return system.journal("docker")
+
+    def status(self):
+        status = ""
+        try:
+            status = self.service("status")
+        except process.CalledProcessError as e:
+            status = e.output
+        return status
+
+    def info(self):
+        return self.docker(["info"])
+
+    def is_alive(self):
+        alive = False
+        try:
+            urllib.urlopen('http://127.0.0.1:%s/' % self.port)
+            alive = True
+        except:
+            #self.logger.debug("Docker is  alive")
+            pass
+        return alive
diff --git a/src/ovirt/node/utils/system.py b/src/ovirt/node/utils/system.py
index 18d66c4..a2771aa 100644
--- a/src/ovirt/node/utils/system.py
+++ b/src/ovirt/node/utils/system.py
@@ -164,6 +164,17 @@
     return r
 
 
+def journal(unit=None, this_boot=True):
+    """Convenience function to access the journal
+    """
+    cmd = ["journalctl"]
+    if unit:
+        cmd += ["--unit", unit]
+    if this_boot:
+        cmd += ["--this-boot"]
+    return process.pipe(cmd)
+
+
 def has_systemd():
     """Determine if the system has systemd available.
     """


-- 
To view, visit http://gerrit.ovirt.org/25370
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I1021f347285ff5ea8e343003291e610b02a2f838
Gerrit-PatchSet: 1
Gerrit-Project: ovirt-node
Gerrit-Branch: master
Gerrit-Owner: Fabian Deutsch <fabiand at fedoraproject.org>



More information about the node-patches mailing list