blob: ee110c91fa2b3846db62f67976240d42c5ad1f8a [file] [log] [blame]
S.Çağlar Onura95895d2015-02-09 13:34:11 -05001#!/usr/bin/env python
Sapan Bhatiad2c59152014-11-19 15:25:38 -05002import jinja2
3import tempfile
4import os
5import json
Sapan01cf3312014-12-02 23:50:37 -05006import pdb
7import string
8import random
Sapan Bhatia76fd1912015-01-23 16:15:37 +00009import re
Scott Bakere43b5572015-05-04 18:20:14 -070010import traceback
11import subprocess
Sapan Bhatia5155c5a2015-04-15 13:31:12 -040012from xos.config import Config, XOS_DIR
Scott Baker0f699912015-09-17 22:17:21 -070013from util.logger import observer_logger
Scott Bakerd9e01232015-02-04 16:59:45 -080014
Sapan Bhatiad2c59152014-11-19 15:25:38 -050015try:
16 step_dir = Config().observer_steps_dir
Sapan01cf3312014-12-02 23:50:37 -050017 sys_dir = Config().observer_sys_dir
Sapan Bhatiad2c59152014-11-19 15:25:38 -050018except:
Scott Bakerd9e01232015-02-04 16:59:45 -080019 step_dir = XOS_DIR + '/observer/steps'
Sapan01cf3312014-12-02 23:50:37 -050020 sys_dir = '/opt/opencloud'
Sapan Bhatiad2c59152014-11-19 15:25:38 -050021
22os_template_loader = jinja2.FileSystemLoader( searchpath=step_dir)
23os_template_env = jinja2.Environment(loader=os_template_loader)
24
25def parse_output(msg):
26 lines = msg.splitlines()
27 results = []
Scott Baker0f699912015-09-17 22:17:21 -070028
29 observer_logger.info(msg)
Sapan Bhatiad2c59152014-11-19 15:25:38 -050030
31 for l in lines:
Sapan01cf3312014-12-02 23:50:37 -050032 magic_str = 'ok: [127.0.0.1] => '
33 magic_str2 = 'changed: [127.0.0.1] => '
Sapan Bhatiad2c59152014-11-19 15:25:38 -050034 if (l.startswith(magic_str)):
Sapan01cf3312014-12-02 23:50:37 -050035 w = len(magic_str)
36 str = l[w:]
37 d = json.loads(str)
38 results.append(d)
39 elif (l.startswith(magic_str2)):
40 w = len(magic_str2)
41 str = l[w:]
42 d = json.loads(str)
43 results.append(d)
Sapan Bhatiad2c59152014-11-19 15:25:38 -050044
45
46 return results
Sapan01cf3312014-12-02 23:50:37 -050047
Scott Baker426c6f52015-05-12 11:07:18 -070048def parse_unreachable(msg):
49 total_unreachable=0
Sapan Bhatia0f6ce342015-09-16 19:14:29 +020050 total_failed=0
Scott Baker426c6f52015-05-12 11:07:18 -070051 for l in msg.splitlines():
52 x = re.findall('ok=([0-9]+).*changed=([0-9]+).*unreachable=([0-9]+).*failed=([0-9]+)', l)
53 if x:
54 (ok, changed, unreachable, failed) = x[0]
55 ok=int(ok)
56 changed=int(changed)
57 unreachable=int(unreachable)
58 failed=int(failed)
59
60 total_unreachable += unreachable
Sapan Bhatia0f6ce342015-09-16 19:14:29 +020061 total_failed += failed
62 return {'unreachable':total_unreachable,'failed':total_failed}
Sapan Bhatia820b9252015-09-10 12:59:35 -040063
Scott Baker426c6f52015-05-12 11:07:18 -070064
Sapan01cf3312014-12-02 23:50:37 -050065def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
66 return ''.join(random.choice(chars) for _ in range(size))
67
Sapan Bhatia76fd1912015-01-23 16:15:37 +000068def shellquote(s):
69 return "'" + s.replace("'", "'\\''") + "'"
70
Scott Baker298f6522015-05-12 16:14:59 -070071def get_playbook_fn(opts, path):
72 if not opts.get("ansible_tag", None):
73 # if no ansible_tag is in the options, then generate a unique one
74 objname= id_generator()
75 opts = opts.copy()
76 opts["ansible_tag"] = objname
77
78 objname = opts["ansible_tag"]
79
80 os.system('mkdir -p %s' % os.path.join(sys_dir, path))
81 return (opts, os.path.join(sys_dir,path,objname))
82
83def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None):
Sapan Bhatiad2c59152014-11-19 15:25:38 -050084 template = os_template_env.get_template(name)
85 buffer = template.render(opts)
Sapan01cf3312014-12-02 23:50:37 -050086
Scott Baker298f6522015-05-12 16:14:59 -070087 (opts, fqp) = get_playbook_fn(opts, path)
Sapan Bhatiac269d062015-03-13 18:43:46 -040088
Sapan01cf3312014-12-02 23:50:37 -050089 f = open(fqp,'w')
Sapan Bhatiad2c59152014-11-19 15:25:38 -050090 f.write(buffer)
91 f.flush()
Sapan01cf3312014-12-02 23:50:37 -050092
Scott Bakere43b5572015-05-04 18:20:14 -070093 # This is messy -- there's no way to specify ansible config file from
94 # the command line, but we can specify it using the environment.
95 env = os.environ.copy()
96 if ansible_config:
97 env["ANSIBLE_CONFIG"] = ansible_config
98 if ansible_hosts:
99 env["ANSIBLE_HOSTS"] = ansible_hosts
100
Sapan Bhatia78589212015-04-15 13:31:37 -0400101 if (not Config().observer_pretend):
Scott Bakere43b5572015-05-04 18:20:14 -0700102 if not run_ansible_script:
Scott Bakerc8c97202015-05-04 18:30:09 -0700103 run_ansible_script = os.path.join(XOS_DIR, "observer/run_ansible")
Scott Bakere43b5572015-05-04 18:20:14 -0700104
Scott Bakerf77aa192015-05-12 16:20:24 -0700105 process = subprocess.Popen("%s %s" % (run_ansible_script, shellquote(fqp)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
106 msg = process.stdout.read()
107 err_msg = process.stderr.read()
Sapan Bhatiad2c59152014-11-19 15:25:38 -0500108
Scott Baker298f6522015-05-12 16:14:59 -0700109 if getattr(Config(), "observer_save_ansible_output", False):
110 try:
111 open(fqp+".out","w").write(msg)
Scott Bakerf77aa192015-05-12 16:20:24 -0700112 open(fqp+".err","w").write(err_msg)
Scott Baker298f6522015-05-12 16:14:59 -0700113 except:
114 # fail silently
115 pass
Andy Bavier4f666932015-09-09 15:12:27 -0400116
Sapan Bhatiaca4939b2015-03-13 18:51:02 -0400117 else:
118 msg = open(fqp+'.out').read()
Scott Bakere43b5572015-05-04 18:20:14 -0700119
Sapan Bhatiac269d062015-03-13 18:43:46 -0400120 try:
121 ok_results = parse_output(msg)
Scott Bakere43b5572015-05-04 18:20:14 -0700122 if (expected_num is not None) and (len(ok_results) != expected_num):
123 raise ValueError('Unexpected num %s!=%d' % (str(expected_num), len(ok_results)) )
Scott Baker426c6f52015-05-12 11:07:18 -0700124
Sapan Bhatia820b9252015-09-10 12:59:35 -0400125 parsed = parse_unreachable(msg)
126 total_unreachable = parsed['unreachable']
127 failed = parsed['failed']
128 if (failed):
129 raise ValueError('Ansible playbook failed.')
130
Scott Baker426c6f52015-05-12 11:07:18 -0700131 if (total_unreachable > 0):
132 raise ValueError("Unreachable results in ansible recipe")
Sapan Bhatiac269d062015-03-13 18:43:46 -0400133 except ValueError,e:
Scott Bakere43b5572015-05-04 18:20:14 -0700134 all_fatal = [e.message] + re.findall(r'^msg: (.*)',msg,re.MULTILINE)
Sapan Bhatiac269d062015-03-13 18:43:46 -0400135 all_fatal2 = re.findall(r'^ERROR: (.*)',msg,re.MULTILINE)
Sapan Bhatiaca4939b2015-03-13 18:51:02 -0400136
Sapan Bhatiaca4939b2015-03-13 18:51:02 -0400137 all_fatal.extend(all_fatal2)
Sapan Bhatia6255f822014-12-21 02:33:13 -0500138 try:
Sapan Bhatiac269d062015-03-13 18:43:46 -0400139 error = ' // '.join(all_fatal)
140 except:
141 pass
142 raise Exception(error)
Sapan Bhatia6255f822014-12-21 02:33:13 -0500143
Sapan Bhatiad2c59152014-11-19 15:25:38 -0500144 return ok_results
145
Scott Bakere43b5572015-05-04 18:20:14 -0700146def run_template_ssh(name, opts, path='', expected_num=None):
147 instance_id = opts["instance_id"]
Tony Mack3de59e32015-08-19 11:58:18 -0400148 instance_name = opts["instance_name"]
Scott Bakere43b5572015-05-04 18:20:14 -0700149 hostname = opts["hostname"]
150 private_key = opts["private_key"]
Andy Bavier4f666932015-09-09 15:12:27 -0400151 nat_ip = opts["nat_ip"]
152
153 try:
154 proxy_ssh = Config().observer_proxy_ssh
155 except:
156 proxy_ssh = True
Scott Bakere43b5572015-05-04 18:20:14 -0700157
Scott Baker298f6522015-05-12 16:14:59 -0700158 (opts, fqp) = get_playbook_fn(opts, path)
159 private_key_pathname = fqp + ".key"
160 config_pathname = fqp + ".config"
161 hosts_pathname = fqp + ".hosts"
Scott Bakere43b5572015-05-04 18:20:14 -0700162
Scott Baker298f6522015-05-12 16:14:59 -0700163 f = open(private_key_pathname, "w")
164 f.write(private_key)
165 f.close()
Scott Bakere43b5572015-05-04 18:20:14 -0700166
Scott Baker298f6522015-05-12 16:14:59 -0700167 f = open(config_pathname, "w")
168 f.write("[ssh_connection]\n")
Andy Bavier4f666932015-09-09 15:12:27 -0400169 if proxy_ssh:
170 proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s" % (private_key_pathname, instance_id, hostname)
171 f.write('ssh_args = -o "%s"\n' % proxy_command)
Scott Baker298f6522015-05-12 16:14:59 -0700172 f.write('scp_if_ssh = True\n')
Andy Bavier2bb97562015-06-10 14:52:49 -0400173 f.write('pipelining = True\n')
Andy Bavier4f666932015-09-09 15:12:27 -0400174 f.write('\n[defaults]\n')
175 f.write('host_key_checking = False\n')
Scott Baker298f6522015-05-12 16:14:59 -0700176 f.close()
Scott Bakere43b5572015-05-04 18:20:14 -0700177
Scott Baker298f6522015-05-12 16:14:59 -0700178 f = open(hosts_pathname, "w")
Tony Mack3de59e32015-08-19 11:58:18 -0400179 f.write("[%s]\n" % instance_name)
Andy Bavier4f666932015-09-09 15:12:27 -0400180 if proxy_ssh:
181 f.write("%s ansible_ssh_private_key_file=%s\n" % (hostname, private_key_pathname))
182 else:
183 # acb: Login user is hardcoded, this is not great
184 f.write("%s ansible_ssh_private_key_file=%s ansible_ssh_user=ubuntu\n" % (nat_ip, private_key_pathname))
Scott Baker298f6522015-05-12 16:14:59 -0700185 f.close()
Scott Bakere43b5572015-05-04 18:20:14 -0700186
Scott Baker586ff542015-05-12 18:54:36 -0700187 # SSH will complain if private key is world or group readable
188 os.chmod(private_key_pathname, 0600)
189
Scott Baker298f6522015-05-12 16:14:59 -0700190 print "ANSIBLE_CONFIG=%s" % config_pathname
191 print "ANSIBLE_HOSTS=%s" % hosts_pathname
Scott Bakere43b5572015-05-04 18:20:14 -0700192
Sapan Bhatia820b9252015-09-10 12:59:35 -0400193 return run_template(name, opts, path, ansible_config = config_pathname, ansible_hosts = hosts_pathname, run_ansible_script="/opt/xos/observer/run_ansible_verbose")
Scott Bakere43b5572015-05-04 18:20:14 -0700194
195
196
Sapan Bhatiad2c59152014-11-19 15:25:38 -0500197def main():
Sapan01cf3312014-12-02 23:50:37 -0500198 run_template('ansible/sync_user_deployments.yaml',{ "endpoint" : "http://172.31.38.128:5000/v2.0/",
199 "name" : "Sapan Bhatia",
200 "email": "gwsapan@gmail.com",
201 "password": "foobar",
202 "admin_user":"admin",
203 "admin_password":"6a789bf69dd647e2",
204 "admin_tenant":"admin",
205 "tenant":"demo",
206 "roles":['user','admin'] })