Skip to content

Commit

Permalink
Add metrics and extended_statistic keys to cloudwatch module (ansible…
Browse files Browse the repository at this point in the history
…-collections#1133)

Add metrics and extended_statistic keys to cloudwatch module

Signed-off-by: GomathiselviS [email protected]
To support https://issues.redhat.com/browse/ACA-638 , a new key metric ( a list of dicts) is added to the cloudwatch module
SUMMARY


ISSUE TYPE


Feature Pull Request

COMPONENT NAME

cloudwatch.py
ADDITIONAL INFORMATION

Reviewed-by: Bikouo Aubin <None>
Reviewed-by: Gonéri Le Bouder <[email protected]>
Reviewed-by: Mike Graves <[email protected]>
Reviewed-by: GomathiselviS <None>
Reviewed-by: Alina Buzachis <None>
  • Loading branch information
GomathiselviS authored Oct 17, 2022
1 parent 9401932 commit b9ee1f6
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 14 deletions.
3 changes: 3 additions & 0 deletions changelogs/fragments/1133-add_metrics_cloudwatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- cloudwatch - Add metrics and extended_statistic keys to cloudwatch module (https://github.com/ansible-collections/amazon.aws/pull/1133).
167 changes: 153 additions & 14 deletions plugins/modules/cloudwatch_metric_alarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,93 @@
- Unique name for the alarm.
required: true
type: str
metric:
metric_name:
description:
- Name of the monitored metric (e.g. C(CPUUtilization)).
- Metric must already exist.
required: false
type: str
aliases: ['metric']
metrics:
description:
- An array of MetricDataQuery structures that enable
you to create an alarm based on the result of a metric math expression.
type: list
required: false
version_added: "5.1.0"
elements: dict
suboptions:
id:
description:
- A short name used to tie this object to the results in the response.
type: str
required: true
metric_stat:
description: The metric to be returned, along with statistics, period, and units.
type: dict
required: false
suboptions:
metric:
description: The metric to return, including the metric name, namespace, and dimensions.
type: dict
required: false
suboptions:
namespace:
description: The namespace of the metric.
type: str
required: false
metric_name:
description: The name of the metric.
type: str
required: True
dimensions:
description: a name/value pair that is part of the identity of a metric.
type: list
elements: dict
required: false
suboptions:
name:
description: The name of the dimension.
type: str
required: True
value:
description: The value of the dimension.
type: str
required: True
period:
description: The granularity, in seconds, of the returned data points.
type: int
required: True
stat:
description: The statistic to return. It can include any CloudWatch statistic or extended statistic.
type: str
required: True
unit:
description: Unit to use when storing the metric.
type: str
required: false
expression:
description:
- This field can contain either a Metrics Insights query,
or a metric math expression to be performed on the returned data.
type: str
required: false
label:
description: A human-readable label for this metric or expression.
type: str
required: false
return_data:
description: This option indicates whether to return the timestamps and raw data values of this metric.
type: bool
required: false
period:
description: The granularity, in seconds, of the returned data points.
type: int
required: false
account_id:
description: The ID of the account where the metrics are located, if this is a cross-account alarm.
type: str
required: false
namespace:
description:
- Name of the appropriate namespace (C(AWS/EC2), C(System/Linux), etc.), which determines the category it will appear under in CloudWatch.
Expand All @@ -60,6 +141,11 @@
required: false
choices: ['SampleCount','Average','Sum','Minimum','Maximum']
type: str
extended_statistic:
description: The percentile statistic for the metric specified in the metric name.
type: str
required: false
version_added: "5.1.0"
comparison:
description:
- Determines how the threshold value is compared
Expand Down Expand Up @@ -171,7 +257,7 @@
state: present
region: ap-southeast-2
name: "cpu-low"
metric: "CPUUtilization"
metric_name: "CPUUtilization"
namespace: "AWS/EC2"
statistic: Average
comparison: "LessThanOrEqualToThreshold"
Expand All @@ -183,6 +269,26 @@
dimensions: {'InstanceId':'i-XXX'}
alarm_actions: ["action1","action2"]
- name: create alarm with metrics
amazon.aws.cloudwatch_metric_alarm:
state: present
region: ap-southeast-2
name: "cpu-low"
metrics:
- id: 'CPU'
metric_stat:
metric:
dimensions:
name: "InstanceId"
value: "i-xx"
metric_name: "CPUUtilization"
namespace: "AWS/EC2"
period: "300"
stat: "Average"
unit: "Percent"
return_data: False
alarm_actions: ["action1","action2"]
- name: Create an alarm to recover a failed instance
amazon.aws.cloudwatch_metric_alarm:
state: present
Expand All @@ -205,18 +311,19 @@
from botocore.exceptions import ClientError
except ImportError:
pass # protected by AnsibleAWSModule

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict


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 params.get('Dimensions'):
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:
Expand All @@ -236,7 +343,7 @@ def create_metric_alarm(connection, module, params):

for key in ['ActionsEnabled', 'StateValue', 'StateReason',
'StateReasonData', 'StateUpdatedTimestamp',
'AlarmArn', 'AlarmConfigurationUpdatedTimestamp']:
'AlarmArn', 'AlarmConfigurationUpdatedTimestamp', 'Metrics']:
alarm.pop(key, None)
if alarm != params:
changed = True
Expand All @@ -256,6 +363,11 @@ def create_metric_alarm(connection, module, params):

result = {}
if alarms['MetricAlarms']:
if alarms['MetricAlarms'][0].get('Metrics'):
metric_list = []
for metric_element in alarms['MetricAlarms'][0]['Metrics']:
metric_list.append(camel_dict_to_snake_dict(metric_element))
alarms['MetricAlarms'][0]['Metrics'] = metric_list
result = alarms['MetricAlarms'][0]

module.exit_json(changed=changed,
Expand All @@ -270,6 +382,8 @@ def create_metric_alarm(connection, module, params):
insufficient_data_actions=result.get('InsufficientDataActions'),
last_updated=result.get('AlarmConfigurationUpdatedTimestamp'),
metric=result.get('MetricName'),
metric_name=result.get('MetricName'),
metrics=result.get('Metrics'),
namespace=result.get('Namespace'),
ok_actions=result.get('OKActions'),
period=result.get('Period'),
Expand Down Expand Up @@ -298,7 +412,7 @@ def delete_metric_alarm(connection, module, params):
def main():
argument_spec = dict(
name=dict(required=True, type='str'),
metric=dict(type='str'),
metric_name=dict(type='str', aliases=['metric']),
namespace=dict(type='str'),
statistic=dict(type='str', choices=['SampleCount', 'Average', 'Sum', 'Minimum', 'Maximum']),
comparison=dict(type='str', choices=['LessThanOrEqualToThreshold', 'LessThanThreshold', 'GreaterThanThreshold',
Expand All @@ -311,22 +425,39 @@ def main():
'Terabytes/Second', 'Bits/Second', 'Kilobits/Second', 'Megabits/Second', 'Gigabits/Second',
'Terabits/Second', 'Count/Second', 'None']),
evaluation_periods=dict(type='int'),
extended_statistic=dict(type='str'),
description=dict(type='str'),
dimensions=dict(type='dict', default={}),
dimensions=dict(type='dict'),
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']),
metrics=dict(type='list', elements='dict', default=[]),
)

module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True)
mutually_exclusive = [
['metric_name', 'metrics'],
['dimensions', 'metrics'],
['period', 'metrics'],
['namespace', 'metrics'],
['statistic', 'metrics'],
['extended_statistic', 'metrics'],
['unit', 'metrics'],
['statistic', 'extended_statistic'],
]

module = AnsibleAWSModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True,
)

