From 51eeebbda0aaf704569f6359d3bf7fe8890355c6 Mon Sep 17 00:00:00 2001 From: Taeho Park <113317744+taehopark32@users.noreply.github.com> Date: Wed, 2 Aug 2023 11:46:45 -0400 Subject: [PATCH] Added started and stopped states for rds cluster (#1647) Added started and stopped states for rds cluster SUMMARY Fixes #1616 ISSUE TYPE Feature Pull Request COMPONENT NAME plugins/modules/rds_cluster.py ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis Reviewed-by: Mike Graves --- .../1647-add-type-started-and-stopped.yml | 4 + plugins/module_utils/rds.py | 2 + plugins/modules/rds_cluster.py | 28 +- .../targets/rds_cluster_states/aliases | 4 + .../rds_cluster_states/defaults/main.yml | 11 + .../targets/rds_cluster_states/tasks/main.yml | 240 ++++++++++++++++++ tests/unit/module_utils/test_rds.py | 30 +++ 7 files changed, 315 insertions(+), 4 deletions(-) create mode 100644 changelogs/fragments/1647-add-type-started-and-stopped.yml create mode 100644 tests/integration/targets/rds_cluster_states/aliases create mode 100644 tests/integration/targets/rds_cluster_states/defaults/main.yml create mode 100644 tests/integration/targets/rds_cluster_states/tasks/main.yml diff --git a/changelogs/fragments/1647-add-type-started-and-stopped.yml b/changelogs/fragments/1647-add-type-started-and-stopped.yml new file mode 100644 index 0000000000..5a13a5848c --- /dev/null +++ b/changelogs/fragments/1647-add-type-started-and-stopped.yml @@ -0,0 +1,4 @@ +--- +minor_changes: + - rds_cluster - add support for another ``state`` choice called ``started``. This starts the rds cluster (https://github.com/ansible-collections/amazon.aws/pull/1647/files). + - rds_cluster - add support for another ``state`` choice called ``stopped``. This stops the rds cluster (https://github.com/ansible-collections/amazon.aws/pull/1647/files). \ No newline at end of file diff --git a/plugins/module_utils/rds.py b/plugins/module_utils/rds.py index 2de153d044..d4fb538074 100644 --- a/plugins/module_utils/rds.py +++ b/plugins/module_utils/rds.py @@ -35,6 +35,8 @@ "remove_tags_from_resource", "list_tags_for_resource", "promote_read_replica_db_cluster", + "stop_db_cluster", + "start_db_cluster", ] instance_method_names = [ "create_db_instance", diff --git a/plugins/modules/rds_cluster.py b/plugins/modules/rds_cluster.py index 7acf6717f5..187bfbe28e 100644 --- a/plugins/modules/rds_cluster.py +++ b/plugins/modules/rds_cluster.py @@ -24,8 +24,11 @@ options: # General module options state: - description: Whether the snapshot should exist or not. - choices: ['present', 'absent'] + description: + - Whether the snapshot should exist or not. + - C(started) and C(stopped) can only be used with aurora clusters + - Support for C(started) and C(stopped) was added in release 6.3.0. + choices: ['present', 'absent', 'started', 'stopped'] default: 'present' type: str creation_source: @@ -689,7 +692,6 @@ sample: sg-12345678 """ - try: import botocore except ImportError: @@ -921,6 +923,14 @@ def get_rds_method_attribute_name(cluster): if cluster and cluster["Status"] not in ["deleting", "deleted"]: method_name = "delete_db_cluster" method_options_name = "get_delete_options" + elif state == "started": + if cluster and cluster["Status"] not in ["starting", "started", "available"]: + method_name = "start_db_cluster" + method_options_name = "get_modify_options" + elif state == "stopped": + if cluster and cluster["Status"] not in ["stopping", "stopped"]: + method_name = "stop_db_cluster" + method_options_name = "get_modify_options" else: if cluster: method_name = "modify_db_cluster" @@ -1030,6 +1040,16 @@ def changing_cluster_options(modify_params, current_cluster): if apply_immediately is not None: changing_params["ApplyImmediately"] = apply_immediately + if module.params["state"] == "started": + if current_cluster["Engine"] in ["mysql", "postgres"]: + module.fail_json("Only aurora clusters can use the state started") + changing_params["DBClusterIdentifier"] = db_cluster_id + + if module.params["state"] == "stopped": + if current_cluster["Engine"] in ["mysql", "postgres"]: + module.fail_json("Only aurora clusters can use the state stopped") + changing_params["DBClusterIdentifier"] = db_cluster_id + return changing_params @@ -1087,7 +1107,7 @@ def main(): global client arg_spec = dict( - state=dict(choices=["present", "absent"], default="present"), + state=dict(choices=["present", "absent", "started", "stopped"], default="present"), creation_source=dict(type="str", choices=["snapshot", "s3", "cluster"]), force_update_password=dict(type="bool", default=False), promote=dict(type="bool", default=False), diff --git a/tests/integration/targets/rds_cluster_states/aliases b/tests/integration/targets/rds_cluster_states/aliases new file mode 100644 index 0000000000..b437e9df50 --- /dev/null +++ b/tests/integration/targets/rds_cluster_states/aliases @@ -0,0 +1,4 @@ +time=30m +cloud/aws +rds_cluster +rds_cluster_info \ No newline at end of file diff --git a/tests/integration/targets/rds_cluster_states/defaults/main.yml b/tests/integration/targets/rds_cluster_states/defaults/main.yml new file mode 100644 index 0000000000..f362c83d4c --- /dev/null +++ b/tests/integration/targets/rds_cluster_states/defaults/main.yml @@ -0,0 +1,11 @@ +cluster_id: ansible-test-cluster-{{ tiny_prefix }} +username: testrdsusername +password: test-rds_password +engine: aurora +db_cluster_instance_class: db.r5.large + +mysql_cluster_id: ansible-test-mysql-cluster-{{ tiny_prefix }} +mysql_engine: mysql +mysql_allocated_storage: 100 +mysql_iops: 1000 +mysql_db_cluster_instance_class: db.m5d.large \ No newline at end of file diff --git a/tests/integration/targets/rds_cluster_states/tasks/main.yml b/tests/integration/targets/rds_cluster_states/tasks/main.yml new file mode 100644 index 0000000000..945fa1abde --- /dev/null +++ b/tests/integration/targets/rds_cluster_states/tasks/main.yml @@ -0,0 +1,240 @@ +- module_defaults: + group/aws: + region: '{{ aws_region }}' + aws_access_key: '{{ aws_access_key }}' + aws_secret_key: '{{ aws_secret_key }}' + security_token: '{{ security_token | default(omit) }}' + block: + # ------------------------------------------------------------------------------------------ + # Create DB cluster + - name: Ensure the resource doesn't exist + rds_cluster: + id: '{{ cluster_id }}' + state: absent + engine: '{{ engine}}' + username: '{{ username }}' + password: '{{ password }}' + skip_final_snapshot: true + register: _result_delete_db_cluster + + - assert: + that: + - not _result_delete_db_cluster.changed + ignore_errors: yes + + - name: Create an Aurora-PostgreSQL DB cluster + rds_cluster: + id: '{{ cluster_id }}' + state: present + engine: aurora-postgresql + engine_mode: provisioned + username: '{{ username }}' + password: '{{ password }}' + register: _result_create_source_db_cluster + + - assert: + that: + - _result_create_source_db_cluster.changed + - "'allocated_storage' in _result_create_source_db_cluster" + - _result_create_source_db_cluster.allocated_storage == 1 + - "'cluster_create_time' in _result_create_source_db_cluster" + - _result_create_source_db_cluster.copy_tags_to_snapshot == false + - "'db_cluster_arn' in _result_create_source_db_cluster" + - _result_create_source_db_cluster.db_cluster_identifier == '{{ cluster_id }}' + - "'db_cluster_parameter_group' in _result_create_source_db_cluster" + - "'db_cluster_resource_id' in _result_create_source_db_cluster" + - "'endpoint' in _result_create_source_db_cluster" + - "'engine' in _result_create_source_db_cluster" + - _result_create_source_db_cluster.engine == "aurora-postgresql" + - "'engine_mode' in _result_create_source_db_cluster" + - _result_create_source_db_cluster.engine_mode == "provisioned" + - "'engine_version' in _result_create_source_db_cluster" + - "'master_username' in _result_create_source_db_cluster" + - _result_create_source_db_cluster.master_username == "{{ username }}" + - "'port' in _result_create_source_db_cluster" + - "'status' in _result_create_source_db_cluster" + - _result_create_source_db_cluster.status == "available" + - "'tags' in _result_create_source_db_cluster" + - "'vpc_security_groups' in _result_create_source_db_cluster" + + # ------------------------------------------------------------------------------------------ + # Test stopping db clusters + - name: Stop db clusters - checkmode + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: stopped + register: check_stopped_cluster + check_mode: yes + + - assert: + that: + - check_stopped_cluster.changed + + - name: Stop db clusters + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: stopped + register: stopped_cluster + + - assert: + that: + - stopped_cluster.changed + + - name: Wait until db clusters state is stopped + amazon.aws.rds_cluster_info: + cluster_id: '{{ cluster_id }}' + register: stopped_cluster_info + retries: 30 + delay: 60 + until: stopped_cluster_info.clusters[0].status == "stopped" + + - name: Stop db clusters (idempotence) - checkmode + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: stopped + register: check_stopped_cluster_idem + check_mode: yes + + - assert: + that: + - not check_stopped_cluster_idem.changed + + - name: Stop db clusters (idempotence) + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: stopped + register: stopped_cluster_idem + + - assert: + that: + - not stopped_cluster_idem.changed + + # ------------------------------------------------------------------------------------------ + # Test starting DB clusters + - name: Start db clusters - checkmode + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: started + register: check_started_cluster + check_mode: yes + + - assert: + that: + - check_started_cluster.changed + + - name: Start db clusters + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: started + register: started_cluster + + - assert: + that: + - started_cluster.changed + + - name: Start db clusters (idempotence) - checkmode + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: started + register: check_started_cluster + check_mode: yes + + - assert: + that: + - not check_started_cluster.changed + + - name: Start db clusters + amazon.aws.rds_cluster: + cluster_id: '{{ cluster_id }}' + state: started + register: started_cluster + + - assert: + that: + - not started_cluster.changed + + # ------------------------------------------------------------------------------------------ + # Give errors for MySql DB cluster + - name: Ensure the resource doesn't exist + rds_cluster: + id: '{{ mysql_cluster_id }}' + state: absent + engine: '{{ mysql_engine }}' + username: '{{ username }}' + password: '{{ password }}' + skip_final_snapshot: true + register: _result_delete_mysql_db_cluster + + - assert: + that: + - not _result_delete_mysql_db_cluster.changed + ignore_errors: yes + + - name: Create an MySql DB cluster + rds_cluster: + id: '{{ mysql_cluster_id }}' + state: present + engine: '{{ mysql_engine }}' + engine_mode: provisioned + allocated_storage: '{{ mysql_allocated_storage }}' + iops: '{{ mysql_iops }}' + db_cluster_instance_class: '{{ mysql_db_cluster_instance_class }}' + username: '{{ username }}' + password: '{{ password }}' + ignore_errors: yes + register: mysql_cluster + + - assert: + that: + - mysql_cluster.changed + - "'allocated_storage' in mysql_cluster" + - mysql_cluster.allocated_storage == 100 + - "'cluster_create_time' in mysql_cluster" + - mysql_cluster.copy_tags_to_snapshot == false + - "'db_cluster_arn' in mysql_cluster" + - mysql_cluster.db_cluster_identifier == "{{ mysql_cluster_id }}" + - "'db_cluster_parameter_group' in mysql_cluster" + - "'db_cluster_resource_id' in mysql_cluster" + - "'endpoint' in mysql_cluster" + - "'engine' in mysql_cluster" + - mysql_cluster.engine == "{{ mysql_engine }}" + - "'engine_mode' in mysql_cluster" + - mysql_cluster.engine_mode == "provisioned" + - "'engine_version' in mysql_cluster" + - "'master_username' in mysql_cluster" + - mysql_cluster.master_username == "{{ username }}" + - "'port' in mysql_cluster" + - "'status' in mysql_cluster" + - mysql_cluster.status == "available" + - "'tags' in mysql_cluster" + - "'vpc_security_groups' in mysql_cluster" + + + - name: Stop MySql DB cluster + amazon.aws.rds_cluster: + cluster_id: '{{ mysql_cluster_id }}' + state: stopped + register: mysql_cluster + ignore_errors: true + + - assert: + that: + - mysql_cluster is failed + - mysql_cluster.msg == "Only aurora clusters can use the state stopped" + + always: + # ------------------------------------------------------------------------------------------ + # Cleanup starts here + - name: Delete MySql db cluster without creating a final snapshot + rds_cluster: + state: absent + cluster_id: '{{ mysql_cluster_id }}' + skip_final_snapshot: true + ignore_errors: true + + - name: Delete Aurora-PostgreSql db cluster without creating a final snapshot + rds_cluster: + state: absent + cluster_id: '{{ cluster_id }}' + skip_final_snapshot: true + ignore_errors: true \ No newline at end of file diff --git a/tests/unit/module_utils/test_rds.py b/tests/unit/module_utils/test_rds.py index 3de8021470..eb90acca77 100644 --- a/tests/unit/module_utils/test_rds.py +++ b/tests/unit/module_utils/test_rds.py @@ -137,6 +137,36 @@ def test__wait_for_cluster_snapshot_status_failed(input, expected): ) ), ), + ( + "start_db_cluster", + { + "new_db_cluster_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="start_db_cluster", + waiter="cluster_available", + operation_description="start DB cluster", + resource="cluster", + retry_codes=["InvalidDBClusterState"], + ) + ), + ), + ( + "stop_db_cluster", + { + "new_db_cluster_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="stop_db_cluster", + waiter="cluster_available", + operation_description="stop DB cluster", + resource="cluster", + retry_codes=["InvalidDBClusterState"], + ) + ), + ), ( "restore_db_cluster_from_snapshot", {