1# Copyright (c) 2009-2011 Reza Lotun http://reza.lotun.name/
2#
3# Permission is hereby granted, free of charge, to any person obtaining a
4# copy of this software and associated documentation files (the
5# "Software"), to deal in the Software without restriction, including
6# without limitation the rights to use, copy, modify, merge, publish, dis-
7# tribute, sublicense, and/or sell copies of the Software, and to permit
8# persons to whom the Software is furnished to do so, subject to the fol-
9# lowing conditions:
10#
11# The above copyright notice and this permission notice shall be included
12# in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
16# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
17# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20# IN THE SOFTWARE.
21
22from boto.ec2.elb.listelement import ListElement
23from boto.resultset import ResultSet
24from boto.ec2.autoscale.launchconfig import LaunchConfiguration
25from boto.ec2.autoscale.request import Request
26from boto.ec2.autoscale.instance import Instance
27from boto.ec2.autoscale.tag import Tag
28
29
30class ProcessType(object):
31    def __init__(self, connection=None):
32        self.connection = connection
33        self.process_name = None
34
35    def __repr__(self):
36        return 'ProcessType(%s)' % self.process_name
37
38    def startElement(self, name, attrs, connection):
39        pass
40
41    def endElement(self, name, value, connection):
42        if name == 'ProcessName':
43            self.process_name = value
44
45
46class SuspendedProcess(object):
47    def __init__(self, connection=None):
48        self.connection = connection
49        self.process_name = None
50        self.reason = None
51
52    def __repr__(self):
53        return 'SuspendedProcess(%s, %s)' % (self.process_name, self.reason)
54
55    def startElement(self, name, attrs, connection):
56        pass
57
58    def endElement(self, name, value, connection):
59        if name == 'ProcessName':
60            self.process_name = value
61        elif name == 'SuspensionReason':
62            self.reason = value
63
64
65class EnabledMetric(object):
66    def __init__(self, connection=None, metric=None, granularity=None):
67        self.connection = connection
68        self.metric = metric
69        self.granularity = granularity
70
71    def __repr__(self):
72        return 'EnabledMetric(%s, %s)' % (self.metric, self.granularity)
73
74    def startElement(self, name, attrs, connection):
75        pass
76
77    def endElement(self, name, value, connection):
78        if name == 'Granularity':
79            self.granularity = value
80        elif name == 'Metric':
81            self.metric = value
82
83
84class TerminationPolicies(list):
85
86    def startElement(self, name, attrs, connection):
87        pass
88
89    def endElement(self, name, value, connection):
90        if name == 'member':
91            self.append(value)
92
93
94class AutoScalingGroup(object):
95    def __init__(self, connection=None, name=None,
96                 launch_config=None, availability_zones=None,
97                 load_balancers=None, default_cooldown=None,
98                 health_check_type=None, health_check_period=None,
99                 placement_group=None, vpc_zone_identifier=None,
100                 desired_capacity=None, min_size=None, max_size=None,
101                 tags=None, termination_policies=None, instance_id=None,
102                 **kwargs):
103        """
104        Creates a new AutoScalingGroup with the specified name.
105
106        You must not have already used up your entire quota of
107        AutoScalingGroups in order for this call to be successful. Once the
108        creation request is completed, the AutoScalingGroup is ready to be
109        used in other calls.
110
111        :type name: str
112        :param name: Name of autoscaling group (required).
113
114        :type availability_zones: list
115        :param availability_zones: List of availability zones (required).
116
117        :type default_cooldown: int
118        :param default_cooldown: Number of seconds after a Scaling Activity
119            completes before any further scaling activities can start.
120
121        :type desired_capacity: int
122        :param desired_capacity: The desired capacity for the group.
123
124        :type health_check_period: str
125        :param health_check_period: Length of time in seconds after a new
126            EC2 instance comes into service that Auto Scaling starts
127            checking its health.
128
129        :type health_check_type: str
130        :param health_check_type: The service you want the health status from,
131            Amazon EC2 or Elastic Load Balancer.
132
133        :type launch_config: str or LaunchConfiguration
134        :param launch_config: Name of launch configuration (required).
135
136        :type load_balancers: list
137        :param load_balancers: List of load balancers.
138
139        :type max_size: int
140        :param max_size: Maximum size of group (required).
141
142        :type min_size: int
143        :param min_size: Minimum size of group (required).
144
145        :type placement_group: str
146        :param placement_group: Physical location of your cluster placement
147            group created in Amazon EC2.
148
149        :type vpc_zone_identifier: str or list
150        :param vpc_zone_identifier: A comma-separated string or python list of
151            the subnet identifiers of the Virtual Private Cloud.
152
153        :type tags: list
154        :param tags: List of :class:`boto.ec2.autoscale.tag.Tag`s
155
156        :type termination_policies: list
157        :param termination_policies: A list of termination policies. Valid values
158            are: "OldestInstance", "NewestInstance", "OldestLaunchConfiguration",
159            "ClosestToNextInstanceHour", "Default".  If no value is specified,
160            the "Default" value is used.
161
162        :type instance_id: str
163        :param instance_id: The ID of the Amazon EC2 instance you want to use
164            to create the Auto Scaling group.
165
166        :rtype: :class:`boto.ec2.autoscale.group.AutoScalingGroup`
167        :return: An autoscale group.
168        """
169        self.name = name or kwargs.get('group_name')   # backwards compat
170        self.connection = connection
171        self.min_size = int(min_size) if min_size is not None else None
172        self.max_size = int(max_size) if max_size is not None else None
173        self.created_time = None
174        # backwards compatibility
175        default_cooldown = default_cooldown or kwargs.get('cooldown')
176        if default_cooldown is not None:
177            default_cooldown = int(default_cooldown)
178        self.default_cooldown = default_cooldown
179        self.launch_config_name = launch_config
180        if launch_config and isinstance(launch_config, LaunchConfiguration):
181            self.launch_config_name = launch_config.name
182        self.desired_capacity = desired_capacity
183        lbs = load_balancers or []
184        self.load_balancers = ListElement(lbs)
185        zones = availability_zones or []
186        self.availability_zones = ListElement(zones)
187        self.health_check_period = health_check_period
188        self.health_check_type = health_check_type
189        self.placement_group = placement_group
190        self.autoscaling_group_arn = None
191        if type(vpc_zone_identifier) is list:
192            vpc_zone_identifier = ','.join(vpc_zone_identifier)
193        self.vpc_zone_identifier = vpc_zone_identifier
194        self.instances = None
195        self.tags = tags or None
196        termination_policies = termination_policies or []
197        self.termination_policies = ListElement(termination_policies)
198        self.instance_id = instance_id
199
200    # backwards compatible access to 'cooldown' param
201    def _get_cooldown(self):
202        return self.default_cooldown
203
204    def _set_cooldown(self, val):
205        self.default_cooldown = val
206
207    cooldown = property(_get_cooldown, _set_cooldown)
208
209    def __repr__(self):
210        return 'AutoScaleGroup<%s>' % self.name
211
212    def startElement(self, name, attrs, connection):
213        if name == 'Instances':
214            self.instances = ResultSet([('member', Instance)])
215            return self.instances
216        elif name == 'LoadBalancerNames':
217            return self.load_balancers
218        elif name == 'AvailabilityZones':
219            return self.availability_zones
220        elif name == 'EnabledMetrics':
221            self.enabled_metrics = ResultSet([('member', EnabledMetric)])
222            return self.enabled_metrics
223        elif name == 'SuspendedProcesses':
224            self.suspended_processes = ResultSet([('member', SuspendedProcess)])
225            return self.suspended_processes
226        elif name == 'Tags':
227            self.tags = ResultSet([('member', Tag)])
228            return self.tags
229        elif name == 'TerminationPolicies':
230            return self.termination_policies
231        else:
232            return
233
234    def endElement(self, name, value, connection):
235        if name == 'MinSize':
236            self.min_size = int(value)
237        elif name == 'AutoScalingGroupARN':
238            self.autoscaling_group_arn = value
239        elif name == 'CreatedTime':
240            self.created_time = value
241        elif name == 'DefaultCooldown':
242            self.default_cooldown = int(value)
243        elif name == 'LaunchConfigurationName':
244            self.launch_config_name = value
245        elif name == 'DesiredCapacity':
246            self.desired_capacity = int(value)
247        elif name == 'MaxSize':
248            self.max_size = int(value)
249        elif name == 'AutoScalingGroupName':
250            self.name = value
251        elif name == 'PlacementGroup':
252            self.placement_group = value
253        elif name == 'HealthCheckGracePeriod':
254            try:
255                self.health_check_period = int(value)
256            except ValueError:
257                self.health_check_period = None
258        elif name == 'HealthCheckType':
259            self.health_check_type = value
260        elif name == 'VPCZoneIdentifier':
261            self.vpc_zone_identifier = value
262        elif name == 'InstanceId':
263            self.instance_id = value
264        else:
265            setattr(self, name, value)
266
267    def set_capacity(self, capacity):
268        """
269        Set the desired capacity for the group.
270        """
271        params = {'AutoScalingGroupName': self.name,
272                  'DesiredCapacity': capacity}
273        req = self.connection.get_object('SetDesiredCapacity', params,
274                                         Request)
275        self.connection.last_request = req
276        return req
277
278    def update(self):
279        """
280        Sync local changes with AutoScaling group.
281        """
282        return self.connection._update_group('UpdateAutoScalingGroup', self)
283
284    def shutdown_instances(self):
285        """
286        Convenience method which shuts down all instances associated with
287        this group.
288        """
289        self.min_size = 0
290        self.max_size = 0
291        self.desired_capacity = 0
292        self.update()
293
294    def delete(self, force_delete=False):
295        """
296        Delete this auto-scaling group if no instances attached or no
297        scaling activities in progress.
298        """
299        return self.connection.delete_auto_scaling_group(self.name,
300                                                         force_delete)
301
302    def get_activities(self, activity_ids=None, max_records=50):
303        """
304        Get all activies for this group.
305        """
306        return self.connection.get_all_activities(self, activity_ids,
307                                                  max_records)
308
309    def put_notification_configuration(self, topic, notification_types):
310        """
311        Configures an Auto Scaling group to send notifications when
312        specified events take place. Valid notification types are:
313        'autoscaling:EC2_INSTANCE_LAUNCH',
314        'autoscaling:EC2_INSTANCE_LAUNCH_ERROR',
315        'autoscaling:EC2_INSTANCE_TERMINATE',
316        'autoscaling:EC2_INSTANCE_TERMINATE_ERROR',
317        'autoscaling:TEST_NOTIFICATION'
318        """
319        return self.connection.put_notification_configuration(self,
320                                                              topic,
321                                                              notification_types)
322
323    def delete_notification_configuration(self, topic):
324        """
325        Deletes notifications created by put_notification_configuration.
326        """
327        return self.connection.delete_notification_configuration(self, topic)
328
329    def suspend_processes(self, scaling_processes=None):
330        """
331        Suspends Auto Scaling processes for an Auto Scaling group.
332        """
333        return self.connection.suspend_processes(self.name, scaling_processes)
334
335    def resume_processes(self, scaling_processes=None):
336        """
337        Resumes Auto Scaling processes for an Auto Scaling group.
338        """
339        return self.connection.resume_processes(self.name, scaling_processes)
340
341
342class AutoScalingGroupMetric(object):
343    def __init__(self, connection=None):
344
345        self.connection = connection
346        self.metric = None
347        self.granularity = None
348
349    def __repr__(self):
350        return 'AutoScalingGroupMetric:%s' % self.metric
351
352    def startElement(self, name, attrs, connection):
353        return
354
355    def endElement(self, name, value, connection):
356        if name == 'Metric':
357            self.metric = value
358        elif name == 'Granularity':
359            self.granularity = value
360        else:
361            setattr(self, name, value)
362