Subversion Repositories DevTools

Rev

Rev 5808 | Go to most recent revision | 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
 
97
        if ( TAG_START_STOP_TIMES ):
98
            last_auto_start_time = getTimeFromInstance(instance, 'LAST_AUTO_START', DEFAULT_AUTO_TIME, TODAY_HOUR_DT_FORMAT) 
99
            last_auto_stop_time = getTimeFromInstance(instance, 'LAST_AUTO_STOP', DEFAULT_AUTO_TIME, TODAY_HOUR_DT_FORMAT)
4719 marundel 100
 
101
        action_taken = "Do Nothing"
102
        try: 
103
            if ( int(start_hour_gmt) == int(stop_hour_gmt) ):
104
                action_taken = "Not modifying - Start (" + start_hour_gmt + ") and stop (" \
105
                             + stop_hour_gmt + ") are same hour."
106
            elif ( isNoActionDay(action_days, this_day_name) ):
5808 bflanaga 107
                action_taken = "Not modifying - Non actionable day (" + action_days + ") " + this_day_name
4719 marundel 108
            else:
109
                total_msg = ""
110
 
5795 marundel 111
                if ( this_hour_time <= no_boot_until_time ):
5808 bflanaga 112
                    total_msg += "No boot until " + strftime(TAG_DATE_TIME_FORMAT, no_boot_until_time)  
4719 marundel 113
                elif ( int(hh) == int(start_hour_gmt) and last_auto_start_time != this_hour_time ):
114
                    total_msg += processStart( conn, instance, state_code, this_hour_str )
5795 marundel 115
                else:
116
                    total_msg += "No boot action"
4719 marundel 117
 
5808 bflanaga 118
                total_msg += " - "
119
 
5795 marundel 120
                if ( this_hour_time <= no_kill_until_time ):
5808 bflanaga 121
                    total_msg += "No kill until " + strftime(TAG_DATE_TIME_FORMAT, no_kill_until_time)
4719 marundel 122
                elif( int(hh) == int(stop_hour_gmt) and last_auto_stop_time != this_hour_time ):
123
                    total_msg += processStop( conn, instance, state_code, this_hour_str )
5795 marundel 124
                else:
5808 bflanaga 125
                    total_msg += "No kill action"
4719 marundel 126
 
127
                if ( total_msg != "" ):
128
                    action_taken = total_msg
129
 
130
        except:
131
            action_taken = "Unknown Exception"
132
 
5808 bflanaga 133
        print this_hour_str, ':',  instance_id, state_code, start_hour_gmt, stop_hour_gmt, " --- ", action_taken, "for:", name
4719 marundel 134
 
135
 
136
 
137
def getSetTag(instance, tag, default_value):
138
    #Attempts to retrieve the the tag from the instance.
139
    #If the tag doesn't exist then it will create the tag on the instance
140
    #with the value set to the default.
141
    #@param instance - boto.ec2.instance.Instance
142
    #@param tag - string for the tag to lookup
143
    #@param default_value - default string if tag does not exist
144
    tag_missing_default = "SOME_DEFAULT_NEVER_RTNED"
145
    the_tag = instance.tags.get(tag, tag_missing_default)
146
    if ( the_tag == tag_missing_default ):
147
        #the tag didnt exist copy callers default for return
148
        #create the tag with the callers default
149
        the_tag = default_value
150
        try:
151
            instance.add_tag(tag, default_value)
152
        except:
153
            doNothing = True
154
    #else the_tag is set to the instances tag.
155
 
156
    return the_tag
157
 
158
 
159
def getTimeFromInstance(instance, tag, default_time_str, time_format):
160
    #Safely reads the tag from the instance and converts it to a time structure
161
    #in the case the tag is incorrectly formatted, or missing it will use the 
162
    #default supplied as the output date
163
    #@param instance - boto.ec2.instance.Instance
164
    #@param tag - string for the tag to lookup
165
    #@param default_time_str - string of the date to use if the tag is missing
166
    #           or incorrectly formed