state = module.params.get('state')

params = dict()
params['AlarmName'] = module.params.get('name')
params['MetricName'] = module.params.get('metric')
params['MetricName'] = module.params.get('metric_name')
params['Namespace'] = module.params.get('namespace')
params['Statistic'] = module.params.get('statistic')
params['ComparisonOperator'] = module.params.get('comparison')
Expand All @@ -341,6 +472,14 @@ def main():
params['InsufficientDataActions'] = module.params.get('insufficient_data_actions', [])
params['OKActions'] = module.params.get('ok_actions', [])
params['TreatMissingData'] = module.params.get('treat_missing_data')
if module.params.get('metrics'):
params['Metrics'] = snake_dict_to_camel_dict(module.params['metrics'], capitalize_first=True)
if module.params.get('extended_statistic'):
params['ExtendedStatistic'] = module.params.get('extended_statistic')

for key, value in list(params.items()):
if value is None:
del params[key]

connection = module.client('cloudwatch')

Expand Down
106 changes: 106 additions & 0 deletions tests/integration/targets/cloudwatch_metric_alarm/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,75 @@
- 'ec2_instance_metric_alarm_no_unit.description == alarm_info_no_unit.metric_alarms[0].alarm_description'
- 'ec2_instance_metric_alarm_no_unit.treat_missing_data == alarm_info_no_unit.metric_alarms[0].treat_missing_data'

