Subversion Repositories DevTools

Rev

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