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