Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | import jinja2 |
| 3 | import tempfile |
| 4 | import os |
| 5 | import json |
| 6 | import pdb |
| 7 | import string |
| 8 | import random |
| 9 | import re |
| 10 | import traceback |
| 11 | import subprocess |
| 12 | from xos.config import Config, XOS_DIR |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 13 | from xos.logger import observer_logger as logger |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 14 | |
Scott Baker | c48f00f | 2016-08-16 16:45:00 -0700 | [diff] [blame] | 15 | step_dir = Config().observer_steps_dir |
| 16 | sys_dir = Config().observer_sys_dir |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 17 | |
Srikanth Vavilapalli | a399315 | 2016-11-17 03:19:00 +0000 | [diff] [blame] | 18 | os_template_loader = jinja2.FileSystemLoader( searchpath=[step_dir, "/opt/xos/synchronizers/shared_templates"]) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 19 | os_template_env = jinja2.Environment(loader=os_template_loader) |
| 20 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 21 | def id_generator(size=6, chars=string.ascii_uppercase + string.digits): |
| 22 | return ''.join(random.choice(chars) for _ in range(size)) |
| 23 | |
| 24 | def shellquote(s): |
| 25 | return "'" + s.replace("'", "'\\''") + "'" |
| 26 | |
| 27 | def get_playbook_fn(opts, path): |
| 28 | if not opts.get("ansible_tag", None): |
| 29 | # if no ansible_tag is in the options, then generate a unique one |
| 30 | objname= id_generator() |
| 31 | opts = opts.copy() |
| 32 | opts["ansible_tag"] = objname |
| 33 | |
| 34 | objname = opts["ansible_tag"] |
| 35 | |
Zack Williams | a177557 | 2016-03-07 20:30:14 -0700 | [diff] [blame] | 36 | pathed_sys_dir = os.path.join(sys_dir, path) |
| 37 | if not os.path.isdir(pathed_sys_dir): |
| 38 | os.makedirs(pathed_sys_dir) |
| 39 | |
| 40 | # symlink steps/roles into sys/roles so that playbooks can access roles |
| 41 | roledir = os.path.join(step_dir,"roles") |
| 42 | rolelink = os.path.join(pathed_sys_dir, "roles") |
| 43 | if os.path.isdir(roledir) and not os.path.islink(rolelink): |
| 44 | os.symlink(roledir,rolelink) |
| 45 | |
| 46 | return (opts, os.path.join(pathed_sys_dir,objname)) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 47 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 48 | def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None, object=None): |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 49 | template = os_template_env.get_template(name) |
| 50 | buffer = template.render(opts) |
| 51 | |
| 52 | (opts, fqp) = get_playbook_fn(opts, path) |
| 53 | |
| 54 | f = open(fqp,'w') |
| 55 | f.write(buffer) |
| 56 | f.flush() |
| 57 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 58 | if ansible_config: |
Sapan Bhatia | a89ee51 | 2017-02-05 20:42:14 -0800 | [diff] [blame] | 59 | os.environ["ANSIBLE_CONFIG"] = ansible_config |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 60 | if ansible_hosts: |
Sapan Bhatia | a89ee51 | 2017-02-05 20:42:14 -0800 | [diff] [blame] | 61 | os.environ["ANSIBLE_HOSTS"] = ansible_hosts |
| 62 | |
| 63 | # This import needs to be here, otherwise ANSIBLE_CONFIG does not take effect |
| 64 | from ansible_runner import Runner |
| 65 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 66 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 67 | # Dropped support for observer_pretend - to be redone |
| 68 | runner = Runner( |
| 69 | playbook=fqp, |
Sapan Bhatia | a89ee51 | 2017-02-05 20:42:14 -0800 | [diff] [blame] | 70 | run_data=opts, |
| 71 | host_file=ansible_hosts) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 72 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 73 | stats,aresults = runner.run() |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 74 | |
| 75 | try: |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 76 | ok_results = [] |
| 77 | total_unreachable = 0 |
| 78 | failed = 0 |
| 79 | |
| 80 | error_msg = [] |
| 81 | for x in aresults: |
| 82 | if not x.is_failed() and not x.is_unreachable() and not x.is_skipped(): |
| 83 | ok_results.append(x) |
| 84 | elif x.is_unreachable(): |
Sapan Bhatia | cb53df7 | 2017-02-06 16:13:06 -0800 | [diff] [blame] | 85 | failed+=1 |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 86 | total_unreachable+=1 |
| 87 | try: |
| 88 | error_msg.append(x._result['msg']) |
| 89 | except: |
| 90 | pass |
| 91 | elif x.is_failed(): |
| 92 | failed+=1 |
| 93 | try: |
| 94 | error_msg.append(x._result['msg']) |
| 95 | except: |
| 96 | pass |
| 97 | |
| 98 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 99 | if (expected_num is not None) and (len(ok_results) != expected_num): |
| 100 | raise ValueError('Unexpected num %s!=%d' % (str(expected_num), len(ok_results)) ) |
| 101 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 102 | #total_unreachable = stats.unreachable |
| 103 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 104 | if (failed): |
| 105 | raise ValueError('Ansible playbook failed.') |
| 106 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 107 | except ValueError,e: |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 108 | try: |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 109 | error = ' // '.join(error_msg) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 110 | except: |
| 111 | pass |
| 112 | raise Exception(error) |
| 113 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 114 | if (object): |
| 115 | oprops = object.tologdict() |
| 116 | for i in ok_results: |
| 117 | ansible = i._result |
| 118 | ansible['ansible'] = 1 |
| 119 | c = dict(oprops.items() + ansible.items()) |
| 120 | logger.info(i._task, extra=c) |
| 121 | |
| 122 | processed_results = map(lambda x:x._result, ok_results) |
| 123 | return processed_results[1:] # 0 is setup |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 124 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 125 | def run_template_ssh(name, opts, path='', expected_num=None, object=None): |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 126 | instance_name = opts["instance_name"] |
| 127 | hostname = opts["hostname"] |
| 128 | private_key = opts["private_key"] |
| 129 | baremetal_ssh = opts.get("baremetal_ssh",False) |
| 130 | if baremetal_ssh: |
Scott Baker | cb75743 | 2016-02-10 15:25:08 -0800 | [diff] [blame] | 131 | # no instance_id or ssh_ip for baremetal |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 132 | # we never proxy to baremetal |
| 133 | proxy_ssh = False |
| 134 | else: |
| 135 | instance_id = opts["instance_id"] |
Scott Baker | cb75743 | 2016-02-10 15:25:08 -0800 | [diff] [blame] | 136 | ssh_ip = opts["ssh_ip"] |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 137 | try: |
| 138 | proxy_ssh = Config().observer_proxy_ssh |
| 139 | except: |
| 140 | proxy_ssh = True |
| 141 | |
Sapan Bhatia | 90ae2b9 | 2017-02-07 17:45:09 -0800 | [diff] [blame] | 142 | if (not ssh_ip): |
| 143 | raise Exception('IP of ssh proxy not available. Synchronization deferred') |
| 144 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 145 | (opts, fqp) = get_playbook_fn(opts, path) |
| 146 | private_key_pathname = fqp + ".key" |
| 147 | config_pathname = fqp + ".config" |
| 148 | hosts_pathname = fqp + ".hosts" |
| 149 | |
| 150 | f = open(private_key_pathname, "w") |
| 151 | f.write(private_key) |
| 152 | f.close() |
| 153 | |
| 154 | f = open(config_pathname, "w") |
| 155 | f.write("[ssh_connection]\n") |
| 156 | if proxy_ssh: |
Scott Baker | 8136578 | 2016-02-10 17:25:07 -0800 | [diff] [blame] | 157 | proxy_ssh_key = getattr(Config(), "observer_proxy_ssh_key", None) |
| 158 | proxy_ssh_user = getattr(Config(), "observer_proxy_ssh_user", "root") |
| 159 | if proxy_ssh_key: |
| 160 | # If proxy_ssh_key is known, then we can proxy into the compute |
| 161 | # node without needing to have the OpenCloud sshd machinery in |
| 162 | # place. |
| 163 | proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s nc %s 22" % (proxy_ssh_key, proxy_ssh_user, hostname, ssh_ip) |
| 164 | else: |
| 165 | proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s" % (private_key_pathname, instance_id, hostname) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 166 | f.write('ssh_args = -o "%s"\n' % proxy_command) |
| 167 | f.write('scp_if_ssh = True\n') |
| 168 | f.write('pipelining = True\n') |
| 169 | f.write('\n[defaults]\n') |
| 170 | f.write('host_key_checking = False\n') |
Zack Williams | 3c5a85f | 2016-04-19 15:53:54 -0700 | [diff] [blame] | 171 | f.write('timeout = 30\n') |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 172 | f.close() |
| 173 | |
| 174 | f = open(hosts_pathname, "w") |
| 175 | f.write("[%s]\n" % instance_name) |
| 176 | if proxy_ssh or baremetal_ssh: |
| 177 | f.write("%s ansible_ssh_private_key_file=%s\n" % (hostname, private_key_pathname)) |
| 178 | else: |
| 179 | # acb: Login user is hardcoded, this is not great |
Scott Baker | cb75743 | 2016-02-10 15:25:08 -0800 | [diff] [blame] | 180 | f.write("%s ansible_ssh_private_key_file=%s ansible_ssh_user=ubuntu\n" % (ssh_ip, private_key_pathname)) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 181 | f.close() |
| 182 | |
| 183 | # SSH will complain if private key is world or group readable |
| 184 | os.chmod(private_key_pathname, 0600) |
| 185 | |
| 186 | print "ANSIBLE_CONFIG=%s" % config_pathname |
| 187 | print "ANSIBLE_HOSTS=%s" % hosts_pathname |
| 188 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 189 | return run_template(name, opts, path, ansible_config = config_pathname, ansible_hosts = hosts_pathname, run_ansible_script="/opt/xos/synchronizers/base/run_ansible_verbose", object=object) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 190 | |
| 191 | |
| 192 | |
| 193 | def main(): |
| 194 | run_template('ansible/sync_user_deployments.yaml',{ "endpoint" : "http://172.31.38.128:5000/v2.0/", |
| 195 | "name" : "Sapan Bhatia", |
| 196 | "email": "gwsapan@gmail.com", |
| 197 | "password": "foobar", |
| 198 | "admin_user":"admin", |
| 199 | "admin_password":"6a789bf69dd647e2", |
| 200 | "admin_tenant":"admin", |
| 201 | "tenant":"demo", |
| 202 | "roles":['user','admin'] }) |