167
    #@param time_format - string of the date format the tag and default_time_str 
168
    #           are expected to conform to.
169
    time_str = getSetTag(instance, tag, default_time_str)
170
    try:
171
        time_as_struct = strptime(time_str, time_format)
172
    except:
173
        time_as_struct = strptime(default_time_str, time_format)
174
 
175
    return time_as_struct
176
 
177
 
178
def isNoActionDay( action_days, this_day_name ):
179
    #Determines if this day name is present in action days string
180
    #@param action_days - the days to return true for, is a string with the 
181
    #                     first letter of the days permitted. H = Thursday, U = Sunday
182
    #@param this_day_name - the current day of the week as three letters strftime(%a, gmtime)
183
    noAction = True
184
    switch_dict = {
185
        'Mon': 'M',
186
        'Tue': 'T',
187
        'Wed': 'W',
188
        'Thu': 'H',
189
        'Fri': 'F',
190
        'Sat': 'S',
191
        'Sun': 'U'
192
    }
193
 
194
    try:
195
       noAction = switch_dict[this_day_name] not in action_days
196
    except:
197
        print 'Unknown day', this_day_name 
198
 
199
    return noAction
200
 
201
 
202
def processStart( conn, instance, state_code, this_hour_str ):
203
    #Contains logic on whether to start instance based on current state.
204
    #Additional it will actually start the instance if determined to do so.
205
    #@param instance - oto.ec2.instance.Instance
206
    #@param state_code - instance's current state
207
    #@param this_hour_str - string indicating current GMT time in TODAY_HOUR_DT_FORMAT
208
    #@returns - string indicating the action taken.
209
    action_taken = "Unknown - start"
210
 
211
    if ( state_code == STATE_RUNNING ):
212
        action_taken = "Starting - no action already running"
213
    elif ( state_code == STATE_STOPPED ):
214
        action_taken = "Starting instance"
215
        try:
216
            conn.start_instances(instance_ids=instance.id, dry_run=IS_DRY_RUN)
5809 bflanaga 217
            if ( TAG_START_STOP_TIMES ):
218
                #tag the instance so we don't potentially attempt to start it again this hour
219
                instance.add_tag('LAST_AUTO_START', this_hour_str)
4719 marundel 220
        except boto.exception.EC2ResponseError, e:
221
            print "Except while starting -", e
222
        #TODO start the instance and poss set elastip ip
223
    else:
224
        action_taken = "Starting - ERROR state_code"
225
 
226
    return action_taken
227
 
228
 
229
def processStop( conn, instance, state_code, this_hour_str ):
230
    #Contains logic on whether to stop instance based on current state.
231
    #Additional it will actually stop the instance if determined to do so.
232
    #@param instance - oto.ec2.instance.Instance
233
    #@param state_code - instance's current state
234
    #@param this_hour_str - string indicating current GMT time in TODAY_HOUR_DT_FORMAT
235
    #@returns - string indicating the action taken.
236
    action_taken = "Unknown - stop"
237
 
238
    if ( state_code in ( STATE_SHUTTING_DOWN, STATE_TERMINATED, STATE_STOPPING, STATE_STOPPED ) ):
239
        action_taken = "Stopping - no action already shutting/shutdown"
240
    elif ( state_code == STATE_RUNNING ):
241
        action_taken = "Stopping instance"
242
        try:
243
            conn.stop_instances(instance_ids=instance.id, dry_run=IS_DRY_RUN)
5809 bflanaga 244
            if ( TAG_START_STOP_TIMES ):
245
                #tag the instance so we don't potentially attempt to stop it again this hour
246
                instance.add_tag('LAST_AUTO_STOP', this_hour_str)
4719 marundel 247
        except boto.exception.EC2ResponseError, e:
248
            print "Except while stopping -", e
249
    else:
250
        action_taken = "Stopping - ERROR state_code"
251
 
252
    return action_taken
253
 
254
 
255
 
256
#Jump to main.
257
if __name__ == '__main__':
258
    main()
259