Subversion Repositories DevTools

Rev

Rev 5809 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
4719 marundel 1
#!/usr/bin/python
2
#
3
# Auto-start and stop EC2 instances
4
#
5
import boto.ec2, datetime, sys
6
from time import gmtime, strftime, strptime, sleep
7
 
5808 bflanaga 8
#Default start stop times (7am / 7pm AWST)
9
DEFAULT_START_GMT = "23"
10
DEFAULT_STOP_GMT = "11"
4719 marundel 11
 
12
#debug setting for dry-run
13
#IS_DRY_RUN = True
14
IS_DRY_RUN = False
15
 
5809 bflanaga 16
#tagging start and stop times is used to prevent starting / stopping instances 
17
#multiple times an hour, but it can be turned off to reduce number of tags used 
18
#by script if you don't think this is an issue (eg script only runs once an hour)
19
#TAG_START_STOP_TIMES = False
20
TAG_START_STOP_TIMES = True
21
 
4719 marundel 22
###################################################################################
23
# Shoudn't need to modify anything below here
24
###################################################################################
25
 
26
#State Code Constants
27
#http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instance-status.html
28
STATE_PENDING = 0
29
STATE_RUNNING = 16
30
STATE_SHUTTING_DOWN = 32
31
STATE_TERMINATED = 48
32
STATE_STOPPING = 64
33
STATE_STOPPED = 80
34
 
35
# This is special flag to never kill this instance. Should really only be used
36
# on your scripting server instance.
37
TAG_DATE_TIME_FORMAT = "%Y-%m-%d"
5808 bflanaga 38
DEFAULT_NO_BOOT_TIME = "2000-01-01"
39
DEFAULT_NO_KILL_TIME = "2000-01-01"
4719 marundel 40
 
41
TODAY_HOUR_DT_FORMAT = '%Y-%m-%d %H'
42
DEFAULT_AUTO_TIME = '2000-01-01 00'
43
 
44
TODAY_WEEKDAY_NAME = '%a'
5808 bflanaga 45
DEFAULT_WEEKDAY_ACTION = 'MTWHFSU'
4719 marundel 46
 
47
def main():
48
    gmtime_now = gmtime()
49
 
50
    #Test cases
51
    #Monday
52
    #gmtime_now = strptime("2014/11/24 10:00:00", "%Y/%m/%d %H:%M:%S")
53
    #Wednesday
54
    #gmtime_now = strptime("2014/11/26 10:00:00", "%Y/%m/%d %H:%M:%S")
55
    #Thursday
56
    #gmtime_now = strptime("2014/11/27 10:00:00", "%Y/%m/%d %H:%M:%S")
57
    #Friday
58
    #gmtime_now = strptime("2014/11/28 10:00:00", "%Y/%m/%d %H:%M:%S")
59
    #Saturday
60
    #gmtime_now = strptime("2014/11/29 10:00:00", "%Y/%m/%d %H:%M:%S")
61
    #Sunday
62
    #gmtime_now = strptime("2014/11/30 10:00:00", "%Y/%m/%d %H:%M:%S")
63
 
64
    startStopInstancesForTime(gmtime_now)
65
 
66
def startStopInstancesForTime(gmtime_now):
67
    #Starts or stops instances based on the supplied GMT time.
68
    #@param gmtime_now - time struct of the current time in GMT
69
 
70
    #AWS credentials
71
    region = boto.config.get('sleep_tight', 'region', 'ap-southeast-2')
72
    aws_key = boto.config.get('sleep_tight', 'aws_access_key_id', '0')
73
    aws_secret = boto.config.get('sleep_tight', 'aws_secret_access_key', '0')
74
 
75
    # Connect to EC2
76
    conn = boto.ec2.connect_to_region(region, aws_access_key_id=aws_key, aws_secret_access_key=aws_secret)
77
 
78
    # Get current hour
79
    hh = strftime("%H", gmtime_now)
80
    # Get Today 
81
    this_hour_str = strftime(TODAY_HOUR_DT_FORMAT, gmtime_now)
82
    this_hour_time = strptime(this_hour_str, TODAY_HOUR_DT_FORMAT)
83
    this_day_name = strftime(TODAY_WEEKDAY_NAME, gmtime_now)
84
 
85
    instances = conn.get_only_instances()
86
    for( instance ) in instances:
87
        instance_id = instance.id
88
        name = instance.tags.get("Name", instance_id)
89
        state_code = instance.state_code
