Quellcode durchsuchen

Merge pull request #445 from mdr-engineering/hotfix/ftd_na_BetterTestScheduling

Stops Auto-starting Instances, Script to start instances manually
Frederick Damstra vor 3 Jahren
Ursprung
Commit
65c77b212d
2 geänderte Dateien mit 197 neuen und 2 gelöschten Zeilen
  1. 5 2
      bin/aws_scheduler_configure.sh
  2. 192 0
      bin/xdrtest

+ 5 - 2
bin/aws_scheduler_configure.sh

@@ -5,21 +5,24 @@ REGION=$2
 
 echo
 echo \*\*\* Step 1 of 2: Creating periods and schedules in case they don\'t already exist. Ignore errors.
+scheduler-cli create-period --name "stop20" --endtime 20:00 --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli create-period --name "weekdays" --begintime 01:30 --endtime 20:00 --weekdays mon-fri --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli create-period --name "business-hours" --begintime 01:30 --endtime 20:00 --weekdays mon-fri --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli create-period --name "extended" --begintime 08:00 --endtime 23:59 --weekdays mon-fri --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli create-period --name "saturday" --begintime 12:00 --endtime 18:00 --weekdays sat  --stack $STACK --region $REGION --profile-name $PROFILE
-scheduler-cli create-schedule --name MSOC --periods business-hours --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
+scheduler-cli create-schedule --name MSOC --periods stop20 --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
+#scheduler-cli create-schedule --name MSOC --periods business-hours --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
 #scheduler-cli create-schedule --enforced=true --name non-prod --periods weekdays --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
 #scheduler-cli create-schedule --enforced=true --name non-prod-extended --periods extended --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
 
 echo
 echo \*\*\* Step 2 of 2:  Updating periods and schedules to our standards
+scheduler-cli update-period --name "stop20" --endtime 20:00 --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli update-period --name "weekdays" --begintime 01:30 --endtime 20:00 --weekdays mon-fri --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli update-period --name "business-hours" --begintime 01:30 --endtime 20:00 --weekdays mon-fri --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli update-period --name "extended" --begintime 08:00 --endtime 23:59 --weekdays mon-fri --stack $STACK --region $REGION --profile-name $PROFILE
 scheduler-cli update-period --name "saturday" --begintime 12:00 --endtime 18:00 --weekdays sat  --stack $STACK --region $REGION --profile-name $PROFILE
-scheduler-cli update-schedule --name MSOC --periods business-hours --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
+scheduler-cli update-schedule --name MSOC --periods stop20 --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
 #scheduler-cli update-schedule --enforced=true --name non-prod --periods weekdays --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
 #scheduler-cli update-schedule --enforced=true --name non-prod-extended --periods extended --timezone "US/Eastern" --stack $STACK --region $REGION --profile-name $PROFILE
 

+ 192 - 0
bin/xdrtest

