commit 3c3bccaba0deafeb131eba253a18b8ae87a82b0c
Author: Dimitris Aragiorgis <dimara@grnet.gr>
Date:   Tue Nov 26 17:09:03 2013 +0200

    Huuuuge snf-deploy refactor
    
    Introduce the concept of Roles and Components.
    
    Roles are ns, mq, db, nfs, astakos, cyclades, pithos, cms, stats,
    ganeti, master, and client.
    
    Each role consists of various SynnefoComponents which  should define:
    
    * commands to check installation prerequisites (check)
    * packages to install (REQUIRED_PACKAGES)
    * commands to prepare installation (prepare)
    * configuration templates (configure)
    * reload commands (restart)
    * initialization commands (initialize)
    * test commands (test)
    
    SynnefoComponents are unaware of fabric environment. They
    get initialized with a Host object and a Env object.
    
    Host includes all the necessary info for the installation node
    (ip, hostname, alias, fqdn..) and Env includes all the configuration info
    (created after parsing config files)
    
    After defining components, fabfile should just do the following:
    
    @roles("somerole")
    def setup_somerole_role():
      SetupSynnefoRole("SomeRole")
    
    In case you want to run a component's specific method just run
    
    RunComponentMethod(SomeComponent, "method_name", *args, **kwargs)
    
    During a role setup you might have to retrieve info from other
    components already installed (user token, backend id, etc.) Use
    'execute' fabric method and fill env object with required info.
    
    Make deployment re-entrant.
    
    Check if specific component is already installed and if yes then skip it.
    
    Currently this is done on node/component level. The component's
    status on a target node is either ok or nothing.
    
    Introduce conflicting components.
    
    Two components might not be able/or should not coexist in the same
    node, e.g. NFS and Mount. These conflicts are defined in CONFLICTS
    dict in roles.py.
    
    Sync ci and deploy conf files.
    
    ci: Change node's password in nodes.conf
    
    Always enable CSRF. Install CMS only if it resides on different
    node than pithos, cyclades and astakos.
    
    Add copyright headers.
    
    Signed-off-by: Dimitris Aragiorgis <dimara@grnet.gr>

diff --git a/snf-deploy/snfdeploy/utils.py b/snf-deploy/snfdeploy/utils.py
index d3ad86a..dfaf27c 100644
--- a/snf-deploy/snfdeploy/utils.py
+++ b/snf-deploy/snfdeploy/utils.py
@@ -1,3 +1,36 @@
+# Copyright (C) 2010, 2011, 2012, 2013 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+#   1. Redistributions of source code must retain the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer.
+#
+#   2. Redistributions in binary form must reproduce the above
+#      copyright notice, this list of conditions and the following
+#      disclaimer in the documentation and/or other materials
+#      provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A. OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
 from __future__ import with_statement
 from fabric.api import hide, env, settings, local, roles
 from fabric.operations import run, put, get
@@ -9,6 +42,8 @@ import tempfile
 import ast
 from snfdeploy.lib import debug, Conf, Env, disable_color
 from snfdeploy import massedit
+from snfdeploy.components import *
+from snfdeploy.roles import ROLES, CONFLICTS
 
 
 def abort(action):
@@ -17,7 +52,8 @@ def abort(action):
             return action(*args, **kwargs)
         except BaseException as e:
             abort = kwargs.get("abort", True)
-            if not abort:
+            force = env.force
+            if not abort or force:
                  debug(env.host, "WARNING: command failed. Continuing anyway...")
             else:
                  fabric.utils.abort(e)
@@ -26,25 +62,33 @@ def abort(action):
 
 @abort
 def try_get(remote_path, local_path=None, **kwargs):
+    if env.dry_run:
+        debug(env.host, " * Fetching file localy... ", remote_path)
+        return
     get(remote_path, local_path=local_path, **kwargs)
 
 
 @abort
-def try_put(local_path=None, remote_path=None, **kwargs):
-    put(local_path=local_path, remote_path=remote_path, **kwargs)
-
+def try_put(local_path=None, remote_path=None, mode=0644, **kwargs):
+    if env.dry_run:
+        debug(env.host, " * Upload file... ", remote_path)
+        return
+    put(local_path=local_path, remote_path=remote_path, mode=mode)
 
 
 @abort
 def try_run(cmd, **kwargs):
