-
Notifications
You must be signed in to change notification settings - Fork 23.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Define module for managing E-Series email alerts #42643
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,280 @@ | ||
#!/usr/bin/python | ||
|
||
# (c) 2018, NetApp, Inc | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
__metaclass__ = type | ||
|
||
ANSIBLE_METADATA = {'metadata_version': '1.1', | ||
'status': ['preview'], | ||
'supported_by': 'community'} | ||
|
||
DOCUMENTATION = """ | ||
--- | ||
module: netapp_e_alerts | ||
short_description: NetApp E-Series manage email notification settings | ||
description: | ||
- Certain E-Series systems have the capability to send email notifications on potentially critical events. | ||
- This module will allow the owner of the system to specify email recipients for these messages. | ||
version_added: '2.7' | ||
author: Michael Price (@lmprice) | ||
extends_documentation_fragment: | ||
- netapp.eseries | ||
options: | ||
state: | ||
description: | ||
- Enable/disable the sending of email-based alerts. | ||
default: enabled | ||
required: false | ||
choices: | ||
- enabled | ||
- disabled | ||
server: | ||
description: | ||
- A fully qualified domain name, IPv4 address, or IPv6 address of a mail server. | ||
- To use a fully qualified domain name, you must configure a DNS server on both controllers using | ||
M(netapp_e_mgmt_interface). | ||
- Required when I(state=enabled). | ||
required: no | ||
sender: | ||
description: | ||
- This is the sender that the recipient will see. It doesn't necessarily need to be a valid email account. | ||
- Required when I(state=enabled). | ||
required: no | ||
contact: | ||
description: | ||
- Allows the owner to specify some free-form contact information to be included in the emails. | ||
- This is typically utilized to provide a contact phone number. | ||
required: no | ||
recipients: | ||
description: | ||
- The email addresses that will receive the email notifications. | ||
- Required when I(state=enabled). | ||
required: no | ||
test: | ||
description: | ||
- When a change is detected in the configuration, a test email will be sent. | ||
- This may take a few minutes to process. | ||
- Only applicable if I(state=enabled). | ||
default: no | ||
type: bool | ||
log_path: | ||
description: | ||
- Path to a file on the Ansible control node to be used for debug logging | ||
required: no | ||
notes: | ||
- Check mode is supported. | ||
- Alertable messages are a subset of messages shown by the Major Event Log (MEL), of the storage-system. Examples | ||
of alertable messages include drive failures, failed controllers, loss of redundancy, and other warning/critical | ||
events. | ||
- This API is currently only supported with the Embedded Web Services API v2.0 and higher. | ||
""" | ||
|
||
EXAMPLES = """ | ||
- name: Enable email-based alerting | ||
netapp_e_alerts: | ||
state: enabled | ||
sender: noreply@example.com | ||
server: mail@example.com | ||
contact: "Phone: 1-555-555-5555" | ||
recipients: | ||
- name1@example.com | ||
- name2@example.com | ||
api_url: "10.1.1.1:8443" | ||
api_username: "admin" | ||
api_password: "myPass" | ||
|
||
- name: Disable alerting | ||
netapp_e_alerts: | ||
state: disabled | ||
api_url: "10.1.1.1:8443" | ||
api_username: "admin" | ||
api_password: "myPass" | ||
""" | ||
|
||
RETURN = """ | ||
msg: | ||
description: Success message | ||
returned: on success | ||
type: string | ||
sample: The settings have been updated. | ||
""" | ||
|
||
import json | ||
import logging | ||
from pprint import pformat | ||
import re | ||
|
||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible.module_utils.netapp import request, eseries_host_argument_spec | ||
from ansible.module_utils._text import to_native | ||
|
||
HEADERS = { | ||
"Content-Type": "application/json", | ||
"Accept": "application/json", | ||
} | ||
|
||
|
||
class Alerts(object): | ||
def __init__(self): | ||
argument_spec = eseries_host_argument_spec() | ||
argument_spec.update(dict( | ||
state=dict(type='str', required=False, default='enabled', | ||
choices=['enabled', 'disabled']), | ||
server=dict(type='str', required=False, ), | ||
sender=dict(type='str', required=False, ), | ||
contact=dict(type='str', required=False, ), | ||
recipients=dict(type='list', required=False, ), | ||
test=dict(type='bool', required=False, default=False, ), | ||
log_path=dict(type='str', required=False), | ||
)) | ||
|
||
required_if = [ | ||
['state', 'enabled', ['server', 'sender', 'recipients']] | ||
] | ||
|
||
self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if) | ||
args = self.module.params | ||
self.alerts = args['state'] == 'enabled' | ||
self.server = args['server'] | ||
self.sender = args['sender'] | ||
self.contact = args['contact'] | ||
self.recipients = args['recipients'] | ||
self.test = args['test'] | ||
|
||
self.ssid = args['ssid'] | ||
self.url = args['api_url'] | ||
self.creds = dict(url_password=args['api_password'], | ||
validate_certs=args['validate_certs'], | ||
url_username=args['api_username'], ) | ||
|
||
self.check_mode = self.module.check_mode | ||
|
||
log_path = args['log_path'] | ||
|
||
# logging setup | ||
self._logger = logging.getLogger(self.__class__.__name__) | ||
|
||
if log_path: | ||
logging.basicConfig( | ||
level=logging.DEBUG, filename=log_path, filemode='w', | ||
format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s') | ||
|
||
if not self.url.endswith('/'): | ||
self.url += '/' | ||
|
||
# Very basic validation on email addresses: xx@yy.zz | ||
email = re.compile(r"[^@]+@[^@]+\.[^@]+") | ||
|
||
if self.sender and not email.match(self.sender): | ||
self.module.fail_json(msg="The sender (%s) provided is not a valid email address." % self.sender) | ||
|
||
if self.recipients is not None: | ||
for recipient in self.recipients: | ||
if not email.match(recipient): | ||
self.module.fail_json(msg="The recipient (%s) provided is not a valid email address." % recipient) | ||
|
||
if len(self.recipients) < 1: | ||
self.module.fail_json(msg="At least one recipient address must be specified.") | ||
|
||
def get_configuration(self): | ||
try: | ||
(rc, result) = request(self.url + 'storage-systems/%s/device-alerts' % self.ssid, headers=HEADERS, | ||
**self.creds) | ||
self._logger.info("Current config: %s", pformat(result)) | ||
return result | ||
|
||
except Exception as err: | ||
self.module.fail_json(msg="Failed to retrieve the alerts configuration! Array Id [%s]. Error [%s]." | ||
% (self.ssid, to_native(err))) | ||
|
||
def update_configuration(self): | ||
config = self.get_configuration() | ||
update = False | ||
body = dict() | ||
|
||
if self.alerts: | ||
body = dict(alertingEnabled=True) | ||
if not config['alertingEnabled']: | ||
update = True | ||
|
||
body.update(emailServerAddress=self.server) | ||
if config['emailServerAddress'] != self.server: | ||
update = True | ||
|
||
body.update(additionalContactInformation=self.contact, sendAdditionalContactInformation=True) | ||
if self.contact and (self.contact != config['additionalContactInformation'] | ||
or not config['sendAdditionalContactInformation']): | ||
update = True | ||
|
||
body.update(emailSenderAddress=self.sender) | ||
if config['emailSenderAddress'] != self.sender: | ||
update = True | ||
|
||
self.recipients.sort() | ||
if config['recipientEmailAddresses']: | ||
config['recipientEmailAddresses'].sort() | ||
|
||
body.update(recipientEmailAddresses=self.recipients) | ||
if config['recipientEmailAddresses'] != self.recipients: | ||
update = True | ||
|
||
elif config['alertingEnabled']: | ||
body = dict(alertingEnabled=False) | ||
update = True | ||
|
||
self._logger.debug(pformat(body)) | ||
|
||
if update and not self.check_mode: | ||
try: | ||
(rc, result) = request(self.url + 'storage-systems/%s/device-alerts' % self.ssid, method='POST', | ||
data=json.dumps(body), headers=HEADERS, **self.creds) | ||
# This is going to catch cases like a connection failure | ||
except Exception as err: | ||
self.module.fail_json(msg="We failed to set the storage-system name! Array Id [%s]. Error [%s]." | ||
% (self.ssid, to_native(err))) | ||
return update | ||
|
||
def send_test_email(self): | ||
"""Send a test email to verify that the provided configuration is valid and functional.""" | ||
if not self.check_mode: | ||
try: | ||
(rc, result) = request(self.url + 'storage-systems/%s/device-alerts/alert-email-test' % self.ssid, | ||
timeout=300, method='POST', headers=HEADERS, **self.creds) | ||
|
||
if result['response'] != 'emailSentOK': | ||
self.module.fail_json(msg="The test email failed with status=[%s]! Array Id [%s]." | ||
% (result['response'], self.ssid)) | ||
|
||
# This is going to catch cases like a connection failure | ||
except Exception as err: | ||
self.module.fail_json(msg="We failed to send the test email! Array Id [%s]. Error [%s]." | ||
% (self.ssid, to_native(err))) | ||
|
||
def update(self): | ||
update = self.update_configuration() | ||
|
||
if self.test and update: | ||
self._logger.info("An update was detected and test=True, running a test.") | ||
self.send_test_email() | ||
|
||
if self.alerts: | ||
msg = 'Alerting has been enabled using server=%s, sender=%s.' % (self.server, self.sender) | ||
else: | ||
msg = 'Alerting has been disabled.' | ||
|
||
self.module.exit_json(msg=msg, changed=update, ) | ||
|
||
def __call__(self, *args, **kwargs): | ||
self.update() | ||
|
||
|
||
def main(): | ||
alerts = Alerts() | ||
alerts() | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# This test is not enabled by default, but can be utilized by defining required variables in integration_config.yml | ||
# Example integration_config.yml: | ||
# --- | ||
#netapp_e_api_host: 10.113.1.111:8443 | ||
#netapp_e_api_username: admin | ||
#netapp_e_api_password: myPass | ||
#netapp_e_ssid: 1 | ||
|
||
unsupported | ||
netapp/eseries |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
- include_tasks: run.yml |
39 changes: 39 additions & 0 deletions
39
test/integration/targets/netapp_eseries_alerts/tasks/run.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Test code for the netapp_e_iscsi_interface module | ||
# (c) 2018, NetApp, Inc | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
- name: NetApp Test ASUP module | ||
fail: | ||
msg: 'Please define netapp_e_api_username, netapp_e_api_password, netapp_e_api_host, and netapp_e_ssid.' | ||
when: netapp_e_api_username is undefined or netapp_e_api_password is undefined | ||
or netapp_e_api_host is undefined or netapp_e_ssid is undefined | ||
vars: | ||
defaults: &defaults | ||
api_url: "https://{{ netapp_e_api_host }}/devmgr/v2" | ||
api_username: "{{ netapp_e_api_username }}" | ||
api_password: "{{ netapp_e_api_password }}" | ||
ssid: "{{ netapp_e_ssid }}" | ||
validate_certs: no | ||
state: enabled | ||
server: mail.example.com | ||
sender: noreply@example.com | ||
recipients: | ||
- noreply@example.com | ||
|
||
- name: set default vars | ||
set_fact: | ||
vars: *defaults | ||
|
||
- name: Set the initial alerting settings | ||
netapp_e_alerts: | ||
<<: *defaults | ||
register: result | ||
|
||
- name: Validate the idempotency of the module | ||
netapp_e_alerts: | ||
<<: *defaults | ||
register: result | ||
|
||
- name: Ensure we still have the same settings, but had no change | ||
assert: | ||
that: not result.changed |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this just be enabled/disabled?
What's present/absent mean?