1#!/usr/local/bin/python3.8 2# (c) 2018, NetApp, Inc 3# GNU General Public License v3.0+ (see COPYING or 4# https://www.gnu.org/licenses/gpl-3.0.txt) 5 6''' 7Element OS Software Snapshot Manager 8''' 9from __future__ import absolute_import, division, print_function 10__metaclass__ = type 11 12 13ANSIBLE_METADATA = {'metadata_version': '1.1', 14 'status': ['preview'], 15 'supported_by': 'certified'} 16 17 18DOCUMENTATION = ''' 19 20module: na_elementsw_snapshot 21 22short_description: NetApp Element Software Manage Snapshots 23extends_documentation_fragment: 24 - netapp.elementsw.netapp.solidfire 25version_added: 2.7.0 26author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com> 27description: 28 - Create, Modify or Delete Snapshot on Element OS Cluster. 29 30options: 31 name: 32 description: 33 - Name of new snapshot create. 34 - If unspecified, date and time when the snapshot was taken is used. 35 type: str 36 37 state: 38 description: 39 - Whether the specified snapshot should exist or not. 40 choices: ['present', 'absent'] 41 default: 'present' 42 type: str 43 44 src_volume_id: 45 description: 46 - ID or Name of active volume. 47 required: true 48 type: str 49 50 account_id: 51 description: 52 - Account ID or Name of Parent/Source Volume. 53 required: true 54 type: str 55 56 retention: 57 description: 58 - Retention period for the snapshot. 59 - Format is 'HH:mm:ss'. 60 type: str 61 62 src_snapshot_id: 63 description: 64 - ID or Name of an existing snapshot. 65 - Required when C(state=present), to modify snapshot properties. 66 - Required when C(state=present), to create snapshot from another snapshot in the volume. 67 - Required when C(state=absent), to delete snapshot. 68 type: str 69 70 enable_remote_replication: 71 description: 72 - Flag, whether to replicate the snapshot created to a remote replication cluster. 73 - To enable specify 'true' value. 74 type: bool 75 76 snap_mirror_label: 77 description: 78 - Label used by SnapMirror software to specify snapshot retention policy on SnapMirror endpoint. 79 type: str 80 81 expiration_time: 82 description: 83 - The date and time (format ISO 8601 date string) at which this snapshot will expire. 84 type: str 85''' 86 87EXAMPLES = """ 88 - name: Create snapshot 89 tags: 90 - elementsw_create_snapshot 91 na_elementsw_snapshot: 92 hostname: "{{ elementsw_hostname }}" 93 username: "{{ elementsw_username }}" 94 password: "{{ elementsw_password }}" 95 state: present 96 src_volume_id: 118 97 account_id: sagarsh 98 name: newsnapshot-1 99 100 - name: Modify Snapshot 101 tags: 102 - elementsw_modify_snapshot 103 na_elementsw_snapshot: 104 hostname: "{{ elementsw_hostname }}" 105 username: "{{ elementsw_username }}" 106 password: "{{ elementsw_password }}" 107 state: present 108 src_volume_id: sagarshansivolume 109 src_snapshot_id: test1 110 account_id: sagarsh 111 expiration_time: '2018-06-16T12:24:56Z' 112 enable_remote_replication: false 113 114 - name: Delete Snapshot 115 tags: 116 - elementsw_delete_snapshot 117 na_elementsw_snapshot: 118 hostname: "{{ elementsw_hostname }}" 119 username: "{{ elementsw_username }}" 120 password: "{{ elementsw_password }}" 121 state: absent 122 src_snapshot_id: deltest1 123 account_id: sagarsh 124 src_volume_id: sagarshansivolume 125""" 126 127 128RETURN = """ 129 130msg: 131 description: Success message 132 returned: success 133 type: str 134 135""" 136import traceback 137 138from ansible.module_utils.basic import AnsibleModule 139from ansible.module_utils._text import to_native 140import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils 141from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule 142 143 144HAS_SF_SDK = netapp_utils.has_sf_sdk() 145 146 147class ElementOSSnapshot(object): 148 """ 149 Element OS Snapshot Manager 150 """ 151 152 def __init__(self): 153 self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() 154 self.argument_spec.update(dict( 155 state=dict(required=False, choices=['present', 'absent'], default='present'), 156 account_id=dict(required=True, type='str'), 157 name=dict(required=False, type='str'), 158 src_volume_id=dict(required=True, type='str'), 159 retention=dict(required=False, type='str'), 160 src_snapshot_id=dict(required=False, type='str'), 161 enable_remote_replication=dict(required=False, type='bool'), 162 expiration_time=dict(required=False, type='str'), 163 snap_mirror_label=dict(required=False, type='str') 164 )) 165 166 self.module = AnsibleModule( 167 argument_spec=self.argument_spec, 168 supports_check_mode=True 169 ) 170 171 input_params = self.module.params 172 173 self.state = input_params['state'] 174 self.name = input_params['name'] 175 self.account_id = input_params['account_id'] 176 self.src_volume_id = input_params['src_volume_id'] 177 self.src_snapshot_id = input_params['src_snapshot_id'] 178 self.retention = input_params['retention'] 179 self.properties_provided = False 180 181 self.expiration_time = input_params['expiration_time'] 182 if input_params['expiration_time'] is not None: 183 self.properties_provided = True 184 185 self.enable_remote_replication = input_params['enable_remote_replication'] 186 if input_params['enable_remote_replication'] is not None: 187 self.properties_provided = True 188 189 self.snap_mirror_label = input_params['snap_mirror_label'] 190 if input_params['snap_mirror_label'] is not None: 191 self.properties_provided = True 192 193 if self.state == 'absent' and self.src_snapshot_id is None: 194 self.module.fail_json( 195 msg="Please provide required parameter : snapshot_id") 196 197 if HAS_SF_SDK is False: 198 self.module.fail_json( 199 msg="Unable to import the SolidFire Python SDK") 200 else: 201 self.sfe = netapp_utils.create_sf_connection(module=self.module) 202 203 self.elementsw_helper = NaElementSWModule(self.sfe) 204 205 # add telemetry attributes 206 self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_snapshot') 207 208 def get_account_id(self): 209 """ 210 Return account id if found 211 """ 212 try: 213 # Update and return self.account_id 214 self.account_id = self.elementsw_helper.account_exists(self.account_id) 215 return self.account_id 216 except Exception as err: 217 self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err)) 218 219 def get_src_volume_id(self): 220 """ 221 Return volume id if found 222 """ 223 src_vol_id = self.elementsw_helper.volume_exists(self.src_volume_id, self.account_id) 224 if src_vol_id is not None: 225 # Update and return self.volume_id 226 self.src_volume_id = src_vol_id 227 # Return src_volume_id 228 return self.src_volume_id 229 return None 230 231 def get_snapshot(self, name=None): 232 """ 233 Return snapshot details if found 234 """ 235 src_snapshot = None 236 if name is not None: 237 src_snapshot = self.elementsw_helper.get_snapshot(name, self.src_volume_id) 238 elif self.src_snapshot_id is not None: 239 src_snapshot = self.elementsw_helper.get_snapshot(self.src_snapshot_id, self.src_volume_id) 240 if src_snapshot is not None: 241 # Update self.src_snapshot_id 242 self.src_snapshot_id = src_snapshot.snapshot_id 243 # Return src_snapshot 244 return src_snapshot 245 246 def create_snapshot(self): 247 """ 248 Create Snapshot 249 """ 250 try: 251 self.sfe.create_snapshot(volume_id=self.src_volume_id, 252 snapshot_id=self.src_snapshot_id, 253 name=self.name, 254 enable_remote_replication=self.enable_remote_replication, 255 retention=self.retention, 256 snap_mirror_label=self.snap_mirror_label, 257 attributes=self.attributes) 258 except Exception as exception_object: 259 self.module.fail_json( 260 msg='Error creating snapshot %s' % ( 261 to_native(exception_object)), 262 exception=traceback.format_exc()) 263 264 def modify_snapshot(self): 265 """ 266 Modify Snapshot Properties 267 """ 268 try: 269 self.sfe.modify_snapshot(snapshot_id=self.src_snapshot_id, 270 expiration_time=self.expiration_time, 271 enable_remote_replication=self.enable_remote_replication, 272 snap_mirror_label=self.snap_mirror_label) 273 except Exception as exception_object: 274 self.module.fail_json( 275 msg='Error modify snapshot %s' % ( 276 to_native(exception_object)), 277 exception=traceback.format_exc()) 278 279 def delete_snapshot(self): 280 """ 281 Delete Snapshot 282 """ 283 try: 284 self.sfe.delete_snapshot(snapshot_id=self.src_snapshot_id) 285 except Exception as exception_object: 286 self.module.fail_json( 287 msg='Error delete snapshot %s' % ( 288 to_native(exception_object)), 289 exception=traceback.format_exc()) 290 291 def apply(self): 292 """ 293 Check, process and initiate snapshot operation 294 """ 295 changed = False 296 result_message = None 297 self.get_account_id() 298 299 # Dont proceed if source volume is not found 300 if self.get_src_volume_id() is None: 301 self.module.fail_json(msg="Volume id not found %s" % self.src_volume_id) 302 303 # Get snapshot details using source volume 304 snapshot_detail = self.get_snapshot() 305 306 if snapshot_detail: 307 if self.properties_provided: 308 if self.expiration_time != snapshot_detail.expiration_time: 309 changed = True 310 else: # To preserve value in case parameter expiration_time is not defined/provided. 311 self.expiration_time = snapshot_detail.expiration_time 312 313 if self.enable_remote_replication != snapshot_detail.enable_remote_replication: 314 changed = True 315 else: # To preserve value in case parameter enable_remote_Replication is not defined/provided. 316 self.enable_remote_replication = snapshot_detail.enable_remote_replication 317 318 if self.snap_mirror_label != snapshot_detail.snap_mirror_label: 319 changed = True 320 else: # To preserve value in case parameter snap_mirror_label is not defined/provided. 321 self.snap_mirror_label = snapshot_detail.snap_mirror_label 322 323 if self.account_id is None or self.src_volume_id is None or self.module.check_mode: 324 changed = False 325 result_message = "Check mode, skipping changes" 326 elif self.state == 'absent' and snapshot_detail is not None: 327 self.delete_snapshot() 328 changed = True 329 elif self.state == 'present' and snapshot_detail is not None: 330 if changed: 331 self.modify_snapshot() # Modify Snapshot properties 332 elif not self.properties_provided: 333 if self.name is not None: 334 snapshot = self.get_snapshot(self.name) 335 # If snapshot with name already exists return without performing any action 336 if snapshot is None: 337 self.create_snapshot() # Create Snapshot using parent src_snapshot_id 338 changed = True 339 else: 340 self.create_snapshot() 341 changed = True 342 elif self.state == 'present': 343 if self.name is not None: 344 snapshot = self.get_snapshot(self.name) 345 # If snapshot with name already exists return without performing any action 346 if snapshot is None: 347 self.create_snapshot() # Create Snapshot using parent src_snapshot_id 348 changed = True 349 else: 350 self.create_snapshot() 351 changed = True 352 else: 353 changed = False 354 result_message = "No changes requested, skipping changes" 355 356 self.module.exit_json(changed=changed, msg=result_message) 357 358 359def main(): 360 """ 361 Main function 362 """ 363 364 na_elementsw_snapshot = ElementOSSnapshot() 365 na_elementsw_snapshot.apply() 366 367 368if __name__ == '__main__': 369 main() 370