1#!/usr/bin/python 2 3# (c) 2018-2019, NetApp, Inc 4# GNU General Public License v3.0+ 5# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['preview'], 13 'supported_by': 'community'} 14 15DOCUMENTATION = ''' 16module: na_ontap_snapshot 17short_description: NetApp ONTAP manage Snapshots 18extends_documentation_fragment: 19 - netapp.na_ontap 20version_added: '2.6' 21author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> 22description: 23- Create/Modify/Delete ONTAP snapshots 24options: 25 state: 26 description: 27 - If you want to create/modify a snapshot, or delete it. 28 choices: ['present', 'absent'] 29 default: present 30 snapshot: 31 description: 32 Name of the snapshot to be managed. 33 The maximum string length is 256 characters. 34 required: true 35 from_name: 36 description: 37 - Name of the existing snapshot to be renamed to. 38 version_added: '2.8' 39 volume: 40 description: 41 - Name of the volume on which the snapshot is to be created. 42 required: true 43 async_bool: 44 description: 45 - If true, the snapshot is to be created asynchronously. 46 type: bool 47 comment: 48 description: 49 A human readable comment attached with the snapshot. 50 The size of the comment can be at most 255 characters. 51 snapmirror_label: 52 description: 53 A human readable SnapMirror Label attached with the snapshot. 54 Size of the label can be at most 31 characters. 55 ignore_owners: 56 description: 57 - if this field is true, snapshot will be deleted 58 even if some other processes are accessing it. 59 type: bool 60 snapshot_instance_uuid: 61 description: 62 - The 128 bit unique snapshot identifier expressed in the form of UUID. 63 vserver: 64 description: 65 - The Vserver name 66 required: true 67''' 68EXAMPLES = """ 69 - name: create SnapShot 70 tags: 71 - create 72 na_ontap_snapshot: 73 state: present 74 snapshot: "{{ snapshot name }}" 75 volume: "{{ vol name }}" 76 comment: "i am a comment" 77 vserver: "{{ vserver name }}" 78 username: "{{ netapp username }}" 79 password: "{{ netapp password }}" 80 hostname: "{{ netapp hostname }}" 81 - name: delete SnapShot 82 tags: 83 - delete 84 na_ontap_snapshot: 85 state: absent 86 snapshot: "{{ snapshot name }}" 87 volume: "{{ vol name }}" 88 vserver: "{{ vserver name }}" 89 username: "{{ netapp username }}" 90 password: "{{ netapp password }}" 91 hostname: "{{ netapp hostname }}" 92 - name: modify SnapShot 93 tags: 94 - modify 95 na_ontap_snapshot: 96 state: present 97 snapshot: "{{ snapshot name }}" 98 comment: "New comments are great" 99 volume: "{{ vol name }}" 100 vserver: "{{ vserver name }}" 101 username: "{{ netapp username }}" 102 password: "{{ netapp password }}" 103 hostname: "{{ netapp hostname }}" 104""" 105 106RETURN = """ 107""" 108import traceback 109 110from ansible.module_utils.basic import AnsibleModule 111from ansible.module_utils.netapp_module import NetAppModule 112from ansible.module_utils._text import to_native 113import ansible.module_utils.netapp as netapp_utils 114 115HAS_NETAPP_LIB = netapp_utils.has_netapp_lib() 116 117 118class NetAppOntapSnapshot(object): 119 """ 120 Creates, modifies, and deletes a Snapshot 121 """ 122 123 def __init__(self): 124 self.argument_spec = netapp_utils.na_ontap_host_argument_spec() 125 self.argument_spec.update(dict( 126 state=dict(required=False, choices=[ 127 'present', 'absent'], default='present'), 128 from_name=dict(required=False, type='str'), 129 snapshot=dict(required=True, type="str"), 130 volume=dict(required=True, type="str"), 131 async_bool=dict(required=False, type="bool", default=False), 132 comment=dict(required=False, type="str"), 133 snapmirror_label=dict(required=False, type="str"), 134 ignore_owners=dict(required=False, type="bool", default=False), 135 snapshot_instance_uuid=dict(required=False, type="str"), 136 vserver=dict(required=True, type="str"), 137 138 )) 139 self.module = AnsibleModule( 140 argument_spec=self.argument_spec, 141 supports_check_mode=True 142 ) 143 144 self.na_helper = NetAppModule() 145 self.parameters = self.na_helper.set_parameters(self.module.params) 146 147 if HAS_NETAPP_LIB is False: 148 self.module.fail_json( 149 msg="the python NetApp-Lib module is required") 150 else: 151 self.server = netapp_utils.setup_na_ontap_zapi( 152 module=self.module, vserver=self.parameters['vserver']) 153 return 154 155 def get_snapshot(self, snapshot_name=None): 156 """ 157 Checks to see if a snapshot exists or not 158 :return: Return True if a snapshot exists, False if it doesn't 159 """ 160 if snapshot_name is None: 161 snapshot_name = self.parameters['snapshot'] 162 snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter") 163 desired_attr = netapp_utils.zapi.NaElement("desired-attributes") 164 snapshot_info = netapp_utils.zapi.NaElement('snapshot-info') 165 comment = netapp_utils.zapi.NaElement('comment') 166 snapmirror_label = netapp_utils.zapi.NaElement('snapmirror-label') 167 # add more desired attributes that are allowed to be modified 168 snapshot_info.add_child_elem(comment) 169 snapshot_info.add_child_elem(snapmirror_label) 170 desired_attr.add_child_elem(snapshot_info) 171 snapshot_obj.add_child_elem(desired_attr) 172 # compose query 173 query = netapp_utils.zapi.NaElement("query") 174 snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") 175 snapshot_info_obj.add_new_child("name", snapshot_name) 176 snapshot_info_obj.add_new_child("volume", self.parameters['volume']) 177 snapshot_info_obj.add_new_child("vserver", self.parameters['vserver']) 178 query.add_child_elem(snapshot_info_obj) 179 snapshot_obj.add_child_elem(query) 180 result = self.server.invoke_successfully(snapshot_obj, True) 181 return_value = None 182 if result.get_child_by_name('num-records') and \ 183 int(result.get_child_content('num-records')) == 1: 184 attributes_list = result.get_child_by_name('attributes-list') 185 snap_info = attributes_list.get_child_by_name('snapshot-info') 186 return_value = {'comment': snap_info.get_child_content('comment')} 187 if snap_info.get_child_by_name('snapmirror-label'): 188 return_value['snapmirror_label'] = snap_info.get_child_content('snapmirror-label') 189 else: 190 return_value['snapmirror_label'] = None 191 return return_value 192 193 def create_snapshot(self): 194 """ 195 Creates a new snapshot 196 """ 197 snapshot_obj = netapp_utils.zapi.NaElement("snapshot-create") 198 199 # set up required variables to create a snapshot 200 snapshot_obj.add_new_child("snapshot", self.parameters['snapshot']) 201 snapshot_obj.add_new_child("volume", self.parameters['volume']) 202 # Set up optional variables to create a snapshot 203 if self.parameters.get('async_bool'): 204 snapshot_obj.add_new_child("async", str(self.parameters['async_bool'])) 205 if self.parameters.get('comment'): 206 snapshot_obj.add_new_child("comment", self.parameters['comment']) 207 if self.parameters.get('snapmirror_label'): 208 snapshot_obj.add_new_child( 209 "snapmirror-label", self.parameters['snapmirror_label']) 210 try: 211 self.server.invoke_successfully(snapshot_obj, True) 212 except netapp_utils.zapi.NaApiError as error: 213 self.module.fail_json(msg='Error creating snapshot %s: %s' % 214 (self.parameters['snapshot'], to_native(error)), 215 exception=traceback.format_exc()) 216 217 def delete_snapshot(self): 218 """ 219 Deletes an existing snapshot 220 """ 221 snapshot_obj = netapp_utils.zapi.NaElement("snapshot-delete") 222 223 # Set up required variables to delete a snapshot 224 snapshot_obj.add_new_child("snapshot", self.parameters['snapshot']) 225 snapshot_obj.add_new_child("volume", self.parameters['volume']) 226 # set up optional variables to delete a snapshot 227 if self.parameters.get('ignore_owners'): 228 snapshot_obj.add_new_child("ignore-owners", str(self.parameters['ignore_owners'])) 229 if self.parameters.get('snapshot_instance_uuid'): 230 snapshot_obj.add_new_child("snapshot-instance-uuid", self.parameters['snapshot_instance_uuid']) 231 try: 232 self.server.invoke_successfully(snapshot_obj, True) 233 except netapp_utils.zapi.NaApiError as error: 234 self.module.fail_json(msg='Error deleting snapshot %s: %s' % 235 (self.parameters['snapshot'], to_native(error)), 236 exception=traceback.format_exc()) 237 238 def modify_snapshot(self): 239 """ 240 Modify an existing snapshot 241 :return: 242 """ 243 snapshot_obj = netapp_utils.zapi.NaElement("snapshot-modify-iter") 244 # Create query object, this is the existing object 245 query = netapp_utils.zapi.NaElement("query") 246 snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") 247 snapshot_info_obj.add_new_child("name", self.parameters['snapshot']) 248 snapshot_info_obj.add_new_child("vserver", self.parameters['vserver']) 249 query.add_child_elem(snapshot_info_obj) 250 snapshot_obj.add_child_elem(query) 251 252 # this is what we want to modify in the snapshot object 253 attributes = netapp_utils.zapi.NaElement("attributes") 254 snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info") 255 snapshot_info_obj.add_new_child("name", self.parameters['snapshot']) 256 if self.parameters.get('comment'): 257 snapshot_info_obj.add_new_child("comment", self.parameters['comment']) 258 if self.parameters.get('snapmirror_label'): 259 snapshot_info_obj.add_new_child("snapmirror-label", self.parameters['snapmirror_label']) 260 attributes.add_child_elem(snapshot_info_obj) 261 snapshot_obj.add_child_elem(attributes) 262 try: 263 self.server.invoke_successfully(snapshot_obj, True) 264 except netapp_utils.zapi.NaApiError as error: 265 self.module.fail_json(msg='Error modifying snapshot %s: %s' % 266 (self.parameters['snapshot'], to_native(error)), 267 exception=traceback.format_exc()) 268 269 def rename_snapshot(self): 270 """ 271 Rename the snapshot 272 """ 273 snapshot_obj = netapp_utils.zapi.NaElement("snapshot-rename") 274 275 # set up required variables to rename a snapshot 276 snapshot_obj.add_new_child("current-name", self.parameters['from_name']) 277 snapshot_obj.add_new_child("new-name", self.parameters['snapshot']) 278 snapshot_obj.add_new_child("volume", self.parameters['volume']) 279 try: 280 self.server.invoke_successfully(snapshot_obj, True) 281 except netapp_utils.zapi.NaApiError as error: 282 self.module.fail_json(msg='Error renaming snapshot %s to %s: %s' % 283 (self.parameters['from_name'], self.parameters['snapshot'], to_native(error)), 284 exception=traceback.format_exc()) 285 286 def apply(self): 287 """ 288 Check to see which play we should run 289 """ 290 current = self.get_snapshot() 291 netapp_utils.ems_log_event("na_ontap_snapshot", self.server) 292 rename, cd_action = None, None 293 modify = {} 294 if self.parameters.get('from_name'): 295 current_old_name = self.get_snapshot(self.parameters['from_name']) 296 rename = self.na_helper.is_rename_action(current_old_name, current) 297 modify = self.na_helper.get_modified_attributes(current_old_name, self.parameters) 298 else: 299 cd_action = self.na_helper.get_cd_action(current, self.parameters) 300 if cd_action is None: 301 modify = self.na_helper.get_modified_attributes(current, self.parameters) 302 if self.na_helper.changed: 303 if self.module.check_mode: 304 pass 305 else: 306 if rename: 307 self.rename_snapshot() 308 if cd_action == 'create': 309 self.create_snapshot() 310 elif cd_action == 'delete': 311 self.delete_snapshot() 312 elif modify: 313 self.modify_snapshot() 314 self.module.exit_json(changed=self.na_helper.changed) 315 316 317def main(): 318 """ 319 Creates, modifies, and deletes a Snapshot 320 """ 321 obj = NetAppOntapSnapshot() 322 obj.apply() 323 324 325if __name__ == '__main__': 326 main() 327