diff --git a/lib/ansible/modules/storage/netapp/netapp_e_volume.py b/lib/ansible/modules/storage/netapp/netapp_e_volume.py index 0483b998b89..1bc5cce94cb 100644 --- a/lib/ansible/modules/storage/netapp/netapp_e_volume.py +++ b/lib/ansible/modules/storage/netapp/netapp_e_volume.py @@ -130,13 +130,13 @@ options: - This option has no effect on thinly provisioned volumes since the architecture for thin volumes cannot benefit from read ahead caching. type: bool - default: false + default: true version_added: 2.8 write_cache_enable: description: - Indicates whether write-back caching should be enabled for the volume. type: bool - default: false + default: true version_added: 2.8 workload_name: description: @@ -164,6 +164,13 @@ options: type: bool default: false version_added: 2.8 + initialization_timeout: + description: + - Duration in seconds before the wait_for_initialization operation will terminate. + - M(wait_for_initialization==True) to have any effect on module's operations. + type: int + required: false + version_added: 2.9 """ EXAMPLES = """ - name: Create simple volume with workload tags (volume meta data) @@ -244,9 +251,7 @@ msg: returned: always sample: "Standard volume [workload_vol_1] has been created." """ - -import time - +from time import sleep from ansible.module_utils.netapp import NetAppESeriesModule from ansible.module_utils._text import to_native @@ -271,11 +276,12 @@ class NetAppESeriesVolume(NetAppESeriesModule): thin_volume_expansion_policy=dict(type="str", choices=["automatic", "manual"]), thin_volume_growth_alert_threshold=dict(type="int", default=95), read_cache_enable=dict(type="bool", default=True), - read_ahead_enable=dict(type="bool", default=False), - write_cache_enable=dict(type="bool", default=False), + read_ahead_enable=dict(type="bool", default=True), + write_cache_enable=dict(type="bool", default=True), workload_name=dict(type="str", required=False), metadata=dict(type="dict", require=False), - wait_for_initialization=dict(type="bool", default=False)) + wait_for_initialization=dict(type="bool", default=False), + initialization_timeout=dict(type="int", required=False)) required_if = [ ["state", "present", ["storage_pool_name", "size"]], @@ -317,6 +323,7 @@ class NetAppESeriesVolume(NetAppESeriesModule): self.workload_name = args["workload_name"] self.metadata = args["metadata"] self.wait_for_initialization = args["wait_for_initialization"] + self.initialization_timeout = args["initialization_timeout"] # convert metadata to a list of dictionaries containing the keys "key" and "value" corresponding to # each of the workload attributes dictionary entries @@ -381,37 +388,44 @@ class NetAppESeriesVolume(NetAppESeriesModule): self.module.fail_json(msg="Timed out waiting for the volume %s to become available. Array [%s]." % (self.name, self.ssid)) if not self.get_volume(): - time.sleep(5) + sleep(5) self.wait_for_volume_availability(retries=retries - 1) def wait_for_volume_action(self, timeout=None): """Waits until volume action is complete is complete. :param: int timeout: Wait duration measured in seconds. Waits indefinitely when None. """ - action = None + action = "unknown" percent_complete = None - - while action != 'none': - time.sleep(5) + while action != "complete": + sleep(5) try: - rc, expansion = self.request("storage-systems/%s/volumes/%s/expand" - % (self.ssid, self.volume_detail["id"])) - action = expansion["action"] - percent_complete = expansion["percentComplete"] + rc, operations = self.request("storage-systems/%s/symbol/getLongLivedOpsProgress" % self.ssid) + + # Search long lived operations for volume + action = "complete" + for operation in operations["longLivedOpsProgress"]: + if operation["volAction"] is not None: + for key in operation.keys(): + if (operation[key] is not None and "volumeRef" in operation[key] and + (operation[key]["volumeRef"] == self.volume_detail["id"] or + ("storageVolumeRef" in self.volume_detail and operation[key]["volumeRef"] == self.volume_detail["storageVolumeRef"]))): + action = operation["volAction"] + percent_complete = operation["init"]["percentComplete"] except Exception as err: self.module.fail_json(msg="Failed to get volume expansion progress. Volume [%s]. Array Id [%s]." " Error[%s]." % (self.name, self.ssid, to_native(err))) - if timeout <= 0: - self.module.warn("Expansion action, %s, failed to complete during the allotted time. Time remaining" - " [%s]. Array Id [%s]." % (action, percent_complete, self.ssid)) - self.module.fail_json(msg="Expansion action failed to complete. Time remaining [%s]. Array Id [%s]." - % (percent_complete, self.ssid)) - if timeout: - timeout -= 5 - self.module.log("Expansion action, %s, is %s complete." % (action, percent_complete)) + if timeout is not None: + if timeout <= 0: + self.module.warn("Expansion action, %s, failed to complete during the allotted time. Time remaining" + " [%s]. Array Id [%s]." % (action, percent_complete, self.ssid)) + self.module.fail_json(msg="Expansion action failed to complete. Time remaining [%s]. Array Id [%s]." % (percent_complete, self.ssid)) + if timeout: + timeout -= 5 + self.module.log("Expansion action, %s, is %s complete." % (action, percent_complete)) self.module.log("Expansion action is complete.") def get_storage_pool(self): @@ -709,10 +723,6 @@ class NetAppESeriesVolume(NetAppESeriesModule): self.module.fail_json(msg="Failed to expand volume. Volume [%s]. Array Id [%s]. Error[%s]." % (self.name, self.ssid, to_native(err))) - if self.wait_for_initialization: - self.module.log("Waiting for expansion operation to complete.") - self.wait_for_volume_action() - self.module.log("Volume storage capacities have been expanded.") def delete_volume(self): @@ -787,6 +797,10 @@ class NetAppESeriesVolume(NetAppESeriesModule): self.expand_volume() msg = msg[:-1] + " and was expanded." if msg else "Volume [%s] was expanded." + if self.wait_for_initialization: + self.module.log("Waiting for volume operation to complete.") + self.wait_for_volume_action(timeout=self.initialization_timeout) + elif self.state == 'absent': self.delete_volume() msg = "Volume [%s] has been deleted." diff --git a/test/integration/targets/netapp_eseries_volume/aliases b/test/integration/targets/netapp_eseries_volume/aliases new file mode 100644 index 00000000000..28cd22b2e7a --- /dev/null +++ b/test/integration/targets/netapp_eseries_volume/aliases @@ -0,0 +1,10 @@ +# This test is not enabled by default, but can be utilized by defining required variables in integration_config.yml +# Example integration_config.yml: +# --- +#netapp_e_api_host: 192.168.1.1 +#netapp_e_api_username: admin +#netapp_e_api_password: myPass +#netapp_e_ssid: 1 + +unsupported +netapp/eseries diff --git a/test/integration/targets/netapp_eseries_volume/tasks/main.yml b/test/integration/targets/netapp_eseries_volume/tasks/main.yml new file mode 100644 index 00000000000..996354c8860 --- /dev/null +++ b/test/integration/targets/netapp_eseries_volume/tasks/main.yml @@ -0,0 +1 @@ +- include_tasks: run.yml diff --git a/test/integration/targets/netapp_eseries_volume/tasks/run.yml b/test/integration/targets/netapp_eseries_volume/tasks/run.yml new file mode 100644 index 00000000000..e14fa13c3ff --- /dev/null +++ b/test/integration/targets/netapp_eseries_volume/tasks/run.yml @@ -0,0 +1,776 @@ +# Test code for the netapp_e_iscsi_interface module +# (c) 2018, NetApp, Inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: NetApp Test ASUP module + fail: + msg: 'Please define netapp_e_api_username, netapp_e_api_password, netapp_e_api_host, and netapp_e_ssid.' + when: netapp_e_api_username is undefined or netapp_e_api_password is undefined + or netapp_e_api_host is undefined or netapp_e_ssid is undefined + vars: + credentials: &creds + api_url: "https://{{ netapp_e_api_host }}:8443/devmgr/v2" + api_username: "{{ netapp_e_api_username }}" + api_password: "{{ netapp_e_api_password }}" + ssid: "{{ netapp_e_ssid }}" + validate_certs: no + +- set_fact: + credentials: *creds + +# test setup +- name: Delete raid 0 storage pool + netapp_e_storagepool: + <<: *creds + state: absent + name: "{{ item }}" + loop: + - storage_pool + - storage_pool2 + - storage_pool3 + +# Thick volume testing: create, delete, expand, change properties (read/write cache), expand and change properties, +- name: Create raid 0 storage pool + netapp_e_storagepool: + <<: *creds + state: present + name: storage_pool + criteria_min_usable_capacity: 5 + criteria_size_unit: tb + erase_secured_drives: yes + raid_level: raid0 + +- name: Delete volume in raid 0 storage pool + netapp_e_volume: + <<: *creds + state: absent + name: volume + +- name: Create volume in raid 0 storage pool + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 100 + size_unit: gb + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '107374182400' and item.segmentSize == 131072}}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +- name: Re-execute volume creation in raid 0 storage pool + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 100 + size_unit: gb + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ not results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '107374182400' and item.segmentSize == 131072}}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +- name: Update volume size + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 200 + size_unit: gb + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '214748364800' and item.segmentSize == 131072}}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +- pause: seconds=15 + +- name: Update volume properties + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 200 + size_unit: gb + write_cache_enable: true + read_cache_enable: false + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '214748364800' and item.segmentSize == 131072 and + not item.cacheSettings.readCacheEnable and item.cacheSettings.writeCacheEnable}}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +- name: Update volume properties and expand storage capabilities + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 300 + size_unit: gb + write_cache_enable: false + read_cache_enable: true + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '322122547200' and item.segmentSize == 131072 and + item.cacheSettings.readCacheEnable and not item.cacheSettings.writeCacheEnable}}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +# Workload tagging testing: create, utilize existing (name only, name with same attributes), modify attributes +- name: Add workload tag (change, new workload tag) + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 300 + size_unit: gb + write_cache_enable: false + read_cache_enable: true + workload_name: volume_tag + metadata: + volume_tag_key: volume_tag_value + register: results +- pause: seconds=15 +- name: Validate volume workload changes + uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '322122547200' and item.segmentSize == 131072 and + item.cacheSettings.readCacheEnable and not item.cacheSettings.writeCacheEnable and + {'key': 'volumeTypeId', 'value': 'volume'} in item.metadata }}" + msg: "Failed to modify volume metadata!" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/workloads" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: workload_tags +- assert: + that: "{{ item.name == 'volume_tag' and + {'key': 'volume_tag_key', 'value': 'volume_tag_value'} in item.workloadAttributes }}" + msg: "Workload tag failed to be created!" + loop: "{{ lookup('list', volume_tag_id, wantList=True) }}" + vars: + volume_tag_id: "{{ workload_tags | json_query('json[?name==`volume_tag`]') }}" + +- name: Repeat add workload tag (no change) + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 300 + size_unit: gb + write_cache_enable: false + read_cache_enable: true + workload_name: volume_tag + metadata: + volume_tag_key: volume_tag_value + register: results +- pause: seconds=15 +- name: Validate volume workload changes + uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ not results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '322122547200' and item.segmentSize == 131072 and + item.cacheSettings.readCacheEnable and not item.cacheSettings.writeCacheEnable and + {'key': 'volumeTypeId', 'value': 'volume'} in item.metadata }}" + msg: "Failed to not modify volume metadata!" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/workloads" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: workload_tags +- assert: + that: "{{ item.name == 'volume_tag' and + {'key': 'volume_tag_key', 'value': 'volume_tag_value'} in item.workloadAttributes }}" + msg: "Workload tag failed not to be changed" + loop: "{{ lookup('list', volume_tag_id, wantList=True) }}" + vars: + volume_tag_id: "{{ workload_tags | json_query('json[?name==`volume_tag`]') }}" + +- name: Workload tag (no change, just using workload_name) + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 300 + size_unit: gb + write_cache_enable: false + read_cache_enable: true + workload_name: volume_tag + register: results +- pause: seconds=15 +- name: Validate volume workload changes + uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ not results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '322122547200' and item.segmentSize == 131072 and + item.cacheSettings.readCacheEnable and not item.cacheSettings.writeCacheEnable and + {'key': 'volumeTypeId', 'value': 'volume'} in item.metadata }}" + msg: "Failed to not modify volume metadata!" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/workloads" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: workload_tags +- assert: + that: "{{ item.name == 'volume_tag' and + {'key': 'volume_tag_key', 'value': 'volume_tag_value'} in item.workloadAttributes }}" + msg: "Workload tag failed to not be modified!" + loop: "{{ lookup('list', volume_tag_id, wantList=True) }}" + vars: + volume_tag_id: "{{ workload_tags | json_query('json[?name==`volume_tag`]') }}" + +- name: Add workload tag (change, new attributes) + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 300 + size_unit: gb + write_cache_enable: false + read_cache_enable: true + workload_name: volume_tag + metadata: + volume_tag_key2: volume_tag_value2 + register: results +- pause: seconds=15 +- name: Validate volume workload changes + uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '322122547200' and item.segmentSize == 131072 and + item.cacheSettings.readCacheEnable and not item.cacheSettings.writeCacheEnable and + {'key': 'volumeTypeId', 'value': 'volume'} in item.metadata }}" + msg: "Failed to not modify volume metadata!" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/workloads" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: workload_tags +- assert: + that: "{{ item.name == 'volume_tag' and + {'key': 'volume_tag_key2', 'value': 'volume_tag_value2'} in item.workloadAttributes }}" + msg: "Workload tag failed to be updated!" + loop: "{{ lookup('list', volume_tag_id, wantList=True) }}" + vars: + volume_tag_id: "{{ workload_tags | json_query('json[?name==`volume_tag`]') }}" + +- name: Remove workload tag from volume (change) + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool + size: 300 + size_unit: gb + write_cache_enable: false + read_cache_enable: true + register: results +- pause: seconds=15 +- name: Validate volume workload changes + uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '322122547200' and item.segmentSize == 131072 and + item.cacheSettings.readCacheEnable and not item.cacheSettings.writeCacheEnable and + item.metadata == []}}" + msg: "Failed to not modify volume metadata!" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/workloads" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: workload_tags +- assert: + that: "{{ item.name == 'volume_tag' and + {'key': 'volume_tag_key2', 'value': 'volume_tag_value2'} in item.workloadAttributes }}" + msg: "Workload tag failed to be updated!" + loop: "{{ lookup('list', volume_tag_id, wantList=True) }}" + vars: + volume_tag_id: "{{ workload_tags | json_query('json[?name==`volume_tag`]') }}" + +- name: Delete workload tag + uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/workloads" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: workload_tags +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/workloads/{{ item }}" + method: DELETE + status_code: 204 + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + loop: "{{ lookup('list', volume_tag_id, wantList=True) }}" + vars: + volume_tag_id: "{{ workload_tags | json_query('json[?name==`volume_tag`].id') }}" + +- name: Delete raid 0 storage pool + netapp_e_storagepool: + <<: *creds + state: absent + name: storage_pool + + +# *** Thin volume testing (May not work with simulator) *** +- name: Create dynamic disk pool + netapp_e_storagepool: + <<: *creds + state: present + name: storage_pool + criteria_min_usable_capacity: 2 + criteria_size_unit: tb + +- name: Create thin volume + netapp_e_volume: + <<: *creds + state: present + name: thin_volume + storage_pool_name: storage_pool + size: 131072 + size_unit: gb + thin_provision: true + thin_volume_repo_size: 32 + thin_volume_max_repo_size: 1024 + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/thin-volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'thin_volume' and item.thinProvisioned and + item.capacity == '140737488355328' and item.initialProvisionedCapacity == '34359738368' and + item.provisionedCapacityQuota == '1099511627776' and item.expansionPolicy == 'automatic' }}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`thin_volume`]') }}" + +- name: (Rerun) Create thin volume + netapp_e_volume: + <<: *creds + state: present + name: thin_volume + storage_pool_name: storage_pool + size: 131072 + size_unit: gb + thin_provision: true + thin_volume_repo_size: 32 + thin_volume_max_repo_size: 1024 + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/thin-volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ not results.changed and item.name == 'thin_volume' and item.thinProvisioned and + item.capacity == '140737488355328' and item.initialProvisionedCapacity == '34359738368' and + item.provisionedCapacityQuota == '1099511627776' and item.expansionPolicy == 'automatic' }}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`thin_volume`]') }}" + + +- name: Expand thin volume's virtual size + netapp_e_volume: + <<: *creds + state: present + name: thin_volume + storage_pool_name: storage_pool + size: 262144 + size_unit: gb + thin_provision: true + thin_volume_repo_size: 32 + thin_volume_max_repo_size: 1024 + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/thin-volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'thin_volume' and item.thinProvisioned and + item.capacity == '281474976710656' and item.initialProvisionedCapacity == '34359738368' and + item.provisionedCapacityQuota == '1099511627776' and item.expansionPolicy == 'automatic' }}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`thin_volume`]') }}" + + +- name: Expand thin volume's maximum repository size + netapp_e_volume: + <<: *creds + state: present + name: thin_volume + storage_pool_name: storage_pool + size: 262144 + size_unit: gb + thin_provision: true + thin_volume_repo_size: 32 + thin_volume_max_repo_size: 2048 + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/thin-volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'thin_volume' and item.thinProvisioned and + item.capacity == '281474976710656' and item.initialProvisionedCapacity == '34359738368' and + item.provisionedCapacityQuota == '2199023255552' and item.expansionPolicy == 'automatic' }}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`thin_volume`]') }}" + +- name: Create dynamic disk pool + netapp_e_storagepool: + <<: *creds + state: present + name: storage_pool2 + criteria_min_usable_capacity: 2 + criteria_size_unit: tb +- pause: seconds=15 + +- name: Create second thin volume with manual expansion policy + netapp_e_volume: + <<: *creds + state: present + name: thin_volume2 + storage_pool_name: storage_pool2 + size_unit: gb + size: 131072 + thin_provision: true + thin_volume_repo_size: 32 + thin_volume_max_repo_size: 32 + thin_volume_expansion_policy: manual + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/thin-volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'thin_volume2' and item.thinProvisioned and + item.capacity == '140737488355328' and item.initialProvisionedCapacity == '34359738368' and + item.currentProvisionedCapacity == '34359738368' and item.expansionPolicy == 'manual' }}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`thin_volume2`]') }}" + + +- name: Create second thin volume with manual expansion policy + netapp_e_volume: + <<: *creds + state: present + name: thin_volume2 + storage_pool_name: storage_pool2 + size_unit: gb + size: 131072 + thin_provision: true + thin_volume_repo_size: 288 + thin_volume_max_repo_size: 288 + thin_volume_expansion_policy: manual + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/thin-volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'thin_volume2' and item.thinProvisioned and + item.capacity == '140737488355328' and item.initialProvisionedCapacity == '34359738368' and + item.currentProvisionedCapacity == '309237645312' and item.expansionPolicy == 'manual' }}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`thin_volume2`]') }}" + +- name: Modify second thin volume to use automatic expansion policy + netapp_e_volume: + <<: *creds + state: present + name: thin_volume2 + storage_pool_name: storage_pool2 + size_unit: gb + size: 131072 + thin_provision: true + thin_volume_repo_size: 288 + thin_volume_max_repo_size: 288 + thin_volume_expansion_policy: automatic + register: results +- pause: seconds=15 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/thin-volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'thin_volume2' and item.thinProvisioned and + item.capacity == '140737488355328' and item.initialProvisionedCapacity == '34359738368' and + item.currentProvisionedCapacity == '309237645312' and item.expansionPolicy == 'automatic' }}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`thin_volume2`]') }}" + +- name: Delete raid 0 storage pool + netapp_e_storagepool: + <<: *creds + state: absent + name: "{{ item }}" + loop: + - storage_pool + - storage_pool2 + +- name: Create raid 0 storage pool + netapp_e_storagepool: + <<: *creds + state: present + name: storage_pool + criteria_min_usable_capacity: 5 + criteria_size_unit: tb + erase_secured_drives: yes + raid_level: raid0 + +# Thick volume expansion testing: wait and don't wait for operation to complete +- name: Create raid 6 storage pool + netapp_e_storagepool: + <<: *creds + state: present + name: storage_pool3 + criteria_min_usable_capacity: 5 + criteria_size_unit: tb + erase_secured_drives: yes + raid_level: raid6 + +- name: Delete volume in raid 6 storage pool + netapp_e_volume: + <<: *creds + state: absent + name: volume + +- name: Create volume in raid 0 storage pool for expansion testing + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool3 + size: 1 + size_unit: gb + register: results +- pause: seconds=10 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '1073741824' and item.segmentSize == 131072}}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +- name: Modify volume in raid 0 storage pool and wait for expansion testing + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool3 + size: 10 + size_unit: gb + wait_for_initialization: True + register: results +- pause: seconds=10 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes/{{ volume[0]['id'] }}/expand" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: expansion_state + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '10737418240' and item.segmentSize == 131072 and + expansion_state['json']['action'] == 'none'}}" + msg: "Volume expansion test failed." + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +- name: Modify volume in raid 0 storage pool and don't wait for expansion testing + netapp_e_volume: + <<: *creds + state: present + name: volume + storage_pool_name: storage_pool3 + size: 100 + size_unit: gb + wait_for_initialization: False + register: results +- pause: seconds=10 +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: current +- uri: + url: "{{ credentials.api_url }}/storage-systems/{{ credentials.ssid }}/volumes/{{ volume[0]['id'] }}/expand" + user: "{{ credentials.api_username }}" + password: "{{ credentials.api_password }}" + validate_certs: no + register: expansion_state + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" +- assert: + that: "{{ results.changed and item.name == 'volume' and not item.thinProvisioned and + item.capacity == '107374182400' and item.segmentSize == 131072 and expansion_state['json']['action'] != 'none'}}" + msg: "Failed to create volume" + loop: "{{ lookup('list', volume, wantList=True) }}" + vars: + volume: "{{ current | json_query('json[?name==`volume`]') }}" + +- name: Delete raid 0 storage pool + netapp_e_storagepool: + <<: *creds + state: absent + name: "{{ item }}" + loop: + - storage_pool3 \ No newline at end of file diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 8efa99592f3..3b984ee7e3e 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -7383,7 +7383,6 @@ test/units/modules/storage/netapp/test_netapp_e_iscsi_target.py future-import-bo test/units/modules/storage/netapp/test_netapp_e_ldap.py future-import-boilerplate test/units/modules/storage/netapp/test_netapp_e_mgmt_interface.py future-import-boilerplate test/units/modules/storage/netapp/test_netapp_e_syslog.py future-import-boilerplate -test/units/modules/storage/netapp/test_netapp_e_volume.py future-import-boilerplate test/units/modules/system/interfaces_file/test_interfaces_file.py future-import-boilerplate test/units/modules/system/interfaces_file/test_interfaces_file.py metaclass-boilerplate test/units/modules/system/interfaces_file/test_interfaces_file.py pylint:blacklisted-name diff --git a/test/units/modules/storage/netapp/test_netapp_e_volume.py b/test/units/modules/storage/netapp/test_netapp_e_volume.py index e91016b557e..8d27f619a68 100644 --- a/test/units/modules/storage/netapp/test_netapp_e_volume.py +++ b/test/units/modules/storage/netapp/test_netapp_e_volume.py @@ -1,6 +1,8 @@ # coding=utf-8 # (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 try: from unittest import mock @@ -11,8 +13,6 @@ from ansible.module_utils.netapp import NetAppESeriesModule from ansible.modules.storage.netapp.netapp_e_volume import NetAppESeriesVolume from units.modules.utils import AnsibleFailJson, ModuleTestCase, set_module_args -__metaclass__ = type - class NetAppESeriesVolumeTest(ModuleTestCase): REQUIRED_PARAMS = {"api_username": "username", @@ -158,7 +158,40 @@ class NetAppESeriesVolumeTest(ModuleTestCase): "diskPool": False, "id": "04000000600A098000A4B28D00000FBD5C2F7F19", "name": "database_storage_pool"}] - REQUEST_FUNC = "ansible.modules.storage.netapp.netapp_e_volume.NetAppESeriesVolume.request" + + GET_LONG_LIVED_OPERATION_RESPONSE = [ + {"returnCode": "ok", + "longLivedOpsProgress": [ + {"volAction": "initializing", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B9D1000037315D494C6F", "pending": False, "percentComplete": 1, "timeToCompletion": 20}, + "format": None, "volCreation": None, "volDeletion": None}, + {"volAction": "initializing", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B28D00003D2C5D494C87", "pending": False, "percentComplete": 0, "timeToCompletion": 18}, + "volCreation": None, "volDeletion": None}]}, + {"returnCode": "ok", + "longLivedOpsProgress": [ + {"volAction": "complete", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B9D1000037315D494C6F", "pending": False, "percentComplete": 1, "timeToCompletion": 20}, + "format": None, "volCreation": None, "volDeletion": None}, + {"volAction": "initializing", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B28D00003D2C5D494C87", "pending": False, "percentComplete": 0, "timeToCompletion": 18}, + "volCreation": None, "volDeletion": None}]}, + {"returnCode": "ok", + "longLivedOpsProgress": [ + {"volAction": "initializing", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B9D1000037315D494C6F", "pending": False, "percentComplete": 1, "timeToCompletion": 20}, + "format": None, "volCreation": None, "volDeletion": None}, + {"volAction": "complete", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B28D00003D2C5D494C87", "pending": False, "percentComplete": 0, "timeToCompletion": 18}, + "volCreation": None, "volDeletion": None}]}, + {"returnCode": "ok", + "longLivedOpsProgress": [ + {"volAction": "complete", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B9D1000037315D494C6F", "pending": False, "percentComplete": 1, "timeToCompletion": 20}, + "format": None, "volCreation": None, "volDeletion": None}, + {"volAction": "complete", "reconstruct": None, "volExpansion": None, "volAndCapExpansion": None, + "init": {"volumeRef": "02000000600A098000A4B28D00003D2C5D494C87", "pending": False, "percentComplete": 0, "timeToCompletion": 18}, + "volCreation": None, "volDeletion": None}]}] WORKLOAD_GET_RESPONSE = [{"id": "4200000001000000000000000000000000000000", "name": "general_workload_1", "workloadAttributes": [{"key": "profileId", "value": "Other_1"}]}, @@ -177,6 +210,10 @@ class NetAppESeriesVolumeTest(ModuleTestCase): {"key": "location", "value": "global"}, {"key": "profileId", "value": "ansible_workload_4"}]}] + REQUEST_FUNC = "ansible.modules.storage.netapp.netapp_e_volume.NetAppESeriesVolume.request" + GET_VOLUME_FUNC = "ansible.modules.storage.netapp.netapp_e_volume.NetAppESeriesVolume.get_volume" + SLEEP_FUNC = "ansible.modules.storage.netapp.netapp_e_volume.sleep" + def _set_args(self, args=None): module_args = self.REQUIRED_PARAMS.copy() if args is not None: @@ -268,6 +305,67 @@ class NetAppESeriesVolumeTest(ModuleTestCase): volume_object = NetAppESeriesVolume() volume_object.get_volume() + def tests_wait_for_volume_availability_pass(self): + """Ensure wait_for_volume_availability completes as expected.""" + self._set_args({"state": "present", "name": "NewVolume", "storage_pool_name": "employee_data_storage_pool", "size": 100, + "wait_for_initialization": True}) + volume_object = NetAppESeriesVolume() + with mock.patch(self.SLEEP_FUNC, return_value=None): + with mock.patch(self.GET_VOLUME_FUNC, side_effect=[False, False, True]): + volume_object.wait_for_volume_availability() + + def tests_wait_for_volume_availability_fail(self): + """Ensure wait_for_volume_availability throws the expected exceptions.""" + self._set_args({"state": "present", "name": "NewVolume", "storage_pool_name": "employee_data_storage_pool", "size": 100, + "wait_for_initialization": True}) + volume_object = NetAppESeriesVolume() + volume_object.get_volume = lambda: False + with self.assertRaisesRegexp(AnsibleFailJson, "Timed out waiting for the volume"): + with mock.patch(self.SLEEP_FUNC, return_value=None): + volume_object.wait_for_volume_availability() + + def tests_wait_for_volume_action_pass(self): + """Ensure wait_for_volume_action completes as expected.""" + self._set_args({"state": "present", "name": "NewVolume", "storage_pool_name": "employee_data_storage_pool", "size": 100, + "wait_for_initialization": True}) + volume_object = NetAppESeriesVolume() + volume_object.volume_detail = {"id": "02000000600A098000A4B9D1000037315D494C6F", + "storageVolumeRef": "02000000600A098000A4B9D1000037315DXXXXXX"} + with mock.patch(self.SLEEP_FUNC, return_value=None): + with mock.patch(self.REQUEST_FUNC, side_effect=[(200, self.GET_LONG_LIVED_OPERATION_RESPONSE[0]), + (200, self.GET_LONG_LIVED_OPERATION_RESPONSE[1]), + (200, self.GET_LONG_LIVED_OPERATION_RESPONSE[2]), + (200, self.GET_LONG_LIVED_OPERATION_RESPONSE[3])]): + volume_object.wait_for_volume_action() + + self._set_args({"state": "present", "name": "NewVolume", "storage_pool_name": "employee_data_storage_pool", "size": 100, + "wait_for_initialization": True}) + volume_object = NetAppESeriesVolume() + volume_object.volume_detail = {"id": "02000000600A098000A4B9D1000037315DXXXXXX", + "storageVolumeRef": "02000000600A098000A4B9D1000037315D494C6F"} + with mock.patch(self.SLEEP_FUNC, return_value=None): + with mock.patch(self.REQUEST_FUNC, side_effect=[(200, self.GET_LONG_LIVED_OPERATION_RESPONSE[0]), + (200, self.GET_LONG_LIVED_OPERATION_RESPONSE[1]), + (200, self.GET_LONG_LIVED_OPERATION_RESPONSE[2]), + (200, self.GET_LONG_LIVED_OPERATION_RESPONSE[3])]): + volume_object.wait_for_volume_action() + + def tests_wait_for_volume_action_fail(self): + """Ensure wait_for_volume_action throws the expected exceptions.""" + self._set_args({"state": "present", "name": "NewVolume", "storage_pool_name": "employee_data_storage_pool", "size": 100, + "wait_for_initialization": True}) + volume_object = NetAppESeriesVolume() + volume_object.volume_detail = {"id": "02000000600A098000A4B9D1000037315DXXXXXX", + "storageVolumeRef": "02000000600A098000A4B9D1000037315D494C6F"} + with mock.patch(self.SLEEP_FUNC, return_value=None): + with self.assertRaisesRegexp(AnsibleFailJson, "Failed to get volume expansion progress."): + with mock.patch(self.REQUEST_FUNC, return_value=Exception()): + volume_object.wait_for_volume_action() + + with self.assertRaisesRegexp(AnsibleFailJson, "Expansion action failed to complete."): + with mock.patch(self.REQUEST_FUNC, return_value=(200, self.GET_LONG_LIVED_OPERATION_RESPONSE[0])): + volume_object.wait_for_volume_action(timeout=300) + def test_get_storage_pool_pass(self): """Evaluate the get_storage_pool method.""" with mock.patch(self.REQUEST_FUNC, return_value=(200, self.STORAGE_POOL_GET_RESPONSE)):