- 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
amazon.aws.cloudwatch_metric_alarm_info:
alarm_names:
- "{{ alarm_full_name }}"
register: alarm_info

- name: Verify that the alarm was deleted using cli
assert:
that:
- 'alarm_info.metric_alarms | length == 0'

- name: create ec2 metric alarm with metrics
ec2_metric_alarm:
state: present
name: '{{ alarm_full_name }}'
treat_missing_data: missing
comparison: LessThanOrEqualToThreshold
threshold: 5.0
evaluation_periods: 3
description: This will alarm when an instance's cpu usage average is lower than
5% for 15 minutes
metrics:
- id: cpu
metric_stat:
metric:
dimensions:
- name: "InstanceId"
value: "{{ ec2_instance_results.instances[0].instance_id }}"
metric_name: "CPUUtilization"
namespace: "AWS/EC2"
period: 300
stat: "Average"
unit: "Percent"
return_data: true
register: ec2_instance_metric_alarm_metrics

- name: get info on alarms
amazon.aws.cloudwatch_metric_alarm_info:
alarm_names:
- "{{ alarm_full_name }}"
register: alarm_info_metrics

- name: verify that an alarm was created
assert:
that:
- ec2_instance_metric_alarm_metrics.changed
- ec2_instance_metric_alarm_metrics.alarm_arn
- 'ec2_instance_metric_alarm_metrics.metrics[0].metric_stat.stat == alarm_info_metrics.metric_alarms[0].metrics[0].metric_stat.stat'
- 'ec2_instance_metric_alarm_metrics.metrics[0].metric_stat.metric.namespace == alarm_info_metrics.metric_alarms[0].metrics[0].metric_stat.metric.namespace'
- 'ec2_instance_metric_alarm_metrics.metrics[0].metric_stat.metric.metric_name == alarm_info_metrics.metric_alarms[0].metrics[0].metric_stat.metric.metric_name'
- 'ec2_instance_metric_alarm_metrics.metrics[0].metric_stat.metric.dimensions[0].name == alarm_info_metrics.metric_alarms[0].metrics[0].metric_stat.metric.dimensions[0].name'
- 'ec2_instance_metric_alarm_metrics.metrics[0].metric_stat.metric.dimensions[0].value == alarm_info_metrics.metric_alarms[0].metrics[0].metric_stat.metric.dimensions[0].value'
- 'ec2_instance_metric_alarm_metrics.metrics[0].id == alarm_info_metrics.metric_alarms[0].metrics[0].id'
- 'ec2_instance_metric_alarm_metrics.metrics[0].metric_stat.period == alarm_info_metrics.metric_alarms[0].metrics[0].metric_stat.period'
- 'ec2_instance_metric_alarm_metrics.metrics[0].metric_stat.unit == alarm_info_metrics.metric_alarms[0].metrics[0].metric_stat.unit'
- 'ec2_instance_metric_alarm_metrics.metrics[0].return_data == alarm_info_metrics.metric_alarms[0].metrics[0].return_data'


- name: try to remove the alarm
ec2_metric_alarm:
state: absent
Expand All @@ -396,6 +465,43 @@
that:
- 'alarm_info_no_unit.metric_alarms | length == 0'

- name: create ec2 metric alarm by providing mutually exclusive values
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
metrics:
- id: cpu
metric_stat:
metric:
dimensions:
- name: "InstanceId"
value: "{{ ec2_instance_results.instances[0].instance_id }}"
metric_name: "CPUUtilization"
namespace: "AWS/EC2"
period: 300
stat: "Average"
unit: "Percent"
return_data: true
register: ec2_instance_metric_mutually_exclusive
ignore_errors: true

- assert:
that:
- ec2_instance_metric_mutually_exclusive.failed
- '"parameters are mutually exclusive" in ec2_instance_metric_mutually_exclusive.msg'

always:
- name: try to delete the alarm
ec2_metric_alarm:
Expand Down

0 comments on commit b9ee1f6

Please sign in to comment.