Skip to content

Commit b3c929b

Browse files
committed
add stop_instances_by_incremental_steps action to ec2
Signed-off-by: Sylvain Hellegouarch <[email protected]>
1 parent 58eebfb commit b3c929b

File tree

4 files changed

+97
-23
lines changed

4 files changed

+97
-23
lines changed

CHANGELOG.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22

33
## [Unreleased][]
44

5-
[Unreleased]: https://github.com/chaostoolkit-incubator/chaostoolkit-aws/compare/0.28.0...HEAD
5+
[Unreleased]: https://github.com/chaostoolkit-incubator/chaostoolkit-aws/compare/0.29.0...HEAD
6+
7+
## [0.29.0][] - 2023-12-10
8+
9+
[0.29.0]: https://github.com/chaostoolkit-incubator/chaostoolkit-aws/compare/0.28.0...0.29.0
10+
11+
### Added
12+
13+
- Action `stop_instances_by_incremental_steps` in the ec2 package
614

715
## [0.28.0][] - 2023-12-01
816

chaosaws/__init__.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from chaoslib.types import Configuration, DiscoveredActivities, Discovery, Secrets
1717
from logzero import logger
1818

19-
__version__ = "0.28.0"
19+
__version__ = "0.29.0"
2020
__all__ = ["__version__", "discover", "aws_client", "signed_api_call"]
2121

2222

@@ -288,3 +288,18 @@ def time_to_datetime(
288288
delta = 60 * 60 * 24
289289

290290
return offset - timedelta(seconds=duration * delta)
291+
292+
293+
def convert_tags(tags: Union[str, Dict[str, str]]) -> Dict[str, str]:
294+
"""
295+
Convert a `k=v,x=y` string into a dictionary
296+
"""
297+
if isinstance(tags, dict):
298+
return tags
299+
300+
result = {}
301+
for t in tags.split(","):
302+
k, v = t.split("=", 1)
303+
result[k] = v
304+
305+
return result

chaosaws/ec2/actions.py

+71-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import random
2+
import time
23
from collections import defaultdict
34
from copy import deepcopy
45
from typing import Any, Dict, List, Union
@@ -9,7 +10,7 @@
910
from chaoslib.types import Configuration, Secrets
1011
from logzero import logger
1112

12-
from chaosaws import aws_client
13+
from chaosaws import aws_client, convert_tags
1314
from chaosaws.types import AWSResponse
1415

1516
__all__ = [
@@ -21,6 +22,7 @@
2122
"restart_instances",
2223
"detach_random_volume",
2324
"attach_volume",
25+
"stop_instances_by_incremental_steps",
2426
]
2527

2628

@@ -423,9 +425,77 @@ def attach_volume(
423425
return results
424426

425427

428+
def stop_instances_by_incremental_steps(
429+
volume: int,
430+
step_quantity: int,
431+
step_duration: int,
432+
az: str = None,
433+
tags: Union[str, Dict[str, Any]] = None,
434+
force: bool = False,
435+
configuration: Configuration = None,
436+
secrets: Secrets = None,
437+
) -> List[AWSResponse]:
438+
"""
439+
Stop a volume of instances incrementally by steps.
440+
441+
The steps are using two dimensions, the duration between two iterations
442+
and the number of instances to stop on each iteration.
443+
444+
The `tags` can be specified as a key=value pair dictionary or a comma
445+
separated list of k=v pairs. They are good to be set when you want to
446+
target only a certain subset of instances. Likewise for the
447+
availability-zone.
448+
"""
449+
client = aws_client("ec2", configuration, secrets)
450+
451+
filters = []
452+
453+
tags = convert_tags(tags) if tags else []
454+
455+
if tags:
456+
filters.append(tags)
457+
458+
if az:
459+
filters.append({"Name": "availability-zone", "Values": [az]})
460+
461+
instances = list_instances_by_type(filters, client)
462+
463+
if not instances:
464+
raise FailedActivity(f"No instances in availability zone: {az}")
465+
466+
logger.debug(
467+
"Picked EC2 instances '{}' from AZ '{}' to be stopped".format(
468+
str(instances), az
469+
)
470+
)
471+
472+
total = len(instances)
473+
count = round(total * volume / 100)
474+
target_instances = random.sample(instances, count)
475+
476+
responses = []
477+
while target_instances:
478+
stop_these_instances_now = target_instances[:step_quantity]
479+
target_instances = target_instances[step_quantity:]
480+
481+
responses.extend(
482+
stop_instances_any_type(
483+
instance_types=stop_these_instances_now, force=force, client=client
484+
)
485+
)
486+
487+
pause_for_a_while(step_duration)
488+
489+
return responses
490+
491+
426492
###############################################################################
427493
# Private functions
428494
###############################################################################
495+
def pause_for_a_while(duration: int) -> None:
496+
time.sleep(float(duration))
497+
498+
429499
def list_instances_by_type(
430500
filters: List[Dict[str, Any]], client: boto3.client
431501
) -> Dict[str, Any]:

chaosaws/fis/actions.py

+1-20
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from chaoslib.types import Configuration, Secrets
55
from logzero import logger
66

7-
from chaosaws import aws_client
7+
from chaosaws import aws_client, convert_tags
88
from chaosaws.types import AWSResponse
99

1010
__all__ = [
@@ -157,7 +157,6 @@ def stop_experiments_by_tags(
157157

158158
stopped = []
159159
for x in experiments["experiments"]:
160-
print(x)
161160
try:
162161
if x["tags"] == tags:
163162
status = x["state"]["status"]
@@ -483,21 +482,3 @@ def start_availability_zone_power_interruption_scenario(
483482
return fis_client.start_experiment(**params)
484483
except Exception as ex:
485484
raise FailedActivity(f"Start Experiment failed, reason was: {ex}")
486-
487-
488-
###############################################################################
489-
# Private functions
490-
###############################################################################
491-
def convert_tags(tags: Union[str, Dict[str, str]]) -> Dict[str, str]:
492-
"""
493-
Convert a `k=v,x=y` string into a dictionary
494-
"""
495-
if isinstance(tags, dict):
496-
return tags
497-
498-
result = {}
499-
for t in tags.split(","):
500-
k, v = t.split("=", 1)
501-
result[k] = v
502-
503-
return result

0 commit comments

Comments
 (0)