diff --git a/plugins/modules/cloudwatch_metric_alarm.py b/plugins/modules/cloudwatch_metric_alarm.py new file mode 100644 index 00000000000..dbe6bf43f94 --- /dev/null +++ b/plugins/modules/cloudwatch_metric_alarm.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +module: cloudwatch_metric_alarm +short_description: "Create/update or delete AWS CloudWatch 'metric alarms'" +version_added: 1.0.0 +description: + - Can create or delete AWS CloudWatch metric alarms. + - Metrics you wish to alarm on must already exist. + - Prior to release 5.0.0 this module was called C(community.aws.ec2_metric_alarm). + The usage did not change. +author: + - "Zacharie Eakin (@Zeekin)" +options: + state: + description: + - Register or deregister the alarm. + choices: ['present', 'absent'] + default: 'present' + type: str + name: + description: + - Unique name for the alarm. + required: true + type: str + metric: + description: + - Name of the monitored metric (e.g. C(CPUUtilization)). + - Metric must already exist. + required: false + type: str + namespace: + description: + - Name of the appropriate namespace (C(AWS/EC2), C(System/Linux), etc.), which determines the category it will appear under in CloudWatch. + required: false + type: str + statistic: + description: + - Operation applied to the metric. + - Works in conjunction with I(period) and I(evaluation_periods) to determine the comparison value. + required: false + choices: ['SampleCount','Average','Sum','Minimum','Maximum'] + type: str + comparison: + description: + - Determines how the threshold value is compared + required: false + type: str + choices: + - 'GreaterThanOrEqualToThreshold' + - 'GreaterThanThreshold' + - 'LessThanThreshold' + - 'LessThanOrEqualToThreshold' + threshold: + description: + - Sets the min/max bound for triggering the alarm. + required: false + type: float + period: + description: + - The time (in seconds) between metric evaluations. + required: false + type: int + evaluation_periods: + description: + - The number of times in which the metric is evaluated before final calculation. + required: false + type: int + unit: + description: + - The threshold's unit of measurement. + required: false + type: str + choices: + - 'Seconds' + - 'Microseconds' + - 'Milliseconds' + - 'Bytes' + - 'Kilobytes' + - 'Megabytes' + - 'Gigabytes' + - 'Terabytes' + - 'Bits' + - 'Kilobits' + - 'Megabits' + - 'Gigabits' + - 'Terabits' + - 'Percent' + - 'Count' + - 'Bytes/Second' + - 'Kilobytes/Second' + - 'Megabytes/Second' + - 'Gigabytes/Second' + - 'Terabytes/Second' + - 'Bits/Second' + - 'Kilobits/Second' + - 'Megabits/Second' + - 'Gigabits/Second' + - 'Terabits/Second' + - 'Count/Second' + - 'None' + description: + description: + - A longer description of the alarm. + required: false + type: str + dimensions: + description: + - A dictionary describing which metric the alarm is applied to. + - 'For more information see the AWS documentation:' + - U(https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Dimension) + required: false + type: dict + alarm_actions: + description: + - A list of the names action(s) taken when the alarm is in the C(alarm) status, denoted as Amazon Resource Name(s). + required: false + type: list + elements: str + insufficient_data_actions: + description: + - A list of the names of action(s) to take when the alarm is in the C(insufficient_data) status. + required: false + type: list + elements: str + ok_actions: + description: + - A list of the names of action(s) to take when the alarm is in the C(ok) status, denoted as Amazon Resource Name(s). + required: false + type: list + elements: str + treat_missing_data: + description: + - Sets how the alarm handles missing data points. + required: false + type: str + choices: + - 'breaching' + - 'notBreaching' + - 'ignore' + - 'missing' + default: 'missing' +extends_documentation_fragment: + - amazon.aws.aws + - amazon.aws.ec2 +''' + +EXAMPLES = r''' + - name: create alarm + community.aws.cloudwatch_metric_alarm: + state: present + region: ap-southeast-2 + name: "cpu-low" + metric: "CPUUtilization" + namespace: "AWS/EC2" + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 300 + evaluation_periods: 3 + unit: "Percent" + description: "This will alarm when a instance's CPU usage average is lower than 5% for 15 minutes" + dimensions: {'InstanceId':'i-XXX'} + alarm_actions: ["action1","action2"] + + - name: Create an alarm to recover a failed instance + community.aws.cloudwatch_metric_alarm: + state: present + region: us-west-1 + name: "recover-instance" + metric: "StatusCheckFailed_System" + namespace: "AWS/EC2" + statistic: "Minimum" + comparison: "GreaterThanOrEqualToThreshold" + threshold: 1.0 + period: 60 + evaluation_periods: 2 + unit: "Count" + description: "This will recover an instance when it fails" + dimensions: {"InstanceId":'i-XXX'} + alarm_actions: ["arn:aws:automate:us-west-1:ec2:recover"] +''' + +try: + from botocore.exceptions import ClientError +except ImportError: + pass # protected by AnsibleAWSModule + +from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule + + +def create_metric_alarm(connection, module, params): + alarms = connection.describe_alarms(AlarmNames=[params['AlarmName']]) + + if not isinstance(params['Dimensions'], list): + fixed_dimensions = [] + for key, value in params['Dimensions'].items(): + fixed_dimensions.append({'Name': key, 'Value': value}) + params['Dimensions'] = fixed_dimensions + + if not alarms['MetricAlarms']: + try: + if not module.check_mode: + connection.put_metric_alarm(**params) + changed = True + except ClientError as e: + module.fail_json_aws(e) + + else: + changed = False + alarm = alarms['MetricAlarms'][0] + + # Workaround for alarms created before TreatMissingData was introduced + if 'TreatMissingData' not in alarm.keys(): + alarm['TreatMissingData'] = 'missing' + + for key in ['ActionsEnabled', 'StateValue', 'StateReason', + 'StateReasonData', 'StateUpdatedTimestamp', + 'AlarmArn', 'AlarmConfigurationUpdatedTimestamp']: + alarm.pop(key, None) + if alarm != params: + changed = True + alarm = params + + try: + if changed: + if not module.check_mode: + connection.put_metric_alarm(**alarm) + except ClientError as e: + module.fail_json_aws(e) + + try: + alarms = connection.describe_alarms(AlarmNames=[params['AlarmName']]) + except ClientError as e: + module.fail_json_aws(e) + + result = {} + if alarms['MetricAlarms']: + result = alarms['MetricAlarms'][0] + + module.exit_json(changed=changed, + name=result.get('AlarmName'), + actions_enabled=result.get('ActionsEnabled'), + alarm_actions=result.get('AlarmActions'), + alarm_arn=result.get('AlarmArn'), + comparison=result.get('ComparisonOperator'), + description=result.get('AlarmDescription'), + dimensions=result.get('Dimensions'), + evaluation_periods=result.get('EvaluationPeriods'), + insufficient_data_actions=result.get('InsufficientDataActions'), + last_updated=result.get('AlarmConfigurationUpdatedTimestamp'), + metric=result.get('MetricName'), + namespace=result.get('Namespace'), + ok_actions=result.get('OKActions'), + period=result.get('Period'), + state_reason=result.get('StateReason'), + state_value=result.get('StateValue'), + statistic=result.get('Statistic'), + threshold=result.get('Threshold'), + treat_missing_data=result.get('TreatMissingData'), + unit=result.get('Unit')) + + +def delete_metric_alarm(connection, module, params): + alarms = connection.describe_alarms(AlarmNames=[params['AlarmName']]) + + if alarms['MetricAlarms']: + try: + if not module.check_mode: + connection.delete_alarms(AlarmNames=[params['AlarmName']]) + module.exit_json(changed=True) + except (ClientError) as e: + module.fail_json_aws(e) + else: + module.exit_json(changed=False) + + +def main(): + argument_spec = dict( + name=dict(required=True, type='str'), + metric=dict(type='str'), + namespace=dict(type='str'), + statistic=dict(type='str', choices=['SampleCount', 'Average', 'Sum', 'Minimum', 'Maximum']), + comparison=dict(type='str', choices=['LessThanOrEqualToThreshold', 'LessThanThreshold', 'GreaterThanThreshold', + 'GreaterThanOrEqualToThreshold']), + threshold=dict(type='float'), + period=dict(type='int'), + unit=dict(type='str', choices=['Seconds', 'Microseconds', 'Milliseconds', 'Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', + 'Terabytes', 'Bits', 'Kilobits', 'Megabits', 'Gigabits', 'Terabits', 'Percent', 'Count', + 'Bytes/Second', 'Kilobytes/Second', 'Megabytes/Second', 'Gigabytes/Second', + 'Terabytes/Second', 'Bits/Second', 'Kilobits/Second', 'Megabits/Second', 'Gigabits/Second', + 'Terabits/Second', 'Count/Second', 'None']), + evaluation_periods=dict(type='int'), + description=dict(type='str'), + dimensions=dict(type='dict', default={}), + alarm_actions=dict(type='list', default=[], elements='str'), + insufficient_data_actions=dict(type='list', default=[], elements='str'), + ok_actions=dict(type='list', default=[], elements='str'), + treat_missing_data=dict(type='str', choices=['breaching', 'notBreaching', 'ignore', 'missing'], default='missing'), + state=dict(default='present', choices=['present', 'absent']), + ) + + module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) + + state = module.params.get('state') + + params = dict() + params['AlarmName'] = module.params.get('name') + params['MetricName'] = module.params.get('metric') + params['Namespace'] = module.params.get('namespace') + params['Statistic'] = module.params.get('statistic') + params['ComparisonOperator'] = module.params.get('comparison') + params['Threshold'] = module.params.get('threshold') + params['Period'] = module.params.get('period') + params['EvaluationPeriods'] = module.params.get('evaluation_periods') + if module.params.get('unit'): + params['Unit'] = module.params.get('unit') + params['AlarmDescription'] = module.params.get('description') + params['Dimensions'] = module.params.get('dimensions') + params['AlarmActions'] = module.params.get('alarm_actions', []) + params['InsufficientDataActions'] = module.params.get('insufficient_data_actions', []) + params['OKActions'] = module.params.get('ok_actions', []) + params['TreatMissingData'] = module.params.get('treat_missing_data') + + connection = module.client('cloudwatch') + + if state == 'present': + create_metric_alarm(connection, module, params) + elif state == 'absent': + delete_metric_alarm(connection, module, params) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/cloudwatch_metric_alarm/aliases b/tests/integration/targets/cloudwatch_metric_alarm/aliases new file mode 100644 index 00000000000..4ef4b2067d0 --- /dev/null +++ b/tests/integration/targets/cloudwatch_metric_alarm/aliases @@ -0,0 +1 @@ +cloud/aws diff --git a/tests/integration/targets/cloudwatch_metric_alarm/defaults/main.yml b/tests/integration/targets/cloudwatch_metric_alarm/defaults/main.yml new file mode 100644 index 00000000000..af554c5dcef --- /dev/null +++ b/tests/integration/targets/cloudwatch_metric_alarm/defaults/main.yml @@ -0,0 +1,5 @@ +--- +# defaults file for ec2_instance +ec2_instance_name: '{{ resource_prefix }}-node' +ec2_instance_owner: 'integration-run-{{ resource_prefix }}' +alarm_prefix: "ansible-test" diff --git a/tests/integration/targets/cloudwatch_metric_alarm/meta/main.yml b/tests/integration/targets/cloudwatch_metric_alarm/meta/main.yml new file mode 100644 index 00000000000..1471b11f658 --- /dev/null +++ b/tests/integration/targets/cloudwatch_metric_alarm/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - setup_ec2_facts diff --git a/tests/integration/targets/cloudwatch_metric_alarm/tasks/env_cleanup.yml b/tests/integration/targets/cloudwatch_metric_alarm/tasks/env_cleanup.yml new file mode 100644 index 00000000000..e90ddc64508 --- /dev/null +++ b/tests/integration/targets/cloudwatch_metric_alarm/tasks/env_cleanup.yml @@ -0,0 +1,94 @@ +- name: remove any instances in the test VPC + ec2_instance: + filters: + vpc_id: "{{ testing_vpc.vpc.id }}" + state: absent + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + +- name: remove ENIs + ec2_eni_info: + filters: + vpc-id: "{{ testing_vpc.vpc.id }}" + register: enis + +- name: delete all ENIs + ec2_eni: + eni_id: "{{ item.id }}" + state: absent + until: removed is not failed + with_items: "{{ enis.network_interfaces }}" + ignore_errors: yes + retries: 10 + +- name: remove the security group + ec2_group: + name: "{{ resource_prefix }}-sg" + description: a security group for ansible tests + vpc_id: "{{ testing_vpc.vpc.id }}" + state: absent + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + +- name: remove routing rules + ec2_vpc_route_table: + state: absent + vpc_id: "{{ testing_vpc.vpc.id }}" + tags: + created: "{{ resource_prefix }}-route" + routes: + - dest: 0.0.0.0/0 + gateway_id: "{{ igw.gateway_id }}" + subnets: + - "{{ testing_subnet_a.subnet.id }}" + - "{{ testing_subnet_b.subnet.id }}" + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + +- name: remove internet gateway + ec2_vpc_igw: + vpc_id: "{{ testing_vpc.vpc.id }}" + state: absent + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + +- name: remove subnet A + ec2_vpc_subnet: + state: absent + vpc_id: "{{ testing_vpc.vpc.id }}" + cidr: 10.22.32.0/24 + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + +- name: remove subnet B + ec2_vpc_subnet: + state: absent + vpc_id: "{{ testing_vpc.vpc.id }}" + cidr: 10.22.33.0/24 + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 + +- name: remove the VPC + ec2_vpc_net: + name: "{{ resource_prefix }}-vpc" + cidr_block: 10.22.32.0/23 + state: absent + tags: + Name: Ansible Testing VPC + tenancy: default + register: removed + until: removed is not failed + ignore_errors: yes + retries: 10 diff --git a/tests/integration/targets/cloudwatch_metric_alarm/tasks/env_setup.yml b/tests/integration/targets/cloudwatch_metric_alarm/tasks/env_setup.yml new file mode 100644 index 00000000000..80b49dbcf7e --- /dev/null +++ b/tests/integration/targets/cloudwatch_metric_alarm/tasks/env_setup.yml @@ -0,0 +1,62 @@ +- name: Create VPC for use in testing + ec2_vpc_net: + name: "{{ resource_prefix }}-vpc" + cidr_block: 10.22.32.0/23 + tags: + Name: Ansible ec2_instance Testing VPC + tenancy: default + register: testing_vpc + +- name: Create internet gateway for use in testing + ec2_vpc_igw: + vpc_id: "{{ testing_vpc.vpc.id }}" + state: present + register: igw + +- name: Create default subnet in zone A + ec2_vpc_subnet: + state: present + vpc_id: "{{ testing_vpc.vpc.id }}" + cidr: 10.22.32.0/24 + az: "{{ aws_region }}a" + resource_tags: + Name: "{{ resource_prefix }}-subnet-a" + register: testing_subnet_a + +- name: Create secondary subnet in zone B + ec2_vpc_subnet: + state: present + vpc_id: "{{ testing_vpc.vpc.id }}" + cidr: 10.22.33.0/24 + az: "{{ aws_region }}b" + resource_tags: + Name: "{{ resource_prefix }}-subnet-b" + register: testing_subnet_b + +- name: create routing rules + ec2_vpc_route_table: + vpc_id: "{{ testing_vpc.vpc.id }}" + tags: + created: "{{ resource_prefix }}-route" + routes: + - dest: 0.0.0.0/0 + gateway_id: "{{ igw.gateway_id }}" + subnets: + - "{{ testing_subnet_a.subnet.id }}" + - "{{ testing_subnet_b.subnet.id }}" + +- name: create a security group with the vpc + ec2_group: + name: "{{ resource_prefix }}-sg" + description: a security group for ansible tests + vpc_id: "{{ testing_vpc.vpc.id }}" + rules: + - proto: tcp + from_port: 22 + to_port: 22 + cidr_ip: 0.0.0.0/0 + - proto: tcp + from_port: 80 + to_port: 80 + cidr_ip: 0.0.0.0/0 + register: sg diff --git a/tests/integration/targets/cloudwatch_metric_alarm/tasks/main.yml b/tests/integration/targets/cloudwatch_metric_alarm/tasks/main.yml new file mode 100644 index 00000000000..3503edeb694 --- /dev/null +++ b/tests/integration/targets/cloudwatch_metric_alarm/tasks/main.yml @@ -0,0 +1,467 @@ +- name: run ec2_metric_alarm tests + module_defaults: + group/aws: + aws_access_key: "{{ aws_access_key }}" + aws_secret_key: "{{ aws_secret_key }}" + security_token: "{{ security_token | default(omit) }}" + region: "{{ aws_region }}" + collections: + - amazon.aws + + block: + - set_fact: + alarm_full_name: "{{ alarm_prefix }}-{{ resource_prefix }}-cpu-low" + + - name: set up environment for testing. + include_tasks: env_setup.yml + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query + + - name: Make instance in a default subnet of the VPC + ec2_instance: + name: "{{ resource_prefix }}-test-default-vpc" + image_id: "{{ ec2_ami_id }}" + tags: + TestId: "{{ resource_prefix }}" + security_groups: "{{ sg.group_id }}" + vpc_subnet_id: "{{ testing_subnet_a.subnet.id }}" + instance_type: t2.micro + wait: true + register: ec2_instance_results + + - name: ensure alarm doesn't exist for a clean test + ec2_metric_alarm: + state: absent + name: "{{ alarm_full_name }}" + + - name: create ec2 metric alarm on ec2 instance (check mode) + ec2_metric_alarm: + dimensions: + InstanceId: "{{ ec2_instance_results.instances[0].instance_id }}" + state: present + name: "{{ alarm_full_name }}" + metric: "CPUUtilization" + namespace: "AWS/EC2" + treat_missing_data: missing + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 300 + evaluation_periods: 3 + unit: "Percent" + description: "This will alarm when an instance's cpu usage average is lower than 5% for 15 minutes" + check_mode: true + register: ec2_instance_metric_alarm_check + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query_check + + - name: convert it to an object + set_fact: + alarm_info_check: "{{ alarm_info_query_check.stdout | from_json }}" + + - name: "verify that an alarm was not created in check mode" + assert: + that: + - 'ec2_instance_metric_alarm_check.changed' + - 'not ec2_instance_metric_alarm_check.alarm_arn' + - 'alarm_info_check["MetricAlarms"] | length == 0' + + - name: create ec2 metric alarm on ec2 instance + ec2_metric_alarm: + dimensions: + InstanceId: "{{ ec2_instance_results.instances[0].instance_id }}" + state: present + name: "{{ alarm_full_name }}" + metric: "CPUUtilization" + namespace: "AWS/EC2" + treat_missing_data: missing + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 300 + evaluation_periods: 3 + unit: "Percent" + description: "This will alarm when an instance's cpu usage average is lower than 5% for 15 minutes" + register: ec2_instance_metric_alarm + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query + + - name: convert it to an object + set_fact: + alarm_info: "{{ alarm_info_query.stdout | from_json }}" + + - name: "verify that an alarm was created" + assert: + that: + - 'ec2_instance_metric_alarm.changed' + - 'ec2_instance_metric_alarm.alarm_arn' + - 'ec2_instance_metric_alarm.statistic == alarm_info["MetricAlarms"][0].Statistic' + - 'ec2_instance_metric_alarm.name == alarm_info["MetricAlarms"][0].AlarmName' + - 'ec2_instance_metric_alarm.metric == alarm_info["MetricAlarms"][0].MetricName' + - 'ec2_instance_metric_alarm.namespace == alarm_info["MetricAlarms"][0].Namespace' + - 'ec2_instance_metric_alarm.comparison == alarm_info["MetricAlarms"][0].ComparisonOperator' + - 'ec2_instance_metric_alarm.threshold == alarm_info["MetricAlarms"][0].Threshold' + - 'ec2_instance_metric_alarm.period == alarm_info["MetricAlarms"][0].Period' + - 'ec2_instance_metric_alarm.unit == alarm_info["MetricAlarms"][0].Unit' + - 'ec2_instance_metric_alarm.evaluation_periods == alarm_info["MetricAlarms"][0].EvaluationPeriods' + - 'ec2_instance_metric_alarm.description == alarm_info["MetricAlarms"][0].AlarmDescription' + - 'ec2_instance_metric_alarm.treat_missing_data == alarm_info["MetricAlarms"][0].TreatMissingData' + + - name: create ec2 metric alarm on ec2 instance (idempotent) (check mode) + ec2_metric_alarm: + dimensions: + InstanceId: "{{ ec2_instance_results.instances[0].instance_id }}" + state: present + name: "{{ alarm_full_name }}" + metric: "CPUUtilization" + namespace: "AWS/EC2" + treat_missing_data: missing + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 300 + evaluation_periods: 3 + unit: "Percent" + description: "This will alarm when an instance's cpu usage average is lower than 5% for 15 minutes" + check_mode: true + register: ec2_instance_metric_alarm_idempotent_check + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query_idempotent_check + + - name: convert it to an object + set_fact: + alarm_info_idempotent_check: "{{ alarm_info_query_idempotent_check.stdout | from_json }}" + + - name: "Verify alarm does not register as changed after update in check mode" + assert: + that: + - not ec2_instance_metric_alarm_idempotent_check.changed + + - name: "Verify alarm did not change after updating in check mode" + assert: + that: + - "alarm_info['MetricAlarms'][0]['{{item}}'] == alarm_info_idempotent_check['MetricAlarms'][0]['{{ item }}']" + with_items: + - AlarmArn + - Statistic + - AlarmName + - MetricName + - Namespace + - ComparisonOperator + - Threshold + - Period + - Unit + - EvaluationPeriods + - AlarmDescription + - TreatMissingData + + - name: create ec2 metric alarm on ec2 instance (idempotent) + ec2_metric_alarm: + dimensions: + InstanceId: "{{ ec2_instance_results.instances[0].instance_id }}" + state: present + name: "{{ alarm_full_name }}" + metric: "CPUUtilization" + namespace: "AWS/EC2" + treat_missing_data: missing + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 300 + evaluation_periods: 3 + unit: "Percent" + description: "This will alarm when an instance's cpu usage average is lower than 5% for 15 minutes" + register: ec2_instance_metric_alarm_idempotent + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query_idempotent + + - name: convert it to an object + set_fact: + alarm_info_idempotent: "{{ alarm_info_query_idempotent.stdout | from_json }}" + + - name: "Verify alarm does not register as changed after update" + assert: + that: + - not ec2_instance_metric_alarm_idempotent.changed + + - name: "Verify alarm did not change after updating" + assert: + that: + - "alarm_info['MetricAlarms'][0]['{{item}}'] == alarm_info_idempotent['MetricAlarms'][0]['{{ item }}']" + with_items: + - AlarmArn + - Statistic + - AlarmName + - MetricName + - Namespace + - ComparisonOperator + - Threshold + - Period + - Unit + - EvaluationPeriods + - AlarmDescription + - TreatMissingData + + - name: update alarm (check mode) + ec2_metric_alarm: + dimensions: + InstanceId: "{{ ec2_instance_results.instances[0].instance_id }}" + state: present + name: "{{ alarm_full_name }}" + metric: "CPUUtilization" + namespace: "AWS/EC2" + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 60 + evaluation_periods: 3 + unit: "Percent" + description: "This will alarm when an instance's cpu usage average is lower than 5% for 3 minutes" + check_mode: true + register: ec2_instance_metric_alarm_update_check + + - name: "verify that alarm registers as updated in check mode" + assert: + that: + - 'ec2_instance_metric_alarm_check.changed' + + - name: "verify that properties were not changed in check mode" + assert: + that: + - 'ec2_instance_metric_alarm_update_check.changed' + - 'ec2_instance_metric_alarm_update_check.period == alarm_info["MetricAlarms"][0].Period' # Period of actual alarm should not change + - 'ec2_instance_metric_alarm_update_check.alarm_arn == ec2_instance_metric_alarm.alarm_arn' + - 'ec2_instance_metric_alarm_update_check.statistic == alarm_info["MetricAlarms"][0].Statistic' + - 'ec2_instance_metric_alarm_update_check.name == alarm_info["MetricAlarms"][0].AlarmName' + - 'ec2_instance_metric_alarm_update_check.metric == alarm_info["MetricAlarms"][0].MetricName' + - 'ec2_instance_metric_alarm_update_check.namespace == alarm_info["MetricAlarms"][0].Namespace' + - 'ec2_instance_metric_alarm_update_check.statistic == alarm_info["MetricAlarms"][0].Statistic' + - 'ec2_instance_metric_alarm_update_check.comparison == alarm_info["MetricAlarms"][0].ComparisonOperator' + - 'ec2_instance_metric_alarm_update_check.threshold == alarm_info["MetricAlarms"][0].Threshold' + - 'ec2_instance_metric_alarm_update_check.unit == alarm_info["MetricAlarms"][0].Unit' + - 'ec2_instance_metric_alarm_update_check.evaluation_periods == alarm_info["MetricAlarms"][0].EvaluationPeriods' + - 'ec2_instance_metric_alarm_update_check.treat_missing_data == alarm_info["MetricAlarms"][0].TreatMissingData' + + - name: update alarm + ec2_metric_alarm: + dimensions: + InstanceId: "{{ ec2_instance_results.instances[0].instance_id }}" + state: present + name: "{{ alarm_full_name }}" + metric: "CPUUtilization" + namespace: "AWS/EC2" + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 60 + evaluation_periods: 3 + unit: "Percent" + description: "This will alarm when an instance's cpu usage average is lower than 5% for 3 minutes" + register: ec2_instance_metric_alarm_update + + - name: "verify that alarm registers as updated" + assert: + that: + - 'ec2_instance_metric_alarm.changed' + + - name: "verify that properties were changed" + assert: + that: + - 'ec2_instance_metric_alarm_update.changed' + - 'ec2_instance_metric_alarm_update.period == 60' # Period should be 60, not matching old value + - 'ec2_instance_metric_alarm_update.alarm_arn == ec2_instance_metric_alarm.alarm_arn' + - 'ec2_instance_metric_alarm_update.statistic == alarm_info["MetricAlarms"][0].Statistic' + - 'ec2_instance_metric_alarm_update.name == alarm_info["MetricAlarms"][0].AlarmName' + - 'ec2_instance_metric_alarm_update.metric == alarm_info["MetricAlarms"][0].MetricName' + - 'ec2_instance_metric_alarm_update.namespace == alarm_info["MetricAlarms"][0].Namespace' + - 'ec2_instance_metric_alarm_update.statistic == alarm_info["MetricAlarms"][0].Statistic' + - 'ec2_instance_metric_alarm_update.comparison == alarm_info["MetricAlarms"][0].ComparisonOperator' + - 'ec2_instance_metric_alarm_update.threshold == alarm_info["MetricAlarms"][0].Threshold' + - 'ec2_instance_metric_alarm_update.unit == alarm_info["MetricAlarms"][0].Unit' + - 'ec2_instance_metric_alarm_update.evaluation_periods == alarm_info["MetricAlarms"][0].EvaluationPeriods' + - 'ec2_instance_metric_alarm_update.treat_missing_data == alarm_info["MetricAlarms"][0].TreatMissingData' + + - name: try to remove the alarm (check mode) + ec2_metric_alarm: + state: absent + name: "{{ alarm_full_name }}" + check_mode: true + register: ec2_instance_metric_alarm_deletion_check + + - name: Verify that the alarm reports deleted/changed + assert: + that: + - 'ec2_instance_metric_alarm_deletion_check.changed' + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query_check + + - name: convert it to an object + set_fact: + alarm_info: "{{ alarm_info_query_check.stdout | from_json }}" + + - name: Verify that the alarm was not deleted in check mode using cli + assert: + that: + - 'alarm_info["MetricAlarms"] | length > 0' + + - name: try to remove the alarm + ec2_metric_alarm: + state: absent + name: "{{ alarm_full_name }}" + register: ec2_instance_metric_alarm_deletion + + - name: Verify that the alarm reports deleted/changed + assert: + that: + - 'ec2_instance_metric_alarm_deletion.changed' + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query + + - name: convert it to an object + set_fact: + alarm_info: "{{ alarm_info_query.stdout | from_json }}" + + - name: Verify that the alarm was deleted using cli + assert: + that: + - 'alarm_info["MetricAlarms"] | length == 0' + + - name: create ec2 metric alarm with no unit on ec2 instance + ec2_metric_alarm: + dimensions: + InstanceId: "{{ ec2_instance_results.instances[0].instance_id }}" + state: present + name: "{{ alarm_full_name }}" + metric: "CPUUtilization" + namespace: "AWS/EC2" + treat_missing_data: missing + statistic: Average + comparison: "LessThanOrEqualToThreshold" + threshold: 5.0 + period: 300 + evaluation_periods: 3 + description: "This will alarm when an instance's cpu usage average is lower than 5% for 15 minutes" + register: ec2_instance_metric_alarm_no_unit + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query_no_unit + + - name: convert it to an object + set_fact: + alarm_info_no_unit: "{{ alarm_info_query_no_unit.stdout | from_json }}" + + - name: "verify that an alarm was created" + assert: + that: + - 'ec2_instance_metric_alarm_no_unit.changed' + - 'ec2_instance_metric_alarm_no_unit.alarm_arn' + - 'ec2_instance_metric_alarm_no_unit.statistic == alarm_info_no_unit["MetricAlarms"][0].Statistic' + - 'ec2_instance_metric_alarm_no_unit.name == alarm_info_no_unit["MetricAlarms"][0].AlarmName' + - 'ec2_instance_metric_alarm_no_unit.metric == alarm_info_no_unit["MetricAlarms"][0].MetricName' + - 'ec2_instance_metric_alarm_no_unit.namespace == alarm_info_no_unit["MetricAlarms"][0].Namespace' + - 'ec2_instance_metric_alarm_no_unit.comparison == alarm_info_no_unit["MetricAlarms"][0].ComparisonOperator' + - 'ec2_instance_metric_alarm_no_unit.threshold == alarm_info_no_unit["MetricAlarms"][0].Threshold' + - 'ec2_instance_metric_alarm_no_unit.period == alarm_info_no_unit["MetricAlarms"][0].Period' + - 'alarm_info_no_unit["MetricAlarms"][0].Unit is not defined' + - 'ec2_instance_metric_alarm_no_unit.evaluation_periods == alarm_info_no_unit["MetricAlarms"][0].EvaluationPeriods' + - 'ec2_instance_metric_alarm_no_unit.description == alarm_info_no_unit["MetricAlarms"][0].AlarmDescription' + - 'ec2_instance_metric_alarm_no_unit.treat_missing_data == alarm_info_no_unit["MetricAlarms"][0].TreatMissingData' + + - name: try to remove the alarm + ec2_metric_alarm: + state: absent + name: "{{ alarm_full_name }}" + register: ec2_instance_metric_alarm_deletion_no_unit + + - name: Verify that the alarm reports deleted/changed + assert: + that: + - 'ec2_instance_metric_alarm_deletion_no_unit.changed' + + - name: get info on alarms + command: aws cloudwatch describe-alarms --alarm-names {{ alarm_full_name }} + environment: + AWS_ACCESS_KEY_ID: "{{ aws_access_key }}" + AWS_SECRET_ACCESS_KEY: "{{ aws_secret_key }}" + AWS_SESSION_TOKEN: "{{ security_token | default('') }}" + AWS_DEFAULT_REGION: "{{ aws_region }}" + register: alarm_info_query_no_unit + + - name: convert it to an object + set_fact: + alarm_info_no_unit: "{{ alarm_info_query_no_unit.stdout | from_json }}" + + - name: Verify that the alarm was deleted using cli + assert: + that: + - 'alarm_info_no_unit["MetricAlarms"] | length == 0' + + always: + - name: try to delete the alarm + ec2_metric_alarm: + state: absent + name: "{{ alarm_full_name }}" + ignore_errors: true + + - name: try to stop the ec2 instance + ec2_instance: + instance_ids: "{{ ec2_instance_results.instances[0].instance_id }}" + state: terminated + ignore_errors: true + + - include_tasks: env_cleanup.yml diff --git a/tests/integration/targets/cloudwatch_metric_alarm/vars/main.yml b/tests/integration/targets/cloudwatch_metric_alarm/vars/main.yml new file mode 100644 index 00000000000..ed97d539c09 --- /dev/null +++ b/tests/integration/targets/cloudwatch_metric_alarm/vars/main.yml @@ -0,0 +1 @@ +---