Subversion Repositories DevTools

Rev

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