active_instances_in_asg.py 4.37 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#!/usr/bin/env python

"""
Build an ansible inventory list suitable for use by -i by finding the active
Auto Scaling Group in an Elastic Load Balancer.  

If multiple ASGs are active in the ELB, no inventory is returned.

Assuming a single active ASG is found, a single machine is returned.  This inventory
is generally used to target a single machine in a cluster to run a cmomand.

Typical reponse:

10.2.42.79,

Typical use

ansible -i $(active_instances_in_asg.py --asg stage-edx-edxapp) -m shell -a 'management command'

"""

22
from __future__ import print_function
23 24 25
import argparse
import botocore.session
import botocore.exceptions
26
import sys
27 28
from collections import defaultdict
from os import environ
29
from itertools import chain
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

class ActiveInventory():

    profile = None

    def __init__(self, profile, region):
        self.profile = profile
        self.region  = region

    def run(self,asg_name):
        session = botocore.session.Session(profile=self.profile)
        asg = session.create_client('autoscaling',self.region)
        ec2 = session.create_client('ec2',self.region)

        groups = asg.describe_auto_scaling_groups()
        matching_groups = [g for g in groups['AutoScalingGroups'] for t in g['Tags'] if t['Key'] == 'Name' and t['Value'] == asg_name]

        groups_to_instances = {group['AutoScalingGroupName']: [instance['InstanceId'] for instance in group['Instances']] for group in matching_groups}
        instances_to_groups = {instance['InstanceId']: group['AutoScalingGroupName'] for group in matching_groups for instance in group['Instances'] }

        # We only need to check for ASGs in an ELB if we have more than 1.
        # If a cluster is running with an ASG out of the ELB, then there are larger problems.
        active_groups = defaultdict(dict)
        if len(matching_groups) > 1:
            elb = session.create_client('elb',self.region)
            for group in matching_groups:
                for load_balancer_name in group['LoadBalancerNames']:
                    instances = elb.describe_instance_health(LoadBalancerName=load_balancer_name)
                    active_instances = [instance['InstanceId'] for instance in instances['InstanceStates'] if instance['State'] == 'InService']
                    for instance_id in active_instances:
                        active_groups[instances_to_groups[instance_id]] = 1 
61

62 63 64
            # If we found no active groups, because there are no ELBs (edxapp workers normally)
            elbs = list(chain.from_iterable([group['LoadBalancerNames'] for group in matching_groups]))
            if not (active_groups or elbs):
65 66 67 68 69 70 71 72 73 74
                # This implies we're in a worker cluster since we have no ELB and we didn't find an active group above
                for group in matching_groups:
                    # Asgard marks a deleting ASG with SuspendedProcesses
                    # If the ASG doesn't have those, then it's "Active" and a worker since there was no ELB above
                    if not {'Launch','AddToLoadBalancer'} <= {i['ProcessName'] for i in group['SuspendedProcesses']}:
                        active_groups[group['AutoScalingGroupName']] = 1

            if len(active_groups) > 1:
                # When we have more than a single active ASG, we need to bail out as we don't know what ASG to pick an instance from
                print("Multiple active ASGs - unable to choose an instance", file=sys.stderr)
75
                return
76 77 78 79 80 81 82 83
        else:
            active_groups = { g['AutoScalingGroupName']: 1 for g in matching_groups }


        for group in active_groups.keys():
            for group_instance in groups_to_instances[group]:
                instance = ec2.describe_instances(InstanceIds=[group_instance])['Reservations'][0]['Instances'][0]
                if 'PrivateIpAddress' in instance:
84
                    print("{},".format(instance['PrivateIpAddress']))
85 86 87 88 89 90 91 92 93 94 95 96 97 98
                    return # We only want a single IP


if __name__=="__main__":

    parser = argparse.ArgumentParser()
    parser.add_argument('-p', '--profile', help='The aws profile to use when connecting.')
    parser.add_argument('-l', '--list', help='Ansible passes this, we ignore it.', action='store_true', default=True)
    parser.add_argument('--asg',help='Name of the ASG we want active instances from.', required=True)
    args = parser.parse_args()

    region = environ.get('AWS_REGION','us-east-1')

    ActiveInventory(args.profile,region).run(args.asg)