@@ -0,0 +1,192 @@
+#!/usr/bin/env python3
+#
+# Startup/Shutdown Groups of our systems
+#
+# Common usage:
+# 
+# View the current state of the things on a schedule:
+#   xdrtest status
+# 
+# At the start of your shift:
+#   xdrtest start --profile mdr-test-c2-gov
+#
+# At the end of your shift:
+#   xdrtest stop
+#
+import argparse
+import boto3
+import json
+import logging
+import os
+import re
+import sys
+
+DEBUG=False # Turn on with --debug
+
+logger = logging.getLogger()
+
+def query_yes_no(question, default="yes"):
+    """Ask a yes/no question via input() and return their answer.
+
+    "question" is a string that is presented to the user.
+    "default" is the presumed answer if the user just hits <Enter>.
+            It must be "yes" (the default), "no" or None (meaning
+            an answer is required of the user).
+
+    The "answer" return value is True for "yes" or False for "no".
+    """
+    valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False}
+    if default is None:
+        prompt = " [y/n] "
+    elif default == "yes":
+        prompt = " [Y/n] "
+    elif default == "no":
+        prompt = " [y/N] "
+    else:
+        raise ValueError("invalid default answer: '%s'" % default)
+
+    while True:
+        sys.stdout.write(question + prompt)
+        choice = input().lower()
+        if default is not None and choice == "":
+            return valid[default]
+        elif choice in valid:
+            return valid[choice]
+        else:
+            sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n")
+
+
+def gather_profiles(file='~/.aws/config', commercial=False):
+    file=os.path.expanduser(file)
+    profiles=list()
+    try:
+        with open(os.path.expanduser(file), "r") as configfile:
+            for line in configfile:
+                match = re.search('^\[profile (.*)\]', line)
+                if match:
+                    if 'test' in match.group(1):
+                        if 'gov' in match.group(1) or commercial:
+                            profiles.append(match.group(1))
+    except Exception as e:
+        logger.error(f'Could not open file {file} for reading: {e}')
+        sys.exit(1)
+    return profiles
+
+
+def gather_instances(ec2, tagname, tagvalue, exclude_state):
+    ''' 
+    Gathers the instance information from the provided ec2 resource
+    '''
+    desired_instances = ec2.instances.filter(
+            Filters=[
+                { 
+                  'Name': f'tag:{tagname}',
+                  'Values': [ f'{tagvalue}' ]
+                }
+              ]
+    )
+    for i in desired_instances:
+        name=""
+        for t in i.tags:
+            if t['Key']=='Name':
+                name = t['Value']
+        logger.debug(f'DEBUG: Discovered instance {i.instance_id}, Name: {name}, State: {i.state["Name"]}')
+        if i.state['Name'] != exclude_state:
+            # Why an iterator? Why not?
+            yield {
+                    'instance_id': i.instance_id,
+                    'name': name,
+                    'state': i.state['Name']
+                  }
+
+
+if __name__ == "__main__":
+    handler = logging.StreamHandler()
+    logger.addHandler(handler)
+
+    # Turn off debugging for AWS
+    for module in [ 'boto3', 'botocore', 'nose', 's3transfer', 'urllib3', 'urllib3.connectionpool' ]:
+            l = logging.getLogger(module)
+            l.setLevel(logging.WARNING) # bump this up if you think you have a boto3 bug
+
+    parser = argparse.ArgumentParser(description='Startup/Shutdown Groups of Instances')
+    parser.add_argument('action', help='Action to Take on Instances', choices=['stop', 'start', 'status'])
+    parser.add_argument('--config', help='AWS Config File', default='~/.aws/config')
+    parser.add_argument('--profile', help='Profile to Use', default=None)
+    parser.add_argument('--tagname', help='Tag Name to Filter On', default='Schedule')
+    parser.add_argument('tagvalue', help='Tag Value to Filter On', nargs='?', default='MSOC')
+    parser.add_argument('--commercial', help='Include Commercial Accounts', action='store_true')
+    parser.add_argument('--dry-run', help='Dry Run', action='store_true')
+    parser.add_argument('--debug', help='Output Debug Information', action='store_true')
+
+    args = parser.parse_args()
+    if args.debug:
+        DEBUG=True
+        logger.setLevel(logging.DEBUG) # Debug
+    else:
+        logger.setLevel(logging.INFO) # Info
+
+    if args.profile is not None:
+        profiles=[ args.profile ]
+    else:
+        profiles=gather_profiles(file=args.config, commercial=args.commercial)
+
+    logger.debug(f'Profiles to gather: {json.dumps(profiles)}')
+
+    # Connect to our profiles:
+    session = dict()
+    ec2 = dict()
+    instances = dict()
+    instance_count = 0
+    print(f'Instances to Start:')
+    for p in profiles:
+        logger.debug(f'Gathering from profile {p}...')
+        try:
+            session[p] = boto3.session.Session(profile_name=p)
+            ec2[p] = session[p].resource('ec2')
+        except Exception as e:
+            logger.error(f'Could not connect to profile "{p}": {e}')
+
+        exclude_state = ""
+        if args.action=="start":
+            exclude_state="running"
+        elif args.action=="stop":
+            exclude_state="stopped"
+       
+        instances[p] = list()
+        for instance in gather_instances(ec2[p], args.tagname, args.tagvalue, exclude_state):
+            print(f'{p:<30}\t{ instance["instance_id"] }\t{instance["state"]:<10}\t{instance["name"]}')
+            instances[p].append(instance['instance_id'])
+            instance_count += 1
+
+    if instance_count == 0:
+        print('')
+        print('No applicable instances discovered.')
+        sys.exit(0)
+
+    logger.debug(f'Num instances: {len(instances)}: {json.dumps(instances)}')
+    if args.action=="status":
+        logger.debug('Exiting after status check')
+        sys.exit(0)
+
+    print('')
+    if query_yes_no(question='Continue?', default='no') is False:
+        logger.debug('Exiting on user request')
+        sys.exit(2)
+
+    for p in profiles:
+        if len(instances[p]) > 0:
+            for i in ec2[p].instances.filter(InstanceIds=instances[p]):
+                if args.action == 'start':
+                    try:
+                        i.start(DryRun=args.dry_run)
+                    except Exception as e:
+                        print(f'An error occured while starting instance {i.id}. Error: {e}. Skipping.')
+                elif args.action == 'stop':
+                    try:
+                        i.stop(DryRun=args.dry_run)
+                    except Exception as e:
+                        print(f'An error occured while stopping instance {i.id}. Error: {e}. Skipping.')
+                else:
+                    print(f'Unsupported action: {args.action}')
+                    sys.exit(3)