Merge branch 'master' of github.com:open-cloud/xos
diff --git a/README.md b/README.md
index 7a72658..ab36a2c 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,10 @@
 Additional design notes, presentations, and other collateral are 
 also available at http://xosproject.org and http://cord.onosproject.org.
 
-The quickest way to get started is to look at the collection of
+One quick way to get started is to build and run the containers in
+*containers* (see the README in that directory for more information).
+
+Another quick way to get started is to look at the collection of
 canned configurations in *xos/configurations*. The *cord* 
 configuration in that directory corresponds to our current 
 CORD development environment, and the README you'll find there
diff --git a/xos/synchronizers/base/ansible.py b/xos/synchronizers/base/ansible.py
index 17a0c45..7c30685 100644
--- a/xos/synchronizers/base/ansible.py
+++ b/xos/synchronizers/base/ansible.py
@@ -100,7 +100,7 @@
 
     if (not Config().observer_pretend):
         if not run_ansible_script:
-            run_ansible_script = os.path.join(XOS_DIR, "observer/run_ansible")
+            run_ansible_script = os.path.join(XOS_DIR, "synchronizers/base/run_ansible")
 
         process = subprocess.Popen("%s %s" % (run_ansible_script, shellquote(fqp)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
         msg = process.stdout.read()
diff --git a/xos/synchronizers/openstack/ansible.py b/xos/synchronizers/openstack/ansible.py
deleted file mode 100644
index fda2300..0000000
--- a/xos/synchronizers/openstack/ansible.py
+++ /dev/null
@@ -1,211 +0,0 @@
-#!/usr/bin/env python
-import jinja2
-import tempfile
-import os
-import json
-import pdb
-import string
-import random
-import re
-import traceback
-import subprocess
-from xos.config import Config, XOS_DIR
-from xos.logger import observer_logger
-
-try:
-    step_dir = Config().observer_steps_dir
-    sys_dir = Config().observer_sys_dir
-except:
-    step_dir = XOS_DIR + '/observer/steps'
-    sys_dir = '/opt/opencloud'
-
-os_template_loader = jinja2.FileSystemLoader( searchpath=step_dir)
-os_template_env = jinja2.Environment(loader=os_template_loader)
-
-def parse_output(msg):
-    lines = msg.splitlines()
-    results = []
-
-    observer_logger.info(msg)
-
-    for l in lines:
-        magic_str = 'ok: [127.0.0.1] => '
-        magic_str2 = 'changed: [127.0.0.1] => '
-        if (l.startswith(magic_str)):
-            w = len(magic_str)
-            str = l[w:]
-            d = json.loads(str)
-            results.append(d)
-        elif (l.startswith(magic_str2)):
-            w = len(magic_str2)
-            str = l[w:]
-            d = json.loads(str)
-            results.append(d)
-
-
-    return results
-
-def parse_unreachable(msg):
-    total_unreachable=0
-    total_failed=0
-    for l in msg.splitlines():
-        x = re.findall('ok=([0-9]+).*changed=([0-9]+).*unreachable=([0-9]+).*failed=([0-9]+)', l)
-        if x:
-            (ok, changed, unreachable, failed) = x[0]
-            ok=int(ok)
-            changed=int(changed)
-            unreachable=int(unreachable)
-            failed=int(failed)
-
-            total_unreachable += unreachable
-            total_failed += failed
-    return {'unreachable':total_unreachable,'failed':total_failed}
-	
-
-def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
-    return ''.join(random.choice(chars) for _ in range(size))
-
-def shellquote(s):
-    return "'" + s.replace("'", "'\\''") + "'"
-
-def get_playbook_fn(opts, path):
-    if not opts.get("ansible_tag", None):
-        # if no ansible_tag is in the options, then generate a unique one
-        objname= id_generator()
-        opts = opts.copy()
-        opts["ansible_tag"] = objname
-
-    objname = opts["ansible_tag"]
-
-    os.system('mkdir -p %s' % os.path.join(sys_dir, path))
-    return (opts, os.path.join(sys_dir,path,objname))
-
-def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None):
-    template = os_template_env.get_template(name)
-    buffer = template.render(opts)
-
-    (opts, fqp) = get_playbook_fn(opts, path)
-
-    f = open(fqp,'w')
-    f.write(buffer)
-    f.flush()
-
-    # This is messy -- there's no way to specify ansible config file from
-    # the command line, but we can specify it using the environment.
-    env = os.environ.copy()
-    if ansible_config:
-       env["ANSIBLE_CONFIG"] = ansible_config
-    if ansible_hosts:
-       env["ANSIBLE_HOSTS"] = ansible_hosts
-
-    if (not Config().observer_pretend):
-        if not run_ansible_script:
-            run_ansible_script = os.path.join(XOS_DIR, "observer/run_ansible")
-
-        process = subprocess.Popen("%s %s" % (run_ansible_script, shellquote(fqp)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
-        msg = process.stdout.read()
-        err_msg = process.stderr.read()
-
-        if getattr(Config(), "observer_save_ansible_output", False):
-            try:
-                open(fqp+".out","w").write(msg)
-                open(fqp+".err","w").write(err_msg)
-            except:
-                # fail silently
-                pass
-
-    else:
-        msg = open(fqp+'.out').read()
-
-    try:
-        ok_results = parse_output(msg)
-        if (expected_num is not None) and (len(ok_results) != expected_num):
-            raise ValueError('Unexpected num %s!=%d' % (str(expected_num), len(ok_results)) )
-
-        parsed = parse_unreachable(msg)
-        total_unreachable = parsed['unreachable']
-	failed = parsed['failed']
-	if (failed):
-		raise ValueError('Ansible playbook failed.')
-
-        if (total_unreachable > 0):
-            raise ValueError("Unreachable results in ansible recipe")
-    except ValueError,e:
-        all_fatal = [e.message] + re.findall(r'^msg: (.*)',msg,re.MULTILINE)
-        all_fatal2 = re.findall(r'^ERROR: (.*)',msg,re.MULTILINE)
-
-        all_fatal.extend(all_fatal2)
-        try:
-            error = ' // '.join(all_fatal)
-        except:
-            pass
-        raise Exception(error)
-
-    return ok_results
-
-def run_template_ssh(name, opts, path='', expected_num=None):
-    instance_name = opts["instance_name"]
-    hostname = opts["hostname"]
-    private_key = opts["private_key"]
-    baremetal_ssh = opts.get("baremetal_ssh",False)
-    if baremetal_ssh:
-        # no instance_id or nat_ip for baremetal
-        # we never proxy to baremetal
-        proxy_ssh = False
-    else:
-        instance_id = opts["instance_id"]
-        nat_ip = opts["nat_ip"]
-        try:
-            proxy_ssh = Config().observer_proxy_ssh
-        except:
-            proxy_ssh = True
-
-    (opts, fqp) = get_playbook_fn(opts, path)
-    private_key_pathname = fqp + ".key"
-    config_pathname = fqp + ".config"
-    hosts_pathname = fqp + ".hosts"
-
-    f = open(private_key_pathname, "w")
-    f.write(private_key)
-    f.close()
-
-    f = open(config_pathname, "w")
-    f.write("[ssh_connection]\n")
-    if proxy_ssh:
-        proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s" % (private_key_pathname, instance_id, hostname)
-        f.write('ssh_args = -o "%s"\n' % proxy_command)
-    f.write('scp_if_ssh = True\n')
-    f.write('pipelining = True\n')
-    f.write('\n[defaults]\n')
-    f.write('host_key_checking = False\n')
-    f.close()
-
-    f = open(hosts_pathname, "w")
-    f.write("[%s]\n" % instance_name)
-    if proxy_ssh or baremetal_ssh:
-        f.write("%s ansible_ssh_private_key_file=%s\n" % (hostname, private_key_pathname))
-    else:
-        # acb: Login user is hardcoded, this is not great
-        f.write("%s ansible_ssh_private_key_file=%s ansible_ssh_user=ubuntu\n" % (nat_ip, private_key_pathname))
-    f.close()
-
-    # SSH will complain if private key is world or group readable
-    os.chmod(private_key_pathname, 0600)
-
-    print "ANSIBLE_CONFIG=%s" % config_pathname
-    print "ANSIBLE_HOSTS=%s" % hosts_pathname
-
-    return run_template(name, opts, path, ansible_config = config_pathname, ansible_hosts = hosts_pathname, run_ansible_script="/opt/xos/observer/run_ansible_verbose")
-
-
-
-def main():
-    run_template('ansible/sync_user_deployments.yaml',{ "endpoint" : "http://172.31.38.128:5000/v2.0/",
-             "name" : "Sapan Bhatia",
-             "email": "gwsapan@gmail.com",
-             "password": "foobar",
-             "admin_user":"admin",
-             "admin_password":"6a789bf69dd647e2",
-             "admin_tenant":"admin",
-             "tenant":"demo",
-             "roles":['user','admin'] })
diff --git a/xos/synchronizers/openstack/ansible.py b/xos/synchronizers/openstack/ansible.py
new file mode 120000
index 0000000..c20dc8b
--- /dev/null
+++ b/xos/synchronizers/openstack/ansible.py
@@ -0,0 +1 @@
+../base/ansible.py
\ No newline at end of file