Subversion Repositories DevTools

Rev

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