90
        start_hour_gmt = getSetTag(instance, 'START_HOUR_GMT', DEFAULT_START_GMT)
91
        stop_hour_gmt = getSetTag(instance, 'STOP_HOUR_GMT', DEFAULT_STOP_GMT)
5795 marundel 92
        action_days = getSetTag(instance, 'ACTION_DAYS', DEFAULT_WEEKDAY_ACTION)
4719 marundel 93
 
94
        no_boot_until_time = getTimeFromInstance(instance, 'NO_BOOT_UNTIL', DEFAULT_NO_BOOT_TIME, TAG_DATE_TIME_FORMAT)
95
        no_kill_until_time = getTimeFromInstance(instance, 'NO_KILL_UNTIL', DEFAULT_NO_KILL_TIME, TAG_DATE_TIME_FORMAT)
5809 bflanaga 96
 
5821 bflanaga 97
        last_auto_start_time = 99;
98
        last_auto_stop_time = 99;
99
 
5809 bflanaga 100
        if ( TAG_START_STOP_TIMES ):
101
            last_auto_start_time = getTimeFromInstance(instance, 'LAST_AUTO_START', DEFAULT_AUTO_TIME, TODAY_HOUR_DT_FORMAT) 
102
            last_auto_stop_time = getTimeFromInstance(instance, 'LAST_AUTO_STOP', DEFAULT_AUTO_TIME, TODAY_HOUR_DT_FORMAT)
4719 marundel 103
 
104
        action_taken = "Do Nothing"
105
        try: 
106
            if ( int(start_hour_gmt) == int(stop_hour_gmt) ):
107
                action_taken = "Not modifying - Start (" + start_hour_gmt + ") and stop (" \
108
                             + stop_hour_gmt + ") are same hour."
109
            elif ( isNoActionDay(action_days, this_day_name) ):
5808 bflanaga 110
                action_taken = "Not modifying - Non actionable day (" + action_days + ") " + this_day_name
4719 marundel 111
            else:
112
                total_msg = ""
113
 
5795 marundel 114
                if ( this_hour_time <= no_boot_until_time ):
5808 bflanaga 115
                    total_msg += "No boot until " + strftime(TAG_DATE_TIME_FORMAT, no_boot_until_time)  
4719 marundel 116
                elif ( int(hh) == int(start_hour_gmt) and last_auto_start_time != this_hour_time ):
117
                    total_msg += processStart( conn, instance, state_code, this_hour_str )
5795 marundel 118
                else:
119
                    total_msg += "No boot action"
4719 marundel 120
 
5808 bflanaga 121
                total_msg += " - "
122
 
5795 marundel 123
                if ( this_hour_time <= no_kill_until_time ):
5808 bflanaga 124
                    total_msg += "No kill until " + strftime(TAG_DATE_TIME_FORMAT, no_kill_until_time)
4719 marundel 125
                elif( int(hh) == int(stop_hour_gmt) and last_auto_stop_time != this_hour_time ):
126
                    total_msg += processStop( conn, instance, state_code, this_hour_str )
5795 marundel 127
                else:
5808 bflanaga 128
                    total_msg += "No kill action"
4719 marundel 129
 
130
                if ( total_msg != "" ):
131
                    action_taken = total_msg
132
 
133
        except:
134
            action_taken = "Unknown Exception"
135
 
5808 bflanaga 136
        print this_hour_str, ':',  instance_id, state_code, start_hour_gmt, stop_hour_gmt, " --- ", action_taken, "for:", name
4719 marundel 137
 
138
 
139
 
140
def getSetTag(instance, tag, default_value):
141
    #Attempts to retrieve the the tag from the instance.
142
    #If the tag doesn't exist then it will create the tag on the instance
143
    #with the value set to the default.
144
    #@param instance - boto.ec2.instance.Instance
145
    #@param tag - string for the tag to lookup
146
    #@param default_value - default string if tag does not exist
147
    tag_missing_default = "SOME_DEFAULT_NEVER_RTNED"
148
    the_tag = instance.tags.get(tag, tag_missing_default)
149
    if ( the_tag == tag_missing_default ):
150
        #the tag didnt exist copy callers default for return
151
        #create the tag with the callers default
152
        the_tag = default_value
153
        try:
154
            instance.add_tag(tag, default_value)
155
        except:
156
            doNothing = True
157
    #else the_tag is set to the instances tag.
158
 
159
    return the_tag
160
 
161
 
