#!/usr/bin/python # # Auto-start and stop EC2 instances # import boto.ec2, datetime, sys from time import gmtime, strftime, strptime, sleep #Default start stop times (7am / 7pm AWST) DEFAULT_START_GMT = "23" DEFAULT_STOP_GMT = "11" #debug setting for dry-run #IS_DRY_RUN = True IS_DRY_RUN = False #tagging start and stop times is used to prevent starting / stopping instances #multiple times an hour, but it can be turned off to reduce number of tags used #by script if you don't think this is an issue (eg script only runs once an hour) #TAG_START_STOP_TIMES = False TAG_START_STOP_TIMES = True ################################################################################### # Shoudn't need to modify anything below here ################################################################################### #State Code Constants #http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instance-status.html STATE_PENDING = 0 STATE_RUNNING = 16 STATE_SHUTTING_DOWN = 32 STATE_TERMINATED = 48 STATE_STOPPING = 64 STATE_STOPPED = 80 # This is special flag to never kill this instance. Should really only be used # on your scripting server instance. TAG_DATE_TIME_FORMAT = "%Y-%m-%d" DEFAULT_NO_BOOT_TIME = "2000-01-01" DEFAULT_NO_KILL_TIME = "2000-01-01" TODAY_HOUR_DT_FORMAT = '%Y-%m-%d %H' DEFAULT_AUTO_TIME = '2000-01-01 00' TODAY_WEEKDAY_NAME = '%a' DEFAULT_WEEKDAY_ACTION = 'MTWHFSU' def main(): gmtime_now = gmtime() #Test cases #Monday #gmtime_now = strptime("2014/11/24 10:00:00", "%Y/%m/%d %H:%M:%S") #Wednesday #gmtime_now = strptime("2014/11/26 10:00:00", "%Y/%m/%d %H:%M:%S") #Thursday #gmtime_now = strptime("2014/11/27 10:00:00", "%Y/%m/%d %H:%M:%S") #Friday #gmtime_now = strptime("2014/11/28 10:00:00", "%Y/%m/%d %H:%M:%S") #Saturday #gmtime_now = strptime("2014/11/29 10:00:00", "%Y/%m/%d %H:%M:%S") #Sunday #gmtime_now = strptime("2014/11/30 10:00:00", "%Y/%m/%d %H:%M:%S") startStopInstancesForTime(gmtime_now) def startStopInstancesForTime(gmtime_now): #Starts or stops instances based on the supplied GMT time. #@param gmtime_now - time struct of the current time in GMT #AWS credentials region = boto.config.get('sleep_tight', 'region', 'ap-southeast-2') aws_key = boto.config.get('sleep_tight', 'aws_access_key_id', '0') aws_secret = boto.config.get('sleep_tight', 'aws_secret_access_key', '0') # Connect to EC2 conn = boto.ec2.connect_to_region(region, aws_access_key_id=aws_key, aws_secret_access_key=aws_secret) # Get current hour hh = strftime("%H", gmtime_now) # Get Today this_hour_str = strftime(TODAY_HOUR_DT_FORMAT, gmtime_now) this_hour_time = strptime(this_hour_str, TODAY_HOUR_DT_FORMAT) this_day_name = strftime(TODAY_WEEKDAY_NAME, gmtime_now) instances = conn.get_only_instances() for( instance ) in instances: instance_id = instance.id name = instance.tags.get("Name", instance_id) state_code = instance.state_code start_hour_gmt = getSetTag(instance, 'START_HOUR_GMT', DEFAULT_START_GMT) stop_hour_gmt = getSetTag(instance, 'STOP_HOUR_GMT', DEFAULT_STOP_GMT) action_days = getSetTag(instance, 'ACTION_DAYS', DEFAULT_WEEKDAY_ACTION) no_boot_until_time = getTimeFromInstance(instance, 'NO_BOOT_UNTIL', DEFAULT_NO_BOOT_TIME, TAG_DATE_TIME_FORMAT) no_kill_until_time = getTimeFromInstance(instance, 'NO_KILL_UNTIL', DEFAULT_NO_KILL_TIME, TAG_DATE_TIME_FORMAT) last_auto_start_time = 99; last_auto_stop_time = 99; if ( TAG_START_STOP_TIMES ): last_auto_start_time = getTimeFromInstance(instance, 'LAST_AUTO_START', DEFAULT_AUTO_TIME, TODAY_HOUR_DT_FORMAT) last_auto_stop_time = getTimeFromInstance(instance, 'LAST_AUTO_STOP', DEFAULT_AUTO_TIME, TODAY_HOUR_DT_FORMAT) action_taken = "Do Nothing" try: if ( int(start_hour_gmt) == int(stop_hour_gmt) ): action_taken = "Not modifying - Start (" + start_hour_gmt + ") and stop (" \ + stop_hour_gmt + ") are same hour." elif ( isNoActionDay(action_days, this_day_name) ): action_taken = "Not modifying - Non actionable day (" + action_days + ") " + this_day_name else: total_msg = "" if ( this_hour_time <= no_boot_until_time ): total_msg += "No boot until " + strftime(TAG_DATE_TIME_FORMAT, no_boot_until_time) elif ( int(hh) == int(start_hour_gmt) and last_auto_start_time != this_hour_time ): total_msg += processStart( conn, instance, state_code, this_hour_str ) else: total_msg += "No boot action" total_msg += " - " if ( this_hour_time <= no_kill_until_time ): total_msg += "No kill until " + strftime(TAG_DATE_TIME_FORMAT, no_kill_until_time) elif( int(hh) == int(stop_hour_gmt) and last_auto_stop_time != this_hour_time ): total_msg += processStop( conn, instance, state_code, this_hour_str ) else: total_msg += "No kill action" if ( total_msg != "" ): action_taken = total_msg except: action_taken = "Unknown Exception" print this_hour_str, ':', instance_id, state_code, start_hour_gmt, stop_hour_gmt, " --- ", action_taken, "for:", name def getSetTag(instance, tag, default_value): #Attempts to retrieve the the tag from the instance. #If the tag doesn't exist then it will create the tag on the instance #with the value set to the default. #@param instance - boto.ec2.instance.Instance #@param tag - string for the tag to lookup #@param default_value - default string if tag does not exist tag_missing_default = "SOME_DEFAULT_NEVER_RTNED" the_tag = instance.tags.get(tag, tag_missing_default) if ( the_tag == tag_missing_default ): #the tag didnt exist copy callers default for return #create the tag with the callers default the_tag = default_value try: instance.add_tag(tag, default_value) except: doNothing = True #else the_tag is set to the instances tag. return the_tag def getTimeFromInstance(instance, tag, default_time_str, time_format): #Safely reads the tag from the instance and converts it to a time structure #in the case the tag is incorrectly formatted, or missing it will use the #default supplied as the output date #@param instance - boto.ec2.instance.Instance #@param tag - string for the tag to lookup #@param default_time_str - string of the date to use if the tag is missing # or incorrectly formed #@param time_format - string of the date format the tag and default_time_str # are expected to conform to. time_str = getSetTag(instance, tag, default_time_str) try: time_as_struct = strptime(time_str, time_format) except: time_as_struct = strptime(default_time_str, time_format) return time_as_struct def isNoActionDay( action_days, this_day_name ): #Determines if this day name is present in action days string #@param action_days - the days to return true for, is a string with the # first letter of the days permitted. H = Thursday, U = Sunday #@param this_day_name - the current day of the week as three letters strftime(%a, gmtime) noAction = True switch_dict = { 'Mon': 'M', 'Tue': 'T', 'Wed': 'W', 'Thu': 'H', 'Fri': 'F', 'Sat': 'S', 'Sun': 'U' } try: noAction = switch_dict[this_day_name] not in action_days except: print 'Unknown day', this_day_name return noAction def processStart( conn, instance, state_code, this_hour_str ): #Contains logic on whether to start instance based on current state. #Additional it will actually start the instance if determined to do so. #@param instance - oto.ec2.instance.Instance #@param state_code - instance's current state #@param this_hour_str - string indicating current GMT time in TODAY_HOUR_DT_FORMAT #@returns - string indicating the action taken. action_taken = "Unknown - start" if ( state_code == STATE_RUNNING ): action_taken = "Starting - no action already running" elif ( state_code == STATE_STOPPED ): action_taken = "Starting instance" try: conn.start_instances(instance_ids=instance.id, dry_run=IS_DRY_RUN) if ( TAG_START_STOP_TIMES ): #tag the instance so we don't potentially attempt to start it again this hour instance.add_tag('LAST_AUTO_START', this_hour_str) except boto.exception.EC2ResponseError, e: print "Except while starting -", e #TODO start the instance and poss set elastip ip else: action_taken = "Starting - ERROR state_code" return action_taken def processStop( conn, instance, state_code, this_hour_str ): #Contains logic on whether to stop instance based on current state. #Additional it will actually stop the instance if determined to do so. #@param instance - oto.ec2.instance.Instance #@param state_code - instance's current state #@param this_hour_str - string indicating current GMT time in TODAY_HOUR_DT_FORMAT #@returns - string indicating the action taken. action_taken = "Unknown - stop" if ( state_code in ( STATE_SHUTTING_DOWN, STATE_TERMINATED, STATE_STOPPING, STATE_STOPPED ) ): action_taken = "Stopping - no action already shutting/shutdown" elif ( state_code == STATE_RUNNING ): action_taken = "Stopping instance" try: conn.stop_instances(instance_ids=instance.id, dry_run=IS_DRY_RUN) if ( TAG_START_STOP_TIMES ): #tag the instance so we don't potentially attempt to stop it again this hour instance.add_tag('LAST_AUTO_STOP', this_hour_str) except boto.exception.EC2ResponseError, e: print "Except while stopping -", e else: action_taken = "Stopping - ERROR state_code" return action_taken #Jump to main. if __name__ == '__main__': main()