-    if env.local:
-        return local(cmd, capture=True)
+    if env.dry_run:
+        debug(env.host, cmd)
+        return ""
+    elif env.local:
+        return local(cmd, capture=True, shell="/bin/bash")
     else:
         return run(cmd)
 
 
 def install_package(package):
-    debug(env.host, " * Installing package %s..." % package)
+    debug(env.host, " * Installing package... ", package)
     apt_get = "export DEBIAN_FRONTEND=noninteractive ;" + \
               "apt-get install -y --force-yes "
 
@@ -92,3 +136,115 @@ def customize_settings_from_tmpl(tmpl, replace):
     return custom
 
 
+def get_node_info(ident):
+    if ident in env.env.ips_info:
+        return env.env.ips_info[ident]
+    elif ident in env.env.hosts_info:
+        return env.env.hosts_info[ident]
+    elif ident in env.env.nodes_info:
+        return env.env.nodes_info[ident]
+
+
+def GetFromComponent(component, remote, local):
+    c = GetSynnefoComponent(component)
+    c.debug(" * Downloading: ", remote)
+    with settings(host_string=c.node_info.ip):
+        try_get(remote, local)
+
+
+def PutToComponent(component, local, remote):
+    c = GetSynnefoComponent(component)
+    c.debug(" * Uploading: ", remote)
+    with settings(host_string=c.node_info.ip):
+        try_put(local, remote)
+
+
+def RunComponentMethod(component, method, *args, **kwargs):
+    c = GetSynnefoComponent(component)
+    c.debug(" * Running method: ", method)
+    with settings(host_string=c.node_info.ip):
+        fn = getattr(c, method)
+        ret = ""
+        for cmd in fn(*args, **kwargs):
+            ret += try_run(cmd)
+        return ret
+
+
+def GetSynnefoComponent(component):
+    node_info = get_node_info(env.host)
+    env.password = node_info.passwd
+    return component(node_info, env)
+
+
+def conflicting_exists(component):
+    conflict = CONFLICTS.get(component, [])
+    for c in conflict:
+        cs = env.env.status.check_status(env.host, c)
+        if cs:
+            debug(env.host, "Conflicting component already exists", c.__name__)
+            return True
+
+    return False
+
+
+def SetupSynnefoRole(role):
+    debug("Setting up base configuration for: ", role)
+    try:
+        components = ROLES.get(role)
+    except KeyError:
+        debug(env.host, "Please give a valid role")
+        return
+    for c in components:
+        if conflicting_exists(c):
+            continue
+        status = env.env.status.check_status(env.host, c)
+        if status:
+            debug(env.host, "Base configuration already exists", c.__name__)
+        else:
+            AddSynnefoComponent(c)
+            env.env.status.update_status(env.host, c, "ok")
+            env.env.status.write_status()
+
+
+class AddSynnefoComponent(object):
+
+    def _run(self, commands):
+        for c in commands:
+            try_run(c)
+
+    def _install(self, packages):
+        for p in packages:
+            install_package(p)
+
+    def _configure(self, templates):
+        for tmpl, replace, opts in templates:
+            mode = opts.get("mode", 0644)
+            remote = opts.get("remote", tmpl)
+            custom = customize_settings_from_tmpl(tmpl, replace)
+            try_put(custom, remote, mode)
+            os.remove(custom)
+
+    def __init__(self, component):
+        self.c = GetSynnefoComponent(component)
+        self.c.debug("Adding component..")
+
+        self.c.debug(" * Checking prerequisites..")
+        self._run(self.c.check())
+
+        self.c.debug(" * Installing packages..")
+        self._install(self.c.install())
+
+        self.c.debug(" * Preparing configuration..")
+        self._run(self.c.prepare())
+
+        self.c.debug(" * Setting up configuration files..")
+        self._configure(self.c.configure())
+
+        self.c.debug(" * Restarting services..")
+        self._run(self.c.restart())
+
+        self.c.debug(" * Initializing setup..")
+        self._run(self.c.initialize())
+
+        self.c.debug(" * Testing setup..")
+        self._run(self.c.test())
