1
1
# -*- coding: utf-8 -*-
2
- from copy import deepcopy
3
2
import random
3
+ from collections import defaultdict
4
+ from copy import deepcopy
4
5
from typing import Any , Dict , List
5
6
6
7
import boto3
7
- from chaoslib .exceptions import FailedActivity
8
- from chaoslib .types import Configuration , Secrets
9
8
from logzero import logger
10
9
11
10
from chaosaws import aws_client
12
11
from chaosaws .types import AWSResponse
13
-
14
- from collections import defaultdict
15
-
12
+ from chaoslib .exceptions import FailedActivity
13
+ from chaoslib .types import Configuration , Secrets
16
14
17
15
__all__ = ["stop_instance" , "stop_instances" ]
18
16
@@ -29,6 +27,7 @@ def stop_instance(instance_id: str = None, az: str = None, force: bool = False,
29
27
also provide a list of filters following the documentation
30
28
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_instances
31
29
"""
30
+
32
31
if not az and not instance_id and not filters :
33
32
raise FailedActivity (
34
33
"To stop an EC2 instance, you must specify either the instance id,"
@@ -38,6 +37,7 @@ def stop_instance(instance_id: str = None, az: str = None, force: bool = False,
38
37
39
38
if not instance_id :
40
39
filters = deepcopy (filters ) if filters else []
40
+
41
41
if az :
42
42
filters .append ({'Name' : 'availability-zone' , 'Values' : [az ]})
43
43
instance_types = pick_random_instance (filters , client )
@@ -58,6 +58,7 @@ def stop_instance(instance_id: str = None, az: str = None, force: bool = False,
58
58
59
59
def stop_instances (instance_ids : List [str ] = None , az : str = None ,
60
60
filters : List [Dict [str , Any ]] = None ,
61
+
61
62
force : bool = False , configuration : Configuration = None ,
62
63
secrets : Secrets = None ) -> AWSResponse :
63
64
"""
@@ -66,6 +67,7 @@ def stop_instances(instance_ids: List[str] = None, az: str = None,
66
67
also provide a list of filters following the documentation
67
68
https://boto3.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_instances
68
69
"""
70
+
69
71
if not az and not instance_ids and not filters :
70
72
raise FailedActivity (
71
73
"To stop EC2 instances, you must specify either the instance ids,"
@@ -79,6 +81,7 @@ def stop_instances(instance_ids: List[str] = None, az: str = None,
79
81
80
82
if not instance_ids :
81
83
filters = deepcopy (filters ) if filters else []
84
+
82
85
if az :
83
86
filters .append ({'Name' : 'availability-zone' , 'Values' : [az ]})
84
87
instance_types = list_instances_by_type (filters , client )
@@ -121,7 +124,8 @@ def pick_random_instance(filters: List[Dict[str, Any]],
121
124
"""
122
125
instances_type = list_instances_by_type (filters , client )
123
126
random_id = random .choice ([item for sublist in instances_type .values ()
124
- for item in sublist ])
127
+ for item in sublist ])
128
+
125
129
for inst_type in instances_type :
126
130
if random_id in instances_type [inst_type ]:
127
131
return {inst_type : [random_id ]}
@@ -133,13 +137,15 @@ def get_instance_type_from_response(response: Dict) -> Dict:
133
137
"""
134
138
instances_type = defaultdict (List )
135
139
# reservations are instances that were started together
140
+
136
141
for reservation in response ['Reservations' ]:
137
142
for inst in reservation ['Instances' ]:
138
143
if inst ['InstanceLifecycle' ] not in instances_type .keys ():
139
144
# adding empty list (value) for new instance type (key)
140
145
instances_type [inst ['InstanceLifecycle' ]] = []
141
146
instances_type [inst ['InstanceLifecycle' ]].append (
142
- inst ['InstanceId' ])
147
+ inst ['InstanceId' ])
148
+
143
149
return instances_type
144
150
145
151
@@ -149,10 +155,12 @@ def get_spot_request_ids_from_response(response: Dict) -> List[str]:
149
155
(DescribeInstances)
150
156
"""
151
157
spot_request_ids = []
158
+
152
159
for reservation in response ['Reservations' ]:
153
160
for inst in reservation ['Instances' ]:
154
161
if inst ['InstanceLifecycle' ] == 'spot' :
155
162
spot_request_ids .append (inst ['SpotInstanceRequestId' ])
163
+
156
164
return spot_request_ids
157
165
158
166
@@ -163,32 +171,35 @@ def get_instance_type_by_id(instance_ids: List[str],
163
171
"""
164
172
instances_type = defaultdict (List )
165
173
res = client .describe_instances (InstanceIds = instance_ids )
174
+
166
175
return get_instance_type_from_response (res )
167
176
168
177
169
178
def stop_instances_any_type (instance_types : dict , force , client : boto3 .client ):
170
179
"""
171
180
Stop instances regardless of the instance type (ondemand, spot)
172
181
"""
182
+
173
183
if 'normal' in instance_types :
174
184
logger .debug ("Stopping instances: {}" .format (instance_types ['normal' ]))
175
185
client .stop_instances (InstanceIds = instance_types ['normal' ],
176
186
Force = force )
187
+
177
188
if 'spot' in instance_types :
178
189
# TODO: proper support for spot fleets
179
190
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-fleet.html
180
191
181
192
# To properly stop spot instances have to cancel spot requests first
182
193
spot_request_ids = get_spot_request_ids_from_response (
183
- client .describe_instances (InstanceIds = instance_types ['spot' ]))
194
+ client .describe_instances (InstanceIds = instance_types ['spot' ]))
184
195
185
196
logger .debug ("Canceling spot requests: {}" .format (spot_request_ids ))
186
197
client .cancel_spot_instance_requests (
187
- SpotInstanceRequestIds = spot_request_ids )
198
+ SpotInstanceRequestIds = spot_request_ids )
188
199
logger .debug ("Terminating spot instances: {}" .format (
189
200
instance_types ['spot' ]))
190
- client .terminate_instances (InstanceIds = instance_types ['spot' ],
191
- Force = force )
201
+ client .terminate_instances (InstanceIds = instance_types ['spot' ])
202
+
192
203
if 'scheduled' in instance_types :
193
204
# TODO: add support for scheduled inststances
194
205
# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-scheduled-instances.html
0 commit comments