| S.Çağlar Onur | a95895d | 2015-02-09 13:34:11 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 2 | import jinja2 |
| 3 | import tempfile |
| 4 | import os |
| 5 | import json |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 6 | import pdb |
| 7 | import string |
| 8 | import random |
| Sapan Bhatia | 76fd191 | 2015-01-23 16:15:37 +0000 | [diff] [blame] | 9 | import re |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 10 | import traceback |
| 11 | import subprocess |
| Sapan Bhatia | 5155c5a | 2015-04-15 13:31:12 -0400 | [diff] [blame] | 12 | from xos.config import Config, XOS_DIR |
| Scott Baker | 0f69991 | 2015-09-17 22:17:21 -0700 | [diff] [blame] | 13 | from util.logger import observer_logger |
| Scott Baker | d9e0123 | 2015-02-04 16:59:45 -0800 | [diff] [blame] | 14 | |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 15 | try: |
| 16 | step_dir = Config().observer_steps_dir |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 17 | sys_dir = Config().observer_sys_dir |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 18 | except: |
| Scott Baker | d9e0123 | 2015-02-04 16:59:45 -0800 | [diff] [blame] | 19 | step_dir = XOS_DIR + '/observer/steps' |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 20 | sys_dir = '/opt/opencloud' |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 21 | |
| 22 | os_template_loader = jinja2.FileSystemLoader( searchpath=step_dir) |
| 23 | os_template_env = jinja2.Environment(loader=os_template_loader) |
| 24 | |
| 25 | def parse_output(msg): |
| 26 | lines = msg.splitlines() |
| 27 | results = [] |
| Scott Baker | 0f69991 | 2015-09-17 22:17:21 -0700 | [diff] [blame] | 28 | |
| 29 | observer_logger.info(msg) |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 30 | |
| 31 | for l in lines: |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 32 | magic_str = 'ok: [127.0.0.1] => ' |
| 33 | magic_str2 = 'changed: [127.0.0.1] => ' |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 34 | if (l.startswith(magic_str)): |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 35 | 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 Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 44 | |
| 45 | |
| 46 | return results |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 47 | |
| Scott Baker | 426c6f5 | 2015-05-12 11:07:18 -0700 | [diff] [blame] | 48 | def parse_unreachable(msg): |
| 49 | total_unreachable=0 |
| Sapan Bhatia | 0f6ce34 | 2015-09-16 19:14:29 +0200 | [diff] [blame] | 50 | total_failed=0 |
| Scott Baker | 426c6f5 | 2015-05-12 11:07:18 -0700 | [diff] [blame] | 51 | 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 Bhatia | 0f6ce34 | 2015-09-16 19:14:29 +0200 | [diff] [blame] | 61 | total_failed += failed |
| 62 | return {'unreachable':total_unreachable,'failed':total_failed} |
| Sapan Bhatia | 820b925 | 2015-09-10 12:59:35 -0400 | [diff] [blame] | 63 | |
| Scott Baker | 426c6f5 | 2015-05-12 11:07:18 -0700 | [diff] [blame] | 64 | |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 65 | def id_generator(size=6, chars=string.ascii_uppercase + string.digits): |
| 66 | return ''.join(random.choice(chars) for _ in range(size)) |
| 67 | |
| Sapan Bhatia | 76fd191 | 2015-01-23 16:15:37 +0000 | [diff] [blame] | 68 | def shellquote(s): |
| 69 | return "'" + s.replace("'", "'\\''") + "'" |
| 70 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 71 | def 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 | |
| 83 | def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None): |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 84 | template = os_template_env.get_template(name) |
| 85 | buffer = template.render(opts) |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 86 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 87 | (opts, fqp) = get_playbook_fn(opts, path) |
| Sapan Bhatia | c269d06 | 2015-03-13 18:43:46 -0400 | [diff] [blame] | 88 | |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 89 | f = open(fqp,'w') |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 90 | f.write(buffer) |
| 91 | f.flush() |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 92 | |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 93 | # 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 Bhatia | 7858921 | 2015-04-15 13:31:37 -0400 | [diff] [blame] | 101 | if (not Config().observer_pretend): |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 102 | if not run_ansible_script: |
| Scott Baker | c8c9720 | 2015-05-04 18:30:09 -0700 | [diff] [blame] | 103 | run_ansible_script = os.path.join(XOS_DIR, "observer/run_ansible") |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 104 | |
| Scott Baker | f77aa19 | 2015-05-12 16:20:24 -0700 | [diff] [blame] | 105 | 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 Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 108 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 109 | if getattr(Config(), "observer_save_ansible_output", False): |
| 110 | try: |
| 111 | open(fqp+".out","w").write(msg) |
| Scott Baker | f77aa19 | 2015-05-12 16:20:24 -0700 | [diff] [blame] | 112 | open(fqp+".err","w").write(err_msg) |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 113 | except: |
| 114 | # fail silently |
| 115 | pass |
| Andy Bavier | 4f66693 | 2015-09-09 15:12:27 -0400 | [diff] [blame] | 116 | |
| Sapan Bhatia | ca4939b | 2015-03-13 18:51:02 -0400 | [diff] [blame] | 117 | else: |
| 118 | msg = open(fqp+'.out').read() |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 119 | |
| Sapan Bhatia | c269d06 | 2015-03-13 18:43:46 -0400 | [diff] [blame] | 120 | try: |
| 121 | ok_results = parse_output(msg) |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 122 | 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 Baker | 426c6f5 | 2015-05-12 11:07:18 -0700 | [diff] [blame] | 124 | |
| Sapan Bhatia | 820b925 | 2015-09-10 12:59:35 -0400 | [diff] [blame] | 125 | parsed = parse_unreachable(msg) |
| 126 | total_unreachable = parsed['unreachable'] |
| 127 | failed = parsed['failed'] |
| 128 | if (failed): |
| 129 | raise ValueError('Ansible playbook failed.') |
| 130 | |
| Scott Baker | 426c6f5 | 2015-05-12 11:07:18 -0700 | [diff] [blame] | 131 | if (total_unreachable > 0): |
| 132 | raise ValueError("Unreachable results in ansible recipe") |
| Sapan Bhatia | c269d06 | 2015-03-13 18:43:46 -0400 | [diff] [blame] | 133 | except ValueError,e: |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 134 | all_fatal = [e.message] + re.findall(r'^msg: (.*)',msg,re.MULTILINE) |
| Sapan Bhatia | c269d06 | 2015-03-13 18:43:46 -0400 | [diff] [blame] | 135 | all_fatal2 = re.findall(r'^ERROR: (.*)',msg,re.MULTILINE) |
| Sapan Bhatia | ca4939b | 2015-03-13 18:51:02 -0400 | [diff] [blame] | 136 | |
| Sapan Bhatia | ca4939b | 2015-03-13 18:51:02 -0400 | [diff] [blame] | 137 | all_fatal.extend(all_fatal2) |
| Sapan Bhatia | 6255f82 | 2014-12-21 02:33:13 -0500 | [diff] [blame] | 138 | try: |
| Sapan Bhatia | c269d06 | 2015-03-13 18:43:46 -0400 | [diff] [blame] | 139 | error = ' // '.join(all_fatal) |
| 140 | except: |
| 141 | pass |
| 142 | raise Exception(error) |
| Sapan Bhatia | 6255f82 | 2014-12-21 02:33:13 -0500 | [diff] [blame] | 143 | |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 144 | return ok_results |
| 145 | |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 146 | def run_template_ssh(name, opts, path='', expected_num=None): |
| 147 | instance_id = opts["instance_id"] |
| Tony Mack | 3de59e3 | 2015-08-19 11:58:18 -0400 | [diff] [blame] | 148 | instance_name = opts["instance_name"] |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 149 | hostname = opts["hostname"] |
| 150 | private_key = opts["private_key"] |
| Andy Bavier | 4f66693 | 2015-09-09 15:12:27 -0400 | [diff] [blame] | 151 | nat_ip = opts["nat_ip"] |
| 152 | |
| 153 | try: |
| 154 | proxy_ssh = Config().observer_proxy_ssh |
| 155 | except: |
| 156 | proxy_ssh = True |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 157 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 158 | (opts, fqp) = get_playbook_fn(opts, path) |
| 159 | private_key_pathname = fqp + ".key" |
| 160 | config_pathname = fqp + ".config" |
| 161 | hosts_pathname = fqp + ".hosts" |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 162 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 163 | f = open(private_key_pathname, "w") |
| 164 | f.write(private_key) |
| 165 | f.close() |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 166 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 167 | f = open(config_pathname, "w") |
| 168 | f.write("[ssh_connection]\n") |
| Andy Bavier | 4f66693 | 2015-09-09 15:12:27 -0400 | [diff] [blame] | 169 | 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 Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 172 | f.write('scp_if_ssh = True\n') |
| Andy Bavier | 2bb9756 | 2015-06-10 14:52:49 -0400 | [diff] [blame] | 173 | f.write('pipelining = True\n') |
| Andy Bavier | 4f66693 | 2015-09-09 15:12:27 -0400 | [diff] [blame] | 174 | f.write('\n[defaults]\n') |
| 175 | f.write('host_key_checking = False\n') |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 176 | f.close() |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 177 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 178 | f = open(hosts_pathname, "w") |
| Tony Mack | 3de59e3 | 2015-08-19 11:58:18 -0400 | [diff] [blame] | 179 | f.write("[%s]\n" % instance_name) |
| Andy Bavier | 4f66693 | 2015-09-09 15:12:27 -0400 | [diff] [blame] | 180 | 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 Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 185 | f.close() |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 186 | |
| Scott Baker | 586ff54 | 2015-05-12 18:54:36 -0700 | [diff] [blame] | 187 | # SSH will complain if private key is world or group readable |
| 188 | os.chmod(private_key_pathname, 0600) |
| 189 | |
| Scott Baker | 298f652 | 2015-05-12 16:14:59 -0700 | [diff] [blame] | 190 | print "ANSIBLE_CONFIG=%s" % config_pathname |
| 191 | print "ANSIBLE_HOSTS=%s" % hosts_pathname |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 192 | |
| Sapan Bhatia | 820b925 | 2015-09-10 12:59:35 -0400 | [diff] [blame] | 193 | return run_template(name, opts, path, ansible_config = config_pathname, ansible_hosts = hosts_pathname, run_ansible_script="/opt/xos/observer/run_ansible_verbose") |
| Scott Baker | e43b557 | 2015-05-04 18:20:14 -0700 | [diff] [blame] | 194 | |
| 195 | |
| 196 | |
| Sapan Bhatia | d2c5915 | 2014-11-19 15:25:38 -0500 | [diff] [blame] | 197 | def main(): |
| Sapan | 01cf331 | 2014-12-02 23:50:37 -0500 | [diff] [blame] | 198 | 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'] }) |