From 0bb200df309d4f0682122202f32147f4f6387e78 Mon Sep 17 00:00:00 2001
From: Antoine Catton <acatton@tiolive.com>
Date: Mon, 19 Sep 2011 13:08:09 +0200
Subject: [PATCH] Merge Antoine Catton work on promises.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Squashed commit of the following:

commit 29caaa556d1d641f6b2241db84d2d8b588689a82
Merge: 32d5fef 13d6e1d
Author: Antoine Catton <acatton@tiolive.com>
Date:   Mon Sep 19 11:49:27 2011 +0200

    Merge branch 'master' into promises

    Conflicts:
    	slapos/recipe/kvm/__init__.py

commit 32d5fef49f22e3a47ca0b0728e502d177979b214
Author: Antoine Catton <acatton@tiolive.com>
Date:   Wed Aug 24 16:45:31 2011 +0200

    Minor: Use pkg_resources the right way

    http://peak.telecommunity.com/DevCenter/PkgResources

commit e2fae493ab6a96fa10cbe6531ee1a1ba9c16bb4c
Author: Antoine Catton <acatton@tiolive.com>
Date:   Wed Aug 24 16:00:02 2011 +0200

    Minor: some typo fix to get the recipe working

commit 3764ab93da5031f9b5ea59072090d2b961a791cf
Author: Antoine Catton <acatton@tiolive.com>
Date:   Wed Aug 24 15:53:17 2011 +0200

    Minor: typo fix

commit 259026f718e0e37c5c151fb40dbf85866f6ab87f
Author: Antoine Catton <acatton@tiolive.com>
Date:   Wed Aug 24 15:33:04 2011 +0200

    Cleanup: use createPromiseWrapper instead of easy_install.script

    - Install the socket_connection_attempt script into bin/ directory
    - Create promises running this script specifying the right command line
      arguments

commit a870a4d77a3b72d79354e557ee0b8571da9a7459
Author: Antoine Catton <acatton@tiolive.com>
Date:   Wed Aug 24 13:44:50 2011 +0200

    Minor: remove appending promise path to path_list

commit 260df1be27ac382061809aa8851130cd26f74be0
Author: Łukasz Nowak <luke@nexedi.com>
Date:   Thu Jul 28 14:40:29 2011 +0200

    Do not expose python's shabang.

commit 7db31e9b6759183a54390480a77032009bf2e9ae
Author: Antoine Catton <acatton@tiolive.com>
Date:   Thu Jul 28 14:13:03 2011 +0200

    Replaced the usage python script as template by the easy_install
    capability usage.

commit 3f62cda3a2a9e64cc92cd4c01585b422bb30ff0e
Author: Antoine Catton <acatton@tiolive.com>
Date:   Tue Jul 26 15:34:13 2011 +0200

    Adding promises to kvm software release.

    Not tested yet

    Minor: removed magic number 5900

commit 2e36426bd11e4833ec3be6a8e2b664e343760417
Author: Antoine Catton <acatton@tiolive.com>
Date:   Tue Jul 26 13:38:14 2011 +0200

    Trailing spaces on KVM recipe

commit 02ed180bd0002cbfe387875ec917e4d6e214e40e
Author: Antoine Catton <acatton@tiolive.com>
Date:   Mon Jul 25 16:57:43 2011 +0200

    Method createPromise in slapos.librecipe

    This method create a promise script which should return 0 whether the
    promise was kept.
---
 slapos/recipe/kvm/__init__.py                 | 42 ++++++++++++++++++-
 .../recipe/kvm/socket_connection_attempt.py   | 26 ++++++++++++
 .../kvm/template/port_listening_promise.in    |  4 ++
 slapos/recipe/librecipe/__init__.py           | 13 ++++++
 4 files changed, 84 insertions(+), 1 deletion(-)
 create mode 100644 slapos/recipe/kvm/socket_connection_attempt.py
 create mode 100644 slapos/recipe/kvm/template/port_listening_promise.in

diff --git a/slapos/recipe/kvm/__init__.py b/slapos/recipe/kvm/__init__.py
index d0ed230de..12d4b7877 100644
--- a/slapos/recipe/kvm/__init__.py
+++ b/slapos/recipe/kvm/__init__.py
@@ -37,6 +37,9 @@ import hashlib
 
 class Recipe(BaseSlapRecipe):
 
