blob: 0b550b44960e674f599c9ee69edc99baa6d67f11 [file] [log] [blame]
#!/usr/bin/env python
import site
from os import path
import time
from fabric.api import env
from fabric.context_managers import settings
from Crypto.Random import atfork
site.addsitedir(
path.join(path.dirname(path.realpath(__file__)), "../../lib/python"))
import util.fabric.actions
def print_status(remaining, failed_masters):
if remaining:
print "=" * 30, "Remaining masters", "=" * 30
for m in remaining:
print m
if failed_masters:
print "=" * 30, "failed masters", "=" * 30
for m in failed_masters:
print m
print "=" * 80
def run_action_on_master(action, master):
atfork()
try:
action_func = getattr(util.fabric.actions, "action_%s" % action)
with settings(host_string=master.get('ip_address', master['hostname'])):
action_func(master)
return True
except AttributeError:
print "[%s] %s action is not defined." % (master['hostname'], action)
return False
except:
import traceback
print "Failed to run", action, "on", master['name']
print traceback.format_exc()
return False
if __name__ == '__main__':
import sys
import urllib
from optparse import OptionParser
import textwrap
try:
import simplejson as json
except ImportError:
import json
actions = []
action_module = util.fabric.actions
for a in action_module.get_actions():
actions.append(a)
actions.sort()
actions_with_help = '\n'.join([" %s\n %s" % (a,
action_module.__dict__['action_%s' % a].__doc__)
for a in actions if
action_module.__dict__['action_%s' % a].__doc__])
action_without_help = ", ".join([a for a in actions if not
action_module.__dict__['action_%s' %
a].__doc__])
action_help = actions_with_help
if action_without_help:
action_help += "\nOther supported actions:\n" + \
textwrap.TextWrapper(initial_indent=' ', subsequent_indent=' ').fill(action_without_help)
parser = OptionParser("""%%prog [options] action [action ...]
Supported actions:
%s""" % action_help)
parser.set_defaults(
hosts=[],
roles=[],
datacentre=[],
concurrency=1,
show_list=False,
all_masters=False,
ignored_roles=[],
)
parser.add_option("-f", "--master-file", dest="master_file",
help="list/url of masters")
parser.add_option("-H", "--host", dest="hosts", action="append")
parser.add_option("-R", "--role", dest="roles", action="append")
parser.add_option("-M", "--match", dest="match", action="append",
help="masters that match the term")
parser.add_option("-D", "--datacentre", dest="datacentre", action="append")
parser.add_option("-j", dest="concurrency", type="int")
parser.add_option("-l", dest="show_list", action="store_true",
help="list hosts")
parser.add_option("--all", dest="all_masters", action="store_true",
help="work on all masters, not just enabled ones")
parser.add_option("-i", dest="status_interval", type="int", default="60",
help="Interval between statuses")
parser.add_option("-u", "--username", dest="username", default="cltbld",
help="Username passed to Fabric")
parser.add_option("-k", "--ssh-key", dest="ssh_key",
help="SSH key passed to Fabric")
parser.add_option("--ignore-role", dest="ignored_roles", action="append",
help="Ignore masters with this role. May be passed multiple times.")
options, actions = parser.parse_args()
if options.concurrency > 1:
import multiprocessing
if not options.master_file:
parser.error("master-file is required")
if not actions and not options.show_list:
parser.error("at least one action is required")
# Load master data
all_masters = json.load(urllib.urlopen(options.master_file))
masters = []
for m in all_masters:
if not m['enabled'] and not options.all_masters:
continue
if options.ignored_roles and m['role'] in options.ignored_roles:
continue
if options.datacentre and m['datacentre'] not in options.datacentre:
continue
if m['name'] in options.hosts:
masters.append(m)
elif m['role'] in options.roles:
masters.append(m)
elif options.match:
for match in options.match:
if match in m["name"]:
masters.append(m)
elif 'all' in options.hosts or 'all' in options.roles:
masters.append(m)
if options.show_list:
if len(masters) == 0:
masters = [m for m in all_masters if m['enabled'] or
options.all_masters]
fmt = "%(role)-9s %(name)-14s %(hostname)s:%(basedir)s"
print fmt % dict(role='role', name='name', hostname='hostname',
basedir='basedir')
for m in masters:
print fmt % m
sys.exit(0)
if len(masters) == 0:
parser.error("No masters matched, check your options -H, -R, -M, -D")
env.user = options.username
if options.ssh_key:
env.key_filename = options.ssh_key
selected_masters = masters
for action in actions:
if hasattr(action, 'per_host'):
hosts = set(m['hostname'] for m in masters)
masters = [dict(hostname=h) for h in hosts]
else:
masters = selected_masters
try:
action_header = getattr(action_module, action + "_header")
action_header()
except AttributeError:
# we don't care if there's no header to print
pass
if options.concurrency == 1:
for master in masters:
run_action_on_master(action, master)
else:
# Don't prompt for passwords when forking
env.abort_on_prompts = True
p = multiprocessing.Pool(processes=options.concurrency)
results = []
for master in masters:
result = p.apply_async(run_action_on_master, (action, master))
results.append((master, result))
p.close()
failed = False
failed_masters = []
tries = 0
while True:
for master, result in list(results):
if result.ready():
results.remove((master, result))
if not result.get():
failed_masters.append(master['name'])
print master['name'], "FAILED"
failed = True
tries += 1
if not results:
break
if tries % options.status_interval == 0:
print_status([m['name'] for (m, r) in results],
failed_masters)
time.sleep(1)
p.join()
# One final print before we exit, to be sure that results are not
# missed
print_status([m['name'] for (m, r) in results],
failed_masters)
if failed:
# failure info may have scrolled, so repeat here
print "Action '%s' failed on %d masters:\n%s\n" \
% (action, len(failed_masters),
textwrap.TextWrapper(initial_indent=' ',
subsequent_indent=' ').fill(", ".join(
failed_masters)))
sys.exit(1)