Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 1 | #!/usr/bin/env python |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 2 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 3 | import jinja2 |
| 4 | import tempfile |
| 5 | import os |
| 6 | import json |
| 7 | import pdb |
| 8 | import string |
| 9 | import random |
| 10 | import re |
| 11 | import traceback |
| 12 | import subprocess |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 13 | import threading |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 14 | from xos.config import Config, XOS_DIR |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 15 | from xos.logger import observer_logger as logger |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 16 | from multiprocessing import Process, Queue |
| 17 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 18 | |
Scott Baker | c48f00f | 2016-08-16 16:45:00 -0700 | [diff] [blame] | 19 | step_dir = Config().observer_steps_dir |
| 20 | sys_dir = Config().observer_sys_dir |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 21 | |
Srikanth Vavilapalli | a399315 | 2016-11-17 03:19:00 +0000 | [diff] [blame] | 22 | 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] | 23 | os_template_env = jinja2.Environment(loader=os_template_loader) |
| 24 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 25 | def id_generator(size=6, chars=string.ascii_uppercase + string.digits): |
| 26 | return ''.join(random.choice(chars) for _ in range(size)) |
| 27 | |
| 28 | def shellquote(s): |
| 29 | return "'" + s.replace("'", "'\\''") + "'" |
| 30 | |
| 31 | def get_playbook_fn(opts, path): |
| 32 | if not opts.get("ansible_tag", None): |
| 33 | # if no ansible_tag is in the options, then generate a unique one |
| 34 | objname= id_generator() |
| 35 | opts = opts.copy() |
| 36 | opts["ansible_tag"] = objname |
| 37 | |
| 38 | objname = opts["ansible_tag"] |
| 39 | |
Zack Williams | a177557 | 2016-03-07 20:30:14 -0700 | [diff] [blame] | 40 | pathed_sys_dir = os.path.join(sys_dir, path) |
| 41 | if not os.path.isdir(pathed_sys_dir): |
| 42 | os.makedirs(pathed_sys_dir) |
| 43 | |
| 44 | # symlink steps/roles into sys/roles so that playbooks can access roles |
| 45 | roledir = os.path.join(step_dir,"roles") |
| 46 | rolelink = os.path.join(pathed_sys_dir, "roles") |
| 47 | if os.path.isdir(roledir) and not os.path.islink(rolelink): |
| 48 | os.symlink(roledir,rolelink) |
| 49 | |
| 50 | return (opts, os.path.join(pathed_sys_dir,objname)) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 51 | |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 52 | def run_playbook(ansible_hosts, ansible_config, fqp, opts):#, q): |
Sapan Bhatia | cedc695 | 2017-03-01 14:39:00 +0100 | [diff] [blame] | 53 | try: |
| 54 | if ansible_config: |
| 55 | os.environ["ANSIBLE_CONFIG"] = ansible_config |
| 56 | else: |
| 57 | try: |
| 58 | del os.environ["ANSIBLE_CONFIG"] |
| 59 | except KeyError: |
| 60 | pass |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 61 | |
Sapan Bhatia | cedc695 | 2017-03-01 14:39:00 +0100 | [diff] [blame] | 62 | if ansible_hosts: |
| 63 | os.environ["ANSIBLE_HOSTS"] = ansible_hosts |
| 64 | else: |
| 65 | try: |
| 66 | del os.environ["ANSIBLE_HOSTS"] |
| 67 | except KeyError: |
| 68 | pass |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 69 | |
Sapan Bhatia | cedc695 | 2017-03-01 14:39:00 +0100 | [diff] [blame] | 70 | import ansible_runner |
| 71 | reload(ansible_runner) |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 72 | |
Sapan Bhatia | cedc695 | 2017-03-01 14:39:00 +0100 | [diff] [blame] | 73 | # Dropped support for observer_pretend - to be redone |
| 74 | runner = ansible_runner.Runner( |
| 75 | playbook=fqp, |
| 76 | run_data=opts, |
| 77 | host_file=ansible_hosts) |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 78 | |
Sapan Bhatia | cedc695 | 2017-03-01 14:39:00 +0100 | [diff] [blame] | 79 | stats,aresults = runner.run() |
| 80 | except Exception, e: |
| 81 | logger.log_exc("Exception executing playbook",extra={'exception':str(e)}) |
| 82 | stats = None |
| 83 | aresults = None |
| 84 | |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 85 | #q.put([stats,aresults]) |
| 86 | return (stats,aresults) |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 87 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 88 | def run_template(name, opts, path='', expected_num=None, ansible_config=None, ansible_hosts=None, run_ansible_script=None, object=None): |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 89 | global uglylock |
| 90 | try: |
| 91 | if (uglylock): |
| 92 | pass |
| 93 | except NameError: |
| 94 | uglylock = threading.Lock() |
| 95 | |
| 96 | uglylock.acquire() |
| 97 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 98 | template = os_template_env.get_template(name) |
| 99 | buffer = template.render(opts) |
| 100 | |
| 101 | (opts, fqp) = get_playbook_fn(opts, path) |
| 102 | |
| 103 | f = open(fqp,'w') |
| 104 | f.write(buffer) |
| 105 | f.flush() |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 106 | |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 107 | """ |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 108 | q = Queue() |
| 109 | p = Process(target=run_playbook, args=(ansible_hosts, ansible_config, fqp, opts, q,)) |
| 110 | p.start() |
| 111 | stats,aresults = q.get() |
| 112 | p.join() |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 113 | """ |
| 114 | stats,aresults = run_playbook(ansible_hosts,ansible_config,fqp,opts) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 115 | |
Sapan Bhatia | abb8ee7 | 2017-03-03 07:06:35 +0100 | [diff] [blame] | 116 | uglylock.release() |
| 117 | |
Sapan Bhatia | d0275bd | 2017-02-27 21:06:34 +0100 | [diff] [blame] | 118 | output_file = fqp + '.out' |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 119 | try: |
Sapan Bhatia | cedc695 | 2017-03-01 14:39:00 +0100 | [diff] [blame] | 120 | if (aresults is None): |
| 121 | raise ValueError("Error executing playbook %s"%fqp) |
| 122 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 123 | ok_results = [] |
| 124 | total_unreachable = 0 |
| 125 | failed = 0 |
| 126 | |
| 127 | error_msg = [] |
Sapan Bhatia | d0275bd | 2017-02-27 21:06:34 +0100 | [diff] [blame] | 128 | |
| 129 | ofile = open(output_file, 'w') |
| 130 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 131 | for x in aresults: |
| 132 | if not x.is_failed() and not x.is_unreachable() and not x.is_skipped(): |
| 133 | ok_results.append(x) |
| 134 | elif x.is_unreachable(): |
Sapan Bhatia | cb53df7 | 2017-02-06 16:13:06 -0800 | [diff] [blame] | 135 | failed+=1 |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 136 | total_unreachable+=1 |
| 137 | try: |
| 138 | error_msg.append(x._result['msg']) |
| 139 | except: |
| 140 | pass |
| 141 | elif x.is_failed(): |
| 142 | failed+=1 |
| 143 | try: |
| 144 | error_msg.append(x._result['msg']) |
| 145 | except: |
| 146 | pass |
| 147 | |
Zack Williams | e48e6e7 | 2017-02-18 23:16:54 -0700 | [diff] [blame] | 148 | # FIXME (zdw, 2017-02-19) - may not be needed with new callback logging |
Sapan Bhatia | d0275bd | 2017-02-27 21:06:34 +0100 | [diff] [blame] | 149 | |
| 150 | ofile.write('%s: %s\n'%(x._task, str(x._result))) |
| 151 | |
Sapan Bhatia | 8199609 | 2017-02-14 22:25:42 -0800 | [diff] [blame] | 152 | if (object): |
| 153 | oprops = object.tologdict() |
| 154 | ansible = x._result |
Zack Williams | e48e6e7 | 2017-02-18 23:16:54 -0700 | [diff] [blame] | 155 | oprops['xos_type']='ansible' |
| 156 | oprops['ansible_result']=json.dumps(ansible) |
Sapan Bhatia | 8199609 | 2017-02-14 22:25:42 -0800 | [diff] [blame] | 157 | |
Zack Williams | e48e6e7 | 2017-02-18 23:16:54 -0700 | [diff] [blame] | 158 | if failed == 0: |
| 159 | oprops['ansible_status']='OK' |
| 160 | else: |
| 161 | oprops['ansible_status']='FAILED' |
| 162 | |
Sapan Bhatia | d0275bd | 2017-02-27 21:06:34 +0100 | [diff] [blame] | 163 | logger.info(x._task, extra=oprops) |
Sapan Bhatia | 8199609 | 2017-02-14 22:25:42 -0800 | [diff] [blame] | 164 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 165 | |
Sapan Bhatia | d0275bd | 2017-02-27 21:06:34 +0100 | [diff] [blame] | 166 | ofile.close() |
| 167 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 168 | if (expected_num is not None) and (len(ok_results) != expected_num): |
| 169 | raise ValueError('Unexpected num %s!=%d' % (str(expected_num), len(ok_results)) ) |
| 170 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 171 | #total_unreachable = stats.unreachable |
| 172 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 173 | if (failed): |
| 174 | raise ValueError('Ansible playbook failed.') |
| 175 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 176 | except ValueError,e: |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 177 | try: |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 178 | error = ' // '.join(error_msg) |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 179 | except: |
| 180 | pass |
| 181 | raise Exception(error) |
| 182 | |
Sapan Bhatia | 8199609 | 2017-02-14 22:25:42 -0800 | [diff] [blame] | 183 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 184 | |
| 185 | processed_results = map(lambda x:x._result, ok_results) |
| 186 | return processed_results[1:] # 0 is setup |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 187 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 188 | def run_template_ssh(name, opts, path='', expected_num=None, object=None): |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 189 | instance_name = opts["instance_name"] |
| 190 | hostname = opts["hostname"] |
| 191 | private_key = opts["private_key"] |
| 192 | baremetal_ssh = opts.get("baremetal_ssh",False) |
| 193 | if baremetal_ssh: |
Scott Baker | cb75743 | 2016-02-10 15:25:08 -0800 | [diff] [blame] | 194 | # no instance_id or ssh_ip for baremetal |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 195 | # we never proxy to baremetal |
| 196 | proxy_ssh = False |
| 197 | else: |
| 198 | instance_id = opts["instance_id"] |
Scott Baker | cb75743 | 2016-02-10 15:25:08 -0800 | [diff] [blame] | 199 | ssh_ip = opts["ssh_ip"] |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 200 | try: |
| 201 | proxy_ssh = Config().observer_proxy_ssh |
| 202 | except: |
| 203 | proxy_ssh = True |
| 204 | |
Sapan Bhatia | c178357 | 2017-02-23 10:39:42 +0100 | [diff] [blame] | 205 | if (not ssh_ip): |
| 206 | raise Exception('IP of ssh proxy not available. Synchronization deferred') |
Sapan Bhatia | 90ae2b9 | 2017-02-07 17:45:09 -0800 | [diff] [blame] | 207 | |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 208 | (opts, fqp) = get_playbook_fn(opts, path) |
| 209 | private_key_pathname = fqp + ".key" |
| 210 | config_pathname = fqp + ".config" |
| 211 | hosts_pathname = fqp + ".hosts" |
| 212 | |
| 213 | f = open(private_key_pathname, "w") |
| 214 | f.write(private_key) |
| 215 | f.close() |
| 216 | |
| 217 | f = open(config_pathname, "w") |
| 218 | f.write("[ssh_connection]\n") |
| 219 | if proxy_ssh: |
Scott Baker | 8136578 | 2016-02-10 17:25:07 -0800 | [diff] [blame] | 220 | proxy_ssh_key = getattr(Config(), "observer_proxy_ssh_key", None) |
| 221 | proxy_ssh_user = getattr(Config(), "observer_proxy_ssh_user", "root") |
| 222 | if proxy_ssh_key: |
| 223 | # If proxy_ssh_key is known, then we can proxy into the compute |
| 224 | # node without needing to have the OpenCloud sshd machinery in |
| 225 | # place. |
| 226 | proxy_command = "ProxyCommand ssh -q -i %s -o StrictHostKeyChecking=no %s@%s nc %s 22" % (proxy_ssh_key, proxy_ssh_user, hostname, ssh_ip) |
| 227 | else: |
| 228 | 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] | 229 | f.write('ssh_args = -o "%s"\n' % proxy_command) |
| 230 | f.write('scp_if_ssh = True\n') |
| 231 | f.write('pipelining = True\n') |
| 232 | f.write('\n[defaults]\n') |
| 233 | f.write('host_key_checking = False\n') |
Zack Williams | 3c5a85f | 2016-04-19 15:53:54 -0700 | [diff] [blame] | 234 | f.write('timeout = 30\n') |
Sapan Bhatia | fe16ae4 | 2016-01-14 11:44:43 -0500 | [diff] [blame] | 235 | f.close() |
| 236 | |
| 237 | f = open(hosts_pathname, "w") |
| 238 | f.write("[%s]\n" % instance_name) |
| 239 | if proxy_ssh or baremetal_ssh: |
| 240 | f.write("%s ansible_ssh_private_key_file=%s\n" % (hostname, private_key_pathname)) |
| 241 | else: |
| 242 | # acb: Login user is hardcoded, this is not great |
Scott Baker | cb75743 | 2016-02-10 15:25:08 -0800 | [diff] [blame] | 243 | 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] | 244 | f.close() |
| 245 | |
| 246 | # SSH will complain if private key is world or group readable |
| 247 | os.chmod(private_key_pathname, 0600) |
| 248 | |
| 249 | print "ANSIBLE_CONFIG=%s" % config_pathname |
| 250 | print "ANSIBLE_HOSTS=%s" % hosts_pathname |
| 251 | |
Sapan Bhatia | bb4b536 | 2017-02-04 09:05:32 -0800 | [diff] [blame] | 252 | 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] | 253 | |
| 254 | |
| 255 | |
| 256 | def main(): |
| 257 | run_template('ansible/sync_user_deployments.yaml',{ "endpoint" : "http://172.31.38.128:5000/v2.0/", |
| 258 | "name" : "Sapan Bhatia", |
| 259 | "email": "gwsapan@gmail.com", |
| 260 | "password": "foobar", |
| 261 | "admin_user":"admin", |
| 262 | "admin_password":"6a789bf69dd647e2", |
| 263 | "admin_tenant":"admin", |
| 264 | "tenant":"demo", |
| 265 | "roles":['user','admin'] }) |