From 3032cf16a2fb06f6c8121380c5ec1e915063cb8f Mon Sep 17 00:00:00 2001 From: Chris Archibald Date: Tue, 2 Jun 2020 15:32:01 -0400 Subject: [PATCH] Sync bitbucket and Github --- ansible_collections/netapp/ontap/README.md | 9 ++- .../ontap/plugins/modules/na_ontap_volume.py | 75 ++++++++++++++++--- .../plugins/modules/test_na_ontap_volume.py | 60 ++++++++++++++- 3 files changed, 129 insertions(+), 15 deletions(-) diff --git a/ansible_collections/netapp/ontap/README.md b/ansible_collections/netapp/ontap/README.md index b53d3aff..457d7c42 100644 --- a/ansible_collections/netapp/ontap/README.md +++ b/ansible_collections/netapp/ontap/README.md @@ -52,10 +52,13 @@ SSL certificate authentication requires python2.7 or 3.x. - na_ontap_user: `privacy_password` option specifies password for the privacy protocol of SNMPv3 user. - na_ontap_user: `privacy_protocol` option specifies privacy protocol of SNMPv3 user. - na_ontap_user: `remote_switch_ipaddress` option specifies the IP Address of the remote switch of SNMPv3 user. -- na_ontap_volume: `check_interval` option check if a volume move has been completed and then waits this number of seconds before checking again. +- na_ontap_volume: `check_interval` option checks if a volume move has been completed and then waits this number of seconds before checking again. +- na_ontap_volume: `auto_remap_luns` option controls automatic mapping of LUNs during volume rehost. +- na_ontap_volume: `force_restore` option forces volume to restore even if the volume has one or more newer Snapshotcopies. +- na_ontap_volume: `force_unmap_luns` option controls automatic unmapping of LUNs during volume rehost. - na_ontap_volume: `from_vserver` option allows volume rehost from one vserver to another. -- na_ontap_volume: `auto_remap_luns` option control automatic mapping of LUNs during volume rehost. -- na_ontap_volume: `force_unmap_luns` option control automatic unmapping of LUNs during volume rehost. +- na_ontap_volume: `preserve_lun_ids` option controls LUNs in the volume being restored will remain mapped and their identities preserved. +- na_ontap_volume: `snapshot_restroe` option specifies name of snapshot to restore from. - all modules: `cert_filepath`, `key_filepath` to enable SSL certificate authentication (python 2.7 or 3.x). ### Bug Fixes diff --git a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py index acd5e101..eb6bc667 100644 --- a/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py +++ b/ansible_collections/netapp/ontap/plugins/modules/na_ontap_volume.py @@ -367,6 +367,32 @@ - Flag to control automatic unmap of LUNs. type: bool version_added: '20.6.0' + + force_restore: + description: + - If this field is set to "true", the Snapshot copy is restored even if the volume has one or more newer Snapshot + copies which are currently used as reference Snapshot copy by SnapMirror. If a restore is done in this + situation, this will cause future SnapMirror transfers to fail. + - Option should only be used along with snapshot_restore. + type: bool + version_added: '20.6.0' + + preserve_lun_ids: + description: + - If this field is set to "true", LUNs in the volume being restored will remain mapped and their identities + preserved such that host connectivity will not be disrupted during the restore operation. I/O's to the LUN will + be fenced during the restore operation by placing the LUNs in an unavailable state. Once the restore operation + has completed, hosts will be able to resume I/O access to the LUNs. + - Option should only be used along with snapshot_restore. + type: bool + version_added: '20.6.0' + + snapshot_restore: + description: + - Name of snapshot to restore from. + - Not supported on Infinite Volume. + type: str + version_added: '20.6.0' ''' EXAMPLES = """ @@ -485,8 +511,6 @@ https: False - name: Modify volume with snapshot auto delete options - tags: - - auto_delete na_ontap_volume: state: present name: vol_auto_delete @@ -505,8 +529,6 @@ https: False - name: Move volume with force cutover action - tags: - - move na_ontap_volume: name: ansible_vol aggregate_name: aggr_ansible @@ -518,8 +540,6 @@ https: false - name: Rehost volume to another vserver auto remap luns - tags: - - rehost na_ontap_volume: name: ansible_vol from_vserver: ansible @@ -531,8 +551,6 @@ https: false - name: Rehost volume to another vserver force unmap luns - tags: - - rehost na_ontap_volume: name: ansible_vol from_vserver: ansible @@ -543,6 +561,17 @@ password: "{{ netapp_password }}" https: false + - name: Snapshot restore volume + na_ontap_volume: + name: ansible_vol + vserver: ansible + snapshot_restore: 2020-05-24-weekly + force_restore: true + preserve_lun_ids: true + hostname: "{{ netapp_hostname }}" + username: "{{ netapp_username }}" + password: "{{ netapp_password }}" + https: false """ RETURN = """ @@ -620,7 +649,10 @@ def __init__(self): check_interval=dict(required=False, type='int', default=30), from_vserver=dict(required=False, type='str'), auto_remap_luns=dict(required=False, type='bool'), - force_unmap_luns=dict(required=False, type='bool') + force_unmap_luns=dict(required=False, type='bool'), + force_restore=dict(required=False, type='bool'), + preserve_lun_ids=dict(required=False, type='bool'), + snapshot_restore=dict(required=False, type='str') )) self.module = AnsibleModule( argument_spec=self.argument_spec, @@ -1552,18 +1584,37 @@ def rehost_volume(self): % (self.parameters['name'], to_native(error)), exception=traceback.format_exc()) + def snapshot_restore_volume(self): + snapshot_restore = netapp_utils.zapi.NaElement.create_node_with_children( + 'snapshot-restore-volume', **{'snapshot': self.parameters['snapshot_restore'], + 'volume': self.parameters['name']}) + if self.parameters.get('force_restore') is not None: + snapshot_restore.add_new_child('force', str(self.parameters['force_restore'])) + if self.parameters.get('preserve_lun_ids') is not None: + snapshot_restore.add_new_child('preserve-lun-ids', str(self.parameters['preserve_lun_ids'])) + try: + self.server.invoke_successfully(snapshot_restore, enable_tunneling=True) + self.ems_log_event("snapshot-restore-volume") + except netapp_utils.zapi.NaApiError as error: + self.module.fail_json(msg='Error restoring volume %s: %s' + % (self.parameters['name'], to_native(error)), + exception=traceback.format_exc()) + def apply(self): '''Call create/modify/delete operations''' efficiency_policy_modify = None current = self.get_volume() self.volume_style = self.get_volume_style(current) # rename and create are mutually exclusive - rename, rehost, cd_action, modify = None, None, None, None + rename, rehost, snapshot_restore, cd_action, modify = None, None, None, None, None if self.parameters.get('from_name'): rename = self.na_helper.is_rename_action(self.get_volume(self.parameters['from_name']), current) elif self.parameters.get('from_vserver'): rehost = True self.na_helper.changed = True + elif self.parameters.get('snapshot_restore'): + snapshot_restore = True + self.na_helper.changed = True else: cd_action = self.na_helper.get_cd_action(current, self.parameters) if self.parameters.get('unix_permissions') is not None: @@ -1596,6 +1647,8 @@ def apply(self): self.rename_volume() if rehost: self.rehost_volume() + if snapshot_restore: + self.snapshot_restore_volume() if cd_action == 'create': self.create_volume() # if we create, and modify only variable are set (snapdir_access or atime_update) we need to run a modify @@ -1631,6 +1684,8 @@ def ems_log_event(self, state): str(self.parameters['size']) + str(self.parameters['size_unit']) elif state == 'volume-rehost': message = "A Volume has been rehosted" + elif state == 'snapshot-restore-volume': + message = "A Volume has been restored by snapshot" elif state == 'volume-change': message = "A Volume state has been changed" else: diff --git a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py index 535cc9af..e3adf4e7 100644 --- a/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py +++ b/ansible_collections/netapp/ontap/tests/unit/plugins/modules/test_na_ontap_volume.py @@ -328,16 +328,16 @@ def get_volume_mock_object(self, kind=None, job_error=None): """ vol_obj = vol_module() vol_obj.ems_log_event = Mock(return_value=None) - vol_obj.cluster = Mock() - vol_obj.cluster.invoke_successfully = Mock() vol_obj.get_efficiency_policy = Mock(return_value='test_efficiency') vol_obj.volume_style = None if kind is None: vol_obj.server = MockONTAPConnection() elif kind == 'job_info': vol_obj.server = MockONTAPConnection(kind='job_info', data=self.mock_vol, job_error=job_error) + vol_obj.cluster = MockONTAPConnection(kind='job_info', data=self.mock_vol, job_error=job_error) else: vol_obj.server = MockONTAPConnection(kind=kind, data=self.mock_vol) + vol_obj.cluster = MockONTAPConnection(kind=kind, data=self.mock_vol) return vol_obj @@ -1061,3 +1061,59 @@ def test_error_modify_snapshot_auto_delete(self): with pytest.raises(AnsibleFailJson) as exc: self.get_volume_mock_object('zapi_error').set_snapshot_auto_delete() assert exc.value.args[0]['msg'] == 'Error setting snapshot auto delete options for volume test_vol: NetApp API failed. Reason - test:error' + + def test_successful_volume_rehost(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'dest_vserver', + 'from_vserver': 'source_vserver' + } + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_error_volume_rehost(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'dest_vserver', + 'from_vserver': 'source_vserver' + } + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('zapi_error').rehost_volume() + assert exc.value.args[0]['msg'] == 'Error rehosting volume test_vol: NetApp API failed. Reason - test:error' + + def test_successful_volume_restore(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'test_vserver', + 'snapshot_restore': 'snapshot_copy' + } + set_module_args(data) + with pytest.raises(AnsibleExitJson) as exc: + self.get_volume_mock_object('volume').apply() + assert exc.value.args[0]['changed'] + + def test_error_volume_restore(self): + data = { + 'hostname': 'test', + 'username': 'test_user', + 'password': 'test_pass!', + 'name': 'test_vol', + 'vserver': 'test_vserver', + 'snapshot_restore': 'snapshot_copy' + } + set_module_args(data) + with pytest.raises(AnsibleFailJson) as exc: + self.get_volume_mock_object('zapi_error').snapshot_restore_volume() + assert exc.value.args[0]['msg'] == 'Error restoring volume test_vol: NetApp API failed. Reason - test:error'