162
def getTimeFromInstance(instance, tag, default_time_str, time_format):
163
    #Safely reads the tag from the instance and converts it to a time structure
164
    #in the case the tag is incorrectly formatted, or missing it will use the 
165
    #default supplied as the output date
166
    #@param instance - boto.ec2.instance.Instance
167
    #@param tag - string for the tag to lookup
168
    #@param default_time_str - string of the date to use if the tag is missing
169
    #           or incorrectly formed
170
    #@param time_format - string of the date format the tag and default_time_str 
171
    #           are expected to conform to.
172
    time_str = getSetTag(instance, tag, default_time_str)
173
    try:
174
        time_as_struct = strptime(time_str, time_format)
175
    except:
176
        time_as_struct = strptime(default_time_str, time_format)
177
 
178
    return time_as_struct
179
 
180
 
181
def isNoActionDay( action_days, this_day_name ):
182
    #Determines if this day name is present in action days string
183
    #@param action_days - the days to return true for, is a string with the 
184
    #                     first letter of the days permitted. H = Thursday, U = Sunday
185
    #@param this_day_name - the current day of the week as three letters strftime(%a, gmtime)
186
    noAction = True
187
    switch_dict = {
188
        'Mon': 'M',
189
        'Tue': 'T',
190
        'Wed': 'W',
191
        'Thu': 'H',
192
        'Fri': 'F',
193
        'Sat': 'S',
194
        'Sun': 'U'
195
    }
196
 
197
    try:
198
       noAction = switch_dict[this_day_name] not in action_days
199
    except:
200
        print 'Unknown day', this_day_name 
201
 
202
    return noAction
203
 
204
 
205
def processStart( conn, instance, state_code, this_hour_str ):
206
    #Contains logic on whether to start instance based on current state.
207
    #Additional it will actually start the instance if determined to do so.
208
    #@param instance - oto.ec2.instance.Instance
209
    #@param state_code - instance's current state
210
    #@param this_hour_str - string indicating current GMT time in TODAY_HOUR_DT_FORMAT
211
    #@returns - string indicating the action taken.
212
    action_taken = "Unknown - start"
213
 
214
    if ( state_code == STATE_RUNNING ):
215
        action_taken = "Starting - no action already running"
216
    elif ( state_code == STATE_STOPPED ):
217
        action_taken = "Starting instance"
218
        try:
219
            conn.start_instances(instance_ids=instance.id, dry_run=IS_DRY_RUN)
5809 bflanaga 220
            if ( TAG_START_STOP_TIMES ):
221
                #tag the instance so we don't potentially attempt to start it again this hour
222
                instance.add_tag('LAST_AUTO_START', this_hour_str)
4719 marundel 223
        except boto.exception.EC2ResponseError, e:
224
            print "Except while starting -", e
225
        #TODO start the instance and poss set elastip ip
226
    else:
227
        action_taken = "Starting - ERROR state_code"
228
 
229
    return action_taken
230
 
231
 
232
def processStop( conn, instance, state_code, this_hour_str ):
233
    #Contains logic on whether to stop instance based on current state.
234
    #Additional it will actually stop the instance if determined to do so.
235
    #@param instance - oto.ec2.instance.Instance
236
    #@param state_code - instance's current state
237
    #@param this_hour_str - string indicating current GMT time in TODAY_HOUR_DT_FORMAT
238
    #@returns - string indicating the action taken.
239
    action_taken = "Unknown - stop"
240
 
241
    if ( state_code in ( STATE_SHUTTING_DOWN, STATE_TERMINATED, STATE_STOPPING, STATE_STOPPED ) ):
242
        action_taken = "Stopping - no action already shutting/shutdown"
243
    elif ( state_code == STATE_RUNNING ):
244
        action_taken = "Stopping instance"
245
        try:
246
            conn.stop_instances(instance_ids=instance.id, dry_run=IS_DRY_RUN)
5809 bflanaga 247
            if ( TAG_START_STOP_TIMES ):
248
                #tag the instance so we don't potentially attempt to stop it again this hour
249
                instance.add_tag('LAST_AUTO_STOP', this_hour_str)
4719 marundel 250
        except boto.exception.EC2ResponseError, e:
251
            print "Except while stopping -", e
252
    else:
253
        action_taken = "Stopping - ERROR state_code"
254
 
255
    return action_taken
256
 
257
 
258
 
259
#Jump to main.
260
if __name__ == '__main__':
261
    main()
262