|
1 | 1 | import random
|
| 2 | +import time |
2 | 3 | from collections import defaultdict
|
3 | 4 | from copy import deepcopy
|
4 | 5 | from typing import Any, Dict, List, Union
|
|
9 | 10 | from chaoslib.types import Configuration, Secrets
|
10 | 11 | from logzero import logger
|
11 | 12 |
|
12 |
| -from chaosaws import aws_client |
| 13 | +from chaosaws import aws_client, convert_tags |
13 | 14 | from chaosaws.types import AWSResponse
|
14 | 15 |
|
15 | 16 | __all__ = [
|
|
21 | 22 | "restart_instances",
|
22 | 23 | "detach_random_volume",
|
23 | 24 | "attach_volume",
|
| 25 | + "stop_instances_by_incremental_steps", |
24 | 26 | ]
|
25 | 27 |
|
26 | 28 |
|
@@ -423,9 +425,77 @@ def attach_volume(
|
423 | 425 | return results
|
424 | 426 |
|
425 | 427 |
|
| 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 | + |
426 | 492 | ###############################################################################
|
427 | 493 | # Private functions
|
428 | 494 | ###############################################################################
|
| 495 | +def pause_for_a_while(duration: int) -> None: |
| 496 | + time.sleep(float(duration)) |
| 497 | + |
| 498 | + |
429 | 499 | def list_instances_by_type(
|
430 | 500 | filters: List[Dict[str, Any]], client: boto3.client
|
431 | 501 | ) -> Dict[str, Any]:
|
|
0 commit comments