blob: b6f1ca20b5a447ba35e37eb0bed2b1d3fda9ec64 [file] [log] [blame]
Sapan Bhatia037c9472016-01-14 11:44:43 -05001#!/usr/bin/env python
2import jinja2
3import tempfile
4import os
5import json
6import pdb
7import string
8import random
9import re
10import traceback
11import subprocess
12from xos.config import Config, XOS_DIR
Scott Baker3586c552016-01-14 15:30:20 -080013from xos.logger import observer_logger
Sapan Bhatia037c9472016-01-14 11:44:43 -050014
15try:
16 step_dir = Config().observer_steps_dir
17 sys_dir = Config().observer_sys_dir
18except:
Sapan Bhatiaf4441882016-01-18 09:19:42 -050019 step_dir = XOS_DIR + '/synchronizers/openstack/steps'
Sapan Bhatia037c9472016-01-14 11:44:43 -050020 sys_dir = '/opt/opencloud'
21
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 = []
28
29 observer_logger.info(msg)
30
31 for l in lines:
32 magic_str = 'ok: [127.0.0.1] => '
33 magic_str2 = 'changed: [127.0.0.1] => '
34 if (l.startswith(magic_str)):
35 w = len(magic_str)
36 str = l[w:]
Scott Bakerb0143462016-02-10 14:31:24 -080037
38 # handle ok: [127.0.0.1] => (item=org.onosproject.driver) => {...
39 if str.startswith("(") and (" => {" in str):
40 str = str.split("=> ",1)[1]
41
Sapan Bhatia037c9472016-01-14 11:44:43 -050042 d = json.loads(str)
43 results.append(d)
44 elif (l.startswith(magic_str2)):
45 w = len(magic_str2)
46 str = l[w:]
Scott Bakerb0143462016-02-10 14:31:24 -080047
48 if str.startswith("(") and (" => {" in str):
49 str = str.split("=> ",1)[1]
50
Sapan Bhatia037c9472016-01-14 11:44:43 -050051 d = json.loads(str)
52 results.append(d)
53
54
55 return results
56
57def parse_unreachable(msg):
58 total_unreachable=0
59 total_failed=0
60 for l in msg.splitlines():
61 x = re.findall('ok=([0-9]+).*changed=([0-9]+).*unreachable=([0-9]+).*failed=([0-9]+)', l)
62 if x:
63 (ok, changed, unreachable, failed) = x[0]
64 ok=int(ok)
65 changed=int(changed)
66 unreachable=int(unreachable)
67 failed=int(failed)
68
69 total_unreachable += unreachable
70 total_failed += failed
71 return {'unreachable':total_unreachable,'failed':total_failed}
Zack Williamsfcaa9b22016-03-07 20:30:14 -070072
Sapan Bhatia037c9472016-01-14 11:44:43 -050073
74def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
75 return ''.join(random.choice(chars) for _ in range(size))
76
77def shellquote(s):
78 return "'" + s.replace("'", "'\\''") + "'"
79
80def get_playbook_fn(opts, path):
81 if not opts.get("ansible_tag", None):
82 # if no ansible_tag is in the options, then generate a unique one
83 objname= id_generator()
84 opts = opts.copy()
85 opts["ansible_tag"] = objname
86
87 objname = opts["ansible_tag"]
88
Zack Williamsfcaa9b22016-03-07 20:30:14 -070089 pathed_sys_dir = os.path.join(sys_dir, path)
90 if not os.path.isdir(pathed_sys_dir):
91 os.makedirs(pathed_sys_dir)
92
93 # symlink steps/roles into sys/roles so that playbooks can access roles
94 roledir = os.path.join(step_dir,"roles")
95 rolelink = os.path.join(pathed_sys_dir, "roles")
96 if os.path.isdir(roledir) and not os.path.islink(rolelink):
97 os.symlink(roledir,rolelink)
98
99 return (opts, os.path.join(pathed_sys_dir,objname))
Sapan Bhatia037c9472016-01-14 11:44:43 -0500100
101def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None):
102 template = os_template_env.get_template(name)
103 buffer = template.render(opts)
104
105 (opts, fqp) = get_playbook_fn(opts, path)
106
107 f = open(fqp,'w')
108 f.write(buffer)
109 f.flush()
110
111 # This is messy -- there's no way to specify ansible config file from
112 # the command line, but we can specify it using the environment.
113 env = os.environ.copy()
114 if ansible_config:
115 env["ANSIBLE_CONFIG"] = ansible_config
116 if ansible_hosts:
117 env["ANSIBLE_HOSTS"] = ansible_hosts
118
119 if (not Config().observer_pretend):
120 if not run_ansible_script:
Scott Baker698626c2016-01-20 15:57:30 -0800121 run_ansible_script = os.path.join(XOS_DIR, "synchronizers/base/run_ansible")
Sapan Bhatia037c9472016-01-14 11:44:43 -0500122
123 process = subprocess.Popen("%s %s" % (run_ansible_script, shellquote(fqp)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
124 msg = process.stdout.read()
125 err_msg = process.stderr.read()
126
127 if getattr(Config(), "observer_save_ansible_output", False):
128 try:
129 open(fqp+".out","w").write(msg)
130 open(fqp+".err","w").write(err_msg)
131 except:
132 # fail silently
133 pass
134
135 else:
136 msg = open(fqp+'.out').read()
137
138 try:
139 ok_results = parse_output(msg)
140 if (expected_num is not None) and (len(ok_results) != expected_num):
141 raise ValueError('Unexpected num %s!=%d' % (str(expected_num), len(ok_results)) )
142
143 parsed = parse_unreachable(msg)
144 total_unreachable = parsed['unreachable']
145 failed = parsed['failed']
146 if (failed):
147 raise ValueError('Ansible playbook failed.')
148
149 if (total_unreachable > 0):
150 raise ValueError("Unreachable results in ansible recipe")
151 except ValueError,e:
152 all_fatal = [e.message] + re.findall(r'^msg: (.*)',msg,re.MULTILINE)
153 all_fatal2 = re.findall(r'^ERROR: (.*)',msg,re.MULTILINE)
154
155 all_fatal.extend(all_fatal2)
156 try:
157 error = ' // '.join(all_fatal)
158 except:
159 pass
160 raise Exception(error)
161
162 return ok_results
163
164def run_template_ssh(name, opts, path='', expected_num=None):
165 instance_name = opts["instance_name"]
166 hostname = opts["hostname"]
167 private_key = opts["private_key"]
168 baremetal_ssh = opts.get("baremetal_ssh",False)
169 if baremetal_ssh:
Scott Bakerac785432016-02-10 15:25:08 -0800170 # no instance_id or ssh_ip for baremetal
Sapan Bhatia037c9472016-01-14 11:44:43 -0500171 # we never proxy to baremetal
172 proxy_ssh = False
173 else:
174 instance_id = opts["instance_id"]
Scott Bakerac785432016-02-10 15:25:08 -0800175 ssh_ip = opts["ssh_ip"]
Sapan Bhatia037c9472016-01-14 11:44:43 -0500176 try:
177 proxy_ssh = Config().observer_proxy_ssh
178 except:
179 proxy_ssh = True
180
181 (opts, fqp) = get_playbook_fn(opts, path)
182 private_key_pathname = fqp + ".key"
183 config_pathname = fqp + ".config"
184 hosts_pathname = fqp + ".hosts"
185
186 f = open(private_key_pathname, "w")
187 f.write(private_key)
188 f.close()
189
190 f = open(config_pathname, "w")
191 f.write("[ssh_connection]\n")
192 if proxy_ssh:
Scott Bakerdce38de2016-02-10 17:25:07 -0800193 proxy_ssh_key = getattr(Config(), "observer_proxy_ssh_key", None)
194 proxy_ssh_user = getattr(Config(), "observer_proxy_ssh_user", "root")
195 if proxy_ssh_key:
196 # If proxy_ssh_key is known, then we can proxy into the compute
197 # node without needing to have the OpenCloud sshd machinery in
198 # place.
199 proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s nc %s 22" % (proxy_ssh_key, proxy_ssh_user, hostname, ssh_ip)
200 else:
201 proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s" % (private_key_pathname, instance_id, hostname)
Sapan Bhatia037c9472016-01-14 11:44:43 -0500202 f.write('ssh_args = -o "%s"\n' % proxy_command)
203 f.write('scp_if_ssh = True\n')
204 f.write('pipelining = True\n')
205 f.write('\n[defaults]\n')
206 f.write('host_key_checking = False\n')
207 f.close()
208
209 f = open(hosts_pathname, "w")
210 f.write("[%s]\n" % instance_name)
211 if proxy_ssh or baremetal_ssh:
212 f.write("%s ansible_ssh_private_key_file=%s\n" % (hostname, private_key_pathname))
213 else:
214 # acb: Login user is hardcoded, this is not great
Scott Bakerac785432016-02-10 15:25:08 -0800215 f.write("%s ansible_ssh_private_key_file=%s ansible_ssh_user=ubuntu\n" % (ssh_ip, private_key_pathname))
Sapan Bhatia037c9472016-01-14 11:44:43 -0500216 f.close()
217
218 # SSH will complain if private key is world or group readable
219 os.chmod(private_key_pathname, 0600)
220
221 print "ANSIBLE_CONFIG=%s" % config_pathname
222 print "ANSIBLE_HOSTS=%s" % hosts_pathname
223
Srikanth Vavilapalli562ba492016-01-25 20:06:43 -0500224 return run_template(name, opts, path, ansible_config = config_pathname, ansible_hosts = hosts_pathname, run_ansible_script="/opt/xos/synchronizers/base/run_ansible_verbose")
Sapan Bhatia037c9472016-01-14 11:44:43 -0500225
226
227
228def main():
229 run_template('ansible/sync_user_deployments.yaml',{ "endpoint" : "http://172.31.38.128:5000/v2.0/",
230 "name" : "Sapan Bhatia",
231 "email": "gwsapan@gmail.com",
232 "password": "foobar",
233 "admin_user":"admin",
234 "admin_password":"6a789bf69dd647e2",
235 "admin_tenant":"admin",
236 "tenant":"demo",
237 "roles":['user','admin'] })