+  # To avoid magic numbers
+  VNC_BASE_PORT = 5900
+
   def _install(self):
     """
     Set the connection dictionnary for the computer partition and create a list
@@ -54,9 +57,25 @@ class Recipe(BaseSlapRecipe):
     self.ca_conf                         = self.installCertificateAuthority()
     self.key_path, self.certificate_path = self.requestCertificate('noVNC')
 
+    # Install the socket_connection_attempt script
+    catcher = zc.buildout.easy_install.scripts(
+      [('check_port_listening', __name__ + 'socket_connection_attempt', 'connection_attempt')],
+      self.ws,
+      sys.executable,
+      self.bin_directory,
+    )
+    # Save the check_port_listening script path
+    check_port_listening_script = catcher[0]
+    # Get the port_listening_promise template path, and save it
+    self.port_listening_promise_path = pkg_resources.resource_filename(
+      __name__, 'template/port_listening_promise.in')
+    self.port_listening_promise_conf = dict(
+     check_port_listening_script=check_port_listening_script,
+    )
+
     kvm_conf = self.installKvm(vnc_ip = self.getLocalIPv4Address())
 
-    vnc_port = 5900 + kvm_conf['vnc_display']
+    vnc_port = Recipe.VNC_BASE_PORT + kvm_conf['vnc_display']
 
     noVNC_conf = self.installNoVnc(source_ip   = self.getGlobalIPv6Address(),
                                    source_port = 6080,
@@ -164,6 +183,17 @@ class Recipe(BaseSlapRecipe):
     ##slapreport_runner_path = self.instanciate_wrapper("slapreport",
     #    [database_path, python_path])
 
+    # Add VNC promise
+    self.port_listening_promise_conf.update(
+      hostname=kvm_conf['vnc_ip'],
+      port=Recipe.VNC_BASE_PORT + kvm_conf['vnc_display'],
+    )
+    self.createPromiseWrapper("vnc_promise",
+        self.substituteTemplate(self.port_listening_promise_path,
+                                self.port_listening_promise_conf,
+                               )
+                             )
+
     return kvm_conf
 
   def installNoVnc(self, source_ip, source_port, target_ip, target_port):
@@ -207,6 +237,16 @@ class Recipe(BaseSlapRecipe):
 
     self.path_list.append(websockify_runner_path)
 
+    # Add noVNC promise
+    self.port_listening_promise_conf.update(hostname=noVNC_conf['source_ip'],
+                                            port=noVNC_conf['source_port'],
+                                           )
+    self.createPromiseWrapper("novnc_promise",
+        self.substituteTemplate(self.port_listening_promise_path,
+                                self.port_listening_promise_conf,
+                               )
+                             )
+
     return noVNC_conf
 
   def linkBinary(self):
diff --git a/slapos/recipe/kvm/socket_connection_attempt.py b/slapos/recipe/kvm/socket_connection_attempt.py
new file mode 100644
index 000000000..dfd9fad4b
--- /dev/null
+++ b/slapos/recipe/kvm/socket_connection_attempt.py
@@ -0,0 +1,26 @@
+import socket
+import sys
+
+def connection_attempt():
+
+  try:
+    hostname, port = sys.argv[1:3]
+  except ValueError:
+    print >> sys.stderr, """Bad command line.
+  Usage: %s hostname|ip port""" % sys.argv[0]
+    sys.exit(1)
+
+  connection_okay = False
+
+  try:
+    s = socket.create_connection((hostname, port))
+    connection_okay = True
+    s.close()
+  except (socket.error, socket.timeout):
+    connection_okay = False
+
+  if not connection_okay:
+    print >> sys.stderr, "%(port)s on %(ip)s isn't listening" % {
+      'port': port, 'ip': hostname
+    }
+    sys.exit(127)
diff --git a/slapos/recipe/kvm/template/port_listening_promise.in b/slapos/recipe/kvm/template/port_listening_promise.in
new file mode 100644
index 000000000..15fa390d0
--- /dev/null
+++ b/slapos/recipe/kvm/template/port_listening_promise.in
@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+
+"%(check_port_listening_script)s" "%(hostname)s" "%(port)s"
+exit $?
diff --git a/slapos/recipe/librecipe/__init__.py b/slapos/recipe/librecipe/__init__.py
index e0eb9d547..c49c6c772 100644
--- a/slapos/recipe/librecipe/__init__.py
+++ b/slapos/recipe/librecipe/__init__.py
@@ -60,6 +60,7 @@ class BaseSlapRecipe:
         'xml_report')
     self.destroy_script_location = os.path.join(self, self.work_directory,
         'sbin', 'destroy')
+    self.promise_directory = os.path.join(self.etc_directory, 'promise')
 
     # default directory structure information
     self.default_directory_list = [
@@ -71,6 +72,7 @@ class BaseSlapRecipe:
       self.etc_directory, # CP/etc - configuration container
       self.wrapper_directory, # CP/etc/run - for wrappers
       self.wrapper_report_directory, # CP/etc/report - for report wrappers
+      self.promise_directory, # CP/etc/promise - for promise checking scripts
       self.var_directory, # CP/var - partition "internal" container for logs,
                           # and another metadata
       self.wrapper_xml_report_directory, # CP/var/xml_report - for xml_report wrappers
@@ -243,3 +245,14 @@ class BaseSlapRecipe:
   def _install(self):
     """Hook which shall be implemented in children class"""
     raise NotImplementedError('Shall be implemented by subclass')
+
+  def createPromiseWrapper(self, promise_name, file_content):
+    """Create a promise wrapper.
+
+    This wrapper aim to check if the software release is doing its job.
+
+    Return the promise file path.
+    """
+    promise_path = os.path.join(self.promise_directory, promise_name)
+    self._writeExecutable(promise_path, file_content)
+    return promise_path
-- 
2.30.9