diff --git a/lib/ansible/modules/storage/netapp/na_ontap_cg_snapshot.py b/lib/ansible/modules/storage/netapp/na_ontap_cg_snapshot.py new file mode 100644 index 0000000000..f64fbe935e --- /dev/null +++ b/lib/ansible/modules/storage/netapp/na_ontap_cg_snapshot.py @@ -0,0 +1,215 @@ +#!/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 = ''' +short_description: Create consistency group snapshot +author: NetApp Ansible Team (ng-ansibleteam@netapp.com) +description: + - Create consistency group snapshot for ONTAP volumes. +extends_documentation_fragment: + - netapp.na_ontap +module: na_ontap_cg_snapshot +options: + state: + description: + - If you want to create a snapshot. + default: present + vserver: + description: + - Name of the vserver. + volumes: + description: + - A list of volumes in this filer that is part of this CG operation. + snapshot: + description: + - The provided name of the snapshot that is created in each volume. + timeout: + description: + - Timeout selector. + choices: ['urgent', 'medium', 'relaxed'] + default: medium + snapmirror_label: + description: + - A human readable SnapMirror label to be attached with the consistency group snapshot copies. +version_added: "2.7" + +''' + +EXAMPLES = """ + - name: + na_ontap_cg_snapshot: + state: present + vserver: vserver_name + snapshot: snapshot name + volume: vol_name + username: "{{ netapp username }}" + password: "{{ netapp password }}" + hostname: "{{ netapp hostname }}" +""" + +RETURN = """ +""" + +import traceback + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +import ansible.module_utils.netapp as netapp_utils + +HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() + + +class NetAppONTAPCGSnapshot(object): + """ + Methods to create CG snapshots + """ + + def __init__(self): + self.argument_spec = netapp_utils.na_ontap_host_argument_spec() + self.argument_spec.update(dict( + state=dict(required=False, default='present'), + vserver=dict(required=True, type='str'), + volumes=dict(required=True, type='list'), + snapshot=dict(required=True, type='str'), + timeout=dict(required=False, type='str', choices=[ + 'urgent', 'medium', 'relaxed'], default='medium'), + snapmirror_label=dict(required=False, type='str') + )) + + self.module = AnsibleModule( + argument_spec=self.argument_spec, + supports_check_mode=True + ) + + parameters = self.module.params + + # set up variables + self.state = parameters['state'] + self.vserver = parameters['vserver'] + self.volumes = parameters['volumes'] + self.snapshot = parameters['snapshot'] + self.timeout = parameters['timeout'] + self.snapmirror_label = parameters['snapmirror_label'] + self.cgid = None + + if HAS_NETAPP_LIB is False: + self.module.fail_json( + msg="the python NetApp-Lib module is required") + else: + self.server = netapp_utils.setup_na_ontap_zapi( + module=self.module, vserver=self.vserver) + + def does_snapshot_exist(self, volume): + """ + This is duplicated from na_ontap_snapshot + Checks to see if a snapshot exists or not + :return: Return True if a snapshot exists, false if it dosn't + """ + # TODO: Remove this method and import snapshot module and + # call get after re-factoring __init__ across all the modules + # we aren't importing now, since __init__ does a lot of Ansible setup + snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter") + desired_attr = netapp_utils.zapi.NaElement("desired-attributes") + snapshot_info = netapp_utils.zapi.NaElement('snapshot-info') + comment = netapp_utils.zapi.NaElement('comment') + # add more desired attributes that are allowed to be modified + snapshot_info.add_child_elem(comment) + desired_attr.add_child_elem(snapshot_info) + snapshot_obj.add_child_elem(desired_attr) + # compose query + query = netapp_utils.zapi.NaElement("query") + snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") + snapshot_info_obj.add_new_child("name", self.snapshot) + snapshot_info_obj.add_new_child("volume", volume) + query.add_child_elem(snapshot_info_obj) + snapshot_obj.add_child_elem(query) + result = self.server.invoke_successfully(snapshot_obj, True) + return_value = None + if result.get_child_by_name('num-records') and \ + int(result.get_child_content('num-records')) == 1: + attributes_list = result.get_child_by_name('attributes-list') + snap_info = attributes_list.get_child_by_name('snapshot-info') + return_value = {'comment': snap_info.get_child_content('comment')} + return return_value + + def cgcreate(self): + """ + Calls cg-start and cg-commit (when cg-start succeeds) + """ + started = self.cg_start() + if started: + if self.cgid is not None: + self.cg_commit() + else: + self.module.fail_json(msg="Error fetching CG ID for CG commit %s" % self.snapshot, + exception=traceback.format_exc()) + return started + + def cg_start(self): + """ + For the given list of volumes, creates cg-snapshot + """ + snapshot_started = False + cgstart = netapp_utils.zapi.NaElement("cg-start") + cgstart.add_new_child("snapshot", self.snapshot) + cgstart.add_new_child("timeout", self.timeout) + volume_list = netapp_utils.zapi.NaElement("volumes") + cgstart.add_child_elem(volume_list) + for vol in self.volumes: + snapshot_exists = self.does_snapshot_exist(vol) + if snapshot_exists is None: + snapshot_started = True + volume_list.add_new_child("volume-name", vol) + if snapshot_started: + if self.snapmirror_label: + cgstart.add_new_child("snapmirror-label", + self.snapmirror_label) + try: + cgresult = self.server.invoke_successfully( + cgstart, enable_tunneling=True) + if cgresult.has_attr('cg-id'): + self.cgid = cgresult['cg-id'] + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error creating CG snapshot %s: %s" % + (self.snapshot, to_native(error)), + exception=traceback.format_exc()) + return snapshot_started + + def cg_commit(self): + """ + When cg-start is successful, performs a cg-commit with the cg-id + """ + cgcommit = netapp_utils.zapi.NaElement.create_node_with_children( + 'cg-commit', **{'cg-id': self.cgid}) + try: + self.server.invoke_successfully(cgcommit, + enable_tunneling=True) + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg="Error committing CG snapshot %s: %s" % + (self.snapshot, to_native(error)), + exception=traceback.format_exc()) + + def apply(self): + '''Applies action from playbook''' + netapp_utils.ems_log_event("na_ontap_cg_snapshot", self.server) + changed = self.cgcreate() + self.module.exit_json(changed=changed) + + +def main(): + '''Execute action from playbook''' + cg_obj = NetAppONTAPCGSnapshot() + cg_obj.apply() + + +if __name__ == '__main__': + main()