summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/storage
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/modules/storage')
-rw-r--r--lib/ansible/modules/storage/emc/emc_vnx_sg_member.py176
l---------lib/ansible/modules/storage/glusterfs/_gluster_heal_facts.py1
-rw-r--r--lib/ansible/modules/storage/glusterfs/gluster_heal_info.py204
-rw-r--r--lib/ansible/modules/storage/glusterfs/gluster_peer.py176
-rw-r--r--lib/ansible/modules/storage/glusterfs/gluster_volume.py611
-rw-r--r--lib/ansible/modules/storage/hpe3par/ss_3par_cpg.py299
-rw-r--r--lib/ansible/modules/storage/ibm/ibm_sa_domain.py161
-rw-r--r--lib/ansible/modules/storage/ibm/ibm_sa_host.py123
-rw-r--r--lib/ansible/modules/storage/ibm/ibm_sa_host_ports.py132
-rw-r--r--lib/ansible/modules/storage/ibm/ibm_sa_pool.py120
-rw-r--r--lib/ansible/modules/storage/ibm/ibm_sa_vol.py112
-rw-r--r--lib/ansible/modules/storage/ibm/ibm_sa_vol_map.py140
-rw-r--r--lib/ansible/modules/storage/infinidat/infini_export.py196
-rw-r--r--lib/ansible/modules/storage/infinidat/infini_export_client.py206
-rw-r--r--lib/ansible/modules/storage/infinidat/infini_fs.py171
-rw-r--r--lib/ansible/modules/storage/infinidat/infini_host.py160
-rw-r--r--lib/ansible/modules/storage/infinidat/infini_pool.py215
-rw-r--r--lib/ansible/modules/storage/infinidat/infini_vol.py166
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_aggregate.py233
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_license.py299
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_lun.py378
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_qtree.py239
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_svm.py251
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_user.py306
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_user_role.py232
-rw-r--r--lib/ansible/modules/storage/netapp/_na_cdot_volume.py445
-rw-r--r--lib/ansible/modules/storage/netapp/_na_ontap_gather_facts.py615
-rw-r--r--lib/ansible/modules/storage/netapp/_sf_account_manager.py268
-rw-r--r--lib/ansible/modules/storage/netapp/_sf_check_connections.py184
-rw-r--r--lib/ansible/modules/storage/netapp/_sf_snapshot_schedule_manager.py389
-rw-r--r--lib/ansible/modules/storage/netapp/_sf_volume_access_group_manager.py249
-rw-r--r--lib/ansible/modules/storage/netapp/_sf_volume_manager.py320
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_alerts.py280
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_amg.py255
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_amg_role.py234
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_amg_sync.py261
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_asup.py309
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_auditlog.py281
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_auth.py276
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_drive_firmware.py215
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_facts.py530
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_firmware.py488
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_flashcache.py415
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_global.py157
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_host.py539
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_hostgroup.py302
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_iscsi_interface.py398
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_iscsi_target.py294
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_ldap.py390
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_lun_mapping.py286
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_mgmt_interface.py708
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_snapshot_group.py370
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_snapshot_images.py247
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_snapshot_volume.py281
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_storage_system.py296
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_storagepool.py936
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_syslog.py280
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_volume.py856
-rw-r--r--lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py400
-rw-r--r--lib/ansible/modules/storage/purestorage/_purefa_facts.py862
-rw-r--r--lib/ansible/modules/storage/purestorage/_purefb_facts.py656
-rw-r--r--lib/ansible/modules/storage/vexata/vexata_eg.py214
-rw-r--r--lib/ansible/modules/storage/vexata/vexata_volume.py201
-rw-r--r--lib/ansible/modules/storage/zfs/zfs.py268
-rw-r--r--lib/ansible/modules/storage/zfs/zfs_delegate_admin.py268
-rw-r--r--lib/ansible/modules/storage/zfs/zfs_facts.py266
-rw-r--r--lib/ansible/modules/storage/zfs/zpool_facts.py215
67 files changed, 0 insertions, 21011 deletions
diff --git a/lib/ansible/modules/storage/emc/emc_vnx_sg_member.py b/lib/ansible/modules/storage/emc/emc_vnx_sg_member.py
deleted file mode 100644
index 452d0a3411..0000000000
--- a/lib/ansible/modules/storage/emc/emc_vnx_sg_member.py
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright (c) 2018, Luca 'remix_tj' Lorenzetto <lorenzetto.luca@gmail.com>
-#
-# 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
-
-
-ANSIBLE_METADATA = {
- 'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'
-}
-
-DOCUMENTATION = '''
----
-module: emc_vnx_sg_member
-
-short_description: Manage storage group member on EMC VNX
-
-version_added: "2.7"
-
-description:
- - "This module manages the members of an existing storage group."
-
-extends_documentation_fragment:
- - emc.emc_vnx
-
-options:
- name:
- description:
- - Name of the Storage group to manage.
- required: true
- lunid:
- description:
- - Lun id to be added.
- required: true
- state:
- description:
- - Indicates the desired lunid state.
- - C(present) ensures specified lunid is present in the Storage Group.
- - C(absent) ensures specified lunid is absent from Storage Group.
- default: present
- choices: [ "present", "absent"]
-
-
-author:
- - Luca 'remix_tj' Lorenzetto (@remixtj)
-'''
-
-EXAMPLES = '''
-- name: Add lun to storage group
- emc_vnx_sg_member:
- name: sg01
- sp_address: sp1a.fqdn
- sp_user: sysadmin
- sp_password: sysadmin
- lunid: 100
- state: present
-
-- name: Remove lun from storage group
- emc_vnx_sg_member:
- name: sg01
- sp_address: sp1a.fqdn
- sp_user: sysadmin
- sp_password: sysadmin
- lunid: 100
- state: absent
-'''
-
-RETURN = '''
-hluid:
- description: LUNID that hosts attached to the storage group will see.
- type: int
- returned: success
-'''
-
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils._text import to_native
-from ansible.module_utils.storage.emc.emc_vnx import emc_vnx_argument_spec
-
-LIB_IMP_ERR = None
-try:
- from storops import VNXSystem
- from storops.exception import VNXCredentialError, VNXStorageGroupError, \
- VNXAluAlreadyAttachedError, VNXAttachAluError, VNXDetachAluNotFoundError
- HAS_LIB = True
-except Exception:
- LIB_IMP_ERR = traceback.format_exc()
- HAS_LIB = False
-
-
-def run_module():
- module_args = dict(
- name=dict(type='str', required=True),
- lunid=dict(type='int', required=True),
- state=dict(default='present', choices=['present', 'absent']),
- )
-
- module_args.update(emc_vnx_argument_spec)
-
- result = dict(
- changed=False,
- hluid=None
- )
-
- module = AnsibleModule(
- argument_spec=module_args,
- supports_check_mode=True
- )
-
- if not HAS_LIB:
- module.fail_json(msg=missing_required_lib('storops >= 0.5.10'),
- exception=LIB_IMP_ERR)
-
- sp_user = module.params['sp_user']
- sp_address = module.params['sp_address']
- sp_password = module.params['sp_password']
- alu = module.params['lunid']
-
- # if the user is working with this module in only check mode we do not
- # want to make any changes to the environment, just return the current
- # state with no modifications
- if module.check_mode:
- return result
-
- try:
- vnx = VNXSystem(sp_address, sp_user, sp_password)
- sg = vnx.get_sg(module.params['name'])
- if sg.existed:
- if module.params['state'] == 'present':
- if not sg.has_alu(alu):
- try:
- result['hluid'] = sg.attach_alu(alu)
- result['changed'] = True
- except VNXAluAlreadyAttachedError:
- result['hluid'] = sg.get_hlu(alu)
- except (VNXAttachAluError, VNXStorageGroupError) as e:
- module.fail_json(msg='Error attaching {0}: '
- '{1} '.format(alu, to_native(e)),
- **result)
- else:
- result['hluid'] = sg.get_hlu(alu)
- if module.params['state'] == 'absent' and sg.has_alu(alu):
- try:
- sg.detach_alu(alu)
- result['changed'] = True
- except VNXDetachAluNotFoundError:
- # being not attached when using absent is OK
- pass
- except VNXStorageGroupError as e:
- module.fail_json(msg='Error detaching alu {0}: '
- '{1} '.format(alu, to_native(e)),
- **result)
- else:
- module.fail_json(msg='No such storage group named '
- '{0}'.format(module.params['name']),
- **result)
- except VNXCredentialError as e:
- module.fail_json(msg='{0}'.format(to_native(e)), **result)
-
- module.exit_json(**result)
-
-
-def main():
- run_module()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/glusterfs/_gluster_heal_facts.py b/lib/ansible/modules/storage/glusterfs/_gluster_heal_facts.py
deleted file mode 120000
index e5a7565c18..0000000000
--- a/lib/ansible/modules/storage/glusterfs/_gluster_heal_facts.py
+++ /dev/null
@@ -1 +0,0 @@
-gluster_heal_info.py \ No newline at end of file
diff --git a/lib/ansible/modules/storage/glusterfs/gluster_heal_info.py b/lib/ansible/modules/storage/glusterfs/gluster_heal_info.py
deleted file mode 100644
index 0da1373b14..0000000000
--- a/lib/ansible/modules/storage/glusterfs/gluster_heal_info.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright: (c) 2016, Red Hat, 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: gluster_heal_info
-short_description: Gather information on self-heal or rebalance status
-author: "Devyani Kota (@devyanikota)"
-version_added: "2.8"
-description:
- - Gather facts about either self-heal or rebalance status.
- - This module was called C(gluster_heal_facts) before Ansible 2.9, returning C(ansible_facts).
- Note that the M(gluster_heal_info) module no longer returns C(ansible_facts)!
-options:
- name:
- description:
- - The volume name.
- required: true
- aliases: ['volume']
- status_filter:
- default: "self-heal"
- choices: ["self-heal", "rebalance"]
- description:
- - Determines which facts are to be returned.
- - If the C(status_filter) is C(self-heal), status of self-heal, along with the number of files still in process are returned.
- - If the C(status_filter) is C(rebalance), rebalance status is returned.
-requirements:
- - GlusterFS > 3.2
-'''
-
-EXAMPLES = '''
-- name: Gather self-heal facts about all gluster hosts in the cluster
- gluster_heal_info:
- name: test_volume
- status_filter: self-heal
- register: self_heal_status
-- debug:
- var: self_heal_status
-
-- name: Gather rebalance facts about all gluster hosts in the cluster
- gluster_heal_info:
- name: test_volume
- status_filter: rebalance
- register: rebalance_status
-- debug:
- var: rebalance_status
-'''
-
-RETURN = '''
-name:
- description: GlusterFS volume name
- returned: always
- type: str
-status_filter:
- description: Whether self-heal or rebalance status is to be returned
- returned: always
- type: str
-heal_info:
- description: List of files that still need healing process
- returned: On success
- type: list
-rebalance_status:
- description: Status of rebalance operation
- returned: On success
- type: list
-'''
-
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-from distutils.version import LooseVersion
-
-glusterbin = ''
-
-
-def run_gluster(gargs, **kwargs):
- global glusterbin
- global module
- args = [glusterbin, '--mode=script']
- args.extend(gargs)
- try:
- rc, out, err = module.run_command(args, **kwargs)
- if rc != 0:
- module.fail_json(msg='error running gluster (%s) command (rc=%d): %s' %
- (' '.join(args), rc, out or err), exception=traceback.format_exc())
- except Exception as e:
- module.fail_json(msg='error running gluster (%s) command: %s' % (' '.join(args),
- to_native(e)), exception=traceback.format_exc())
- return out
-
-
-def get_self_heal_status(name):
- out = run_gluster(['volume', 'heal', name, 'info'], environ_update=dict(LANG='C', LC_ALL='C', LC_MESSAGES='C'))
- raw_out = out.split("\n")
- heal_info = []
- # return files that still need healing.
- for line in raw_out:
- if 'Brick' in line:
- br_dict = {}
- br_dict['brick'] = line.strip().strip("Brick")
- elif 'Status' in line:
- br_dict['status'] = line.split(":")[1].strip()
- elif 'Number' in line:
- br_dict['no_of_entries'] = line.split(":")[1].strip()
- elif line.startswith('/') or line.startswith('<') or '\n' in line:
- continue
- else:
- br_dict and heal_info.append(br_dict)
- br_dict = {}
- return heal_info
-
-
-def get_rebalance_status(name):
- out = run_gluster(['volume', 'rebalance', name, 'status'], environ_update=dict(LANG='C', LC_ALL='C', LC_MESSAGES='C'))
- raw_out = out.split("\n")
- rebalance_status = []
- # return the files that are either still 'in progress' state or 'completed'.
- for line in raw_out:
- line = " ".join(line.split())
- line_vals = line.split(" ")
- if line_vals[0].startswith('-') or line_vals[0].startswith('Node'):
- continue
- node_dict = {}
- if len(line_vals) == 1 or len(line_vals) == 4:
- continue
- node_dict['node'] = line_vals[0]
- node_dict['rebalanced_files'] = line_vals[1]
- node_dict['failures'] = line_vals[4]
- if 'in progress' in line:
- node_dict['status'] = line_vals[5] + line_vals[6]
- rebalance_status.append(node_dict)
- elif 'completed' in line:
- node_dict['status'] = line_vals[5]
- rebalance_status.append(node_dict)
- return rebalance_status
-
-
-def is_invalid_gluster_version(module, required_version):
- cmd = module.get_bin_path('gluster', True) + ' --version'
- result = module.run_command(cmd)
- ver_line = result[1].split('\n')[0]
- version = ver_line.split(' ')[1]
- # If the installed version is less than 3.2, it is an invalid version
- # return True
- return LooseVersion(version) < LooseVersion(required_version)
-
-
-def main():
- global module
- global glusterbin
- module = AnsibleModule(
- argument_spec=dict(
- name=dict(type='str', required=True, aliases=['volume']),
- status_filter=dict(type='str', default='self-heal', choices=['self-heal', 'rebalance']),
- ),
- )
- is_old_facts = module._name == 'gluster_heal_facts'
- if is_old_facts:
- module.deprecate("The 'gluster_heal_facts' module has been renamed to 'gluster_heal_info', "
- "and the renamed one no longer returns ansible_facts", version='2.13')
-
- glusterbin = module.get_bin_path('gluster', True)
- required_version = "3.2"
- status_filter = module.params['status_filter']
- volume_name = module.params['name']
- heal_info = ''
- rebalance_status = ''
-
- # Verify if required GlusterFS version is installed
- if is_invalid_gluster_version(module, required_version):
- module.fail_json(msg="GlusterFS version > %s is required" %
- required_version)
-
- try:
- if status_filter == "self-heal":
- heal_info = get_self_heal_status(volume_name)
- elif status_filter == "rebalance":
- rebalance_status = get_rebalance_status(volume_name)
- except Exception as e:
- module.fail_json(msg='Error retrieving status: %s' % e, exception=traceback.format_exc())
-
- facts = {}
- facts['glusterfs'] = {'volume': volume_name, 'status_filter': status_filter, 'heal_info': heal_info, 'rebalance': rebalance_status}
-
- if is_old_facts:
- module.exit_json(ansible_facts=facts)
- else:
- module.exit_json(**facts)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/glusterfs/gluster_peer.py b/lib/ansible/modules/storage/glusterfs/gluster_peer.py
deleted file mode 100644
index 987dc78b4c..0000000000
--- a/lib/ansible/modules/storage/glusterfs/gluster_peer.py
+++ /dev/null
@@ -1,176 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-#
-# Copyright 2015 Nandaja Varma <nvarma@redhat.com>
-# Copyright 2018 Red Hat, 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
----
-module: gluster_peer
-short_description: Attach/Detach peers to/from the cluster
-description:
- - Create or diminish a GlusterFS trusted storage pool. A set of nodes can be
- added into an existing trusted storage pool or a new storage pool can be
- formed. Or, nodes can be removed from an existing trusted storage pool.
-version_added: "2.6"
-author: Sachidananda Urs (@sac)
-options:
- state:
- choices: ["present", "absent"]
- default: "present"
- description:
- - Determines whether the nodes should be attached to the pool or
- removed from the pool. If the state is present, nodes will be
- attached to the pool. If state is absent, nodes will be detached
- from the pool.
- required: true
- nodes:
- description:
- - List of nodes that have to be probed into the pool.
- required: true
- force:
- type: bool
- default: "false"
- description:
- - Applicable only while removing the nodes from the pool. gluster
- will refuse to detach a node from the pool if any one of the node
- is down, in such cases force can be used.
-requirements:
- - GlusterFS > 3.2
-notes:
- - This module does not support check mode.
-'''
-
-EXAMPLES = '''
-- name: Create a trusted storage pool
- gluster_peer:
- state: present
- nodes:
- - 10.0.1.5
- - 10.0.1.10
-
-- name: Delete a node from the trusted storage pool
- gluster_peer:
- state: absent
- nodes:
- - 10.0.1.10
-
-- name: Delete a node from the trusted storage pool by force
- gluster_peer:
- state: absent
- nodes:
- - 10.0.0.1
- force: true
-'''
-
-RETURN = '''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from distutils.version import LooseVersion
-
-
-class Peer(object):
- def __init__(self, module):
- self.module = module
- self.state = self.module.params['state']
- self.nodes = self.module.params['nodes']
- self.glustercmd = self.module.get_bin_path('gluster', True)
- self.lang = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C')
- self.action = ''
- self.force = ''
-
- def gluster_peer_ops(self):
- if not self.nodes:
- self.module.fail_json(msg="nodes list cannot be empty")
- self.force = 'force' if self.module.params.get('force') else ''
- if self.state == 'present':
- self.nodes = self.get_to_be_probed_hosts(self.nodes)
- self.action = 'probe'
- # In case of peer probe, we do not need `force'
- self.force = ''
- else:
- self.action = 'detach'
- self.call_peer_commands()
-
- def get_to_be_probed_hosts(self, hosts):
- peercmd = [self.glustercmd, 'pool', 'list', '--mode=script']
- rc, output, err = self.module.run_command(peercmd,
- environ_update=self.lang)
- peers_in_cluster = [line.split('\t')[1].strip() for
- line in filter(None, output.split('\n')[1:])]
- try:
- peers_in_cluster.remove('localhost')
- except ValueError:
- # It is ok not to have localhost in list
- pass
- hosts_to_be_probed = [host for host in hosts if host not in
- peers_in_cluster]
- return hosts_to_be_probed
-
- def call_peer_commands(self):
- result = {}
- result['msg'] = ''
- result['changed'] = False
-
- for node in self.nodes:
- peercmd = [self.glustercmd, 'peer', self.action, node, '--mode=script']
- if self.force:
- peercmd.append(self.force)
- rc, out, err = self.module.run_command(peercmd,
- environ_update=self.lang)
- if rc:
- result['rc'] = rc
- result['msg'] = err
- # Fail early, do not wait for the loop to finish
- self.module.fail_json(**result)
- else:
- if 'already in peer' in out or \
- 'localhost not needed' in out:
- result['changed'] |= False
- else:
- result['changed'] = True
- self.module.exit_json(**result)
-
-
-def main():
- module = AnsibleModule(
- argument_spec=dict(
- force=dict(type='bool', required=False),
- nodes=dict(type='list', required=True),
- state=dict(type='str', choices=['absent', 'present'],
- default='present'),
- ),
- supports_check_mode=False
- )
- pops = Peer(module)
- required_version = "3.2"
- # Verify if required GlusterFS version is installed
- if is_invalid_gluster_version(module, required_version):
- module.fail_json(msg="GlusterFS version > %s is required" %
- required_version)
- pops.gluster_peer_ops()
-
-
-def is_invalid_gluster_version(module, required_version):
- cmd = module.get_bin_path('gluster', True) + ' --version'
- result = module.run_command(cmd)
- ver_line = result[1].split('\n')[0]
- version = ver_line.split(' ')[1]
- # If the installed version is less than 3.2, it is an invalid version
- # return True
- return LooseVersion(version) < LooseVersion(required_version)
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/ansible/modules/storage/glusterfs/gluster_volume.py b/lib/ansible/modules/storage/glusterfs/gluster_volume.py
deleted file mode 100644
index 30611e723b..0000000000
--- a/lib/ansible/modules/storage/glusterfs/gluster_volume.py
+++ /dev/null
@@ -1,611 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2014, Taneli Leppä <taneli@crasman.fi>
-# 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
-module: gluster_volume
-short_description: Manage GlusterFS volumes
-description:
- - Create, remove, start, stop and tune GlusterFS volumes
-version_added: '1.9'
-options:
- name:
- description:
- - The volume name.
- required: true
- aliases: ['volume']
- state:
- description:
- - Use present/absent ensure if a volume exists or not.
- Use started/stopped to control its availability.
- required: true
- choices: ['absent', 'present', 'started', 'stopped']
- cluster:
- description:
- - List of hosts to use for probing and brick setup.
- host:
- description:
- - Override local hostname (for peer probing purposes).
- replicas:
- description:
- - Replica count for volume.
- arbiters:
- description:
- - Arbiter count for volume.
- version_added: '2.3'
- stripes:
- description:
- - Stripe count for volume.
- disperses:
- description:
- - Disperse count for volume.
- version_added: '2.2'
- redundancies:
- description:
- - Redundancy count for volume.
- version_added: '2.2'
- transport:
- description:
- - Transport type for volume.
- default: tcp
- choices: [ tcp, rdma, 'tcp,rdma' ]
- bricks:
- description:
- - Brick paths on servers. Multiple brick paths can be separated by commas.
- aliases: [ brick ]
- start_on_create:
- description:
- - Controls whether the volume is started after creation or not.
- type: bool
- default: 'yes'
- rebalance:
- description:
- - Controls whether the cluster is rebalanced after changes.
- type: bool
- default: 'no'
- directory:
- description:
- - Directory for limit-usage.
- options:
- description:
- - A dictionary/hash with options/settings for the volume.
- quota:
- description:
- - Quota value for limit-usage (be sure to use 10.0MB instead of 10MB, see quota list).
- force:
- description:
- - If brick is being created in the root partition, module will fail.
- Set force to true to override this behaviour.
- type: bool
-notes:
- - Requires cli tools for GlusterFS on servers.
- - Will add new bricks, but not remove them.
-author:
-- Taneli Leppä (@rosmo)
-"""
-
-EXAMPLES = """
-- name: create gluster volume
- gluster_volume:
- state: present
- name: test1
- bricks: /bricks/brick1/g1
- rebalance: yes
- cluster:
- - 192.0.2.10
- - 192.0.2.11
- run_once: true
-
-- name: tune
- gluster_volume:
- state: present
- name: test1
- options:
- performance.cache-size: 256MB
-
-- name: Set multiple options on GlusterFS volume
- gluster_volume:
- state: present
- name: test1
- options:
- { performance.cache-size: 128MB,
- write-behind: 'off',
- quick-read: 'on'
- }
-
-- name: start gluster volume
- gluster_volume:
- state: started
- name: test1
-
-- name: limit usage
- gluster_volume:
- state: present
- name: test1
- directory: /foo
- quota: 20.0MB
-
-- name: stop gluster volume
- gluster_volume:
- state: stopped
- name: test1
-
-- name: remove gluster volume
- gluster_volume:
- state: absent
- name: test1
-
-- name: create gluster volume with multiple bricks
- gluster_volume:
- state: present
- name: test2
- bricks: /bricks/brick1/g2,/bricks/brick2/g2
- cluster:
- - 192.0.2.10
- - 192.0.2.11
- run_once: true
-
-- name: Remove the bricks from gluster volume
- gluster_volume:
- state: present
- name: testvol
- bricks: /bricks/brick1/b1,/bricks/brick2/b2
- cluster:
- - 10.70.42.85
- force: true
- run_once: true
-
-- name: Reduce cluster configuration
- gluster_volume:
- state: present
- name: testvol
- bricks: /bricks/brick3/b1,/bricks/brick4/b2
- replicas: 2
- cluster:
- - 10.70.42.85
- force: true
- run_once: true
-"""
-
-import re
-import socket
-import time
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-
-glusterbin = ''
-
-
-def run_gluster(gargs, **kwargs):
- global glusterbin
- global module
- args = [glusterbin, '--mode=script']
- args.extend(gargs)
- try:
- rc, out, err = module.run_command(args, **kwargs)
- if rc != 0:
- module.fail_json(msg='error running gluster (%s) command (rc=%d): %s' %
- (' '.join(args), rc, out or err), exception=traceback.format_exc())
- except Exception as e:
- module.fail_json(msg='error running gluster (%s) command: %s' % (' '.join(args),
- to_native(e)), exception=traceback.format_exc())
- return out
-
-
-def run_gluster_nofail(gargs, **kwargs):
- global glusterbin
- global module
- args = [glusterbin]
- args.extend(gargs)
- rc, out, err = module.run_command(args, **kwargs)
- if rc != 0:
- return None
- return out
-
-
-def get_peers():
- out = run_gluster(['peer', 'status'])
- peers = {}
- hostname = None
- uuid = None
- state = None
- shortNames = False
- for row in out.split('\n'):
- if ': ' in row:
- key, value = row.split(': ')
- if key.lower() == 'hostname':
- hostname = value
- shortNames = False
- if key.lower() == 'uuid':
- uuid = value
- if key.lower() == 'state':
- state = value
- peers[hostname] = [uuid, state]
- elif row.lower() == 'other names:':
- shortNames = True
- elif row != '' and shortNames is True:
- peers[row] = [uuid, state]
- elif row == '':
- shortNames = False
- return peers
-
-
-def get_volumes():
- out = run_gluster(['volume', 'info'])
-
- volumes = {}
- volume = {}
- for row in out.split('\n'):
- if ': ' in row:
- key, value = row.split(': ')
- if key.lower() == 'volume name':
- volume['name'] = value
- volume['options'] = {}
- volume['quota'] = False
- if key.lower() == 'volume id':
- volume['id'] = value
- if key.lower() == 'status':
- volume['status'] = value
- if key.lower() == 'transport-type':
- volume['transport'] = value
- if value.lower().endswith(' (arbiter)'):
- if 'arbiters' not in volume:
- volume['arbiters'] = []
- value = value[:-10]
- volume['arbiters'].append(value)
- elif key.lower() == 'number of bricks':
- volume['replicas'] = value[-1:]
- if key.lower() != 'bricks' and key.lower()[:5] == 'brick':
- if 'bricks' not in volume:
- volume['bricks'] = []
- volume['bricks'].append(value)
- # Volume options
- if '.' in key:
- if 'options' not in volume:
- volume['options'] = {}
- volume['options'][key] = value
- if key == 'features.quota' and value == 'on':
- volume['quota'] = True
- else:
- if row.lower() != 'bricks:' and row.lower() != 'options reconfigured:':
- if len(volume) > 0:
- volumes[volume['name']] = volume
- volume = {}
- return volumes
-
-
-def get_quotas(name, nofail):
- quotas = {}
- if nofail:
- out = run_gluster_nofail(['volume', 'quota', name, 'list'])
- if not out:
- return quotas
- else:
- out = run_gluster(['volume', 'quota', name, 'list'])
- for row in out.split('\n'):
- if row[:1] == '/':
- q = re.split(r'\s+', row)
- quotas[q[0]] = q[1]
- return quotas
-
-
-def wait_for_peer(host):
- for x in range(0, 4):
- peers = get_peers()
- if host in peers and peers[host][1].lower().find('peer in cluster') != -1:
- return True
- time.sleep(1)
- return False
-
-
-def probe(host, myhostname):
- global module
- out = run_gluster(['peer', 'probe', host])
- if out.find('localhost') == -1 and not wait_for_peer(host):
- module.fail_json(msg='failed to probe peer %s on %s' % (host, myhostname))
-
-
-def probe_all_peers(hosts, peers, myhostname):
- for host in hosts:
- host = host.strip() # Clean up any extra space for exact comparison
- if host not in peers:
- probe(host, myhostname)
-
-
-def create_volume(name, stripe, replica, arbiter, disperse, redundancy, transport, hosts, bricks, force):
- args = ['volume', 'create']
- args.append(name)
- if stripe:
- args.append('stripe')
- args.append(str(stripe))
- if replica:
- args.append('replica')
- args.append(str(replica))
- if arbiter:
- args.append('arbiter')
- args.append(str(arbiter))
- if disperse:
- args.append('disperse')
- args.append(str(disperse))
- if redundancy:
- args.append('redundancy')
- args.append(str(redundancy))
- args.append('transport')
- args.append(transport)
- for brick in bricks:
- for host in hosts:
- args.append(('%s:%s' % (host, brick)))
- if force:
- args.append('force')
- run_gluster(args)
-
-
-def start_volume(name):
- run_gluster(['volume', 'start', name])
-
-
-def stop_volume(name):
- run_gluster(['volume', 'stop', name])
-
-
-def set_volume_option(name, option, parameter):
- run_gluster(['volume', 'set', name, option, parameter])
-
-
-def add_bricks(name, new_bricks, stripe, replica, force):
- args = ['volume', 'add-brick', name]
- if stripe:
- args.append('stripe')
- args.append(str(stripe))
- if replica:
- args.append('replica')
- args.append(str(replica))
- args.extend(new_bricks)
- if force:
- args.append('force')
- run_gluster(args)
-
-
-def remove_bricks(name, removed_bricks, force):
- # max-tries=12 with default_interval=10 secs
- max_tries = 12
- retries = 0
- success = False
- args = ['volume', 'remove-brick', name]
- args.extend(removed_bricks)
- # create a copy of args to use for commit operation
- args_c = args[:]
- args.append('start')
- run_gluster(args)
- # remove-brick operation needs to be followed by commit operation.
- if not force:
- module.fail_json(msg="Force option is mandatory.")
- else:
- while retries < max_tries:
- last_brick = removed_bricks[-1]
- out = run_gluster(['volume', 'remove-brick', name, last_brick, 'status'])
- for row in out.split('\n')[1:]:
- if 'completed' in row:
- # remove-brick successful, call commit operation.
- args_c.append('commit')
- out = run_gluster(args_c)
- success = True
- break
- else:
- time.sleep(10)
- if success:
- break
- retries += 1
- if not success:
- # remove-brick still in process, needs to be committed after completion.
- module.fail_json(msg="Exceeded number of tries, check remove-brick status.\n"
- "Commit operation needs to be followed.")
-
-
-def reduce_config(name, removed_bricks, replicas, force):
- out = run_gluster(['volume', 'heal', name, 'info'])
- summary = out.split("\n")
- for line in summary:
- if 'Number' in line and int(line.split(":")[1].strip()) != 0:
- module.fail_json(msg="Operation aborted, self-heal in progress.")
- args = ['volume', 'remove-brick', name, 'replica', replicas]
- args.extend(removed_bricks)
- if force:
- args.append('force')
- else:
- module.fail_json(msg="Force option is mandatory")
- run_gluster(args)
-
-
-def do_rebalance(name):
- run_gluster(['volume', 'rebalance', name, 'start'])
-
-
-def enable_quota(name):
- run_gluster(['volume', 'quota', name, 'enable'])
-
-
-def set_quota(name, directory, value):
- run_gluster(['volume', 'quota', name, 'limit-usage', directory, value])
-
-
-def main():
- # MAIN
-
- global module
- module = AnsibleModule(
- argument_spec=dict(
- name=dict(type='str', required=True, aliases=['volume']),
- state=dict(type='str', required=True, choices=['absent', 'started', 'stopped', 'present']),
- cluster=dict(type='list'),
- host=dict(type='str'),
- stripes=dict(type='int'),
- replicas=dict(type='int'),
- arbiters=dict(type='int'),
- disperses=dict(type='int'),
- redundancies=dict(type='int'),
- transport=dict(type='str', default='tcp', choices=['tcp', 'rdma', 'tcp,rdma']),
- bricks=dict(type='str', aliases=['brick']),
- start_on_create=dict(type='bool', default=True),
- rebalance=dict(type='bool', default=False),
- options=dict(type='dict', default={}),
- quota=dict(type='str'),
- directory=dict(type='str'),
- force=dict(type='bool', default=False),
- ),
- )
-
- global glusterbin
- glusterbin = module.get_bin_path('gluster', True)
-
- changed = False
-
- action = module.params['state']
- volume_name = module.params['name']
- cluster = module.params['cluster']
- brick_paths = module.params['bricks']
- stripes = module.params['stripes']
- replicas = module.params['replicas']
- arbiters = module.params['arbiters']
- disperses = module.params['disperses']
- redundancies = module.params['redundancies']
- transport = module.params['transport']
- myhostname = module.params['host']
- start_on_create = module.boolean(module.params['start_on_create'])
- rebalance = module.boolean(module.params['rebalance'])
- force = module.boolean(module.params['force'])
-
- if not myhostname:
- myhostname = socket.gethostname()
-
- # Clean up if last element is empty. Consider that yml can look like this:
- # cluster="{% for host in groups['glusterfs'] %}{{ hostvars[host]['private_ip'] }},{% endfor %}"
- if cluster is not None and len(cluster) > 1 and cluster[-1] == '':
- cluster = cluster[0:-1]
-
- if cluster is None:
- cluster = []
-
- if brick_paths is not None and "," in brick_paths:
- brick_paths = brick_paths.split(",")
- else:
- brick_paths = [brick_paths]
-
- options = module.params['options']
- quota = module.params['quota']
- directory = module.params['directory']
-
- # get current state info
- peers = get_peers()
- volumes = get_volumes()
- quotas = {}
- if volume_name in volumes and volumes[volume_name]['quota'] and volumes[volume_name]['status'].lower() == 'started':
- quotas = get_quotas(volume_name, True)
-
- # do the work!
- if action == 'absent':
- if volume_name in volumes:
- if volumes[volume_name]['status'].lower() != 'stopped':
- stop_volume(volume_name)
- run_gluster(['volume', 'delete', volume_name])
- changed = True
-
- if action == 'present':
- probe_all_peers(cluster, peers, myhostname)
-
- # create if it doesn't exist
- if volume_name not in volumes:
- create_volume(volume_name, stripes, replicas, arbiters, disperses, redundancies, transport, cluster, brick_paths, force)
- volumes = get_volumes()
- changed = True
-
- if volume_name in volumes:
- if volumes[volume_name]['status'].lower() != 'started' and start_on_create:
- start_volume(volume_name)
- changed = True
-
- # switch bricks
- new_bricks = []
- removed_bricks = []
- all_bricks = []
- bricks_in_volume = volumes[volume_name]['bricks']
-
- for node in cluster:
- for brick_path in brick_paths:
- brick = '%s:%s' % (node, brick_path)
- all_bricks.append(brick)
- if brick not in bricks_in_volume:
- new_bricks.append(brick)
-
- if not new_bricks and len(all_bricks) > 0 and \
- len(all_bricks) < len(bricks_in_volume):
- for brick in bricks_in_volume:
- if brick not in all_bricks:
- removed_bricks.append(brick)
-
- if new_bricks:
- add_bricks(volume_name, new_bricks, stripes, replicas, force)
- changed = True
-
- if removed_bricks:
- if replicas and int(replicas) < int(volumes[volume_name]['replicas']):
- reduce_config(volume_name, removed_bricks, str(replicas), force)
- else:
- remove_bricks(volume_name, removed_bricks, force)
- changed = True
-
- # handle quotas
- if quota:
- if not volumes[volume_name]['quota']:
- enable_quota(volume_name)
- quotas = get_quotas(volume_name, False)
- if directory not in quotas or quotas[directory] != quota:
- set_quota(volume_name, directory, quota)
- changed = True
-
- # set options
- for option in options.keys():
- if option not in volumes[volume_name]['options'] or volumes[volume_name]['options'][option] != options[option]:
- set_volume_option(volume_name, option, options[option])
- changed = True
-
- else:
- module.fail_json(msg='failed to create volume %s' % volume_name)
-
- if action != 'absent' and volume_name not in volumes:
- module.fail_json(msg='volume not found %s' % volume_name)
-
- if action == 'started':
- if volumes[volume_name]['status'].lower() != 'started':
- start_volume(volume_name)
- changed = True
-
- if action == 'stopped':
- if volumes[volume_name]['status'].lower() != 'stopped':
- stop_volume(volume_name)
- changed = True
-
- if changed:
- volumes = get_volumes()
- if rebalance:
- do_rebalance(volume_name)
-
- facts = {}
- facts['glusterfs'] = {'peers': peers, 'volumes': volumes, 'quotas': quotas}
-
- module.exit_json(changed=changed, ansible_facts=facts)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/hpe3par/ss_3par_cpg.py b/lib/ansible/modules/storage/hpe3par/ss_3par_cpg.py
deleted file mode 100644
index 0a5d7859b1..0000000000
--- a/lib/ansible/modules/storage/hpe3par/ss_3par_cpg.py
+++ /dev/null
@@ -1,299 +0,0 @@
-#!/usr/bin/python
-# Copyright: (c) 2018, Hewlett Packard Enterprise Development LP
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = r'''
----
-short_description: Manage HPE StoreServ 3PAR CPG
-author:
- - Farhan Nomani (@farhan7500)
- - Gautham P Hegde (@gautamphegde)
-description:
- - Create and delete CPG on HPE 3PAR.
-module: ss_3par_cpg
-options:
- cpg_name:
- description:
- - Name of the CPG.
- type: str
- required: true
- disk_type:
- choices:
- - FC
- - NL
- - SSD
- description:
- - Specifies that physical disks must have the specified device type.
- type: str
- domain:
- description:
- - Specifies the name of the domain in which the object will reside.
- type: str
- growth_increment:
- description:
- - Specifies the growth increment(in MiB, GiB or TiB) the amount of logical disk storage
- created on each auto-grow operation.
- type: str
- growth_limit:
- description:
- - Specifies that the autogrow operation is limited to the specified
- storage amount that sets the growth limit(in MiB, GiB or TiB).
- type: str
- growth_warning:
- description:
- - Specifies that the threshold(in MiB, GiB or TiB) of used logical disk space when exceeded
- results in a warning alert.
- type: str
- high_availability:
- choices:
- - PORT
- - CAGE
- - MAG
- description:
- - Specifies that the layout must support the failure of one port pair,
- one cage, or one magazine.
- type: str
- raid_type:
- choices:
- - R0
- - R1
- - R5
- - R6
- description:
- - Specifies the RAID type for the logical disk.
- type: str
- set_size:
- description:
- - Specifies the set size in the number of chunklets.
- type: int
- state:
- choices:
- - present
- - absent
- description:
- - Whether the specified CPG should exist or not.
- required: true
- type: str
- secure:
- description:
- - Specifies whether the certificate needs to be validated while communicating.
- type: bool
- default: no
-extends_documentation_fragment: hpe3par
-version_added: '2.8'
-'''
-
-
-EXAMPLES = r'''
- - name: Create CPG sample_cpg
- ss_3par_cpg:
- storage_system_ip: 10.10.10.1
- storage_system_username: username
- storage_system_password: password
- state: present
- cpg_name: sample_cpg
- domain: sample_domain
- growth_increment: 32000 MiB
- growth_limit: 64000 MiB
- growth_warning: 48000 MiB
- raid_type: R6
- set_size: 8
- high_availability: MAG
- disk_type: FC
- secure: no
-
- - name: Delete CPG sample_cpg
- ss_3par_cpg:
- storage_system_ip: 10.10.10.1
- storage_system_username: username
- storage_system_password: password
- state: absent
- cpg_name: sample_cpg
- secure: no
-'''
-
-RETURN = r'''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.storage.hpe3par import hpe3par
-try:
- from hpe3par_sdk import client
- from hpe3parclient import exceptions
- HAS_3PARCLIENT = True
-except ImportError:
- HAS_3PARCLIENT = False
-
-
-def validate_set_size(raid_type, set_size):
- if raid_type:
- set_size_array = client.HPE3ParClient.RAID_MAP[raid_type]['set_sizes']
- if set_size in set_size_array:
- return True
- return False
-
-
-def cpg_ldlayout_map(ldlayout_dict):
- if ldlayout_dict['RAIDType'] is not None and ldlayout_dict['RAIDType']:
- ldlayout_dict['RAIDType'] = client.HPE3ParClient.RAID_MAP[
- ldlayout_dict['RAIDType']]['raid_value']
- if ldlayout_dict['HA'] is not None and ldlayout_dict['HA']:
- ldlayout_dict['HA'] = getattr(
- client.HPE3ParClient, ldlayout_dict['HA'])
- return ldlayout_dict
-
-
-def create_cpg(
- client_obj,
- cpg_name,
- domain,
- growth_increment,
- growth_limit,
- growth_warning,
- raid_type,
- set_size,
- high_availability,
- disk_type):
- try:
- if not validate_set_size(raid_type, set_size):
- return (False, False, "Set size %s not part of RAID set %s" % (set_size, raid_type))
- if not client_obj.cpgExists(cpg_name):
-
- disk_patterns = []
- if disk_type:
- disk_type = getattr(client.HPE3ParClient, disk_type)
- disk_patterns = [{'diskType': disk_type}]
- ld_layout = {
- 'RAIDType': raid_type,
- 'setSize': set_size,
- 'HA': high_availability,
- 'diskPatterns': disk_patterns}
- ld_layout = cpg_ldlayout_map(ld_layout)
- if growth_increment is not None:
- growth_increment = hpe3par.convert_to_binary_multiple(
- growth_increment)
- if growth_limit is not None:
- growth_limit = hpe3par.convert_to_binary_multiple(
- growth_limit)
- if growth_warning is not None:
- growth_warning = hpe3par.convert_to_binary_multiple(
- growth_warning)
- optional = {
- 'domain': domain,
- 'growthIncrementMiB': growth_increment,
- 'growthLimitMiB': growth_limit,
- 'usedLDWarningAlertMiB': growth_warning,
- 'LDLayout': ld_layout}
- client_obj.createCPG(cpg_name, optional)
- else:
- return (True, False, "CPG already present")
- except exceptions.ClientException as e:
- return (False, False, "CPG creation failed | %s" % (e))
- return (True, True, "Created CPG %s successfully." % cpg_name)
-
-
-def delete_cpg(
- client_obj,
- cpg_name):
- try:
- if client_obj.cpgExists(cpg_name):
- client_obj.deleteCPG(cpg_name)
- else:
- return (True, False, "CPG does not exist")
- except exceptions.ClientException as e:
- return (False, False, "CPG delete failed | %s" % e)
- return (True, True, "Deleted CPG %s successfully." % cpg_name)
-
-
-def main():
- module = AnsibleModule(argument_spec=hpe3par.cpg_argument_spec(),
- required_together=[['raid_type', 'set_size']])
- if not HAS_3PARCLIENT:
- module.fail_json(msg='the python hpe3par_sdk library is required (https://pypi.org/project/hpe3par_sdk)')
-
- if len(module.params["cpg_name"]) < 1 or len(module.params["cpg_name"]) > 31:
- module.fail_json(msg="CPG name must be at least 1 character and not more than 31 characters")
-
- storage_system_ip = module.params["storage_system_ip"]
- storage_system_username = module.params["storage_system_username"]
- storage_system_password = module.params["storage_system_password"]
- cpg_name = module.params["cpg_name"]
- domain = module.params["domain"]
- growth_increment = module.params["growth_increment"]
- growth_limit = module.params["growth_limit"]
- growth_warning = module.params["growth_warning"]
- raid_type = module.params["raid_type"]
- set_size = module.params["set_size"]
- high_availability = module.params["high_availability"]
- disk_type = module.params["disk_type"]
- secure = module.params["secure"]
-
- wsapi_url = 'https://%s:8080/api/v1' % storage_system_ip
- try:
- client_obj = client.HPE3ParClient(wsapi_url, secure)
- except exceptions.SSLCertFailed:
- module.fail_json(msg="SSL Certificate Failed")
- except exceptions.ConnectionError:
- module.fail_json(msg="Connection Error")
- except exceptions.UnsupportedVersion:
- module.fail_json(msg="Unsupported WSAPI version")
- except Exception as e:
- module.fail_json(msg="Initializing client failed. %s" % e)
-
- if storage_system_username is None or storage_system_password is None:
- module.fail_json(msg="Storage system username or password is None")
- if cpg_name is None:
- module.fail_json(msg="CPG Name is None")
-
- # States
- if module.params["state"] == "present":
- try:
- client_obj.login(storage_system_username, storage_system_password)
- return_status, changed, msg = create_cpg(
- client_obj,
- cpg_name,
- domain,
- growth_increment,
- growth_limit,
- growth_warning,
- raid_type,
- set_size,
- high_availability,
- disk_type
- )
- except Exception as e:
- module.fail_json(msg="CPG create failed | %s" % e)
- finally:
- client_obj.logout()
-
- elif module.params["state"] == "absent":
- try:
- client_obj.login(storage_system_username, storage_system_password)
- return_status, changed, msg = delete_cpg(
- client_obj,
- cpg_name
- )
- except Exception as e:
- module.fail_json(msg="CPG create failed | %s" % e)
- finally:
- client_obj.logout()
-
- if return_status:
- module.exit_json(changed=changed, msg=msg)
- else:
- module.fail_json(msg=msg)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/ibm/ibm_sa_domain.py b/lib/ansible/modules/storage/ibm/ibm_sa_domain.py
deleted file mode 100644
index 6866ad2e04..0000000000
--- a/lib/ansible/modules/storage/ibm/ibm_sa_domain.py
+++ /dev/null
@@ -1,161 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2018, IBM CORPORATION
-# Author(s): Tzur Eliyahu <tzure@il.ibm.com>
-#
-# 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
-
-ANSIBLE_METADATA = {'status': ['preview'],
- 'supported_by': 'community',
- 'metadata_version': '1.1'}
-
-DOCUMENTATION = '''
----
-module: ibm_sa_domain
-short_description: Manages domains on IBM Spectrum Accelerate Family storage systems
-version_added: "2.8"
-
-description:
- - "This module can be used to add domains to or removes them from IBM Spectrum Accelerate Family storage systems."
-
-options:
- domain:
- description:
- - Name of the domain to be managed.
- required: true
- state:
- description:
- - The desired state of the domain.
- required: true
- default: "present"
- choices: [ "present", "absent" ]
- ldap_id:
- description:
- - ldap id to add to the domain.
- required: false
- size:
- description:
- - Size of the domain.
- required: false
- hard_capacity:
- description:
- - Hard capacity of the domain.
- required: false
- soft_capacity:
- description:
- - Soft capacity of the domain.
- required: false
- max_cgs:
- description:
- - Number of max cgs.
- required: false
- max_dms:
- description:
- - Number of max dms.
- required: false
- max_mirrors:
- description:
- - Number of max_mirrors.
- required: false
- max_pools:
- description:
- - Number of max_pools.
- required: false
- max_volumes:
- description:
- - Number of max_volumes.
- required: false
- perf_class:
- description:
- - Add the domain to a performance class.
- required: false
-
-extends_documentation_fragment:
- - ibm_storage
-
-author:
- - Tzur Eliyahu (@tzure)
-'''
-
-EXAMPLES = '''
-- name: Define new domain.
- ibm_sa_domain:
- domain: domain_name
- size: domain_size
- state: present
- username: admin
- password: secret
- endpoints: hostdev-system
-
-- name: Delete domain.
- ibm_sa_domain:
- domain: domain_name
- state: absent
- username: admin
- password: secret
- endpoints: hostdev-system
-'''
-RETURN = '''
-msg:
- description: module return status.
- returned: as needed
- type: str
- sample: "domain 'domain_name' created successfully."
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ibm_sa_utils import execute_pyxcli_command, \
- connect_ssl, spectrum_accelerate_spec, is_pyxcli_installed
-
-
-def main():
- argument_spec = spectrum_accelerate_spec()
- argument_spec.update(
- dict(
- state=dict(default='present', choices=['present', 'absent']),
- domain=dict(required=True),
- size=dict(),
- max_dms=dict(),
- max_cgs=dict(),
- ldap_id=dict(),
- max_mirrors=dict(),
- max_pools=dict(),
- max_volumes=dict(),
- perf_class=dict(),
- hard_capacity=dict(),
- soft_capacity=dict()
- )
- )
-
- module = AnsibleModule(argument_spec)
-
- is_pyxcli_installed(module)
-
- xcli_client = connect_ssl(module)
- domain = xcli_client.cmd.domain_list(
- domain=module.params['domain']).as_single_element
- state = module.params['state']
-
- state_changed = False
- msg = 'Domain \'{0}\''.format(module.params['domain'])
- if state == 'present' and not domain:
- state_changed = execute_pyxcli_command(
- module, 'domain_create', xcli_client)
- msg += " created successfully."
- elif state == 'absent' and domain:
- state_changed = execute_pyxcli_command(
- module, 'domain_delete', xcli_client)
- msg += " deleted successfully."
- else:
- msg += " state unchanged."
-
- module.exit_json(changed=state_changed, msg=msg)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/ibm/ibm_sa_host.py b/lib/ansible/modules/storage/ibm/ibm_sa_host.py
deleted file mode 100644
index 483b7ce58b..0000000000
--- a/lib/ansible/modules/storage/ibm/ibm_sa_host.py
+++ /dev/null
@@ -1,123 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2018 IBM CORPORATION
-# Author(s): Tzur Eliyahu <tzure@il.ibm.com>
-#
-# 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
-
-ANSIBLE_METADATA = {'status': ['preview'],
- 'supported_by': 'community',
- 'metadata_version': '1.1'}
-
-DOCUMENTATION = '''
----
-module: ibm_sa_host
-short_description: Adds hosts to or removes them from IBM Spectrum Accelerate Family storage systems.
-version_added: "2.7"
-
-description:
- - "This module adds hosts to or removes them from IBM Spectrum Accelerate Family storage systems."
-
-options:
- host:
- description:
- - Host name.
- required: true
- state:
- description:
- - Host state.
- required: true
- default: "present"
- choices: [ "present", "absent" ]
- cluster:
- description:
- - The name of the cluster to include the host.
- required: false
- domain:
- description:
- - The domains the cluster will be attached to.
- To include more than one domain,
- separate domain names with commas.
- To include all existing domains, use an asterisk ("*").
- required: false
- iscsi_chap_name:
- description:
- - The host's CHAP name identifier
- required: false
- iscsi_chap_secret:
- description:
- - The password of the initiator used to
- authenticate to the system when CHAP is enable
- required: false
-
-extends_documentation_fragment:
- - ibm_storage
-
-author:
- - Tzur Eliyahu (@tzure)
-'''
-
-EXAMPLES = '''
-- name: Define new host.
- ibm_sa_host:
- host: host_name
- state: present
- username: admin
- password: secret
- endpoints: hostdev-system
-
-- name: Delete host.
- ibm_sa_host:
- host: host_name
- state: absent
- username: admin
- password: secret
- endpoints: hostdev-system
-'''
-RETURN = '''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ibm_sa_utils import execute_pyxcli_command, \
- connect_ssl, spectrum_accelerate_spec, is_pyxcli_installed
-
-
-def main():
- argument_spec = spectrum_accelerate_spec()
- argument_spec.update(
- dict(
- state=dict(default='present', choices=['present', 'absent']),
- host=dict(required=True),
- cluster=dict(),
- domain=dict(),
- iscsi_chap_name=dict(),
- iscsi_chap_secret=dict()
- )
- )
-
- module = AnsibleModule(argument_spec)
-
- is_pyxcli_installed(module)
-
- xcli_client = connect_ssl(module)
- host = xcli_client.cmd.host_list(
- host=module.params['host']).as_single_element
- state = module.params['state']
-
- state_changed = False
- if state == 'present' and not host:
- state_changed = execute_pyxcli_command(
- module, 'host_define', xcli_client)
- elif state == 'absent' and host:
- state_changed = execute_pyxcli_command(
- module, 'host_delete', xcli_client)
-
- module.exit_json(changed=state_changed)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/ibm/ibm_sa_host_ports.py b/lib/ansible/modules/storage/ibm/ibm_sa_host_ports.py
deleted file mode 100644
index 7523e1e2b3..0000000000
--- a/lib/ansible/modules/storage/ibm/ibm_sa_host_ports.py
+++ /dev/null
@@ -1,132 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2018 IBM CORPORATION
-# Author(s): Tzur Eliyahu <tzure@il.ibm.com>
-#
-# 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
-
-ANSIBLE_METADATA = {'status': ['preview'],
- 'supported_by': 'community',
- 'metadata_version': '1.1'}
-
-DOCUMENTATION = '''
----
-module: ibm_sa_host_ports
-short_description: Add host ports on IBM Spectrum Accelerate Family storage systems.
-version_added: "2.8"
-
-description:
- - "This module adds ports to or removes them from the hosts
- on IBM Spectrum Accelerate Family storage systems."
-
-options:
- host:
- description:
- - Host name.
- required: true
- state:
- description:
- - Host ports state.
- required: true
- default: "present"
- choices: [ "present", "absent" ]
- iscsi_name:
- description:
- - iSCSI initiator name.
- required: false
- fcaddress:
- description:
- - Fiber channel address.
- required: false
- num_of_visible_targets:
- description:
- - Number of visible targets.
- required: false
-
-extends_documentation_fragment:
- - ibm_storage
-
-author:
- - Tzur Eliyahu (@tzure)
-'''
-
-EXAMPLES = '''
-- name: Add ports for host.
- ibm_sa_host_ports:
- host: test_host
- iscsi_name: iqn.1994-05.com***
- username: admin
- password: secret
- endpoints: hostdev-system
- state: present
-
-- name: Remove ports for host.
- ibm_sa_host_ports:
- host: test_host
- iscsi_name: iqn.1994-05.com***
- username: admin
- password: secret
- endpoints: hostdev-system
- state: absent
-
-'''
-RETURN = '''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ibm_sa_utils import (execute_pyxcli_command, connect_ssl,
- spectrum_accelerate_spec, is_pyxcli_installed)
-
-
-def main():
- argument_spec = spectrum_accelerate_spec()
- argument_spec.update(
- dict(
- state=dict(default='present', choices=['present', 'absent']),
- host=dict(required=True),
- iscsi_name=dict(),
- fcaddress=dict(),
- num_of_visible_targets=dict()
- )
- )
-
- module = AnsibleModule(argument_spec)
- is_pyxcli_installed(module)
-
- xcli_client = connect_ssl(module)
- # required args
- ports = []
- try:
- ports = xcli_client.cmd.host_list_ports(
- host=module.params.get('host')).as_list
- except Exception:
- pass
- state = module.params['state']
- port_exists = False
- ports = [port.get('port_name') for port in ports]
-
- fc_ports = (module.params.get('fcaddress')
- if module.params.get('fcaddress') else [])
- iscsi_ports = (module.params.get('iscsi_name')
- if module.params.get('iscsi_name') else [])
- for port in ports:
- if port in iscsi_ports or port in fc_ports:
- port_exists = True
- break
- state_changed = False
- if state == 'present' and not port_exists:
- state_changed = execute_pyxcli_command(
- module, 'host_add_port', xcli_client)
- if state == 'absent' and port_exists:
- state_changed = execute_pyxcli_command(
- module, 'host_remove_port', xcli_client)
-
- module.exit_json(changed=state_changed)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/ibm/ibm_sa_pool.py b/lib/ansible/modules/storage/ibm/ibm_sa_pool.py
deleted file mode 100644
index 3f7143e7b0..0000000000
--- a/lib/ansible/modules/storage/ibm/ibm_sa_pool.py
+++ /dev/null
@@ -1,120 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2018 IBM CORPORATION
-# Author(s): Tzur Eliyahu <tzure@il.ibm.com>
-#
-# 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
-
-ANSIBLE_METADATA = {'status': ['preview'],
- 'supported_by': 'community',
- 'metadata_version': '1.1'}
-
-DOCUMENTATION = '''
----
-module: ibm_sa_pool
-short_description: Handles pools on IBM Spectrum Accelerate Family storage systems.
-version_added: "2.7"
-
-description:
- - "This module creates or deletes pools to be used on IBM Spectrum Accelerate Family storage systems"
-
-options:
- pool:
- description:
- - Pool name.
- required: true
- state:
- description:
- - Pool state.
- required: true
- default: "present"
- choices: [ "present", "absent" ]
- size:
- description:
- - Pool size in GB
- required: false
- snapshot_size:
- description:
- - Pool snapshot size in GB
- required: false
- domain:
- description:
- - Adds the pool to the specified domain.
- required: false
- perf_class:
- description:
- - Assigns a perf_class to the pool.
- required: false
-
-extends_documentation_fragment:
- - ibm_storage
-
-author:
- - Tzur Eliyahu (@tzure)
-'''
-
-EXAMPLES = '''
-- name: Create new pool.
- ibm_sa_pool:
- name: pool_name
- size: 300
- state: present
- username: admin
- password: secret
- endpoints: hostdev-system
-
-- name: Delete pool.
- ibm_sa_pool:
- name: pool_name
- state: absent
- username: admin
- password: secret
- endpoints: hostdev-system
-'''
-RETURN = '''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ibm_sa_utils import execute_pyxcli_command, \
- connect_ssl, spectrum_accelerate_spec, is_pyxcli_installed
-
-
-def main():
- argument_spec = spectrum_accelerate_spec()
- argument_spec.update(
- dict(
- state=dict(default='present', choices=['present', 'absent']),
- pool=dict(required=True),
- size=dict(),
- snapshot_size=dict(),
- domain=dict(),
- perf_class=dict()
- )
- )
-
- module = AnsibleModule(argument_spec)
-
- is_pyxcli_installed(module)
-
- xcli_client = connect_ssl(module)
- pool = xcli_client.cmd.pool_list(
- pool=module.params['pool']).as_single_element
- state = module.params['state']
-
- state_changed = False
- if state == 'present' and not pool:
- state_changed = execute_pyxcli_command(
- module, 'pool_create', xcli_client)
- if state == 'absent' and pool:
- state_changed = execute_pyxcli_command(
- module, 'pool_delete', xcli_client)
-
- module.exit_json(changed=state_changed)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/ibm/ibm_sa_vol.py b/lib/ansible/modules/storage/ibm/ibm_sa_vol.py
deleted file mode 100644
index 1680901638..0000000000
--- a/lib/ansible/modules/storage/ibm/ibm_sa_vol.py
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2018 IBM CORPORATION
-# Author(s): Tzur Eliyahu <tzure@il.ibm.com>
-#
-# 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
-
-ANSIBLE_METADATA = {'status': ['preview'],
- 'supported_by': 'community',
- 'metadata_version': '1.1'}
-
-DOCUMENTATION = '''
----
-module: ibm_sa_vol
-short_description: Handle volumes on IBM Spectrum Accelerate Family storage systems.
-version_added: "2.7"
-
-description:
- - "This module creates or deletes volumes to be used on IBM Spectrum Accelerate Family storage systems."
-
-options:
- vol:
- description:
- - Volume name.
- required: true
- pool:
- description:
- - Volume pool.
- required: false
- state:
- description:
- - Volume state.
- required: true
- default: "present"
- choices: [ "present", "absent" ]
- size:
- description:
- - Volume size.
- required: false
-
-extends_documentation_fragment:
- - ibm_storage
-
-author:
- - Tzur Eliyahu (@tzure)
-'''
-
-EXAMPLES = '''
-- name: Create a new volume.
- ibm_sa_vol:
- vol: volume_name
- pool: pool_name
- size: 17
- state: present
- username: admin
- password: secret
- endpoints: hostdev-system
-
-- name: Delete an existing volume.
- ibm_sa_vol:
- vol: volume_name
- state: absent
- username: admin
- password: secret
- endpoints: hostdev-system
-'''
-RETURN = '''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ibm_sa_utils import execute_pyxcli_command, \
- connect_ssl, spectrum_accelerate_spec, is_pyxcli_installed
-
-
-def main():
- argument_spec = spectrum_accelerate_spec()
- argument_spec.update(
- dict(
- state=dict(default='present', choices=['present', 'absent']),
- vol=dict(required=True),
- pool=dict(),
- size=dict()
- )
- )
-
- module = AnsibleModule(argument_spec)
-
- is_pyxcli_installed(module)
-
- xcli_client = connect_ssl(module)
- # required args
- volume = xcli_client.cmd.vol_list(
- vol=module.params.get('vol')).as_single_element
- state = module.params['state']
-
- state_changed = False
- if state == 'present' and not volume:
- state_changed = execute_pyxcli_command(
- module, 'vol_create', xcli_client)
- elif state == 'absent' and volume:
- state_changed = execute_pyxcli_command(
- module, 'vol_delete', xcli_client)
-
- module.exit_json(changed=state_changed)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/ibm/ibm_sa_vol_map.py b/lib/ansible/modules/storage/ibm/ibm_sa_vol_map.py
deleted file mode 100644
index 750bac0b83..0000000000
--- a/lib/ansible/modules/storage/ibm/ibm_sa_vol_map.py
+++ /dev/null
@@ -1,140 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2018 IBM CORPORATION
-# Author(s): Tzur Eliyahu <tzure@il.ibm.com>
-#
-# 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
-
-ANSIBLE_METADATA = {'status': ['preview'],
- 'supported_by': 'community',
- 'metadata_version': '1.1'}
-
-DOCUMENTATION = '''
----
-module: ibm_sa_vol_map
-short_description: Handles volume mapping on IBM Spectrum Accelerate Family storage systems.
-version_added: "2.8"
-
-description:
- - "This module maps volumes to or unmaps them from the hosts on
- IBM Spectrum Accelerate Family storage systems."
-
-options:
- vol:
- description:
- - Volume name.
- required: true
- state:
- default: "present"
- choices: [ "present", "absent" ]
- description:
- - When the state is present the volume is mapped.
- When the state is absent, the volume is meant to be unmapped.
- required: true
- cluster:
- description:
- - Maps the volume to a cluster.
- required: false
- host:
- description:
- - Maps the volume to a host.
- required: false
- lun:
- description:
- - The LUN identifier.
- required: false
- override:
- description:
- - Overrides the existing volume mapping.
- required: false
-
-extends_documentation_fragment:
- - ibm_storage
-
-author:
- - Tzur Eliyahu (@tzure)
-'''
-
-EXAMPLES = '''
-- name: Map volume to host.
- ibm_sa_vol_map:
- vol: volume_name
- lun: 1
- host: host_name
- username: admin
- password: secret
- endpoints: hostdev-system
- state: present
-
-- name: Map volume to cluster.
- ibm_sa_vol_map:
- vol: volume_name
- lun: 1
- cluster: cluster_name
- username: admin
- password: secret
- endpoints: hostdev-system
- state: present
-
-- name: Unmap volume.
- ibm_sa_vol_map:
- host: host_name
- username: admin
- password: secret
- endpoints: hostdev-system
- state: absent
-'''
-RETURN = '''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.ibm_sa_utils import (execute_pyxcli_command,
- connect_ssl, spectrum_accelerate_spec, is_pyxcli_installed)
-
-
-def main():
- argument_spec = spectrum_accelerate_spec()
- argument_spec.update(
- dict(
- state=dict(default='present', choices=['present', 'absent']),
- vol=dict(required=True),
- lun=dict(),
- cluster=dict(),
- host=dict(),
- override=dict()
- )
- )
-
- module = AnsibleModule(argument_spec)
- is_pyxcli_installed(module)
-
- xcli_client = connect_ssl(module)
- # required args
- mapping = False
- try:
- mapped_hosts = xcli_client.cmd.vol_mapping_list(
- vol=module.params.get('vol')).as_list
- for host in mapped_hosts:
- if host['host'] == module.params.get("host", ""):
- mapping = True
- except Exception:
- pass
- state = module.params['state']
-
- state_changed = False
- if state == 'present' and not mapping:
- state_changed = execute_pyxcli_command(module, 'map_vol', xcli_client)
- if state == 'absent' and mapping:
- state_changed = execute_pyxcli_command(
- module, 'unmap_vol', xcli_client)
-
- module.exit_json(changed=state_changed)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/infinidat/infini_export.py b/lib/ansible/modules/storage/infinidat/infini_export.py
deleted file mode 100644
index 2f83e9534c..0000000000
--- a/lib/ansible/modules/storage/infinidat/infini_export.py
+++ /dev/null
@@ -1,196 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2016, Gregory Shulov (gregory.shulov@gmail.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: infini_export
-version_added: 2.3
-short_description: Create, Delete or Modify NFS Exports on Infinibox
-description:
- - This module creates, deletes or modifies NFS exports on Infinibox.
-author: Gregory Shulov (@GR360RY)
-options:
- name:
- description:
- - Export name. Should always start with C(/). (ex. name=/data)
- aliases: ['export', 'path']
- required: true
- state:
- description:
- - Creates/Modifies export when present and removes when absent.
- required: false
- default: "present"
- choices: [ "present", "absent" ]
- inner_path:
- description:
- - Internal path of the export.
- default: "/"
- client_list:
- description:
- - List of dictionaries with client entries. See examples.
- Check infini_export_client module to modify individual NFS client entries for export.
- default: "All Hosts(*), RW, no_root_squash: True"
- required: false
- filesystem:
- description:
- - Name of exported file system.
- required: true
-extends_documentation_fragment:
- - infinibox
-requirements:
- - munch
-'''
-
-EXAMPLES = '''
-- name: Export bar filesystem under foo pool as /data
- infini_export:
- name: /data01
- filesystem: foo
- user: admin
- password: secret
- system: ibox001
-
-- name: Export and specify client list explicitly
- infini_export:
- name: /data02
- filesystem: foo
- client_list:
- - client: 192.168.0.2
- access: RW
- no_root_squash: True
- - client: 192.168.0.100
- access: RO
- no_root_squash: False
- - client: 192.168.0.10-192.168.0.20
- access: RO
- no_root_squash: False
- system: ibox001
- user: admin
- password: secret
-'''
-
-RETURN = '''
-'''
-import traceback
-
-MUNCH_IMP_ERR = None
-try:
- from munch import unmunchify
- HAS_MUNCH = True
-except ImportError:
- MUNCH_IMP_ERR = traceback.format_exc()
- HAS_MUNCH = False
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.infinibox import HAS_INFINISDK, api_wrapper, get_system, infinibox_argument_spec
-
-
-def transform(d):
- return frozenset(d.items())
-
-
-@api_wrapper
-def get_filesystem(module, system):
- """Return Filesystem or None"""
- try:
- return system.filesystems.get(name=module.params['filesystem'])
- except Exception:
- return None
-
-
-@api_wrapper
-def get_export(module, filesystem, system):
- """Return export if found. When not found return None"""
-
- export = None
- exports_to_list = system.exports.to_list()
-
- for e in exports_to_list:
- if e.get_export_path() == module.params['name']:
- export = e
- break
-
- return export
-
-
-@api_wrapper
-def update_export(module, export, filesystem, system):
- """ Create new filesystem or update existing one"""
-
- changed = False
-
- name = module.params['name']
- client_list = module.params['client_list']
-
- if export is None:
- if not module.check_mode:
- export = system.exports.create(export_path=name, filesystem=filesystem)
- if client_list:
- export.update_permissions(client_list)
- changed = True
- else:
- if client_list:
- if set(map(transform, unmunchify(export.get_permissions()))) != set(map(transform, client_list)):
- if not module.check_mode:
- export.update_permissions(client_list)
- changed = True
-
- module.exit_json(changed=changed)
-
-
-@api_wrapper
-def delete_export(module, export):
- """ Delete file system"""
- if not module.check_mode:
- export.delete()
- module.exit_json(changed=True)
-
-
-def main():
- argument_spec = infinibox_argument_spec()
- argument_spec.update(
- dict(
- name=dict(required=True),
- state=dict(default='present', choices=['present', 'absent']),
- filesystem=dict(required=True),
- client_list=dict(type='list')
- )
- )
-
- module = AnsibleModule(argument_spec, supports_check_mode=True)
-
- if not HAS_INFINISDK:
- module.fail_json(msg=missing_required_lib('infinisdk'))
- if not HAS_MUNCH:
- module.fail_json(msg=missing_required_lib('munch'), exception=MUNCH_IMP_ERR)
-
- state = module.params['state']
- system = get_system(module)
- filesystem = get_filesystem(module, system)
- export = get_export(module, filesystem, system)
-
- if filesystem is None:
- module.fail_json(msg='Filesystem {0} not found'.format(module.params['filesystem']))
-
- if state == 'present':
- update_export(module, export, filesystem, system)
- elif export and state == 'absent':
- delete_export(module, export)
- elif export is None and state == 'absent':
- module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/infinidat/infini_export_client.py b/lib/ansible/modules/storage/infinidat/infini_export_client.py
deleted file mode 100644
index 22214841b6..0000000000
--- a/lib/ansible/modules/storage/infinidat/infini_export_client.py
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2016, Gregory Shulov (gregory.shulov@gmail.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: infini_export_client
-version_added: 2.3
-short_description: Create, Delete or Modify NFS Client(s) for existing exports on Infinibox
-description:
- - This module creates, deletes or modifys NFS client(s) for existing exports on Infinibox.
-author: Gregory Shulov (@GR360RY)
-options:
- client:
- description:
- - Client IP or Range. Ranges can be defined as follows
- 192.168.0.1-192.168.0.254.
- aliases: ['name']
- required: true
- state:
- description:
- - Creates/Modifies client when present and removes when absent.
- required: false
- default: "present"
- choices: [ "present", "absent" ]
- access_mode:
- description:
- - Read Write or Read Only Access.
- choices: [ "RW", "RO" ]
- default: RW
- required: false
- no_root_squash:
- description:
- - Don't squash root user to anonymous. Will be set to "no" on creation if not specified explicitly.
- type: bool
- default: no
- required: false
- export:
- description:
- - Name of the export.
- required: true
-extends_documentation_fragment:
- - infinibox
-requirements:
- - munch
-'''
-
-EXAMPLES = '''
-- name: Make sure nfs client 10.0.0.1 is configured for export. Allow root access
- infini_export_client:
- client: 10.0.0.1
- access_mode: RW
- no_root_squash: yes
- export: /data
- user: admin
- password: secret
- system: ibox001
-
-- name: Add multiple clients with RO access. Squash root privileges
- infini_export_client:
- client: "{{ item }}"
- access_mode: RO
- no_root_squash: no
- export: /data
- user: admin
- password: secret
- system: ibox001
- with_items:
- - 10.0.0.2
- - 10.0.0.3
-'''
-
-RETURN = '''
-'''
-import traceback
-
-MUNCH_IMP_ERR = None
-try:
- from munch import Munch, unmunchify
- HAS_MUNCH = True
-except ImportError:
- MUNCH_IMP_ERR = traceback.format_exc()
- HAS_MUNCH = False
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.infinibox import HAS_INFINISDK, api_wrapper, get_system, infinibox_argument_spec
-
-
-def transform(d):
- return frozenset(d.items())
-
-
-@api_wrapper
-def get_export(module, system):
- """Return export if found. Fail module if not found"""
-
- try:
- export = system.exports.get(export_path=module.params['export'])
- except Exception:
- module.fail_json(msg="Export with export path {0} not found".format(module.params['export']))
-
- return export
-
-
-@api_wrapper
-def update_client(module, export):
- """Update export client list"""
-
- changed = False
-
- client = module.params['client']
- access_mode = module.params['access_mode']
- no_root_squash = module.params['no_root_squash']
-
- client_list = export.get_permissions()
- client_not_in_list = True
-
- for index, item in enumerate(client_list):
- if item.client == client:
- client_not_in_list = False
- if item.access != access_mode:
- item.access = access_mode
- changed = True
- if item.no_root_squash is not no_root_squash:
- item.no_root_squash = no_root_squash
- changed = True
-
- # If access_mode and/or no_root_squash not passed as arguments to the module,
- # use access_mode with RW value and set no_root_squash to False
- if client_not_in_list:
- changed = True
- client_list.append(Munch(client=client, access=access_mode, no_root_squash=no_root_squash))
-
- if changed:
- for index, item in enumerate(client_list):
- client_list[index] = unmunchify(item)
- if not module.check_mode:
- export.update_permissions(client_list)
-
- module.exit_json(changed=changed)
-
-
-@api_wrapper
-def delete_client(module, export):
- """Update export client list"""
-
- changed = False
-
- client = module.params['client']
- client_list = export.get_permissions()
-
- for index, item in enumerate(client_list):
- if item.client == client:
- changed = True
- del client_list[index]
-
- if changed:
- for index, item in enumerate(client_list):
- client_list[index] = unmunchify(item)
- if not module.check_mode:
- export.update_permissions(client_list)
-
- module.exit_json(changed=changed)
-
-
-def main():
- argument_spec = infinibox_argument_spec()
- argument_spec.update(
- dict(
- client=dict(required=True),
- access_mode=dict(choices=['RO', 'RW'], default='RW'),
- no_root_squash=dict(type='bool', default='no'),
- state=dict(default='present', choices=['present', 'absent']),
- export=dict(required=True)
- )
- )
-
- module = AnsibleModule(argument_spec, supports_check_mode=True)
-
- if not HAS_INFINISDK:
- module.fail_json(msg=missing_required_lib('infinisdk'))
- if not HAS_MUNCH:
- module.fail_json(msg=missing_required_lib('munch'), exception=MUNCH_IMP_ERR)
-
- system = get_system(module)
- export = get_export(module, system)
-
- if module.params['state'] == 'present':
- update_client(module, export)
- else:
- delete_client(module, export)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/infinidat/infini_fs.py b/lib/ansible/modules/storage/infinidat/infini_fs.py
deleted file mode 100644
index 81f58d0e1e..0000000000
--- a/lib/ansible/modules/storage/infinidat/infini_fs.py
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2016, Gregory Shulov (gregory.shulov@gmail.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: infini_fs
-version_added: 2.3
-short_description: Create, Delete or Modify filesystems on Infinibox
-description:
- - This module creates, deletes or modifies filesystems on Infinibox.
-author: Gregory Shulov (@GR360RY)
-options:
- name:
- description:
- - File system name.
- required: true
- state:
- description:
- - Creates/Modifies file system when present or removes when absent.
- required: false
- default: present
- choices: [ "present", "absent" ]
- size:
- description:
- - File system size in MB, GB or TB units. See examples.
- required: false
- pool:
- description:
- - Pool that will host file system.
- required: true
-extends_documentation_fragment:
- - infinibox
-requirements:
- - capacity
-'''
-
-EXAMPLES = '''
-- name: Create new file system named foo under pool named bar
- infini_fs:
- name: foo
- size: 1TB
- pool: bar
- state: present
- user: admin
- password: secret
- system: ibox001
-'''
-
-RETURN = '''
-'''
-import traceback
-
-CAPACITY_IMP_ERR = None
-try:
- from capacity import KiB, Capacity
- HAS_CAPACITY = True
-except ImportError:
- CAPACITY_IMP_ERR = traceback.format_exc()
- HAS_CAPACITY = False
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.infinibox import HAS_INFINISDK, api_wrapper, get_system, infinibox_argument_spec
-
-
-@api_wrapper
-def get_pool(module, system):
- """Return Pool or None"""
- try:
- return system.pools.get(name=module.params['pool'])
- except Exception:
- return None
-
-
-@api_wrapper
-def get_filesystem(module, system):
- """Return Filesystem or None"""
- try:
- return system.filesystems.get(name=module.params['name'])
- except Exception:
- return None
-
-
-@api_wrapper
-def create_filesystem(module, system):
- """Create Filesystem"""
- if not module.check_mode:
- filesystem = system.filesystems.create(name=module.params['name'], pool=get_pool(module, system))
- if module.params['size']:
- size = Capacity(module.params['size']).roundup(64 * KiB)
- filesystem.update_size(size)
- module.exit_json(changed=True)
-
-
-@api_wrapper
-def update_filesystem(module, filesystem):
- """Update Filesystem"""
- changed = False
- if module.params['size']:
- size = Capacity(module.params['size']).roundup(64 * KiB)
- if filesystem.get_size() != size:
- if not module.check_mode:
- filesystem.update_size(size)
- changed = True
-
- module.exit_json(changed=changed)
-
-
-@api_wrapper
-def delete_filesystem(module, filesystem):
- """ Delete Filesystem"""
- if not module.check_mode:
- filesystem.delete()
- module.exit_json(changed=True)
-
-
-def main():
- argument_spec = infinibox_argument_spec()
- argument_spec.update(
- dict(
- name=dict(required=True),
- state=dict(default='present', choices=['present', 'absent']),
- pool=dict(required=True),
- size=dict()
- )
- )
-
- module = AnsibleModule(argument_spec, supports_check_mode=True)
-
- if not HAS_INFINISDK:
- module.fail_json(msg=missing_required_lib('infinisdk'))
- if not HAS_CAPACITY:
- module.fail_json(msg=missing_required_lib('capacity'), exception=CAPACITY_IMP_ERR)
-
- if module.params['size']:
- try:
- Capacity(module.params['size'])
- except Exception:
- module.fail_json(msg='size (Physical Capacity) should be defined in MB, GB, TB or PB units')
-
- state = module.params['state']
- system = get_system(module)
- pool = get_pool(module, system)
- filesystem = get_filesystem(module, system)
-
- if pool is None:
- module.fail_json(msg='Pool {0} not found'.format(module.params['pool']))
-
- if state == 'present' and not filesystem:
- create_filesystem(module, system)
- elif state == 'present' and filesystem:
- update_filesystem(module, filesystem)
- elif state == 'absent' and filesystem:
- delete_filesystem(module, filesystem)
- elif state == 'absent' and not filesystem:
- module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/infinidat/infini_host.py b/lib/ansible/modules/storage/infinidat/infini_host.py
deleted file mode 100644
index ac90a92803..0000000000
--- a/lib/ansible/modules/storage/infinidat/infini_host.py
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2016, Gregory Shulov (gregory.shulov@gmail.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: infini_host
-version_added: 2.3
-short_description: Create, Delete and Modify Hosts on Infinibox
-description:
- - This module creates, deletes or modifies hosts on Infinibox.
-author: Gregory Shulov (@GR360RY)
-options:
- name:
- description:
- - Host Name
- required: true
- state:
- description:
- - Creates/Modifies Host when present or removes when absent
- required: false
- default: present
- choices: [ "present", "absent" ]
- wwns:
- description:
- - List of wwns of the host
- required: false
- volume:
- description:
- - Volume name to map to the host
- required: false
-extends_documentation_fragment:
- - infinibox
-'''
-
-EXAMPLES = '''
-- name: Create new new host
- infini_host:
- name: foo.example.com
- user: admin
- password: secret
- system: ibox001
-
-- name: Make sure host bar is available with wwn ports
- infini_host:
- name: bar.example.com
- wwns:
- - "00:00:00:00:00:00:00"
- - "11:11:11:11:11:11:11"
- system: ibox01
- user: admin
- password: secret
-
-- name: Map host foo.example.com to volume bar
- infini_host:
- name: foo.example.com
- volume: bar
- system: ibox01
- user: admin
- password: secret
-'''
-
-RETURN = '''
-'''
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.infinibox import HAS_INFINISDK, api_wrapper, get_system, infinibox_argument_spec
-
-
-@api_wrapper
-def get_host(module, system):
-
- host = None
-
- for h in system.hosts.to_list():
- if h.get_name() == module.params['name']:
- host = h
- break
-
- return host
-
-
-@api_wrapper
-def create_host(module, system):
-
- changed = True
-
- if not module.check_mode:
- host = system.hosts.create(name=module.params['name'])
- if module.params['wwns']:
- for p in module.params['wwns']:
- host.add_fc_port(p)
- if module.params['volume']:
- host.map_volume(system.volumes.get(name=module.params['volume']))
- module.exit_json(changed=changed)
-
-
-@api_wrapper
-def update_host(module, host):
- changed = False
- module.exit_json(changed=changed)
-
-
-@api_wrapper
-def delete_host(module, host):
- changed = True
- if not module.check_mode:
- host.delete()
- module.exit_json(changed=changed)
-
-
-def main():
- argument_spec = infinibox_argument_spec()
- argument_spec.update(
- dict(
- name=dict(required=True),
- state=dict(default='present', choices=['present', 'absent']),
- wwns=dict(type='list'),
- volume=dict()
- )
- )
-
- module = AnsibleModule(argument_spec, supports_check_mode=True)
-
- if not HAS_INFINISDK:
- module.fail_json(msg=missing_required_lib('infinisdk'))
-
- state = module.params['state']
- system = get_system(module)
- host = get_host(module, system)
-
- if module.params['volume']:
- try:
- system.volumes.get(name=module.params['volume'])
- except Exception:
- module.fail_json(msg='Volume {0} not found'.format(module.params['volume']))
-
- if host and state == 'present':
- update_host(module, host)
- elif host and state == 'absent':
- delete_host(module, host)
- elif host is None and state == 'absent':
- module.exit_json(changed=False)
- else:
- create_host(module, system)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/infinidat/infini_pool.py b/lib/ansible/modules/storage/infinidat/infini_pool.py
deleted file mode 100644
index a178295a0f..0000000000
--- a/lib/ansible/modules/storage/infinidat/infini_pool.py
+++ /dev/null
@@ -1,215 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# (c) 2016, Gregory Shulov (gregory.shulov@gmail.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: infini_pool
-version_added: 2.3
-short_description: Create, Delete and Modify Pools on Infinibox
-description:
- - This module to creates, deletes or modifies pools on Infinibox.
-author: Gregory Shulov (@GR360RY)
-options:
- name:
- description:
- - Pool Name
- required: true
- state:
- description:
- - Creates/Modifies Pool when present or removes when absent
- required: false
- default: present
- choices: [ "present", "absent" ]
- size:
- description:
- - Pool Physical Capacity in MB, GB or TB units.
- If pool size is not set on pool creation, size will be equal to 1TB.
- See examples.
- required: false
- vsize:
- description:
- - Pool Virtual Capacity in MB, GB or TB units.
- If pool vsize is not set on pool creation, Virtual Capacity will be equal to Physical Capacity.
- See examples.
- required: false
- ssd_cache:
- description:
- - Enable/Disable SSD Cache on Pool
- required: false
- default: yes
- type: bool
-notes:
- - Infinibox Admin level access is required for pool modifications
-extends_documentation_fragment:
- - infinibox
-requirements:
- - capacity
-'''
-
-EXAMPLES = '''
-- name: Make sure pool foo exists. Set pool physical capacity to 10TB
- infini_pool:
- name: foo
- size: 10TB
- vsize: 10TB
- user: admin
- password: secret
- system: ibox001
-
-- name: Disable SSD Cache on pool
- infini_pool:
- name: foo
- ssd_cache: no
- user: admin
- password: secret
- system: ibox001
-'''
-
-RETURN = '''
-'''
-import traceback
-
-CAPACITY_IMP_ERR = None
-try:
- from capacity import KiB, Capacity
- HAS_CAPACITY = True
-except ImportError:
- CAPACITY_IMP_ERR = traceback.format_exc()
- HAS_CAPACITY = False
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.infinibox import HAS_INFINISDK, api_wrapper, get_system, infinibox_argument_spec
-
-
-@api_wrapper
-def get_pool(module, system):
- """Return Pool on None"""
- try:
- return system.pools.get(name=module.params['name'])
- except Exception:
- return None
-
-
-@api_wrapper
-def create_pool(module, system):
- """Create Pool"""
- name = module.params['name']
- size = module.params['size']
- vsize = module.params['vsize']
- ssd_cache = module.params['ssd_cache']
-
- if not module.check_mode:
- if not size and not vsize:
- pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity('1TB'))
- elif size and not vsize:
- pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(size))
- elif not size and vsize:
- pool = system.pools.create(name=name, physical_capacity=Capacity('1TB'), virtual_capacity=Capacity(vsize))
- else:
- pool = system.pools.create(name=name, physical_capacity=Capacity(size), virtual_capacity=Capacity(vsize))
- # Default value of ssd_cache is True. Disable ssd caching if False
- if not ssd_cache:
- pool.update_ssd_enabled(ssd_cache)
-
- module.exit_json(changed=True)
-
-
-@api_wrapper
-def update_pool(module, system, pool):
- """Update Pool"""
- changed = False
-
- size = module.params['size']
- vsize = module.params['vsize']
- ssd_cache = module.params['ssd_cache']
-
- # Roundup the capacity to mimic Infinibox behaviour
- if size:
- physical_capacity = Capacity(size).roundup(6 * 64 * KiB)
- if pool.get_physical_capacity() != physical_capacity:
- if not module.check_mode:
- pool.update_physical_capacity(physical_capacity)
- changed = True
-
- if vsize:
- virtual_capacity = Capacity(vsize).roundup(6 * 64 * KiB)
- if pool.get_virtual_capacity() != virtual_capacity:
- if not module.check_mode:
- pool.update_virtual_capacity(virtual_capacity)
- changed = True
-
- if pool.get_ssd_enabled() != ssd_cache:
- if not module.check_mode:
- pool.update_ssd_enabled(ssd_cache)
- changed = True
-
- module.exit_json(changed=changed)
-
-
-@api_wrapper
-def delete_pool(module, pool):
- """Delete Pool"""
- if not module.check_mode:
- pool.delete()
- module.exit_json(changed=True)
-
-
-def main():
- argument_spec = infinibox_argument_spec()
- argument_spec.update(
- dict(
- name=dict(required=True),
- state=dict(default='present', choices=['present', 'absent']),
- size=dict(),
- vsize=dict(),
- ssd_cache=dict(type='bool', default=True)
- )
- )
-
- module = AnsibleModule(argument_spec, supports_check_mode=True)
-
- if not HAS_INFINISDK:
- module.fail_json(msg=missing_required_lib('infinisdk'))
- if not HAS_CAPACITY:
- module.fail_json(msg=missing_required_lib('capacity'), exception=CAPACITY_IMP_ERR)
-
- if module.params['size']:
- try:
- Capacity(module.params['size'])
- except Exception:
- module.fail_json(msg='size (Physical Capacity) should be defined in MB, GB, TB or PB units')
-
- if module.params['vsize']:
- try:
- Capacity(module.params['vsize'])
- except Exception:
- module.fail_json(msg='vsize (Virtual Capacity) should be defined in MB, GB, TB or PB units')
-
- state = module.params['state']
- system = get_system(module)
- pool = get_pool(module, system)
-
- if state == 'present' and not pool:
- create_pool(module, system)
- elif state == 'present' and pool:
- update_pool(module, system, pool)
- elif state == 'absent' and pool:
- delete_pool(module, pool)
- elif state == 'absent' and not pool:
- module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/infinidat/infini_vol.py b/lib/ansible/modules/storage/infinidat/infini_vol.py
deleted file mode 100644
index f81f65fd8f..0000000000
--- a/lib/ansible/modules/storage/infinidat/infini_vol.py
+++ /dev/null
@@ -1,166 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2016, Gregory Shulov (gregory.shulov@gmail.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: infini_vol
-version_added: 2.3
-short_description: Create, Delete or Modify volumes on Infinibox
-description:
- - This module creates, deletes or modifies volume on Infinibox.
-author: Gregory Shulov (@GR360RY)
-options:
- name:
- description:
- - Volume Name
- required: true
- state:
- description:
- - Creates/Modifies volume when present or removes when absent
- required: false
- default: present
- choices: [ "present", "absent" ]
- size:
- description:
- - Volume size in MB, GB or TB units. See examples.
- required: false
- pool:
- description:
- - Pool that volume will reside on
- required: true
-extends_documentation_fragment:
- - infinibox
-requirements:
- - capacity
-'''
-
-EXAMPLES = '''
-- name: Create new volume named foo under pool named bar
- infini_vol:
- name: foo
- size: 1TB
- pool: bar
- state: present
- user: admin
- password: secret
- system: ibox001
-'''
-
-RETURN = '''
-'''
-
-try:
- from capacity import KiB, Capacity
- HAS_CAPACITY = True
-except ImportError:
- HAS_CAPACITY = False
-
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.infinibox import HAS_INFINISDK, api_wrapper, get_system, infinibox_argument_spec
-
-
-@api_wrapper
-def get_pool(module, system):
- """Return Pool or None"""
- try:
- return system.pools.get(name=module.params['pool'])
- except Exception:
- return None
-
-
-@api_wrapper
-def get_volume(module, system):
- """Return Volume or None"""
- try:
- return system.volumes.get(name=module.params['name'])
- except Exception:
- return None
-
-
-@api_wrapper
-def create_volume(module, system):
- """Create Volume"""
- if not module.check_mode:
- volume = system.volumes.create(name=module.params['name'], pool=get_pool(module, system))
- if module.params['size']:
- size = Capacity(module.params['size']).roundup(64 * KiB)
- volume.update_size(size)
- module.exit_json(changed=True)
-
-
-@api_wrapper
-def update_volume(module, volume):
- """Update Volume"""
- changed = False
- if module.params['size']:
- size = Capacity(module.params['size']).roundup(64 * KiB)
- if volume.get_size() != size:
- if not module.check_mode:
- volume.update_size(size)
- changed = True
-
- module.exit_json(changed=changed)
-
-
-@api_wrapper
-def delete_volume(module, volume):
- """ Delete Volume"""
- if not module.check_mode:
- volume.delete()
- module.exit_json(changed=True)
-
-
-def main():
- argument_spec = infinibox_argument_spec()
- argument_spec.update(
- dict(
- name=dict(required=True),
- state=dict(default='present', choices=['present', 'absent']),
- pool=dict(required=True),
- size=dict()
- )
- )
-
- module = AnsibleModule(argument_spec, supports_check_mode=True)
-
- if not HAS_INFINISDK:
- module.fail_json(msg=missing_required_lib('infinisdk'))
-
- if module.params['size']:
- try:
- Capacity(module.params['size'])
- except Exception:
- module.fail_json(msg='size (Physical Capacity) should be defined in MB, GB, TB or PB units')
-
- state = module.params['state']
- system = get_system(module)
- pool = get_pool(module, system)
- volume = get_volume(module, system)
-
- if pool is None:
- module.fail_json(msg='Pool {0} not found'.format(module.params['pool']))
-
- if state == 'present' and not volume:
- create_volume(module, system)
- elif state == 'present' and volume:
- update_volume(module, volume)
- elif state == 'absent' and volume:
- delete_volume(module, volume)
- elif state == 'absent' and not volume:
- module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_aggregate.py b/lib/ansible/modules/storage/netapp/_na_cdot_aggregate.py
deleted file mode 100644
index 6c34a7d315..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_aggregate.py
+++ /dev/null
@@ -1,233 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_aggregate
-
-short_description: Manage NetApp cDOT aggregates.
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_aggregate) instead.
-
-description:
-- Create or destroy aggregates on NetApp cDOT.
-
-options:
-
- state:
- required: true
- description:
- - Whether the specified aggregate should exist or not.
- choices: ['present', 'absent']
-
- name:
- required: true
- description:
- - The name of the aggregate to manage.
-
- disk_count:
- description:
- - Number of disks to place into the aggregate, including parity disks.
- - The disks in this newly-created aggregate come from the spare disk pool.
- - The smallest disks in this pool join the aggregate first, unless the C(disk-size) argument is provided.
- - Either C(disk-count) or C(disks) must be supplied. Range [0..2^31-1].
- - Required when C(state=present).
-
-'''
-
-EXAMPLES = """
-- name: Manage Aggregates
- na_cdot_aggregate:
- state: present
- name: ansibleAggr
- disk_count: 1
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-- name: Manage Aggregates
- na_cdot_aggregate:
- state: present
- name: ansibleAggr
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTAggregate(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- disk_count=dict(required=False, type='int'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['disk_count'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.disk_count = p['disk_count']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module)
-
- def get_aggr(self):
- """
- Checks if aggregate exists.
-
- :return:
- True if aggregate found
- False if aggregate is not found
- :rtype: bool
- """
-
- aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter')
- query_details = netapp_utils.zapi.NaElement.create_node_with_children(
- 'aggr-attributes', **{'aggregate-name': self.name})
-
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(query_details)
- aggr_get_iter.add_child_elem(query)
-
- try:
- result = self.server.invoke_successfully(aggr_get_iter,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- # Error 13040 denotes an aggregate not being found.
- if to_native(e.code) == "13040":
- return False
- else:
- self.module.fail_json(msg=to_native(e), exception=traceback.format_exc())
-
- if (result.get_child_by_name('num-records') and
- int(result.get_child_content('num-records')) >= 1):
- return True
- else:
- return False
-
- def create_aggr(self):
- aggr_create = netapp_utils.zapi.NaElement.create_node_with_children(
- 'aggr-create', **{'aggregate': self.name,
- 'disk-count': str(self.disk_count)})
-
- try:
- self.server.invoke_successfully(aggr_create,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error provisioning aggregate %s: %s" % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_aggr(self):
- aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
- 'aggr-destroy', **{'aggregate': self.name})
-
- try:
- self.server.invoke_successfully(aggr_destroy,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error removing aggregate %s: %s" % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def rename_aggregate(self):
- aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children(
- 'aggr-rename', **{'aggregate': self.name,
- 'new-aggregate-name':
- self.name})
-
- try:
- self.server.invoke_successfully(aggr_rename,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error renaming aggregate %s: %s" % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- aggregate_exists = self.get_aggr()
- rename_aggregate = False
-
- # check if anything needs to be changed (add/delete/update)
-
- if aggregate_exists:
- if self.state == 'absent':
- changed = True
-
- elif self.state == 'present':
- if self.name is not None and not self.name == self.name:
- rename_aggregate = True
- changed = True
-
- else:
- if self.state == 'present':
- # Aggregate does not exist, but requested state is present.
- changed = True
-
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not aggregate_exists:
- self.create_aggr()
-
- else:
- if rename_aggregate:
- self.rename_aggregate()
-
- elif self.state == 'absent':
- self.delete_aggr()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTAggregate()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_license.py b/lib/ansible/modules/storage/netapp/_na_cdot_license.py
deleted file mode 100644
index 75341c0288..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_license.py
+++ /dev/null
@@ -1,299 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_license
-
-short_description: Manage NetApp cDOT protocol and feature licenses
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_license) instead.
-
-description:
-- Add or remove licenses on NetApp ONTAP.
-
-options:
-
- remove_unused:
- description:
- - Remove licenses that have no controller affiliation in the cluster.
- type: bool
-
- remove_expired:
- description:
- - Remove licenses that have expired in the cluster.
- type: bool
-
- serial_number:
- description:
- - Serial number of the node associated with the license.
- - This parameter is used primarily when removing license for a specific service.
- - If this parameter is not provided, the cluster serial number is used by default.
-
- licenses:
- description:
- - List of licenses to add or remove.
- - Please note that trying to remove a non-existent license will throw an error.
- suboptions:
- base:
- description:
- - Cluster Base License
- nfs:
- description:
- - NFS License
- cifs:
- description:
- - CIFS License
- iscsi:
- description:
- - iSCSI License
- fcp:
- description:
- - FCP License
- cdmi:
- description:
- - CDMI License
- snaprestore:
- description:
- - SnapRestore License
- snapmirror:
- description:
- - SnapMirror License
- flexclone:
- description:
- - FlexClone License
- snapvault:
- description:
- - SnapVault License
- snaplock:
- description:
- - SnapLock License
- snapmanagersuite:
- description:
- - SnapManagerSuite License
- snapprotectapps:
- description:
- - SnapProtectApp License
- v_storageattach:
- description:
- - Virtual Attached Storage License
-
-'''
-
-
-EXAMPLES = """
-- name: Add licenses
- na_cdot_license:
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
- serial_number: #################
- licenses:
- nfs: #################
- cifs: #################
- iscsi: #################
- fcp: #################
- snaprestore: #################
- flexclone: #################
-
-- name: Remove licenses
- na_cdot_license:
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
- remove_unused: false
- remove_expired: true
- serial_number: #################
- licenses:
- nfs: remove
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTLicense(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- serial_number=dict(required=False, type='str', default=None),
- remove_unused=dict(default=False, type='bool'),
- remove_expired=dict(default=False, type='bool'),
- licenses=dict(default=False, type='dict'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=False
- )
-
- p = self.module.params
-
- # set up state variables
- self.serial_number = p['serial_number']
- self.remove_unused = p['remove_unused']
- self.remove_expired = p['remove_expired']
- self.licenses = p['licenses']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module)
-
- def get_licensing_status(self):
- """
- Check licensing status
-
- :return: package (key) and licensing status (value)
- :rtype: dict
- """
- license_status = netapp_utils.zapi.NaElement('license-v2-status-list-info')
- result = None
- try:
- result = self.server.invoke_successfully(license_status,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error checking license status: %s" %
- to_native(e), exception=traceback.format_exc())
-
- return_dictionary = {}
- license_v2_status = result.get_child_by_name('license-v2-status')
- if license_v2_status:
- for license_v2_status_info in license_v2_status.get_children():
- package = license_v2_status_info.get_child_content('package')
- status = license_v2_status_info.get_child_content('method')
- return_dictionary[package] = status
-
- return return_dictionary
-
- def remove_licenses(self, remove_list):
- """
- Remove requested licenses
- :param:
- remove_list : List of packages to remove
-
- """
- license_delete = netapp_utils.zapi.NaElement('license-v2-delete')
- for package in remove_list:
- license_delete.add_new_child('package', package)
-
- if self.serial_number is not None:
- license_delete.add_new_child('serial-number', self.serial_number)
-
- try:
- self.server.invoke_successfully(license_delete,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error removing license %s" %
- to_native(e), exception=traceback.format_exc())
-
- def remove_unused_licenses(self):
- """
- Remove unused licenses
- """
- remove_unused = netapp_utils.zapi.NaElement('license-v2-delete-unused')
- try:
- self.server.invoke_successfully(remove_unused,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error removing unused licenses: %s" %
- to_native(e), exception=traceback.format_exc())
-
- def remove_expired_licenses(self):
- """
- Remove expired licenses
- """
- remove_expired = netapp_utils.zapi.NaElement('license-v2-delete-expired')
- try:
- self.server.invoke_successfully(remove_expired,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error removing expired licenses: %s" %
- to_native(e), exception=traceback.format_exc())
-
- def update_licenses(self):
- """
- Update licenses
- """
- # Remove unused and expired licenses, if requested.
- if self.remove_unused:
- self.remove_unused_licenses()
-
- if self.remove_expired:
- self.remove_expired_licenses()
-
- # Next, add/remove specific requested licenses.
- license_add = netapp_utils.zapi.NaElement('license-v2-add')
- codes = netapp_utils.zapi.NaElement('codes')
- remove_list = []
- for key, value in self.licenses.items():
- str_value = str(value)
- # Make sure license is not an empty string.
- if str_value and str_value.strip():
- if str_value.lower() == 'remove':
- remove_list.append(str(key).lower())
- else:
- codes.add_new_child('license-code-v2', str_value)
-
- # Remove requested licenses.
- if len(remove_list) != 0:
- self.remove_licenses(remove_list)
-
- # Add requested licenses
- if len(codes.get_children()) != 0:
- license_add.add_child_elem(codes)
- try:
- self.server.invoke_successfully(license_add,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error adding licenses: %s" %
- to_native(e), exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- # Add / Update licenses.
- license_status = self.get_licensing_status()
- self.update_licenses()
- new_license_status = self.get_licensing_status()
-
- if license_status != new_license_status:
- changed = True
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTLicense()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_lun.py b/lib/ansible/modules/storage/netapp/_na_cdot_lun.py
deleted file mode 100644
index 8bdc481b49..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_lun.py
+++ /dev/null
@@ -1,378 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_lun
-
-short_description: Manage NetApp cDOT luns
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_lun) instead.
-
-description:
-- Create, destroy, resize luns on NetApp cDOT.
-
-options:
-
- state:
- description:
- - Whether the specified lun should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - The name of the lun to manage.
- required: true
-
- flexvol_name:
- description:
- - The name of the FlexVol the lun should exist on.
- - Required when C(state=present).
-
- size:
- description:
- - The size of the lun in C(size_unit).
- - Required when C(state=present).
-
- size_unit:
- description:
- - The unit used to interpret the size parameter.
- choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
- default: 'gb'
-
- force_resize:
- description:
- - Forcibly reduce the size. This is required for reducing the size of the LUN to avoid accidentally reducing the LUN size.
- default: false
-
- force_remove:
- description:
- - If "true", override checks that prevent a LUN from being destroyed if it is online and mapped.
- - If "false", destroying an online and mapped LUN will fail.
- default: false
-
- force_remove_fenced:
- description:
- - If "true", override checks that prevent a LUN from being destroyed while it is fenced.
- - If "false", attempting to destroy a fenced LUN will fail.
- - The default if not specified is "false". This field is available in Data ONTAP 8.2 and later.
- default: false
-
- vserver:
- required: true
- description:
- - The name of the vserver to use.
-
-'''
-
-EXAMPLES = """
-- name: Create LUN
- na_cdot_lun:
- state: present
- name: ansibleLUN
- flexvol_name: ansibleVolume
- vserver: ansibleVServer
- size: 5
- size_unit: mb
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-- name: Resize Lun
- na_cdot_lun:
- state: present
- name: ansibleLUN
- force_resize: True
- flexvol_name: ansibleVolume
- vserver: ansibleVServer
- size: 5
- size_unit: gb
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTLUN(object):
-
- def __init__(self):
-
- self._size_unit_map = dict(
- bytes=1,
- b=1,
- kb=1024,
- mb=1024 ** 2,
- gb=1024 ** 3,
- tb=1024 ** 4,
- pb=1024 ** 5,
- eb=1024 ** 6,
- zb=1024 ** 7,
- yb=1024 ** 8
- )
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- size=dict(type='int'),
- size_unit=dict(default='gb',
- choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
- 'pb', 'eb', 'zb', 'yb'], type='str'),
- force_resize=dict(default=False, type='bool'),
- force_remove=dict(default=False, type='bool'),
- force_remove_fenced=dict(default=False, type='bool'),
- flexvol_name=dict(type='str'),
- vserver=dict(required=True, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['flexvol_name', 'size'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.size_unit = p['size_unit']
- if p['size'] is not None:
- self.size = p['size'] * self._size_unit_map[self.size_unit]
- else:
- self.size = None
- self.force_resize = p['force_resize']
- self.force_remove = p['force_remove']
- self.force_remove_fenced = p['force_remove_fenced']
- self.flexvol_name = p['flexvol_name']
- self.vserver = p['vserver']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module, vserver=self.vserver)
-
- def get_lun(self):
- """
- Return details about the LUN
-
- :return: Details about the lun
- :rtype: dict
- """
-
- luns = []
- tag = None
- while True:
- lun_info = netapp_utils.zapi.NaElement('lun-get-iter')
- if tag:
- lun_info.add_new_child('tag', tag, True)
-
- query_details = netapp_utils.zapi.NaElement('lun-info')
- query_details.add_new_child('vserver', self.vserver)
- query_details.add_new_child('volume', self.flexvol_name)
-
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(query_details)
-
- lun_info.add_child_elem(query)
-
- result = self.server.invoke_successfully(lun_info, True)
- if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
- attr_list = result.get_child_by_name('attributes-list')
- luns.extend(attr_list.get_children())
-
- tag = result.get_child_content('next-tag')
-
- if tag is None:
- break
-
- # The LUNs have been extracted.
- # Find the specified lun and extract details.
- return_value = None
- for lun in luns:
- path = lun.get_child_content('path')
- _rest, _splitter, found_name = path.rpartition('/')
-
- if found_name == self.name:
- size = lun.get_child_content('size')
-
- # Find out if the lun is attached
- attached_to = None
- lun_id = None
- if lun.get_child_content('mapped') == 'true':
- lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children(
- 'lun-map-list-info', **{'path': path})
-
- result = self.server.invoke_successfully(
- lun_map_list, enable_tunneling=True)
-
- igroups = result.get_child_by_name('initiator-groups')
- if igroups:
- for igroup_info in igroups.get_children():
- igroup = igroup_info.get_child_content(
- 'initiator-group-name')
- attached_to = igroup
- lun_id = igroup_info.get_child_content('lun-id')
-
- return_value = {
- 'name': found_name,
- 'size': size,
- 'attached_to': attached_to,
- 'lun_id': lun_id
- }
- else:
- continue
-
- return return_value
-
- def create_lun(self):
- """
- Create LUN with requested name and size
- """
- path = '/vol/%s/%s' % (self.flexvol_name, self.name)
- lun_create = netapp_utils.zapi.NaElement.create_node_with_children(
- 'lun-create-by-size', **{'path': path,
- 'size': str(self.size),
- 'ostype': 'linux'})
-
- try:
- self.server.invoke_successfully(lun_create, enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error provisioning lun %s of size %s: %s" % (self.name, self.size, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_lun(self):
- """
- Delete requested LUN
- """
- path = '/vol/%s/%s' % (self.flexvol_name, self.name)
-
- lun_delete = netapp_utils.zapi.NaElement.create_node_with_children(
- 'lun-destroy', **{'path': path,
- 'force': str(self.force_remove),
- 'destroy-fenced-lun':
- str(self.force_remove_fenced)})
-
- try:
- self.server.invoke_successfully(lun_delete, enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(e)),
- exception=traceback.format_exc())
-
- def resize_lun(self):
- """
- Resize requested LUN.
-
- :return: True if LUN was actually re-sized, false otherwise.
- :rtype: bool
- """
- path = '/vol/%s/%s' % (self.flexvol_name, self.name)
-
- lun_resize = netapp_utils.zapi.NaElement.create_node_with_children(
- 'lun-resize', **{'path': path,
- 'size': str(self.size),
- 'force': str(self.force_resize)})
- try:
- self.server.invoke_successfully(lun_resize, enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- if to_native(e.code) == "9042":
- # Error 9042 denotes the new LUN size being the same as the
- # old LUN size. This happens when there's barely any difference
- # in the two sizes. For example, from 8388608 bytes to
- # 8194304 bytes. This should go away if/when the default size
- # requested/reported to/from the controller is changed to a
- # larger unit (MB/GB/TB).
- return False
- else:
- self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(e)),
- exception=traceback.format_exc())
-
- return True
-
- def apply(self):
- property_changed = False
- multiple_properties_changed = False
- size_changed = False
- lun_exists = False
- lun_detail = self.get_lun()
-
- if lun_detail:
- lun_exists = True
- current_size = lun_detail['size']
-
- if self.state == 'absent':
- property_changed = True
-
- elif self.state == 'present':
- if not int(current_size) == self.size:
- size_changed = True
- property_changed = True
-
- else:
- if self.state == 'present':
- property_changed = True
-
- if property_changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not lun_exists:
- self.create_lun()
-
- else:
- if size_changed:
- # Ensure that size was actually changed. Please
- # read notes in 'resize_lun' function for details.
- size_changed = self.resize_lun()
- if not size_changed and not \
- multiple_properties_changed:
- property_changed = False
-
- elif self.state == 'absent':
- self.delete_lun()
-
- changed = property_changed or size_changed
- # TODO: include other details about the lun (size, etc.)
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTLUN()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_qtree.py b/lib/ansible/modules/storage/netapp/_na_cdot_qtree.py
deleted file mode 100644
index 0f5d2532df..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_qtree.py
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_qtree
-
-short_description: Manage qtrees
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_qtree) instead.
-
-description:
-- Create or destroy Qtrees.
-
-options:
-
- state:
- description:
- - Whether the specified Qtree should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - The name of the Qtree to manage.
- required: true
-
- flexvol_name:
- description:
- - The name of the FlexVol the Qtree should exist on. Required when C(state=present).
-
- vserver:
- description:
- - The name of the vserver to use.
- required: true
-
-'''
-
-EXAMPLES = """
-- name: Create QTree
- na_cdot_qtree:
- state: present
- name: ansibleQTree
- flexvol_name: ansibleVolume
- vserver: ansibleVServer
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-- name: Rename QTree
- na_cdot_qtree:
- state: present
- name: ansibleQTree
- flexvol_name: ansibleVolume
- vserver: ansibleVServer
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTQTree(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- flexvol_name=dict(type='str'),
- vserver=dict(required=True, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['flexvol_name'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.flexvol_name = p['flexvol_name']
- self.vserver = p['vserver']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module, vserver=self.vserver)
-
- def get_qtree(self):
- """
- Checks if the qtree exists.
-
- :return:
- True if qtree found
- False if qtree is not found
- :rtype: bool
- """
-
- qtree_list_iter = netapp_utils.zapi.NaElement('qtree-list-iter')
- query_details = netapp_utils.zapi.NaElement.create_node_with_children(
- 'qtree-info', **{'vserver': self.vserver,
- 'volume': self.flexvol_name,
- 'qtree': self.name})
-
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(query_details)
- qtree_list_iter.add_child_elem(query)
-
- result = self.server.invoke_successfully(qtree_list_iter,
- enable_tunneling=True)
-
- if (result.get_child_by_name('num-records') and
- int(result.get_child_content('num-records')) >= 1):
- return True
- else:
- return False
-
- def create_qtree(self):
- qtree_create = netapp_utils.zapi.NaElement.create_node_with_children(
- 'qtree-create', **{'volume': self.flexvol_name,
- 'qtree': self.name})
-
- try:
- self.server.invoke_successfully(qtree_create,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error provisioning qtree %s: %s" % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_qtree(self):
- path = '/vol/%s/%s' % (self.flexvol_name, self.name)
- qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children(
- 'qtree-delete', **{'qtree': path})
-
- try:
- self.server.invoke_successfully(qtree_delete,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(e)),
- exception=traceback.format_exc())
-
- def rename_qtree(self):
- path = '/vol/%s/%s' % (self.flexvol_name, self.name)
- new_path = '/vol/%s/%s' % (self.flexvol_name, self.name)
- qtree_rename = netapp_utils.zapi.NaElement.create_node_with_children(
- 'qtree-rename', **{'qtree': path,
- 'new-qtree-name': new_path})
-
- try:
- self.server.invoke_successfully(qtree_rename,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg="Error renaming qtree %s: %s" % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- qtree_exists = False
- rename_qtree = False
- qtree_detail = self.get_qtree()
-
- if qtree_detail:
- qtree_exists = True
-
- if self.state == 'absent':
- # Qtree exists, but requested state is 'absent'.
- changed = True
-
- elif self.state == 'present':
- if self.name is not None and not self.name == \
- self.name:
- changed = True
- rename_qtree = True
-
- else:
- if self.state == 'present':
- # Qtree does not exist, but requested state is 'present'.
- changed = True
-
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not qtree_exists:
- self.create_qtree()
-
- else:
- if rename_qtree:
- self.rename_qtree()
-
- elif self.state == 'absent':
- self.delete_qtree()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTQTree()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_svm.py b/lib/ansible/modules/storage/netapp/_na_cdot_svm.py
deleted file mode 100644
index ce5151238d..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_svm.py
+++ /dev/null
@@ -1,251 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_svm
-
-short_description: Manage NetApp cDOT svm
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_svm) instead.
-
-description:
-- Create or destroy svm on NetApp cDOT
-
-options:
-
- state:
- description:
- - Whether the specified SVM should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - The name of the SVM to manage.
- required: true
-
- root_volume:
- description:
- - Root volume of the SVM. Required when C(state=present).
-
- root_volume_aggregate:
- description:
- - The aggregate on which the root volume will be created.
- - Required when C(state=present).
-
- root_volume_security_style:
- description:
- - Security Style of the root volume.
- - When specified as part of the vserver-create, this field represents the security style for the Vserver root volume.
- - When specified as part of vserver-get-iter call, this will return the list of matching Vservers.
- - Possible values are 'unix', 'ntfs', 'mixed'.
- - The 'unified' security style, which applies only to Infinite Volumes, cannot be applied to a Vserver's root volume.
- - Valid options are "unix" for NFS, "ntfs" for CIFS, "mixed" for Mixed, "unified" for Unified.
- - Required when C(state=present)
- choices: ['unix', 'ntfs', 'mixed', 'unified']
-
-'''
-
-EXAMPLES = """
-
- - name: Create SVM
- na_cdot_svm:
- state: present
- name: ansibleVServer
- root_volume: vol1
- root_volume_aggregate: aggr1
- root_volume_security_style: mixed
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTSVM(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- root_volume=dict(type='str'),
- root_volume_aggregate=dict(type='str'),
- root_volume_security_style=dict(type='str', choices=['unix',
- 'ntfs',
- 'mixed',
- 'unified'
- ]),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['root_volume',
- 'root_volume_aggregate',
- 'root_volume_security_style'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.root_volume = p['root_volume']
- self.root_volume_aggregate = p['root_volume_aggregate']
- self.root_volume_security_style = p['root_volume_security_style']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module)
-
- def get_vserver(self):
- """
- Checks if vserver exists.
-
- :return:
- True if vserver found
- False if vserver is not found
- :rtype: bool
- """
-
- vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter')
- query_details = netapp_utils.zapi.NaElement.create_node_with_children(
- 'vserver-info', **{'vserver-name': self.name})
-
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(query_details)
- vserver_info.add_child_elem(query)
-
- result = self.server.invoke_successfully(vserver_info,
- enable_tunneling=False)
-
- if (result.get_child_by_name('num-records') and
- int(result.get_child_content('num-records')) >= 1):
-
- """
- TODO:
- Return more relevant parameters about vserver that can
- be updated by the playbook.
- """
- return True
- else:
- return False
-
- def create_vserver(self):
- vserver_create = netapp_utils.zapi.NaElement.create_node_with_children(
- 'vserver-create', **{'vserver-name': self.name,
- 'root-volume': self.root_volume,
- 'root-volume-aggregate':
- self.root_volume_aggregate,
- 'root-volume-security-style':
- self.root_volume_security_style
- })
-
- try:
- self.server.invoke_successfully(vserver_create,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error provisioning SVM %s with root volume %s on aggregate %s: %s'
- % (self.name, self.root_volume, self.root_volume_aggregate, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_vserver(self):
- vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children(
- 'vserver-destroy', **{'vserver-name': self.name})
-
- try:
- self.server.invoke_successfully(vserver_delete,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error deleting SVM %s with root volume %s on aggregate %s: %s'
- % (self.name, self.root_volume, self.root_volume_aggregate, to_native(e)),
- exception=traceback.format_exc())
-
- def rename_vserver(self):
- vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children(
- 'vserver-rename', **{'vserver-name': self.name,
- 'new-name': self.name})
-
- try:
- self.server.invoke_successfully(vserver_rename,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error renaming SVM %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- vserver_exists = self.get_vserver()
- rename_vserver = False
- if vserver_exists:
- if self.state == 'absent':
- changed = True
-
- elif self.state == 'present':
- # Update properties
- pass
-
- else:
- if self.state == 'present':
- changed = True
-
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not vserver_exists:
- self.create_vserver()
-
- else:
- if rename_vserver:
- self.rename_vserver()
-
- elif self.state == 'absent':
- self.delete_vserver()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTSVM()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_user.py b/lib/ansible/modules/storage/netapp/_na_cdot_user.py
deleted file mode 100644
index c3580091da..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_user.py
+++ /dev/null
@@ -1,306 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_user
-
-short_description: useradmin configuration and management
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_user) instead.
-
-description:
-- Create or destroy users.
-
-options:
-
- state:
- description:
- - Whether the specified user should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - The name of the user to manage.
- required: true
-
- application:
- description:
- - Applications to grant access to.
- required: true
- choices: ['console', 'http','ontapi','rsh','snmp','sp','ssh','telnet']
-
- authentication_method:
- description:
- - Authentication method for the application.
- - Not all authentication methods are valid for an application.
- - Valid authentication methods for each application are as denoted in I(authentication_choices_description).
- - password for console application
- - password, domain, nsswitch, cert for http application.
- - password, domain, nsswitch, cert for ontapi application.
- - community for snmp application (when creating SNMPv1 and SNMPv2 users).
- - usm and community for snmp application (when creating SNMPv3 users).
- - password for sp application.
- - password for rsh application.
- - password for telnet application.
- - password, publickey, domain, nsswitch for ssh application.
- required: true
- choices: ['community', 'password', 'publickey', 'domain', 'nsswitch', 'usm']
-
- set_password:
- description:
- - Password for the user account.
- - It is ignored for creating snmp users, but is required for creating non-snmp users.
- - For an existing user, this value will be used as the new password.
-
- role_name:
- description:
- - The name of the role. Required when C(state=present)
-
-
- vserver:
- description:
- - The name of the vserver to use.
- required: true
-
-'''
-
-EXAMPLES = """
-
- - name: Create User
- na_cdot_user:
- state: present
- name: SampleUser
- application: ssh
- authentication_method: password
- set_password: apn1242183u1298u41
- role_name: vsadmin
- vserver: ansibleVServer
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTUser(object):
- """
- Common operations to manage users and roles.
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
-
- application=dict(required=True, type='str', choices=[
- 'console', 'http', 'ontapi', 'rsh',
- 'snmp', 'sp', 'ssh', 'telnet']),
- authentication_method=dict(required=True, type='str',
- choices=['community', 'password',
- 'publickey', 'domain',
- 'nsswitch', 'usm']),
- set_password=dict(required=False, type='str', default=None),
- role_name=dict(required=False, type='str'),
-
- vserver=dict(required=True, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['role_name'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
-
- self.application = p['application']
- self.authentication_method = p['authentication_method']
- self.set_password = p['set_password']
- self.role_name = p['role_name']
-
- self.vserver = p['vserver']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module)
-
- def get_user(self):
- """
- Checks if the user exists.
-
- :return:
- True if user found
- False if user is not found
- :rtype: bool
- """
-
- security_login_get_iter = netapp_utils.zapi.NaElement('security-login-get-iter')
- query_details = netapp_utils.zapi.NaElement.create_node_with_children(
- 'security-login-account-info', **{'vserver': self.vserver,
- 'user-name': self.name,
- 'application': self.application,
- 'authentication-method':
- self.authentication_method})
-
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(query_details)
- security_login_get_iter.add_child_elem(query)
-
- try:
- result = self.server.invoke_successfully(security_login_get_iter,
- enable_tunneling=False)
-
- if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
- return True
- else:
- return False
-
- except netapp_utils.zapi.NaApiError as e:
- # Error 16034 denotes a user not being found.
- if to_native(e.code) == "16034":
- return False
- else:
- self.module.fail_json(msg='Error getting user %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def create_user(self):
- user_create = netapp_utils.zapi.NaElement.create_node_with_children(
- 'security-login-create', **{'vserver': self.vserver,
- 'user-name': self.name,
- 'application': self.application,
- 'authentication-method':
- self.authentication_method,
- 'role-name': self.role_name})
- if self.set_password is not None:
- user_create.add_new_child('password', self.set_password)
-
- try:
- self.server.invoke_successfully(user_create,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error creating user %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_user(self):
- user_delete = netapp_utils.zapi.NaElement.create_node_with_children(
- 'security-login-delete', **{'vserver': self.vserver,
- 'user-name': self.name,
- 'application': self.application,
- 'authentication-method':
- self.authentication_method})
-
- try:
- self.server.invoke_successfully(user_delete,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error removing user %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def change_password(self):
- """
- Changes the password
-
- :return:
- True if password updated
- False if password is not updated
- :rtype: bool
- """
- self.server.set_vserver(self.vserver)
- modify_password = netapp_utils.zapi.NaElement.create_node_with_children(
- 'security-login-modify-password', **{
- 'new-password': str(self.set_password),
- 'user-name': self.name})
- try:
- self.server.invoke_successfully(modify_password,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- if to_native(e.code) == '13114':
- return False
- else:
- self.module.fail_json(msg='Error setting password for user %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- self.server.set_vserver(None)
- return True
-
- def apply(self):
- property_changed = False
- password_changed = False
- user_exists = self.get_user()
-
- if user_exists:
- if self.state == 'absent':
- property_changed = True
-
- elif self.state == 'present':
- if self.set_password is not None:
- password_changed = self.change_password()
- else:
- if self.state == 'present':
- # Check if anything needs to be updated
- property_changed = True
-
- if property_changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not user_exists:
- self.create_user()
-
- # Add ability to update parameters.
-
- elif self.state == 'absent':
- self.delete_user()
-
- changed = property_changed or password_changed
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTUser()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_user_role.py b/lib/ansible/modules/storage/netapp/_na_cdot_user_role.py
deleted file mode 100644
index eb7ffb9b8c..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_user_role.py
+++ /dev/null
@@ -1,232 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_user_role
-
-short_description: useradmin configuration and management
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_user_role) instead.
-
-description:
-- Create or destroy user roles
-
-options:
-
- state:
- description:
- - Whether the specified user should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - The name of the role to manage.
- required: true
-
- command_directory_name:
- description:
- - The command or command directory to which the role has an access.
- required: true
-
- access_level:
- description:
- - The name of the role to manage.
- choices: ['none', 'readonly', 'all']
- default: 'all'
-
- vserver:
- description:
- - The name of the vserver to use.
- required: true
-
-'''
-
-EXAMPLES = """
-
- - name: Create User Role
- na_cdot_user_role:
- state: present
- name: ansibleRole
- command_directory_name: DEFAULT
- access_level: none
- vserver: ansibleVServer
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTUserRole(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
-
- command_directory_name=dict(required=True, type='str'),
- access_level=dict(required=False, type='str', default='all',
- choices=['none', 'readonly', 'all']),
-
- vserver=dict(required=True, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
-
- self.command_directory_name = p['command_directory_name']
- self.access_level = p['access_level']
-
- self.vserver = p['vserver']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module)
-
- def get_role(self):
- """
- Checks if the role exists for specific command-directory-name.
-
- :return:
- True if role found
- False if role is not found
- :rtype: bool
- """
-
- security_login_role_get_iter = netapp_utils.zapi.NaElement(
- 'security-login-role-get-iter')
- query_details = netapp_utils.zapi.NaElement.create_node_with_children(
- 'security-login-role-info', **{'vserver': self.vserver,
- 'role-name': self.name,
- 'command-directory-name':
- self.command_directory_name})
-
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(query_details)
- security_login_role_get_iter.add_child_elem(query)
-
- try:
- result = self.server.invoke_successfully(
- security_login_role_get_iter, enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- # Error 16031 denotes a role not being found.
- if to_native(e.code) == "16031":
- return False
- else:
- self.module.fail_json(msg='Error getting role %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- if (result.get_child_by_name('num-records') and
- int(result.get_child_content('num-records')) >= 1):
- return True
- else:
- return False
-
- def create_role(self):
- role_create = netapp_utils.zapi.NaElement.create_node_with_children(
- 'security-login-role-create', **{'vserver': self.vserver,
- 'role-name': self.name,
- 'command-directory-name':
- self.command_directory_name,
- 'access-level':
- self.access_level})
-
- try:
- self.server.invoke_successfully(role_create,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error creating role %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_role(self):
- role_delete = netapp_utils.zapi.NaElement.create_node_with_children(
- 'security-login-role-delete', **{'vserver': self.vserver,
- 'role-name': self.name,
- 'command-directory-name':
- self.command_directory_name})
-
- try:
- self.server.invoke_successfully(role_delete,
- enable_tunneling=False)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error removing role %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- role_exists = self.get_role()
-
- if role_exists:
- if self.state == 'absent':
- changed = True
-
- # Check if properties need to be updated
- else:
- if self.state == 'present':
- changed = True
-
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not role_exists:
- self.create_role()
-
- # Update properties
-
- elif self.state == 'absent':
- self.delete_role()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTUserRole()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_cdot_volume.py b/lib/ansible/modules/storage/netapp/_na_cdot_volume.py
deleted file mode 100644
index 0c2b11ac65..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_cdot_volume.py
+++ /dev/null
@@ -1,445 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_cdot_volume
-
-short_description: Manage NetApp cDOT volumes
-extends_documentation_fragment:
- - netapp.ontap
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-
-deprecated:
- removed_in: '2.11'
- why: Updated modules released with increased functionality
- alternative: Use M(na_ontap_volume) instead.
-
-description:
-- Create or destroy volumes on NetApp cDOT
-
-options:
-
- state:
- description:
- - Whether the specified volume should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - The name of the volume to manage.
- required: true
-
- infinite:
- description:
- - Set True if the volume is an Infinite Volume.
- type: bool
- default: 'no'
-
- online:
- description:
- - Whether the specified volume is online, or not.
- type: bool
- default: 'yes'
-
- aggregate_name:
- description:
- - The name of the aggregate the flexvol should exist on. Required when C(state=present).
-
- size:
- description:
- - The size of the volume in (size_unit). Required when C(state=present).
-
- size_unit:
- description:
- - The unit used to interpret the size parameter.
- choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
- default: 'gb'
-
- vserver:
- description:
- - Name of the vserver to use.
- required: true
-
- junction_path:
- description:
- - Junction path where to mount the volume
- required: false
- version_added: '2.6'
-
- export_policy:
- description:
- - Export policy to set for the specified junction path.
- required: false
- default: default
- version_added: '2.6'
-
- snapshot_policy:
- description:
- - Snapshot policy to set for the specified volume.
- required: false
- default: default
- version_added: '2.6'
-
-'''
-
-EXAMPLES = """
-
- - name: Create FlexVol
- na_cdot_volume:
- state: present
- name: ansibleVolume
- infinite: False
- aggregate_name: aggr1
- size: 20
- size_unit: mb
- vserver: ansibleVServer
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
- junction_path: /ansibleVolume
- export_policy: all_nfs_networks
- snapshot_policy: daily
-
- - name: Make FlexVol offline
- na_cdot_volume:
- state: present
- name: ansibleVolume
- infinite: False
- online: False
- vserver: ansibleVServer
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-"""
-
-RETURN = """
-
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppCDOTVolume(object):
-
- def __init__(self):
-
- self._size_unit_map = dict(
- bytes=1,
- b=1,
- kb=1024,
- mb=1024 ** 2,
- gb=1024 ** 3,
- tb=1024 ** 4,
- pb=1024 ** 5,
- eb=1024 ** 6,
- zb=1024 ** 7,
- yb=1024 ** 8
- )
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- is_infinite=dict(required=False, type='bool', default=False, aliases=['infinite']),
- is_online=dict(required=False, type='bool', default=True, aliases=['online']),
- size=dict(type='int'),
- size_unit=dict(default='gb',
- choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
- 'pb', 'eb', 'zb', 'yb'], type='str'),
- aggregate_name=dict(type='str'),
- vserver=dict(required=True, type='str', default=None),
- junction_path=dict(required=False, type='str', default=None),
- export_policy=dict(required=False, type='str', default='default'),
- snapshot_policy=dict(required=False, type='str', default='default'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['aggregate_name', 'size'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.is_infinite = p['is_infinite']
- self.is_online = p['is_online']
- self.size_unit = p['size_unit']
- self.vserver = p['vserver']
- self.junction_path = p['junction_path']
- self.export_policy = p['export_policy']
- self.snapshot_policy = p['snapshot_policy']
-
- if p['size'] is not None:
- self.size = p['size'] * self._size_unit_map[self.size_unit]
- else:
- self.size = None
- self.aggregate_name = p['aggregate_name']
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_ontap_zapi(module=self.module, vserver=self.vserver)
-
- def get_volume(self):
- """
- Return details about the volume
- :param:
- name : Name of the volume
-
- :return: Details about the volume. None if not found.
- :rtype: dict
- """
- volume_info = netapp_utils.zapi.NaElement('volume-get-iter')
- volume_attributes = netapp_utils.zapi.NaElement('volume-attributes')
- volume_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes')
- volume_id_attributes.add_new_child('name', self.name)
- volume_attributes.add_child_elem(volume_id_attributes)
-
- query = netapp_utils.zapi.NaElement('query')
- query.add_child_elem(volume_attributes)
-
- volume_info.add_child_elem(query)
-
- result = self.server.invoke_successfully(volume_info, True)
-
- return_value = None
-
- if result.get_child_by_name('num-records') and \
- int(result.get_child_content('num-records')) >= 1:
-
- volume_attributes = result.get_child_by_name(
- 'attributes-list').get_child_by_name(
- 'volume-attributes')
- # Get volume's current size
- volume_space_attributes = volume_attributes.get_child_by_name(
- 'volume-space-attributes')
- current_size = volume_space_attributes.get_child_content('size')
-
- # Get volume's state (online/offline)
- volume_state_attributes = volume_attributes.get_child_by_name(
- 'volume-state-attributes')
- current_state = volume_state_attributes.get_child_content('state')
- is_online = None
- if current_state == "online":
- is_online = True
- elif current_state == "offline":
- is_online = False
- return_value = {
- 'name': self.name,
- 'size': current_size,
- 'is_online': is_online,
- }
-
- return return_value
-
- def create_volume(self):
- create_parameters = {'volume': self.name,
- 'containing-aggr-name': self.aggregate_name,
- 'size': str(self.size),
- }
- if self.junction_path:
- create_parameters['junction-path'] = str(self.junction_path)
- if self.export_policy != 'default':
- create_parameters['export-policy'] = str(self.export_policy)
- if self.snapshot_policy != 'default':
- create_parameters['snapshot-policy'] = str(self.snapshot_policy)
-
- volume_create = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-create', **create_parameters)
-
- try:
- self.server.invoke_successfully(volume_create,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error provisioning volume %s of size %s: %s' % (self.name, self.size, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_volume(self):
- if self.is_infinite:
- volume_delete = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-destroy-async', **{'volume-name': self.name})
- else:
- volume_delete = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-destroy', **{'name': self.name, 'unmount-and-offline':
- 'true'})
-
- try:
- self.server.invoke_successfully(volume_delete,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error deleting volume %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def rename_volume(self):
- """
- Rename the volume.
-
- Note: 'is_infinite' needs to be set to True in order to rename an
- Infinite Volume.
- """
- if self.is_infinite:
- volume_rename = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-rename-async',
- **{'volume-name': self.name, 'new-volume-name': str(
- self.name)})
- else:
- volume_rename = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-rename', **{'volume': self.name, 'new-volume-name': str(
- self.name)})
- try:
- self.server.invoke_successfully(volume_rename,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error renaming volume %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def resize_volume(self):
- """
- Re-size the volume.
-
- Note: 'is_infinite' needs to be set to True in order to rename an
- Infinite Volume.
- """
- if self.is_infinite:
- volume_resize = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-size-async',
- **{'volume-name': self.name, 'new-size': str(
- self.size)})
- else:
- volume_resize = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-size', **{'volume': self.name, 'new-size': str(
- self.size)})
- try:
- self.server.invoke_successfully(volume_resize,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error re-sizing volume %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def change_volume_state(self):
- """
- Change volume's state (offline/online).
-
- Note: 'is_infinite' needs to be set to True in order to change the
- state of an Infinite Volume.
- """
- state_requested = None
- if self.is_online:
- # Requested state is 'online'.
- state_requested = "online"
- if self.is_infinite:
- volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-online-async',
- **{'volume-name': self.name})
- else:
- volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-online',
- **{'name': self.name})
- else:
- # Requested state is 'offline'.
- state_requested = "offline"
- if self.is_infinite:
- volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-offline-async',
- **{'volume-name': self.name})
- else:
- volume_change_state = netapp_utils.zapi.NaElement.create_node_with_children(
- 'volume-offline',
- **{'name': self.name})
- try:
- self.server.invoke_successfully(volume_change_state,
- enable_tunneling=True)
- except netapp_utils.zapi.NaApiError as e:
- self.module.fail_json(msg='Error changing the state of volume %s to %s: %s' %
- (self.name, state_requested, to_native(e)),
- exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- volume_exists = False
- rename_volume = False
- resize_volume = False
- volume_detail = self.get_volume()
-
- if volume_detail:
- volume_exists = True
-
- if self.state == 'absent':
- changed = True
-
- elif self.state == 'present':
- if str(volume_detail['size']) != str(self.size):
- resize_volume = True
- changed = True
- if (volume_detail['is_online'] is not None) and (volume_detail['is_online'] != self.is_online):
- changed = True
- if self.is_online is False:
- # Volume is online, but requested state is offline
- pass
- else:
- # Volume is offline but requested state is online
- pass
-
- else:
- if self.state == 'present':
- changed = True
-
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not volume_exists:
- self.create_volume()
-
- else:
- if resize_volume:
- self.resize_volume()
- if volume_detail['is_online'] is not \
- None and volume_detail['is_online'] != \
- self.is_online:
- self.change_volume_state()
- # Ensure re-naming is the last change made.
- if rename_volume:
- self.rename_volume()
-
- elif self.state == 'absent':
- self.delete_volume()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppCDOTVolume()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_na_ontap_gather_facts.py b/lib/ansible/modules/storage/netapp/_na_ontap_gather_facts.py
deleted file mode 100644
index d88b7ce98f..0000000000
--- a/lib/ansible/modules/storage/netapp/_na_ontap_gather_facts.py
+++ /dev/null
@@ -1,615 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2018 Piotr Olczak <piotr.olczak@redhat.com>
-# (c) 2018-2019, 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'certified'}
-
-DOCUMENTATION = '''
-module: na_ontap_gather_facts
-deprecated:
- removed_in: '2.13'
- why: Deprecated in favour of C(_info) module.
- alternative: Use M(na_ontap_info) instead.
-author: Piotr Olczak (@dprts) <polczak@redhat.com>
-extends_documentation_fragment:
- - netapp.na_ontap
-short_description: NetApp information gatherer
-description:
- - This module allows you to gather various information about ONTAP configuration
-version_added: "2.7"
-requirements:
- - netapp_lib
-options:
- state:
- description:
- - Returns "info"
- default: "info"
- choices: ['info']
- gather_subset:
- description:
- - When supplied, this argument will restrict the facts collected
- to a given subset. Possible values for this argument include
- "aggregate_info", "cluster_node_info", "igroup_info", "lun_info", "net_dns_info",
- "net_ifgrp_info",
- "net_interface_info", "net_port_info", "nvme_info", "nvme_interface_info",
- "nvme_namespace_info", "nvme_subsystem_info", "ontap_version",
- "qos_adaptive_policy_info", "qos_policy_info", "security_key_manager_key_info",
- "security_login_account_info", "storage_failover_info", "volume_info",
- "vserver_info", "vserver_login_banner_info", "vserver_motd_info", "vserver_nfs_info"
- Can specify a list of values to include a larger subset. Values can also be used
- with an initial C(M(!)) to specify that a specific subset should
- not be collected.
- - nvme is supported with ONTAP 9.4 onwards.
- - use "help" to get a list of supported facts for your system.
- default: "all"
- version_added: 2.8
-'''
-
-EXAMPLES = '''
-- name: Get NetApp info (Password Authentication)
- na_ontap_gather_facts:
- state: info
- hostname: "na-vsim"
- username: "admin"
- password: "admins_password"
-- debug:
- var: ontap_facts
-- name: Limit Fact Gathering to Aggregate Information
- na_ontap_gather_facts:
- state: info
- hostname: "na-vsim"
- username: "admin"
- password: "admins_password"
- gather_subset: "aggregate_info"
-- name: Limit Fact Gathering to Volume and Lun Information
- na_ontap_gather_facts:
- state: info
- hostname: "na-vsim"
- username: "admin"
- password: "admins_password"
- gather_subset:
- - volume_info
- - lun_info
-- name: Gather all facts except for volume and lun information
- na_ontap_gather_facts:
- state: info
- hostname: "na-vsim"
- username: "admin"
- password: "admins_password"
- gather_subset:
- - "!volume_info"
- - "!lun_info"
-'''
-
-RETURN = '''
-ontap_facts:
- description: Returns various information about NetApp cluster configuration
- returned: always
- type: dict
- sample: '{
- "ontap_facts": {
- "aggregate_info": {...},
- "cluster_node_info": {...},
- "net_dns_info": {...},
- "net_ifgrp_info": {...},
- "net_interface_info": {...},
- "net_port_info": {...},
- "security_key_manager_key_info": {...},
- "security_login_account_info": {...},
- "volume_info": {...},
- "lun_info": {...},
- "storage_failover_info": {...},
- "vserver_login_banner_info": {...},
- "vserver_motd_info": {...},
- "vserver_info": {...},
- "vserver_nfs_info": {...},
- "ontap_version": {...},
- "igroup_info": {...},
- "qos_policy_info": {...},
- "qos_adaptive_policy_info": {...}
- }'
-'''
-
-import traceback
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-try:
- import xmltodict
- HAS_XMLTODICT = True
-except ImportError:
- HAS_XMLTODICT = False
-
-try:
- import json
- HAS_JSON = True
-except ImportError:
- HAS_JSON = False
-
-HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
-
-
-class NetAppONTAPGatherFacts(object):
- '''Class with gather facts methods'''
-
- def __init__(self, module):
- self.module = module
- self.netapp_info = dict()
-
- # thanks to coreywan (https://github.com/ansible/ansible/pull/47016)
- # for starting this
- # min_version identifies the ontapi version which supports this ZAPI
- # use 0 if it is supported since 9.1
- self.fact_subsets = {
- 'net_dns_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'net-dns-get-iter',
- 'attribute': 'net-dns-info',
- 'field': 'vserver-name',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'net_interface_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'net-interface-get-iter',
- 'attribute': 'net-interface-info',
- 'field': 'interface-name',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'net_port_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'net-port-get-iter',
- 'attribute': 'net-port-info',
- 'field': ('node', 'port'),
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'cluster_node_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'cluster-node-get-iter',
- 'attribute': 'cluster-node-info',
- 'field': 'node-name',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'security_login_account_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'security-login-get-iter',
- 'attribute': 'security-login-account-info',
- 'field': ('vserver', 'user-name', 'application', 'authentication-method'),
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'aggregate_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'aggr-get-iter',
- 'attribute': 'aggr-attributes',
- 'field': 'aggregate-name',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'volume_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'volume-get-iter',
- 'attribute': 'volume-attributes',
- 'field': ('name', 'owning-vserver-name'),
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'lun_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'lun-get-iter',
- 'attribute': 'lun-info',
- 'field': 'path',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'storage_failover_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'cf-get-iter',
- 'attribute': 'storage-failover-info',
- 'field': 'node',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'vserver_motd_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'vserver-motd-get-iter',
- 'attribute': 'vserver-motd-info',
- 'field': 'vserver',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'vserver_login_banner_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'vserver-login-banner-get-iter',
- 'attribute': 'vserver-login-banner-info',
- 'field': 'vserver',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'security_key_manager_key_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'security-key-manager-key-get-iter',
- 'attribute': 'security-key-manager-key-info',
- 'field': ('node', 'key-id'),
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'vserver_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'vserver-get-iter',
- 'attribute': 'vserver-info',
- 'field': 'vserver-name',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'vserver_nfs_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'nfs-service-get-iter',
- 'attribute': 'nfs-info',
- 'field': 'vserver',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'net_ifgrp_info': {
- 'method': self.get_ifgrp_info,
- 'kwargs': {},
- 'min_version': '0',
- },
- 'ontap_version': {
- 'method': self.ontapi,
- 'kwargs': {},
- 'min_version': '0',
- },
- 'system_node_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'system-node-get-iter',
- 'attribute': 'node-details-info',
- 'field': 'node',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'igroup_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'igroup-get-iter',
- 'attribute': 'initiator-group-info',
- 'field': ('vserver', 'initiator-group-name'),
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- 'qos_policy_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'qos-policy-group-get-iter',
- 'attribute': 'qos-policy-group-info',
- 'field': 'policy-group',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '0',
- },
- # supported in ONTAP 9.3 and onwards
- 'qos_adaptive_policy_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'qos-adaptive-policy-group-get-iter',
- 'attribute': 'qos-adaptive-policy-group-info',
- 'field': 'policy-group',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '130',
- },
- # supported in ONTAP 9.4 and onwards
- 'nvme_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'nvme-get-iter',
- 'attribute': 'nvme-target-service-info',
- 'field': 'vserver',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '140',
- },
- 'nvme_interface_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'nvme-interface-get-iter',
- 'attribute': 'nvme-interface-info',
- 'field': 'vserver',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '140',
- },
- 'nvme_subsystem_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'nvme-subsystem-get-iter',
- 'attribute': 'nvme-subsystem-info',
- 'field': 'subsystem',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '140',
- },
- 'nvme_namespace_info': {
- 'method': self.get_generic_get_iter,
- 'kwargs': {
- 'call': 'nvme-namespace-get-iter',
- 'attribute': 'nvme-namespace-info',
- 'field': 'path',
- 'query': {'max-records': '1024'},
- },
- 'min_version': '140',
- },
- }
-
- if HAS_NETAPP_LIB is False:
- self.module.fail_json(msg="the python NetApp-Lib module is required")
- else:
- self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
-
- def ontapi(self):
- '''Method to get ontapi version'''
-
- api = 'system-get-ontapi-version'
- api_call = netapp_utils.zapi.NaElement(api)
- try:
- results = self.server.invoke_successfully(api_call, enable_tunneling=False)
- ontapi_version = results.get_child_content('minor-version')
- return ontapi_version if ontapi_version is not None else '0'
- except netapp_utils.zapi.NaApiError as error:
- self.module.fail_json(msg="Error calling API %s: %s" %
- (api, to_native(error)), exception=traceback.format_exc())
-
- def call_api(self, call, query=None):
- '''Main method to run an API call'''
-
- api_call = netapp_utils.zapi.NaElement(call)
- result = None
-
- if query:
- for key, val in query.items():
- # Can val be nested?
- api_call.add_new_child(key, val)
- try:
- result = self.server.invoke_successfully(api_call, enable_tunneling=False)
- return result
- except netapp_utils.zapi.NaApiError as error:
- if call in ['security-key-manager-key-get-iter']:
- return result
- else:
- self.module.fail_json(msg="Error calling API %s: %s"
- % (call, to_native(error)), exception=traceback.format_exc())
-
- def get_ifgrp_info(self):
- '''Method to get network port ifgroups info'''
-
- try:
- net_port_info = self.netapp_info['net_port_info']
- except KeyError:
- net_port_info_calls = self.fact_subsets['net_port_info']
- net_port_info = net_port_info_calls['method'](**net_port_info_calls['kwargs'])
- interfaces = net_port_info.keys()
-
- ifgrps = []
- for ifn in interfaces:
- if net_port_info[ifn]['port_type'] == 'if_group':
- ifgrps.append(ifn)
-
- net_ifgrp_info = dict()
- for ifgrp in ifgrps:
- query = dict()
- query['node'], query['ifgrp-name'] = ifgrp.split(':')
-
- tmp = self.get_generic_get_iter('net-port-ifgrp-get', field=('node', 'ifgrp-name'),
- attribute='net-ifgrp-info', query=query)
- net_ifgrp_info = net_ifgrp_info.copy()
- net_ifgrp_info.update(tmp)
- return net_ifgrp_info
-
- def get_generic_get_iter(self, call, attribute=None, field=None, query=None):
- '''Method to run a generic get-iter call'''
-
- generic_call = self.call_api(call, query)
-
- if call == 'net-port-ifgrp-get':
- children = 'attributes'
- else:
- children = 'attributes-list'
-
- if generic_call is None:
- return None
-
- if field is None:
- out = []
- else:
- out = {}
-
- attributes_list = generic_call.get_child_by_name(children)
-
- if attributes_list is None:
- return None
-
- for child in attributes_list.get_children():
- dic = xmltodict.parse(child.to_string(), xml_attribs=False)
-
- if attribute is not None:
- dic = dic[attribute]
-
- if isinstance(field, str):
- unique_key = _finditem(dic, field)
- out = out.copy()
- out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))})
- elif isinstance(field, tuple):
- unique_key = ':'.join([_finditem(dic, el) for el in field])
- out = out.copy()
- out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))})
- else:
- out.append(convert_keys(json.loads(json.dumps(dic))))
-
- return out
-
- def get_all(self, gather_subset):
- '''Method to get all subsets'''
-
- results = netapp_utils.get_cserver(self.server)
- cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
- netapp_utils.ems_log_event("na_ontap_gather_facts", cserver)
-
- self.netapp_info['ontap_version'] = self.ontapi()
-
- run_subset = self.get_subset(gather_subset, self.netapp_info['ontap_version'])
- if 'help' in gather_subset:
- self.netapp_info['help'] = sorted(run_subset)
- else:
- for subset in run_subset:
- call = self.fact_subsets[subset]
- self.netapp_info[subset] = call['method'](**call['kwargs'])
-
- return self.netapp_info
-
- def get_subset(self, gather_subset, version):
- '''Method to get a single subset'''
-
- runable_subsets = set()
- exclude_subsets = set()
- usable_subsets = [key for key in self.fact_subsets.keys() if version >= self.fact_subsets[key]['min_version']]
- if 'help' in gather_subset:
- return usable_subsets
- for subset in gather_subset:
- if subset == 'all':
- runable_subsets.update(usable_subsets)
- return runable_subsets
- if subset.startswith('!'):
- subset = subset[1:]
- if subset == 'all':
- return set()
- exclude = True
- else:
- exclude = False
-
- if subset not in usable_subsets:
- if subset not in self.fact_subsets.keys():
- self.module.fail_json(msg='Bad subset: %s' % subset)
- self.module.fail_json(msg='Remote system at version %s does not support %s' %
- (version, subset))
-
- if exclude:
- exclude_subsets.add(subset)
- else:
- runable_subsets.add(subset)
-
- if not runable_subsets:
- runable_subsets.update(usable_subsets)
-
- runable_subsets.difference_update(exclude_subsets)
-
- return runable_subsets
-
-
-# https://stackoverflow.com/questions/14962485/finding-a-key-recursively-in-a-dictionary
-def __finditem(obj, key):
-
- if key in obj:
- return obj[key]
- for dummy, val in obj.items():
- if isinstance(val, dict):
- item = __finditem(val, key)
- if item is not None:
- return item
- return None
-
-
-def _finditem(obj, key):
-
- value = __finditem(obj, key)
- if value is not None:
- return value
- raise KeyError(key)
-
-
-def convert_keys(d_param):
- '''Method to convert hyphen to underscore'''
-
- out = {}
- if isinstance(d_param, dict):
- for key, val in d_param.items():
- val = convert_keys(val)
- out[key.replace('-', '_')] = val
- else:
- return d_param
- return out
-
-
-def main():
- '''Execute action'''
-
- argument_spec = netapp_utils.na_ontap_host_argument_spec()
- argument_spec.update(dict(
- state=dict(default='info', choices=['info']),
- gather_subset=dict(default=['all'], type='list'),
- ))
-
- module = AnsibleModule(
- argument_spec=argument_spec,
- supports_check_mode=True
- )
-
- if not HAS_XMLTODICT:
- module.fail_json(msg="xmltodict missing")
-
- if not HAS_JSON:
- module.fail_json(msg="json missing")
-
- state = module.params['state']
- gather_subset = module.params['gather_subset']
- if gather_subset is None:
- gather_subset = ['all']
- gf_obj = NetAppONTAPGatherFacts(module)
- gf_all = gf_obj.get_all(gather_subset)
- result = {'state': state, 'changed': False}
- module.exit_json(ansible_facts={'ontap_facts': gf_all}, **result)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_sf_account_manager.py b/lib/ansible/modules/storage/netapp/_sf_account_manager.py
deleted file mode 100644
index a4f270892f..0000000000
--- a/lib/ansible/modules/storage/netapp/_sf_account_manager.py
+++ /dev/null
@@ -1,268 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: sf_account_manager
-deprecated:
- removed_in: "2.11"
- why: This Module has been replaced
- alternative: please use M(na_elementsw_account)
-short_description: Manage SolidFire accounts
-extends_documentation_fragment:
- - netapp.solidfire
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-description:
-- Create, destroy, or update accounts on SolidFire
-
-options:
-
- state:
- description:
- - Whether the specified account should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - Unique username for this account. (May be 1 to 64 characters in length).
- required: true
-
- new_name:
- description:
- - New name for the user account.
-
- initiator_secret:
- description:
- - CHAP secret to use for the initiator. Should be 12-16 characters long and impenetrable.
- - The CHAP initiator secrets must be unique and cannot be the same as the target CHAP secret.
- - If not specified, a random secret is created.
-
- target_secret:
- description:
- - CHAP secret to use for the target (mutual CHAP authentication).
- - Should be 12-16 characters long and impenetrable.
- - The CHAP target secrets must be unique and cannot be the same as the initiator CHAP secret.
- - If not specified, a random secret is created.
-
- attributes:
- description: List of Name/Value pairs in JSON object format.
-
- account_id:
- description:
- - The ID of the account to manage or update.
-
- status:
- description:
- - Status of the account.
-
-'''
-
-EXAMPLES = """
-- name: Create Account
- sf_account_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- name: TenantA
-
-- name: Modify Account
- sf_account_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- name: TenantA
- new_name: TenantA-Renamed
-
-- name: Delete Account
- sf_account_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: absent
- name: TenantA-Renamed
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class SolidFireAccount(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- account_id=dict(required=False, type='int', default=None),
-
- new_name=dict(required=False, type='str', default=None),
- initiator_secret=dict(required=False, type='str'),
- target_secret=dict(required=False, type='str'),
- attributes=dict(required=False, type='dict'),
- status=dict(required=False, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.account_id = p['account_id']
-
- self.new_name = p['new_name']
- self.initiator_secret = p['initiator_secret']
- self.target_secret = p['target_secret']
- self.attributes = p['attributes']
- self.status = p['status']
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
-
- def get_account(self):
- """
- Return account object if found
-
- :return: Details about the account. None if not found.
- :rtype: dict
- """
- account_list = self.sfe.list_accounts()
-
- for account in account_list.accounts:
- if account.username == self.name:
- # Update self.account_id:
- if self.account_id is not None:
- if account.account_id == self.account_id:
- return account
- else:
- self.account_id = account.account_id
- return account
- return None
-
- def create_account(self):
- try:
- self.sfe.add_account(username=self.name,
- initiator_secret=self.initiator_secret,
- target_secret=self.target_secret,
- attributes=self.attributes)
- except Exception as e:
- self.module.fail_json(msg='Error creating account %s: %s)' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_account(self):
- try:
- self.sfe.remove_account(account_id=self.account_id)
-
- except Exception as e:
- self.module.fail_json(msg='Error deleting account %s: %s' % (self.account_id, to_native(e)),
- exception=traceback.format_exc())
-
- def update_account(self):
- try:
- self.sfe.modify_account(account_id=self.account_id,
- username=self.new_name,
- status=self.status,
- initiator_secret=self.initiator_secret,
- target_secret=self.target_secret,
- attributes=self.attributes)
-
- except Exception as e:
- self.module.fail_json(msg='Error updating account %s: %s' % (self.account_id, to_native(e)),
- exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- account_exists = False
- update_account = False
- account_detail = self.get_account()
-
- if account_detail:
- account_exists = True
-
- if self.state == 'absent':
- changed = True
-
- elif self.state == 'present':
- # Check if we need to update the account
-
- if account_detail.username is not None and self.new_name is not None and \
- account_detail.username != self.new_name:
- update_account = True
- changed = True
-
- elif account_detail.status is not None and self.status is not None \
- and account_detail.status != self.status:
- update_account = True
- changed = True
-
- elif account_detail.initiator_secret is not None and self.initiator_secret is not None \
- and account_detail.initiator_secret != self.initiator_secret:
- update_account = True
- changed = True
-
- elif account_detail.target_secret is not None and self.target_secret is not None \
- and account_detail.target_secret != self.target_secret:
- update_account = True
- changed = True
-
- elif account_detail.attributes is not None and self.attributes is not None \
- and account_detail.attributes != self.attributes:
- update_account = True
- changed = True
- else:
- if self.state == 'present':
- changed = True
-
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not account_exists:
- self.create_account()
- elif update_account:
- self.update_account()
-
- elif self.state == 'absent':
- self.delete_account()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = SolidFireAccount()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_sf_check_connections.py b/lib/ansible/modules/storage/netapp/_sf_check_connections.py
deleted file mode 100644
index 5f3a44329e..0000000000
--- a/lib/ansible/modules/storage/netapp/_sf_check_connections.py
+++ /dev/null
@@ -1,184 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: sf_check_connections
-deprecated:
- removed_in: "2.11"
- why: This Module has been replaced
- alternative: please use M(na_elementsw_check_connections)
-short_description: Check connectivity to MVIP and SVIP.
-extends_documentation_fragment:
- - netapp.solidfire
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-description:
-- Used to test the management connection to the cluster.
-- The test pings the MVIP and SVIP, and executes a simple API method to verify connectivity.
-
-options:
-
- skip:
- description:
- - Skip checking connection to SVIP or MVIP.
- choices: ['svip', 'mvip']
-
- mvip:
- description:
- - Optionally, use to test connection of a different MVIP.
- - This is not needed to test the connection to the target cluster.
-
- svip:
- description:
- - Optionally, use to test connection of a different SVIP.
- - This is not needed to test the connection to the target cluster.
-
-'''
-
-
-EXAMPLES = """
- - name: Check connections to MVIP and SVIP
- sf_check_connections:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class SolidFireConnection(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- skip=dict(required=False, type='str', default=None, choices=['mvip', 'svip']),
- mvip=dict(required=False, type='str', default=None),
- svip=dict(required=False, type='str', default=None)
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.skip = p['skip']
- self.mvip = p['mvip']
- self.svip = p['svip']
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.sfe = netapp_utils.ElementFactory.create(p['hostname'], p['username'], p['password'], port=442)
-
- def check_mvip_connection(self):
- """
- Check connection to MVIP
-
- :return: true if connection was successful, false otherwise.
- :rtype: bool
- """
- try:
- test = self.sfe.test_connect_mvip(mvip=self.mvip)
- result = test.details.connected
- # Todo - Log details about the test
- return result
-
- except Exception as e:
- self.module.fail_json(msg='Error checking connection to MVIP: %s' % to_native(e), exception=traceback.format_exc())
- return False
-
- def check_svip_connection(self):
- """
- Check connection to SVIP
-
- :return: true if connection was successful, false otherwise.
- :rtype: bool
- """
- try:
- test = self.sfe.test_connect_svip(svip=self.svip)
- result = test.details.connected
- # Todo - Log details about the test
- return result
-
- except Exception as e:
- self.module.fail_json(msg='Error checking connection to SVIP: %s' % to_native(e), exception=traceback.format_exc())
- return False
-
- def check(self):
-
- failed = True
- msg = ''
-
- if self.skip is None:
- mvip_connection_established = self.check_mvip_connection()
- svip_connection_established = self.check_svip_connection()
-
- # Set failed and msg
- if not mvip_connection_established:
- failed = True
- msg = 'Connection to MVIP failed.'
- elif not svip_connection_established:
- failed = True
- msg = 'Connection to SVIP failed.'
- else:
- failed = False
-
- elif self.skip == 'mvip':
- svip_connection_established = self.check_svip_connection()
-
- # Set failed and msg
- if not svip_connection_established:
- failed = True
- msg = 'Connection to SVIP failed.'
- else:
- failed = False
-
- elif self.skip == 'svip':
- mvip_connection_established = self.check_mvip_connection()
-
- # Set failed and msg
- if not mvip_connection_established:
- failed = True
- msg = 'Connection to MVIP failed.'
- else:
- failed = False
-
- if failed:
- self.module.fail_json(msg=msg)
- else:
- self.module.exit_json()
-
-
-def main():
- v = SolidFireConnection()
- v.check()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_sf_snapshot_schedule_manager.py b/lib/ansible/modules/storage/netapp/_sf_snapshot_schedule_manager.py
deleted file mode 100644
index 0301375bec..0000000000
--- a/lib/ansible/modules/storage/netapp/_sf_snapshot_schedule_manager.py
+++ /dev/null
@@ -1,389 +0,0 @@
-#!/usr/bin/python
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: sf_snapshot_schedule_manager
-deprecated:
- removed_in: "2.11"
- why: This Module has been replaced
- alternative: please use M(na_elementsw_snapshot_schedule)
-short_description: Manage SolidFire snapshot schedules
-extends_documentation_fragment:
- - netapp.solidfire
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-description:
-- Create, destroy, or update accounts on SolidFire
-
-options:
-
- state:
- description:
- - Whether the specified schedule should exist or not.
- required: true
- choices: ['present', 'absent']
-
- paused:
- description:
- - Pause / Resume a schedule.
- required: false
-
- recurring:
- description:
- - Should the schedule recur?
- required: false
-
- time_interval_days:
- description: Time interval in days.
- required: false
- default: 1
-
- time_interval_hours:
- description: Time interval in hours.
- required: false
- default: 0
-
- time_interval_minutes:
- description: Time interval in minutes.
- required: false
- default: 0
-
- name:
- description:
- - Name for the snapshot schedule.
- required: true
-
- snapshot_name:
- description:
- - Name for the created snapshots.
- required: false
-
- volumes:
- description:
- - Volume IDs that you want to set the snapshot schedule for.
- - At least 1 volume ID is required for creating a new schedule.
- - required when C(state=present)
- required: false
-
- retention:
- description:
- - Retention period for the snapshot.
- - Format is 'HH:mm:ss'.
- required: false
-
- schedule_id:
- description:
- - The schedule ID for the schedule that you want to update or delete.
- required: false
-
- starting_date:
- description:
- - Starting date for the schedule.
- - Required when C(state=present).
- - Please use two '-' in the above format, or you may see an error- TypeError, is not JSON serializable description.
- - "Format: C(2016--12--01T00:00:00Z)"
- required: false
-'''
-
-EXAMPLES = """
- - name: Create Snapshot schedule
- sf_snapshot_schedule_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- name: Schedule_A
- time_interval_days: 1
- starting_date: 2016--12--01T00:00:00Z
- volumes: 7
-
- - name: Update Snapshot schedule
- sf_snapshot_schedule_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- schedule_id: 6
- recurring: True
- snapshot_name: AnsibleSnapshots
-
- - name: Delete Snapshot schedule
- sf_snapshot_schedule_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: absent
- schedule_id: 6
-"""
-
-RETURN = """
-
-schedule_id:
- description: Schedule ID of the newly created schedule
- returned: success
- type: str
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class SolidFireSnapShotSchedule(object):
-
- def __init__(self):
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
-
- time_interval_days=dict(required=False, type='int', default=1),
- time_interval_hours=dict(required=False, type='int', default=0),
- time_interval_minutes=dict(required=False, type='int', default=0),
-
- paused=dict(required=False, type='bool'),
- recurring=dict(required=False, type='bool'),
-
- starting_date=dict(type='str'),
-
- snapshot_name=dict(required=False, type='str'),
- volumes=dict(required=False, type='list'),
- retention=dict(required=False, type='str'),
-
- schedule_id=dict(type='int'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['starting_date', 'volumes'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
-
- # self.interval = p['interval']
-
- self.time_interval_days = p['time_interval_days']
- self.time_interval_hours = p['time_interval_hours']
- self.time_interval_minutes = p['time_interval_minutes']
-
- self.paused = p['paused']
- self.recurring = p['recurring']
-
- self.starting_date = p['starting_date']
- if self.starting_date is not None:
- self.starting_date = self.starting_date.replace("--", "-")
-
- self.snapshot_name = p['snapshot_name']
- self.volumes = p['volumes']
- self.retention = p['retention']
-
- self.schedule_id = p['schedule_id']
-
- self.create_schedule_result = None
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
-
- def get_schedule(self):
- schedule_list = self.sfe.list_schedules()
- for schedule in schedule_list.schedules:
- if schedule.name == self.name:
- # Update self.schedule_id:
- if self.schedule_id is not None:
- if schedule.schedule_id == self.schedule_id:
- return schedule
- else:
- self.schedule_id = schedule.schedule_id
- return schedule
-
- return None
-
- def create_schedule(self):
-
- try:
- sched = netapp_utils.Schedule()
- # if self.interval == 'time_interval':
- sched.frequency = netapp_utils.TimeIntervalFrequency(days=self.time_interval_days,
- hours=self.time_interval_hours,
- minutes=self.time_interval_minutes)
-
- # Create schedule
- sched.name = self.name
- sched.schedule_info = netapp_utils.ScheduleInfo(
- volume_ids=self.volumes,
- snapshot_name=self.snapshot_name,
- retention=self.retention
- )
- sched.paused = self.paused
- sched.recurring = self.recurring
- sched.starting_date = self.starting_date
-
- self.create_schedule_result = self.sfe.create_schedule(schedule=sched)
-
- except Exception as e:
- self.module.fail_json(msg='Error creating schedule %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_schedule(self):
-
- try:
- get_schedule_result = self.sfe.get_schedule(schedule_id=self.schedule_id)
- sched = get_schedule_result.schedule
- sched.to_be_deleted = True
- self.sfe.modify_schedule(schedule=sched)
-
- except Exception as e:
- self.module.fail_json(msg='Error deleting schedule %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def update_schedule(self):
-
- try:
- get_schedule_result = self.sfe.get_schedule(schedule_id=self.schedule_id)
- sched = get_schedule_result.schedule
-
- # Update schedule properties
-
- # if self.interval == 'time_interval':
- temp_frequency = netapp_utils.TimeIntervalFrequency(days=self.time_interval_days,
- hours=self.time_interval_hours,
- minutes=self.time_interval_minutes)
-
- if sched.frequency.days != temp_frequency.days or \
- sched.frequency.hours != temp_frequency.hours \
- or sched.frequency.minutes != temp_frequency.minutes:
- sched.frequency = temp_frequency
-
- sched.name = self.name
- if self.volumes is not None:
- sched.schedule_info.volume_ids = self.volumes
- if self.retention is not None:
- sched.schedule_info.retention = self.retention
- if self.snapshot_name is not None:
- sched.schedule_info.snapshot_name = self.snapshot_name
- if self.paused is not None:
- sched.paused = self.paused
- if self.recurring is not None:
- sched.recurring = self.recurring
- if self.starting_date is not None:
- sched.starting_date = self.starting_date
-
- # Make API call
- self.sfe.modify_schedule(schedule=sched)
-
- except Exception as e:
- self.module.fail_json(msg='Error updating schedule %s: %s' % (self.name, to_native(e)),
- exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- schedule_exists = False
- update_schedule = False
- schedule_detail = self.get_schedule()
-
- if schedule_detail:
- schedule_exists = True
-
- if self.state == 'absent':
- changed = True
-
- elif self.state == 'present':
- # Check if we need to update the account
-
- if self.retention is not None and schedule_detail.schedule_info.retention != self.retention:
- update_schedule = True
- changed = True
-
- elif schedule_detail.name != self.name:
- update_schedule = True
- changed = True
-
- elif self.snapshot_name is not None and schedule_detail.schedule_info.snapshot_name != self.snapshot_name:
- update_schedule = True
- changed = True
-
- elif self.volumes is not None and schedule_detail.schedule_info.volume_ids != self.volumes:
- update_schedule = True
- changed = True
-
- elif self.paused is not None and schedule_detail.paused != self.paused:
- update_schedule = True
- changed = True
-
- elif self.recurring is not None and schedule_detail.recurring != self.recurring:
- update_schedule = True
- changed = True
-
- elif self.starting_date is not None and schedule_detail.starting_date != self.starting_date:
- update_schedule = True
- changed = True
-
- elif self.time_interval_minutes is not None or self.time_interval_hours is not None \
- or self.time_interval_days is not None:
-
- temp_frequency = netapp_utils.TimeIntervalFrequency(days=self.time_interval_days,
- hours=self.time_interval_hours,
- minutes=self.time_interval_minutes)
-
- if schedule_detail.frequency.days != temp_frequency.days or \
- schedule_detail.frequency.hours != temp_frequency.hours \
- or schedule_detail.frequency.minutes != temp_frequency.minutes:
- update_schedule = True
- changed = True
-
- else:
- if self.state == 'present':
- changed = True
-
- if changed:
- if self.module.check_mode:
- # Skip changes
- pass
- else:
- if self.state == 'present':
- if not schedule_exists:
- self.create_schedule()
- elif update_schedule:
- self.update_schedule()
-
- elif self.state == 'absent':
- self.delete_schedule()
-
- if self.create_schedule_result is not None:
- self.module.exit_json(changed=changed, schedule_id=self.create_schedule_result.schedule_id)
- else:
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = SolidFireSnapShotSchedule()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_sf_volume_access_group_manager.py b/lib/ansible/modules/storage/netapp/_sf_volume_access_group_manager.py
deleted file mode 100644
index 0c635336a4..0000000000
--- a/lib/ansible/modules/storage/netapp/_sf_volume_access_group_manager.py
+++ /dev/null
@@ -1,249 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: sf_volume_access_group_manager
-deprecated:
- removed_in: "2.11"
- why: This Module has been replaced
- alternative: please use M(na_elementsw_access_group)
-short_description: Manage SolidFire Volume Access Groups
-extends_documentation_fragment:
- - netapp.solidfire
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-description:
-- Create, destroy, or update volume access groups on SolidFire
-
-options:
-
- state:
- description:
- - Whether the specified volume access group should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - Name of the volume access group. It is not required to be unique, but recommended.
- required: true
-
- initiators:
- description:
- - List of initiators to include in the volume access group. If unspecified, the access group will start out without configured initiators.
-
- volumes:
- description:
- - List of volumes to initially include in the volume access group. If unspecified, the access group will start without any volumes.
-
- virtual_network_id:
- description:
- - The ID of the SolidFire Virtual Network ID to associate the volume access group with.
-
- virtual_network_tags:
- description:
- - The ID of the VLAN Virtual Network Tag to associate the volume access group with.
-
- attributes:
- description: List of Name/Value pairs in JSON object format.
-
- volume_access_group_id:
- description:
- - The ID of the volume access group to modify or delete.
-
-'''
-
-EXAMPLES = """
- - name: Create Volume Access Group
- sf_volume_access_group_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- name: AnsibleVolumeAccessGroup
- volumes: [7,8]
-
- - name: Modify Volume Access Group
- sf_volume_access_group_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- volume_access_group_id: 1
- name: AnsibleVolumeAccessGroup-Renamed
- attributes: {"volumes": [1,2,3], "virtual_network_id": 12345}
-
- - name: Delete Volume Access Group
- sf_volume_access_group_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: absent
- volume_access_group_id: 1
-"""
-
-RETURN = """
-
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class SolidFireVolumeAccessGroup(object):
-
- def __init__(self):
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- volume_access_group_id=dict(required=False, type='int', default=None),
-
- initiators=dict(required=False, type='list', default=None),
- volumes=dict(required=False, type='list', default=None),
- virtual_network_id=dict(required=False, type='list', default=None),
- virtual_network_tags=dict(required=False, type='list', default=None),
- attributes=dict(required=False, type='dict', default=None),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.volume_access_group_id = p['volume_access_group_id']
-
- self.initiators = p['initiators']
- self.volumes = p['volumes']
- self.virtual_network_id = p['virtual_network_id']
- self.virtual_network_tags = p['virtual_network_tags']
- self.attributes = p['attributes']
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
-
- def get_volume_access_group(self):
- access_groups_list = self.sfe.list_volume_access_groups()
-
- for group in access_groups_list.volume_access_groups:
- if group.name == self.name:
- # Update self.volume_access_group_id:
- if self.volume_access_group_id is not None:
- if group.volume_access_group_id == self.volume_access_group_id:
- return group
- else:
- self.volume_access_group_id = group.volume_access_group_id
- return group
- return None
-
- def create_volume_access_group(self):
- try:
- self.sfe.create_volume_access_group(name=self.name,
- initiators=self.initiators,
- volumes=self.volumes,
- virtual_network_id=self.virtual_network_id,
- virtual_network_tags=self.virtual_network_tags,
- attributes=self.attributes)
- except Exception as e:
- self.module.fail_json(msg="Error creating volume access group %s: %s" %
- (self.name, to_native(e)), exception=traceback.format_exc())
-
- def delete_volume_access_group(self):
- try:
- self.sfe.delete_volume_access_group(volume_access_group_id=self.volume_access_group_id)
-
- except Exception as e:
- self.module.fail_json(msg="Error deleting volume access group %s: %s" %
- (self.volume_access_group_id, to_native(e)),
- exception=traceback.format_exc())
-
- def update_volume_access_group(self):
- try:
- self.sfe.modify_volume_access_group(volume_access_group_id=self.volume_access_group_id,
- virtual_network_id=self.virtual_network_id,
- virtual_network_tags=self.virtual_network_tags,
- name=self.name,
- initiators=self.initiators,
- volumes=self.volumes,
- attributes=self.attributes)
- except Exception as e:
- self.module.fail_json(msg="Error updating volume access group %s: %s" %
- (self.volume_access_group_id, to_native(e)), exception=traceback.format_exc())
-
- def apply(self):
- changed = False
- group_exists = False
- update_group = False
- group_detail = self.get_volume_access_group()
-
- if group_detail:
- group_exists = True
-
- if self.state == 'absent':
- changed = True
-
- elif self.state == 'present':
- # Check if we need to update the group
- if self.volumes is not None and group_detail.volumes != self.volumes:
- update_group = True
- changed = True
- elif self.initiators is not None and group_detail.initiators != self.initiators:
- update_group = True
- changed = True
- elif self.virtual_network_id is not None or self.virtual_network_tags is not None or \
- self.attributes is not None:
- update_group = True
- changed = True
-
- else:
- if self.state == 'present':
- changed = True
-
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- if not group_exists:
- self.create_volume_access_group()
- elif update_group:
- self.update_volume_access_group()
-
- elif self.state == 'absent':
- self.delete_volume_access_group()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = SolidFireVolumeAccessGroup()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/_sf_volume_manager.py b/lib/ansible/modules/storage/netapp/_sf_volume_manager.py
deleted file mode 100644
index 16cb4220c3..0000000000
--- a/lib/ansible/modules/storage/netapp/_sf_volume_manager.py
+++ /dev/null
@@ -1,320 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: sf_volume_manager
-deprecated:
- removed_in: "2.11"
- why: This Module has been replaced
- alternative: please use M(na_elementsw_volume)
-short_description: Manage SolidFire volumes
-extends_documentation_fragment:
- - netapp.solidfire
-version_added: '2.3'
-author: Sumit Kumar (@timuster) <sumit4@netapp.com>
-description:
-- Create, destroy, or update volumes on SolidFire
-
-options:
-
- state:
- description:
- - Whether the specified volume should exist or not.
- required: true
- choices: ['present', 'absent']
-
- name:
- description:
- - The name of the volume to manage.
- required: true
-
- account_id:
- description:
- - Account ID for the owner of this volume.
- required: true
-
- 512emulation:
- description:
- - Should the volume provide 512-byte sector emulation?
- - Required when C(state=present)
-
- qos:
- description: Initial quality of service settings for this volume. Configure as dict in playbooks.
-
- attributes:
- description: A YAML dictionary of attributes that you would like to apply on this volume.
-
- volume_id:
- description:
- - The ID of the volume to manage or update.
- - In order to create multiple volumes with the same name, but different volume_ids, please declare the I(volume_id)
- parameter with an arbitrary value. However, the specified volume_id will not be assigned to the newly created
- volume (since it's an auto-generated property).
-
- size:
- description:
- - The size of the volume in (size_unit).
- - Required when C(state = present).
-
- size_unit:
- description:
- - The unit used to interpret the size parameter.
- choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
- default: 'gb'
-
- access:
- description:
- - "Access allowed for the volume."
- - "readOnly: Only read operations are allowed."
- - "readWrite: Reads and writes are allowed."
- - "locked: No reads or writes are allowed."
- - "replicationTarget: Identify a volume as the target volume for a paired set of volumes. If the volume is not paired, the access status is locked."
- - "If unspecified, the access settings of the clone will be the same as the source."
- choices: ['readOnly', 'readWrite', 'locked', 'replicationTarget']
-
-'''
-
-EXAMPLES = """
- - name: Create Volume
- sf_volume_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- name: AnsibleVol
- qos: {minIOPS: 1000, maxIOPS: 20000, burstIOPS: 50000}
- account_id: 3
- enable512e: False
- size: 1
- size_unit: gb
-
- - name: Update Volume
- sf_volume_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: present
- name: AnsibleVol
- account_id: 3
- access: readWrite
-
- - name: Delete Volume
- sf_volume_manager:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
- state: absent
- name: AnsibleVol
- account_id: 2
-"""
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible.module_utils.netapp as netapp_utils
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class SolidFireVolume(object):
-
- def __init__(self):
-
- self._size_unit_map = netapp_utils.SF_BYTE_MAP
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- name=dict(required=True, type='str'),
- account_id=dict(required=True, type='int'),
-
- enable512e=dict(type='bool', aliases=['512emulation']),
- qos=dict(required=False, type='dict', default=None),
- attributes=dict(required=False, type='dict', default=None),
-
- volume_id=dict(type='int', default=None),
- size=dict(type='int'),
- size_unit=dict(default='gb',
- choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
- 'pb', 'eb', 'zb', 'yb'], type='str'),
-
- access=dict(required=False, type='str', default=None, choices=['readOnly', 'readWrite',
- 'locked', 'replicationTarget']),
-
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['size', 'enable512e'])
- ],
- supports_check_mode=True
- )
-
- p = self.module.params
-
- # set up state variables
- self.state = p['state']
- self.name = p['name']
- self.account_id = p['account_id']
- self.enable512e = p['enable512e']
- self.qos = p['qos']
- self.attributes = p['attributes']
-
- self.volume_id = p['volume_id']
- self.size_unit = p['size_unit']
- if p['size'] is not None:
- self.size = p['size'] * self._size_unit_map[self.size_unit]
- else:
- self.size = None
- self.access = p['access']
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
-
- def get_volume(self):
- """
- Return volume object if found
-
- :return: Details about the volume. None if not found.
- :rtype: dict
- """
- volume_list = self.sfe.list_volumes_for_account(account_id=self.account_id)
- for volume in volume_list.volumes:
- if volume.name == self.name:
- # Update self.volume_id
- if self.volume_id is not None:
- if volume.volume_id == self.volume_id and str(volume.delete_time) == "":
- return volume
- else:
- if str(volume.delete_time) == "":
- self.volume_id = volume.volume_id
- return volume
- return None
-
- def create_volume(self):
- try:
- self.sfe.create_volume(name=self.name,
- account_id=self.account_id,
- total_size=self.size,
- enable512e=self.enable512e,
- qos=self.qos,
- attributes=self.attributes)
-
- except Exception as err:
- self.module.fail_json(msg="Error provisioning volume %s of size %s" % (self.name, self.size),
- exception=to_native(err))
-
- def delete_volume(self):
- try:
- self.sfe.delete_volume(volume_id=self.volume_id)
-
- except Exception as err:
- self.module.fail_json(msg="Error deleting volume %s" % self.volume_id,
- exception=to_native(err))
-
- def update_volume(self):
- try:
- self.sfe.modify_volume(self.volume_id,
- account_id=self.account_id,
- access=self.access,
- qos=self.qos,
- total_size=self.size,
- attributes=self.attributes)
-
- except Exception as err:
- self.module.fail_json(msg="Error updating volume %s" % self.name,
- exception=to_native(err))
-
- def apply(self):
- changed = False
- volume_exists = False
- update_volume = False
- volume_detail = self.get_volume()
-
- if volume_detail:
- volume_exists = True
-
- if self.state == 'absent':
- # Checking for state change(s) here, and applying it later in the code allows us to support
- # check_mode
- changed = True
-
- elif self.state == 'present':
- if volume_detail.access is not None and self.access is not None and volume_detail.access != self.access:
- update_volume = True
- changed = True
-
- elif volume_detail.account_id is not None and self.account_id is not None \
- and volume_detail.account_id != self.account_id:
- update_volume = True
- changed = True
-
- elif volume_detail.qos is not None and self.qos is not None and volume_detail.qos != self.qos:
- update_volume = True
- changed = True
-
- elif volume_detail.total_size is not None and volume_detail.total_size != self.size:
- size_difference = abs(float(volume_detail.total_size - self.size))
- # Change size only if difference is bigger than 0.001
- if size_difference / self.size > 0.001:
- update_volume = True
- changed = True
-
- elif volume_detail.attributes is not None and self.attributes is not None and \
- volume_detail.attributes != self.attributes:
- update_volume = True
- changed = True
- else:
- if self.state == 'present':
- changed = True
-
- result_message = ""
-
- if changed:
- if self.module.check_mode:
- result_message = "Check mode, skipping changes"
- else:
- if self.state == 'present':
- if not volume_exists:
- self.create_volume()
- result_message = "Volume created"
- elif update_volume:
- self.update_volume()
- result_message = "Volume updated"
-
- elif self.state == 'absent':
- self.delete_volume()
- result_message = "Volume deleted"
-
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- v = SolidFireVolume()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_alerts.py b/lib/ansible/modules/storage/netapp/netapp_e_alerts.py
deleted file mode 100644
index 017a1cfd9b..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_alerts.py
+++ /dev/null
@@ -1,280 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_alerts
-short_description: NetApp E-Series manage email notification settings
-description:
- - Certain E-Series systems have the capability to send email notifications on potentially critical events.
- - This module will allow the owner of the system to specify email recipients for these messages.
-version_added: '2.7'
-author: Michael Price (@lmprice)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Enable/disable the sending of email-based alerts.
- default: enabled
- required: false
- choices:
- - enabled
- - disabled
- server:
- description:
- - A fully qualified domain name, IPv4 address, or IPv6 address of a mail server.
- - To use a fully qualified domain name, you must configure a DNS server on both controllers using
- M(netapp_e_mgmt_interface).
- - Required when I(state=enabled).
- required: no
- sender:
- description:
- - This is the sender that the recipient will see. It doesn't necessarily need to be a valid email account.
- - Required when I(state=enabled).
- required: no
- contact:
- description:
- - Allows the owner to specify some free-form contact information to be included in the emails.
- - This is typically utilized to provide a contact phone number.
- required: no
- recipients:
- description:
- - The email addresses that will receive the email notifications.
- - Required when I(state=enabled).
- required: no
- test:
- description:
- - When a change is detected in the configuration, a test email will be sent.
- - This may take a few minutes to process.
- - Only applicable if I(state=enabled).
- default: no
- type: bool
- log_path:
- description:
- - Path to a file on the Ansible control node to be used for debug logging
- required: no
-notes:
- - Check mode is supported.
- - Alertable messages are a subset of messages shown by the Major Event Log (MEL), of the storage-system. Examples
- of alertable messages include drive failures, failed controllers, loss of redundancy, and other warning/critical
- events.
- - This API is currently only supported with the Embedded Web Services API v2.0 and higher.
-"""
-
-EXAMPLES = """
- - name: Enable email-based alerting
- netapp_e_alerts:
- state: enabled
- sender: noreply@example.com
- server: mail@example.com
- contact: "Phone: 1-555-555-5555"
- recipients:
- - name1@example.com
- - name2@example.com
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
-
- - name: Disable alerting
- netapp_e_alerts:
- state: disabled
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The settings have been updated.
-"""
-
-import json
-import logging
-from pprint import pformat
-import re
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class Alerts(object):
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- state=dict(type='str', required=False, default='enabled',
- choices=['enabled', 'disabled']),
- server=dict(type='str', required=False, ),
- sender=dict(type='str', required=False, ),
- contact=dict(type='str', required=False, ),
- recipients=dict(type='list', required=False, ),
- test=dict(type='bool', required=False, default=False, ),
- log_path=dict(type='str', required=False),
- ))
-
- required_if = [
- ['state', 'enabled', ['server', 'sender', 'recipients']]
- ]
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if)
- args = self.module.params
- self.alerts = args['state'] == 'enabled'
- self.server = args['server']
- self.sender = args['sender']
- self.contact = args['contact']
- self.recipients = args['recipients']
- self.test = args['test']
-
- self.ssid = args['ssid']
- self.url = args['api_url']
- self.creds = dict(url_password=args['api_password'],
- validate_certs=args['validate_certs'],
- url_username=args['api_username'], )
-
- self.check_mode = self.module.check_mode
-
- log_path = args['log_path']
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- # Very basic validation on email addresses: xx@yy.zz
- email = re.compile(r"[^@]+@[^@]+\.[^@]+")
-
- if self.sender and not email.match(self.sender):
- self.module.fail_json(msg="The sender (%s) provided is not a valid email address." % self.sender)
-
- if self.recipients is not None:
- for recipient in self.recipients:
- if not email.match(recipient):
- self.module.fail_json(msg="The recipient (%s) provided is not a valid email address." % recipient)
-
- if len(self.recipients) < 1:
- self.module.fail_json(msg="At least one recipient address must be specified.")
-
- def get_configuration(self):
- try:
- (rc, result) = request(self.url + 'storage-systems/%s/device-alerts' % self.ssid, headers=HEADERS,
- **self.creds)
- self._logger.info("Current config: %s", pformat(result))
- return result
-
- except Exception as err:
- self.module.fail_json(msg="Failed to retrieve the alerts configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def update_configuration(self):
- config = self.get_configuration()
- update = False
- body = dict()
-
- if self.alerts:
- body = dict(alertingEnabled=True)
- if not config['alertingEnabled']:
- update = True
-
- body.update(emailServerAddress=self.server)
- if config['emailServerAddress'] != self.server:
- update = True
-
- body.update(additionalContactInformation=self.contact, sendAdditionalContactInformation=True)
- if self.contact and (self.contact != config['additionalContactInformation']
- or not config['sendAdditionalContactInformation']):
- update = True
-
- body.update(emailSenderAddress=self.sender)
- if config['emailSenderAddress'] != self.sender:
- update = True
-
- self.recipients.sort()
- if config['recipientEmailAddresses']:
- config['recipientEmailAddresses'].sort()
-
- body.update(recipientEmailAddresses=self.recipients)
- if config['recipientEmailAddresses'] != self.recipients:
- update = True
-
- elif config['alertingEnabled']:
- body = dict(alertingEnabled=False)
- update = True
-
- self._logger.debug(pformat(body))
-
- if update and not self.check_mode:
- try:
- (rc, result) = request(self.url + 'storage-systems/%s/device-alerts' % self.ssid, method='POST',
- data=json.dumps(body), headers=HEADERS, **self.creds)
- # This is going to catch cases like a connection failure
- except Exception as err:
- self.module.fail_json(msg="We failed to set the storage-system name! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
- return update
-
- def send_test_email(self):
- """Send a test email to verify that the provided configuration is valid and functional."""
- if not self.check_mode:
- try:
- (rc, result) = request(self.url + 'storage-systems/%s/device-alerts/alert-email-test' % self.ssid,
- timeout=300, method='POST', headers=HEADERS, **self.creds)
-
- if result['response'] != 'emailSentOK':
- self.module.fail_json(msg="The test email failed with status=[%s]! Array Id [%s]."
- % (result['response'], self.ssid))
-
- # This is going to catch cases like a connection failure
- except Exception as err:
- self.module.fail_json(msg="We failed to send the test email! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def update(self):
- update = self.update_configuration()
-
- if self.test and update:
- self._logger.info("An update was detected and test=True, running a test.")
- self.send_test_email()
-
- if self.alerts:
- msg = 'Alerting has been enabled using server=%s, sender=%s.' % (self.server, self.sender)
- else:
- msg = 'Alerting has been disabled.'
-
- self.module.exit_json(msg=msg, changed=update, )
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- alerts = Alerts()
- alerts()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_amg.py b/lib/ansible/modules/storage/netapp/netapp_e_amg.py
deleted file mode 100644
index 9ebbcbb28b..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_amg.py
+++ /dev/null
@@ -1,255 +0,0 @@
-#!/usr/bin/python
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_amg
-short_description: NetApp E-Series create, remove, and update asynchronous mirror groups
-description:
- - Allows for the creation, removal and updating of Asynchronous Mirror Groups for NetApp E-series storage arrays
-version_added: '2.2'
-author: Kevin Hulquest (@hulquest)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- name:
- description:
- - The name of the async array you wish to target, or create.
- - If C(state) is present and the name isn't found, it will attempt to create.
- required: yes
- secondaryArrayId:
- description:
- - The ID of the secondary array to be used in mirroring process
- required: yes
- syncIntervalMinutes:
- description:
- - The synchronization interval in minutes
- default: 10
- manualSync:
- description:
- - Setting this to true will cause other synchronization values to be ignored
- type: bool
- default: 'no'
- recoveryWarnThresholdMinutes:
- description:
- - Recovery point warning threshold (minutes). The user will be warned when the age of the last good failures point exceeds this value
- default: 20
- repoUtilizationWarnThreshold:
- description:
- - Recovery point warning threshold
- default: 80
- interfaceType:
- description:
- - The intended protocol to use if both Fibre and iSCSI are available.
- choices:
- - iscsi
- - fibre
- syncWarnThresholdMinutes:
- description:
- - The threshold (in minutes) for notifying the user that periodic synchronization has taken too long to complete.
- default: 10
- state:
- description:
- - A C(state) of present will either create or update the async mirror group.
- - A C(state) of absent will remove the async mirror group.
- choices: [ absent, present ]
- required: yes
-"""
-
-EXAMPLES = """
- - name: AMG removal
- na_eseries_amg:
- state: absent
- ssid: "{{ ssid }}"
- secondaryArrayId: "{{amg_secondaryArrayId}}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- new_name: "{{amg_array_name}}"
- name: "{{amg_name}}"
- when: amg_create
-
- - name: AMG create
- netapp_e_amg:
- state: present
- ssid: "{{ ssid }}"
- secondaryArrayId: "{{amg_secondaryArrayId}}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- new_name: "{{amg_array_name}}"
- name: "{{amg_name}}"
- when: amg_create
-"""
-
-RETURN = """
-msg:
- description: Successful creation
- returned: success
- type: str
- sample: '{"changed": true, "connectionType": "fc", "groupRef": "3700000060080E5000299C24000006E857AC7EEC", "groupState": "optimal", "id": "3700000060080E5000299C24000006E857AC7EEC", "label": "amg_made_by_ansible", "localRole": "primary", "mirrorChannelRemoteTarget": "9000000060080E5000299C24005B06E557AC7EEC", "orphanGroup": false, "recoveryPointAgeAlertThresholdMinutes": 20, "remoteRole": "secondary", "remoteTarget": {"nodeName": {"ioInterfaceType": "fc", "iscsiNodeName": null, "remoteNodeWWN": "20040080E5299F1C"}, "remoteRef": "9000000060080E5000299C24005B06E557AC7EEC", "scsiinitiatorTargetBaseProperties": {"ioInterfaceType": "fc", "iscsiinitiatorTargetBaseParameters": null}}, "remoteTargetId": "ansible2", "remoteTargetName": "Ansible2", "remoteTargetWwn": "60080E5000299F880000000056A25D56", "repositoryUtilizationWarnThreshold": 80, "roleChangeProgress": "none", "syncActivity": "idle", "syncCompletionTimeAlertThresholdMinutes": 10, "syncIntervalMinutes": 10, "worldWideName": "60080E5000299C24000006E857AC7EEC"}'
-""" # NOQA
-
-import json
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-def has_match(module, ssid, api_url, api_pwd, api_usr, body):
- compare_keys = ['syncIntervalMinutes', 'syncWarnThresholdMinutes',
- 'recoveryWarnThresholdMinutes', 'repoUtilizationWarnThreshold']
- desired_state = dict((x, (body.get(x))) for x in compare_keys)
- label_exists = False
- matches_spec = False
- current_state = None
- async_id = None
- api_data = None
- desired_name = body.get('name')
- endpoint = 'storage-systems/%s/async-mirrors' % ssid
- url = api_url + endpoint
- try:
- rc, data = request(url, url_username=api_usr, url_password=api_pwd, headers=HEADERS)
- except Exception as e:
- module.exit_json(msg="Error finding a match. Message: %s" % to_native(e), exception=traceback.format_exc())
-
- for async_group in data:
- if async_group['label'] == desired_name:
- label_exists = True
- api_data = async_group
- async_id = async_group['groupRef']
- current_state = dict(
- syncIntervalMinutes=async_group['syncIntervalMinutes'],
- syncWarnThresholdMinutes=async_group['syncCompletionTimeAlertThresholdMinutes'],
- recoveryWarnThresholdMinutes=async_group['recoveryPointAgeAlertThresholdMinutes'],
- repoUtilizationWarnThreshold=async_group['repositoryUtilizationWarnThreshold'],
- )
-
- if current_state == desired_state:
- matches_spec = True
-
- return label_exists, matches_spec, api_data, async_id
-
-
-def create_async(module, ssid, api_url, api_pwd, api_usr, body):
- endpoint = 'storage-systems/%s/async-mirrors' % ssid
- url = api_url + endpoint
- post_data = json.dumps(body)
- try:
- rc, data = request(url, data=post_data, method='POST', url_username=api_usr, url_password=api_pwd,
- headers=HEADERS)
- except Exception as e:
- module.exit_json(msg="Exception while creating aysnc mirror group. Message: %s" % to_native(e),
- exception=traceback.format_exc())
- return data
-
-
-def update_async(module, ssid, api_url, pwd, user, body, new_name, async_id):
- endpoint = 'storage-systems/%s/async-mirrors/%s' % (ssid, async_id)
- url = api_url + endpoint
- compare_keys = ['syncIntervalMinutes', 'syncWarnThresholdMinutes',
- 'recoveryWarnThresholdMinutes', 'repoUtilizationWarnThreshold']
- desired_state = dict((x, (body.get(x))) for x in compare_keys)
-
- if new_name:
- desired_state['new_name'] = new_name
-
- post_data = json.dumps(desired_state)
-
- try:
- rc, data = request(url, data=post_data, method='POST', headers=HEADERS,
- url_username=user, url_password=pwd)
- except Exception as e:
- module.exit_json(msg="Exception while updating async mirror group. Message: %s" % to_native(e),
- exception=traceback.format_exc())
-
- return data
-
-
-def remove_amg(module, ssid, api_url, pwd, user, async_id):
- endpoint = 'storage-systems/%s/async-mirrors/%s' % (ssid, async_id)
- url = api_url + endpoint
- try:
- rc, data = request(url, method='DELETE', url_username=user, url_password=pwd,
- headers=HEADERS)
- except Exception as e:
- module.exit_json(msg="Exception while removing async mirror group. Message: %s" % to_native(e),
- exception=traceback.format_exc())
-
- return
-
-
-def main():
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- name=dict(required=True, type='str'),
- new_name=dict(required=False, type='str'),
- secondaryArrayId=dict(required=True, type='str'),
- syncIntervalMinutes=dict(required=False, default=10, type='int'),
- manualSync=dict(required=False, default=False, type='bool'),
- recoveryWarnThresholdMinutes=dict(required=False, default=20, type='int'),
- repoUtilizationWarnThreshold=dict(required=False, default=80, type='int'),
- interfaceType=dict(required=False, choices=['fibre', 'iscsi'], type='str'),
- state=dict(required=True, choices=['present', 'absent']),
- syncWarnThresholdMinutes=dict(required=False, default=10, type='int')
- ))
-
- module = AnsibleModule(argument_spec=argument_spec)
-
- p = module.params
-
- ssid = p.pop('ssid')
- api_url = p.pop('api_url')
- user = p.pop('api_username')
- pwd = p.pop('api_password')
- new_name = p.pop('new_name')
- state = p.pop('state')
-
- if not api_url.endswith('/'):
- api_url += '/'
-
- name_exists, spec_matches, api_data, async_id = has_match(module, ssid, api_url, pwd, user, p)
-
- if state == 'present':
- if name_exists and spec_matches:
- module.exit_json(changed=False, msg="Desired state met", **api_data)
- elif name_exists and not spec_matches:
- results = update_async(module, ssid, api_url, pwd, user,
- p, new_name, async_id)
- module.exit_json(changed=True,
- msg="Async mirror group updated", async_id=async_id,
- **results)
- elif not name_exists:
- results = create_async(module, ssid, api_url, user, pwd, p)
- module.exit_json(changed=True, **results)
-
- elif state == 'absent':
- if name_exists:
- remove_amg(module, ssid, api_url, pwd, user, async_id)
- module.exit_json(changed=True, msg="Async mirror group removed.",
- async_id=async_id)
- else:
- module.exit_json(changed=False,
- msg="Async Mirror group: %s already absent" % p['name'])
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_amg_role.py b/lib/ansible/modules/storage/netapp/netapp_e_amg_role.py
deleted file mode 100644
index a1de1f98ed..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_amg_role.py
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_amg_role
-short_description: NetApp E-Series update the role of a storage array within an Asynchronous Mirror Group (AMG).
-description:
- - Update a storage array to become the primary or secondary instance in an asynchronous mirror group
-version_added: '2.2'
-author: Kevin Hulquest (@hulquest)
-options:
- api_username:
- required: true
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_password:
- required: true
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_url:
- required: true
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API.
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- type: bool
- ssid:
- description:
- - The ID of the primary storage array for the async mirror action
- required: yes
- role:
- description:
- - Whether the array should be the primary or secondary array for the AMG
- required: yes
- choices: ['primary', 'secondary']
- noSync:
- description:
- - Whether to avoid synchronization prior to role reversal
- required: no
- default: no
- type: bool
- force:
- description:
- - Whether to force the role reversal regardless of the online-state of the primary
- required: no
- default: no
- type: bool
-"""
-
-EXAMPLES = """
- - name: Update the role of a storage array
- netapp_e_amg_role:
- name: updating amg role
- role: primary
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
-"""
-
-RETURN = """
-msg:
- description: Failure message
- returned: failure
- type: str
- sample: "No Async Mirror Group with the name."
-"""
-import json
-import traceback
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-from ansible.module_utils._text import to_native
-from ansible.module_utils.urls import open_url
-
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as e:
- r = e.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-def has_match(module, ssid, api_url, api_pwd, api_usr, body, name):
- amg_exists = False
- has_desired_role = False
- amg_id = None
- amg_data = None
- get_amgs = 'storage-systems/%s/async-mirrors' % ssid
- url = api_url + get_amgs
- try:
- amg_rc, amgs = request(url, url_username=api_usr, url_password=api_pwd,
- headers=HEADERS)
- except Exception:
- module.fail_json(msg="Failed to find AMGs on storage array. Id [%s]" % (ssid))
-
- for amg in amgs:
- if amg['label'] == name:
- amg_exists = True
- amg_id = amg['id']
- amg_data = amg
- if amg['localRole'] == body.get('role'):
- has_desired_role = True
-
- return amg_exists, has_desired_role, amg_id, amg_data
-
-
-def update_amg(module, ssid, api_url, api_usr, api_pwd, body, amg_id):
- endpoint = 'storage-systems/%s/async-mirrors/%s/role' % (ssid, amg_id)
- url = api_url + endpoint
- post_data = json.dumps(body)
- try:
- request(url, data=post_data, method='POST', url_username=api_usr,
- url_password=api_pwd, headers=HEADERS)
- except Exception as e:
- module.fail_json(
- msg="Failed to change role of AMG. Id [%s]. AMG Id [%s]. Error [%s]" % (ssid, amg_id, to_native(e)),
- exception=traceback.format_exc())
-
- status_endpoint = 'storage-systems/%s/async-mirrors/%s' % (ssid, amg_id)
- status_url = api_url + status_endpoint
- try:
- rc, status = request(status_url, method='GET', url_username=api_usr,
- url_password=api_pwd, headers=HEADERS)
- except Exception as e:
- module.fail_json(
- msg="Failed to check status of AMG after role reversal. "
- "Id [%s]. AMG Id [%s]. Error [%s]" % (ssid, amg_id, to_native(e)),
- exception=traceback.format_exc())
-
- # Here we wait for the role reversal to complete
- if 'roleChangeProgress' in status:
- while status['roleChangeProgress'] != "none":
- try:
- rc, status = request(status_url, method='GET',
- url_username=api_usr, url_password=api_pwd, headers=HEADERS)
- except Exception as e:
- module.fail_json(
- msg="Failed to check status of AMG after role reversal. "
- "Id [%s]. AMG Id [%s]. Error [%s]" % (ssid, amg_id, to_native(e)),
- exception=traceback.format_exc())
- return status
-
-
-def main():
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- name=dict(required=True, type='str'),
- role=dict(required=True, choices=['primary', 'secondary']),
- noSync=dict(required=False, type='bool', default=False),
- force=dict(required=False, type='bool', default=False),
- ssid=dict(required=True, type='str'),
- api_url=dict(required=True),
- api_username=dict(required=False),
- api_password=dict(required=False, no_log=True),
- ))
-
- module = AnsibleModule(argument_spec=argument_spec)
-
- p = module.params
-
- ssid = p.pop('ssid')
- api_url = p.pop('api_url')
- user = p.pop('api_username')
- pwd = p.pop('api_password')
- name = p.pop('name')
-
- if not api_url.endswith('/'):
- api_url += '/'
-
- agm_exists, has_desired_role, async_id, amg_data = has_match(module, ssid, api_url, pwd, user, p, name)
-
- if not agm_exists:
- module.fail_json(msg="No Async Mirror Group with the name: '%s' was found" % name)
- elif has_desired_role:
- module.exit_json(changed=False, **amg_data)
-
- else:
- amg_data = update_amg(module, ssid, api_url, user, pwd, p, async_id)
- if amg_data:
- module.exit_json(changed=True, **amg_data)
- else:
- module.exit_json(changed=True, msg="AMG role changed.")
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_amg_sync.py b/lib/ansible/modules/storage/netapp/netapp_e_amg_sync.py
deleted file mode 100644
index b4f99813b7..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_amg_sync.py
+++ /dev/null
@@ -1,261 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_amg_sync
-short_description: NetApp E-Series conduct synchronization actions on asynchronous mirror groups.
-description:
- - Allows for the initialization, suspension and resumption of an asynchronous mirror group's synchronization for NetApp E-series storage arrays.
-version_added: '2.2'
-author: Kevin Hulquest (@hulquest)
-options:
- api_username:
- required: true
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_password:
- required: true
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_url:
- required: true
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API.
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- type: bool
- ssid:
- description:
- - The ID of the storage array containing the AMG you wish to target
- name:
- description:
- - The name of the async mirror group you wish to target
- required: yes
- state:
- description:
- - The synchronization action you'd like to take.
- - If C(running) then it will begin syncing if there is no active sync or will resume a suspended sync. If there is already a sync in
- progress, it will return with an OK status.
- - If C(suspended) it will suspend any ongoing sync action, but return OK if there is no active sync or if the sync is already suspended
- choices:
- - running
- - suspended
- required: yes
- delete_recovery_point:
- description:
- - Indicates whether the failures point can be deleted on the secondary if necessary to achieve the synchronization.
- - If true, and if the amount of unsynchronized data exceeds the CoW repository capacity on the secondary for any member volume, the last
- failures point will be deleted and synchronization will continue.
- - If false, the synchronization will be suspended if the amount of unsynchronized data exceeds the CoW Repository capacity on the secondary
- and the failures point will be preserved.
- - "NOTE: This only has impact for newly launched syncs."
- type: bool
- default: no
-"""
-EXAMPLES = """
- - name: start AMG async
- netapp_e_amg_sync:
- name: "{{ amg_sync_name }}"
- state: running
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
-"""
-RETURN = """
-json:
- description: The object attributes of the AMG.
- returned: success
- type: str
- example:
- {
- "changed": false,
- "connectionType": "fc",
- "groupRef": "3700000060080E5000299C24000006EF57ACAC70",
- "groupState": "optimal",
- "id": "3700000060080E5000299C24000006EF57ACAC70",
- "label": "made_with_ansible",
- "localRole": "primary",
- "mirrorChannelRemoteTarget": "9000000060080E5000299C24005B06E557AC7EEC",
- "orphanGroup": false,
- "recoveryPointAgeAlertThresholdMinutes": 20,
- "remoteRole": "secondary",
- "remoteTarget": {
- "nodeName": {
- "ioInterfaceType": "fc",
- "iscsiNodeName": null,
- "remoteNodeWWN": "20040080E5299F1C"
- },
- "remoteRef": "9000000060080E5000299C24005B06E557AC7EEC",
- "scsiinitiatorTargetBaseProperties": {
- "ioInterfaceType": "fc",
- "iscsiinitiatorTargetBaseParameters": null
- }
- },
- "remoteTargetId": "ansible2",
- "remoteTargetName": "Ansible2",
- "remoteTargetWwn": "60080E5000299F880000000056A25D56",
- "repositoryUtilizationWarnThreshold": 80,
- "roleChangeProgress": "none",
- "syncActivity": "idle",
- "syncCompletionTimeAlertThresholdMinutes": 10,
- "syncIntervalMinutes": 10,
- "worldWideName": "60080E5000299C24000006EF57ACAC70"
- }
-"""
-import json
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-from ansible.module_utils.urls import open_url
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as e:
- r = e.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-class AMGsync(object):
- def __init__(self):
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- api_username=dict(type='str', required=True),
- api_password=dict(type='str', required=True, no_log=True),
- api_url=dict(type='str', required=True),
- name=dict(required=True, type='str'),
- ssid=dict(required=True, type='str'),
- state=dict(required=True, type='str', choices=['running', 'suspended']),
- delete_recovery_point=dict(required=False, type='bool', default=False)
- ))
- self.module = AnsibleModule(argument_spec=argument_spec)
- args = self.module.params
- self.name = args['name']
- self.ssid = args['ssid']
- self.state = args['state']
- self.delete_recovery_point = args['delete_recovery_point']
- try:
- self.user = args['api_username']
- self.pwd = args['api_password']
- self.url = args['api_url']
- except KeyError:
- self.module.fail_json(msg="You must pass in api_username"
- "and api_password and api_url to the module.")
- self.certs = args['validate_certs']
-
- self.post_headers = {
- "Accept": "application/json",
- "Content-Type": "application/json"
- }
- self.amg_id, self.amg_obj = self.get_amg()
-
- def get_amg(self):
- endpoint = self.url + '/storage-systems/%s/async-mirrors' % self.ssid
- (rc, amg_objs) = request(endpoint, url_username=self.user, url_password=self.pwd, validate_certs=self.certs,
- headers=self.post_headers)
- try:
- amg_id = filter(lambda d: d['label'] == self.name, amg_objs)[0]['id']
- amg_obj = filter(lambda d: d['label'] == self.name, amg_objs)[0]
- except IndexError:
- self.module.fail_json(
- msg="There is no async mirror group %s associated with storage array %s" % (self.name, self.ssid))
- return amg_id, amg_obj
-
- @property
- def current_state(self):
- amg_id, amg_obj = self.get_amg()
- return amg_obj['syncActivity']
-
- def run_sync_action(self):
- # If we get to this point we know that the states differ, and there is no 'err' state,
- # so no need to revalidate
-
- post_body = dict()
- if self.state == 'running':
- if self.current_state == 'idle':
- if self.delete_recovery_point:
- post_body.update(dict(deleteRecoveryPointIfNecessary=self.delete_recovery_point))
- suffix = 'sync'
- else:
- # In a suspended state
- suffix = 'resume'
- else:
- suffix = 'suspend'
-
- endpoint = self.url + "/storage-systems/%s/async-mirrors/%s/%s" % (self.ssid, self.amg_id, suffix)
-
- (rc, resp) = request(endpoint, method='POST', url_username=self.user, url_password=self.pwd,
- validate_certs=self.certs, data=json.dumps(post_body), headers=self.post_headers,
- ignore_errors=True)
-
- if not str(rc).startswith('2'):
- self.module.fail_json(msg=str(resp['errorMessage']))
-
- return resp
-
- def apply(self):
- state_map = dict(
- running=['active'],
- suspended=['userSuspended', 'internallySuspended', 'paused'],
- err=['unkown', '_UNDEFINED'])
-
- if self.current_state not in state_map[self.state]:
- if self.current_state in state_map['err']:
- self.module.fail_json(
- msg="The sync is a state of '%s', this requires manual intervention. " +
- "Please investigate and try again" % self.current_state)
- else:
- self.amg_obj = self.run_sync_action()
-
- (ret, amg) = self.get_amg()
- self.module.exit_json(changed=False, **amg)
-
-
-def main():
- sync = AMGsync()
- sync.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_asup.py b/lib/ansible/modules/storage/netapp/netapp_e_asup.py
deleted file mode 100644
index a1d0d93681..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_asup.py
+++ /dev/null
@@ -1,309 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_asup
-short_description: NetApp E-Series manage auto-support settings
-description:
- - Allow the auto-support settings to be configured for an individual E-Series storage-system
-version_added: '2.7'
-author: Michael Price (@lmprice)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Enable/disable the E-Series auto-support configuration.
- - When this option is enabled, configuration, logs, and other support-related information will be relayed
- to NetApp to help better support your system. No personally identifiable information, passwords, etc, will
- be collected.
- default: enabled
- choices:
- - enabled
- - disabled
- aliases:
- - asup
- - auto_support
- - autosupport
- active:
- description:
- - Enable active/proactive monitoring for ASUP. When a problem is detected by our monitoring systems, it's
- possible that the bundle did not contain all of the required information at the time of the event.
- Enabling this option allows NetApp support personnel to manually request transmission or re-transmission
- of support data in order ot resolve the problem.
- - Only applicable if I(state=enabled).
- default: yes
- type: bool
- start:
- description:
- - A start hour may be specified in a range from 0 to 23 hours.
- - ASUP bundles will be sent daily between the provided start and end time (UTC).
- - I(start) must be less than I(end).
- aliases:
- - start_time
- default: 0
- end:
- description:
- - An end hour may be specified in a range from 1 to 24 hours.
- - ASUP bundles will be sent daily between the provided start and end time (UTC).
- - I(start) must be less than I(end).
- aliases:
- - end_time
- default: 24
- days:
- description:
- - A list of days of the week that ASUP bundles will be sent. A larger, weekly bundle will be sent on one
- of the provided days.
- choices:
- - monday
- - tuesday
- - wednesday
- - thursday
- - friday
- - saturday
- - sunday
- required: no
- aliases:
- - days_of_week
- - schedule_days
- verbose:
- description:
- - Provide the full ASUP configuration in the return.
- default: no
- required: no
- type: bool
- log_path:
- description:
- - A local path to a file to be used for debug logging
- required: no
-notes:
- - Check mode is supported.
- - Enabling ASUP will allow our support teams to monitor the logs of the storage-system in order to proactively
- respond to issues with the system. It is recommended that all ASUP-related options be enabled, but they may be
- disabled if desired.
- - This API is currently only supported with the Embedded Web Services API v2.0 and higher.
-"""
-
-EXAMPLES = """
- - name: Enable ASUP and allow pro-active retrieval of bundles
- netapp_e_asup:
- state: enabled
- active: yes
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
-
- - name: Set the ASUP schedule to only send bundles from 12 AM CST to 3 AM CST.
- netapp_e_asup:
- start: 17
- end: 20
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The settings have been updated.
-asup:
- description:
- - True if ASUP is enabled.
- returned: on success
- sample: True
- type: bool
-active:
- description:
- - True if the active option has been enabled.
- returned: on success
- sample: True
- type: bool
-cfg:
- description:
- - Provide the full ASUP configuration.
- returned: on success when I(verbose=true).
- type: complex
- contains:
- asupEnabled:
- description:
- - True if ASUP has been enabled.
- type: bool
- onDemandEnabled:
- description:
- - True if ASUP active monitoring has been enabled.
- type: bool
- daysOfWeek:
- description:
- - The days of the week that ASUP bundles will be sent.
- type: list
-"""
-
-import json
-import logging
-from pprint import pformat
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class Asup(object):
- DAYS_OPTIONS = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']
-
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- state=dict(type='str', required=False, default='enabled', aliases=['asup', 'auto_support', 'autosupport'],
- choices=['enabled', 'disabled']),
- active=dict(type='bool', required=False, default=True, ),
- days=dict(type='list', required=False, aliases=['schedule_days', 'days_of_week'],
- choices=self.DAYS_OPTIONS),
- start=dict(type='int', required=False, default=0, aliases=['start_time']),
- end=dict(type='int', required=False, default=24, aliases=['end_time']),
- verbose=dict(type='bool', required=False, default=False),
- log_path=dict(type='str', required=False),
- ))
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, )
- args = self.module.params
- self.asup = args['state'] == 'enabled'
- self.active = args['active']
- self.days = args['days']
- self.start = args['start']
- self.end = args['end']
- self.verbose = args['verbose']
-
- self.ssid = args['ssid']
- self.url = args['api_url']
- self.creds = dict(url_password=args['api_password'],
- validate_certs=args['validate_certs'],
- url_username=args['api_username'], )
-
- self.check_mode = self.module.check_mode
-
- log_path = args['log_path']
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- if self.start >= self.end:
- self.module.fail_json(msg="The value provided for the start time is invalid."
- " It must be less than the end time.")
- if self.start < 0 or self.start > 23:
- self.module.fail_json(msg="The value provided for the start time is invalid. It must be between 0 and 23.")
- else:
- self.start = self.start * 60
- if self.end < 1 or self.end > 24:
- self.module.fail_json(msg="The value provided for the end time is invalid. It must be between 1 and 24.")
- else:
- self.end = min(self.end * 60, 1439)
-
- if not self.days:
- self.days = self.DAYS_OPTIONS
-
- def get_configuration(self):
- try:
- (rc, result) = request(self.url + 'device-asup', headers=HEADERS, **self.creds)
-
- if not (result['asupCapable'] and result['onDemandCapable']):
- self.module.fail_json(msg="ASUP is not supported on this device. Array Id [%s]." % (self.ssid))
- return result
-
- except Exception as err:
- self.module.fail_json(msg="Failed to retrieve ASUP configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def update_configuration(self):
- config = self.get_configuration()
- update = False
- body = dict()
-
- if self.asup:
- body = dict(asupEnabled=True)
- if not config['asupEnabled']:
- update = True
-
- if (config['onDemandEnabled'] and config['remoteDiagsEnabled']) != self.active:
- update = True
- body.update(dict(onDemandEnabled=self.active,
- remoteDiagsEnabled=self.active))
- self.days.sort()
- config['schedule']['daysOfWeek'].sort()
-
- body['schedule'] = dict(daysOfWeek=self.days,
- dailyMinTime=self.start,
- dailyMaxTime=self.end,
- weeklyMinTime=self.start,
- weeklyMaxTime=self.end)
-
- if self.days != config['schedule']['daysOfWeek']:
- update = True
- if self.start != config['schedule']['dailyMinTime'] or self.start != config['schedule']['weeklyMinTime']:
- update = True
- elif self.end != config['schedule']['dailyMaxTime'] or self.end != config['schedule']['weeklyMaxTime']:
- update = True
-
- elif config['asupEnabled']:
- body = dict(asupEnabled=False)
- update = True
-
- self._logger.info(pformat(body))
-
- if update and not self.check_mode:
- try:
- (rc, result) = request(self.url + 'device-asup', method='POST',
- data=json.dumps(body), headers=HEADERS, **self.creds)
- # This is going to catch cases like a connection failure
- except Exception as err:
- self.module.fail_json(msg="We failed to set the storage-system name! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- return update
-
- def update(self):
- update = self.update_configuration()
- cfg = self.get_configuration()
- if self.verbose:
- self.module.exit_json(msg="The ASUP settings have been updated.", changed=update,
- asup=cfg['asupEnabled'], active=cfg['onDemandEnabled'], cfg=cfg)
- else:
- self.module.exit_json(msg="The ASUP settings have been updated.", changed=update,
- asup=cfg['asupEnabled'], active=cfg['onDemandEnabled'])
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- settings = Asup()
- settings()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_auditlog.py b/lib/ansible/modules/storage/netapp/netapp_e_auditlog.py
deleted file mode 100644
index 7191adc0d5..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_auditlog.py
+++ /dev/null
@@ -1,281 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_auditlog
-short_description: NetApp E-Series manage audit-log configuration
-description:
- - This module allows an e-series storage system owner to set audit-log configuration parameters.
-version_added: '2.7'
-author: Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- max_records:
- description:
- - The maximum number log messages audit-log will retain.
- - Max records must be between and including 100 and 50000.
- default: 50000
- log_level:
- description: Filters the log messages according to the specified log level selection.
- choices:
- - all
- - writeOnly
- default: writeOnly
- full_policy:
- description: Specifies what audit-log should do once the number of entries approach the record limit.
- choices:
- - overWrite
- - preventSystemAccess
- default: overWrite
- threshold:
- description:
- - This is the memory full percent threshold that audit-log will start issuing warning messages.
- - Percent range must be between and including 60 and 90.
- default: 90
- force:
- description:
- - Forces the audit-log configuration to delete log history when log messages fullness cause immediate
- warning or full condition.
- - Warning! This will cause any existing audit-log messages to be deleted.
- - This is only applicable for I(full_policy=preventSystemAccess).
- type: bool
- default: no
- log_path:
- description: A local path to a file to be used for debug logging.
- required: no
-notes:
- - Check mode is supported.
- - This module is currently only supported with the Embedded Web Services API v3.0 and higher.
-"""
-
-EXAMPLES = """
-- name: Define audit-log to prevent system access if records exceed 50000 with warnings occurring at 60% capacity.
- netapp_e_auditlog:
- api_url: "https://{{ netapp_e_api_host }}/devmgr/v2"
- api_username: "{{ netapp_e_api_username }}"
- api_password: "{{ netapp_e_api_password }}"
- ssid: "{{ netapp_e_ssid }}"
- validate_certs: no
- max_records: 50000
- log_level: all
- full_policy: preventSystemAccess
- threshold: 60
- log_path: /path/to/log_file.log
-- name: Define audit-log utilize the default values.
- netapp_e_auditlog:
- api_url: "https://{{ netapp_e_api_host }}/devmgr/v2"
- api_username: "{{ netapp_e_api_username }}"
- api_password: "{{ netapp_e_api_password }}"
- ssid: "{{ netapp_e_ssid }}"
-- name: Force audit-log configuration when full or warning conditions occur while enacting preventSystemAccess policy.
- netapp_e_auditlog:
- api_url: "https://{{ netapp_e_api_host }}/devmgr/v2"
- api_username: "{{ netapp_e_api_username }}"
- api_password: "{{ netapp_e_api_password }}"
- ssid: "{{ netapp_e_ssid }}"
- max_records: 5000
- log_level: all
- full_policy: preventSystemAccess
- threshold: 60
- force: yes
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The settings have been updated.
-"""
-
-import json
-import logging
-from pprint import pformat
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-try:
- from urlparse import urlparse, urlunparse
-except Exception:
- from urllib.parse import urlparse, urlunparse
-
-
-class AuditLog(object):
- """Audit-log module configuration class."""
- MAX_RECORDS = 50000
- HEADERS = {"Content-Type": "application/json",
- "Accept": "application/json"}
-
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- max_records=dict(type="int", default=50000),
- log_level=dict(type="str", default="writeOnly", choices=["all", "writeOnly"]),
- full_policy=dict(type="str", default="overWrite", choices=["overWrite", "preventSystemAccess"]),
- threshold=dict(type="int", default=90),
- force=dict(type="bool", default=False),
- log_path=dict(type='str', required=False)))
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
- args = self.module.params
-
- self.max_records = args["max_records"]
- if self.max_records < 100 or self.max_records > self.MAX_RECORDS:
- self.module.fail_json(msg="Audit-log max_records count must be between 100 and 50000: [%s]"
- % self.max_records)
- self.threshold = args["threshold"]
- if self.threshold < 60 or self.threshold > 90:
- self.module.fail_json(msg="Audit-log percent threshold must be between 60 and 90: [%s]" % self.threshold)
- self.log_level = args["log_level"]
- self.full_policy = args["full_policy"]
- self.force = args["force"]
- self.ssid = args['ssid']
- self.url = args['api_url']
- if not self.url.endswith('/'):
- self.url += '/'
- self.creds = dict(url_password=args['api_password'],
- validate_certs=args['validate_certs'],
- url_username=args['api_username'], )
-
- # logging setup
- log_path = args['log_path']
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- self.proxy_used = self.is_proxy()
- self._logger.info(self.proxy_used)
- self.check_mode = self.module.check_mode
-
- def is_proxy(self):
- """Determine whether the API is embedded or proxy."""
- try:
-
- # replace http url path with devmgr/utils/about
- about_url = list(urlparse(self.url))
- about_url[2] = "devmgr/utils/about"
- about_url = urlunparse(about_url)
-
- rc, data = request(about_url, timeout=300, headers=self.HEADERS, **self.creds)
-
- return data["runningAsProxy"]
- except Exception as err:
- self.module.fail_json(msg="Failed to retrieve the webservices about information! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def get_configuration(self):
- """Retrieve the existing audit-log configurations.
-
- :returns: dictionary containing current audit-log configuration
- """
- try:
- if self.proxy_used:
- rc, data = request(self.url + "audit-log/config", timeout=300, headers=self.HEADERS, **self.creds)
- else:
- rc, data = request(self.url + "storage-systems/%s/audit-log/config" % self.ssid,
- timeout=300, headers=self.HEADERS, **self.creds)
- return data
- except Exception as err:
- self.module.fail_json(msg="Failed to retrieve the audit-log configuration! "
- "Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def build_configuration(self):
- """Build audit-log expected configuration.
-
- :returns: Tuple containing update boolean value and dictionary of audit-log configuration
- """
- config = self.get_configuration()
-
- current = dict(auditLogMaxRecords=config["auditLogMaxRecords"],
- auditLogLevel=config["auditLogLevel"],
- auditLogFullPolicy=config["auditLogFullPolicy"],
- auditLogWarningThresholdPct=config["auditLogWarningThresholdPct"])
-
- body = dict(auditLogMaxRecords=self.max_records,
- auditLogLevel=self.log_level,
- auditLogFullPolicy=self.full_policy,
- auditLogWarningThresholdPct=self.threshold)
-
- update = current != body
-
- self._logger.info(pformat(update))
- self._logger.info(pformat(body))
- return update, body
-
- def delete_log_messages(self):
- """Delete all audit-log messages."""
- self._logger.info("Deleting audit-log messages...")
- try:
- if self.proxy_used:
- rc, result = request(self.url + "audit-log?clearAll=True", timeout=300,
- method="DELETE", headers=self.HEADERS, **self.creds)
- else:
- rc, result = request(self.url + "storage-systems/%s/audit-log?clearAll=True" % self.ssid, timeout=300,
- method="DELETE", headers=self.HEADERS, **self.creds)
- except Exception as err:
- self.module.fail_json(msg="Failed to delete audit-log messages! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def update_configuration(self, update=None, body=None, attempt_recovery=True):
- """Update audit-log configuration."""
- if update is None or body is None:
- update, body = self.build_configuration()
-
- if update and not self.check_mode:
- try:
- if self.proxy_used:
- rc, result = request(self.url + "storage-systems/audit-log/config", timeout=300,
- data=json.dumps(body), method='POST', headers=self.HEADERS,
- ignore_errors=True, **self.creds)
- else:
- rc, result = request(self.url + "storage-systems/%s/audit-log/config" % self.ssid, timeout=300,
- data=json.dumps(body), method='POST', headers=self.HEADERS,
- ignore_errors=True, **self.creds)
-
- if rc == 422:
- if self.force and attempt_recovery:
- self.delete_log_messages()
- update = self.update_configuration(update, body, False)
- else:
- self.module.fail_json(msg="Failed to update audit-log configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(rc, result)))
-
- except Exception as error:
- self.module.fail_json(msg="Failed to update audit-log configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(error)))
- return update
-
- def update(self):
- """Update the audit-log configuration."""
- update = self.update_configuration()
- self.module.exit_json(msg="Audit-log update complete", changed=update)
-
- def __call__(self):
- self.update()
-
-
-def main():
- auditlog = AuditLog()
- auditlog()
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_auth.py b/lib/ansible/modules/storage/netapp/netapp_e_auth.py
deleted file mode 100644
index 521e2c6b39..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_auth.py
+++ /dev/null
@@ -1,276 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
----
-module: netapp_e_auth
-short_description: NetApp E-Series set or update the password for a storage array.
-description:
- - Sets or updates the password for a storage array. When the password is updated on the storage array, it must be updated on the SANtricity Web
- Services proxy. Note, all storage arrays do not have a Monitor or RO role.
-version_added: "2.2"
-author: Kevin Hulquest (@hulquest)
-options:
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- type: bool
- name:
- description:
- - The name of the storage array. Note that if more than one storage array with this name is detected, the task will fail and you'll have to use
- the ID instead.
- required: False
- ssid:
- description:
- - the identifier of the storage array in the Web Services Proxy.
- required: False
- set_admin:
- description:
- - Boolean value on whether to update the admin password. If set to false then the RO account is updated.
- type: bool
- default: False
- current_password:
- description:
- - The current admin password. This is not required if the password hasn't been set before.
- required: False
- new_password:
- description:
- - The password you would like to set. Cannot be more than 30 characters.
- required: True
- api_url:
- description:
- - The full API url.
- - "Example: http://ENDPOINT:8080/devmgr/v2"
- - This can optionally be set via an environment variable, API_URL
- required: False
- api_username:
- description:
- - The username used to authenticate against the API
- - This can optionally be set via an environment variable, API_USERNAME
- required: False
- api_password:
- description:
- - The password used to authenticate against the API
- - This can optionally be set via an environment variable, API_PASSWORD
- required: False
-'''
-
-EXAMPLES = '''
-- name: Test module
- netapp_e_auth:
- name: trex
- current_password: OldPasswd
- new_password: NewPasswd
- set_admin: yes
- api_url: '{{ netapp_api_url }}'
- api_username: '{{ netapp_api_username }}'
- api_password: '{{ netapp_api_password }}'
-'''
-
-RETURN = '''
-msg:
- description: Success message
- returned: success
- type: str
- sample: "Password Updated Successfully"
-'''
-import json
-import traceback
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-from ansible.module_utils._text import to_native
-from ansible.module_utils.urls import open_url
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
- "x-netapp-password-validate-method": "none"
-
-}
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as e:
- r = e.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-def get_ssid(module, name, api_url, user, pwd):
- count = 0
- all_systems = 'storage-systems'
- systems_url = api_url + all_systems
- rc, data = request(systems_url, headers=HEADERS, url_username=user, url_password=pwd,
- validate_certs=module.validate_certs)
- for system in data:
- if system['name'] == name:
- count += 1
- if count > 1:
- module.fail_json(
- msg="You supplied a name for the Storage Array but more than 1 array was found with that name. " +
- "Use the id instead")
- else:
- ssid = system['id']
- else:
- continue
-
- if count == 0:
- module.fail_json(msg="No storage array with the name %s was found" % name)
-
- else:
- return ssid
-
-
-def get_pwd_status(module, ssid, api_url, user, pwd):
- pwd_status = "storage-systems/%s/passwords" % ssid
- url = api_url + pwd_status
- try:
- rc, data = request(url, headers=HEADERS, url_username=user, url_password=pwd,
- validate_certs=module.validate_certs)
- return data['readOnlyPasswordSet'], data['adminPasswordSet']
- except HTTPError as e:
- module.fail_json(msg="There was an issue with connecting, please check that your "
- "endpoint is properly defined and your credentials are correct: %s" % to_native(e))
-
-
-def update_storage_system_pwd(module, ssid, pwd, api_url, api_usr, api_pwd):
- """Update the stored storage-system password"""
- update_pwd = 'storage-systems/%s' % ssid
- url = api_url + update_pwd
- post_body = json.dumps(dict(storedPassword=pwd))
- try:
- rc, data = request(url, data=post_body, method='POST', headers=HEADERS, url_username=api_usr,
- url_password=api_pwd, validate_certs=module.validate_certs)
- return rc, data
- except Exception as e:
- module.fail_json(msg="Failed to update system password. Id [%s]. Error [%s]" % (ssid, to_native(e)))
-
-
-def set_password(module, ssid, api_url, user, pwd, current_password=None, new_password=None, set_admin=False):
- """Set the storage-system password"""
- set_pass = "storage-systems/%s/passwords" % ssid
- url = api_url + set_pass
-
- if not current_password:
- current_password = ""
-
- post_body = json.dumps(
- dict(currentAdminPassword=current_password, adminPassword=set_admin, newPassword=new_password))
-
- try:
- rc, data = request(url, method='POST', data=post_body, headers=HEADERS, url_username=user, url_password=pwd,
- ignore_errors=True, validate_certs=module.validate_certs)
- except Exception as e:
- module.fail_json(msg="Failed to set system password. Id [%s]. Error [%s]" % (ssid, to_native(e)),
- exception=traceback.format_exc())
-
- if rc == 422:
- post_body = json.dumps(dict(currentAdminPassword='', adminPassword=set_admin, newPassword=new_password))
- try:
- rc, data = request(url, method='POST', data=post_body, headers=HEADERS, url_username=user, url_password=pwd,
- validate_certs=module.validate_certs)
- except Exception:
- # TODO(lorenp): Resolve ignored rc, data
- module.fail_json(msg="Wrong or no admin password supplied. Please update your playbook and try again")
-
- if int(rc) >= 300:
- module.fail_json(msg="Failed to set system password. Id [%s] Code [%s]. Error [%s]" % (ssid, rc, data))
-
- rc, update_data = update_storage_system_pwd(module, ssid, new_password, api_url, user, pwd)
-
- if int(rc) < 300:
- return update_data
- else:
- module.fail_json(msg="%s:%s" % (rc, update_data))
-
-
-def main():
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- name=dict(required=False, type='str'),
- ssid=dict(required=False, type='str'),
- current_password=dict(required=False, no_log=True),
- new_password=dict(required=True, no_log=True),
- set_admin=dict(required=True, type='bool'),
- api_url=dict(required=True),
- api_username=dict(required=False),
- api_password=dict(required=False, no_log=True)
- )
- )
- module = AnsibleModule(argument_spec=argument_spec, mutually_exclusive=[['name', 'ssid']],
- required_one_of=[['name', 'ssid']])
-
- name = module.params['name']
- ssid = module.params['ssid']
- current_password = module.params['current_password']
- new_password = module.params['new_password']
- set_admin = module.params['set_admin']
- user = module.params['api_username']
- pwd = module.params['api_password']
- api_url = module.params['api_url']
- module.validate_certs = module.params['validate_certs']
-
- if not api_url.endswith('/'):
- api_url += '/'
-
- if name:
- ssid = get_ssid(module, name, api_url, user, pwd)
-
- ro_pwd, admin_pwd = get_pwd_status(module, ssid, api_url, user, pwd)
-
- if admin_pwd and not current_password:
- module.fail_json(
- msg="Admin account has a password set. " +
- "You must supply current_password in order to update the RO or Admin passwords")
-
- if len(new_password) > 30:
- module.fail_json(msg="Passwords must not be greater than 30 characters in length")
-
- result = set_password(module, ssid, api_url, user, pwd, current_password=current_password,
- new_password=new_password, set_admin=set_admin)
-
- module.exit_json(changed=True, msg="Password Updated Successfully",
- password_set=result['passwordSet'],
- password_status=result['passwordStatus'])
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_drive_firmware.py b/lib/ansible/modules/storage/netapp/netapp_e_drive_firmware.py
deleted file mode 100644
index 88e9faef35..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_drive_firmware.py
+++ /dev/null
@@ -1,215 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_drive_firmware
-version_added: "2.9"
-short_description: NetApp E-Series manage drive firmware
-description:
- - Ensure drive firmware version is activated on specified drive model.
-author:
- - Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- firmware:
- description:
- - list of drive firmware file paths.
- - NetApp E-Series drives require special firmware which can be downloaded from https://mysupport.netapp.com/NOW/download/tools/diskfw_eseries/
- type: list
- required: True
- wait_for_completion:
- description:
- - This flag will cause module to wait for any upgrade actions to complete.
- type: bool
- default: false
- ignore_inaccessible_drives:
- description:
- - This flag will determine whether drive firmware upgrade should fail if any affected drives are inaccessible.
- type: bool
- default: false
- upgrade_drives_online:
- description:
- - This flag will determine whether drive firmware can be upgrade while drives are accepting I/O.
- - When I(upgrade_drives_online==False) stop all I/O before running task.
- type: bool
- default: true
-"""
-EXAMPLES = """
-- name: Ensure correct firmware versions
- nac_santricity_drive_firmware:
- ssid: "1"
- api_url: "https://192.168.1.100:8443/devmgr/v2"
- api_username: "admin"
- api_password: "adminpass"
- validate_certs: true
- firmware: "path/to/drive_firmware"
- wait_for_completion: true
- ignore_inaccessible_drives: false
-"""
-RETURN = """
-msg:
- description: Whether any drive firmware was upgraded and whether it is in progress.
- type: str
- returned: always
- sample:
- { changed: True, upgrade_in_process: True }
-"""
-import os
-import re
-
-from time import sleep
-from ansible.module_utils.netapp import NetAppESeriesModule, create_multipart_formdata
-from ansible.module_utils._text import to_native, to_text, to_bytes
-
-
-class NetAppESeriesDriveFirmware(NetAppESeriesModule):
- WAIT_TIMEOUT_SEC = 60 * 15
-
- def __init__(self):
- ansible_options = dict(
- firmware=dict(type="list", required=True),
- wait_for_completion=dict(type="bool", default=False),
- ignore_inaccessible_drives=dict(type="bool", default=False),
- upgrade_drives_online=dict(type="bool", default=True))
-
- super(NetAppESeriesDriveFirmware, self).__init__(ansible_options=ansible_options,
- web_services_version="02.00.0000.0000",
- supports_check_mode=True)
-
- args = self.module.params
- self.firmware_list = args["firmware"]
- self.wait_for_completion = args["wait_for_completion"]
- self.ignore_inaccessible_drives = args["ignore_inaccessible_drives"]
- self.upgrade_drives_online = args["upgrade_drives_online"]
-
- self.upgrade_list_cache = None
-
- self.upgrade_required_cache = None
- self.upgrade_in_progress = False
- self.drive_info_cache = None
-
- def upload_firmware(self):
- """Ensure firmware has been upload prior to uploaded."""
- for firmware in self.firmware_list:
- firmware_name = os.path.basename(firmware)
- files = [("file", firmware_name, firmware)]
- headers, data = create_multipart_formdata(files)
- try:
- rc, response = self.request("/files/drive", method="POST", headers=headers, data=data)
- except Exception as error:
- self.module.fail_json(msg="Failed to upload drive firmware [%s]. Array [%s]. Error [%s]." % (firmware_name, self.ssid, to_native(error)))
-
- def upgrade_list(self):
- """Determine whether firmware is compatible with the specified drives."""
- if self.upgrade_list_cache is None:
- self.upgrade_list_cache = list()
- try:
- rc, response = self.request("storage-systems/%s/firmware/drives" % self.ssid)
-
- # Create upgrade list, this ensures only the firmware uploaded is applied
- for firmware in self.firmware_list:
- filename = os.path.basename(firmware)
-
- for uploaded_firmware in response["compatibilities"]:
- if uploaded_firmware["filename"] == filename:
-
- # Determine whether upgrade is required
- drive_reference_list = []
- for drive in uploaded_firmware["compatibleDrives"]:
- try:
- rc, drive_info = self.request("storage-systems/%s/drives/%s" % (self.ssid, drive["driveRef"]))
-
- # Add drive references that are supported and differ from current firmware
- if (drive_info["firmwareVersion"] != uploaded_firmware["firmwareVersion"] and
- uploaded_firmware["firmwareVersion"] in uploaded_firmware["supportedFirmwareVersions"]):
-
- if self.ignore_inaccessible_drives or (not drive_info["offline"] and drive_info["available"]):
- drive_reference_list.append(drive["driveRef"])
-
- if not drive["onlineUpgradeCapable"] and self.upgrade_drives_online:
- self.module.fail_json(msg="Drive is not capable of online upgrade. Array [%s]. Drive [%s]."
- % (self.ssid, drive["driveRef"]))
-
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve drive information. Array [%s]. Drive [%s]. Error [%s]."
- % (self.ssid, drive["driveRef"], to_native(error)))
-
- if drive_reference_list:
- self.upgrade_list_cache.extend([{"filename": filename, "driveRefList": drive_reference_list}])
-
- except Exception as error:
- self.module.fail_json(msg="Failed to complete compatibility and health check. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
-
- return self.upgrade_list_cache
-
- def wait_for_upgrade_completion(self):
- """Wait for drive firmware upgrade to complete."""
- drive_references = [reference for drive in self.upgrade_list() for reference in drive["driveRefList"]]
- last_status = None
- for attempt in range(int(self.WAIT_TIMEOUT_SEC / 5)):
- try:
- rc, response = self.request("storage-systems/%s/firmware/drives/state" % self.ssid)
-
- # Check drive status
- for status in response["driveStatus"]:
- last_status = status
- if status["driveRef"] in drive_references:
- if status["status"] == "okay":
- continue
- elif status["status"] in ["inProgress", "inProgressRecon", "pending", "notAttempted"]:
- break
- else:
- self.module.fail_json(msg="Drive firmware upgrade failed. Array [%s]. Drive [%s]. Status [%s]."
- % (self.ssid, status["driveRef"], status["status"]))
- else:
- self.upgrade_in_progress = False
- break
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve drive status. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
-
- sleep(5)
- else:
- self.module.fail_json(msg="Timed out waiting for drive firmware upgrade. Array [%s]. Status [%s]." % (self.ssid, last_status))
-
- def upgrade(self):
- """Apply firmware to applicable drives."""
- try:
- rc, response = self.request("storage-systems/%s/firmware/drives/initiate-upgrade?onlineUpdate=%s"
- % (self.ssid, "true" if self.upgrade_drives_online else "false"), method="POST", data=self.upgrade_list())
- self.upgrade_in_progress = True
- except Exception as error:
- self.module.fail_json(msg="Failed to upgrade drive firmware. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
-
- if self.wait_for_completion:
- self.wait_for_upgrade_completion()
-
- def apply(self):
- """Apply firmware policy has been enforced on E-Series storage system."""
- self.upload_firmware()
-
- if self.upgrade_list() and not self.module.check_mode:
- self.upgrade()
-
- self.module.exit_json(changed=True if self.upgrade_list() else False,
- upgrade_in_process=self.upgrade_in_progress)
-
-
-def main():
- drive_firmware = NetAppESeriesDriveFirmware()
- drive_firmware.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_facts.py b/lib/ansible/modules/storage/netapp/netapp_e_facts.py
deleted file mode 100644
index 6e2c0ece7f..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_facts.py
+++ /dev/null
@@ -1,530 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
-module: netapp_e_facts
-short_description: NetApp E-Series retrieve facts about NetApp E-Series storage arrays
-description:
- - The netapp_e_facts module returns a collection of facts regarding NetApp E-Series storage arrays.
-version_added: '2.2'
-author:
- - Kevin Hulquest (@hulquest)
- - Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-'''
-
-EXAMPLES = """
----
-- name: Get array facts
- netapp_e_facts:
- ssid: "1"
- api_url: "https://192.168.1.100:8443/devmgr/v2"
- api_username: "admin"
- api_password: "adminpass"
- validate_certs: true
-"""
-
-RETURN = """
- msg:
- description: Success message
- returned: on success
- type: str
- sample:
- - Gathered facts for storage array. Array ID [1].
- - Gathered facts for web services proxy.
- storage_array_facts:
- description: provides details about the array, controllers, management interfaces, hostside interfaces,
- driveside interfaces, disks, storage pools, volumes, snapshots, and features.
- returned: on successful inquiry from from embedded web services rest api
- type: complex
- contains:
- netapp_controllers:
- description: storage array controller list that contains basic controller identification and status
- type: complex
- sample:
- - [{"name": "A", "serial": "021632007299", "status": "optimal"},
- {"name": "B", "serial": "021632007300", "status": "failed"}]
- netapp_disks:
- description: drive list that contains identification, type, and status information for each drive
- type: complex
- sample:
- - [{"available": false,
- "firmware_version": "MS02",
- "id": "01000000500003960C8B67880000000000000000",
- "media_type": "ssd",
- "product_id": "PX02SMU080 ",
- "serial_number": "15R0A08LT2BA",
- "status": "optimal",
- "tray_ref": "0E00000000000000000000000000000000000000",
- "usable_bytes": "799629205504" }]
- netapp_driveside_interfaces:
- description: drive side interface list that contains identification, type, and speed for each interface
- type: complex
- sample:
- - [{ "controller": "A", "interface_speed": "12g", "interface_type": "sas" }]
- - [{ "controller": "B", "interface_speed": "10g", "interface_type": "iscsi" }]
- netapp_enabled_features:
- description: specifies the enabled features on the storage array.
- returned: on success
- type: complex
- sample:
- - [ "flashReadCache", "performanceTier", "protectionInformation", "secureVolume" ]
- netapp_host_groups:
- description: specifies the host groups on the storage arrays.
- returned: on success
- type: complex
- sample:
- - [{ "id": "85000000600A098000A4B28D003610705C40B964", "name": "group1" }]
- netapp_hosts:
- description: specifies the hosts on the storage arrays.
- returned: on success
- type: complex
- sample:
- - [{ "id": "8203800000000000000000000000000000000000",
- "name": "host1",
- "group_id": "85000000600A098000A4B28D003610705C40B964",
- "host_type_index": 28,
- "ports": [{ "type": "fc", "address": "1000FF7CFFFFFF01", "label": "FC_1" },
- { "type": "fc", "address": "1000FF7CFFFFFF00", "label": "FC_2" }]}]
- netapp_host_types:
- description: lists the available host types on the storage array.
- returned: on success
- type: complex
- sample:
- - [{ "index": 0, "type": "FactoryDefault" },
- { "index": 1, "type": "W2KNETNCL"},
- { "index": 2, "type": "SOL" },
- { "index": 5, "type": "AVT_4M" },
- { "index": 6, "type": "LNX" },
- { "index": 7, "type": "LnxALUA" },
- { "index": 8, "type": "W2KNETCL" },
- { "index": 9, "type": "AIX MPIO" },
- { "index": 10, "type": "VmwTPGSALUA" },
- { "index": 15, "type": "HPXTPGS" },
- { "index": 17, "type": "SolTPGSALUA" },
- { "index": 18, "type": "SVC" },
- { "index": 22, "type": "MacTPGSALUA" },
- { "index": 23, "type": "WinTPGSALUA" },
- { "index": 24, "type": "LnxTPGSALUA" },
- { "index": 25, "type": "LnxTPGSALUA_PM" },
- { "index": 26, "type": "ONTAP_ALUA" },
- { "index": 27, "type": "LnxTPGSALUA_SF" },
- { "index": 28, "type": "LnxDHALUA" },
- { "index": 29, "type": "ATTOClusterAllOS" }]
- netapp_hostside_interfaces:
- description: host side interface list that contains identification, configuration, type, speed, and
- status information for each interface
- type: complex
- sample:
- - [{"iscsi":
- [{ "controller": "A",
- "current_interface_speed": "10g",
- "ipv4_address": "10.10.10.1",
- "ipv4_enabled": true,
- "ipv4_gateway": "10.10.10.1",
- "ipv4_subnet_mask": "255.255.255.0",
- "ipv6_enabled": false,
- "iqn": "iqn.1996-03.com.netapp:2806.600a098000a81b6d0000000059d60c76",
- "link_status": "up",
- "mtu": 9000,
- "supported_interface_speeds": [ "10g" ] }]}]
- netapp_management_interfaces:
- description: management interface list that contains identification, configuration, and status for
- each interface
- type: complex
- sample:
- - [{"alias": "ict-2800-A",
- "channel": 1,
- "controller": "A",
- "dns_config_method": "dhcp",
- "dns_servers": [],
- "ipv4_address": "10.1.1.1",
- "ipv4_address_config_method": "static",
- "ipv4_enabled": true,
- "ipv4_gateway": "10.113.1.1",
- "ipv4_subnet_mask": "255.255.255.0",
- "ipv6_enabled": false,
- "link_status": "up",
- "mac_address": "00A098A81B5D",
- "name": "wan0",
- "ntp_config_method": "disabled",
- "ntp_servers": [],
- "remote_ssh_access": false }]
- netapp_storage_array:
- description: provides storage array identification, firmware version, and available capabilities
- type: dict
- sample:
- - {"chassis_serial": "021540006043",
- "firmware": "08.40.00.01",
- "name": "ict-2800-11_40",
- "wwn": "600A098000A81B5D0000000059D60C76",
- "cacheBlockSizes": [4096,
- 8192,
- 16384,
- 32768],
- "supportedSegSizes": [8192,
- 16384,
- 32768,
- 65536,
- 131072,
- 262144,
- 524288]}
- netapp_storage_pools:
- description: storage pool list that contains identification and capacity information for each pool
- type: complex
- sample:
- - [{"available_capacity": "3490353782784",
- "id": "04000000600A098000A81B5D000002B45A953A61",
- "name": "Raid6",
- "total_capacity": "5399466745856",
- "used_capacity": "1909112963072" }]
- netapp_volumes:
- description: storage volume list that contains identification and capacity information for each volume
- type: complex
- sample:
- - [{"capacity": "5368709120",
- "id": "02000000600A098000AAC0C3000002C45A952BAA",
- "is_thin_provisioned": false,
- "name": "5G",
- "parent_storage_pool_id": "04000000600A098000A81B5D000002B45A953A61" }]
- netapp_workload_tags:
- description: workload tag list
- type: complex
- sample:
- - [{"id": "87e19568-43fb-4d8d-99ea-2811daaa2b38",
- "name": "ftp_server",
- "workloadAttributes": [{"key": "use",
- "value": "general"}]}]
- netapp_volumes_by_initiators:
- description: list of available volumes keyed by the mapped initiators.
- type: complex
- sample:
- - {"192_168_1_1": [{"id": "02000000600A098000A4B9D1000015FD5C8F7F9E",
- "meta_data": {"filetype": "xfs", "public": true},
- "name": "some_volume",
- "workload_name": "test2_volumes",
- "wwn": "600A098000A4B9D1000015FD5C8F7F9E"}]}
- snapshot_images:
- description: snapshot image list that contains identification, capacity, and status information for each
- snapshot image
- type: complex
- sample:
- - [{"active_cow": true,
- "creation_method": "user",
- "id": "34000000600A098000A81B5D00630A965B0535AC",
- "pit_capacity": "5368709120",
- "reposity_cap_utilization": "0",
- "rollback_source": false,
- "status": "optimal" }]
-"""
-
-from re import match
-from pprint import pformat
-from ansible.module_utils.netapp import NetAppESeriesModule
-
-
-class Facts(NetAppESeriesModule):
- def __init__(self):
- web_services_version = "02.00.0000.0000"
- super(Facts, self).__init__(ansible_options={},
- web_services_version=web_services_version,
- supports_check_mode=True)
-
- def get_controllers(self):
- """Retrieve a mapping of controller references to their labels."""
- controllers = list()
- try:
- rc, controllers = self.request('storage-systems/%s/graph/xpath-filter?query=/controller/id' % self.ssid)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to retrieve controller list! Array Id [%s]. Error [%s]."
- % (self.ssid, str(err)))
-
- controllers.sort()
-
- controllers_dict = {}
- i = ord('A')
- for controller in controllers:
- label = chr(i)
- controllers_dict[controller] = label
- i += 1
-
- return controllers_dict
-
- def get_array_facts(self):
- """Extract particular facts from the storage array graph"""
- facts = dict(facts_from_proxy=(not self.is_embedded()), ssid=self.ssid)
- controller_reference_label = self.get_controllers()
- array_facts = None
-
- # Get the storage array graph
- try:
- rc, array_facts = self.request("storage-systems/%s/graph" % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to obtain facts from storage array with id [%s]. Error [%s]" % (self.ssid, str(error)))
-
- facts['netapp_storage_array'] = dict(
- name=array_facts['sa']['saData']['storageArrayLabel'],
- chassis_serial=array_facts['sa']['saData']['chassisSerialNumber'],
- firmware=array_facts['sa']['saData']['fwVersion'],
- wwn=array_facts['sa']['saData']['saId']['worldWideName'],
- segment_sizes=array_facts['sa']['featureParameters']['supportedSegSizes'],
- cache_block_sizes=array_facts['sa']['featureParameters']['cacheBlockSizes'])
-
- facts['netapp_controllers'] = [
- dict(
- name=controller_reference_label[controller['controllerRef']],
- serial=controller['serialNumber'].strip(),
- status=controller['status'],
- ) for controller in array_facts['controller']]
-
- facts['netapp_host_groups'] = [
- dict(
- id=group['id'],
- name=group['name']
- ) for group in array_facts['storagePoolBundle']['cluster']]
-
- facts['netapp_hosts'] = [
- dict(
- group_id=host['clusterRef'],
- hosts_reference=host['hostRef'],
- id=host['id'],
- name=host['name'],
- host_type_index=host['hostTypeIndex'],
- posts=host['hostSidePorts']
- ) for host in array_facts['storagePoolBundle']['host']]
-
- facts['netapp_host_types'] = [
- dict(
- type=host_type['hostType'],
- index=host_type['index']
- ) for host_type in array_facts['sa']['hostSpecificVals']
- if 'hostType' in host_type.keys() and host_type['hostType']
- # This conditional ignores zero-length strings which indicates that the associated host-specific NVSRAM region has been cleared.
- ]
- facts['snapshot_images'] = [
- dict(
- id=snapshot['id'],
- status=snapshot['status'],
- pit_capacity=snapshot['pitCapacity'],
- creation_method=snapshot['creationMethod'],
- reposity_cap_utilization=snapshot['repositoryCapacityUtilization'],
- active_cow=snapshot['activeCOW'],
- rollback_source=snapshot['isRollbackSource']
- ) for snapshot in array_facts['highLevelVolBundle']['pit']]
-
- facts['netapp_disks'] = [
- dict(
- id=disk['id'],
- available=disk['available'],
- media_type=disk['driveMediaType'],
- status=disk['status'],
- usable_bytes=disk['usableCapacity'],
- tray_ref=disk['physicalLocation']['trayRef'],
- product_id=disk['productID'],
- firmware_version=disk['firmwareVersion'],
- serial_number=disk['serialNumber'].lstrip()
- ) for disk in array_facts['drive']]
-
- facts['netapp_management_interfaces'] = [
- dict(controller=controller_reference_label[controller['controllerRef']],
- name=iface['ethernet']['interfaceName'],
- alias=iface['ethernet']['alias'],
- channel=iface['ethernet']['channel'],
- mac_address=iface['ethernet']['macAddr'],
- remote_ssh_access=iface['ethernet']['rloginEnabled'],
- link_status=iface['ethernet']['linkStatus'],
- ipv4_enabled=iface['ethernet']['ipv4Enabled'],
- ipv4_address_config_method=iface['ethernet']['ipv4AddressConfigMethod'].lower().replace("config", ""),
- ipv4_address=iface['ethernet']['ipv4Address'],
- ipv4_subnet_mask=iface['ethernet']['ipv4SubnetMask'],
- ipv4_gateway=iface['ethernet']['ipv4GatewayAddress'],
- ipv6_enabled=iface['ethernet']['ipv6Enabled'],
- dns_config_method=iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsAcquisitionType'],
- dns_servers=(iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsServers']
- if iface['ethernet']['dnsProperties']['acquisitionProperties']['dnsServers'] else []),
- ntp_config_method=iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpAcquisitionType'],
- ntp_servers=(iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpServers']
- if iface['ethernet']['ntpProperties']['acquisitionProperties']['ntpServers'] else [])
- ) for controller in array_facts['controller'] for iface in controller['netInterfaces']]
-
- facts['netapp_hostside_interfaces'] = [
- dict(
- fc=[dict(controller=controller_reference_label[controller['controllerRef']],
- channel=iface['fibre']['channel'],
- link_status=iface['fibre']['linkStatus'],
- current_interface_speed=strip_interface_speed(iface['fibre']['currentInterfaceSpeed']),
- maximum_interface_speed=strip_interface_speed(iface['fibre']['maximumInterfaceSpeed']))
- for controller in array_facts['controller']
- for iface in controller['hostInterfaces']
- if iface['interfaceType'] == 'fc'],
- ib=[dict(controller=controller_reference_label[controller['controllerRef']],
- channel=iface['ib']['channel'],
- link_status=iface['ib']['linkState'],
- mtu=iface['ib']['maximumTransmissionUnit'],
- current_interface_speed=strip_interface_speed(iface['ib']['currentSpeed']),
- maximum_interface_speed=strip_interface_speed(iface['ib']['supportedSpeed']))
- for controller in array_facts['controller']
- for iface in controller['hostInterfaces']
- if iface['interfaceType'] == 'ib'],
- iscsi=[dict(controller=controller_reference_label[controller['controllerRef']],
- iqn=iface['iscsi']['iqn'],
- link_status=iface['iscsi']['interfaceData']['ethernetData']['linkStatus'],
- ipv4_enabled=iface['iscsi']['ipv4Enabled'],
- ipv4_address=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4Address'],
- ipv4_subnet_mask=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4SubnetMask'],
- ipv4_gateway=iface['iscsi']['ipv4Data']['ipv4AddressData']['ipv4GatewayAddress'],
- ipv6_enabled=iface['iscsi']['ipv6Enabled'],
- mtu=iface['iscsi']['interfaceData']['ethernetData']['maximumFramePayloadSize'],
- current_interface_speed=strip_interface_speed(iface['iscsi']['interfaceData']
- ['ethernetData']['currentInterfaceSpeed']),
- supported_interface_speeds=strip_interface_speed(iface['iscsi']['interfaceData']
- ['ethernetData']
- ['supportedInterfaceSpeeds']))
- for controller in array_facts['controller']
- for iface in controller['hostInterfaces']
- if iface['interfaceType'] == 'iscsi'],
- sas=[dict(controller=controller_reference_label[controller['controllerRef']],
- channel=iface['sas']['channel'],
- current_interface_speed=strip_interface_speed(iface['sas']['currentInterfaceSpeed']),
- maximum_interface_speed=strip_interface_speed(iface['sas']['maximumInterfaceSpeed']),
- link_status=iface['sas']['iocPort']['state'])
- for controller in array_facts['controller']
- for iface in controller['hostInterfaces']
- if iface['interfaceType'] == 'sas'])]
-
- facts['netapp_driveside_interfaces'] = [
- dict(
- controller=controller_reference_label[controller['controllerRef']],
- interface_type=interface['interfaceType'],
- interface_speed=strip_interface_speed(
- interface[interface['interfaceType']]['maximumInterfaceSpeed']
- if (interface['interfaceType'] == 'sata' or
- interface['interfaceType'] == 'sas' or
- interface['interfaceType'] == 'fibre')
- else (
- interface[interface['interfaceType']]['currentSpeed']
- if interface['interfaceType'] == 'ib'
- else (
- interface[interface['interfaceType']]['interfaceData']['maximumInterfaceSpeed']
- if interface['interfaceType'] == 'iscsi' else 'unknown'
- ))),
- )
- for controller in array_facts['controller']
- for interface in controller['driveInterfaces']]
-
- facts['netapp_storage_pools'] = [
- dict(
- id=storage_pool['id'],
- name=storage_pool['name'],
- available_capacity=storage_pool['freeSpace'],
- total_capacity=storage_pool['totalRaidedSpace'],
- used_capacity=storage_pool['usedSpace']
- ) for storage_pool in array_facts['volumeGroup']]
-
- all_volumes = list(array_facts['volume'])
-
- facts['netapp_volumes'] = [
- dict(
- id=v['id'],
- name=v['name'],
- parent_storage_pool_id=v['volumeGroupRef'],
- capacity=v['capacity'],
- is_thin_provisioned=v['thinProvisioned'],
- workload=v['metadata'],
- ) for v in all_volumes]
-
- workload_tags = None
- try:
- rc, workload_tags = self.request("storage-systems/%s/workloads" % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve workload tags. Array [%s]." % self.ssid)
-
- facts['netapp_workload_tags'] = [
- dict(
- id=workload_tag['id'],
- name=workload_tag['name'],
- attributes=workload_tag['workloadAttributes']
- ) for workload_tag in workload_tags]
-
- # Create a dictionary of volume lists keyed by host names
- facts['netapp_volumes_by_initiators'] = dict()
- for mapping in array_facts['storagePoolBundle']['lunMapping']:
- for host in facts['netapp_hosts']:
- if mapping['mapRef'] == host['hosts_reference'] or mapping['mapRef'] == host['group_id']:
- if host['name'] not in facts['netapp_volumes_by_initiators'].keys():
- facts['netapp_volumes_by_initiators'].update({host['name']: []})
-
- for volume in all_volumes:
- if mapping['id'] in [volume_mapping['id'] for volume_mapping in volume['listOfMappings']]:
-
- # Determine workload name if there is one
- workload_name = ""
- metadata = dict()
- for volume_tag in volume['metadata']:
- if volume_tag['key'] == 'workloadId':
- for workload_tag in facts['netapp_workload_tags']:
- if volume_tag['value'] == workload_tag['id']:
- workload_name = workload_tag['name']
- metadata = dict((entry['key'], entry['value'])
- for entry in workload_tag['attributes']
- if entry['key'] != 'profileId')
-
- facts['netapp_volumes_by_initiators'][host['name']].append(
- dict(name=volume['name'],
- id=volume['id'],
- wwn=volume['wwn'],
- workload_name=workload_name,
- meta_data=metadata))
-
- features = [feature for feature in array_facts['sa']['capabilities']]
- features.extend([feature['capability'] for feature in array_facts['sa']['premiumFeatures']
- if feature['isEnabled']])
- features = list(set(features)) # ensure unique
- features.sort()
- facts['netapp_enabled_features'] = features
-
- return facts
-
- def get_facts(self):
- """Get the embedded or web services proxy information."""
- facts = self.get_array_facts()
-
- self.module.log("isEmbedded: %s" % self.is_embedded())
- self.module.log(pformat(facts))
-
- self.module.exit_json(msg="Gathered facts for storage array. Array ID: [%s]." % self.ssid,
- storage_array_facts=facts)
-
-
-def strip_interface_speed(speed):
- """Converts symbol interface speeds to a more common notation. Example: 'speed10gig' -> '10g'"""
- if isinstance(speed, list):
- result = [match(r"speed[0-9]{1,3}[gm]", sp) for sp in speed]
- result = [sp.group().replace("speed", "") if result else "unknown" for sp in result if sp]
- result = ["auto" if match(r"auto", sp) else sp for sp in result]
- else:
- result = match(r"speed[0-9]{1,3}[gm]", speed)
- result = result.group().replace("speed", "") if result else "unknown"
- result = "auto" if match(r"auto", result.lower()) else result
- return result
-
-
-def main():
- facts = Facts()
- facts.get_facts()
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_firmware.py b/lib/ansible/modules/storage/netapp/netapp_e_firmware.py
deleted file mode 100644
index 7c6b3ee9f1..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_firmware.py
+++ /dev/null
@@ -1,488 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_firmware
-version_added: "2.9"
-short_description: NetApp E-Series manage firmware.
-description:
- - Ensure specific firmware versions are activated on E-Series storage system.
-author:
- - Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- nvsram:
- description:
- - Path to the NVSRAM file.
- type: str
- required: true
- firmware:
- description:
- - Path to the firmware file.
- type: str
- required: true
- wait_for_completion:
- description:
- - This flag will cause module to wait for any upgrade actions to complete.
- type: bool
- default: false
- ignore_health_check:
- description:
- - This flag will force firmware to be activated in spite of the health check.
- - Use at your own risk. Certain non-optimal states could result in data loss.
- type: bool
- default: false
-"""
-EXAMPLES = """
-- name: Ensure correct firmware versions
- netapp_e_firmware:
- ssid: "1"
- api_url: "https://192.168.1.100:8443/devmgr/v2"
- api_username: "admin"
- api_password: "adminpass"
- validate_certs: true
- nvsram: "path/to/nvsram"
- bundle: "path/to/bundle"
- wait_for_completion: true
-- name: Ensure correct firmware versions
- netapp_e_firmware:
- ssid: "1"
- api_url: "https://192.168.1.100:8443/devmgr/v2"
- api_username: "admin"
- api_password: "adminpass"
- validate_certs: true
- nvsram: "path/to/nvsram"
- firmware: "path/to/firmware"
-"""
-RETURN = """
-msg:
- description: Status and version of firmware and NVSRAM.
- type: str
- returned: always
- sample:
-"""
-import os
-
-from time import sleep
-from ansible.module_utils import six
-from ansible.module_utils.netapp import NetAppESeriesModule, create_multipart_formdata, request
-from ansible.module_utils._text import to_native, to_text, to_bytes
-
-
-class NetAppESeriesFirmware(NetAppESeriesModule):
- HEALTH_CHECK_TIMEOUT_MS = 120000
- REBOOT_TIMEOUT_SEC = 15 * 60
- FIRMWARE_COMPATIBILITY_CHECK_TIMEOUT_SEC = 60
- DEFAULT_TIMEOUT = 60 * 15 # This will override the NetAppESeriesModule request method timeout.
-
- def __init__(self):
- ansible_options = dict(
- nvsram=dict(type="str", required=True),
- firmware=dict(type="str", required=True),
- wait_for_completion=dict(type="bool", default=False),
- ignore_health_check=dict(type="bool", default=False))
-
- super(NetAppESeriesFirmware, self).__init__(ansible_options=ansible_options,
- web_services_version="02.00.0000.0000",
- supports_check_mode=True)
-
- args = self.module.params
- self.nvsram = args["nvsram"]
- self.firmware = args["firmware"]
- self.wait_for_completion = args["wait_for_completion"]
- self.ignore_health_check = args["ignore_health_check"]
-
- self.nvsram_name = None
- self.firmware_name = None
- self.is_bundle_cache = None
- self.firmware_version_cache = None
- self.nvsram_version_cache = None
- self.upgrade_required = False
- self.upgrade_in_progress = False
- self.module_info = dict()
-
- self.nvsram_name = os.path.basename(self.nvsram)
- self.firmware_name = os.path.basename(self.firmware)
-
- def is_firmware_bundled(self):
- """Determine whether supplied firmware is bundle."""
- if self.is_bundle_cache is None:
- with open(self.firmware, "rb") as fh:
- signature = fh.read(16).lower()
-
- if b"firmware" in signature:
- self.is_bundle_cache = False
- elif b"combined_content" in signature:
- self.is_bundle_cache = True
- else:
- self.module.fail_json(msg="Firmware file is invalid. File [%s]. Array [%s]" % (self.firmware, self.ssid))
-
- return self.is_bundle_cache
-
- def firmware_version(self):
- """Retrieve firmware version of the firmware file. Return: bytes string"""
- if self.firmware_version_cache is None:
-
- # Search firmware file for bundle or firmware version
- with open(self.firmware, "rb") as fh:
- line = fh.readline()
- while line:
- if self.is_firmware_bundled():
- if b'displayableAttributeList=' in line:
- for item in line[25:].split(b','):
- key, value = item.split(b"|")
- if key == b'VERSION':
- self.firmware_version_cache = value.strip(b"\n")
- break
- elif b"Version:" in line:
- self.firmware_version_cache = line.split()[-1].strip(b"\n")
- break
- line = fh.readline()
- else:
- self.module.fail_json(msg="Failed to determine firmware version. File [%s]. Array [%s]." % (self.firmware, self.ssid))
- return self.firmware_version_cache
-
- def nvsram_version(self):
- """Retrieve NVSRAM version of the NVSRAM file. Return: byte string"""
- if self.nvsram_version_cache is None:
-
- with open(self.nvsram, "rb") as fh:
- line = fh.readline()
- while line:
- if b".NVSRAM Configuration Number" in line:
- self.nvsram_version_cache = line.split(b'"')[-2]
- break
- line = fh.readline()
- else:
- self.module.fail_json(msg="Failed to determine NVSRAM file version. File [%s]. Array [%s]." % (self.nvsram, self.ssid))
- return self.nvsram_version_cache
-
- def check_system_health(self):
- """Ensure E-Series storage system is healthy. Works for both embedded and proxy web services."""
- try:
- rc, request_id = self.request("health-check", method="POST", data={"onlineOnly": True, "storageDeviceIds": [self.ssid]})
-
- while True:
- sleep(1)
-
- try:
- rc, response = self.request("health-check?requestId=%s" % request_id["requestId"])
-
- if not response["healthCheckRunning"]:
- return response["results"][0]["successful"]
- elif int(response["results"][0]["processingTimeMS"]) > self.HEALTH_CHECK_TIMEOUT_MS:
- self.module.fail_json(msg="Health check failed to complete. Array Id [%s]." % self.ssid)
-
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve health check status. Array Id [%s]. Error[%s]." % (self.ssid, to_native(error)))
- except Exception as error:
- self.module.fail_json(msg="Failed to initiate health check. Array Id [%s]. Error[%s]." % (self.ssid, to_native(error)))
-
- self.module.fail_json(msg="Failed to retrieve health check status. Array Id [%s]. Error[%s]." % self.ssid)
-
- def embedded_check_compatibility(self):
- """Verify files are compatible with E-Series storage system."""
- self.embedded_check_nvsram_compatibility()
- self.embedded_check_bundle_compatibility()
-
- def embedded_check_nvsram_compatibility(self):
- """Verify the provided NVSRAM is compatible with E-Series storage system."""
-
- # Check nvsram compatibility
- try:
- files = [("nvsramimage", self.nvsram_name, self.nvsram)]
- headers, data = create_multipart_formdata(files=files)
-
- rc, nvsram_compatible = self.request("firmware/embedded-firmware/%s/nvsram-compatibility-check" % self.ssid,
- method="POST", data=data, headers=headers)
-
- if not nvsram_compatible["signatureTestingPassed"]:
- self.module.fail_json(msg="Invalid NVSRAM file. File [%s]." % self.nvsram)
- if not nvsram_compatible["fileCompatible"]:
- self.module.fail_json(msg="Incompatible NVSRAM file. File [%s]." % self.nvsram)
-
- # Determine whether nvsram is required
- for module in nvsram_compatible["versionContents"]:
- if module["bundledVersion"] != module["onboardVersion"]:
- self.upgrade_required = True
-
- # Update bundle info
- self.module_info.update({module["module"]: {"onboard_version": module["onboardVersion"], "bundled_version": module["bundledVersion"]}})
-
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve NVSRAM compatibility results. Array Id [%s]. Error[%s]." % (self.ssid, to_native(error)))
-
- def embedded_check_bundle_compatibility(self):
- """Verify the provided firmware bundle is compatible with E-Series storage system."""
- try:
- files = [("files[]", "blob", self.firmware)]
- headers, data = create_multipart_formdata(files=files, send_8kb=True)
- rc, bundle_compatible = self.request("firmware/embedded-firmware/%s/bundle-compatibility-check" % self.ssid,
- method="POST", data=data, headers=headers)
-
- # Determine whether valid and compatible firmware
- if not bundle_compatible["signatureTestingPassed"]:
- self.module.fail_json(msg="Invalid firmware bundle file. File [%s]." % self.firmware)
- if not bundle_compatible["fileCompatible"]:
- self.module.fail_json(msg="Incompatible firmware bundle file. File [%s]." % self.firmware)
-
- # Determine whether upgrade is required
- for module in bundle_compatible["versionContents"]:
-
- bundle_module_version = module["bundledVersion"].split(".")
- onboard_module_version = module["onboardVersion"].split(".")
- version_minimum_length = min(len(bundle_module_version), len(onboard_module_version))
- if bundle_module_version[:version_minimum_length] != onboard_module_version[:version_minimum_length]:
- self.upgrade_required = True
-
- # Check whether downgrade is being attempted
- bundle_version = module["bundledVersion"].split(".")[:2]
- onboard_version = module["onboardVersion"].split(".")[:2]
- if bundle_version[0] < onboard_version[0] or (bundle_version[0] == onboard_version[0] and bundle_version[1] < onboard_version[1]):
- self.module.fail_json(msg="Downgrades are not permitted. onboard [%s] > bundled[%s]."
- % (module["onboardVersion"], module["bundledVersion"]))
-
- # Update bundle info
- self.module_info.update({module["module"]: {"onboard_version": module["onboardVersion"], "bundled_version": module["bundledVersion"]}})
-
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve bundle compatibility results. Array Id [%s]. Error[%s]." % (self.ssid, to_native(error)))
-
- def embedded_wait_for_upgrade(self):
- """Wait for SANtricity Web Services Embedded to be available after reboot."""
- for count in range(0, self.REBOOT_TIMEOUT_SEC):
- try:
- rc, response = self.request("storage-systems/%s/graph/xpath-filter?query=/sa/saData" % self.ssid)
- bundle_display = [m["versionString"] for m in response[0]["extendedSAData"]["codeVersions"] if m["codeModule"] == "bundleDisplay"][0]
- if rc == 200 and six.b(bundle_display) == self.firmware_version() and six.b(response[0]["nvsramVersion"]) == self.nvsram_version():
- self.upgrade_in_progress = False
- break
- except Exception as error:
- pass
- sleep(1)
- else:
- self.module.fail_json(msg="Timeout waiting for Santricity Web Services Embedded. Array [%s]" % self.ssid)
-
- def embedded_upgrade(self):
- """Upload and activate both firmware and NVSRAM."""
- files = [("nvsramfile", self.nvsram_name, self.nvsram),
- ("dlpfile", self.firmware_name, self.firmware)]
- headers, data = create_multipart_formdata(files=files)
- try:
- rc, response = self.request("firmware/embedded-firmware?staged=false&nvsram=true", method="POST", data=data, headers=headers)
- self.upgrade_in_progress = True
- except Exception as error:
- self.module.fail_json(msg="Failed to upload and activate firmware. Array Id [%s]. Error[%s]." % (self.ssid, to_native(error)))
- if self.wait_for_completion:
- self.embedded_wait_for_upgrade()
-
- def proxy_check_nvsram_compatibility(self):
- """Verify nvsram is compatible with E-Series storage system."""
- data = {"storageDeviceIds": [self.ssid]}
- try:
- rc, check = self.request("firmware/compatibility-check", method="POST", data=data)
- for count in range(0, int((self.FIRMWARE_COMPATIBILITY_CHECK_TIMEOUT_SEC / 5))):
- sleep(5)
- try:
- rc, response = self.request("firmware/compatibility-check?requestId=%s" % check["requestId"])
- if not response["checkRunning"]:
- for result in response["results"][0]["nvsramFiles"]:
- if result["filename"] == self.nvsram_name:
- return
- self.module.fail_json(msg="NVSRAM is not compatible. NVSRAM [%s]. Array [%s]." % (self.nvsram_name, self.ssid))
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve NVSRAM status update from proxy. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
- except Exception as error:
- self.module.fail_json(msg="Failed to receive NVSRAM compatibility information. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
-
- def proxy_check_firmware_compatibility(self):
- """Verify firmware is compatible with E-Series storage system."""
- data = {"storageDeviceIds": [self.ssid]}
- try:
- rc, check = self.request("firmware/compatibility-check", method="POST", data=data)
- for count in range(0, int((self.FIRMWARE_COMPATIBILITY_CHECK_TIMEOUT_SEC / 5))):
- sleep(5)
- try:
- rc, response = self.request("firmware/compatibility-check?requestId=%s" % check["requestId"])
- if not response["checkRunning"]:
- for result in response["results"][0]["cfwFiles"]:
- if result["filename"] == self.firmware_name:
- return
- self.module.fail_json(msg="Firmware bundle is not compatible. firmware [%s]. Array [%s]." % (self.firmware_name, self.ssid))
-
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve firmware status update from proxy. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
- except Exception as error:
- self.module.fail_json(msg="Failed to receive firmware compatibility information. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
-
- def proxy_upload_and_check_compatibility(self):
- """Ensure firmware is uploaded and verify compatibility."""
- try:
- rc, cfw_files = self.request("firmware/cfw-files")
- for file in cfw_files:
- if file["filename"] == self.nvsram_name:
- break
- else:
- fields = [("validate", "true")]
- files = [("firmwareFile", self.nvsram_name, self.nvsram)]
- headers, data = create_multipart_formdata(files=files, fields=fields)
- try:
- rc, response = self.request("firmware/upload", method="POST", data=data, headers=headers)
- except Exception as error:
- self.module.fail_json(msg="Failed to upload NVSRAM file. File [%s]. Array [%s]. Error [%s]."
- % (self.nvsram_name, self.ssid, to_native(error)))
-
- self.proxy_check_nvsram_compatibility()
-
- for file in cfw_files:
- if file["filename"] == self.firmware_name:
- break
- else:
- fields = [("validate", "true")]
- files = [("firmwareFile", self.firmware_name, self.firmware)]
- headers, data = create_multipart_formdata(files=files, fields=fields)
- try:
- rc, response = self.request("firmware/upload", method="POST", data=data, headers=headers)
- except Exception as error:
- self.module.fail_json(msg="Failed to upload firmware bundle file. File [%s]. Array [%s]. Error [%s]."
- % (self.firmware_name, self.ssid, to_native(error)))
-
- self.proxy_check_firmware_compatibility()
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve existing existing firmware files. Error [%s]" % to_native(error))
-
- def proxy_check_upgrade_required(self):
- """Staging is required to collect firmware information from the web services proxy."""
- # Verify controller consistency and get firmware versions
- try:
- # Retrieve current bundle version
- if self.is_firmware_bundled():
- rc, response = self.request("storage-systems/%s/graph/xpath-filter?query=/controller/codeVersions[codeModule='bundleDisplay']" % self.ssid)
- current_firmware_version = six.b(response[0]["versionString"])
- else:
- rc, response = self.request("storage-systems/%s/graph/xpath-filter?query=/sa/saData/fwVersion" % self.ssid)
- current_firmware_version = six.b(response[0])
-
- # Determine whether upgrade is required
- if current_firmware_version != self.firmware_version():
-
- current = current_firmware_version.split(b".")[:2]
- upgrade = self.firmware_version().split(b".")[:2]
- if current[0] < upgrade[0] or (current[0] == upgrade[0] and current[1] <= upgrade[1]):
- self.upgrade_required = True
- else:
- self.module.fail_json(msg="Downgrades are not permitted. Firmware [%s]. Array [%s]." % (self.firmware, self.ssid))
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve controller firmware information. Array [%s]. Error [%s]" % (self.ssid, to_native(error)))
- # Determine current NVSRAM version and whether change is required
- try:
- rc, response = self.request("storage-systems/%s/graph/xpath-filter?query=/sa/saData/nvsramVersion" % self.ssid)
- if six.b(response[0]) != self.nvsram_version():
- self.upgrade_required = True
-
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve storage system's NVSRAM version. Array [%s]. Error [%s]" % (self.ssid, to_native(error)))
-
- def proxy_wait_for_upgrade(self, request_id):
- """Wait for SANtricity Web Services Proxy to report upgrade complete"""
- if self.is_firmware_bundled():
- while True:
- try:
- sleep(5)
- rc, response = self.request("batch/cfw-upgrade/%s" % request_id)
-
- if response["status"] == "complete":
- self.upgrade_in_progress = False
- break
- elif response["status"] in ["failed", "cancelled"]:
- self.module.fail_json(msg="Firmware upgrade failed to complete. Array [%s]." % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve firmware upgrade status. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
- else:
- for count in range(0, int(self.REBOOT_TIMEOUT_SEC / 5)):
- try:
- sleep(5)
- rc_firmware, firmware = self.request("storage-systems/%s/graph/xpath-filter?query=/sa/saData/fwVersion" % self.ssid)
- rc_nvsram, nvsram = self.request("storage-systems/%s/graph/xpath-filter?query=/sa/saData/nvsramVersion" % self.ssid)
-
- if six.b(firmware[0]) == self.firmware_version() and six.b(nvsram[0]) == self.nvsram_version():
- self.upgrade_in_progress = False
- break
- except Exception as error:
- pass
- else:
- self.module.fail_json(msg="Timed out waiting for firmware upgrade to complete. Array [%s]." % self.ssid)
-
- def proxy_upgrade(self):
- """Activate previously uploaded firmware related files."""
- request_id = None
- if self.is_firmware_bundled():
- data = {"activate": True,
- "firmwareFile": self.firmware_name,
- "nvsramFile": self.nvsram_name,
- "systemInfos": [{"systemId": self.ssid,
- "allowNonOptimalActivation": self.ignore_health_check}]}
- try:
- rc, response = self.request("batch/cfw-upgrade", method="POST", data=data)
- request_id = response["requestId"]
- except Exception as error:
- self.module.fail_json(msg="Failed to initiate firmware upgrade. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
-
- else:
- data = {"stageFirmware": False,
- "skipMelCheck": self.ignore_health_check,
- "cfwFile": self.firmware_name,
- "nvsramFile": self.nvsram_name}
- try:
- rc, response = self.request("storage-systems/%s/cfw-upgrade" % self.ssid, method="POST", data=data)
- request_id = response["requestId"]
- except Exception as error:
- self.module.fail_json(msg="Failed to initiate firmware upgrade. Array [%s]. Error [%s]." % (self.ssid, to_native(error)))
-
- self.upgrade_in_progress = True
- if self.wait_for_completion:
- self.proxy_wait_for_upgrade(request_id)
-
- def apply(self):
- """Upgrade controller firmware."""
- self.check_system_health()
-
- # Verify firmware compatibility and whether changes are required
- if self.is_embedded():
- self.embedded_check_compatibility()
- else:
- self.proxy_check_upgrade_required()
-
- # This will upload the firmware files to the web services proxy but not to the controller
- if self.upgrade_required:
- self.proxy_upload_and_check_compatibility()
-
- # Perform upgrade
- if self.upgrade_required and not self.module.check_mode:
- if self.is_embedded():
- self.embedded_upgrade()
- else:
- self.proxy_upgrade()
-
- self.module.exit_json(changed=self.upgrade_required, upgrade_in_process=self.upgrade_in_progress, status=self.module_info)
-
-
-def main():
- firmware = NetAppESeriesFirmware()
- firmware.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_flashcache.py b/lib/ansible/modules/storage/netapp/netapp_e_flashcache.py
deleted file mode 100644
index 6aba25ab99..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_flashcache.py
+++ /dev/null
@@ -1,415 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-module: netapp_e_flashcache
-author: Kevin Hulquest (@hulquest)
-version_added: '2.2'
-short_description: NetApp E-Series manage SSD caches
-description:
-- Create or remove SSD caches on a NetApp E-Series storage array.
-options:
- api_username:
- required: true
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_password:
- required: true
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_url:
- required: true
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API.
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- type: bool
- ssid:
- required: true
- description:
- - The ID of the array to manage (as configured on the web services proxy).
- state:
- required: true
- description:
- - Whether the specified SSD cache should exist or not.
- choices: ['present', 'absent']
- default: present
- name:
- required: true
- description:
- - The name of the SSD cache to manage
- io_type:
- description:
- - The type of workload to optimize the cache for.
- choices: ['filesystem','database','media']
- default: filesystem
- disk_count:
- description:
- - The minimum number of disks to use for building the cache. The cache will be expanded if this number exceeds the number of disks already in place
- size_unit:
- description:
- - The unit to be applied to size arguments
- choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
- default: gb
- cache_size_min:
- description:
- - The minimum size (in size_units) of the ssd cache. The cache will be expanded if this exceeds the current size of the cache.
-'''
-
-EXAMPLES = """
- - name: Flash Cache
- netapp_e_flashcache:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- name: SSDCacheBuiltByAnsible
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: success
- type: str
- sample: json for newly created flash cache
-"""
-import json
-import logging
-import sys
-import traceback
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.six.moves import reduce
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-from ansible.module_utils._text import to_native
-from ansible.module_utils.urls import open_url
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as err:
- r = err.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-class NetAppESeriesFlashCache(object):
- def __init__(self):
- self.name = None
- self.log_mode = None
- self.log_path = None
- self.api_url = None
- self.api_username = None
- self.api_password = None
- self.ssid = None
- self.validate_certs = None
- self.disk_count = None
- self.size_unit = None
- self.cache_size_min = None
- self.io_type = None
- self.driveRefs = None
- self.state = None
- self._size_unit_map = dict(
- bytes=1,
- b=1,
- kb=1024,
- mb=1024 ** 2,
- gb=1024 ** 3,
- tb=1024 ** 4,
- pb=1024 ** 5,
- eb=1024 ** 6,
- zb=1024 ** 7,
- yb=1024 ** 8
- )
-
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- api_username=dict(type='str', required=True),
- api_password=dict(type='str', required=True, no_log=True),
- api_url=dict(type='str', required=True),
- state=dict(default='present', choices=['present', 'absent'], type='str'),
- ssid=dict(required=True, type='str'),
- name=dict(required=True, type='str'),
- disk_count=dict(type='int'),
- disk_refs=dict(type='list'),
- cache_size_min=dict(type='int'),
- io_type=dict(default='filesystem', choices=['filesystem', 'database', 'media']),
- size_unit=dict(default='gb', choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'],
- type='str'),
- criteria_disk_phy_type=dict(choices=['sas', 'sas4k', 'fibre', 'fibre520b', 'scsi', 'sata', 'pata'],
- type='str'),
- log_mode=dict(type='str'),
- log_path=dict(type='str'),
- ))
- self.module = AnsibleModule(
- argument_spec=argument_spec,
- required_if=[
-
- ],
- mutually_exclusive=[
-
- ],
- # TODO: update validation for various selection criteria
- supports_check_mode=True
- )
-
- self.__dict__.update(self.module.params)
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
- self.debug = self._logger.debug
-
- if self.log_mode == 'file' and self.log_path:
- logging.basicConfig(level=logging.DEBUG, filename=self.log_path)
- elif self.log_mode == 'stderr':
- logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
-
- self.post_headers = dict(Accept="application/json")
- self.post_headers['Content-Type'] = 'application/json'
-
- def get_candidate_disks(self, disk_count, size_unit='gb', capacity=None):
- self.debug("getting candidate disks...")
-
- drives_req = dict(
- driveCount=disk_count,
- sizeUnit=size_unit,
- driveType='ssd',
- )
-
- if capacity:
- drives_req['targetUsableCapacity'] = capacity
-
- (rc, drives_resp) = request(self.api_url + "/storage-systems/%s/drives" % (self.ssid),
- data=json.dumps(drives_req), headers=self.post_headers, method='POST',
- url_username=self.api_username, url_password=self.api_password,
- validate_certs=self.validate_certs)
-
- if rc == 204:
- self.module.fail_json(msg='Cannot find disks to match requested criteria for ssd cache')
-
- disk_ids = [d['id'] for d in drives_resp]
- bytes = reduce(lambda s, d: s + int(d['usableCapacity']), drives_resp, 0)
-
- return (disk_ids, bytes)
-
- def create_cache(self):
- (disk_ids, bytes) = self.get_candidate_disks(disk_count=self.disk_count, size_unit=self.size_unit,
- capacity=self.cache_size_min)
-
- self.debug("creating ssd cache...")
-
- create_fc_req = dict(
- driveRefs=disk_ids,
- name=self.name
- )
-
- (rc, self.resp) = request(self.api_url + "/storage-systems/%s/flash-cache" % (self.ssid),
- data=json.dumps(create_fc_req), headers=self.post_headers, method='POST',
- url_username=self.api_username, url_password=self.api_password,
- validate_certs=self.validate_certs)
-
- def update_cache(self):
- self.debug('updating flash cache config...')
- update_fc_req = dict(
- name=self.name,
- configType=self.io_type
- )
-
- (rc, self.resp) = request(self.api_url + "/storage-systems/%s/flash-cache/configure" % (self.ssid),
- data=json.dumps(update_fc_req), headers=self.post_headers, method='POST',
- url_username=self.api_username, url_password=self.api_password,
- validate_certs=self.validate_certs)
-
- def delete_cache(self):
- self.debug('deleting flash cache...')
- (rc, self.resp) = request(self.api_url + "/storage-systems/%s/flash-cache" % (self.ssid), method='DELETE',
- url_username=self.api_username, url_password=self.api_password,
- validate_certs=self.validate_certs, ignore_errors=True)
-
- @property
- def needs_more_disks(self):
- if len(self.cache_detail['driveRefs']) < self.disk_count:
- self.debug("needs resize: current disk count %s < requested requested count %s",
- len(self.cache_detail['driveRefs']), self.disk_count)
- return True
-
- @property
- def needs_less_disks(self):
- if len(self.cache_detail['driveRefs']) > self.disk_count:
- self.debug("needs resize: current disk count %s < requested requested count %s",
- len(self.cache_detail['driveRefs']), self.disk_count)
- return True
-
- @property
- def current_size_bytes(self):
- return int(self.cache_detail['fcDriveInfo']['fcWithDrives']['usedCapacity'])
-
- @property
- def requested_size_bytes(self):
- if self.cache_size_min:
- return self.cache_size_min * self._size_unit_map[self.size_unit]
- else:
- return 0
-
- @property
- def needs_more_capacity(self):
- if self.current_size_bytes < self.requested_size_bytes:
- self.debug("needs resize: current capacity %sb is less than requested minimum %sb",
- self.current_size_bytes, self.requested_size_bytes)
- return True
-
- @property
- def needs_resize(self):
- return self.needs_more_disks or self.needs_more_capacity or self.needs_less_disks
-
- def resize_cache(self):
- # increase up to disk count first, then iteratively add disks until we meet requested capacity
-
- # TODO: perform this calculation in check mode
- current_disk_count = len(self.cache_detail['driveRefs'])
- proposed_new_disks = 0
-
- proposed_additional_bytes = 0
- proposed_disk_ids = []
-
- if self.needs_more_disks:
- proposed_disk_count = self.disk_count - current_disk_count
-
- (disk_ids, bytes) = self.get_candidate_disks(disk_count=proposed_disk_count)
- proposed_additional_bytes = bytes
- proposed_disk_ids = disk_ids
-
- while self.current_size_bytes + proposed_additional_bytes < self.requested_size_bytes:
- proposed_new_disks += 1
- (disk_ids, bytes) = self.get_candidate_disks(disk_count=proposed_new_disks)
- proposed_disk_ids = disk_ids
- proposed_additional_bytes = bytes
-
- add_drives_req = dict(
- driveRef=proposed_disk_ids
- )
-
- self.debug("adding drives to flash-cache...")
- (rc, self.resp) = request(self.api_url + "/storage-systems/%s/flash-cache/addDrives" % (self.ssid),
- data=json.dumps(add_drives_req), headers=self.post_headers, method='POST',
- url_username=self.api_username, url_password=self.api_password,
- validate_certs=self.validate_certs)
-
- elif self.needs_less_disks and self.driveRefs:
- rm_drives = dict(driveRef=self.driveRefs)
- (rc, self.resp) = request(self.api_url + "/storage-systems/%s/flash-cache/removeDrives" % (self.ssid),
- data=json.dumps(rm_drives), headers=self.post_headers, method='POST',
- url_username=self.api_username, url_password=self.api_password,
- validate_certs=self.validate_certs)
-
- def apply(self):
- result = dict(changed=False)
- (rc, cache_resp) = request(self.api_url + "/storage-systems/%s/flash-cache" % (self.ssid),
- url_username=self.api_username, url_password=self.api_password,
- validate_certs=self.validate_certs, ignore_errors=True)
-
- if rc == 200:
- self.cache_detail = cache_resp
- else:
- self.cache_detail = None
-
- if rc not in [200, 404]:
- raise Exception(
- "Unexpected error code %s fetching flash cache detail. Response data was %s" % (rc, cache_resp))
-
- if self.state == 'present':
- if self.cache_detail:
- # TODO: verify parameters against detail for changes
- if self.cache_detail['name'] != self.name:
- self.debug("CHANGED: name differs")
- result['changed'] = True
- if self.cache_detail['flashCacheBase']['configType'] != self.io_type:
- self.debug("CHANGED: io_type differs")
- result['changed'] = True
- if self.needs_resize:
- self.debug("CHANGED: resize required")
- result['changed'] = True
- else:
- self.debug("CHANGED: requested state is 'present' but cache does not exist")
- result['changed'] = True
- else: # requested state is absent
- if self.cache_detail:
- self.debug("CHANGED: requested state is 'absent' but cache exists")
- result['changed'] = True
-
- if not result['changed']:
- self.debug("no changes, exiting...")
- self.module.exit_json(**result)
-
- if self.module.check_mode:
- self.debug("changes pending in check mode, exiting early...")
- self.module.exit_json(**result)
-
- if self.state == 'present':
- if not self.cache_detail:
- self.create_cache()
- else:
- if self.needs_resize:
- self.resize_cache()
-
- # run update here as well, since io_type can't be set on creation
- self.update_cache()
-
- elif self.state == 'absent':
- self.delete_cache()
-
- # TODO: include other details about the storage pool (size, type, id, etc)
- self.module.exit_json(changed=result['changed'], **self.resp)
-
-
-def main():
- sp = NetAppESeriesFlashCache()
- try:
- sp.apply()
- except Exception as e:
- sp.debug("Exception in apply(): \n%s", to_native(e))
- sp.module.fail_json(msg="Failed to create flash cache. Error[%s]" % to_native(e),
- exception=traceback.format_exc())
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_global.py b/lib/ansible/modules/storage/netapp/netapp_e_global.py
deleted file mode 100644
index 04d71e8e78..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_global.py
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_global
-short_description: NetApp E-Series manage global settings configuration
-description:
- - Allow the user to configure several of the global settings associated with an E-Series storage-system
-version_added: '2.7'
-author: Michael Price (@lmprice)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- name:
- description:
- - Set the name of the E-Series storage-system
- - This label/name doesn't have to be unique.
- - May be up to 30 characters in length.
- aliases:
- - label
- log_path:
- description:
- - A local path to a file to be used for debug logging
- required: no
-notes:
- - Check mode is supported.
- - This module requires Web Services API v1.3 or newer.
-"""
-
-EXAMPLES = """
- - name: Set the storage-system name
- netapp_e_global:
- name: myArrayName
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The settings have been updated.
-name:
- description:
- - The current name/label of the storage-system.
- returned: on success
- sample: myArrayName
- type: str
-"""
-import json
-import logging
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class GlobalSettings(object):
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- name=dict(type='str', required=False, aliases=['label']),
- log_path=dict(type='str', required=False),
- ))
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, )
- args = self.module.params
- self.name = args['name']
-
- self.ssid = args['ssid']
- self.url = args['api_url']
- self.creds = dict(url_password=args['api_password'],
- validate_certs=args['validate_certs'],
- url_username=args['api_username'], )
-
- self.check_mode = self.module.check_mode
-
- log_path = args['log_path']
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- if self.name and len(self.name) > 30:
- self.module.fail_json(msg="The provided name is invalid, it must be < 30 characters in length.")
-
- def get_name(self):
- try:
- (rc, result) = request(self.url + 'storage-systems/%s' % self.ssid, headers=HEADERS, **self.creds)
- if result['status'] in ['offline', 'neverContacted']:
- self.module.fail_json(msg="This storage-system is offline! Array Id [%s]." % (self.ssid))
- return result['name']
- except Exception as err:
- self.module.fail_json(msg="Connection failure! Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
-
- def update_name(self):
- name = self.get_name()
- update = False
- if self.name != name:
- update = True
-
- body = dict(name=self.name)
-
- if update and not self.check_mode:
- try:
- (rc, result) = request(self.url + 'storage-systems/%s/configuration' % self.ssid, method='POST',
- data=json.dumps(body), headers=HEADERS, **self.creds)
- self._logger.info("Set name to %s.", result['name'])
- # This is going to catch cases like a connection failure
- except Exception as err:
- self.module.fail_json(
- msg="We failed to set the storage-system name! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
- return update
-
- def update(self):
- update = self.update_name()
- name = self.get_name()
-
- self.module.exit_json(msg="The requested settings have been updated.", changed=update, name=name)
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- settings = GlobalSettings()
- settings()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_host.py b/lib/ansible/modules/storage/netapp/netapp_e_host.py
deleted file mode 100644
index 0d220550af..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_host.py
+++ /dev/null
@@ -1,539 +0,0 @@
-#!/usr/bin/python
-# -*- 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_host
-short_description: NetApp E-Series manage eseries hosts
-description: Create, update, remove hosts on NetApp E-series storage arrays
-version_added: '2.2'
-author:
- - Kevin Hulquest (@hulquest)
- - Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- name:
- description:
- - If the host doesn't yet exist, the label/name to assign at creation time.
- - If the hosts already exists, this will be used to uniquely identify the host to make any required changes
- required: True
- aliases:
- - label
- state:
- description:
- - Set to absent to remove an existing host
- - Set to present to modify or create a new host definition
- choices:
- - absent
- - present
- default: present
- version_added: 2.7
- host_type:
- description:
- - This is the type of host to be mapped
- - Required when C(state=present)
- - Either one of the following names can be specified, Linux DM-MP, VMWare, Windows, Windows Clustered, or a
- host type index which can be found in M(netapp_e_facts)
- type: str
- aliases:
- - host_type_index
- ports:
- description:
- - A list of host ports you wish to associate with the host.
- - Host ports are uniquely identified by their WWN or IQN. Their assignments to a particular host are
- uniquely identified by a label and these must be unique.
- required: False
- suboptions:
- type:
- description:
- - The interface type of the port to define.
- - Acceptable choices depend on the capabilities of the target hardware/software platform.
- required: true
- choices:
- - iscsi
- - sas
- - fc
- - ib
- - nvmeof
- - ethernet
- label:
- description:
- - A unique label to assign to this port assignment.
- required: true
- port:
- description:
- - The WWN or IQN of the hostPort to assign to this port definition.
- required: true
- force_port:
- description:
- - Allow ports that are already assigned to be re-assigned to your current host
- required: false
- type: bool
- version_added: 2.7
- group:
- description:
- - The unique identifier of the host-group you want the host to be a member of; this is used for clustering.
- required: False
- aliases:
- - cluster
- log_path:
- description:
- - A local path to a file to be used for debug logging
- required: False
- version_added: 2.7
-"""
-
-EXAMPLES = """
- - name: Define or update an existing host named 'Host1'
- netapp_e_host:
- ssid: "1"
- api_url: "10.113.1.101:8443"
- api_username: admin
- api_password: myPassword
- name: "Host1"
- state: present
- host_type_index: Linux DM-MP
- ports:
- - type: 'iscsi'
- label: 'PORT_1'
- port: 'iqn.1996-04.de.suse:01:56f86f9bd1fe'
- - type: 'fc'
- label: 'FC_1'
- port: '10:00:FF:7C:FF:FF:FF:01'
- - type: 'fc'
- label: 'FC_2'
- port: '10:00:FF:7C:FF:FF:FF:00'
-
- - name: Ensure a host named 'Host2' doesn't exist
- netapp_e_host:
- ssid: "1"
- api_url: "10.113.1.101:8443"
- api_username: admin
- api_password: myPassword
- name: "Host2"
- state: absent
-"""
-
-RETURN = """
-msg:
- description:
- - A user-readable description of the actions performed.
- returned: on success
- type: str
- sample: The host has been created.
-id:
- description:
- - the unique identifier of the host on the E-Series storage-system
- returned: on success when state=present
- type: str
- sample: 00000000600A098000AAC0C3003004700AD86A52
- version_added: "2.6"
-
-ssid:
- description:
- - the unique identifier of the E-Series storage-system with the current api
- returned: on success
- type: str
- sample: 1
- version_added: "2.6"
-
-api_url:
- description:
- - the url of the API that this request was processed by
- returned: on success
- type: str
- sample: https://webservices.example.com:8443
- version_added: "2.6"
-"""
-import json
-import logging
-import re
-from pprint import pformat
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class Host(object):
- HOST_TYPE_INDEXES = {"linux dm-mp": 28, "vmware": 10, "windows": 1, "windows clustered": 8}
-
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- state=dict(type='str', default='present', choices=['absent', 'present']),
- group=dict(type='str', required=False, aliases=['cluster']),
- ports=dict(type='list', required=False),
- force_port=dict(type='bool', default=False),
- name=dict(type='str', required=True, aliases=['label']),
- host_type_index=dict(type='str', aliases=['host_type']),
- log_path=dict(type='str', required=False),
- ))
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
- self.check_mode = self.module.check_mode
- args = self.module.params
- self.group = args['group']
- self.ports = args['ports']
- self.force_port = args['force_port']
- self.name = args['name']
- self.state = args['state']
- self.ssid = args['ssid']
- self.url = args['api_url']
- self.user = args['api_username']
- self.pwd = args['api_password']
- self.certs = args['validate_certs']
-
- self.post_body = dict()
- self.all_hosts = list()
- self.host_obj = dict()
- self.newPorts = list()
- self.portsForUpdate = list()
- self.portsForRemoval = list()
-
- # Update host type with the corresponding index
- host_type = args['host_type_index']
- if host_type:
- host_type = host_type.lower()
- if host_type in [key.lower() for key in list(self.HOST_TYPE_INDEXES.keys())]:
- self.host_type_index = self.HOST_TYPE_INDEXES[host_type]
- elif host_type.isdigit():
- self.host_type_index = int(args['host_type_index'])
- else:
- self.module.fail_json(msg="host_type must be either a host type name or host type index found integer"
- " the documentation.")
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
- if args['log_path']:
- logging.basicConfig(
- level=logging.DEBUG, filename=args['log_path'], filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- # Ensure when state==present then host_type_index is defined
- if self.state == "present" and self.host_type_index is None:
- self.module.fail_json(msg="Host_type_index is required when state=='present'. Array Id: [%s]" % self.ssid)
-
- # Fix port representation if they are provided with colons
- if self.ports is not None:
- for port in self.ports:
- port['label'] = port['label'].lower()
- port['type'] = port['type'].lower()
- port['port'] = port['port'].lower()
-
- # Determine whether address is 16-byte WWPN and, if so, remove
- if re.match(r'^(0x)?[0-9a-f]{16}$', port['port'].replace(':', '')):
- port['port'] = port['port'].replace(':', '').replace('0x', '')
-
- def valid_host_type(self):
- host_types = None
- try:
- (rc, host_types) = request(self.url + 'storage-systems/%s/host-types' % self.ssid, url_password=self.pwd,
- url_username=self.user, validate_certs=self.certs, headers=HEADERS)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to get host types. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
-
- try:
- match = list(filter(lambda host_type: host_type['index'] == self.host_type_index, host_types))[0]
- return True
- except IndexError:
- self.module.fail_json(msg="There is no host type with index %s" % self.host_type_index)
-
- def assigned_host_ports(self, apply_unassigning=False):
- """Determine if the hostPorts requested have already been assigned and return list of required used ports."""
- used_host_ports = {}
- for host in self.all_hosts:
- if host['label'] != self.name:
- for host_port in host['hostSidePorts']:
- for port in self.ports:
- if port['port'] == host_port["address"] or port['label'] == host_port['label']:
- if not self.force_port:
- self.module.fail_json(msg="There are no host ports available OR there are not enough"
- " unassigned host ports")
- else:
- # Determine port reference
- port_ref = [port["hostPortRef"] for port in host["ports"]
- if port["hostPortName"] == host_port["address"]]
- port_ref.extend([port["initiatorRef"] for port in host["initiators"]
- if port["nodeName"]["iscsiNodeName"] == host_port["address"]])
-
- # Create dictionary of hosts containing list of port references
- if host["hostRef"] not in used_host_ports.keys():
- used_host_ports.update({host["hostRef"]: port_ref})
- else:
- used_host_ports[host["hostRef"]].extend(port_ref)
- else:
- for host_port in host['hostSidePorts']:
- for port in self.ports:
- if ((host_port['label'] == port['label'] and host_port['address'] != port['port']) or
- (host_port['label'] != port['label'] and host_port['address'] == port['port'])):
- if not self.force_port:
- self.module.fail_json(msg="There are no host ports available OR there are not enough"
- " unassigned host ports")
- else:
- # Determine port reference
- port_ref = [port["hostPortRef"] for port in host["ports"]
- if port["hostPortName"] == host_port["address"]]
- port_ref.extend([port["initiatorRef"] for port in host["initiators"]
- if port["nodeName"]["iscsiNodeName"] == host_port["address"]])
-
- # Create dictionary of hosts containing list of port references
- if host["hostRef"] not in used_host_ports.keys():
- used_host_ports.update({host["hostRef"]: port_ref})
- else:
- used_host_ports[host["hostRef"]].extend(port_ref)
-
- # Unassign assigned ports
- if apply_unassigning:
- for host_ref in used_host_ports.keys():
- try:
- rc, resp = request(self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, host_ref),
- url_username=self.user, url_password=self.pwd, headers=HEADERS,
- validate_certs=self.certs, method='POST',
- data=json.dumps({"portsToRemove": used_host_ports[host_ref]}))
- except Exception as err:
- self.module.fail_json(msg="Failed to unassign host port. Host Id [%s]. Array Id [%s]. Ports [%s]."
- " Error [%s]." % (self.host_obj['id'], self.ssid,
- used_host_ports[host_ref], to_native(err)))
-
- return used_host_ports
-
- def group_id(self):
- if self.group:
- try:
- (rc, all_groups) = request(self.url + 'storage-systems/%s/host-groups' % self.ssid,
- url_password=self.pwd,
- url_username=self.user, validate_certs=self.certs, headers=HEADERS)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to get host groups. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
-
- try:
- group_obj = list(filter(lambda group: group['name'] == self.group, all_groups))[0]
- return group_obj['id']
- except IndexError:
- self.module.fail_json(msg="No group with the name: %s exists" % self.group)
- else:
- # Return the value equivalent of no group
- return "0000000000000000000000000000000000000000"
-
- def host_exists(self):
- """Determine if the requested host exists
- As a side effect, set the full list of defined hosts in 'all_hosts', and the target host in 'host_obj'.
- """
- match = False
- all_hosts = list()
-
- try:
- (rc, all_hosts) = request(self.url + 'storage-systems/%s/hosts' % self.ssid, url_password=self.pwd,
- url_username=self.user, validate_certs=self.certs, headers=HEADERS)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to determine host existence. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
-
- # Augment the host objects
- for host in all_hosts:
- for port in host['hostSidePorts']:
- port['type'] = port['type'].lower()
- port['address'] = port['address'].lower()
- port['label'] = port['label'].lower()
-
- # Augment hostSidePorts with their ID (this is an omission in the API)
- ports = dict((port['label'], port['id']) for port in host['ports'])
- ports.update((port['label'], port['id']) for port in host['initiators'])
-
- for host_side_port in host['hostSidePorts']:
- if host_side_port['label'] in ports:
- host_side_port['id'] = ports[host_side_port['label']]
-
- if host['label'] == self.name:
- self.host_obj = host
- match = True
-
- self.all_hosts = all_hosts
- return match
-
- def needs_update(self):
- """Determine whether we need to update the Host object
- As a side effect, we will set the ports that we need to update (portsForUpdate), and the ports we need to add
- (newPorts), on self.
- """
- changed = False
- if (self.host_obj["clusterRef"].lower() != self.group_id().lower() or
- self.host_obj["hostTypeIndex"] != self.host_type_index):
- self._logger.info("Either hostType or the clusterRef doesn't match, an update is required.")
- changed = True
- current_host_ports = dict((port["id"], {"type": port["type"], "port": port["address"], "label": port["label"]})
- for port in self.host_obj["hostSidePorts"])
-
- if self.ports:
- for port in self.ports:
- for current_host_port_id in current_host_ports.keys():
- if port == current_host_ports[current_host_port_id]:
- current_host_ports.pop(current_host_port_id)
- break
- elif port["port"] == current_host_ports[current_host_port_id]["port"]:
- if self.port_on_diff_host(port) and not self.force_port:
- self.module.fail_json(msg="The port you specified [%s] is associated with a different host."
- " Specify force_port as True or try a different port spec" % port)
-
- if (port["label"] != current_host_ports[current_host_port_id]["label"] or
- port["type"] != current_host_ports[current_host_port_id]["type"]):
- current_host_ports.pop(current_host_port_id)
- self.portsForUpdate.append({"portRef": current_host_port_id, "port": port["port"],
- "label": port["label"], "hostRef": self.host_obj["hostRef"]})
- break
- else:
- self.newPorts.append(port)
-
- self.portsForRemoval = list(current_host_ports.keys())
- changed = any([self.newPorts, self.portsForUpdate, self.portsForRemoval, changed])
-
- return changed
-
- def port_on_diff_host(self, arg_port):
- """ Checks to see if a passed in port arg is present on a different host """
- for host in self.all_hosts:
- # Only check 'other' hosts
- if host['name'] != self.name:
- for port in host['hostSidePorts']:
- # Check if the port label is found in the port dict list of each host
- if arg_port['label'] == port['label'] or arg_port['port'] == port['address']:
- self.other_host = host
- return True
- return False
-
- def update_host(self):
- self._logger.info("Beginning the update for host=%s.", self.name)
-
- if self.ports:
-
- # Remove ports that need reassigning from their current host.
- self.assigned_host_ports(apply_unassigning=True)
-
- self.post_body["portsToUpdate"] = self.portsForUpdate
- self.post_body["ports"] = self.newPorts
- self._logger.info("Requested ports: %s", pformat(self.ports))
- else:
- self._logger.info("No host ports were defined.")
-
- if self.group:
- self.post_body['groupId'] = self.group_id()
-
- self.post_body['hostType'] = dict(index=self.host_type_index)
-
- api = self.url + 'storage-systems/%s/hosts/%s' % (self.ssid, self.host_obj['id'])
- self._logger.info("POST => url=%s, body=%s.", api, pformat(self.post_body))
-
- if not self.check_mode:
- try:
- (rc, self.host_obj) = request(api, url_username=self.user, url_password=self.pwd, headers=HEADERS,
- validate_certs=self.certs, method='POST', data=json.dumps(self.post_body))
- except Exception as err:
- self.module.fail_json(
- msg="Failed to update host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
-
- payload = self.build_success_payload(self.host_obj)
- self.module.exit_json(changed=True, **payload)
-
- def create_host(self):
- self._logger.info("Creating host definition.")
-
- # Remove ports that need reassigning from their current host.
- self.assigned_host_ports(apply_unassigning=True)
-
- # needs_reassignment = False
- post_body = dict(
- name=self.name,
- hostType=dict(index=self.host_type_index),
- groupId=self.group_id(),
- )
-
- if self.ports:
- post_body.update(ports=self.ports)
-
- api = self.url + "storage-systems/%s/hosts" % self.ssid
- self._logger.info('POST => url=%s, body=%s', api, pformat(post_body))
-
- if not self.check_mode:
- if not self.host_exists():
- try:
- (rc, self.host_obj) = request(api, method='POST', url_username=self.user, url_password=self.pwd, validate_certs=self.certs,
- data=json.dumps(post_body), headers=HEADERS)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to create host. Array Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
- else:
- payload = self.build_success_payload(self.host_obj)
- self.module.exit_json(changed=False, msg="Host already exists. Id [%s]. Host [%s]." % (self.ssid, self.name), **payload)
-
- payload = self.build_success_payload(self.host_obj)
- self.module.exit_json(changed=True, msg='Host created.', **payload)
-
- def remove_host(self):
- try:
- (rc, resp) = request(self.url + "storage-systems/%s/hosts/%s" % (self.ssid, self.host_obj['id']),
- method='DELETE',
- url_username=self.user, url_password=self.pwd, validate_certs=self.certs)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to remove host. Host[%s]. Array Id [%s]. Error [%s]." % (self.host_obj['id'],
- self.ssid,
- to_native(err)))
-
- def build_success_payload(self, host=None):
- keys = ['id']
- if host is not None:
- result = dict((key, host[key]) for key in keys)
- else:
- result = dict()
- result['ssid'] = self.ssid
- result['api_url'] = self.url
- return result
-
- def apply(self):
- if self.state == 'present':
- if self.host_exists():
- if self.needs_update() and self.valid_host_type():
- self.update_host()
- else:
- payload = self.build_success_payload(self.host_obj)
- self.module.exit_json(changed=False, msg="Host already present; no changes required.", **payload)
- elif self.valid_host_type():
- self.create_host()
- else:
- payload = self.build_success_payload()
- if self.host_exists():
- self.remove_host()
- self.module.exit_json(changed=True, msg="Host removed.", **payload)
- else:
- self.module.exit_json(changed=False, msg="Host already absent.", **payload)
-
-
-def main():
- host = Host()
- host.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_hostgroup.py b/lib/ansible/modules/storage/netapp/netapp_e_hostgroup.py
deleted file mode 100644
index 56f1c340a7..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_hostgroup.py
+++ /dev/null
@@ -1,302 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {"metadata_version": "1.1",
- "status": ["preview"],
- "supported_by": "community"}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_hostgroup
-version_added: "2.2"
-short_description: NetApp E-Series manage array host groups
-author:
- - Kevin Hulquest (@hulquest)
- - Nathan Swartz (@ndswartz)
-description: Create, update or destroy host groups on a NetApp E-Series storage array.
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- required: true
- description:
- - Whether the specified host group should exist or not.
- choices: ["present", "absent"]
- name:
- required: false
- description:
- - Name of the host group to manage
- - This option is mutually exclusive with I(id).
- new_name:
- required: false
- description:
- - Specify this when you need to update the name of a host group
- id:
- required: false
- description:
- - Host reference identifier for the host group to manage.
- - This option is mutually exclusive with I(name).
- hosts:
- required: false
- description:
- - List of host names/labels to add to the group
-"""
-EXAMPLES = """
- - name: Configure Hostgroup
- netapp_e_hostgroup:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- state: present
-"""
-RETURN = """
-clusterRef:
- description: The unique identification value for this object. Other objects may use this reference value to refer to the cluster.
- returned: always except when state is absent
- type: str
- sample: "3233343536373839303132333100000000000000"
-confirmLUNMappingCreation:
- description: If true, indicates that creation of LUN-to-volume mappings should require careful confirmation from the end-user, since such a mapping
- will alter the volume access rights of other clusters, in addition to this one.
- returned: always
- type: bool
- sample: false
-hosts:
- description: A list of the hosts that are part of the host group after all operations.
- returned: always except when state is absent
- type: list
- sample: ["HostA","HostB"]
-id:
- description: The id number of the hostgroup
- returned: always except when state is absent
- type: str
- sample: "3233343536373839303132333100000000000000"
-isSAControlled:
- description: If true, indicates that I/O accesses from this cluster are subject to the storage array's default LUN-to-volume mappings. If false,
- indicates that I/O accesses from the cluster are subject to cluster-specific LUN-to-volume mappings.
- returned: always except when state is absent
- type: bool
- sample: false
-label:
- description: The user-assigned, descriptive label string for the cluster.
- returned: always
- type: str
- sample: "MyHostGroup"
-name:
- description: same as label
- returned: always except when state is absent
- type: str
- sample: "MyHostGroup"
-protectionInformationCapableAccessMethod:
- description: This field is true if the host has a PI capable access method.
- returned: always except when state is absent
- type: bool
- sample: true
-"""
-
-from ansible.module_utils.netapp import NetAppESeriesModule
-from ansible.module_utils._text import to_native
-
-
-class NetAppESeriesHostGroup(NetAppESeriesModule):
- EXPANSION_TIMEOUT_SEC = 10
- DEFAULT_DISK_POOL_MINIMUM_DISK_COUNT = 11
-
- def __init__(self):
- version = "02.00.0000.0000"
- ansible_options = dict(
- state=dict(required=True, choices=["present", "absent"], type="str"),
- name=dict(required=False, type="str"),
- new_name=dict(required=False, type="str"),
- id=dict(required=False, type="str"),
- hosts=dict(required=False, type="list"))
- mutually_exclusive = [["name", "id"]]
- super(NetAppESeriesHostGroup, self).__init__(ansible_options=ansible_options,
- web_services_version=version,
- supports_check_mode=True,
- mutually_exclusive=mutually_exclusive)
-
- args = self.module.params
- self.state = args["state"]
- self.name = args["name"]
- self.new_name = args["new_name"]
- self.id = args["id"]
- self.hosts_list = args["hosts"]
-
- self.current_host_group = None
-
- @property
- def hosts(self):
- """Retrieve a list of host reference identifiers should be associated with the host group."""
- host_list = []
- existing_hosts = []
-
- if self.hosts_list:
- try:
- rc, existing_hosts = self.request("storage-systems/%s/hosts" % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve hosts information. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- for host in self.hosts_list:
- for existing_host in existing_hosts:
- if host in existing_host["id"] or host in existing_host["name"]:
- host_list.append(existing_host["id"])
- break
- else:
- self.module.fail_json(msg="Expected host does not exist. Array id [%s]. Host [%s]."
- % (self.ssid, host))
-
- return host_list
-
- @property
- def host_groups(self):
- """Retrieve a list of existing host groups."""
- host_groups = []
- hosts = []
- try:
- rc, host_groups = self.request("storage-systems/%s/host-groups" % self.ssid)
- rc, hosts = self.request("storage-systems/%s/hosts" % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve host group information. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- host_groups = [{"id": group["clusterRef"], "name": group["name"]} for group in host_groups]
- for group in host_groups:
- hosts_ids = []
- for host in hosts:
- if group["id"] == host["clusterRef"]:
- hosts_ids.append(host["hostRef"])
- group.update({"hosts": hosts_ids})
-
- return host_groups
-
- @property
- def current_hosts_in_host_group(self):
- """Retrieve the current hosts associated with the current hostgroup."""
- current_hosts = []
- for group in self.host_groups:
- if (self.name and group["name"] == self.name) or (self.id and group["id"] == self.id):
- current_hosts = group["hosts"]
-
- return current_hosts
-
- def unassign_hosts(self, host_list=None):
- """Unassign hosts from host group."""
- if host_list is None:
- host_list = self.current_host_group["hosts"]
-
- for host_id in host_list:
- try:
- rc, resp = self.request("storage-systems/%s/hosts/%s/move" % (self.ssid, host_id),
- method="POST", data={"group": "0000000000000000000000000000000000000000"})
- except Exception as error:
- self.module.fail_json(msg="Failed to unassign hosts from host group. Array id [%s]. Host id [%s]."
- " Error[%s]." % (self.ssid, host_id, to_native(error)))
-
- def delete_host_group(self, unassign_hosts=True):
- """Delete host group"""
- if unassign_hosts:
- self.unassign_hosts()
-
- try:
- rc, resp = self.request("storage-systems/%s/host-groups/%s" % (self.ssid, self.current_host_group["id"]),
- method="DELETE")
- except Exception as error:
- self.module.fail_json(msg="Failed to delete host group. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- def create_host_group(self):
- """Create host group."""
- data = {"name": self.name, "hosts": self.hosts}
-
- response = None
- try:
- rc, response = self.request("storage-systems/%s/host-groups" % self.ssid, method="POST", data=data)
- except Exception as error:
- self.module.fail_json(msg="Failed to create host group. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- return response
-
- def update_host_group(self):
- """Update host group."""
- data = {"name": self.new_name if self.new_name else self.name,
- "hosts": self.hosts}
-
- # unassign hosts that should not be part of the hostgroup
- desired_host_ids = self.hosts
- for host in self.current_hosts_in_host_group:
- if host not in desired_host_ids:
- self.unassign_hosts([host])
-
- update_response = None
- try:
- rc, update_response = self.request("storage-systems/%s/host-groups/%s"
- % (self.ssid, self.current_host_group["id"]), method="POST", data=data)
- except Exception as error:
- self.module.fail_json(msg="Failed to create host group. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- return update_response
-
- def apply(self):
- """Apply desired host group state to the storage array."""
- changes_required = False
-
- # Search for existing host group match
- for group in self.host_groups:
- if (self.id and group["id"] == self.id) or (self.name and group["name"] == self.name):
- self.current_host_group = group
-
- # Determine whether changes are required
- if self.state == "present":
- if self.current_host_group:
- if (self.new_name and self.new_name != self.name) or self.hosts != self.current_host_group["hosts"]:
- changes_required = True
- else:
- if not self.name:
- self.module.fail_json(msg="The option name must be supplied when creating a new host group."
- " Array id [%s]." % self.ssid)
- changes_required = True
-
- elif self.current_host_group:
- changes_required = True
-
- # Apply any necessary changes
- msg = ""
- if changes_required and not self.module.check_mode:
- msg = "No changes required."
- if self.state == "present":
- if self.current_host_group:
- if ((self.new_name and self.new_name != self.name) or
- (self.hosts != self.current_host_group["hosts"])):
- msg = self.update_host_group()
- else:
- msg = self.create_host_group()
-
- elif self.current_host_group:
- self.delete_host_group()
- msg = "Host group deleted. Array Id [%s]. Host Name [%s]. Host Id [%s]."\
- % (self.ssid, self.current_host_group["name"], self.current_host_group["id"])
-
- self.module.exit_json(msg=msg, changed=changes_required)
-
-
-def main():
- hostgroup = NetAppESeriesHostGroup()
- hostgroup.apply()
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_iscsi_interface.py b/lib/ansible/modules/storage/netapp/netapp_e_iscsi_interface.py
deleted file mode 100644
index de9c357e81..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_iscsi_interface.py
+++ /dev/null
@@ -1,398 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_iscsi_interface
-short_description: NetApp E-Series manage iSCSI interface configuration
-description:
- - Configure settings of an E-Series iSCSI interface
-version_added: '2.7'
-author: Michael Price (@lmprice)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- controller:
- description:
- - The controller that owns the port you want to configure.
- - Controller names are presented alphabetically, with the first controller as A,
- the second as B, and so on.
- - Current hardware models have either 1 or 2 available controllers, but that is not a guaranteed hard
- limitation and could change in the future.
- required: yes
- choices:
- - A
- - B
- name:
- description:
- - The channel of the port to modify the configuration of.
- - The list of choices is not necessarily comprehensive. It depends on the number of ports
- that are available in the system.
- - The numerical value represents the number of the channel (typically from left to right on the HIC),
- beginning with a value of 1.
- required: yes
- aliases:
- - channel
- state:
- description:
- - When enabled, the provided configuration will be utilized.
- - When disabled, the IPv4 configuration will be cleared and IPv4 connectivity disabled.
- choices:
- - enabled
- - disabled
- default: enabled
- address:
- description:
- - The IPv4 address to assign to the interface.
- - Should be specified in xx.xx.xx.xx form.
- - Mutually exclusive with I(config_method=dhcp)
- subnet_mask:
- description:
- - The subnet mask to utilize for the interface.
- - Should be specified in xx.xx.xx.xx form.
- - Mutually exclusive with I(config_method=dhcp)
- gateway:
- description:
- - The IPv4 gateway address to utilize for the interface.
- - Should be specified in xx.xx.xx.xx form.
- - Mutually exclusive with I(config_method=dhcp)
- config_method:
- description:
- - The configuration method type to use for this interface.
- - dhcp is mutually exclusive with I(address), I(subnet_mask), and I(gateway).
- choices:
- - dhcp
- - static
- default: dhcp
- mtu:
- description:
- - The maximum transmission units (MTU), in bytes.
- - This allows you to configure a larger value for the MTU, in order to enable jumbo frames
- (any value > 1500).
- - Generally, it is necessary to have your host, switches, and other components not only support jumbo
- frames, but also have it configured properly. Therefore, unless you know what you're doing, it's best to
- leave this at the default.
- default: 1500
- aliases:
- - max_frame_size
- log_path:
- description:
- - A local path to a file to be used for debug logging
- required: no
-notes:
- - Check mode is supported.
- - The interface settings are applied synchronously, but changes to the interface itself (receiving a new IP address
- via dhcp, etc), can take seconds or minutes longer to take effect.
- - This module will not be useful/usable on an E-Series system without any iSCSI interfaces.
- - This module requires a Web Services API version of >= 1.3.
-"""
-
-EXAMPLES = """
- - name: Configure the first port on the A controller with a static IPv4 address
- netapp_e_iscsi_interface:
- name: "1"
- controller: "A"
- config_method: static
- address: "192.168.1.100"
- subnet_mask: "255.255.255.0"
- gateway: "192.168.1.1"
- ssid: "1"
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
-
- - name: Disable ipv4 connectivity for the second port on the B controller
- netapp_e_iscsi_interface:
- name: "2"
- controller: "B"
- state: disabled
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
-
- - name: Enable jumbo frames for the first 4 ports on controller A
- netapp_e_iscsi_interface:
- name: "{{ item | int }}"
- controller: "A"
- state: enabled
- mtu: 9000
- config_method: dhcp
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- loop:
- - 1
- - 2
- - 3
- - 4
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The interface settings have been updated.
-enabled:
- description:
- - Indicates whether IPv4 connectivity has been enabled or disabled.
- - This does not necessarily indicate connectivity. If dhcp was enabled without a dhcp server, for instance,
- it is unlikely that the configuration will actually be valid.
- returned: on success
- sample: True
- type: bool
-"""
-import json
-import logging
-from pprint import pformat
-import re
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class IscsiInterface(object):
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- controller=dict(type='str', required=True, choices=['A', 'B']),
- name=dict(type='int', aliases=['channel']),
- state=dict(type='str', required=False, default='enabled', choices=['enabled', 'disabled']),
- address=dict(type='str', required=False),
- subnet_mask=dict(type='str', required=False),
- gateway=dict(type='str', required=False),
- config_method=dict(type='str', required=False, default='dhcp', choices=['dhcp', 'static']),
- mtu=dict(type='int', default=1500, required=False, aliases=['max_frame_size']),
- log_path=dict(type='str', required=False),
- ))
-
- required_if = [
- ["config_method", "static", ["address", "subnet_mask"]],
- ]
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, )
- args = self.module.params
- self.controller = args['controller']
- self.name = args['name']
- self.mtu = args['mtu']
- self.state = args['state']
- self.address = args['address']
- self.subnet_mask = args['subnet_mask']
- self.gateway = args['gateway']
- self.config_method = args['config_method']
-
- self.ssid = args['ssid']
- self.url = args['api_url']
- self.creds = dict(url_password=args['api_password'],
- validate_certs=args['validate_certs'],
- url_username=args['api_username'], )
-
- self.check_mode = self.module.check_mode
- self.post_body = dict()
- self.controllers = list()
-
- log_path = args['log_path']
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- if self.mtu < 1500 or self.mtu > 9000:
- self.module.fail_json(msg="The provided mtu is invalid, it must be > 1500 and < 9000 bytes.")
-
- if self.config_method == 'dhcp' and any([self.address, self.subnet_mask, self.gateway]):
- self.module.fail_json(msg='A config_method of dhcp is mutually exclusive with the address,'
- ' subnet_mask, and gateway options.')
-
- # A relatively primitive regex to validate that the input is formatted like a valid ip address
- address_regex = re.compile(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}')
-
- if self.address and not address_regex.match(self.address):
- self.module.fail_json(msg="An invalid ip address was provided for address.")
-
- if self.subnet_mask and not address_regex.match(self.subnet_mask):
- self.module.fail_json(msg="An invalid ip address was provided for subnet_mask.")
-
- if self.gateway and not address_regex.match(self.gateway):
- self.module.fail_json(msg="An invalid ip address was provided for gateway.")
-
- @property
- def interfaces(self):
- ifaces = list()
- try:
- (rc, ifaces) = request(self.url + 'storage-systems/%s/graph/xpath-filter?query=/controller/hostInterfaces'
- % self.ssid, headers=HEADERS, **self.creds)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to retrieve defined host interfaces. Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- # Filter out non-iSCSI interfaces
- ifaces = [iface['iscsi'] for iface in ifaces if iface['interfaceType'] == 'iscsi']
-
- return ifaces
-
- def get_controllers(self):
- """Retrieve a mapping of controller labels to their references
- {
- 'A': '070000000000000000000001',
- 'B': '070000000000000000000002',
- }
- :return: the controllers defined on the system
- """
- controllers = list()
- try:
- (rc, controllers) = request(self.url + 'storage-systems/%s/graph/xpath-filter?query=/controller/id'
- % self.ssid, headers=HEADERS, **self.creds)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to retrieve controller list! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- controllers.sort()
-
- controllers_dict = {}
- i = ord('A')
- for controller in controllers:
- label = chr(i)
- controllers_dict[label] = controller
- i += 1
-
- return controllers_dict
-
- def fetch_target_interface(self):
- interfaces = self.interfaces
-
- for iface in interfaces:
- if iface['channel'] == self.name and self.controllers[self.controller] == iface['controllerId']:
- return iface
-
- channels = sorted(set((str(iface['channel'])) for iface in interfaces
- if self.controllers[self.controller] == iface['controllerId']))
-
- self.module.fail_json(msg="The requested channel of %s is not valid. Valid channels include: %s."
- % (self.name, ", ".join(channels)))
-
- def make_update_body(self, target_iface):
- body = dict(iscsiInterface=target_iface['id'])
- update_required = False
-
- self._logger.info("Requested state=%s.", self.state)
- self._logger.info("config_method: current=%s, requested=%s",
- target_iface['ipv4Data']['ipv4AddressConfigMethod'], self.config_method)
-
- if self.state == 'enabled':
- settings = dict()
- if not target_iface['ipv4Enabled']:
- update_required = True
- settings['ipv4Enabled'] = [True]
- if self.mtu != target_iface['interfaceData']['ethernetData']['maximumFramePayloadSize']:
- update_required = True
- settings['maximumFramePayloadSize'] = [self.mtu]
- if self.config_method == 'static':
- ipv4Data = target_iface['ipv4Data']['ipv4AddressData']
-
- if ipv4Data['ipv4Address'] != self.address:
- update_required = True
- settings['ipv4Address'] = [self.address]
- if ipv4Data['ipv4SubnetMask'] != self.subnet_mask:
- update_required = True
- settings['ipv4SubnetMask'] = [self.subnet_mask]
- if self.gateway is not None and ipv4Data['ipv4GatewayAddress'] != self.gateway:
- update_required = True
- settings['ipv4GatewayAddress'] = [self.gateway]
-
- if target_iface['ipv4Data']['ipv4AddressConfigMethod'] != 'configStatic':
- update_required = True
- settings['ipv4AddressConfigMethod'] = ['configStatic']
-
- elif (target_iface['ipv4Data']['ipv4AddressConfigMethod'] != 'configDhcp'):
- update_required = True
- settings.update(dict(ipv4Enabled=[True],
- ipv4AddressConfigMethod=['configDhcp']))
- body['settings'] = settings
-
- else:
- if target_iface['ipv4Enabled']:
- update_required = True
- body['settings'] = dict(ipv4Enabled=[False])
-
- self._logger.info("Update required ?=%s", update_required)
- self._logger.info("Update body: %s", pformat(body))
-
- return update_required, body
-
- def update(self):
- self.controllers = self.get_controllers()
- if self.controller not in self.controllers:
- self.module.fail_json(msg="The provided controller name is invalid. Valid controllers: %s."
- % ", ".join(self.controllers.keys()))
-
- iface_before = self.fetch_target_interface()
- update_required, body = self.make_update_body(iface_before)
- if update_required and not self.check_mode:
- try:
- url = (self.url +
- 'storage-systems/%s/symbol/setIscsiInterfaceProperties' % self.ssid)
- (rc, result) = request(url, method='POST', data=json.dumps(body), headers=HEADERS, timeout=300,
- ignore_errors=True, **self.creds)
- # We could potentially retry this a few times, but it's probably a rare enough case (unless a playbook
- # is cancelled mid-flight), that it isn't worth the complexity.
- if rc == 422 and result['retcode'] in ['busy', '3']:
- self.module.fail_json(
- msg="The interface is currently busy (probably processing a previously requested modification"
- " request). This operation cannot currently be completed. Array Id [%s]. Error [%s]."
- % (self.ssid, result))
- # Handle authentication issues, etc.
- elif rc != 200:
- self.module.fail_json(
- msg="Failed to modify the interface! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(result)))
- self._logger.debug("Update request completed successfully.")
- # This is going to catch cases like a connection failure
- except Exception as err:
- self.module.fail_json(
- msg="Connection failure: we failed to modify the interface! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- iface_after = self.fetch_target_interface()
-
- self.module.exit_json(msg="The interface settings have been updated.", changed=update_required,
- enabled=iface_after['ipv4Enabled'])
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- iface = IscsiInterface()
- iface()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_iscsi_target.py b/lib/ansible/modules/storage/netapp/netapp_e_iscsi_target.py
deleted file mode 100644
index 2286906f47..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_iscsi_target.py
+++ /dev/null
@@ -1,294 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_iscsi_target
-short_description: NetApp E-Series manage iSCSI target configuration
-description:
- - Configure the settings of an E-Series iSCSI target
-version_added: '2.7'
-author: Michael Price (@lmprice)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- name:
- description:
- - The name/alias to assign to the iSCSI target.
- - This alias is often used by the initiator software in order to make an iSCSI target easier to identify.
- aliases:
- - alias
- ping:
- description:
- - Enable ICMP ping responses from the configured iSCSI ports.
- type: bool
- default: yes
- chap_secret:
- description:
- - Enable Challenge-Handshake Authentication Protocol (CHAP), utilizing this value as the password.
- - When this value is specified, we will always trigger an update (changed=True). We have no way of verifying
- whether or not the password has changed.
- - The chap secret may only use ascii characters with values between 32 and 126 decimal.
- - The chap secret must be no less than 12 characters, but no greater than 57 characters in length.
- - The chap secret is cleared when not specified or an empty string.
- aliases:
- - chap
- - password
- unnamed_discovery:
- description:
- - When an initiator initiates a discovery session to an initiator port, it is considered an unnamed
- discovery session if the iSCSI target iqn is not specified in the request.
- - This option may be disabled to increase security if desired.
- type: bool
- default: yes
- log_path:
- description:
- - A local path (on the Ansible controller), to a file to be used for debug logging.
- required: no
-notes:
- - Check mode is supported.
- - Some of the settings are dependent on the settings applied to the iSCSI interfaces. These can be configured using
- M(netapp_e_iscsi_interface).
- - This module requires a Web Services API version of >= 1.3.
-"""
-
-EXAMPLES = """
- - name: Enable ping responses and unnamed discovery sessions for all iSCSI ports
- netapp_e_iscsi_target:
- api_url: "https://localhost:8443/devmgr/v2"
- api_username: admin
- api_password: myPassword
- ssid: "1"
- validate_certs: no
- name: myTarget
- ping: yes
- unnamed_discovery: yes
-
- - name: Set the target alias and the CHAP secret
- netapp_e_iscsi_target:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- name: myTarget
- chap: password1234
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The iSCSI target settings have been updated.
-alias:
- description:
- - The alias assigned to the iSCSI target.
- returned: on success
- sample: myArray
- type: str
-iqn:
- description:
- - The iqn (iSCSI Qualified Name), assigned to the iSCSI target.
- returned: on success
- sample: iqn.1992-08.com.netapp:2800.000a132000b006d2000000005a0e8f45
- type: str
-"""
-import json
-import logging
-from pprint import pformat
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class IscsiTarget(object):
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- name=dict(type='str', required=False, aliases=['alias']),
- ping=dict(type='bool', required=False, default=True),
- chap_secret=dict(type='str', required=False, aliases=['chap', 'password'], no_log=True),
- unnamed_discovery=dict(type='bool', required=False, default=True),
- log_path=dict(type='str', required=False),
- ))
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, )
- args = self.module.params
-
- self.name = args['name']
- self.ping = args['ping']
- self.chap_secret = args['chap_secret']
- self.unnamed_discovery = args['unnamed_discovery']
-
- self.ssid = args['ssid']
- self.url = args['api_url']
- self.creds = dict(url_password=args['api_password'],
- validate_certs=args['validate_certs'],
- url_username=args['api_username'], )
-
- self.check_mode = self.module.check_mode
- self.post_body = dict()
- self.controllers = list()
-
- log_path = args['log_path']
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- if self.chap_secret:
- if len(self.chap_secret) < 12 or len(self.chap_secret) > 57:
- self.module.fail_json(msg="The provided CHAP secret is not valid, it must be between 12 and 57"
- " characters in length.")
-
- for c in self.chap_secret:
- ordinal = ord(c)
- if ordinal < 32 or ordinal > 126:
- self.module.fail_json(msg="The provided CHAP secret is not valid, it may only utilize ascii"
- " characters with decimal values between 32 and 126.")
-
- @property
- def target(self):
- """Provide information on the iSCSI Target configuration
-
- Sample:
- {
- 'alias': 'myCustomName',
- 'ping': True,
- 'unnamed_discovery': True,
- 'chap': False,
- 'iqn': 'iqn.1992-08.com.netapp:2800.000a132000b006d2000000005a0e8f45',
- }
- """
- target = dict()
- try:
- (rc, data) = request(self.url + 'storage-systems/%s/graph/xpath-filter?query=/storagePoolBundle/target'
- % self.ssid, headers=HEADERS, **self.creds)
- # This likely isn't an iSCSI-enabled system
- if not data:
- self.module.fail_json(
- msg="This storage-system doesn't appear to have iSCSI interfaces. Array Id [%s]." % (self.ssid))
-
- data = data[0]
-
- chap = any(
- [auth for auth in data['configuredAuthMethods']['authMethodData'] if auth['authMethod'] == 'chap'])
-
- target.update(dict(alias=data['alias']['iscsiAlias'],
- iqn=data['nodeName']['iscsiNodeName'],
- chap=chap))
-
- (rc, data) = request(self.url + 'storage-systems/%s/graph/xpath-filter?query=/sa/iscsiEntityData'
- % self.ssid, headers=HEADERS, **self.creds)
-
- data = data[0]
- target.update(dict(ping=data['icmpPingResponseEnabled'],
- unnamed_discovery=data['unnamedDiscoverySessionsEnabled']))
-
- except Exception as err:
- self.module.fail_json(
- msg="Failed to retrieve the iSCSI target information. Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- return target
-
- def apply_iscsi_settings(self):
- """Update the iSCSI target alias and CHAP settings"""
- update = False
- target = self.target
-
- body = dict()
-
- if self.name is not None and self.name != target['alias']:
- update = True
- body['alias'] = self.name
-
- # If the CHAP secret was provided, we trigger an update.
- if self.chap_secret:
- update = True
- body.update(dict(enableChapAuthentication=True,
- chapSecret=self.chap_secret))
- # If no secret was provided, then we disable chap
- elif target['chap']:
- update = True
- body.update(dict(enableChapAuthentication=False))
-
- if update and not self.check_mode:
- try:
- request(self.url + 'storage-systems/%s/iscsi/target-settings' % self.ssid, method='POST',
- data=json.dumps(body), headers=HEADERS, **self.creds)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to update the iSCSI target settings. Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- return update
-
- def apply_target_changes(self):
- update = False
- target = self.target
-
- body = dict()
-
- if self.ping != target['ping']:
- update = True
- body['icmpPingResponseEnabled'] = self.ping
-
- if self.unnamed_discovery != target['unnamed_discovery']:
- update = True
- body['unnamedDiscoverySessionsEnabled'] = self.unnamed_discovery
-
- self._logger.info(pformat(body))
- if update and not self.check_mode:
- try:
- request(self.url + 'storage-systems/%s/iscsi/entity' % self.ssid, method='POST',
- data=json.dumps(body), timeout=60, headers=HEADERS, **self.creds)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to update the iSCSI target settings. Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
- return update
-
- def update(self):
- update = self.apply_iscsi_settings()
- update = self.apply_target_changes() or update
-
- target = self.target
- data = dict((key, target[key]) for key in target if key in ['iqn', 'alias'])
-
- self.module.exit_json(msg="The interface settings have been updated.", changed=update, **data)
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- iface = IscsiTarget()
- iface()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_ldap.py b/lib/ansible/modules/storage/netapp/netapp_e_ldap.py
deleted file mode 100644
index c94b934320..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_ldap.py
+++ /dev/null
@@ -1,390 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
----
-module: netapp_e_ldap
-short_description: NetApp E-Series manage LDAP integration to use for authentication
-description:
- - Configure an E-Series system to allow authentication via an LDAP server
-version_added: '2.7'
-author: Michael Price (@lmprice)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Enable/disable LDAP support on the system. Disabling will clear out any existing defined domains.
- choices:
- - present
- - absent
- default: present
- identifier:
- description:
- - This is a unique identifier for the configuration (for cases where there are multiple domains configured).
- - If this is not specified, but I(state=present), we will utilize a default value of 'default'.
- username:
- description:
- - This is the user account that will be used for querying the LDAP server.
- - "Example: CN=MyBindAcct,OU=ServiceAccounts,DC=example,DC=com"
- required: yes
- aliases:
- - bind_username
- password:
- description:
- - This is the password for the bind user account.
- required: yes
- aliases:
- - bind_password
- attributes:
- description:
- - The user attributes that should be considered for the group to role mapping.
- - Typically this is used with something like 'memberOf', and a user's access is tested against group
- membership or lack thereof.
- default: memberOf
- server:
- description:
- - This is the LDAP server url.
- - The connection string should be specified as using the ldap or ldaps protocol along with the port
- information.
- aliases:
- - server_url
- required: yes
- name:
- description:
- - The domain name[s] that will be utilized when authenticating to identify which domain to utilize.
- - Default to use the DNS name of the I(server).
- - The only requirement is that the name[s] be resolvable.
- - "Example: user@example.com"
- required: no
- search_base:
- description:
- - The search base is used to find group memberships of the user.
- - "Example: ou=users,dc=example,dc=com"
- required: yes
- role_mappings:
- description:
- - This is where you specify which groups should have access to what permissions for the
- storage-system.
- - For example, all users in group A will be assigned all 4 available roles, which will allow access
- to all the management functionality of the system (super-user). Those in group B only have the
- storage.monitor role, which will allow only read-only access.
- - This is specified as a mapping of regular expressions to a list of roles. See the examples.
- - The roles that will be assigned to to the group/groups matching the provided regex.
- - storage.admin allows users full read/write access to storage objects and operations.
- - storage.monitor allows users read-only access to storage objects and operations.
- - support.admin allows users access to hardware, diagnostic information, the Major Event
- Log, and other critical support-related functionality, but not the storage configuration.
- - security.admin allows users access to authentication/authorization configuration, as well
- as the audit log configuration, and certification management.
- required: yes
- user_attribute:
- description:
- - This is the attribute we will use to match the provided username when a user attempts to
- authenticate.
- default: sAMAccountName
- log_path:
- description:
- - A local path to a file to be used for debug logging
- required: no
-notes:
- - Check mode is supported.
- - This module allows you to define one or more LDAP domains identified uniquely by I(identifier) to use for
- authentication. Authorization is determined by I(role_mappings), in that different groups of users may be given
- different (or no), access to certain aspects of the system and API.
- - The local user accounts will still be available if the LDAP server becomes unavailable/inaccessible.
- - Generally, you'll need to get the details of your organization's LDAP server before you'll be able to configure
- the system for using LDAP authentication; every implementation is likely to be very different.
- - This API is currently only supported with the Embedded Web Services API v2.0 and higher, or the Web Services Proxy
- v3.0 and higher.
-'''
-
-EXAMPLES = '''
- - name: Disable LDAP authentication
- netapp_e_ldap:
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
- ssid: "1"
- state: absent
-
- - name: Remove the 'default' LDAP domain configuration
- netapp_e_ldap:
- state: absent
- identifier: default
-
- - name: Define a new LDAP domain, utilizing defaults where possible
- netapp_e_ldap:
- state: present
- bind_username: "CN=MyBindAccount,OU=ServiceAccounts,DC=example,DC=com"
- bind_password: "mySecretPass"
- server: "ldap://example.com:389"
- search_base: 'OU=Users,DC=example,DC=com'
- role_mappings:
- ".*dist-dev-storage.*":
- - storage.admin
- - security.admin
- - support.admin
- - storage.monitor
-'''
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The ldap settings have been updated.
-"""
-
-import json
-import logging
-
-try:
- import urlparse
-except ImportError:
- import urllib.parse as urlparse
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-
-class Ldap(object):
- NO_CHANGE_MSG = "No changes were necessary."
-
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- state=dict(type='str', required=False, default='present',
- choices=['present', 'absent']),
- identifier=dict(type='str', required=False, ),
- username=dict(type='str', required=False, aliases=['bind_username']),
- password=dict(type='str', required=False, aliases=['bind_password'], no_log=True),
- name=dict(type='list', required=False, ),
- server=dict(type='str', required=False, aliases=['server_url']),
- search_base=dict(type='str', required=False, ),
- role_mappings=dict(type='dict', required=False, ),
- user_attribute=dict(type='str', required=False, default='sAMAccountName'),
- attributes=dict(type='list', default=['memberOf'], required=False, ),
- log_path=dict(type='str', required=False),
- ))
-
- required_if = [
- ["state", "present", ["username", "password", "server", "search_base", "role_mappings", ]]
- ]
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if)
- args = self.module.params
- self.ldap = args['state'] == 'present'
- self.identifier = args['identifier']
- self.username = args['username']
- self.password = args['password']
- self.names = args['name']
- self.server = args['server']
- self.search_base = args['search_base']
- self.role_mappings = args['role_mappings']
- self.user_attribute = args['user_attribute']
- self.attributes = args['attributes']
-
- self.ssid = args['ssid']
- self.url = args['api_url']
- self.creds = dict(url_password=args['api_password'],
- validate_certs=args['validate_certs'],
- url_username=args['api_username'],
- timeout=60)
-
- self.check_mode = self.module.check_mode
-
- log_path = args['log_path']
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- self.embedded = None
- self.base_path = None
-
- def make_configuration(self):
- if not self.identifier:
- self.identifier = 'default'
-
- if not self.names:
- parts = urlparse.urlparse(self.server)
- netloc = parts.netloc
- if ':' in netloc:
- netloc = netloc.split(':')[0]
- self.names = [netloc]
-
- roles = list()
- for regex in self.role_mappings:
- for role in self.role_mappings[regex]:
- roles.append(dict(groupRegex=regex,
- ignoreCase=True,
- name=role))
-
- domain = dict(id=self.identifier,
- ldapUrl=self.server,
- bindLookupUser=dict(user=self.username, password=self.password),
- roleMapCollection=roles,
- groupAttributes=self.attributes,
- names=self.names,
- searchBase=self.search_base,
- userAttribute=self.user_attribute,
- )
-
- return domain
-
- def is_embedded(self):
- """Determine whether or not we're using the embedded or proxy implementation of Web Services"""
- if self.embedded is None:
- url = self.url
- try:
- parts = urlparse.urlparse(url)
- parts = parts._replace(path='/devmgr/utils/')
- url = urlparse.urlunparse(parts)
-
- (rc, result) = request(url + 'about', **self.creds)
- self.embedded = not result['runningAsProxy']
- except Exception as err:
- self._logger.exception("Failed to retrieve the About information.")
- self.module.fail_json(msg="Failed to determine the Web Services implementation type!"
- " Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- return self.embedded
-
- def get_full_configuration(self):
- try:
- (rc, result) = request(self.url + self.base_path, **self.creds)
- return result
- except Exception as err:
- self._logger.exception("Failed to retrieve the LDAP configuration.")
- self.module.fail_json(msg="Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def get_configuration(self, identifier):
- try:
- (rc, result) = request(self.url + self.base_path + '%s' % (identifier), ignore_errors=True, **self.creds)
- if rc == 200:
- return result
- elif rc == 404:
- return None
- else:
- self.module.fail_json(msg="Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, result))
- except Exception as err:
- self._logger.exception("Failed to retrieve the LDAP configuration.")
- self.module.fail_json(msg="Failed to retrieve LDAP configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def update_configuration(self):
- # Define a new domain based on the user input
- domain = self.make_configuration()
-
- # This is the current list of configurations
- current = self.get_configuration(self.identifier)
-
- update = current != domain
- msg = "No changes were necessary for [%s]." % self.identifier
- self._logger.info("Is updated: %s", update)
- if update and not self.check_mode:
- msg = "The configuration changes were made for [%s]." % self.identifier
- try:
- if current is None:
- api = self.base_path + 'addDomain'
- else:
- api = self.base_path + '%s' % (domain['id'])
-
- (rc, result) = request(self.url + api, method='POST', data=json.dumps(domain), **self.creds)
- except Exception as err:
- self._logger.exception("Failed to modify the LDAP configuration.")
- self.module.fail_json(msg="Failed to modify LDAP configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- return msg, update
-
- def clear_single_configuration(self, identifier=None):
- if identifier is None:
- identifier = self.identifier
-
- configuration = self.get_configuration(identifier)
- updated = False
- msg = self.NO_CHANGE_MSG
- if configuration:
- updated = True
- msg = "The LDAP domain configuration for [%s] was cleared." % identifier
- if not self.check_mode:
- try:
- (rc, result) = request(self.url + self.base_path + '%s' % identifier, method='DELETE', **self.creds)
- except Exception as err:
- self.module.fail_json(msg="Failed to remove LDAP configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
- return msg, updated
-
- def clear_configuration(self):
- configuration = self.get_full_configuration()
- updated = False
- msg = self.NO_CHANGE_MSG
- if configuration['ldapDomains']:
- updated = True
- msg = "The LDAP configuration for all domains was cleared."
- if not self.check_mode:
- try:
- (rc, result) = request(self.url + self.base_path, method='DELETE', ignore_errors=True, **self.creds)
-
- # Older versions of NetApp E-Series restAPI does not possess an API to remove all existing configs
- if rc == 405:
- for config in configuration['ldapDomains']:
- self.clear_single_configuration(config['id'])
-
- except Exception as err:
- self.module.fail_json(msg="Failed to clear LDAP configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
- return msg, updated
-
- def get_base_path(self):
- embedded = self.is_embedded()
- if embedded:
- return 'storage-systems/%s/ldap/' % self.ssid
- else:
- return '/ldap/'
-
- def update(self):
- self.base_path = self.get_base_path()
-
- if self.ldap:
- msg, update = self.update_configuration()
- elif self.identifier:
- msg, update = self.clear_single_configuration()
- else:
- msg, update = self.clear_configuration()
- self.module.exit_json(msg=msg, changed=update, )
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- settings = Ldap()
- settings()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_lun_mapping.py b/lib/ansible/modules/storage/netapp/netapp_e_lun_mapping.py
deleted file mode 100644
index 643a79710a..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_lun_mapping.py
+++ /dev/null
@@ -1,286 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
----
-module: netapp_e_lun_mapping
-author:
- - Kevin Hulquest (@hulquest)
- - Nathan Swartz (@ndswartz)
-short_description: NetApp E-Series create, delete, or modify lun mappings
-description:
- - Create, delete, or modify mappings between a volume and a targeted host/host+ group.
-version_added: "2.2"
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Present will ensure the mapping exists, absent will remove the mapping.
- required: True
- choices: ["present", "absent"]
- target:
- description:
- - The name of host or hostgroup you wish to assign to the mapping
- - If omitted, the default hostgroup is used.
- - If the supplied I(volume_name) is associated with a different target, it will be updated to what is supplied here.
- required: False
- volume_name:
- description:
- - The name of the volume you wish to include in the mapping.
- required: True
- aliases:
- - volume
- lun:
- description:
- - The LUN value you wish to give the mapping.
- - If the supplied I(volume_name) is associated with a different LUN, it will be updated to what is supplied here.
- - LUN value will be determine by the storage-system when not specified.
- version_added: 2.7
- required: no
- target_type:
- description:
- - This option specifies the whether the target should be a host or a group of hosts
- - Only necessary when the target name is used for both a host and a group of hosts
- choices:
- - host
- - group
- version_added: 2.7
- required: no
-'''
-
-EXAMPLES = '''
----
- - name: Map volume1 to the host target host1
- netapp_e_lun_mapping:
- ssid: 1
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: no
- state: present
- target: host1
- volume: volume1
- - name: Delete the lun mapping between volume1 and host1
- netapp_e_lun_mapping:
- ssid: 1
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: yes
- state: absent
- target: host1
- volume: volume1
-'''
-RETURN = '''
-msg:
- description: success of the module
- returned: always
- type: str
- sample: Lun mapping is complete
-'''
-import json
-import logging
-from pprint import pformat
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json"
-}
-
-
-class LunMapping(object):
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- state=dict(required=True, choices=["present", "absent"]),
- target=dict(required=False, default=None),
- volume_name=dict(required=True, aliases=["volume"]),
- lun=dict(type="int", required=False),
- target_type=dict(required=False, choices=["host", "group"])))
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
- args = self.module.params
-
- self.state = args["state"] in ["present"]
- self.target = args["target"]
- self.volume = args["volume_name"]
- self.lun = args["lun"]
- self.target_type = args["target_type"]
- self.ssid = args["ssid"]
- self.url = args["api_url"]
- self.check_mode = self.module.check_mode
- self.creds = dict(url_username=args["api_username"],
- url_password=args["api_password"],
- validate_certs=args["validate_certs"])
- self.mapping_info = None
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- def update_mapping_info(self):
- """Collect the current state of the storage array."""
- response = None
- try:
- rc, response = request(self.url + "storage-systems/%s/graph" % self.ssid,
- method="GET", headers=HEADERS, **self.creds)
-
- except Exception as error:
- self.module.fail_json(
- msg="Failed to retrieve storage array graph. Id [%s]. Error [%s]" % (self.ssid, to_native(error)))
-
- # Create dictionary containing host/cluster references mapped to their names
- target_reference = {}
- target_name = {}
- target_type = {}
-
- if self.target_type is None or self.target_type == "host":
- for host in response["storagePoolBundle"]["host"]:
- target_reference.update({host["hostRef"]: host["name"]})
- target_name.update({host["name"]: host["hostRef"]})
- target_type.update({host["name"]: "host"})
-
- if self.target_type is None or self.target_type == "group":
- for cluster in response["storagePoolBundle"]["cluster"]:
-
- # Verify there is no ambiguity between target's type (ie host and group has the same name)
- if self.target and self.target_type is None and cluster["name"] == self.target and \
- self.target in target_name.keys():
- self.module.fail_json(msg="Ambiguous target type: target name is used for both host and group"
- " targets! Id [%s]" % self.ssid)
-
- target_reference.update({cluster["clusterRef"]: cluster["name"]})
- target_name.update({cluster["name"]: cluster["clusterRef"]})
- target_type.update({cluster["name"]: "group"})
-
- volume_reference = {}
- volume_name = {}
- lun_name = {}
- for volume in response["volume"]:
- volume_reference.update({volume["volumeRef"]: volume["name"]})
- volume_name.update({volume["name"]: volume["volumeRef"]})
- if volume["listOfMappings"]:
- lun_name.update({volume["name"]: volume["listOfMappings"][0]["lun"]})
- for volume in response["highLevelVolBundle"]["thinVolume"]:
- volume_reference.update({volume["volumeRef"]: volume["name"]})
- volume_name.update({volume["name"]: volume["volumeRef"]})
- if volume["listOfMappings"]:
- lun_name.update({volume["name"]: volume["listOfMappings"][0]["lun"]})
-
- # Build current mapping object
- self.mapping_info = dict(lun_mapping=[dict(volume_reference=mapping["volumeRef"],
- map_reference=mapping["mapRef"],
- lun_mapping_reference=mapping["lunMappingRef"],
- lun=mapping["lun"]
- ) for mapping in response["storagePoolBundle"]["lunMapping"]],
- volume_by_reference=volume_reference,
- volume_by_name=volume_name,
- lun_by_name=lun_name,
- target_by_reference=target_reference,
- target_by_name=target_name,
- target_type_by_name=target_type)
-
- def get_lun_mapping(self):
- """Find the matching lun mapping reference.
-
- Returns: tuple(bool, int, int): contains volume match, volume mapping reference and mapping lun
- """
- target_match = False
- reference = None
- lun = None
-
- self.update_mapping_info()
-
- # Verify that when a lun is specified that it does not match an existing lun value unless it is associated with
- # the specified volume (ie for an update)
- if self.lun and any((self.lun == lun_mapping["lun"] and
- self.target == self.mapping_info["target_by_reference"][lun_mapping["map_reference"]] and
- self.volume != self.mapping_info["volume_by_reference"][lun_mapping["volume_reference"]]
- ) for lun_mapping in self.mapping_info["lun_mapping"]):
- self.module.fail_json(msg="Option lun value is already in use for target! Array Id [%s]." % self.ssid)
-
- # Verify that when target_type is specified then it matches the target's actually type
- if self.target and self.target_type and self.target in self.mapping_info["target_type_by_name"].keys() and \
- self.mapping_info["target_type_by_name"][self.target] != self.target_type:
- self.module.fail_json(
- msg="Option target does not match the specified target_type! Id [%s]." % self.ssid)
-
- # Verify volume and target exist if needed for expected state.
- if self.state:
- if self.volume not in self.mapping_info["volume_by_name"].keys():
- self.module.fail_json(msg="Volume does not exist. Id [%s]." % self.ssid)
- if self.target and self.target not in self.mapping_info["target_by_name"].keys():
- self.module.fail_json(msg="Target does not exist. Id [%s'." % self.ssid)
-
- for lun_mapping in self.mapping_info["lun_mapping"]:
-
- # Find matching volume reference
- if lun_mapping["volume_reference"] == self.mapping_info["volume_by_name"][self.volume]:
- reference = lun_mapping["lun_mapping_reference"]
- lun = lun_mapping["lun"]
-
- # Determine if lun mapping is attached to target with the
- if (lun_mapping["map_reference"] in self.mapping_info["target_by_reference"].keys() and
- self.mapping_info["target_by_reference"][lun_mapping["map_reference"]] == self.target and
- (self.lun is None or lun == self.lun)):
- target_match = True
-
- return target_match, reference, lun
-
- def update(self):
- """Execute the changes the require changes on the storage array."""
- target_match, lun_reference, lun = self.get_lun_mapping()
- update = (self.state and not target_match) or (not self.state and target_match)
-
- if update and not self.check_mode:
- try:
- if self.state:
- body = dict()
- target = None if not self.target else self.mapping_info["target_by_name"][self.target]
- if target:
- body.update(dict(targetId=target))
- if self.lun is not None:
- body.update(dict(lun=self.lun))
-
- if lun_reference:
-
- rc, response = request(self.url + "storage-systems/%s/volume-mappings/%s/move"
- % (self.ssid, lun_reference), method="POST", data=json.dumps(body),
- headers=HEADERS, **self.creds)
- else:
- body.update(dict(mappableObjectId=self.mapping_info["volume_by_name"][self.volume]))
- rc, response = request(self.url + "storage-systems/%s/volume-mappings" % self.ssid,
- method="POST", data=json.dumps(body), headers=HEADERS, **self.creds)
-
- else: # Remove existing lun mapping for volume and target
- rc, response = request(self.url + "storage-systems/%s/volume-mappings/%s"
- % (self.ssid, lun_reference),
- method="DELETE", headers=HEADERS, **self.creds)
- except Exception as error:
- self.module.fail_json(
- msg="Failed to update storage array lun mapping. Id [%s]. Error [%s]"
- % (self.ssid, to_native(error)))
-
- self.module.exit_json(msg="Lun mapping is complete.", changed=update)
-
-
-def main():
- lun_mapping = LunMapping()
- lun_mapping.update()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_mgmt_interface.py b/lib/ansible/modules/storage/netapp/netapp_e_mgmt_interface.py
deleted file mode 100644
index 594479ab03..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_mgmt_interface.py
+++ /dev/null
@@ -1,708 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_mgmt_interface
-short_description: NetApp E-Series management interface configuration
-description:
- - Configure the E-Series management interfaces
-version_added: '2.7'
-author:
- - Michael Price (@lmprice)
- - Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Enable or disable IPv4 network interface configuration.
- - Either IPv4 or IPv6 must be enabled otherwise error will occur.
- - Only required when enabling or disabling IPv4 network interface
- choices:
- - enable
- - disable
- required: no
- aliases:
- - enable_interface
- controller:
- description:
- - The controller that owns the port you want to configure.
- - Controller names are represented alphabetically, with the first controller as A,
- the second as B, and so on.
- - Current hardware models have either 1 or 2 available controllers, but that is not a guaranteed hard
- limitation and could change in the future.
- required: yes
- choices:
- - A
- - B
- name:
- description:
- - The port to modify the configuration for.
- - The list of choices is not necessarily comprehensive. It depends on the number of ports
- that are present in the system.
- - The name represents the port number (typically from left to right on the controller),
- beginning with a value of 1.
- - Mutually exclusive with I(channel).
- aliases:
- - port
- - iface
- channel:
- description:
- - The port to modify the configuration for.
- - The channel represents the port number (typically from left to right on the controller),
- beginning with a value of 1.
- - Mutually exclusive with I(name).
- address:
- description:
- - The IPv4 address to assign to the interface.
- - Should be specified in xx.xx.xx.xx form.
- - Mutually exclusive with I(config_method=dhcp)
- required: no
- subnet_mask:
- description:
- - The subnet mask to utilize for the interface.
- - Should be specified in xx.xx.xx.xx form.
- - Mutually exclusive with I(config_method=dhcp)
- required: no
- gateway:
- description:
- - The IPv4 gateway address to utilize for the interface.
- - Should be specified in xx.xx.xx.xx form.
- - Mutually exclusive with I(config_method=dhcp)
- required: no
- config_method:
- description:
- - The configuration method type to use for network interface ports.
- - dhcp is mutually exclusive with I(address), I(subnet_mask), and I(gateway).
- choices:
- - dhcp
- - static
- required: no
- dns_config_method:
- description:
- - The configuration method type to use for DNS services.
- - dhcp is mutually exclusive with I(dns_address), and I(dns_address_backup).
- choices:
- - dhcp
- - static
- required: no
- dns_address:
- description:
- - Primary IPv4 DNS server address
- required: no
- dns_address_backup:
- description:
- - Backup IPv4 DNS server address
- - Queried when primary DNS server fails
- required: no
- ntp_config_method:
- description:
- - The configuration method type to use for NTP services.
- - disable is mutually exclusive with I(ntp_address) and I(ntp_address_backup).
- - dhcp is mutually exclusive with I(ntp_address) and I(ntp_address_backup).
- choices:
- - disable
- - dhcp
- - static
- required: no
- ntp_address:
- description:
- - Primary IPv4 NTP server address
- required: no
- ntp_address_backup:
- description:
- - Backup IPv4 NTP server address
- - Queried when primary NTP server fails
- required: no
- ssh:
- type: bool
- description:
- - Enable ssh access to the controller for debug purposes.
- - This is a controller-level setting.
- - rlogin/telnet will be enabled for ancient equipment where ssh is not available.
- required: no
- log_path:
- description:
- - A local path to a file to be used for debug logging
- required: no
-notes:
- - Check mode is supported.
- - The interface settings are applied synchronously, but changes to the interface itself (receiving a new IP address
- via dhcp, etc), can take seconds or minutes longer to take effect.
- - "Known issue: Changes specifically to down ports will result in a failure. However, this may not be the case in up
- coming NetApp E-Series firmware releases (released after firmware version 11.40.2)."
-"""
-
-EXAMPLES = """
- - name: Configure the first port on the A controller with a static IPv4 address
- netapp_e_mgmt_interface:
- name: "1"
- controller: "A"
- config_method: static
- address: "192.168.1.100"
- subnet_mask: "255.255.255.0"
- gateway: "192.168.1.1"
- ssid: "1"
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
-
- - name: Disable ipv4 connectivity for the second port on the B controller
- netapp_e_mgmt_interface:
- name: "2"
- controller: "B"
- enable_interface: no
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
-
- - name: Enable ssh access for ports one and two on controller A
- netapp_e_mgmt_interface:
- name: "{{ item }}"
- controller: "A"
- ssh: yes
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- loop:
- - 1
- - 2
-
- - name: Configure static DNS settings for the first port on controller A
- netapp_e_mgmt_interface:
- name: "1"
- controller: "A"
- dns_config_method: static
- dns_address: "192.168.1.100"
- dns_address_backup: "192.168.1.1"
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
-
- - name: Configure static NTP settings for ports one and two on controller B
- netapp_e_mgmt_interface:
- name: "{{ item }}"
- controller: "B"
- ntp_config_method: static
- ntp_address: "129.100.1.100"
- ntp_address_backup: "127.100.1.1"
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- loop:
- - 1
- - 2
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The interface settings have been updated.
-enabled:
- description:
- - Indicates whether IPv4 connectivity has been enabled or disabled.
- - This does not necessarily indicate connectivity. If dhcp was enabled absent a dhcp server, for instance,
- it is unlikely that the configuration will actually be valid.
- returned: on success
- sample: True
- type: bool
-"""
-import json
-import logging
-from pprint import pformat, pprint
-import time
-import socket
-
-try:
- import urlparse
-except ImportError:
- import urllib.parse as urlparse
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class MgmtInterface(object):
- MAX_RETRIES = 15
-
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- state=dict(type="str", choices=["enable", "disable"],
- aliases=["enable_interface"], required=False),
- controller=dict(type="str", required=True, choices=["A", "B"]),
- name=dict(type="str", aliases=["port", "iface"]),
- channel=dict(type="int"),
- address=dict(type="str", required=False),
- subnet_mask=dict(type="str", required=False),
- gateway=dict(type="str", required=False),
- config_method=dict(type="str", required=False, choices=["dhcp", "static"]),
- dns_config_method=dict(type="str", required=False, choices=["dhcp", "static"]),
- dns_address=dict(type="str", required=False),
- dns_address_backup=dict(type="str", required=False),
- ntp_config_method=dict(type="str", required=False, choices=["disable", "dhcp", "static"]),
- ntp_address=dict(type="str", required=False),
- ntp_address_backup=dict(type="str", required=False),
- ssh=dict(type="bool", required=False),
- log_path=dict(type="str", required=False),
- ))
-
- required_if = [
- ["state", "enable", ["config_method"]],
- ["config_method", "static", ["address", "subnet_mask"]],
- ["dns_config_method", "static", ["dns_address"]],
- ["ntp_config_method", "static", ["ntp_address"]],
- ]
-
- mutually_exclusive = [
- ["name", "channel"],
- ]
-
- self.module = AnsibleModule(argument_spec=argument_spec,
- supports_check_mode=True,
- required_if=required_if,
- mutually_exclusive=mutually_exclusive)
- args = self.module.params
-
- self.controller = args["controller"]
- self.name = args["name"]
- self.channel = args["channel"]
-
- self.config_method = args["config_method"]
- self.address = args["address"]
- self.subnet_mask = args["subnet_mask"]
- self.gateway = args["gateway"]
- self.enable_interface = None if args["state"] is None else args["state"] == "enable"
-
- self.dns_config_method = args["dns_config_method"]
- self.dns_address = args["dns_address"]
- self.dns_address_backup = args["dns_address_backup"]
-
- self.ntp_config_method = args["ntp_config_method"]
- self.ntp_address = args["ntp_address"]
- self.ntp_address_backup = args["ntp_address_backup"]
-
- self.ssh = args["ssh"]
-
- self.ssid = args["ssid"]
- self.url = args["api_url"]
- self.creds = dict(url_password=args["api_password"],
- validate_certs=args["validate_certs"],
- url_username=args["api_username"], )
-
- self.retries = 0
-
- self.check_mode = self.module.check_mode
- self.post_body = dict()
-
- log_path = args["log_path"]
-
- # logging setup
- self._logger = logging.getLogger(self.__class__.__name__)
-
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- @property
- def controllers(self):
- """Retrieve a mapping of controller labels to their references
- {
- 'A': '070000000000000000000001',
- 'B': '070000000000000000000002',
- }
- :return: the controllers defined on the system
- """
- try:
- (rc, controllers) = request(self.url + 'storage-systems/%s/controllers'
- % self.ssid, headers=HEADERS, **self.creds)
- except Exception as err:
- controllers = list()
- self.module.fail_json(
- msg="Failed to retrieve the controller settings. Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- controllers.sort(key=lambda c: c['physicalLocation']['slot'])
-
- controllers_dict = dict()
- i = ord('A')
- for controller in controllers:
- label = chr(i)
- settings = dict(controllerSlot=controller['physicalLocation']['slot'],
- controllerRef=controller['controllerRef'],
- ssh=controller['networkSettings']['remoteAccessEnabled'])
- controllers_dict[label] = settings
- i += 1
-
- return controllers_dict
-
- @property
- def interface(self):
- net_interfaces = list()
- try:
- (rc, net_interfaces) = request(self.url + 'storage-systems/%s/configuration/ethernet-interfaces'
- % self.ssid, headers=HEADERS, **self.creds)
- except Exception as err:
- self.module.fail_json(
- msg="Failed to retrieve defined management interfaces. Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- controllers = self.controllers
- controller = controllers[self.controller]
-
- net_interfaces = [iface for iface in net_interfaces if iface["controllerRef"] == controller["controllerRef"]]
-
- # Find the correct interface
- iface = None
- for net in net_interfaces:
- if self.name:
- if net["alias"] == self.name or net["interfaceName"] == self.name:
- iface = net
- break
- elif self.channel:
- if net["channel"] == self.channel:
- iface = net
- break
-
- if iface is None:
- identifier = self.name if self.name is not None else self.channel
- self.module.fail_json(msg="We could not find an interface matching [%s] on Array=[%s]."
- % (identifier, self.ssid))
-
- return dict(alias=iface["alias"],
- channel=iface["channel"],
- link_status=iface["linkStatus"],
- enabled=iface["ipv4Enabled"],
- address=iface["ipv4Address"],
- gateway=iface["ipv4GatewayAddress"],
- subnet_mask=iface["ipv4SubnetMask"],
- dns_config_method=iface["dnsProperties"]["acquisitionProperties"]["dnsAcquisitionType"],
- dns_servers=iface["dnsProperties"]["acquisitionProperties"]["dnsServers"],
- ntp_config_method=iface["ntpProperties"]["acquisitionProperties"]["ntpAcquisitionType"],
- ntp_servers=iface["ntpProperties"]["acquisitionProperties"]["ntpServers"],
- config_method=iface["ipv4AddressConfigMethod"],
- controllerRef=iface["controllerRef"],
- controllerSlot=iface["controllerSlot"],
- ipv6Enabled=iface["ipv6Enabled"],
- id=iface["interfaceRef"], )
-
- def get_enable_interface_settings(self, iface, expected_iface, update, body):
- """Enable or disable the IPv4 network interface."""
- if self.enable_interface:
- if not iface["enabled"]:
- update = True
- body["ipv4Enabled"] = True
- else:
- if iface["enabled"]:
- update = True
- body["ipv4Enabled"] = False
-
- expected_iface["enabled"] = body["ipv4Enabled"]
- return update, expected_iface, body
-
- def get_interface_settings(self, iface, expected_iface, update, body):
- """Update network interface settings."""
-
- if self.config_method == "dhcp":
- if iface["config_method"] != "configDhcp":
- update = True
- body["ipv4AddressConfigMethod"] = "configDhcp"
-
- else:
- if iface["config_method"] != "configStatic":
- update = True
- body["ipv4AddressConfigMethod"] = "configStatic"
-
- if iface["address"] != self.address:
- update = True
- body["ipv4Address"] = self.address
-
- if iface["subnet_mask"] != self.subnet_mask:
- update = True
- body["ipv4SubnetMask"] = self.subnet_mask
-
- if self.gateway and iface["gateway"] != self.gateway:
- update = True
- body["ipv4GatewayAddress"] = self.gateway
-
- expected_iface["address"] = body["ipv4Address"]
- expected_iface["subnet_mask"] = body["ipv4SubnetMask"]
- expected_iface["gateway"] = body["ipv4GatewayAddress"]
-
- expected_iface["config_method"] = body["ipv4AddressConfigMethod"]
-
- return update, expected_iface, body
-
- def get_dns_server_settings(self, iface, expected_iface, update, body):
- """Add DNS server information to the request body."""
- if self.dns_config_method == "dhcp":
- if iface["dns_config_method"] != "dhcp":
- update = True
- body["dnsAcquisitionDescriptor"] = dict(dnsAcquisitionType="dhcp")
-
- elif self.dns_config_method == "static":
- dns_servers = [dict(addressType="ipv4", ipv4Address=self.dns_address)]
- if self.dns_address_backup:
- dns_servers.append(dict(addressType="ipv4", ipv4Address=self.dns_address_backup))
-
- body["dnsAcquisitionDescriptor"] = dict(dnsAcquisitionType="stat", dnsServers=dns_servers)
-
- if (iface["dns_config_method"] != "stat" or
- len(iface["dns_servers"]) != len(dns_servers) or
- (len(iface["dns_servers"]) == 2 and
- (iface["dns_servers"][0]["ipv4Address"] != self.dns_address or
- iface["dns_servers"][1]["ipv4Address"] != self.dns_address_backup)) or
- (len(iface["dns_servers"]) == 1 and
- iface["dns_servers"][0]["ipv4Address"] != self.dns_address)):
- update = True
-
- expected_iface["dns_servers"] = dns_servers
-
- expected_iface["dns_config_method"] = body["dnsAcquisitionDescriptor"]["dnsAcquisitionType"]
- return update, expected_iface, body
-
- def get_ntp_server_settings(self, iface, expected_iface, update, body):
- """Add NTP server information to the request body."""
- if self.ntp_config_method == "disable":
- if iface["ntp_config_method"] != "disabled":
- update = True
- body["ntpAcquisitionDescriptor"] = dict(ntpAcquisitionType="disabled")
-
- elif self.ntp_config_method == "dhcp":
- if iface["ntp_config_method"] != "dhcp":
- update = True
- body["ntpAcquisitionDescriptor"] = dict(ntpAcquisitionType="dhcp")
-
- elif self.ntp_config_method == "static":
- ntp_servers = [dict(addrType="ipvx", ipvxAddress=dict(addressType="ipv4", ipv4Address=self.ntp_address))]
- if self.ntp_address_backup:
- ntp_servers.append(dict(addrType="ipvx",
- ipvxAddress=dict(addressType="ipv4", ipv4Address=self.ntp_address_backup)))
-
- body["ntpAcquisitionDescriptor"] = dict(ntpAcquisitionType="stat", ntpServers=ntp_servers)
-
- if (iface["ntp_config_method"] != "stat" or
- len(iface["ntp_servers"]) != len(ntp_servers) or
- ((len(iface["ntp_servers"]) == 2 and
- (iface["ntp_servers"][0]["ipvxAddress"]["ipv4Address"] != self.ntp_address or
- iface["ntp_servers"][1]["ipvxAddress"]["ipv4Address"] != self.ntp_address_backup)) or
- (len(iface["ntp_servers"]) == 1 and
- iface["ntp_servers"][0]["ipvxAddress"]["ipv4Address"] != self.ntp_address))):
- update = True
-
- expected_iface["ntp_servers"] = ntp_servers
-
- expected_iface["ntp_config_method"] = body["ntpAcquisitionDescriptor"]["ntpAcquisitionType"]
- return update, expected_iface, body
-
- def get_remote_ssh_settings(self, settings, update, body):
- """Configure network interface ports for remote ssh access."""
- if self.ssh != settings["ssh"]:
- update = True
-
- body["enableRemoteAccess"] = self.ssh
- return update, body
-
- def update_array(self, settings, iface):
- """Update controller with new interface, dns service, ntp service and/or remote ssh access information.
-
- :returns: whether information passed will modify the controller's current state
- :rtype: bool
- """
- update = False
- body = dict(controllerRef=settings['controllerRef'],
- interfaceRef=iface['id'])
- expected_iface = iface.copy()
-
- # Check if api url is using the effected management interface to change itself
- update_used_matching_address = False
- if self.enable_interface and self.config_method:
- netloc = list(urlparse.urlparse(self.url))[1]
- address = netloc.split(":")[0]
- address_info = socket.getaddrinfo(address, 8443)
- url_address_info = socket.getaddrinfo(iface["address"], 8443)
- update_used_matching_address = any(info in url_address_info for info in address_info)
-
- self._logger.info("update_used_matching_address: %s", update_used_matching_address)
-
- # Populate the body of the request and check for changes
- if self.enable_interface is not None:
- update, expected_iface, body = self.get_enable_interface_settings(iface, expected_iface, update, body)
-
- if self.config_method is not None:
- update, expected_iface, body = self.get_interface_settings(iface, expected_iface, update, body)
-
- if self.dns_config_method is not None:
- update, expected_iface, body = self.get_dns_server_settings(iface, expected_iface, update, body)
-
- if self.ntp_config_method is not None:
- update, expected_iface, body = self.get_ntp_server_settings(iface, expected_iface, update, body)
-
- if self.ssh is not None:
- update, body = self.get_remote_ssh_settings(settings, update, body)
- iface["ssh"] = self.ssh
- expected_iface["ssh"] = self.ssh
-
- # debug information
- self._logger.info(pformat(body))
- self._logger.info(pformat(iface))
- self._logger.info(pformat(expected_iface))
-
- if self.check_mode:
- return update
-
- if update and not self.check_mode:
- if not update_used_matching_address:
- try:
- (rc, data) = request(self.url + 'storage-systems/%s/configuration/ethernet-interfaces'
- % self.ssid, method='POST', data=json.dumps(body), headers=HEADERS,
- timeout=300, ignore_errors=True, **self.creds)
- if rc == 422:
- if data['retcode'] == "4" or data['retcode'] == "illegalParam":
- if not (body['ipv4Enabled'] or iface['ipv6Enabled']):
- self.module.fail_json(msg="This storage-system already has IPv6 connectivity disabled. "
- "DHCP configuration for IPv4 is required at a minimum."
- " Array Id [%s] Message [%s]."
- % (self.ssid, data['errorMessage']))
- else:
- self.module.fail_json(msg="We failed to configure the management interface. Array Id "
- "[%s] Message [%s]." % (self.ssid, data))
- elif rc >= 300:
- self.module.fail_json(
- msg="We failed to configure the management interface. Array Id [%s] Message [%s]." %
- (self.ssid, data))
-
- # This is going to catch cases like a connection failure
- except Exception as err:
- self.module.fail_json(
- msg="Connection failure: we failed to modify the network settings! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
- else:
- self.update_api_address_interface_match(body)
-
- return self.validate_changes(expected_iface) if update and iface["link_status"] != "up" else update
-
- def update_api_address_interface_match(self, body):
- """Change network interface address which matches the api_address"""
- try:
- try:
- (rc, data) = request(self.url + 'storage-systems/%s/configuration/ethernet-interfaces' % self.ssid,
- use_proxy=False, force=True, ignore_errors=True, method='POST',
- data=json.dumps(body), headers=HEADERS, timeout=10, **self.creds)
- except Exception:
- url_parts = list(urlparse.urlparse(self.url))
- domain = url_parts[1].split(":")
- domain[0] = self.address
- url_parts[1] = ":".join(domain)
- expected_url = urlparse.urlunparse(url_parts)
- self._logger.info(pformat(expected_url))
-
- (rc, data) = request(expected_url + 'storage-systems/%s/configuration/ethernet-interfaces' % self.ssid,
- headers=HEADERS, timeout=300, **self.creds)
- return
- except Exception as err:
- self._logger.info(type(err))
- self.module.fail_json(
- msg="Connection failure: we failed to modify the network settings! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def validate_changes(self, expected_iface, retry=6):
- """Validate interface changes were applied to the controller interface port. 30 second timeout"""
- if self.interface != expected_iface:
- time.sleep(5)
- if retry:
- return self.validate_changes(expected_iface, retry - 1)
-
- self.module.fail_json(msg="Update failure: we failed to verify the necessary state change.")
-
- return True
-
- def check_health(self):
- """It's possible, due to a previous operation, for the API to report a 424 (offline) status for the
- storage-system. Therefore, we run a manual check with retries to attempt to contact the system before we
- continue.
- """
- try:
- (rc, data) = request(self.url + 'storage-systems/%s/controllers'
- % self.ssid, headers=HEADERS,
- ignore_errors=True, **self.creds)
-
- # We've probably recently changed the interface settings and it's still coming back up: retry.
- if rc == 424:
- if self.retries < self.MAX_RETRIES:
- self.retries += 1
- self._logger.info("We hit a 424, retrying in 5s.")
- time.sleep(5)
- self.check_health()
- else:
- self.module.fail_json(
- msg="We failed to pull storage-system information. Array Id [%s] Message [%s]." %
- (self.ssid, data))
- elif rc >= 300:
- self.module.fail_json(
- msg="We failed to pull storage-system information. Array Id [%s] Message [%s]." %
- (self.ssid, data))
- # This is going to catch cases like a connection failure
- except Exception as err:
- if self.retries < self.MAX_RETRIES:
- self._logger.info("We hit a connection failure, retrying in 5s.")
- self.retries += 1
- time.sleep(5)
- self.check_health()
- else:
- self.module.fail_json(
- msg="Connection failure: we failed to modify the network settings! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def update(self):
- """Update storage system with necessary changes."""
- # Check if the storage array can be contacted
- self.check_health()
-
- # make the necessary changes to the storage system
- settings = self.controllers[self.controller]
- iface = self.interface
- self._logger.info(pformat(settings))
- self._logger.info(pformat(iface))
- update = self.update_array(settings, iface)
-
- self.module.exit_json(msg="The interface settings have been updated.", changed=update)
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- iface = MgmtInterface()
- iface()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_snapshot_group.py b/lib/ansible/modules/storage/netapp/netapp_e_snapshot_group.py
deleted file mode 100644
index 0b678fe818..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_snapshot_group.py
+++ /dev/null
@@ -1,370 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_snapshot_group
-short_description: NetApp E-Series manage snapshot groups
-description:
- - Create, update, delete snapshot groups for NetApp E-series storage arrays
-version_added: '2.2'
-author: Kevin Hulquest (@hulquest)
-options:
- api_username:
- required: true
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_password:
- required: true
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_url:
- required: true
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API.
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- type: bool
- state:
- description:
- - Whether to ensure the group is present or absent.
- required: True
- choices:
- - present
- - absent
- name:
- description:
- - The name to give the snapshot group
- required: True
- base_volume_name:
- description:
- - The name of the base volume or thin volume to use as the base for the new snapshot group.
- - If a snapshot group with an identical C(name) already exists but with a different base volume
- an error will be returned.
- required: True
- repo_pct:
- description:
- - The size of the repository in relation to the size of the base volume
- required: False
- default: 20
- warning_threshold:
- description:
- - The repository utilization warning threshold, as a percentage of the repository volume capacity.
- required: False
- default: 80
- delete_limit:
- description:
- - The automatic deletion indicator.
- - If non-zero, the oldest snapshot image will be automatically deleted when creating a new snapshot image to keep the total number of
- snapshot images limited to the number specified.
- - This value is overridden by the consistency group setting if this snapshot group is associated with a consistency group.
- required: False
- default: 30
- full_policy:
- description:
- - The behavior on when the data repository becomes full.
- - This value is overridden by consistency group setting if this snapshot group is associated with a consistency group
- required: False
- default: purgepit
- choices:
- - purgepit
- - unknown
- - failbasewrites
- - __UNDEFINED
- storage_pool_name:
- required: True
- description:
- - The name of the storage pool on which to allocate the repository volume.
- rollback_priority:
- required: False
- description:
- - The importance of the rollback operation.
- - This value is overridden by consistency group setting if this snapshot group is associated with a consistency group
- choices:
- - highest
- - high
- - medium
- - low
- - lowest
- - __UNDEFINED
- default: medium
-"""
-
-EXAMPLES = """
- - name: Configure Snapshot group
- netapp_e_snapshot_group:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- base_volume_name: SSGroup_test
- name=: OOSS_Group
- repo_pct: 20
- warning_threshold: 85
- delete_limit: 30
- full_policy: purgepit
- storage_pool_name: Disk_Pool_1
- rollback_priority: medium
-"""
-RETURN = """
-msg:
- description: Success message
- returned: success
- type: str
- sample: json facts for newly created snapshot group.
-"""
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-import json
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-
-from ansible.module_utils._text import to_native
-from ansible.module_utils.urls import open_url
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as err:
- r = err.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-class SnapshotGroup(object):
- def __init__(self):
-
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(
- api_username=dict(type='str', required=True),
- api_password=dict(type='str', required=True, no_log=True),
- api_url=dict(type='str', required=True),
- state=dict(required=True, choices=['present', 'absent']),
- base_volume_name=dict(required=True),
- name=dict(required=True),
- repo_pct=dict(default=20, type='int'),
- warning_threshold=dict(default=80, type='int'),
- delete_limit=dict(default=30, type='int'),
- full_policy=dict(default='purgepit', choices=['unknown', 'failbasewrites', 'purgepit']),
- rollback_priority=dict(default='medium', choices=['highest', 'high', 'medium', 'low', 'lowest']),
- storage_pool_name=dict(type='str'),
- ssid=dict(required=True),
- )
-
- self.module = AnsibleModule(argument_spec=argument_spec)
-
- self.post_data = dict()
- self.warning_threshold = self.module.params['warning_threshold']
- self.base_volume_name = self.module.params['base_volume_name']
- self.name = self.module.params['name']
- self.repo_pct = self.module.params['repo_pct']
- self.delete_limit = self.module.params['delete_limit']
- self.full_policy = self.module.params['full_policy']
- self.rollback_priority = self.module.params['rollback_priority']
- self.storage_pool_name = self.module.params['storage_pool_name']
- self.state = self.module.params['state']
-
- self.url = self.module.params['api_url']
- self.user = self.module.params['api_username']
- self.pwd = self.module.params['api_password']
- self.certs = self.module.params['validate_certs']
- self.ssid = self.module.params['ssid']
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- self.changed = False
-
- @property
- def pool_id(self):
- pools = 'storage-systems/%s/storage-pools' % self.ssid
- url = self.url + pools
- try:
- (rc, data) = request(url, headers=HEADERS, url_username=self.user, url_password=self.pwd)
- except Exception as err:
- self.module.fail_json(msg="Snapshot group module - Failed to fetch storage pools. " +
- "Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
-
- for pool in data:
- if pool['name'] == self.storage_pool_name:
- self.pool_data = pool
- return pool['id']
-
- self.module.fail_json(msg="No storage pool with the name: '%s' was found" % self.name)
-
- @property
- def volume_id(self):
- volumes = 'storage-systems/%s/volumes' % self.ssid
- url = self.url + volumes
- try:
- rc, data = request(url, headers=HEADERS, url_username=self.user, url_password=self.pwd,
- validate_certs=self.certs)
- except Exception as err:
- self.module.fail_json(msg="Snapshot group module - Failed to fetch volumes. " +
- "Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
- qty = 0
- for volume in data:
- if volume['name'] == self.base_volume_name:
- qty += 1
-
- if qty > 1:
- self.module.fail_json(msg="More than one volume with the name: %s was found, "
- "please ensure your volume has a unique name" % self.base_volume_name)
- else:
- Id = volume['id']
- self.volume = volume
-
- try:
- return Id
- except NameError:
- self.module.fail_json(msg="No volume with the name: %s, was found" % self.base_volume_name)
-
- @property
- def snapshot_group_id(self):
- url = self.url + 'storage-systems/%s/snapshot-groups' % self.ssid
- try:
- rc, data = request(url, headers=HEADERS, url_username=self.user, url_password=self.pwd,
- validate_certs=self.certs)
- except Exception as err:
- self.module.fail_json(msg="Failed to fetch snapshot groups. " +
- "Id [%s]. Error [%s]." % (self.ssid, to_native(err)))
- for ssg in data:
- if ssg['name'] == self.name:
- self.ssg_data = ssg
- return ssg['id']
-
- return None
-
- @property
- def ssg_needs_update(self):
- if self.ssg_data['fullWarnThreshold'] != self.warning_threshold or \
- self.ssg_data['autoDeleteLimit'] != self.delete_limit or \
- self.ssg_data['repFullPolicy'] != self.full_policy or \
- self.ssg_data['rollbackPriority'] != self.rollback_priority:
- return True
- else:
- return False
-
- def create_snapshot_group(self):
- self.post_data = dict(
- baseMappableObjectId=self.volume_id,
- name=self.name,
- repositoryPercentage=self.repo_pct,
- warningThreshold=self.warning_threshold,
- autoDeleteLimit=self.delete_limit,
- fullPolicy=self.full_policy,
- storagePoolId=self.pool_id,
- )
- snapshot = 'storage-systems/%s/snapshot-groups' % self.ssid
- url = self.url + snapshot
- try:
- rc, self.ssg_data = request(url, data=json.dumps(self.post_data), method='POST', headers=HEADERS,
- url_username=self.user, url_password=self.pwd, validate_certs=self.certs)
- except Exception as err:
- self.module.fail_json(msg="Failed to create snapshot group. " +
- "Snapshot group [%s]. Id [%s]. Error [%s]." % (self.name,
- self.ssid,
- to_native(err)))
-
- if not self.snapshot_group_id:
- self.snapshot_group_id = self.ssg_data['id']
-
- if self.ssg_needs_update:
- self.update_ssg()
- else:
- self.module.exit_json(changed=True, **self.ssg_data)
-
- def update_ssg(self):
- self.post_data = dict(
- warningThreshold=self.warning_threshold,
- autoDeleteLimit=self.delete_limit,
- fullPolicy=self.full_policy,
- rollbackPriority=self.rollback_priority
- )
-
- url = self.url + "storage-systems/%s/snapshot-groups/%s" % (self.ssid, self.snapshot_group_id)
- try:
- rc, self.ssg_data = request(url, data=json.dumps(self.post_data), method='POST', headers=HEADERS,
- url_username=self.user, url_password=self.pwd, validate_certs=self.certs)
- except Exception as err:
- self.module.fail_json(msg="Failed to update snapshot group. " +
- "Snapshot group [%s]. Id [%s]. Error [%s]." % (self.name,
- self.ssid,
- to_native(err)))
-
- def apply(self):
- if self.state == 'absent':
- if self.snapshot_group_id:
- try:
- rc, resp = request(
- self.url + 'storage-systems/%s/snapshot-groups/%s' % (self.ssid, self.snapshot_group_id),
- method='DELETE', headers=HEADERS, url_password=self.pwd, url_username=self.user,
- validate_certs=self.certs)
- except Exception as err:
- self.module.fail_json(msg="Failed to delete snapshot group. " +
- "Snapshot group [%s]. Id [%s]. Error [%s]." % (self.name,
- self.ssid,
- to_native(err)))
- self.module.exit_json(changed=True, msg="Snapshot group removed", **self.ssg_data)
- else:
- self.module.exit_json(changed=False, msg="Snapshot group absent")
-
- elif self.snapshot_group_id:
- if self.ssg_needs_update:
- self.update_ssg()
- self.module.exit_json(changed=True, **self.ssg_data)
- else:
- self.module.exit_json(changed=False, **self.ssg_data)
- else:
- self.create_snapshot_group()
-
-
-def main():
- vg = SnapshotGroup()
- vg.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_snapshot_images.py b/lib/ansible/modules/storage/netapp/netapp_e_snapshot_images.py
deleted file mode 100644
index c56b66c960..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_snapshot_images.py
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_snapshot_images
-short_description: NetApp E-Series create and delete snapshot images
-description:
- - Create and delete snapshots images on snapshot groups for NetApp E-series storage arrays.
- - Only the oldest snapshot image can be deleted so consistency is preserved.
- - "Related: Snapshot volumes are created from snapshot images."
-version_added: '2.2'
-author: Kevin Hulquest (@hulquest)
-options:
- api_username:
- required: true
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_password:
- required: true
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_url:
- required: true
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API.
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- snapshot_group:
- description:
- - The name of the snapshot group in which you want to create a snapshot image.
- required: True
- state:
- description:
- - Whether a new snapshot image should be created or oldest be deleted.
- required: True
- choices: ['create', 'remove']
-"""
-EXAMPLES = """
- - name: Create Snapshot
- netapp_e_snapshot_images:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ validate_certs }}"
- snapshot_group: "3300000060080E5000299C24000005B656D9F394"
- state: 'create'
-"""
-RETURN = """
----
- msg:
- description: State of operation
- type: str
- returned: always
- sample: "Created snapshot image"
- image_id:
- description: ID of snapshot image
- type: str
- returned: state == created
- sample: "3400000060080E5000299B640063074057BC5C5E "
-"""
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-import json
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-
-from ansible.module_utils._text import to_native
-from ansible.module_utils.urls import open_url
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as err:
- r = err.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-def snapshot_group_from_name(module, ssid, api_url, api_pwd, api_usr, name):
- snap_groups = 'storage-systems/%s/snapshot-groups' % ssid
- snap_groups_url = api_url + snap_groups
- (ret, snapshot_groups) = request(snap_groups_url, url_username=api_usr, url_password=api_pwd, headers=HEADERS,
- validate_certs=module.params['validate_certs'])
-
- snapshot_group_id = None
- for snapshot_group in snapshot_groups:
- if name == snapshot_group['label']:
- snapshot_group_id = snapshot_group['pitGroupRef']
- break
- if snapshot_group_id is None:
- module.fail_json(msg="Failed to lookup snapshot group. Group [%s]. Id [%s]." % (name, ssid))
-
- return snapshot_group
-
-
-def oldest_image(module, ssid, api_url, api_pwd, api_usr, name):
- get_status = 'storage-systems/%s/snapshot-images' % ssid
- url = api_url + get_status
-
- try:
- (ret, images) = request(url, url_username=api_usr, url_password=api_pwd, headers=HEADERS,
- validate_certs=module.params['validate_certs'])
- except Exception as err:
- module.fail_json(msg="Failed to get snapshot images for group. Group [%s]. Id [%s]. Error [%s]" %
- (name, ssid, to_native(err)))
- if not images:
- module.exit_json(msg="There are no snapshot images to remove. Group [%s]. Id [%s]." % (name, ssid))
-
- oldest = min(images, key=lambda x: x['pitSequenceNumber'])
- if oldest is None or "pitRef" not in oldest:
- module.fail_json(msg="Failed to lookup oldest snapshot group. Group [%s]. Id [%s]." % (name, ssid))
-
- return oldest
-
-
-def create_image(module, ssid, api_url, pwd, user, p, snapshot_group):
- snapshot_group_obj = snapshot_group_from_name(module, ssid, api_url, pwd, user, snapshot_group)
- snapshot_group_id = snapshot_group_obj['pitGroupRef']
- endpoint = 'storage-systems/%s/snapshot-images' % ssid
- url = api_url + endpoint
- post_data = json.dumps({'groupId': snapshot_group_id})
-
- image_data = request(url, data=post_data, method='POST', url_username=user, url_password=pwd, headers=HEADERS,
- validate_certs=module.params['validate_certs'])
-
- if image_data[1]['status'] == 'optimal':
- status = True
- id = image_data[1]['id']
- else:
- status = False
- id = ''
-
- return status, id
-
-
-def delete_image(module, ssid, api_url, pwd, user, snapshot_group):
- image = oldest_image(module, ssid, api_url, pwd, user, snapshot_group)
- image_id = image['pitRef']
- endpoint = 'storage-systems/%s/snapshot-images/%s' % (ssid, image_id)
- url = api_url + endpoint
-
- try:
- (ret, image_data) = request(url, method='DELETE', url_username=user, url_password=pwd, headers=HEADERS,
- validate_certs=module.params['validate_certs'])
- except Exception as e:
- image_data = (e[0], e[1])
-
- if ret == 204:
- deleted_status = True
- error_message = ''
- else:
- deleted_status = False
- error_message = image_data[1]['errorMessage']
-
- return deleted_status, error_message
-
-
-def main():
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- snapshot_group=dict(required=True, type='str'),
- ssid=dict(required=True, type='str'),
- api_url=dict(required=True),
- api_username=dict(required=False),
- api_password=dict(required=False, no_log=True),
- validate_certs=dict(required=False, default=True),
- state=dict(required=True, choices=['create', 'remove'], type='str'),
- ))
- module = AnsibleModule(argument_spec)
-
- p = module.params
-
- ssid = p.pop('ssid')
- api_url = p.pop('api_url')
- user = p.pop('api_username')
- pwd = p.pop('api_password')
- snapshot_group = p.pop('snapshot_group')
- desired_state = p.pop('state')
-
- if not api_url.endswith('/'):
- api_url += '/'
-
- if desired_state == 'create':
- created_status, snapshot_id = create_image(module, ssid, api_url, pwd, user, p, snapshot_group)
-
- if created_status:
- module.exit_json(changed=True, msg='Created snapshot image', image_id=snapshot_id)
- else:
- module.fail_json(
- msg="Could not create snapshot image on system %s, in snapshot group %s" % (ssid, snapshot_group))
- else:
- deleted, error_msg = delete_image(module, ssid, api_url, pwd, user, snapshot_group)
-
- if deleted:
- module.exit_json(changed=True, msg='Deleted snapshot image for snapshot group [%s]' % (snapshot_group))
- else:
- module.fail_json(
- msg="Could not create snapshot image on system %s, in snapshot group %s --- %s" % (
- ssid, snapshot_group, error_msg))
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_snapshot_volume.py b/lib/ansible/modules/storage/netapp/netapp_e_snapshot_volume.py
deleted file mode 100644
index 38e9fa89c0..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_snapshot_volume.py
+++ /dev/null
@@ -1,281 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_snapshot_volume
-short_description: NetApp E-Series manage snapshot volumes.
-description:
- - Create, update, remove snapshot volumes for NetApp E/EF-Series storage arrays.
-version_added: '2.2'
-author: Kevin Hulquest (@hulquest)
-notes:
- - Only I(full_threshold) is supported for update operations. If the snapshot volume already exists and the threshold matches, then an C(ok) status
- will be returned, no other changes can be made to a pre-existing snapshot volume.
-options:
- api_username:
- required: true
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_password:
- required: true
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_url:
- required: true
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API.
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- type: bool
- ssid:
- description:
- - storage array ID
- required: True
- snapshot_image_id:
- required: True
- description:
- - The identifier of the snapshot image used to create the new snapshot volume.
- - "Note: You'll likely want to use the M(netapp_e_facts) module to find the ID of the image you want."
- full_threshold:
- description:
- - The repository utilization warning threshold percentage
- default: 85
- name:
- required: True
- description:
- - The name you wish to give the snapshot volume
- view_mode:
- required: True
- description:
- - The snapshot volume access mode
- choices:
- - modeUnknown
- - readWrite
- - readOnly
- - __UNDEFINED
- repo_percentage:
- description:
- - The size of the view in relation to the size of the base volume
- default: 20
- storage_pool_name:
- description:
- - Name of the storage pool on which to allocate the repository volume.
- required: True
- state:
- description:
- - Whether to create or remove the snapshot volume
- required: True
- choices:
- - absent
- - present
-"""
-EXAMPLES = """
- - name: Snapshot volume
- netapp_e_snapshot_volume:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}/"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- state: present
- storage_pool_name: "{{ snapshot_volume_storage_pool_name }}"
- snapshot_image_id: "{{ snapshot_volume_image_id }}"
- name: "{{ snapshot_volume_name }}"
-"""
-RETURN = """
-msg:
- description: Success message
- returned: success
- type: str
- sample: Json facts for the volume that was created.
-"""
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-import json
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-
-from ansible.module_utils.urls import open_url
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as err:
- r = err.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-class SnapshotVolume(object):
- def __init__(self):
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- api_username=dict(type='str', required=True),
- api_password=dict(type='str', required=True, no_log=True),
- api_url=dict(type='str', required=True),
- ssid=dict(type='str', required=True),
- snapshot_image_id=dict(type='str', required=True),
- full_threshold=dict(type='int', default=85),
- name=dict(type='str', required=True),
- view_mode=dict(type='str', default='readOnly',
- choices=['readOnly', 'readWrite', 'modeUnknown', '__Undefined']),
- repo_percentage=dict(type='int', default=20),
- storage_pool_name=dict(type='str', required=True),
- state=dict(type='str', required=True, choices=['absent', 'present'])
- ))
-
- self.module = AnsibleModule(argument_spec=argument_spec)
- args = self.module.params
- self.state = args['state']
- self.ssid = args['ssid']
- self.snapshot_image_id = args['snapshot_image_id']
- self.full_threshold = args['full_threshold']
- self.name = args['name']
- self.view_mode = args['view_mode']
- self.repo_percentage = args['repo_percentage']
- self.storage_pool_name = args['storage_pool_name']
- self.url = args['api_url']
- self.user = args['api_username']
- self.pwd = args['api_password']
- self.certs = args['validate_certs']
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- @property
- def pool_id(self):
- pools = 'storage-systems/%s/storage-pools' % self.ssid
- url = self.url + pools
- (rc, data) = request(url, headers=HEADERS, url_username=self.user, url_password=self.pwd,
- validate_certs=self.certs)
-
- for pool in data:
- if pool['name'] == self.storage_pool_name:
- self.pool_data = pool
- return pool['id']
-
- self.module.fail_json(msg="No storage pool with the name: '%s' was found" % self.name)
-
- @property
- def ss_vol_exists(self):
- rc, ss_vols = request(self.url + 'storage-systems/%s/snapshot-volumes' % self.ssid, headers=HEADERS,
- url_username=self.user, url_password=self.pwd, validate_certs=self.certs)
- if ss_vols:
- for ss_vol in ss_vols:
- if ss_vol['name'] == self.name:
- self.ss_vol = ss_vol
- return True
- else:
- return False
-
- return False
-
- @property
- def ss_vol_needs_update(self):
- if self.ss_vol['fullWarnThreshold'] != self.full_threshold:
- return True
- else:
- return False
-
- def create_ss_vol(self):
- post_data = dict(
- snapshotImageId=self.snapshot_image_id,
- fullThreshold=self.full_threshold,
- name=self.name,
- viewMode=self.view_mode,
- repositoryPercentage=self.repo_percentage,
- repositoryPoolId=self.pool_id
- )
-
- rc, create_resp = request(self.url + 'storage-systems/%s/snapshot-volumes' % self.ssid,
- data=json.dumps(post_data), headers=HEADERS, url_username=self.user,
- url_password=self.pwd, validate_certs=self.certs, method='POST')
-
- self.ss_vol = create_resp
- # Doing a check after creation because the creation call fails to set the specified warning threshold
- if self.ss_vol_needs_update:
- self.update_ss_vol()
- else:
- self.module.exit_json(changed=True, **create_resp)
-
- def update_ss_vol(self):
- post_data = dict(
- fullThreshold=self.full_threshold,
- )
-
- rc, resp = request(self.url + 'storage-systems/%s/snapshot-volumes/%s' % (self.ssid, self.ss_vol['id']),
- data=json.dumps(post_data), headers=HEADERS, url_username=self.user, url_password=self.pwd,
- method='POST', validate_certs=self.certs)
-
- self.module.exit_json(changed=True, **resp)
-
- def remove_ss_vol(self):
- rc, resp = request(self.url + 'storage-systems/%s/snapshot-volumes/%s' % (self.ssid, self.ss_vol['id']),
- headers=HEADERS, url_username=self.user, url_password=self.pwd, validate_certs=self.certs,
- method='DELETE')
- self.module.exit_json(changed=True, msg="Volume successfully deleted")
-
- def apply(self):
- if self.state == 'present':
- if self.ss_vol_exists:
- if self.ss_vol_needs_update:
- self.update_ss_vol()
- else:
- self.module.exit_json(changed=False, **self.ss_vol)
- else:
- self.create_ss_vol()
- else:
- if self.ss_vol_exists:
- self.remove_ss_vol()
- else:
- self.module.exit_json(changed=False, msg="Volume already absent")
-
-
-def main():
- sv = SnapshotVolume()
- sv.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_storage_system.py b/lib/ansible/modules/storage/netapp/netapp_e_storage_system.py
deleted file mode 100644
index 48ce3353b8..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_storage_system.py
+++ /dev/null
@@ -1,296 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-module: netapp_e_storage_system
-version_added: "2.2"
-short_description: NetApp E-Series Web Services Proxy manage storage arrays
-description:
-- Manage the arrays accessible via a NetApp Web Services Proxy for NetApp E-series storage arrays.
-options:
- api_username:
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- required: true
- api_password:
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- required: true
- api_url:
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API.
- required: true
- validate_certs:
- description:
- - Should https certificates be validated?
- type: bool
- default: 'yes'
- ssid:
- description:
- - The ID of the array to manage. This value must be unique for each array.
- required: true
- state:
- description:
- - Whether the specified array should be configured on the Web Services Proxy or not.
- required: true
- choices: ['present', 'absent']
- controller_addresses:
- description:
- - The list addresses for the out-of-band management adapter or the agent host. Mutually exclusive of array_wwn parameter.
- required: true
- array_wwn:
- description:
- - The WWN of the array to manage. Only necessary if in-band managing multiple arrays on the same agent host. Mutually exclusive of
- controller_addresses parameter.
- array_password:
- description:
- - The management password of the array to manage, if set.
- enable_trace:
- description:
- - Enable trace logging for SYMbol calls to the storage system.
- type: bool
- default: 'no'
- meta_tags:
- description:
- - Optional meta tags to associate to this storage system
-author: Kevin Hulquest (@hulquest)
-'''
-
-EXAMPLES = '''
----
- - name: Presence of storage system
- netapp_e_storage_system:
- ssid: "{{ item.key }}"
- state: present
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- controller_addresses:
- - "{{ item.value.address1 }}"
- - "{{ item.value.address2 }}"
- with_dict: "{{ storage_systems }}"
- when: check_storage_system
-'''
-
-RETURN = '''
-msg:
- description: State of request
- type: str
- returned: always
- sample: 'Storage system removed.'
-'''
-import json
-from datetime import datetime as dt, timedelta
-from time import sleep
-
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-from ansible.module_utils.urls import open_url
-from ansible.module_utils.six.moves.urllib.error import HTTPError
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as err:
- r = err.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-def do_post(ssid, api_url, post_headers, api_usr, api_pwd, validate_certs, request_body, timeout):
- (rc, resp) = request(api_url + "/storage-systems", data=request_body, headers=post_headers,
- method='POST', url_username=api_usr, url_password=api_pwd,
- validate_certs=validate_certs)
- status = None
- return_resp = resp
- if 'status' in resp:
- status = resp['status']
-
- if rc == 201:
- status = 'neverContacted'
- fail_after_time = dt.utcnow() + timedelta(seconds=timeout)
-
- while status == 'neverContacted':
- if dt.utcnow() > fail_after_time:
- raise Exception("web proxy timed out waiting for array status")
-
- sleep(1)
- (rc, system_resp) = request(api_url + "/storage-systems/%s" % ssid,
- headers=dict(Accept="application/json"), url_username=api_usr,
- url_password=api_pwd, validate_certs=validate_certs,
- ignore_errors=True)
- status = system_resp['status']
- return_resp = system_resp
-
- return status, return_resp
-
-
-def main():
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- state=dict(required=True, choices=['present', 'absent']),
- ssid=dict(required=True, type='str'),
- controller_addresses=dict(type='list'),
- array_wwn=dict(required=False, type='str'),
- array_password=dict(required=False, type='str', no_log=True),
- array_status_timeout_sec=dict(default=60, type='int'),
- enable_trace=dict(default=False, type='bool'),
- meta_tags=dict(type='list')
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- supports_check_mode=True,
- mutually_exclusive=[['controller_addresses', 'array_wwn']],
- required_if=[('state', 'present', ['controller_addresses'])]
- )
-
- p = module.params
-
- state = p['state']
- ssid = p['ssid']
- controller_addresses = p['controller_addresses']
- array_wwn = p['array_wwn']
- array_password = p['array_password']
- array_status_timeout_sec = p['array_status_timeout_sec']
- validate_certs = p['validate_certs']
- meta_tags = p['meta_tags']
- enable_trace = p['enable_trace']
-
- api_usr = p['api_username']
- api_pwd = p['api_password']
- api_url = p['api_url']
-
- changed = False
- array_exists = False
-
- try:
- (rc, resp) = request(api_url + "/storage-systems/%s" % ssid, headers=dict(Accept="application/json"),
- url_username=api_usr, url_password=api_pwd, validate_certs=validate_certs,
- ignore_errors=True)
- except Exception as err:
- module.fail_json(msg="Error accessing storage-system with id [%s]. Error [%s]" % (ssid, to_native(err)))
-
- array_exists = True
- array_detail = resp
-
- if rc == 200:
- if state == 'absent':
- changed = True
- array_exists = False
- elif state == 'present':
- current_addresses = frozenset(i for i in (array_detail['ip1'], array_detail['ip2']) if i)
- if set(controller_addresses) != current_addresses:
- changed = True
- if array_detail['wwn'] != array_wwn and array_wwn is not None:
- module.fail_json(
- msg='It seems you may have specified a bad WWN. The storage system ID you specified, %s, currently has the WWN of %s' %
- (ssid, array_detail['wwn'])
- )
- elif rc == 404:
- if state == 'present':
- changed = True
- array_exists = False
- else:
- changed = False
- module.exit_json(changed=changed, msg="Storage system was not present.")
-
- if changed and not module.check_mode:
- if state == 'present':
- if not array_exists:
- # add the array
- array_add_req = dict(
- id=ssid,
- controllerAddresses=controller_addresses,
- metaTags=meta_tags,
- enableTrace=enable_trace
- )
-
- if array_wwn:
- array_add_req['wwn'] = array_wwn
-
- if array_password:
- array_add_req['password'] = array_password
-
- post_headers = dict(Accept="application/json")
- post_headers['Content-Type'] = 'application/json'
- request_data = json.dumps(array_add_req)
-
- try:
- (rc, resp) = do_post(ssid, api_url, post_headers, api_usr, api_pwd, validate_certs, request_data,
- array_status_timeout_sec)
- except Exception as err:
- module.fail_json(msg="Failed to add storage system. Id[%s]. Request body [%s]. Error[%s]." %
- (ssid, request_data, to_native(err)))
-
- else: # array exists, modify...
- post_headers = dict(Accept="application/json")
- post_headers['Content-Type'] = 'application/json'
- post_body = dict(
- controllerAddresses=controller_addresses,
- removeAllTags=True,
- enableTrace=enable_trace,
- metaTags=meta_tags
- )
-
- try:
- (rc, resp) = do_post(ssid, api_url, post_headers, api_usr, api_pwd, validate_certs, post_body,
- array_status_timeout_sec)
- except Exception as err:
- module.fail_json(msg="Failed to update storage system. Id[%s]. Request body [%s]. Error[%s]." %
- (ssid, post_body, to_native(err)))
-
- elif state == 'absent':
- # delete the array
- try:
- (rc, resp) = request(api_url + "/storage-systems/%s" % ssid, method='DELETE',
- url_username=api_usr,
- url_password=api_pwd, validate_certs=validate_certs)
- except Exception as err:
- module.fail_json(msg="Failed to remove storage array. Id[%s]. Error[%s]." % (ssid, to_native(err)))
-
- if rc == 422:
- module.exit_json(changed=changed, msg="Storage system was not presented.")
- if rc == 204:
- module.exit_json(changed=changed, msg="Storage system removed.")
-
- module.exit_json(changed=changed, **resp)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_storagepool.py b/lib/ansible/modules/storage/netapp/netapp_e_storagepool.py
deleted file mode 100644
index 28de8f09e3..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_storagepool.py
+++ /dev/null
@@ -1,936 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-ANSIBLE_METADATA = {"metadata_version": "1.1",
- "status": ["preview"],
- "supported_by": "community"}
-
-DOCUMENTATION = """
----
-module: netapp_e_storagepool
-short_description: NetApp E-Series manage volume groups and disk pools
-description: Create or remove volume groups and disk pools for NetApp E-series storage arrays.
-version_added: '2.2'
-author:
- - Kevin Hulquest (@hulquest)
- - Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Whether the specified storage pool should exist or not.
- - Note that removing a storage pool currently requires the removal of all defined volumes first.
- required: true
- choices: ["present", "absent"]
- name:
- description:
- - The name of the storage pool to manage
- required: true
- criteria_drive_count:
- description:
- - The number of disks to use for building the storage pool.
- - When I(state=="present") then I(criteria_drive_count) or I(criteria_min_usable_capacity) must be specified.
- - The pool will be expanded if this number exceeds the number of disks already in place (See expansion note below)
- required: false
- type: int
- criteria_min_usable_capacity:
- description:
- - The minimum size of the storage pool (in size_unit).
- - When I(state=="present") then I(criteria_drive_count) or I(criteria_min_usable_capacity) must be specified.
- - The pool will be expanded if this value exceeds its current size. (See expansion note below)
- required: false
- type: float
- criteria_drive_type:
- description:
- - The type of disk (hdd or ssd) to use when searching for candidates to use.
- - When not specified each drive type will be evaluated until successful drive candidates are found starting with
- the most prevalent drive type.
- required: false
- choices: ["hdd","ssd"]
- criteria_size_unit:
- description:
- - The unit used to interpret size parameters
- choices: ["bytes", "b", "kb", "mb", "gb", "tb", "pb", "eb", "zb", "yb"]
- default: "gb"
- criteria_drive_min_size:
- description:
- - The minimum individual drive size (in size_unit) to consider when choosing drives for the storage pool.
- criteria_drive_interface_type:
- description:
- - The interface type to use when selecting drives for the storage pool
- - If not provided then all interface types will be considered.
- choices: ["sas", "sas4k", "fibre", "fibre520b", "scsi", "sata", "pata"]
- required: false
- criteria_drive_require_da:
- description:
- - Ensures the storage pool will be created with only data assurance (DA) capable drives.
- - Only available for new storage pools; existing storage pools cannot be converted.
- default: false
- type: bool
- version_added: '2.9'
- criteria_drive_require_fde:
- description:
- - Whether full disk encryption ability is required for drives to be added to the storage pool
- default: false
- type: bool
- raid_level:
- description:
- - The RAID level of the storage pool to be created.
- - Required only when I(state=="present").
- - When I(raid_level=="raidDiskPool") then I(criteria_drive_count >= 10 or criteria_drive_count >= 11) is required
- depending on the storage array specifications.
- - When I(raid_level=="raid0") then I(1<=criteria_drive_count) is required.
- - When I(raid_level=="raid1") then I(2<=criteria_drive_count) is required.
- - When I(raid_level=="raid3") then I(3<=criteria_drive_count<=30) is required.
- - When I(raid_level=="raid5") then I(3<=criteria_drive_count<=30) is required.
- - When I(raid_level=="raid6") then I(5<=criteria_drive_count<=30) is required.
- - Note that raidAll will be treated as raidDiskPool and raid3 as raid5.
- required: false
- choices: ["raidAll", "raid0", "raid1", "raid3", "raid5", "raid6", "raidDiskPool"]
- default: "raidDiskPool"
- secure_pool:
- description:
- - Enables security at rest feature on the storage pool.
- - Will only work if all drives in the pool are security capable (FDE, FIPS, or mix)
- - Warning, once security is enabled it is impossible to disable without erasing the drives.
- required: false
- type: bool
- reserve_drive_count:
- description:
- - Set the number of drives reserved by the storage pool for reconstruction operations.
- - Only valid on raid disk pools.
- required: false
- remove_volumes:
- description:
- - Prior to removing a storage pool, delete all volumes in the pool.
- default: true
- erase_secured_drives:
- description:
- - If I(state=="absent") then all storage pool drives will be erase
- - If I(state=="present") then delete all available storage array drives that have security enabled.
- default: true
- type: bool
-notes:
- - The expansion operations are non-blocking due to the time consuming nature of expanding volume groups
- - Traditional volume groups (raid0, raid1, raid5, raid6) are performed in steps dictated by the storage array. Each
- required step will be attempted until the request fails which is likely because of the required expansion time.
- - raidUnsupported will be treated as raid0, raidAll as raidDiskPool and raid3 as raid5.
- - Tray loss protection and drawer loss protection will be chosen if at all possible.
-"""
-EXAMPLES = """
-- name: No disk groups
- netapp_e_storagepool:
- ssid: "{{ ssid }}"
- name: "{{ item }}"
- state: absent
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
-"""
-RETURN = """
-msg:
- description: Success message
- returned: success
- type: str
- sample: Json facts for the pool that was created.
-"""
-import functools
-from itertools import groupby
-from time import sleep
-from pprint import pformat
-from ansible.module_utils.netapp import NetAppESeriesModule
-from ansible.module_utils._text import to_native
-
-
-def get_most_common_elements(iterator):
- """Returns a generator containing a descending list of most common elements."""
- if not isinstance(iterator, list):
- raise TypeError("iterator must be a list.")
-
- grouped = [(key, len(list(group))) for key, group in groupby(sorted(iterator))]
- return sorted(grouped, key=lambda x: x[1], reverse=True)
-
-
-def memoize(func):
- """Generic memoizer for any function with any number of arguments including zero."""
-
- @functools.wraps(func)
- def wrapper(*args, **kwargs):
- class MemoizeFuncArgs(dict):
- def __missing__(self, _key):
- self[_key] = func(*args, **kwargs)
- return self[_key]
-
- key = str((args, kwargs)) if args and kwargs else "no_argument_response"
- return MemoizeFuncArgs().__getitem__(key)
-
- return wrapper
-
-
-class NetAppESeriesStoragePool(NetAppESeriesModule):
- EXPANSION_TIMEOUT_SEC = 10
- DEFAULT_DISK_POOL_MINIMUM_DISK_COUNT = 11
-
- def __init__(self):
- version = "02.00.0000.0000"
- ansible_options = dict(
- state=dict(required=True, choices=["present", "absent"], type="str"),
- name=dict(required=True, type="str"),
- criteria_size_unit=dict(choices=["bytes", "b", "kb", "mb", "gb", "tb", "pb", "eb", "zb", "yb"],
- default="gb", type="str"),
- criteria_drive_count=dict(type="int"),
- criteria_drive_interface_type=dict(choices=["sas", "sas4k", "fibre", "fibre520b", "scsi", "sata", "pata"],
- type="str"),
- criteria_drive_type=dict(choices=["ssd", "hdd"], type="str", required=False),
- criteria_drive_min_size=dict(type="float"),
- criteria_drive_require_da=dict(type="bool", required=False),
- criteria_drive_require_fde=dict(type="bool", required=False),
- criteria_min_usable_capacity=dict(type="float"),
- raid_level=dict(choices=["raidAll", "raid0", "raid1", "raid3", "raid5", "raid6", "raidDiskPool"],
- default="raidDiskPool"),
- erase_secured_drives=dict(type="bool", default=True),
- secure_pool=dict(type="bool", default=False),
- reserve_drive_count=dict(type="int"),
- remove_volumes=dict(type="bool", default=True))
-
- required_if = [["state", "present", ["raid_level"]]]
- super(NetAppESeriesStoragePool, self).__init__(ansible_options=ansible_options,
- web_services_version=version,
- supports_check_mode=True,
- required_if=required_if)
-
- args = self.module.params
- self.state = args["state"]
- self.ssid = args["ssid"]
- self.name = args["name"]
- self.criteria_drive_count = args["criteria_drive_count"]
- self.criteria_min_usable_capacity = args["criteria_min_usable_capacity"]
- self.criteria_size_unit = args["criteria_size_unit"]
- self.criteria_drive_min_size = args["criteria_drive_min_size"]
- self.criteria_drive_type = args["criteria_drive_type"]
- self.criteria_drive_interface_type = args["criteria_drive_interface_type"]
- self.criteria_drive_require_fde = args["criteria_drive_require_fde"]
- self.criteria_drive_require_da = args["criteria_drive_require_da"]
- self.raid_level = args["raid_level"]
- self.erase_secured_drives = args["erase_secured_drives"]
- self.secure_pool = args["secure_pool"]
- self.reserve_drive_count = args["reserve_drive_count"]
- self.remove_volumes = args["remove_volumes"]
- self.pool_detail = None
-
- # Change all sizes to be measured in bytes
- if self.criteria_min_usable_capacity:
- self.criteria_min_usable_capacity = int(self.criteria_min_usable_capacity *
- self.SIZE_UNIT_MAP[self.criteria_size_unit])
- if self.criteria_drive_min_size:
- self.criteria_drive_min_size = int(self.criteria_drive_min_size *
- self.SIZE_UNIT_MAP[self.criteria_size_unit])
- self.criteria_size_unit = "bytes"
-
- # Adjust unused raid level option to reflect documentation
- if self.raid_level == "raidAll":
- self.raid_level = "raidDiskPool"
- if self.raid_level == "raid3":
- self.raid_level = "raid5"
-
- @property
- @memoize
- def available_drives(self):
- """Determine the list of available drives"""
- return [drive["id"] for drive in self.drives if drive["available"] and drive["status"] == "optimal"]
-
- @property
- @memoize
- def available_drive_types(self):
- """Determine the types of available drives sorted by the most common first."""
- types = [drive["driveMediaType"] for drive in self.drives]
- return [entry[0] for entry in get_most_common_elements(types)]
-
- @property
- @memoize
- def available_drive_interface_types(self):
- """Determine the types of available drives."""
- interfaces = [drive["phyDriveType"] for drive in self.drives]
- return [entry[0] for entry in get_most_common_elements(interfaces)]
-
- @property
- def storage_pool_drives(self, exclude_hotspares=True):
- """Retrieve list of drives found in storage pool."""
- if exclude_hotspares:
- return [drive for drive in self.drives
- if drive["currentVolumeGroupRef"] == self.pool_detail["id"] and not drive["hotSpare"]]
-
- return [drive for drive in self.drives if drive["currentVolumeGroupRef"] == self.pool_detail["id"]]
-
- @property
- def expandable_drive_count(self):
- """Maximum number of drives that a storage pool can be expanded at a given time."""
- capabilities = None
- if self.raid_level == "raidDiskPool":
- return len(self.available_drives)
-
- try:
- rc, capabilities = self.request("storage-systems/%s/capabilities" % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to fetch maximum expandable drive count. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- return capabilities["featureParameters"]["maxDCEDrives"]
-
- @property
- def disk_pool_drive_minimum(self):
- """Provide the storage array's minimum disk pool drive count."""
- rc, attr = self.request("storage-systems/%s/symbol/getSystemAttributeDefaults" % self.ssid, ignore_errors=True)
-
- # Standard minimum is 11 drives but some allow 10 drives. 10 will be the default
- if (rc != 200 or "minimumDriveCount" not in attr["defaults"]["diskPoolDefaultAttributes"].keys() or
- attr["defaults"]["diskPoolDefaultAttributes"]["minimumDriveCount"] == 0):
- return self.DEFAULT_DISK_POOL_MINIMUM_DISK_COUNT
-
- return attr["defaults"]["diskPoolDefaultAttributes"]["minimumDriveCount"]
-
- def get_available_drive_capacities(self, drive_id_list=None):
- """Determine the list of available drive capacities."""
- if drive_id_list:
- available_drive_capacities = set([int(drive["usableCapacity"]) for drive in self.drives
- if drive["id"] in drive_id_list and drive["available"] and
- drive["status"] == "optimal"])
- else:
- available_drive_capacities = set([int(drive["usableCapacity"]) for drive in self.drives
- if drive["available"] and drive["status"] == "optimal"])
-
- self.module.log("available drive capacities: %s" % available_drive_capacities)
- return list(available_drive_capacities)
-
- @property
- def drives(self):
- """Retrieve list of drives found in storage pool."""
- drives = None
- try:
- rc, drives = self.request("storage-systems/%s/drives" % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to fetch disk drives. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- return drives
-
- def is_drive_count_valid(self, drive_count):
- """Validate drive count criteria is met."""
- if self.criteria_drive_count and drive_count < self.criteria_drive_count:
- return False
-
- if self.raid_level == "raidDiskPool":
- return drive_count >= self.disk_pool_drive_minimum
- if self.raid_level == "raid0":
- return drive_count > 0
- if self.raid_level == "raid1":
- return drive_count >= 2 and (drive_count % 2) == 0
- if self.raid_level in ["raid3", "raid5"]:
- return 3 <= drive_count <= 30
- if self.raid_level == "raid6":
- return 5 <= drive_count <= 30
- return False
-
- @property
- def storage_pool(self):
- """Retrieve storage pool information."""
- storage_pools_resp = None
- try:
- rc, storage_pools_resp = self.request("storage-systems/%s/storage-pools" % self.ssid)
- except Exception as err:
- self.module.fail_json(msg="Failed to get storage pools. Array id [%s]. Error[%s]. State[%s]."
- % (self.ssid, to_native(err), self.state))
-
- pool_detail = [pool for pool in storage_pools_resp if pool["name"] == self.name]
- return pool_detail[0] if pool_detail else dict()
-
- @property
- def storage_pool_volumes(self):
- """Retrieve list of volumes associated with storage pool."""
- volumes_resp = None
- try:
- rc, volumes_resp = self.request("storage-systems/%s/volumes" % self.ssid)
- except Exception as err:
- self.module.fail_json(msg="Failed to get storage pools. Array id [%s]. Error[%s]. State[%s]."
- % (self.ssid, to_native(err), self.state))
-
- group_ref = self.storage_pool["volumeGroupRef"]
- storage_pool_volume_list = [volume["id"] for volume in volumes_resp if volume["volumeGroupRef"] == group_ref]
- return storage_pool_volume_list
-
- def get_ddp_capacity(self, expansion_drive_list):
- """Return the total usable capacity based on the additional drives."""
-
- def get_ddp_error_percent(_drive_count, _extent_count):
- """Determine the space reserved for reconstruction"""
- if _drive_count <= 36:
- if _extent_count <= 600:
- return 0.40
- elif _extent_count <= 1400:
- return 0.35
- elif _extent_count <= 6200:
- return 0.20
- elif _extent_count <= 50000:
- return 0.15
- elif _drive_count <= 64:
- if _extent_count <= 600:
- return 0.20
- elif _extent_count <= 1400:
- return 0.15
- elif _extent_count <= 6200:
- return 0.10
- elif _extent_count <= 50000:
- return 0.05
- elif _drive_count <= 480:
- if _extent_count <= 600:
- return 0.20
- elif _extent_count <= 1400:
- return 0.15
- elif _extent_count <= 6200:
- return 0.10
- elif _extent_count <= 50000:
- return 0.05
-
- self.module.fail_json(msg="Drive count exceeded the error percent table. Array[%s]" % self.ssid)
-
- def get_ddp_reserved_drive_count(_disk_count):
- """Determine the number of reserved drive."""
- reserve_count = 0
-
- if self.reserve_drive_count:
- reserve_count = self.reserve_drive_count
- elif _disk_count >= 256:
- reserve_count = 8
- elif _disk_count >= 192:
- reserve_count = 7
- elif _disk_count >= 128:
- reserve_count = 6
- elif _disk_count >= 64:
- reserve_count = 4
- elif _disk_count >= 32:
- reserve_count = 3
- elif _disk_count >= 12:
- reserve_count = 2
- elif _disk_count == 11:
- reserve_count = 1
-
- return reserve_count
-
- if self.pool_detail:
- drive_count = len(self.storage_pool_drives) + len(expansion_drive_list)
- else:
- drive_count = len(expansion_drive_list)
-
- drive_usable_capacity = min(min(self.get_available_drive_capacities()),
- min(self.get_available_drive_capacities(expansion_drive_list)))
- drive_data_extents = ((drive_usable_capacity - 8053063680) / 536870912)
- maximum_stripe_count = (drive_count * drive_data_extents) / 10
-
- error_percent = get_ddp_error_percent(drive_count, drive_data_extents)
- error_overhead = (drive_count * drive_data_extents / 10 * error_percent + 10) / 10
-
- total_stripe_count = maximum_stripe_count - error_overhead
- stripe_count_per_drive = total_stripe_count / drive_count
- reserved_stripe_count = get_ddp_reserved_drive_count(drive_count) * stripe_count_per_drive
- available_stripe_count = total_stripe_count - reserved_stripe_count
-
- return available_stripe_count * 4294967296
-
- @memoize
- def get_candidate_drives(self):
- """Retrieve set of drives candidates for creating a new storage pool."""
-
- def get_candidate_drive_request():
- """Perform request for new volume creation."""
- candidates_list = list()
- drive_types = [self.criteria_drive_type] if self.criteria_drive_type else self.available_drive_types
- interface_types = [self.criteria_drive_interface_type] \
- if self.criteria_drive_interface_type else self.available_drive_interface_types
-
- for interface_type in interface_types:
- for drive_type in drive_types:
- candidates = None
- volume_candidate_request_data = dict(
- type="diskPool" if self.raid_level == "raidDiskPool" else "traditional",
- diskPoolVolumeCandidateRequestData=dict(
- reconstructionReservedDriveCount=65535))
- candidate_selection_type = dict(
- candidateSelectionType="count",
- driveRefList=dict(driveRef=self.available_drives))
- criteria = dict(raidLevel=self.raid_level,
- phyDriveType=interface_type,
- dssPreallocEnabled=False,
- securityType="capable" if self.criteria_drive_require_fde else "none",
- driveMediaType=drive_type,
- onlyProtectionInformationCapable=True if self.criteria_drive_require_da else False,
- volumeCandidateRequestData=volume_candidate_request_data,
- allocateReserveSpace=False,
- securityLevel="fde" if self.criteria_drive_require_fde else "none",
- candidateSelectionType=candidate_selection_type)
-
- try:
- rc, candidates = self.request("storage-systems/%s/symbol/getVolumeCandidates?verboseError"
- "Response=true" % self.ssid, data=criteria, method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve volume candidates. Array [%s]. Error [%s]."
- % (self.ssid, to_native(error)))
-
- if candidates:
- candidates_list.extend(candidates["volumeCandidate"])
-
- # Sort output based on tray and then drawer protection first
- tray_drawer_protection = list()
- tray_protection = list()
- drawer_protection = list()
- no_protection = list()
- sorted_candidates = list()
- for item in candidates_list:
- if item["trayLossProtection"]:
- if item["drawerLossProtection"]:
- tray_drawer_protection.append(item)
- else:
- tray_protection.append(item)
- elif item["drawerLossProtection"]:
- drawer_protection.append(item)
- else:
- no_protection.append(item)
-
- if tray_drawer_protection:
- sorted_candidates.extend(tray_drawer_protection)
- if tray_protection:
- sorted_candidates.extend(tray_protection)
- if drawer_protection:
- sorted_candidates.extend(drawer_protection)
- if no_protection:
- sorted_candidates.extend(no_protection)
-
- return sorted_candidates
-
- # Determine the appropriate candidate list
- for candidate in get_candidate_drive_request():
-
- # Evaluate candidates for required drive count, collective drive usable capacity and minimum drive size
- if self.criteria_drive_count:
- if self.criteria_drive_count != int(candidate["driveCount"]):
- continue
- if self.criteria_min_usable_capacity:
- if ((self.raid_level == "raidDiskPool" and self.criteria_min_usable_capacity >
- self.get_ddp_capacity(candidate["driveRefList"]["driveRef"])) or
- self.criteria_min_usable_capacity > int(candidate["usableSize"])):
- continue
- if self.criteria_drive_min_size:
- if self.criteria_drive_min_size > min(self.get_available_drive_capacities(candidate["driveRefList"]["driveRef"])):
- continue
-
- return candidate
-
- self.module.fail_json(msg="Not enough drives to meet the specified criteria. Array [%s]." % self.ssid)
-
- @memoize
- def get_expansion_candidate_drives(self):
- """Retrieve required expansion drive list.
-
- Note: To satisfy the expansion criteria each item in the candidate list must added specified group since there
- is a potential limitation on how many drives can be incorporated at a time.
- * Traditional raid volume groups must be added two drives maximum at a time. No limits on raid disk pools.
-
- :return list(candidate): list of candidate structures from the getVolumeGroupExpansionCandidates symbol endpoint
- """
-
- def get_expansion_candidate_drive_request():
- """Perform the request for expanding existing volume groups or disk pools.
-
- Note: the list of candidate structures do not necessarily produce candidates that meet all criteria.
- """
- candidates_list = None
- url = "storage-systems/%s/symbol/getVolumeGroupExpansionCandidates?verboseErrorResponse=true" % self.ssid
- if self.raid_level == "raidDiskPool":
- url = "storage-systems/%s/symbol/getDiskPoolExpansionCandidates?verboseErrorResponse=true" % self.ssid
-
- try:
- rc, candidates_list = self.request(url, method="POST", data=self.pool_detail["id"])
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve volume candidates. Array [%s]. Error [%s]."
- % (self.ssid, to_native(error)))
-
- return candidates_list["candidates"]
-
- required_candidate_list = list()
- required_additional_drives = 0
- required_additional_capacity = 0
- total_required_capacity = 0
-
- # determine whether and how much expansion is need to satisfy the specified criteria
- if self.criteria_min_usable_capacity:
- total_required_capacity = self.criteria_min_usable_capacity
- required_additional_capacity = self.criteria_min_usable_capacity - int(self.pool_detail["totalRaidedSpace"])
-
- if self.criteria_drive_count:
- required_additional_drives = self.criteria_drive_count - len(self.storage_pool_drives)
-
- # Determine the appropriate expansion candidate list
- if required_additional_drives > 0 or required_additional_capacity > 0:
- for candidate in get_expansion_candidate_drive_request():
-
- if self.criteria_drive_min_size:
- if self.criteria_drive_min_size > min(self.get_available_drive_capacities(candidate["drives"])):
- continue
-
- if self.raid_level == "raidDiskPool":
- if (len(candidate["drives"]) >= required_additional_drives and
- self.get_ddp_capacity(candidate["drives"]) >= total_required_capacity):
- required_candidate_list.append(candidate)
- break
- else:
- required_additional_drives -= len(candidate["drives"])
- required_additional_capacity -= int(candidate["usableCapacity"])
- required_candidate_list.append(candidate)
-
- # Determine if required drives and capacities are satisfied
- if required_additional_drives <= 0 and required_additional_capacity <= 0:
- break
- else:
- self.module.fail_json(msg="Not enough drives to meet the specified criteria. Array [%s]." % self.ssid)
-
- return required_candidate_list
-
- def get_reserve_drive_count(self):
- """Retrieve the current number of reserve drives for raidDiskPool (Only for raidDiskPool)."""
-
- if not self.pool_detail:
- self.module.fail_json(msg="The storage pool must exist. Array [%s]." % self.ssid)
-
- if self.raid_level != "raidDiskPool":
- self.module.fail_json(msg="The storage pool must be a raidDiskPool. Pool [%s]. Array [%s]."
- % (self.pool_detail["id"], self.ssid))
-
- return self.pool_detail["volumeGroupData"]["diskPoolData"]["reconstructionReservedDriveCount"]
-
- def get_maximum_reserve_drive_count(self):
- """Retrieve the maximum number of reserve drives for storage pool (Only for raidDiskPool)."""
- if self.raid_level != "raidDiskPool":
- self.module.fail_json(msg="The storage pool must be a raidDiskPool. Pool [%s]. Array [%s]."
- % (self.pool_detail["id"], self.ssid))
-
- drives_ids = list()
-
- if self.pool_detail:
- drives_ids.extend(self.storage_pool_drives)
- for candidate in self.get_expansion_candidate_drives():
- drives_ids.extend((candidate["drives"]))
- else:
- candidate = self.get_candidate_drives()
- drives_ids.extend(candidate["driveRefList"]["driveRef"])
-
- drive_count = len(drives_ids)
- maximum_reserve_drive_count = min(int(drive_count * 0.2 + 1), drive_count - 10)
- if maximum_reserve_drive_count > 10:
- maximum_reserve_drive_count = 10
-
- return maximum_reserve_drive_count
-
- def set_reserve_drive_count(self, check_mode=False):
- """Set the reserve drive count for raidDiskPool."""
- changed = False
-
- if self.raid_level == "raidDiskPool" and self.reserve_drive_count:
- maximum_count = self.get_maximum_reserve_drive_count()
-
- if self.reserve_drive_count < 0 or self.reserve_drive_count > maximum_count:
- self.module.fail_json(msg="Supplied reserve drive count is invalid or exceeds the maximum allowed. "
- "Note that it may be necessary to wait for expansion operations to complete "
- "before the adjusting the reserve drive count. Maximum [%s]. Array [%s]."
- % (maximum_count, self.ssid))
-
- if self.reserve_drive_count != self.get_reserve_drive_count():
- changed = True
-
- if not check_mode:
- try:
- rc, resp = self.request("storage-systems/%s/symbol/setDiskPoolReservedDriveCount" % self.ssid,
- method="POST", data=dict(volumeGroupRef=self.pool_detail["id"],
- newDriveCount=self.reserve_drive_count))
- except Exception as error:
- self.module.fail_json(msg="Failed to set reserve drive count for disk pool. Disk Pool [%s]."
- " Array [%s]." % (self.pool_detail["id"], self.ssid))
-
- return changed
-
- def erase_all_available_secured_drives(self, check_mode=False):
- """Erase all available drives that have encryption at rest feature enabled."""
- changed = False
- drives_list = list()
- for drive in self.drives:
- if drive["available"] and drive["fdeEnabled"]:
- changed = True
- drives_list.append(drive["id"])
-
- if drives_list and not check_mode:
- try:
- rc, resp = self.request("storage-systems/%s/symbol/reprovisionDrive?verboseErrorResponse=true"
- % self.ssid, method="POST", data=dict(driveRef=drives_list))
- except Exception as error:
- self.module.fail_json(msg="Failed to erase all secured drives. Array [%s]" % self.ssid)
-
- return changed
-
- def create_storage_pool(self):
- """Create new storage pool."""
- url = "storage-systems/%s/symbol/createVolumeGroup?verboseErrorResponse=true" % self.ssid
- request_body = dict(label=self.name,
- candidate=self.get_candidate_drives())
-
- if self.raid_level == "raidDiskPool":
- url = "storage-systems/%s/symbol/createDiskPool?verboseErrorResponse=true" % self.ssid
-
- request_body.update(
- dict(backgroundOperationPriority="useDefault",
- criticalReconstructPriority="useDefault",
- degradedReconstructPriority="useDefault",
- poolUtilizationCriticalThreshold=65535,
- poolUtilizationWarningThreshold=0))
-
- if self.reserve_drive_count:
- request_body.update(dict(volumeCandidateData=dict(
- diskPoolVolumeCandidateData=dict(reconstructionReservedDriveCount=self.reserve_drive_count))))
-
- try:
- rc, resp = self.request(url, method="POST", data=request_body)
- except Exception as error:
- self.module.fail_json(msg="Failed to create storage pool. Array id [%s]. Error[%s]."
- % (self.ssid, to_native(error)))
-
- # Update drive and storage pool information
- self.pool_detail = self.storage_pool
-
- def delete_storage_pool(self):
- """Delete storage pool."""
- storage_pool_drives = [drive["id"] for drive in self.storage_pool_drives if drive["fdeEnabled"]]
- try:
- delete_volumes_parameter = "?delete-volumes=true" if self.remove_volumes else ""
- rc, resp = self.request("storage-systems/%s/storage-pools/%s%s"
- % (self.ssid, self.pool_detail["id"], delete_volumes_parameter), method="DELETE")
- except Exception as error:
- self.module.fail_json(msg="Failed to delete storage pool. Pool id [%s]. Array id [%s]. Error[%s]."
- % (self.pool_detail["id"], self.ssid, to_native(error)))
-
- if storage_pool_drives and self.erase_secured_drives:
- try:
- rc, resp = self.request("storage-systems/%s/symbol/reprovisionDrive?verboseErrorResponse=true"
- % self.ssid, method="POST", data=dict(driveRef=storage_pool_drives))
- except Exception as error:
- self.module.fail_json(msg="Failed to erase drives prior to creating new storage pool. Array [%s]."
- " Error [%s]." % (self.ssid, to_native(error)))
-
- def secure_storage_pool(self, check_mode=False):
- """Enable security on an existing storage pool"""
- self.pool_detail = self.storage_pool
- needs_secure_pool = False
-
- if not self.secure_pool and self.pool_detail["securityType"] == "enabled":
- self.module.fail_json(msg="It is not possible to disable storage pool security! See array documentation.")
- if self.secure_pool and self.pool_detail["securityType"] != "enabled":
- needs_secure_pool = True
-
- if needs_secure_pool and not check_mode:
- try:
- rc, resp = self.request("storage-systems/%s/storage-pools/%s" % (self.ssid, self.pool_detail["id"]),
- data=dict(securePool=True), method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to secure storage pool. Pool id [%s]. Array [%s]. Error"
- " [%s]." % (self.pool_detail["id"], self.ssid, to_native(error)))
-
- self.pool_detail = self.storage_pool
- return needs_secure_pool
-
- def migrate_raid_level(self, check_mode=False):
- """Request storage pool raid level migration."""
- needs_migration = self.raid_level != self.pool_detail["raidLevel"]
- if needs_migration and self.pool_detail["raidLevel"] == "raidDiskPool":
- self.module.fail_json(msg="Raid level cannot be changed for disk pools")
-
- if needs_migration and not check_mode:
- sp_raid_migrate_req = dict(raidLevel=self.raid_level)
-
- try:
- rc, resp = self.request("storage-systems/%s/storage-pools/%s/raid-type-migration"
- % (self.ssid, self.name), data=sp_raid_migrate_req, method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to change the raid level of storage pool. Array id [%s]."
- " Error[%s]." % (self.ssid, to_native(error)))
-
- self.pool_detail = self.storage_pool
- return needs_migration
-
- def expand_storage_pool(self, check_mode=False):
- """Add drives to existing storage pool.
-
- :return bool: whether drives were required to be added to satisfy the specified criteria."""
- expansion_candidate_list = self.get_expansion_candidate_drives()
- changed_required = bool(expansion_candidate_list)
- estimated_completion_time = 0.0
-
- # build expandable groupings of traditional raid candidate
- required_expansion_candidate_list = list()
- while expansion_candidate_list:
- subset = list()
- while expansion_candidate_list and len(subset) < self.expandable_drive_count:
- subset.extend(expansion_candidate_list.pop()["drives"])
- required_expansion_candidate_list.append(subset)
-
- if required_expansion_candidate_list and not check_mode:
- url = "storage-systems/%s/symbol/startVolumeGroupExpansion?verboseErrorResponse=true" % self.ssid
- if self.raid_level == "raidDiskPool":
- url = "storage-systems/%s/symbol/startDiskPoolExpansion?verboseErrorResponse=true" % self.ssid
-
- while required_expansion_candidate_list:
- candidate_drives_list = required_expansion_candidate_list.pop()
- request_body = dict(volumeGroupRef=self.pool_detail["volumeGroupRef"],
- driveRef=candidate_drives_list)
- try:
- rc, resp = self.request(url, method="POST", data=request_body)
- except Exception as error:
- rc, actions_resp = self.request("storage-systems/%s/storage-pools/%s/action-progress"
- % (self.ssid, self.pool_detail["id"]), ignore_errors=True)
- if rc == 200 and actions_resp:
- actions = [action["currentAction"] for action in actions_resp
- if action["volumeRef"] in self.storage_pool_volumes]
- self.module.fail_json(msg="Failed to add drives to the storage pool possibly because of actions"
- " in progress. Actions [%s]. Pool id [%s]. Array id [%s]. Error[%s]."
- % (", ".join(actions), self.pool_detail["id"], self.ssid,
- to_native(error)))
-
- self.module.fail_json(msg="Failed to add drives to storage pool. Pool id [%s]. Array id [%s]."
- " Error[%s]." % (self.pool_detail["id"], self.ssid, to_native(error)))
-
- # Wait for expansion completion unless it is the last request in the candidate list
- if required_expansion_candidate_list:
- for dummy in range(self.EXPANSION_TIMEOUT_SEC):
- rc, actions_resp = self.request("storage-systems/%s/storage-pools/%s/action-progress"
- % (self.ssid, self.pool_detail["id"]), ignore_errors=True)
- if rc == 200:
- for action in actions_resp:
- if (action["volumeRef"] in self.storage_pool_volumes and
- action["currentAction"] == "remappingDce"):
- sleep(1)
- estimated_completion_time = action["estimatedTimeToCompletion"]
- break
- else:
- estimated_completion_time = 0.0
- break
-
- return changed_required, estimated_completion_time
-
- def apply(self):
- """Apply requested state to storage array."""
- changed = False
-
- if self.state == "present":
- if self.criteria_drive_count is None and self.criteria_min_usable_capacity is None:
- self.module.fail_json(msg="One of criteria_min_usable_capacity or criteria_drive_count must be"
- " specified.")
- if self.criteria_drive_count and not self.is_drive_count_valid(self.criteria_drive_count):
- self.module.fail_json(msg="criteria_drive_count must be valid for the specified raid level.")
-
- self.pool_detail = self.storage_pool
- self.module.log(pformat(self.pool_detail))
-
- if self.state == "present" and self.erase_secured_drives:
- self.erase_all_available_secured_drives(check_mode=True)
-
- # Determine whether changes need to be applied to the storage array
- if self.pool_detail:
-
- if self.state == "absent":
- changed = True
-
- elif self.state == "present":
-
- if self.criteria_drive_count and self.criteria_drive_count < len(self.storage_pool_drives):
- self.module.fail_json(msg="Failed to reduce the size of the storage pool. Array [%s]. Pool [%s]."
- % (self.ssid, self.pool_detail["id"]))
-
- if self.criteria_drive_type and self.criteria_drive_type != self.pool_detail["driveMediaType"]:
- self.module.fail_json(msg="Failed! It is not possible to modify storage pool media type."
- " Array [%s]. Pool [%s]." % (self.ssid, self.pool_detail["id"]))
-
- if (self.criteria_drive_require_da is not None and self.criteria_drive_require_da !=
- self.pool_detail["protectionInformationCapabilities"]["protectionInformationCapable"]):
- self.module.fail_json(msg="Failed! It is not possible to modify DA-capability. Array [%s]."
- " Pool [%s]." % (self.ssid, self.pool_detail["id"]))
-
- # Evaluate current storage pool for required change.
- needs_expansion, estimated_completion_time = self.expand_storage_pool(check_mode=True)
- if needs_expansion:
- changed = True
- if self.migrate_raid_level(check_mode=True):
- changed = True
- if self.secure_storage_pool(check_mode=True):
- changed = True
- if self.set_reserve_drive_count(check_mode=True):
- changed = True
-
- elif self.state == "present":
- changed = True
-
- # Apply changes to storage array
- msg = "No changes were required for the storage pool [%s]."
- if changed and not self.module.check_mode:
- if self.state == "present":
- if self.erase_secured_drives:
- self.erase_all_available_secured_drives()
-
- if self.pool_detail:
- change_list = list()
-
- # Expansion needs to occur before raid level migration to account for any sizing needs.
- expanded, estimated_completion_time = self.expand_storage_pool()
- if expanded:
- change_list.append("expanded")
- if self.migrate_raid_level():
- change_list.append("raid migration")
- if self.secure_storage_pool():
- change_list.append("secured")
- if self.set_reserve_drive_count():
- change_list.append("adjusted reserve drive count")
-
- if change_list:
- msg = "Following changes have been applied to the storage pool [%s]: " + ", ".join(change_list)
-
- if expanded:
- msg += "\nThe expansion operation will complete in an estimated %s minutes."\
- % estimated_completion_time
- else:
- self.create_storage_pool()
- msg = "Storage pool [%s] was created."
-
- if self.secure_storage_pool():
- msg = "Storage pool [%s] was created and secured."
- if self.set_reserve_drive_count():
- msg += " Adjusted reserve drive count."
-
- elif self.pool_detail:
- self.delete_storage_pool()
- msg = "Storage pool [%s] removed."
-
- self.pool_detail = self.storage_pool
- self.module.log(pformat(self.pool_detail))
- self.module.log(msg % self.name)
- self.module.exit_json(msg=msg % self.name, changed=changed, **self.pool_detail)
-
-
-def main():
- storage_pool = NetAppESeriesStoragePool()
- storage_pool.apply()
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_syslog.py b/lib/ansible/modules/storage/netapp/netapp_e_syslog.py
deleted file mode 100644
index a069e26ca5..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_syslog.py
+++ /dev/null
@@ -1,280 +0,0 @@
-#!/usr/bin/python
-
-# (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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_syslog
-short_description: NetApp E-Series manage syslog settings
-description:
- - Allow the syslog settings to be configured for an individual E-Series storage-system
-version_added: '2.7'
-author: Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Add or remove the syslog server configuration for E-Series storage array.
- - Existing syslog server configuration will be removed or updated when its address matches I(address).
- - Fully qualified hostname that resolve to an IPv4 address that matches I(address) will not be
- treated as a match.
- choices:
- - present
- - absent
- default: present
- address:
- description:
- - The syslog server's IPv4 address or a fully qualified hostname.
- - All existing syslog configurations will be removed when I(state=absent) and I(address=None).
- port:
- description:
- - This is the port the syslog server is using.
- default: 514
- protocol:
- description:
- - This is the transmission protocol the syslog server's using to receive syslog messages.
- choices:
- - udp
- - tcp
- - tls
- default: udp
- components:
- description:
- - The e-series logging components define the specific logs to transfer to the syslog server.
- - At the time of writing, 'auditLog' is the only logging component but more may become available.
- default: ["auditLog"]
- test:
- description:
- - This forces a test syslog message to be sent to the stated syslog server.
- - Only attempts transmission when I(state=present).
- type: bool
- default: no
- log_path:
- description:
- - This argument specifies a local path for logging purposes.
- required: no
-notes:
- - Check mode is supported.
- - This API is currently only supported with the Embedded Web Services API v2.12 (bundled with
- SANtricity OS 11.40.2) and higher.
-"""
-
-EXAMPLES = """
- - name: Add two syslog server configurations to NetApp E-Series storage array.
- netapp_e_syslog:
- state: present
- address: "{{ item }}"
- port: 514
- protocol: tcp
- component: "auditLog"
- api_url: "10.1.1.1:8443"
- api_username: "admin"
- api_password: "myPass"
- loop:
- - "192.168.1.1"
- - "192.168.1.100"
-"""
-
-RETURN = """
-msg:
- description: Success message
- returned: on success
- type: str
- sample: The settings have been updated.
-syslog:
- description:
- - True if syslog server configuration has been added to e-series storage array.
- returned: on success
- sample: True
- type: bool
-"""
-
-import json
-import logging
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.netapp import request, eseries_host_argument_spec
-from ansible.module_utils._text import to_native
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-class Syslog(object):
- def __init__(self):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(dict(
- state=dict(choices=["present", "absent"], required=False, default="present"),
- address=dict(type="str", required=False),
- port=dict(type="int", default=514, required=False),
- protocol=dict(choices=["tcp", "tls", "udp"], default="udp", required=False),
- components=dict(type="list", required=False, default=["auditLog"]),
- test=dict(type="bool", default=False, required=False),
- log_path=dict(type="str", required=False),
- ))
-
- required_if = [
- ["state", "present", ["address", "port", "protocol", "components"]],
- ]
-
- mutually_exclusive = [
- ["test", "absent"],
- ]
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True, required_if=required_if,
- mutually_exclusive=mutually_exclusive)
- args = self.module.params
-
- self.syslog = args["state"] in ["present"]
- self.address = args["address"]
- self.port = args["port"]
- self.protocol = args["protocol"]
- self.components = args["components"]
- self.test = args["test"]
- self.ssid = args["ssid"]
- self.url = args["api_url"]
- self.creds = dict(url_password=args["api_password"],
- validate_certs=args["validate_certs"],
- url_username=args["api_username"], )
-
- self.components.sort()
-
- self.check_mode = self.module.check_mode
-
- # logging setup
- log_path = args["log_path"]
- self._logger = logging.getLogger(self.__class__.__name__)
- if log_path:
- logging.basicConfig(
- level=logging.DEBUG, filename=log_path, filemode='w',
- format='%(relativeCreated)dms %(levelname)s %(module)s.%(funcName)s:%(lineno)d\n %(message)s')
-
- if not self.url.endswith('/'):
- self.url += '/'
-
- def get_configuration(self):
- """Retrieve existing syslog configuration."""
- try:
- (rc, result) = request(self.url + "storage-systems/{0}/syslog".format(self.ssid),
- headers=HEADERS, **self.creds)
- return result
- except Exception as err:
- self.module.fail_json(msg="Failed to retrieve syslog configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def test_configuration(self, body):
- """Send test syslog message to the storage array.
-
- Allows fix number of retries to occur before failure is issued to give the storage array time to create
- new syslog server record.
- """
- try:
- (rc, result) = request(self.url + "storage-systems/{0}/syslog/{1}/test".format(self.ssid, body["id"]),
- method='POST', headers=HEADERS, **self.creds)
- except Exception as err:
- self.module.fail_json(
- msg="We failed to send test message! Array Id [{0}]. Error [{1}].".format(self.ssid, to_native(err)))
-
- def update_configuration(self):
- """Post the syslog request to array."""
- config_match = None
- perfect_match = None
- update = False
- body = dict()
-
- # search existing configuration for syslog server entry match
- configs = self.get_configuration()
- if self.address:
- for config in configs:
- if config["serverAddress"] == self.address:
- config_match = config
- if (config["port"] == self.port and config["protocol"] == self.protocol and
- len(config["components"]) == len(self.components) and
- all([component["type"] in self.components for component in config["components"]])):
- perfect_match = config_match
- break
-
- # generate body for the http request
- if self.syslog:
- if not perfect_match:
- update = True
- if config_match:
- body.update(dict(id=config_match["id"]))
- components = [dict(type=component_type) for component_type in self.components]
- body.update(dict(serverAddress=self.address, port=self.port,
- protocol=self.protocol, components=components))
- self._logger.info(body)
- self.make_configuration_request(body)
-
- # remove specific syslog server configuration
- elif self.address:
- update = True
- body.update(dict(id=config_match["id"]))
- self._logger.info(body)
- self.make_configuration_request(body)
-
- # if no address is specified, remove all syslog server configurations
- elif configs:
- update = True
- for config in configs:
- body.update(dict(id=config["id"]))
- self._logger.info(body)
- self.make_configuration_request(body)
-
- return update
-
- def make_configuration_request(self, body):
- # make http request(s)
- if not self.check_mode:
- try:
- if self.syslog:
- if "id" in body:
- (rc, result) = request(
- self.url + "storage-systems/{0}/syslog/{1}".format(self.ssid, body["id"]),
- method='POST', data=json.dumps(body), headers=HEADERS, **self.creds)
- else:
- (rc, result) = request(self.url + "storage-systems/{0}/syslog".format(self.ssid),
- method='POST', data=json.dumps(body), headers=HEADERS, **self.creds)
- body.update(result)
-
- # send syslog test message
- if self.test:
- self.test_configuration(body)
-
- elif "id" in body:
- (rc, result) = request(self.url + "storage-systems/{0}/syslog/{1}".format(self.ssid, body["id"]),
- method='DELETE', headers=HEADERS, **self.creds)
-
- # This is going to catch cases like a connection failure
- except Exception as err:
- self.module.fail_json(msg="We failed to modify syslog configuration! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(err)))
-
- def update(self):
- """Update configuration and respond to ansible."""
- update = self.update_configuration()
- self.module.exit_json(msg="The syslog settings have been updated.", changed=update)
-
- def __call__(self, *args, **kwargs):
- self.update()
-
-
-def main():
- settings = Syslog()
- settings()
-
-
-if __name__ == "__main__":
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_volume.py b/lib/ansible/modules/storage/netapp/netapp_e_volume.py
deleted file mode 100644
index 4398119fba..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_volume.py
+++ /dev/null
@@ -1,856 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = """
----
-module: netapp_e_volume
-version_added: "2.2"
-short_description: NetApp E-Series manage storage volumes (standard and thin)
-description:
- - Create or remove volumes (standard and thin) for NetApp E/EF-series storage arrays.
-author:
- - Kevin Hulquest (@hulquest)
- - Nathan Swartz (@ndswartz)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- state:
- description:
- - Whether the specified volume should exist
- required: true
- choices: ['present', 'absent']
- name:
- description:
- - The name of the volume to manage.
- required: true
- storage_pool_name:
- description:
- - Required only when requested I(state=='present').
- - Name of the storage pool wherein the volume should reside.
- required: false
- size_unit:
- description:
- - The unit used to interpret the size parameter
- choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
- default: 'gb'
- size:
- description:
- - Required only when I(state=='present').
- - Size of the volume in I(size_unit).
- - Size of the virtual volume in the case of a thin volume in I(size_unit).
- - Maximum virtual volume size of a thin provisioned volume is 256tb; however other OS-level restrictions may
- exist.
- required: true
- segment_size_kb:
- description:
- - Segment size of the volume
- - All values are in kibibytes.
- - Some common choices include '8', '16', '32', '64', '128', '256', and '512' but options are system
- dependent.
- - Retrieve the definitive system list from M(netapp_e_facts) under segment_sizes.
- - When the storage pool is a raidDiskPool then the segment size must be 128kb.
- - Segment size migrations are not allowed in this module
- default: '128'
- thin_provision:
- description:
- - Whether the volume should be thin provisioned.
- - Thin volumes can only be created when I(raid_level=="raidDiskPool").
- - Generally, use of thin-provisioning is not recommended due to performance impacts.
- type: bool
- default: false
- thin_volume_repo_size:
- description:
- - This value (in size_unit) sets the allocated space for the thin provisioned repository.
- - Initial value must between or equal to 4gb and 256gb in increments of 4gb.
- - During expansion operations the increase must be between or equal to 4gb and 256gb in increments of 4gb.
- - This option has no effect during expansion if I(thin_volume_expansion_policy=="automatic").
- - Generally speaking you should almost always use I(thin_volume_expansion_policy=="automatic).
- required: false
- thin_volume_max_repo_size:
- description:
- - This is the maximum amount the thin volume repository will be allowed to grow.
- - Only has significance when I(thin_volume_expansion_policy=="automatic").
- - When the percentage I(thin_volume_repo_size) of I(thin_volume_max_repo_size) exceeds
- I(thin_volume_growth_alert_threshold) then a warning will be issued and the storage array will execute
- the I(thin_volume_expansion_policy) policy.
- - Expansion operations when I(thin_volume_expansion_policy=="automatic") will increase the maximum
- repository size.
- default: same as size (in size_unit)
- thin_volume_expansion_policy:
- description:
- - This is the thin volume expansion policy.
- - When I(thin_volume_expansion_policy=="automatic") and I(thin_volume_growth_alert_threshold) is exceed the
- I(thin_volume_max_repo_size) will be automatically expanded.
- - When I(thin_volume_expansion_policy=="manual") and I(thin_volume_growth_alert_threshold) is exceeded the
- storage system will wait for manual intervention.
- - The thin volume_expansion policy can not be modified on existing thin volumes in this module.
- - Generally speaking you should almost always use I(thin_volume_expansion_policy=="automatic).
- choices: ["automatic", "manual"]
- default: "automatic"
- version_added: 2.8
- thin_volume_growth_alert_threshold:
- description:
- - This is the thin provision repository utilization threshold (in percent).
- - When the percentage of used storage of the maximum repository size exceeds this value then a alert will
- be issued and the I(thin_volume_expansion_policy) will be executed.
- - Values must be between or equal to 10 and 99.
- default: 95
- version_added: 2.8
- owning_controller:
- description:
- - Specifies which controller will be the primary owner of the volume
- - Not specifying will allow the controller to choose ownership.
- required: false
- choices: ["A", "B"]
- version_added: 2.9
- ssd_cache_enabled:
- description:
- - Whether an existing SSD cache should be enabled on the volume (fails if no SSD cache defined)
- - The default value is to ignore existing SSD cache setting.
- type: bool
- default: false
- data_assurance_enabled:
- description:
- - Determines whether data assurance (DA) should be enabled for the volume
- - Only available when creating a new volume and on a storage pool with drives supporting the DA capability.
- type: bool
- default: false
- read_cache_enable:
- description:
- - Indicates whether read caching should be enabled for the volume.
- type: bool
- default: true
- version_added: 2.8
- read_ahead_enable:
- description:
- - Indicates whether or not automatic cache read-ahead is enabled.
- - This option has no effect on thinly provisioned volumes since the architecture for thin volumes cannot
- benefit from read ahead caching.
- type: bool
- default: true
- version_added: 2.8
- write_cache_enable:
- description:
- - Indicates whether write-back caching should be enabled for the volume.
- type: bool
- default: true
- version_added: 2.8
- cache_without_batteries:
- description:
- - Indicates whether caching should be used without battery backup.
- - Warning, M(cache_without_batteries==true) and the storage system looses power and there is no battery backup, data will be lost!
- type: bool
- default: false
- version_added: 2.9
- workload_name:
- description:
- - Label for the workload defined by the metadata.
- - When I(workload_name) and I(metadata) are specified then the defined workload will be added to the storage
- array.
- - When I(workload_name) exists on the storage array but the metadata is different then the workload
- definition will be updated. (Changes will update all associated volumes!)
- - Existing workloads can be retrieved using M(netapp_e_facts).
- required: false
- version_added: 2.8
- metadata:
- description:
- - Dictionary containing meta data for the use, user, location, etc of the volume (dictionary is arbitrarily
- defined for whatever the user deems useful)
- - When I(workload_name) exists on the storage array but the metadata is different then the workload
- definition will be updated. (Changes will update all associated volumes!)
- - I(workload_name) must be specified when I(metadata) are defined.
- type: dict
- required: false
- version_added: 2.8
- wait_for_initialization:
- description:
- - Forces the module to wait for expansion operations to complete before continuing.
- 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)
- netapp_e_volume:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- state: present
- name: volume
- storage_pool_name: storage_pool
- size: 300
- size_unit: gb
- workload_name: volume_tag
- metadata:
- key1: value1
- key2: value2
-- name: Create a thin volume
- netapp_e_volume:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- state: present
- name: volume1
- storage_pool_name: storage_pool
- size: 131072
- size_unit: gb
- thin_provision: true
- thin_volume_repo_size: 32
- thin_volume_max_repo_size: 1024
-- name: Expand thin volume's virtual size
- netapp_e_volume:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- state: present
- name: volume1
- storage_pool_name: storage_pool
- size: 262144
- size_unit: gb
- thin_provision: true
- thin_volume_repo_size: 32
- thin_volume_max_repo_size: 1024
-- name: Expand thin volume's maximum repository size
- netapp_e_volume:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- state: present
- name: volume1
- storage_pool_name: storage_pool
- size: 262144
- size_unit: gb
- thin_provision: true
- thin_volume_repo_size: 32
- thin_volume_max_repo_size: 2048
-- name: Delete volume
- netapp_e_volume:
- ssid: "{{ ssid }}"
- api_url: "{{ netapp_api_url }}"
- api_username: "{{ netapp_api_username }}"
- api_password: "{{ netapp_api_password }}"
- validate_certs: "{{ netapp_api_validate_certs }}"
- state: absent
- name: volume
-"""
-RETURN = """
-msg:
- description: State of volume
- type: str
- returned: always
- sample: "Standard volume [workload_vol_1] has been created."
-"""
-from time import sleep
-from ansible.module_utils.netapp import NetAppESeriesModule
-from ansible.module_utils._text import to_native
-
-
-class NetAppESeriesVolume(NetAppESeriesModule):
- VOLUME_CREATION_BLOCKING_TIMEOUT_SEC = 300
-
- def __init__(self):
- ansible_options = dict(
- state=dict(required=True, choices=["present", "absent"]),
- name=dict(required=True, type="str"),
- storage_pool_name=dict(type="str"),
- size_unit=dict(default="gb", choices=["bytes", "b", "kb", "mb", "gb", "tb", "pb", "eb", "zb", "yb"],
- type="str"),
- size=dict(type="float"),
- segment_size_kb=dict(type="int", default=128),
- owning_controller=dict(required=False, choices=['A', 'B']),
- ssd_cache_enabled=dict(type="bool", default=False),
- data_assurance_enabled=dict(type="bool", default=False),
- thin_provision=dict(type="bool", default=False),
- thin_volume_repo_size=dict(type="int"),
- thin_volume_max_repo_size=dict(type="float"),
- 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=True),
- write_cache_enable=dict(type="bool", default=True),
- cache_without_batteries=dict(type="bool", default=False),
- workload_name=dict(type="str", required=False),
- metadata=dict(type="dict", required=False),
- wait_for_initialization=dict(type="bool", default=False),
- initialization_timeout=dict(type="int", required=False))
-
- required_if = [
- ["state", "present", ["storage_pool_name", "size"]],
- ["thin_provision", "true", ["thin_volume_repo_size"]]
- ]
-
- super(NetAppESeriesVolume, self).__init__(ansible_options=ansible_options,
- web_services_version="02.00.0000.0000",
- supports_check_mode=True,
- required_if=required_if)
-
- args = self.module.params
- self.state = args["state"]
- self.name = args["name"]
- self.storage_pool_name = args["storage_pool_name"]
- self.size_unit = args["size_unit"]
- self.segment_size_kb = args["segment_size_kb"]
- if args["size"]:
- self.size_b = self.convert_to_aligned_bytes(args["size"])
-
- self.owning_controller_id = None
- if args["owning_controller"]:
- self.owning_controller_id = "070000000000000000000001" if args["owning_controller"] == "A" else "070000000000000000000002"
-
- self.read_cache_enable = args["read_cache_enable"]
- self.read_ahead_enable = args["read_ahead_enable"]
- self.write_cache_enable = args["write_cache_enable"]
- self.ssd_cache_enabled = args["ssd_cache_enabled"]
- self.cache_without_batteries = args["cache_without_batteries"]
- self.data_assurance_enabled = args["data_assurance_enabled"]
-
- self.thin_provision = args["thin_provision"]
- self.thin_volume_expansion_policy = args["thin_volume_expansion_policy"]
- self.thin_volume_growth_alert_threshold = int(args["thin_volume_growth_alert_threshold"])
- self.thin_volume_repo_size_b = None
- self.thin_volume_max_repo_size_b = None
-
- if args["thin_volume_repo_size"]:
- self.thin_volume_repo_size_b = self.convert_to_aligned_bytes(args["thin_volume_repo_size"])
- if args["thin_volume_max_repo_size"]:
- self.thin_volume_max_repo_size_b = self.convert_to_aligned_bytes(args["thin_volume_max_repo_size"])
-
- 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
- metadata = []
- if self.metadata:
- if not self.workload_name:
- self.module.fail_json(msg="When metadata is specified then the name for the workload must be specified."
- " Array [%s]." % self.ssid)
- for key in self.metadata.keys():
- metadata.append(dict(key=key, value=self.metadata[key]))
- self.metadata = metadata
-
- if self.thin_provision:
- if not self.thin_volume_max_repo_size_b:
- self.thin_volume_max_repo_size_b = self.size_b
-
- if not self.thin_volume_expansion_policy:
- self.thin_volume_expansion_policy = "automatic"
-
- if self.size_b > 256 * 1024 ** 4:
- self.module.fail_json(msg="Thin provisioned volumes must be less than or equal to 256tb is size."
- " Attempted size [%sg]" % (self.size_b * 1024 ** 3))
-
- if (self.thin_volume_repo_size_b and self.thin_volume_max_repo_size_b and
- self.thin_volume_repo_size_b > self.thin_volume_max_repo_size_b):
- self.module.fail_json(msg="The initial size of the thin volume must not be larger than the maximum"
- " repository size. Array [%s]." % self.ssid)
-
- if self.thin_volume_growth_alert_threshold < 10 or self.thin_volume_growth_alert_threshold > 99:
- self.module.fail_json(msg="thin_volume_growth_alert_threshold must be between or equal to 10 and 99."
- "thin_volume_growth_alert_threshold [%s]. Array [%s]."
- % (self.thin_volume_growth_alert_threshold, self.ssid))
-
- self.volume_detail = None
- self.pool_detail = None
- self.workload_id = None
-
- def convert_to_aligned_bytes(self, size):
- """Convert size to the truncated byte size that aligns on the segment size."""
- size_bytes = int(size * self.SIZE_UNIT_MAP[self.size_unit])
- segment_size_bytes = int(self.segment_size_kb * self.SIZE_UNIT_MAP["kb"])
- segment_count = int(size_bytes / segment_size_bytes)
- return segment_count * segment_size_bytes
-
- def get_volume(self):
- """Retrieve volume details from storage array."""
- volumes = list()
- thin_volumes = list()
- try:
- rc, volumes = self.request("storage-systems/%s/volumes" % self.ssid)
- except Exception as err:
- self.module.fail_json(msg="Failed to obtain list of thick volumes. Array Id [%s]. Error[%s]."
- % (self.ssid, to_native(err)))
- try:
- rc, thin_volumes = self.request("storage-systems/%s/thin-volumes" % self.ssid)
- except Exception as err:
- self.module.fail_json(msg="Failed to obtain list of thin volumes. Array Id [%s]. Error[%s]."
- % (self.ssid, to_native(err)))
-
- volume_detail = [volume for volume in volumes + thin_volumes if volume["name"] == self.name]
- return volume_detail[0] if volume_detail else dict()
-
- def wait_for_volume_availability(self, retries=VOLUME_CREATION_BLOCKING_TIMEOUT_SEC / 5):
- """Waits until volume becomes available.
-
- :raises AnsibleFailJson when retries are exhausted.
- """
- if retries == 0:
- 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():
- 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 = "unknown"
- percent_complete = None
- while action != "complete":
- sleep(5)
-
- try:
- 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 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):
- """Retrieve storage pool details from the storage array."""
- storage_pools = list()
- try:
- rc, storage_pools = self.request("storage-systems/%s/storage-pools" % self.ssid)
- except Exception as err:
- self.module.fail_json(msg="Failed to obtain list of storage pools. Array Id [%s]. Error[%s]."
- % (self.ssid, to_native(err)))
-
- pool_detail = [storage_pool for storage_pool in storage_pools if storage_pool["name"] == self.storage_pool_name]
- return pool_detail[0] if pool_detail else dict()
-
- def check_storage_pool_sufficiency(self):
- """Perform a series of checks as to the sufficiency of the storage pool for the volume."""
- if not self.pool_detail:
- self.module.fail_json(msg='Requested storage pool (%s) not found' % self.storage_pool_name)
-
- if not self.volume_detail:
- if self.thin_provision and not self.pool_detail['diskPool']:
- self.module.fail_json(msg='Thin provisioned volumes can only be created on raid disk pools.')
-
- if (self.data_assurance_enabled and not
- (self.pool_detail["protectionInformationCapabilities"]["protectionInformationCapable"] and
- self.pool_detail["protectionInformationCapabilities"]["protectionType"] == "type2Protection")):
- self.module.fail_json(msg="Data Assurance (DA) requires the storage pool to be DA-compatible."
- " Array [%s]." % self.ssid)
-
- if int(self.pool_detail["freeSpace"]) < self.size_b and not self.thin_provision:
- self.module.fail_json(msg="Not enough storage pool free space available for the volume's needs."
- " Array [%s]." % self.ssid)
- else:
- # Check for expansion
- if (int(self.pool_detail["freeSpace"]) < int(self.volume_detail["totalSizeInBytes"]) - self.size_b and
- not self.thin_provision):
- self.module.fail_json(msg="Not enough storage pool free space available for the volume's needs."
- " Array [%s]." % self.ssid)
-
- def update_workload_tags(self, check_mode=False):
- """Check the status of the workload tag and update storage array definitions if necessary.
-
- When the workload attributes are not provided but an existing workload tag name is, then the attributes will be
- used.
-
- :return bool: Whether changes were required to be made."""
- change_required = False
- workload_tags = None
- request_body = None
- ansible_profile_id = None
-
- if self.workload_name:
- try:
- rc, workload_tags = self.request("storage-systems/%s/workloads" % self.ssid)
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve storage array workload tags. Array [%s]" % self.ssid)
-
- # Generate common indexed Ansible workload tag
- current_tag_index_list = [int(pair["value"].replace("ansible_workload_", ""))
- for tag in workload_tags for pair in tag["workloadAttributes"]
- if pair["key"] == "profileId" and "ansible_workload_" in pair["value"] and
- str(pair["value"]).replace("ansible_workload_", "").isdigit()]
-
- tag_index = 1
- if current_tag_index_list:
- tag_index = max(current_tag_index_list) + 1
-
- ansible_profile_id = "ansible_workload_%d" % tag_index
- request_body = dict(name=self.workload_name,
- profileId=ansible_profile_id,
- workloadInstanceIndex=None,
- isValid=True)
-
- # evaluate and update storage array when needed
- for tag in workload_tags:
- if tag["name"] == self.workload_name:
- self.workload_id = tag["id"]
-
- if not self.metadata:
- break
-
- # Determine if core attributes (everything but profileId) is the same
- metadata_set = set(tuple(sorted(attr.items())) for attr in self.metadata)
- tag_set = set(tuple(sorted(attr.items()))
- for attr in tag["workloadAttributes"] if attr["key"] != "profileId")
- if metadata_set != tag_set:
- self.module.log("Workload tag change is required!")
- change_required = True
-
- # only perform the required action when check_mode==False
- if change_required and not check_mode:
- self.metadata.append(dict(key="profileId", value=ansible_profile_id))
- request_body.update(dict(isNewWorkloadInstance=False,
- isWorkloadDataInitialized=True,
- isWorkloadCardDataToBeReset=True,
- workloadAttributes=self.metadata))
- try:
- rc, resp = self.request("storage-systems/%s/workloads/%s" % (self.ssid, tag["id"]),
- data=request_body, method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to create new workload tag. Array [%s]. Error [%s]"
- % (self.ssid, to_native(error)))
- self.module.log("Workload tag [%s] required change." % self.workload_name)
- break
-
- # existing workload tag not found so create new workload tag
- else:
- change_required = True
- self.module.log("Workload tag creation is required!")
-
- if change_required and not check_mode:
- if self.metadata:
- self.metadata.append(dict(key="profileId", value=ansible_profile_id))
- else:
- self.metadata = [dict(key="profileId", value=ansible_profile_id)]
-
- request_body.update(dict(isNewWorkloadInstance=True,
- isWorkloadDataInitialized=False,
- isWorkloadCardDataToBeReset=False,
- workloadAttributes=self.metadata))
- try:
- rc, resp = self.request("storage-systems/%s/workloads" % self.ssid,
- method="POST", data=request_body)
- self.workload_id = resp["id"]
- except Exception as error:
- self.module.fail_json(msg="Failed to create new workload tag. Array [%s]. Error [%s]"
- % (self.ssid, to_native(error)))
- self.module.log("Workload tag [%s] was added." % self.workload_name)
-
- return change_required
-
- def get_volume_property_changes(self):
- """Retrieve the volume update request body when change(s) are required.
-
- :raise AnsibleFailJson when attempting to change segment size on existing volume.
- :return dict: request body when change(s) to a volume's properties are required.
- """
- change = False
- request_body = dict(flashCache=self.ssd_cache_enabled, metaTags=[],
- cacheSettings=dict(readCacheEnable=self.read_cache_enable,
- writeCacheEnable=self.write_cache_enable))
-
- # check for invalid modifications
- if self.segment_size_kb * 1024 != int(self.volume_detail["segmentSize"]):
- self.module.fail_json(msg="Existing volume segment size is %s and cannot be modified."
- % self.volume_detail["segmentSize"])
-
- # common thick/thin volume properties
- if (self.read_cache_enable != self.volume_detail["cacheSettings"]["readCacheEnable"] or
- self.write_cache_enable != self.volume_detail["cacheSettings"]["writeCacheEnable"] or
- self.ssd_cache_enabled != self.volume_detail["flashCached"]):
- change = True
-
- # controller ownership
- if self.owning_controller_id and self.owning_controller_id != self.volume_detail["preferredManager"]:
- change = True
- request_body.update(dict(owningControllerId=self.owning_controller_id))
-
- if self.workload_name:
- request_body.update(dict(metaTags=[dict(key="workloadId", value=self.workload_id),
- dict(key="volumeTypeId", value="volume")]))
- if {"key": "workloadId", "value": self.workload_id} not in self.volume_detail["metadata"]:
- change = True
- elif self.volume_detail["metadata"]:
- change = True
-
- # thick/thin volume specific properties
- if self.thin_provision:
- if self.thin_volume_growth_alert_threshold != int(self.volume_detail["growthAlertThreshold"]):
- change = True
- request_body.update(dict(growthAlertThreshold=self.thin_volume_growth_alert_threshold))
- if self.thin_volume_expansion_policy != self.volume_detail["expansionPolicy"]:
- change = True
- request_body.update(dict(expansionPolicy=self.thin_volume_expansion_policy))
- else:
- if self.read_ahead_enable != (int(self.volume_detail["cacheSettings"]["readAheadMultiplier"]) > 0):
- change = True
- request_body["cacheSettings"].update(dict(readAheadEnable=self.read_ahead_enable))
- if self.cache_without_batteries != self.volume_detail["cacheSettings"]["cwob"]:
- change = True
- request_body["cacheSettings"].update(dict(cacheWithoutBatteries=self.cache_without_batteries))
-
- return request_body if change else dict()
-
- def get_expand_volume_changes(self):
- """Expand the storage specifications for the existing thick/thin volume.
-
- :raise AnsibleFailJson when a thick/thin volume expansion request fails.
- :return dict: dictionary containing all the necessary values for volume expansion request
- """
- request_body = dict()
-
- if self.size_b < int(self.volume_detail["capacity"]):
- self.module.fail_json(msg="Reducing the size of volumes is not permitted. Volume [%s]. Array [%s]"
- % (self.name, self.ssid))
-
- if self.volume_detail["thinProvisioned"]:
- if self.size_b > int(self.volume_detail["capacity"]):
- request_body.update(dict(sizeUnit="bytes", newVirtualSize=self.size_b))
- self.module.log("Thin volume virtual size have been expanded.")
-
- if self.volume_detail["expansionPolicy"] == "automatic":
- if self.thin_volume_max_repo_size_b > int(self.volume_detail["provisionedCapacityQuota"]):
- request_body.update(dict(sizeUnit="bytes", newRepositorySize=self.thin_volume_max_repo_size_b))
- self.module.log("Thin volume maximum repository size have been expanded (automatic policy).")
-
- elif self.volume_detail["expansionPolicy"] == "manual":
- if self.thin_volume_repo_size_b > int(self.volume_detail["currentProvisionedCapacity"]):
- change = self.thin_volume_repo_size_b - int(self.volume_detail["currentProvisionedCapacity"])
- if change < 4 * 1024 ** 3 or change > 256 * 1024 ** 3 or change % (4 * 1024 ** 3) != 0:
- self.module.fail_json(msg="The thin volume repository increase must be between or equal to 4gb"
- " and 256gb in increments of 4gb. Attempted size [%sg]."
- % (self.thin_volume_repo_size_b * 1024 ** 3))
-
- request_body.update(dict(sizeUnit="bytes", newRepositorySize=self.thin_volume_repo_size_b))
- self.module.log("Thin volume maximum repository size have been expanded (manual policy).")
-
- elif self.size_b > int(self.volume_detail["capacity"]):
- request_body.update(dict(sizeUnit="bytes", expansionSize=self.size_b))
- self.module.log("Volume storage capacities have been expanded.")
-
- return request_body
-
- def create_volume(self):
- """Create thick/thin volume according to the specified criteria."""
- body = dict(name=self.name, poolId=self.pool_detail["id"], sizeUnit="bytes",
- dataAssuranceEnabled=self.data_assurance_enabled)
-
- if self.thin_provision:
- body.update(dict(virtualSize=self.size_b,
- repositorySize=self.thin_volume_repo_size_b,
- maximumRepositorySize=self.thin_volume_max_repo_size_b,
- expansionPolicy=self.thin_volume_expansion_policy,
- growthAlertThreshold=self.thin_volume_growth_alert_threshold))
- try:
- rc, volume = self.request("storage-systems/%s/thin-volumes" % self.ssid, data=body, method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to create thin volume. Volume [%s]. Array Id [%s]. Error[%s]."
- % (self.name, self.ssid, to_native(error)))
-
- self.module.log("New thin volume created [%s]." % self.name)
-
- else:
- body.update(dict(size=self.size_b, segSize=self.segment_size_kb))
- try:
- rc, volume = self.request("storage-systems/%s/volumes" % self.ssid, data=body, method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to create volume. Volume [%s]. Array Id [%s]. Error[%s]."
- % (self.name, self.ssid, to_native(error)))
-
- self.module.log("New volume created [%s]." % self.name)
-
- def update_volume_properties(self):
- """Update existing thin-volume or volume properties.
-
- :raise AnsibleFailJson when either thick/thin volume update request fails.
- :return bool: whether update was applied
- """
- self.wait_for_volume_availability()
- self.volume_detail = self.get_volume()
-
- request_body = self.get_volume_property_changes()
-
- if request_body:
- if self.thin_provision:
- try:
- rc, resp = self.request("storage-systems/%s/thin-volumes/%s"
- % (self.ssid, self.volume_detail["id"]), data=request_body, method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to update thin volume properties. Volume [%s]. Array Id [%s]."
- " Error[%s]." % (self.name, self.ssid, to_native(error)))
- else:
- try:
- rc, resp = self.request("storage-systems/%s/volumes/%s" % (self.ssid, self.volume_detail["id"]),
- data=request_body, method="POST")
- except Exception as error:
- self.module.fail_json(msg="Failed to update volume properties. Volume [%s]. Array Id [%s]."
- " Error[%s]." % (self.name, self.ssid, to_native(error)))
- return True
- return False
-
- def expand_volume(self):
- """Expand the storage specifications for the existing thick/thin volume.
-
- :raise AnsibleFailJson when a thick/thin volume expansion request fails.
- """
- request_body = self.get_expand_volume_changes()
- if request_body:
- if self.volume_detail["thinProvisioned"]:
- try:
- rc, resp = self.request("storage-systems/%s/thin-volumes/%s/expand"
- % (self.ssid, self.volume_detail["id"]), data=request_body, method="POST")
- except Exception as err:
- self.module.fail_json(msg="Failed to expand thin volume. Volume [%s]. Array Id [%s]. Error[%s]."
- % (self.name, self.ssid, to_native(err)))
- self.module.log("Thin volume specifications have been expanded.")
-
- else:
- try:
- rc, resp = self.request(
- "storage-systems/%s/volumes/%s/expand" % (self.ssid, self.volume_detail['id']),
- data=request_body, method="POST")
- except Exception as err:
- self.module.fail_json(msg="Failed to expand volume. Volume [%s]. Array Id [%s]. Error[%s]."
- % (self.name, self.ssid, to_native(err)))
-
- self.module.log("Volume storage capacities have been expanded.")
-
- def delete_volume(self):
- """Delete existing thin/thick volume."""
- if self.thin_provision:
- try:
- rc, resp = self.request("storage-systems/%s/thin-volumes/%s" % (self.ssid, self.volume_detail["id"]),
- method="DELETE")
- except Exception as error:
- self.module.fail_json(msg="Failed to delete thin volume. Volume [%s]. Array Id [%s]. Error[%s]."
- % (self.name, self.ssid, to_native(error)))
- self.module.log("Thin volume deleted [%s]." % self.name)
- else:
- try:
- rc, resp = self.request("storage-systems/%s/volumes/%s" % (self.ssid, self.volume_detail["id"]),
- method="DELETE")
- except Exception as error:
- self.module.fail_json(msg="Failed to delete volume. Volume [%s]. Array Id [%s]. Error[%s]."
- % (self.name, self.ssid, to_native(error)))
- self.module.log("Volume deleted [%s]." % self.name)
-
- def apply(self):
- """Determine and apply any changes necessary to satisfy the specified criteria.
-
- :raise AnsibleExitJson when completes successfully"""
- change = False
- msg = None
-
- self.volume_detail = self.get_volume()
- self.pool_detail = self.get_storage_pool()
-
- # Determine whether changes need to be applied to existing workload tags
- if self.state == 'present' and self.update_workload_tags(check_mode=True):
- change = True
-
- # Determine if any changes need to be applied
- if self.volume_detail:
- if self.state == 'absent':
- change = True
-
- elif self.state == 'present':
- if self.get_expand_volume_changes() or self.get_volume_property_changes():
- change = True
-
- elif self.state == 'present':
- if self.thin_provision and (self.thin_volume_repo_size_b < 4 * 1024 ** 3 or
- self.thin_volume_repo_size_b > 256 * 1024 ** 3 or
- self.thin_volume_repo_size_b % (4 * 1024 ** 3) != 0):
- self.module.fail_json(msg="The initial thin volume repository size must be between 4gb and 256gb in"
- " increments of 4gb. Attempted size [%sg]."
- % (self.thin_volume_repo_size_b * 1024 ** 3))
- change = True
-
- self.module.log("Update required: [%s]." % change)
-
- # Apply any necessary changes
- if change and not self.module.check_mode:
- if self.state == 'present':
- if self.update_workload_tags():
- msg = "Workload tag change occurred."
-
- if not self.volume_detail:
- self.check_storage_pool_sufficiency()
- self.create_volume()
- self.update_volume_properties()
- msg = msg[:-1] + " and volume [%s] was created." if msg else "Volume [%s] has been created."
- else:
- if self.update_volume_properties():
- msg = "Volume [%s] properties were updated."
-
- if self.get_expand_volume_changes():
- 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."
-
- else:
- msg = "Volume [%s] does not exist." if self.state == 'absent' else "Volume [%s] exists."
-
- self.module.exit_json(msg=(msg % self.name if msg and "%s" in msg else msg), changed=change)
-
-
-def main():
- volume = NetAppESeriesVolume()
- volume.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py b/lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py
deleted file mode 100644
index 3f86197baf..0000000000
--- a/lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py
+++ /dev/null
@@ -1,400 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2016, 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = """
----
-module: netapp_e_volume_copy
-short_description: NetApp E-Series create volume copy pairs
-description:
- - Create and delete snapshots images on volume groups for NetApp E-series storage arrays.
-version_added: '2.2'
-author: Kevin Hulquest (@hulquest)
-extends_documentation_fragment:
- - netapp.eseries
-options:
- api_username:
- required: true
- description:
- - The username to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_password:
- required: true
- description:
- - The password to authenticate with the SANtricity WebServices Proxy or embedded REST API.
- api_url:
- required: true
- description:
- - The url to the SANtricity WebServices Proxy or embedded REST API, for example C(https://prod-1.wahoo.acme.com/devmgr/v2).
- validate_certs:
- required: false
- default: true
- description:
- - Should https certificates be validated?
- source_volume_id:
- description:
- - The id of the volume copy source.
- - If used, must be paired with destination_volume_id
- - Mutually exclusive with volume_copy_pair_id, and search_volume_id
- destination_volume_id:
- description:
- - The id of the volume copy destination.
- - If used, must be paired with source_volume_id
- - Mutually exclusive with volume_copy_pair_id, and search_volume_id
- volume_copy_pair_id:
- description:
- - The id of a given volume copy pair
- - Mutually exclusive with destination_volume_id, source_volume_id, and search_volume_id
- - Can use to delete or check presence of volume pairs
- - Must specify this or (destination_volume_id and source_volume_id)
- state:
- description:
- - Whether the specified volume copy pair should exist or not.
- required: True
- choices: ['present', 'absent']
- create_copy_pair_if_does_not_exist:
- description:
- - Defines if a copy pair will be created if it does not exist.
- - If set to True destination_volume_id and source_volume_id are required.
- type: bool
- default: True
- start_stop_copy:
- description:
- - starts a re-copy or stops a copy in progress
- - "Note: If you stop the initial file copy before it it done the copy pair will be destroyed"
- - Requires volume_copy_pair_id
- search_volume_id:
- description:
- - Searches for all valid potential target and source volumes that could be used in a copy_pair
- - Mutually exclusive with volume_copy_pair_id, destination_volume_id and source_volume_id
-"""
-RESULTS = """
-"""
-EXAMPLES = """
----
-msg:
- description: Success message
- returned: success
- type: str
- sample: Json facts for the volume copy that was created.
-"""
-RETURN = """
-msg:
- description: Success message
- returned: success
- type: str
- sample: Created Volume Copy Pair with ID
-"""
-
-import json
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-from ansible.module_utils.netapp import request
-
-HEADERS = {
- "Content-Type": "application/json",
- "Accept": "application/json",
-}
-
-
-def find_volume_copy_pair_id_from_source_volume_id_and_destination_volume_id(params):
- get_status = 'storage-systems/%s/volume-copy-jobs' % params['ssid']
- url = params['api_url'] + get_status
-
- (rc, resp) = request(url, method='GET', url_username=params['api_username'],
- url_password=params['api_password'], headers=HEADERS,
- validate_certs=params['validate_certs'])
-
- volume_copy_pair_id = None
- for potential_copy_pair in resp:
- if potential_copy_pair['sourceVolume'] == params['source_volume_id']:
- if potential_copy_pair['sourceVolume'] == params['source_volume_id']:
- volume_copy_pair_id = potential_copy_pair['id']
-
- return volume_copy_pair_id
-
-
-def create_copy_pair(params):
- get_status = 'storage-systems/%s/volume-copy-jobs' % params['ssid']
- url = params['api_url'] + get_status
-
- rData = {
- "sourceId": params['source_volume_id'],
- "targetId": params['destination_volume_id']
- }
-
- (rc, resp) = request(url, data=json.dumps(rData), ignore_errors=True, method='POST',
- url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS,
- validate_certs=params['validate_certs'])
- if rc != 200:
- return False, (rc, resp)
- else:
- return True, (rc, resp)
-
-
-def delete_copy_pair_by_copy_pair_id(params):
- get_status = 'storage-systems/%s/volume-copy-jobs/%s?retainRepositories=false' % (
- params['ssid'], params['volume_copy_pair_id'])
- url = params['api_url'] + get_status
-
- (rc, resp) = request(url, ignore_errors=True, method='DELETE',
- url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS,
- validate_certs=params['validate_certs'])
- if rc != 204:
- return False, (rc, resp)
- else:
- return True, (rc, resp)
-
-
-def find_volume_copy_pair_id_by_volume_copy_pair_id(params):
- get_status = 'storage-systems/%s/volume-copy-jobs/%s?retainRepositories=false' % (
- params['ssid'], params['volume_copy_pair_id'])
- url = params['api_url'] + get_status
-
- (rc, resp) = request(url, ignore_errors=True, method='DELETE',
- url_username=params['api_username'], url_password=params['api_password'], headers=HEADERS,
- validate_certs=params['validate_certs'])
- if rc != 200:
- return False, (rc, resp)
- else:
- return True, (rc, resp)
-
-
-def start_stop_copy(params):
- get_status = 'storage-systems/%s/volume-copy-jobs-control/%s?control=%s' % (
- params['ssid'], params['volume_copy_pair_id'], params['start_stop_copy'])
- url = params['api_url'] + get_status
-
- (response_code, response_data) = request(url, ignore_errors=True, method='POST',
- url_username=params['api_username'], url_password=params['api_password'],
- headers=HEADERS,
- validate_certs=params['validate_certs'])
-
- if response_code == 200:
- return True, response_data[0]['percentComplete']
- else:
- return False, response_data
-
-
-def check_copy_status(params):
- get_status = 'storage-systems/%s/volume-copy-jobs-control/%s' % (
- params['ssid'], params['volume_copy_pair_id'])
- url = params['api_url'] + get_status
-
- (response_code, response_data) = request(url, ignore_errors=True, method='GET',
- url_username=params['api_username'], url_password=params['api_password'],
- headers=HEADERS,
- validate_certs=params['validate_certs'])
-
- if response_code == 200:
- if response_data['percentComplete'] != -1:
-
- return True, response_data['percentComplete']
- else:
- return False, response_data['percentComplete']
- else:
- return False, response_data
-
-
-def find_valid_copy_pair_targets_and_sources(params):
- get_status = 'storage-systems/%s/volumes' % params['ssid']
- url = params['api_url'] + get_status
-
- (response_code, response_data) = request(url, ignore_errors=True, method='GET',
- url_username=params['api_username'], url_password=params['api_password'],
- headers=HEADERS,
- validate_certs=params['validate_certs'])
-
- if response_code == 200:
- source_capacity = None
- candidates = []
- for volume in response_data:
- if volume['id'] == params['search_volume_id']:
- source_capacity = volume['capacity']
- else:
- candidates.append(volume)
-
- potential_sources = []
- potential_targets = []
-
- for volume in candidates:
- if volume['capacity'] > source_capacity:
- if volume['volumeCopyTarget'] is False:
- if volume['volumeCopySource'] is False:
- potential_targets.append(volume['id'])
- else:
- if volume['volumeCopyTarget'] is False:
- if volume['volumeCopySource'] is False:
- potential_sources.append(volume['id'])
-
- return potential_targets, potential_sources
-
- else:
- raise Exception("Response [%s]" % response_code)
-
-
-def main():
- module = AnsibleModule(argument_spec=dict(
- source_volume_id=dict(type='str'),
- destination_volume_id=dict(type='str'),
- copy_priority=dict(required=False, default=0, type='int'),
- ssid=dict(required=True, type='str'),
- api_url=dict(required=True),
- api_username=dict(required=False),
- api_password=dict(required=False, no_log=True),
- validate_certs=dict(required=False, default=True),
- targetWriteProtected=dict(required=False, default=True, type='bool'),
- onlineCopy=dict(required=False, default=False, type='bool'),
- volume_copy_pair_id=dict(type='str'),
- status=dict(required=True, choices=['present', 'absent'], type='str'),
- create_copy_pair_if_does_not_exist=dict(required=False, default=True, type='bool'),
- start_stop_copy=dict(required=False, choices=['start', 'stop'], type='str'),
- search_volume_id=dict(type='str'),
- ),
- mutually_exclusive=[['volume_copy_pair_id', 'destination_volume_id'],
- ['volume_copy_pair_id', 'source_volume_id'],
- ['volume_copy_pair_id', 'search_volume_id'],
- ['search_volume_id', 'destination_volume_id'],
- ['search_volume_id', 'source_volume_id'],
- ],
- required_together=[['source_volume_id', 'destination_volume_id'],
- ],
- required_if=[["create_copy_pair_if_does_not_exist", True, ['source_volume_id', 'destination_volume_id'], ],
- ["start_stop_copy", 'stop', ['volume_copy_pair_id'], ],
- ["start_stop_copy", 'start', ['volume_copy_pair_id'], ],
- ]
-
- )
- params = module.params
-
- if not params['api_url'].endswith('/'):
- params['api_url'] += '/'
-
- # Check if we want to search
- if params['search_volume_id'] is not None:
- try:
- potential_targets, potential_sources = find_valid_copy_pair_targets_and_sources(params)
- except Exception as e:
- module.fail_json(msg="Failed to find valid copy pair candidates. Error [%s]" % to_native(e))
-
- module.exit_json(changed=False,
- msg=' Valid source devices found: %s Valid target devices found: %s' % (len(potential_sources), len(potential_targets)),
- search_volume_id=params['search_volume_id'],
- valid_targets=potential_targets,
- valid_sources=potential_sources)
-
- # Check if we want to start or stop a copy operation
- if params['start_stop_copy'] == 'start' or params['start_stop_copy'] == 'stop':
-
- # Get the current status info
- currenty_running, status_info = check_copy_status(params)
-
- # If we want to start
- if params['start_stop_copy'] == 'start':
-
- # If we have already started
- if currenty_running is True:
- module.exit_json(changed=False, msg='Volume Copy Pair copy has started.',
- volume_copy_pair_id=params['volume_copy_pair_id'], percent_done=status_info)
- # If we need to start
- else:
-
- start_status, info = start_stop_copy(params)
-
- if start_status is True:
- module.exit_json(changed=True, msg='Volume Copy Pair copy has started.',
- volume_copy_pair_id=params['volume_copy_pair_id'], percent_done=info)
- else:
- module.fail_json(msg="Could not start volume copy pair Error: %s" % info)
-
- # If we want to stop
- else:
- # If it has already stopped
- if currenty_running is False:
- module.exit_json(changed=False, msg='Volume Copy Pair copy is stopped.',
- volume_copy_pair_id=params['volume_copy_pair_id'])
-
- # If we need to stop it
- else:
- start_status, info = start_stop_copy(params)
-
- if start_status is True:
- module.exit_json(changed=True, msg='Volume Copy Pair copy has been stopped.',
- volume_copy_pair_id=params['volume_copy_pair_id'])
- else:
- module.fail_json(msg="Could not stop volume copy pair Error: %s" % info)
-
- # If we want the copy pair to exist we do this stuff
- if params['status'] == 'present':
-
- # We need to check if it exists first
- if params['volume_copy_pair_id'] is None:
- params['volume_copy_pair_id'] = find_volume_copy_pair_id_from_source_volume_id_and_destination_volume_id(
- params)
-
- # If no volume copy pair is found we need need to make it.
- if params['volume_copy_pair_id'] is None:
-
- # In order to create we can not do so with just a volume_copy_pair_id
-
- copy_began_status, (rc, resp) = create_copy_pair(params)
-
- if copy_began_status is True:
- module.exit_json(changed=True, msg='Created Volume Copy Pair with ID: %s' % resp['id'])
- else:
- module.fail_json(msg="Could not create volume copy pair Code: %s Error: %s" % (rc, resp))
-
- # If it does exist we do nothing
- else:
- # We verify that it exists
- exist_status, (exist_status_code, exist_status_data) = find_volume_copy_pair_id_by_volume_copy_pair_id(
- params)
-
- if exist_status:
- module.exit_json(changed=False,
- msg=' Volume Copy Pair with ID: %s exists' % params['volume_copy_pair_id'])
- else:
- if exist_status_code == 404:
- module.fail_json(
- msg=' Volume Copy Pair with ID: %s does not exist. Can not create without source_volume_id and destination_volume_id' %
- params['volume_copy_pair_id'])
- else:
- module.fail_json(msg="Could not find volume copy pair Code: %s Error: %s" % (
- exist_status_code, exist_status_data))
-
- module.fail_json(msg="Done")
-
- # If we want it to not exist we do this
- else:
-
- if params['volume_copy_pair_id'] is None:
- params['volume_copy_pair_id'] = find_volume_copy_pair_id_from_source_volume_id_and_destination_volume_id(
- params)
-
- # We delete it by the volume_copy_pair_id
- delete_status, (delete_status_code, delete_status_data) = delete_copy_pair_by_copy_pair_id(params)
-
- if delete_status is True:
- module.exit_json(changed=True,
- msg=' Volume Copy Pair with ID: %s was deleted' % params['volume_copy_pair_id'])
- else:
- if delete_status_code == 404:
- module.exit_json(changed=False,
- msg=' Volume Copy Pair with ID: %s does not exist' % params['volume_copy_pair_id'])
- else:
- module.fail_json(msg="Could not delete volume copy pair Code: %s Error: %s" % (
- delete_status_code, delete_status_data))
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/purestorage/_purefa_facts.py b/lib/ansible/modules/storage/purestorage/_purefa_facts.py
deleted file mode 100644
index a5da8191ff..0000000000
--- a/lib/ansible/modules/storage/purestorage/_purefa_facts.py
+++ /dev/null
@@ -1,862 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# (c) 2018, Simon Dodsley (simon@purestorage.com)
-# 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = r'''
----
-module: purefa_facts
-version_added: '2.6'
-deprecated:
- removed_in: '2.13'
- why: Deprecated in favor of C(_info) module.
- alternative: Use M(purefa_info) instead.
-short_description: Collect facts from Pure Storage FlashArray
-description:
- - Collect facts information from a Pure Storage Flasharray running the
- Purity//FA operating system. By default, the module will collect basic
- fact information including hosts, host groups, protection
- groups and volume counts. Additional fact information can be collected
- based on the configured set of arguments.
-author:
- - Pure Storage ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
-options:
- gather_subset:
- description:
- - When supplied, this argument will define the facts to be collected.
- Possible values for this include all, minimum, config, performance,
- capacity, network, subnet, interfaces, hgroups, pgroups, hosts,
- admins, volumes, snapshots, pods, vgroups, offload, apps and arrays.
- type: list
- required: false
- default: minimum
-extends_documentation_fragment:
- - purestorage.fa
-'''
-
-EXAMPLES = r'''
-- name: collect default set of facts
- purefa_facts:
- fa_url: 10.10.10.2
- api_token: e31060a7-21fc-e277-6240-25983c6c4592
-
-- name: collect configuration and capacity facts
- purefa_facts:
- gather_subset:
- - config
- - capacity
- fa_url: 10.10.10.2
- api_token: e31060a7-21fc-e277-6240-25983c6c4592
-
-- name: collect all facts
- purefa_facts:
- gather_subset:
- - all
- fa_url: 10.10.10.2
- api_token: e31060a7-21fc-e277-6240-25983c6c4592
-'''
-
-RETURN = r'''
-ansible_facts:
- description: Returns the facts collected from the FlashArray
- returned: always
- type: complex
- sample: {
- "capacity": {},
- "config": {
- "directory_service": {
- "array_admin_group": null,
- "base_dn": null,
- "bind_password": null,
- "bind_user": null,
- "check_peer": false,
- "enabled": false,
- "group_base": null,
- "readonly_group": null,
- "storage_admin_group": null,
- "uri": []
- },
- "dns": {
- "domain": "domain.com",
- "nameservers": [
- "8.8.8.8",
- "8.8.4.4"
- ]
- },
- "ntp": [
- "0.ntp.pool.org",
- "1.ntp.pool.org",
- "2.ntp.pool.org",
- "3.ntp.pool.org"
- ],
- "smtp": [
- {
- "enabled": true,
- "name": "alerts@acme.com"
- },
- {
- "enabled": true,
- "name": "user@acme.com"
- }
- ],
- "snmp": [
- {
- "auth_passphrase": null,
- "auth_protocol": null,
- "community": null,
- "host": "localhost",
- "name": "localhost",
- "privacy_passphrase": null,
- "privacy_protocol": null,
- "user": null,
- "version": "v2c"
- }
- ],
- "ssl_certs": {
- "country": null,
- "email": null,
- "issued_by": "",
- "issued_to": "",
- "key_size": 2048,
- "locality": null,
- "organization": "Acme Storage, Inc.",
- "organizational_unit": "Acme Storage, Inc.",
- "state": null,
- "status": "self-signed",
- "valid_from": "2017-08-11T23:09:06Z",
- "valid_to": "2027-08-09T23:09:06Z"
- },
- "syslog": []
- },
- "default": {
- "array_name": "flasharray1",
- "connected_arrays": 1,
- "hostgroups": 0,
- "hosts": 10,
- "pods": 3,
- "protection_groups": 1,
- "purity_version": "5.0.4",
- "snapshots": 1,
- "volume_groups": 2
- },
- "hgroups": {},
- "hosts": {
- "host1": {
- "hgroup": null,
- "iqn": [
- "iqn.1994-05.com.redhat:2f6f5715a533"
- ],
- "wwn": []
- },
- "host2": {
- "hgroup": null,
- "iqn": [
- "iqn.1994-05.com.redhat:d17fb13fe0b"
- ],
- "wwn": []
- },
- "host3": {
- "hgroup": null,
- "iqn": [
- "iqn.1994-05.com.redhat:97b1351bfb2"
- ],
- "wwn": []
- },
- "host4": {
- "hgroup": null,
- "iqn": [
- "iqn.1994-05.com.redhat:dd84e9a7b2cb"
- ],
- "wwn": [
- "10000000C96C48D1",
- "10000000C96C48D2"
- ]
- }
- },
- "interfaces": {
- "CT0.ETH4": "iqn.2010-06.com.purestorage:flasharray.2111b767484e4682",
- "CT0.ETH5": "iqn.2010-06.com.purestorage:flasharray.2111b767484e4682",
- "CT1.ETH4": "iqn.2010-06.com.purestorage:flasharray.2111b767484e4682",
- "CT1.ETH5": "iqn.2010-06.com.purestorage:flasharray.2111b767484e4682"
- },
- "network": {
- "ct0.eth0": {
- "address": "10.10.10.10",
- "gateway": "10.10.10.1",
- "hwaddr": "ec:f4:bb:c8:8a:04",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "management"
- ],
- "speed": 1000000000
- },
- "ct0.eth2": {
- "address": "10.10.10.11",
- "gateway": null,
- "hwaddr": "ec:f4:bb:c8:8a:00",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "replication"
- ],
- "speed": 10000000000
- },
- "ct0.eth3": {
- "address": "10.10.10.12",
- "gateway": null,
- "hwaddr": "ec:f4:bb:c8:8a:02",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "replication"
- ],
- "speed": 10000000000
- },
- "ct0.eth4": {
- "address": "10.10.10.13",
- "gateway": null,
- "hwaddr": "90:e2:ba:83:79:0c",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "iscsi"
- ],
- "speed": 10000000000
- },
- "ct0.eth5": {
- "address": "10.10.10.14",
- "gateway": null,
- "hwaddr": "90:e2:ba:83:79:0d",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "iscsi"
- ],
- "speed": 10000000000
- },
- "vir0": {
- "address": "10.10.10.20",
- "gateway": "10.10.10.1",
- "hwaddr": "fe:ba:e9:e7:6b:0f",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "management"
- ],
- "speed": 1000000000
- }
- },
- "offload": {
- "nfstarget": {
- "address": "10.0.2.53",
- "mount_options": null,
- "mount_point": "/offload",
- "protocol": "nfs",
- "status": "scanning"
- }
- },
- "performance": {
- "input_per_sec": 8191,
- "output_per_sec": 0,
- "queue_depth": 1,
- "reads_per_sec": 0,
- "san_usec_per_write_op": 15,
- "usec_per_read_op": 0,
- "usec_per_write_op": 642,
- "writes_per_sec": 2
- },
- "pgroups": {
- "consisgroup-07b6b983-986e-46f5-bdc3-deaa3dbb299e-cinder": {
- "hgroups": null,
- "hosts": null,
- "source": "host1",
- "targets": null,
- "volumes": [
- "volume-1"
- ]
- }
- },
- "pods": {
- "srm-pod": {
- "arrays": [
- {
- "array_id": "52595f7e-b460-4b46-8851-a5defd2ac192",
- "mediator_status": "online",
- "name": "sn1-405-c09-37",
- "status": "online"
- },
- {
- "array_id": "a2c32301-f8a0-4382-949b-e69b552ce8ca",
- "mediator_status": "online",
- "name": "sn1-420-c11-31",
- "status": "online"
- }
- ],
- "source": null
- }
- },
- "snapshots": {
- "consisgroup.cgsnapshot": {
- "created": "2018-03-28T09:34:02Z",
- "size": 13958643712,
- "source": "volume-1"
- }
- },
- "subnet": {},
- "vgroups": {
- "vvol--vSphere-HA-0ffc7dd1-vg": {
- "volumes": [
- "vvol--vSphere-HA-0ffc7dd1-vg/Config-aad5d7c6"
- ]
- }
- },
- "volumes": {
- "ansible_data": {
- "bandwidth": null,
- "hosts": [
- [
- "host1",
- 1
- ]
- ],
- "serial": "43BE47C12334399B000114A6",
- "size": 1099511627776,
- "source": null
- }
- }
- }
-'''
-
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.pure import get_system, purefa_argument_spec
-
-
-ADMIN_API_VERSION = '1.14'
-S3_REQUIRED_API_VERSION = '1.16'
-LATENCY_REQUIRED_API_VERSION = '1.16'
-AC_REQUIRED_API_VERSION = '1.14'
-CAP_REQUIRED_API_VERSION = '1.6'
-SAN_REQUIRED_API_VERSION = '1.10'
-NVME_API_VERSION = '1.16'
-PREFERRED_API_VERSION = '1.15'
-CONN_STATUS_API_VERSION = '1.17'
-
-
-def generate_default_dict(array):
- default_facts = {}
- defaults = array.get()
- api_version = array._list_available_rest_versions()
- if AC_REQUIRED_API_VERSION in api_version:
- default_facts['volume_groups'] = len(array.list_vgroups())
- default_facts['connected_arrays'] = len(array.list_array_connections())
- default_facts['pods'] = len(array.list_pods())
- default_facts['connection_key'] = array.get(connection_key=True)['connection_key']
- hosts = array.list_hosts()
- admins = array.list_admins()
- snaps = array.list_volumes(snap=True, pending=True)
- pgroups = array.list_pgroups(pending=True)
- hgroups = array.list_hgroups()
- # Old FA arrays only report model from the primary controller
- ct0_model = array.get_hardware('CT0')['model']
- if ct0_model:
- model = ct0_model
- else:
- ct1_model = array.get_hardware('CT1')['model']
- model = ct1_model
- default_facts['array_model'] = model
- default_facts['array_name'] = defaults['array_name']
- default_facts['purity_version'] = defaults['version']
- default_facts['hosts'] = len(hosts)
- default_facts['snapshots'] = len(snaps)
- default_facts['protection_groups'] = len(pgroups)
- default_facts['hostgroups'] = len(hgroups)
- default_facts['admins'] = len(admins)
- return default_facts
-
-
-def generate_perf_dict(array):
- perf_facts = {}
- api_version = array._list_available_rest_versions()
- if LATENCY_REQUIRED_API_VERSION in api_version:
- latency_info = array.get(action='monitor', latency=True)[0]
- perf_info = array.get(action='monitor')[0]
- # IOPS
- perf_facts['writes_per_sec'] = perf_info['writes_per_sec']
- perf_facts['reads_per_sec'] = perf_info['reads_per_sec']
-
- # Bandwidth
- perf_facts['input_per_sec'] = perf_info['input_per_sec']
- perf_facts['output_per_sec'] = perf_info['output_per_sec']
-
- # Latency
- if LATENCY_REQUIRED_API_VERSION in api_version:
- perf_facts['san_usec_per_read_op'] = latency_info['san_usec_per_read_op']
- perf_facts['san_usec_per_write_op'] = latency_info['san_usec_per_write_op']
- perf_facts['queue_usec_per_read_op'] = latency_info['queue_usec_per_read_op']
- perf_facts['queue_usec_per_write_op'] = latency_info['queue_usec_per_write_op']
- perf_facts['qos_rate_limit_usec_per_read_op'] = latency_info['qos_rate_limit_usec_per_read_op']
- perf_facts['qos_rate_limit_usec_per_write_op'] = latency_info['qos_rate_limit_usec_per_write_op']
- perf_facts['local_queue_usec_per_op'] = perf_info['local_queue_usec_per_op']
- perf_facts['usec_per_read_op'] = perf_info['usec_per_read_op']
- perf_facts['usec_per_write_op'] = perf_info['usec_per_write_op']
- perf_facts['queue_depth'] = perf_info['queue_depth']
- return perf_facts
-
-
-def generate_config_dict(array):
- config_facts = {}
- api_version = array._list_available_rest_versions()
- # DNS
- config_facts['dns'] = array.get_dns()
- # SMTP
- config_facts['smtp'] = array.list_alert_recipients()
- # SNMP
- config_facts['snmp'] = array.list_snmp_managers()
- config_facts['snmp_v3_engine_id'] = array.get_snmp_engine_id()['engine_id']
- # DS
- config_facts['directory_service'] = array.get_directory_service()
- if S3_REQUIRED_API_VERSION in api_version:
- config_facts['directory_service_roles'] = {}
- roles = array.list_directory_service_roles()
- for role in range(0, len(roles)):
- role_name = roles[role]['name']
- config_facts['directory_service_roles'][role_name] = {
- 'group': roles[role]['group'],
- 'group_base': roles[role]['group_base'],
- }
- else:
- config_facts['directory_service'].update(array.get_directory_service(groups=True))
- # NTP
- config_facts['ntp'] = array.get(ntpserver=True)['ntpserver']
- # SYSLOG
- config_facts['syslog'] = array.get(syslogserver=True)['syslogserver']
- # Phonehome
- config_facts['phonehome'] = array.get(phonehome=True)['phonehome']
- # Proxy
- config_facts['proxy'] = array.get(proxy=True)['proxy']
- # Relay Host
- config_facts['relayhost'] = array.get(relayhost=True)['relayhost']
- # Sender Domain
- config_facts['senderdomain'] = array.get(senderdomain=True)['senderdomain']
- # SYSLOG
- config_facts['syslog'] = array.get(syslogserver=True)['syslogserver']
- # Idle Timeout
- config_facts['idle_timeout'] = array.get(idle_timeout=True)['idle_timeout']
- # SCSI Timeout
- config_facts['scsi_timeout'] = array.get(scsi_timeout=True)['scsi_timeout']
- # SSL
- config_facts['ssl_certs'] = array.get_certificate()
- # Global Admin settings
- if S3_REQUIRED_API_VERSION in api_version:
- config_facts['global_admin'] = array.get_global_admin_attributes()
- return config_facts
-
-
-def generate_admin_dict(array):
- api_version = array._list_available_rest_versions()
- admin_facts = {}
- if ADMIN_API_VERSION in api_version:
- admins = array.list_admins()
- for admin in range(0, len(admins)):
- admin_name = admins[admin]['name']
- admin_facts[admin_name] = {
- 'type': admins[admin]['type'],
- 'role': admins[admin]['role'],
- }
- return admin_facts
-
-
-def generate_subnet_dict(array):
- sub_facts = {}
- subnets = array.list_subnets()
- for sub in range(0, len(subnets)):
- sub_name = subnets[sub]['name']
- if subnets[sub]['enabled']:
- sub_facts[sub_name] = {
- 'gateway': subnets[sub]['gateway'],
- 'mtu': subnets[sub]['mtu'],
- 'vlan': subnets[sub]['vlan'],
- 'prefix': subnets[sub]['prefix'],
- 'interfaces': subnets[sub]['interfaces'],
- 'services': subnets[sub]['services'],
- }
- return sub_facts
-
-
-def generate_network_dict(array):
- net_facts = {}
- ports = array.list_network_interfaces()
- for port in range(0, len(ports)):
- int_name = ports[port]['name']
- net_facts[int_name] = {
- 'hwaddr': ports[port]['hwaddr'],
- 'mtu': ports[port]['mtu'],
- 'enabled': ports[port]['enabled'],
- 'speed': ports[port]['speed'],
- 'address': ports[port]['address'],
- 'slaves': ports[port]['slaves'],
- 'services': ports[port]['services'],
- 'gateway': ports[port]['gateway'],
- 'netmask': ports[port]['netmask'],
- }
- if ports[port]['subnet']:
- subnets = array.get_subnet(ports[port]['subnet'])
- if subnets['enabled']:
- net_facts[int_name]['subnet'] = {
- 'name': subnets['name'],
- 'prefix': subnets['prefix'],
- 'vlan': subnets['vlan'],
- }
- return net_facts
-
-
-def generate_capacity_dict(array):
- capacity_facts = {}
- api_version = array._list_available_rest_versions()
- if CAP_REQUIRED_API_VERSION in api_version:
- volumes = array.list_volumes(pending=True)
- capacity_facts['provisioned_space'] = sum(item['size'] for item in volumes)
- capacity = array.get(space=True)
- total_capacity = capacity[0]['capacity']
- used_space = capacity[0]["total"]
- capacity_facts['free_space'] = total_capacity - used_space
- capacity_facts['total_capacity'] = total_capacity
- capacity_facts['data_reduction'] = capacity[0]['data_reduction']
- capacity_facts['system_space'] = capacity[0]['system']
- capacity_facts['volume_space'] = capacity[0]['volumes']
- capacity_facts['shared_space'] = capacity[0]['shared_space']
- capacity_facts['snapshot_space'] = capacity[0]['snapshots']
- capacity_facts['thin_provisioning'] = capacity[0]['thin_provisioning']
- capacity_facts['total_reduction'] = capacity[0]['total_reduction']
-
- return capacity_facts
-
-
-def generate_snap_dict(array):
- snap_facts = {}
- snaps = array.list_volumes(snap=True)
- for snap in range(0, len(snaps)):
- snapshot = snaps[snap]['name']
- snap_facts[snapshot] = {
- 'size': snaps[snap]['size'],
- 'source': snaps[snap]['source'],
- 'created': snaps[snap]['created'],
- }
- return snap_facts
-
-
-def generate_vol_dict(array):
- volume_facts = {}
- vols = array.list_volumes()
- for vol in range(0, len(vols)):
- volume = vols[vol]['name']
- volume_facts[volume] = {
- 'source': vols[vol]['source'],
- 'size': vols[vol]['size'],
- 'serial': vols[vol]['serial'],
- 'hosts': [],
- 'bandwidth': ""
- }
- api_version = array._list_available_rest_versions()
- if AC_REQUIRED_API_VERSION in api_version:
- qvols = array.list_volumes(qos=True)
- for qvol in range(0, len(qvols)):
- volume = qvols[qvol]['name']
- qos = qvols[qvol]['bandwidth_limit']
- volume_facts[volume]['bandwidth'] = qos
- vvols = array.list_volumes(protocol_endpoint=True)
- for vvol in range(0, len(vvols)):
- volume = vvols[vvol]['name']
- volume_facts[volume] = {
- 'source': vvols[vvol]['source'],
- 'serial': vvols[vvol]['serial'],
- 'hosts': []
- }
- cvols = array.list_volumes(connect=True)
- for cvol in range(0, len(cvols)):
- volume = cvols[cvol]['name']
- voldict = [cvols[cvol]['host'], cvols[cvol]['lun']]
- volume_facts[volume]['hosts'].append(voldict)
- return volume_facts
-
-
-def generate_host_dict(array):
- api_version = array._list_available_rest_versions()
- host_facts = {}
- hosts = array.list_hosts()
- for host in range(0, len(hosts)):
- hostname = hosts[host]['name']
- tports = []
- host_all_info = array.get_host(hostname, all=True)
- if host_all_info:
- tports = host_all_info[0]['target_port']
- host_facts[hostname] = {
- 'hgroup': hosts[host]['hgroup'],
- 'iqn': hosts[host]['iqn'],
- 'wwn': hosts[host]['wwn'],
- 'personality': array.get_host(hostname,
- personality=True)['personality'],
- 'target_port': tports
- }
- if NVME_API_VERSION in api_version:
- host_facts[hostname]['nqn'] = hosts[host]['nqn']
- if PREFERRED_API_VERSION in api_version:
- hosts = array.list_hosts(preferred_array=True)
- for host in range(0, len(hosts)):
- hostname = hosts[host]['name']
- host_facts[hostname]['preferred_array'] = hosts[host]['preferred_array']
- return host_facts
-
-
-def generate_pgroups_dict(array):
- pgroups_facts = {}
- pgroups = array.list_pgroups()
- for pgroup in range(0, len(pgroups)):
- protgroup = pgroups[pgroup]['name']
- pgroups_facts[protgroup] = {
- 'hgroups': pgroups[pgroup]['hgroups'],
- 'hosts': pgroups[pgroup]['hosts'],
- 'source': pgroups[pgroup]['source'],
- 'targets': pgroups[pgroup]['targets'],
- 'volumes': pgroups[pgroup]['volumes'],
- }
- prot_sched = array.get_pgroup(protgroup, schedule=True)
- prot_reten = array.get_pgroup(protgroup, retention=True)
- if prot_sched['snap_enabled'] or prot_sched['replicate_enabled']:
- pgroups_facts[protgroup]['snap_freqyency'] = prot_sched['snap_frequency']
- pgroups_facts[protgroup]['replicate_freqyency'] = prot_sched['replicate_frequency']
- pgroups_facts[protgroup]['snap_enabled'] = prot_sched['snap_enabled']
- pgroups_facts[protgroup]['replicate_enabled'] = prot_sched['replicate_enabled']
- pgroups_facts[protgroup]['snap_at'] = prot_sched['snap_at']
- pgroups_facts[protgroup]['replicate_at'] = prot_sched['replicate_at']
- pgroups_facts[protgroup]['replicate_blackout'] = prot_sched['replicate_blackout']
- pgroups_facts[protgroup]['per_day'] = prot_reten['per_day']
- pgroups_facts[protgroup]['target_per_day'] = prot_reten['target_per_day']
- pgroups_facts[protgroup]['target_days'] = prot_reten['target_days']
- pgroups_facts[protgroup]['days'] = prot_reten['days']
- pgroups_facts[protgroup]['all_for'] = prot_reten['all_for']
- pgroups_facts[protgroup]['target_all_for'] = prot_reten['target_all_for']
- if ":" in protgroup:
- snap_transfers = array.get_pgroup(protgroup, snap=True, transfer=True)
- pgroups_facts[protgroup]['snaps'] = {}
- for snap_transfer in range(0, len(snap_transfers)):
- snap = snap_transfers[snap_transfer]['name']
- pgroups_facts[protgroup]['snaps'][snap] = {
- 'created': snap_transfers[snap_transfer]['created'],
- 'started': snap_transfers[snap_transfer]['started'],
- 'completed': snap_transfers[snap_transfer]['completed'],
- 'physical_bytes_written': snap_transfers[snap_transfer]['physical_bytes_written'],
- 'data_transferred': snap_transfers[snap_transfer]['data_transferred'],
- 'progress': snap_transfers[snap_transfer]['progress'],
- }
- return pgroups_facts
-
-
-def generate_pods_dict(array):
- pods_facts = {}
- api_version = array._list_available_rest_versions()
- if AC_REQUIRED_API_VERSION in api_version:
- pods = array.list_pods()
- for pod in range(0, len(pods)):
- acpod = pods[pod]['name']
- pods_facts[acpod] = {
- 'source': pods[pod]['source'],
- 'arrays': pods[pod]['arrays'],
- }
- return pods_facts
-
-
-def generate_conn_array_dict(array):
- conn_array_facts = {}
- api_version = array._list_available_rest_versions()
- if CONN_STATUS_API_VERSION in api_version:
- carrays = array.list_connected_arrays()
- for carray in range(0, len(carrays)):
- arrayname = carrays[carray]['array_name']
- conn_array_facts[arrayname] = {
- 'array_id': carrays[carray]['id'],
- 'throtled': carrays[carray]['throtled'],
- 'version': carrays[carray]['version'],
- 'type': carrays[carray]['type'],
- 'mgmt_ip': carrays[carray]['management_address'],
- 'repl_ip': carrays[carray]['replication_address'],
- }
- if CONN_STATUS_API_VERSION in api_version:
- conn_array_facts[arrayname]['status'] = carrays[carray]['status']
- return conn_array_facts
-
-
-def generate_apps_dict(array):
- apps_facts = {}
- api_version = array._list_available_rest_versions()
- if SAN_REQUIRED_API_VERSION in api_version:
- apps = array.list_apps()
- for app in range(0, len(apps)):
- appname = apps[app]['name']
- apps_facts[appname] = {
- 'version': apps[app]['version'],
- 'status': apps[app]['status'],
- 'description': apps[app]['description'],
- }
- return apps_facts
-
-
-def generate_vgroups_dict(array):
- vgroups_facts = {}
- api_version = array._list_available_rest_versions()
- if AC_REQUIRED_API_VERSION in api_version:
- vgroups = array.list_vgroups()
- for vgroup in range(0, len(vgroups)):
- virtgroup = vgroups[vgroup]['name']
- vgroups_facts[virtgroup] = {
- 'volumes': vgroups[vgroup]['volumes'],
- }
- return vgroups_facts
-
-
-def generate_nfs_offload_dict(array):
- offload_facts = {}
- api_version = array._list_available_rest_versions()
- if AC_REQUIRED_API_VERSION in api_version:
- offload = array.list_nfs_offload()
- for target in range(0, len(offload)):
- offloadt = offload[target]['name']
- offload_facts[offloadt] = {
- 'status': offload[target]['status'],
- 'mount_point': offload[target]['mount_point'],
- 'protocol': offload[target]['protocol'],
- 'mount_options': offload[target]['mount_options'],
- 'address': offload[target]['address'],
- }
- return offload_facts
-
-
-def generate_s3_offload_dict(array):
- offload_facts = {}
- api_version = array._list_available_rest_versions()
- if S3_REQUIRED_API_VERSION in api_version:
- offload = array.list_s3_offload()
- for target in range(0, len(offload)):
- offloadt = offload[target]['name']
- offload_facts[offloadt] = {
- 'status': offload[target]['status'],
- 'bucket': offload[target]['bucket'],
- 'protocol': offload[target]['protocol'],
- 'access_key_id': offload[target]['access_key_id'],
- }
- return offload_facts
-
-
-def generate_hgroups_dict(array):
- hgroups_facts = {}
- hgroups = array.list_hgroups()
- for hgroup in range(0, len(hgroups)):
- hostgroup = hgroups[hgroup]['name']
- hgroups_facts[hostgroup] = {
- 'hosts': hgroups[hgroup]['hosts'],
- 'pgs': [],
- 'vols': [],
- }
- pghgroups = array.list_hgroups(protect=True)
- for pghg in range(0, len(pghgroups)):
- pgname = pghgroups[pghg]['name']
- hgroups_facts[pgname]['pgs'].append(pghgroups[pghg]['protection_group'])
- volhgroups = array.list_hgroups(connect=True)
- for pgvol in range(0, len(volhgroups)):
- pgname = volhgroups[pgvol]['name']
- volpgdict = [volhgroups[pgvol]['vol'], volhgroups[pgvol]['lun']]
- hgroups_facts[pgname]['vols'].append(volpgdict)
- return hgroups_facts
-
-
-def generate_interfaces_dict(array):
- api_version = array._list_available_rest_versions()
- int_facts = {}
- ports = array.list_ports()
- for port in range(0, len(ports)):
- int_name = ports[port]['name']
- if ports[port]['wwn']:
- int_facts[int_name] = ports[port]['wwn']
- if ports[port]['iqn']:
- int_facts[int_name] = ports[port]['iqn']
- if NVME_API_VERSION in api_version:
- if ports[port]['nqn']:
- int_facts[int_name] = ports[port]['nqn']
- return int_facts
-
-
-def main():
- argument_spec = purefa_argument_spec()
- argument_spec.update(dict(
- gather_subset=dict(default='minimum', type='list',)
- ))
-
- module = AnsibleModule(argument_spec, supports_check_mode=False)
-
- array = get_system(module)
-
- subset = [test.lower() for test in module.params['gather_subset']]
- valid_subsets = ('all', 'minimum', 'config', 'performance', 'capacity',
- 'network', 'subnet', 'interfaces', 'hgroups', 'pgroups',
- 'hosts', 'admins', 'volumes', 'snapshots', 'pods',
- 'vgroups', 'offload', 'apps', 'arrays')
- subset_test = (test in valid_subsets for test in subset)
- if not all(subset_test):
- module.fail_json(msg="value must gather_subset must be one or more of: %s, got: %s"
- % (",".join(valid_subsets), ",".join(subset)))
-
- facts = {}
-
- if 'minimum' in subset or 'all' in subset:
- facts['default'] = generate_default_dict(array)
- if 'performance' in subset or 'all' in subset:
- facts['performance'] = generate_perf_dict(array)
- if 'config' in subset or 'all' in subset:
- facts['config'] = generate_config_dict(array)
- if 'capacity' in subset or 'all' in subset:
- facts['capacity'] = generate_capacity_dict(array)
- if 'network' in subset or 'all' in subset:
- facts['network'] = generate_network_dict(array)
- if 'subnet' in subset or 'all' in subset:
- facts['subnet'] = generate_subnet_dict(array)
- if 'interfaces' in subset or 'all' in subset:
- facts['interfaces'] = generate_interfaces_dict(array)
- if 'hosts' in subset or 'all' in subset:
- facts['hosts'] = generate_host_dict(array)
- if 'volumes' in subset or 'all' in subset:
- facts['volumes'] = generate_vol_dict(array)
- if 'snapshots' in subset or 'all' in subset:
- facts['snapshots'] = generate_snap_dict(array)
- if 'hgroups' in subset or 'all' in subset:
- facts['hgroups'] = generate_hgroups_dict(array)
- if 'pgroups' in subset or 'all' in subset:
- facts['pgroups'] = generate_pgroups_dict(array)
- if 'pods' in subset or 'all' in subset:
- facts['pods'] = generate_pods_dict(array)
- if 'admins' in subset or 'all' in subset:
- facts['admins'] = generate_admin_dict(array)
- if 'vgroups' in subset or 'all' in subset:
- facts['vgroups'] = generate_vgroups_dict(array)
- if 'offload' in subset or 'all' in subset:
- facts['nfs_offload'] = generate_nfs_offload_dict(array)
- facts['s3_offload'] = generate_s3_offload_dict(array)
- if 'apps' in subset or 'all' in subset:
- facts['apps'] = generate_apps_dict(array)
- if 'arrays' in subset or 'all' in subset:
- facts['arrays'] = generate_conn_array_dict(array)
-
- module.exit_json(ansible_facts={'ansible_purefa_facts': facts})
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/purestorage/_purefb_facts.py b/lib/ansible/modules/storage/purestorage/_purefb_facts.py
deleted file mode 100644
index 026fe47225..0000000000
--- a/lib/ansible/modules/storage/purestorage/_purefb_facts.py
+++ /dev/null
@@ -1,656 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# (c) 2018, Simon Dodsley (simon@purestorage.com)
-# 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['deprecated'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = r'''
----
-module: purefb_facts
-version_added: '2.7'
-deprecated:
- removed_in: '2.13'
- why: Deprecated in favor of C(_info) module.
- alternative: Use M(purefb_info) instead.
-short_description: Collect facts from Pure Storage FlashBlade
-description:
- - Collect facts information from a Pure Storage FlashBlade running the
- Purity//FB operating system. By default, the module will collect basic
- fact information including hosts, host groups, protection
- groups and volume counts. Additional fact information can be collected
- based on the configured set of arguments.
-author:
- - Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
-options:
- gather_subset:
- description:
- - When supplied, this argument will define the facts to be collected.
- Possible values for this include all, minimum, config, performance,
- capacity, network, subnets, lags, filesystems and snapshots.
- required: false
- type: list
- default: minimum
-extends_documentation_fragment:
- - purestorage.fb
-'''
-
-EXAMPLES = r'''
-- name: collect default set of facts
- purefb_facts:
- fb_url: 10.10.10.2
- api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
-
-- name: collect configuration and capacity facts
- purefb_facts:
- gather_subset:
- - config
- - capacity
- fb_url: 10.10.10.2
- api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
-
-- name: collect all facts
- purefb_facts:
- gather_subset:
- - all
- fb_url: 10.10.10.2
- api_token: T-55a68eb5-c785-4720-a2ca-8b03903bf641
-'''
-
-RETURN = r'''
-ansible_facts:
- description: Returns the facts collected from the FlashBlade
- returned: always
- type: complex
- sample: {
- "capacity": {
- "aggregate": {
- "data_reduction": 1.1179228,
- "snapshots": 0,
- "total_physical": 17519748439,
- "unique": 17519748439,
- "virtual": 19585726464
- },
- "file-system": {
- "data_reduction": 1.3642412,
- "snapshots": 0,
- "total_physical": 4748219708,
- "unique": 4748219708,
- "virtual": 6477716992
- },
- "object-store": {
- "data_reduction": 1.0263462,
- "snapshots": 0,
- "total_physical": 12771528731,
- "unique": 12771528731,
- "virtual": 6477716992
- },
- "total": 83359896948925
- },
- "config": {
- "alert_watchers": {
- "enabled": true,
- "name": "notify@acmestorage.com"
- },
- "array_management": {
- "base_dn": null,
- "bind_password": null,
- "bind_user": null,
- "enabled": false,
- "name": "management",
- "services": [
- "management"
- ],
- "uris": []
- },
- "directory_service_roles": {
- "array_admin": {
- "group": null,
- "group_base": null
- },
- "ops_admin": {
- "group": null,
- "group_base": null
- },
- "readonly": {
- "group": null,
- "group_base": null
- },
- "storage_admin": {
- "group": null,
- "group_base": null
- }
- },
- "dns": {
- "domain": "demo.acmestorage.com",
- "name": "demo-fb-1",
- "nameservers": [
- "8.8.8.8"
- ],
- "search": [
- "demo.acmestorage.com"
- ]
- },
- "nfs_directory_service": {
- "base_dn": null,
- "bind_password": null,
- "bind_user": null,
- "enabled": false,
- "name": "nfs",
- "services": [
- "nfs"
- ],
- "uris": []
- },
- "ntp": [
- "0.ntp.pool.org"
- ],
- "smb_directory_service": {
- "base_dn": null,
- "bind_password": null,
- "bind_user": null,
- "enabled": false,
- "name": "smb",
- "services": [
- "smb"
- ],
- "uris": []
- },
- "smtp": {
- "name": "demo-fb-1",
- "relay_host": null,
- "sender_domain": "acmestorage.com"
- },
- "ssl_certs": {
- "certificate": "-----BEGIN CERTIFICATE-----\n\n-----END CERTIFICATE-----",
- "common_name": "Acme Storage",
- "country": "US",
- "email": null,
- "intermediate_certificate": null,
- "issued_by": "Acme Storage",
- "issued_to": "Acme Storage",
- "key_size": 4096,
- "locality": null,
- "name": "global",
- "organization": "Acme Storage",
- "organizational_unit": "Acme Storage",
- "passphrase": null,
- "private_key": null,
- "state": null,
- "status": "self-signed",
- "valid_from": "1508433967000",
- "valid_to": "2458833967000"
- }
- },
- "default": {
- "blades": 15,
- "buckets": 7,
- "filesystems": 2,
- "flashblade_name": "demo-fb-1",
- "object_store_accounts": 1,
- "object_store_users": 1,
- "purity_version": "2.2.0",
- "snapshots": 1,
- "total_capacity": 83359896948925
- },
- "filesystems": {
- "k8s-pvc-d24b1357-579e-11e8-811f-ecf4bbc88f54": {
- "destroyed": false,
- "fast_remove": false,
- "hard_limit": true,
- "nfs_rules": "*(rw,no_root_squash)",
- "provisioned": 21474836480,
- "snapshot_enabled": false
- },
- "z": {
- "destroyed": false,
- "fast_remove": false,
- "hard_limit": false,
- "provisioned": 1073741824,
- "snapshot_enabled": false
- }
- },
- "lag": {
- "uplink": {
- "lag_speed": 0,
- "port_speed": 40000000000,
- "ports": [
- {
- "name": "CH1.FM1.ETH1.1"
- },
- {
- "name": "CH1.FM1.ETH1.2"
- },
- ],
- "status": "healthy"
- }
- },
- "network": {
- "fm1.admin0": {
- "address": "10.10.100.6",
- "gateway": "10.10.100.1",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "support"
- ],
- "type": "vip",
- "vlan": 2200
- },
- "fm2.admin0": {
- "address": "10.10.100.7",
- "gateway": "10.10.100.1",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "support"
- ],
- "type": "vip",
- "vlan": 2200
- },
- "nfs1": {
- "address": "10.10.100.4",
- "gateway": "10.10.100.1",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "data"
- ],
- "type": "vip",
- "vlan": 2200
- },
- "vir0": {
- "address": "10.10.100.5",
- "gateway": "10.10.100.1",
- "mtu": 1500,
- "netmask": "255.255.255.0",
- "services": [
- "management"
- ],
- "type": "vip",
- "vlan": 2200
- }
- },
- "performance": {
- "aggregate": {
- "bytes_per_op": 0,
- "bytes_per_read": 0,
- "bytes_per_write": 0,
- "read_bytes_per_sec": 0,
- "reads_per_sec": 0,
- "usec_per_other_op": 0,
- "usec_per_read_op": 0,
- "usec_per_write_op": 0,
- "write_bytes_per_sec": 0,
- "writes_per_sec": 0
- },
- "http": {
- "bytes_per_op": 0,
- "bytes_per_read": 0,
- "bytes_per_write": 0,
- "read_bytes_per_sec": 0,
- "reads_per_sec": 0,
- "usec_per_other_op": 0,
- "usec_per_read_op": 0,
- "usec_per_write_op": 0,
- "write_bytes_per_sec": 0,
- "writes_per_sec": 0
- },
- "nfs": {
- "bytes_per_op": 0,
- "bytes_per_read": 0,
- "bytes_per_write": 0,
- "read_bytes_per_sec": 0,
- "reads_per_sec": 0,
- "usec_per_other_op": 0,
- "usec_per_read_op": 0,
- "usec_per_write_op": 0,
- "write_bytes_per_sec": 0,
- "writes_per_sec": 0
- },
- "s3": {
- "bytes_per_op": 0,
- "bytes_per_read": 0,
- "bytes_per_write": 0,
- "read_bytes_per_sec": 0,
- "reads_per_sec": 0,
- "usec_per_other_op": 0,
- "usec_per_read_op": 0,
- "usec_per_write_op": 0,
- "write_bytes_per_sec": 0,
- "writes_per_sec": 0
- }
- },
- "snapshots": {
- "z.188": {
- "destroyed": false,
- "source": "z",
- "source_destroyed": false,
- "suffix": "188"
- }
- },
- "subnet": {
- "new-mgmt": {
- "gateway": "10.10.100.1",
- "interfaces": [
- {
- "name": "fm1.admin0"
- },
- {
- "name": "fm2.admin0"
- },
- {
- "name": "nfs1"
- },
- {
- "name": "vir0"
- }
- ],
- "lag": "uplink",
- "mtu": 1500,
- "prefix": "10.10.100.0/24",
- "services": [
- "data",
- "management",
- "support"
- ],
- "vlan": 2200
- }
- }
- }
-'''
-
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.pure import get_blade, purefb_argument_spec
-
-
-MIN_REQUIRED_API_VERSION = '1.3'
-HARD_LIMIT_API_VERSION = '1.4'
-
-
-def generate_default_dict(blade):
- default_facts = {}
- defaults = blade.arrays.list_arrays().items[0]
- default_facts['flashblade_name'] = defaults.name
- default_facts['purity_version'] = defaults.version
- default_facts['filesystems'] = \
- len(blade.file_systems.list_file_systems().items)
- default_facts['snapshots'] = \
- len(blade.file_system_snapshots.list_file_system_snapshots().items)
- default_facts['buckets'] = len(blade.buckets.list_buckets().items)
- default_facts['object_store_users'] = \
- len(blade.object_store_users.list_object_store_users().items)
- default_facts['object_store_accounts'] = \
- len(blade.object_store_accounts.list_object_store_accounts().items)
- default_facts['blades'] = len(blade.blade.list_blades().items)
- default_facts['total_capacity'] = \
- blade.arrays.list_arrays_space().items[0].capacity
- return default_facts
-
-
-def generate_perf_dict(blade):
- perf_facts = {}
- total_perf = blade.arrays.list_arrays_performance()
- http_perf = blade.arrays.list_arrays_performance(protocol='http')
- s3_perf = blade.arrays.list_arrays_performance(protocol='s3')
- nfs_perf = blade.arrays.list_arrays_performance(protocol='nfs')
- perf_facts['aggregate'] = {
- 'bytes_per_op': total_perf.items[0].bytes_per_op,
- 'bytes_per_read': total_perf.items[0].bytes_per_read,
- 'bytes_per_write': total_perf.items[0].bytes_per_write,
- 'read_bytes_per_sec': total_perf.items[0].read_bytes_per_sec,
- 'reads_per_sec': total_perf.items[0].reads_per_sec,
- 'usec_per_other_op': total_perf.items[0].usec_per_other_op,
- 'usec_per_read_op': total_perf.items[0].usec_per_read_op,
- 'usec_per_write_op': total_perf.items[0].usec_per_write_op,
- 'write_bytes_per_sec': total_perf.items[0].write_bytes_per_sec,
- 'writes_per_sec': total_perf.items[0].writes_per_sec,
- }
- perf_facts['http'] = {
- 'bytes_per_op': http_perf.items[0].bytes_per_op,
- 'bytes_per_read': http_perf.items[0].bytes_per_read,
- 'bytes_per_write': http_perf.items[0].bytes_per_write,
- 'read_bytes_per_sec': http_perf.items[0].read_bytes_per_sec,
- 'reads_per_sec': http_perf.items[0].reads_per_sec,
- 'usec_per_other_op': http_perf.items[0].usec_per_other_op,
- 'usec_per_read_op': http_perf.items[0].usec_per_read_op,
- 'usec_per_write_op': http_perf.items[0].usec_per_write_op,
- 'write_bytes_per_sec': http_perf.items[0].write_bytes_per_sec,
- 'writes_per_sec': http_perf.items[0].writes_per_sec,
- }
- perf_facts['s3'] = {
- 'bytes_per_op': s3_perf.items[0].bytes_per_op,
- 'bytes_per_read': s3_perf.items[0].bytes_per_read,
- 'bytes_per_write': s3_perf.items[0].bytes_per_write,
- 'read_bytes_per_sec': s3_perf.items[0].read_bytes_per_sec,
- 'reads_per_sec': s3_perf.items[0].reads_per_sec,
- 'usec_per_other_op': s3_perf.items[0].usec_per_other_op,
- 'usec_per_read_op': s3_perf.items[0].usec_per_read_op,
- 'usec_per_write_op': s3_perf.items[0].usec_per_write_op,
- 'write_bytes_per_sec': s3_perf.items[0].write_bytes_per_sec,
- 'writes_per_sec': s3_perf.items[0].writes_per_sec,
- }
- perf_facts['nfs'] = {
- 'bytes_per_op': nfs_perf.items[0].bytes_per_op,
- 'bytes_per_read': nfs_perf.items[0].bytes_per_read,
- 'bytes_per_write': nfs_perf.items[0].bytes_per_write,
- 'read_bytes_per_sec': nfs_perf.items[0].read_bytes_per_sec,
- 'reads_per_sec': nfs_perf.items[0].reads_per_sec,
- 'usec_per_other_op': nfs_perf.items[0].usec_per_other_op,
- 'usec_per_read_op': nfs_perf.items[0].usec_per_read_op,
- 'usec_per_write_op': nfs_perf.items[0].usec_per_write_op,
- 'write_bytes_per_sec': nfs_perf.items[0].write_bytes_per_sec,
- 'writes_per_sec': nfs_perf.items[0].writes_per_sec,
- }
-
- return perf_facts
-
-
-def generate_config_dict(blade):
- config_facts = {}
- config_facts['dns'] = blade.dns.list_dns().items[0].to_dict()
- config_facts['smtp'] = blade.smtp.list_smtp().items[0].to_dict()
- config_facts['alert_watchers'] = \
- blade.alert_watchers.list_alert_watchers().items[0].to_dict()
- api_version = blade.api_version.list_versions().versions
- if HARD_LIMIT_API_VERSION in api_version:
- config_facts['array_management'] = \
- blade.directory_services.list_directory_services(names=['management']).items[0].to_dict()
- config_facts['directory_service_roles'] = {}
- roles = blade.directory_services.list_directory_services_roles()
- for role in range(0, len(roles.items)):
- role_name = roles.items[role].name
- config_facts['directory_service_roles'][role_name] = {
- 'group': roles.items[role].group,
- 'group_base': roles.items[role].group_base
- }
- config_facts['nfs_directory_service'] = \
- blade.directory_services.list_directory_services(names=['nfs']).items[0].to_dict()
- config_facts['smb_directory_service'] = \
- blade.directory_services.list_directory_services(names=['smb']).items[0].to_dict()
- config_facts['ntp'] = blade.arrays.list_arrays().items[0].ntp_servers
- config_facts['ssl_certs'] = \
- blade.certificates.list_certificates().items[0].to_dict()
- return config_facts
-
-
-def generate_subnet_dict(blade):
- sub_facts = {}
- subnets = blade.subnets.list_subnets()
- for sub in range(0, len(subnets.items)):
- sub_name = subnets.items[sub].name
- if subnets.items[sub].enabled:
- sub_facts[sub_name] = {
- 'gateway': subnets.items[sub].gateway,
- 'mtu': subnets.items[sub].mtu,
- 'vlan': subnets.items[sub].vlan,
- 'prefix': subnets.items[sub].prefix,
- 'services': subnets.items[sub].services,
- }
- sub_facts[sub_name]['lag'] = subnets.items[sub].link_aggregation_group.name
- sub_facts[sub_name]['interfaces'] = []
- for iface in range(0, len(subnets.items[sub].interfaces)):
- sub_facts[sub_name]['interfaces'].append({'name': subnets.items[sub].interfaces[iface].name})
- return sub_facts
-
-
-def generate_lag_dict(blade):
- lag_facts = {}
- groups = blade.link_aggregation_groups.list_link_aggregation_groups()
- for groupcnt in range(0, len(groups.items)):
- lag_name = groups.items[groupcnt].name
- lag_facts[lag_name] = {
- 'lag_speed': groups.items[groupcnt].lag_speed,
- 'port_speed': groups.items[groupcnt].port_speed,
- 'status': groups.items[groupcnt].status,
- }
- lag_facts[lag_name]['ports'] = []
- for port in range(0, len(groups.items[groupcnt].ports)):
- lag_facts[lag_name]['ports'].append({'name': groups.items[groupcnt].ports[port].name})
- return lag_facts
-
-
-def generate_network_dict(blade):
- net_facts = {}
- ports = blade.network_interfaces.list_network_interfaces()
- for portcnt in range(0, len(ports.items)):
- int_name = ports.items[portcnt].name
- if ports.items[portcnt].enabled:
- net_facts[int_name] = {
- 'type': ports.items[portcnt].type,
- 'mtu': ports.items[portcnt].mtu,
- 'vlan': ports.items[portcnt].vlan,
- 'address': ports.items[portcnt].address,
- 'services': ports.items[portcnt].services,
- 'gateway': ports.items[portcnt].gateway,
- 'netmask': ports.items[portcnt].netmask,
- }
- return net_facts
-
-
-def generate_capacity_dict(blade):
- capacity_facts = {}
- total_cap = blade.arrays.list_arrays_space()
- file_cap = blade.arrays.list_arrays_space(type='file-system')
- object_cap = blade.arrays.list_arrays_space(type='object-store')
- capacity_facts['total'] = total_cap.items[0].capacity
- capacity_facts['aggregate'] = {
- 'data_reduction': total_cap.items[0].space.data_reduction,
- 'snapshots': total_cap.items[0].space.snapshots,
- 'total_physical': total_cap.items[0].space.total_physical,
- 'unique': total_cap.items[0].space.unique,
- 'virtual': total_cap.items[0].space.virtual,
- }
- capacity_facts['file-system'] = {
- 'data_reduction': file_cap.items[0].space.data_reduction,
- 'snapshots': file_cap.items[0].space.snapshots,
- 'total_physical': file_cap.items[0].space.total_physical,
- 'unique': file_cap.items[0].space.unique,
- 'virtual': file_cap.items[0].space.virtual,
- }
- capacity_facts['object-store'] = {
- 'data_reduction': object_cap.items[0].space.data_reduction,
- 'snapshots': object_cap.items[0].space.snapshots,
- 'total_physical': object_cap.items[0].space.total_physical,
- 'unique': object_cap.items[0].space.unique,
- 'virtual': file_cap.items[0].space.virtual,
- }
-
- return capacity_facts
-
-
-def generate_snap_dict(blade):
- snap_facts = {}
- snaps = blade.file_system_snapshots.list_file_system_snapshots()
- for snap in range(0, len(snaps.items)):
- snapshot = snaps.items[snap].name
- snap_facts[snapshot] = {
- 'destroyed': snaps.items[snap].destroyed,
- 'source': snaps.items[snap].source,
- 'suffix': snaps.items[snap].suffix,
- 'source_destroyed': snaps.items[snap].source_destroyed,
- }
- return snap_facts
-
-
-def generate_fs_dict(blade):
- fs_facts = {}
- fsys = blade.file_systems.list_file_systems()
- for fsystem in range(0, len(fsys.items)):
- share = fsys.items[fsystem].name
- fs_facts[share] = {
- 'fast_remove': fsys.items[fsystem].fast_remove_directory_enabled,
- 'snapshot_enabled': fsys.items[fsystem].snapshot_directory_enabled,
- 'provisioned': fsys.items[fsystem].provisioned,
- 'destroyed': fsys.items[fsystem].destroyed,
- }
- if fsys.items[fsystem].http.enabled:
- fs_facts[share]['http'] = fsys.items[fsystem].http.enabled
- if fsys.items[fsystem].smb.enabled:
- fs_facts[share]['smb_mode'] = fsys.items[fsystem].smb.acl_mode
- if fsys.items[fsystem].nfs.enabled:
- fs_facts[share]['nfs_rules'] = fsys.items[fsystem].nfs.rules
- api_version = blade.api_version.list_versions().versions
- if HARD_LIMIT_API_VERSION in api_version:
- fs_facts[share]['hard_limit'] = fsys.items[fsystem].hard_limit_enabled
-
- return fs_facts
-
-
-def main():
- argument_spec = purefb_argument_spec()
- argument_spec.update(dict(
- gather_subset=dict(default='minimum', type='list',)
- ))
-
- module = AnsibleModule(argument_spec, supports_check_mode=True)
-
- blade = get_blade(module)
- versions = blade.api_version.list_versions().versions
-
- if MIN_REQUIRED_API_VERSION not in versions:
- module.fail_json(msg='FlashBlade REST version not supported. Minimum version required: {0}'.format(MIN_REQUIRED_API_VERSION))
-
- subset = [test.lower() for test in module.params['gather_subset']]
- valid_subsets = ('all', 'minimum', 'config', 'performance', 'capacity',
- 'network', 'subnets', 'lags',
- 'filesystems', 'snapshots')
- subset_test = (test in valid_subsets for test in subset)
- if not all(subset_test):
- module.fail_json(msg="value must gather_subset must be one or more of: %s, got: %s"
- % (",".join(valid_subsets), ",".join(subset)))
-
- facts = {}
-
- if 'minimum' in subset or 'all' in subset:
- facts['default'] = generate_default_dict(blade)
- if 'performance' in subset or 'all' in subset:
- facts['performance'] = generate_perf_dict(blade)
- if 'config' in subset or 'all' in subset:
- facts['config'] = generate_config_dict(blade)
- if 'capacity' in subset or 'all' in subset:
- facts['capacity'] = generate_capacity_dict(blade)
- if 'lags' in subset or 'all' in subset:
- facts['lag'] = generate_lag_dict(blade)
- if 'network' in subset or 'all' in subset:
- facts['network'] = generate_network_dict(blade)
- if 'subnets' in subset or 'all' in subset:
- facts['subnet'] = generate_subnet_dict(blade)
- if 'filesystems' in subset or 'all' in subset:
- facts['filesystems'] = generate_fs_dict(blade)
- if 'snapshots' in subset or 'all' in subset:
- facts['snapshots'] = generate_snap_dict(blade)
-
- module.exit_json(ansible_facts={'ansible_purefb_facts': facts})
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/vexata/vexata_eg.py b/lib/ansible/modules/storage/vexata/vexata_eg.py
deleted file mode 100644
index 046c57e8f7..0000000000
--- a/lib/ansible/modules/storage/vexata/vexata_eg.py
+++ /dev/null
@@ -1,214 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2019, Sandeep Kasargod (sandeep@vexata.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = r'''
----
-module: vexata_eg
-version_added: 2.9
-short_description: Manage export groups on Vexata VX100 storage arrays
-description:
- - Create or delete export groups on a Vexata VX100 array.
- - An export group is a tuple of a volume group, initiator group and port
- group that allows a set of volumes to be exposed to one or more hosts
- through specific array ports.
-author:
- - Sandeep Kasargod (@vexata)
-options:
- name:
- description:
- - Export group name.
- required: true
- type: str
- state:
- description:
- - Creates export group when present or delete when absent.
- default: present
- choices: [ present, absent ]
- type: str
- vg:
- description:
- - Volume group name.
- type: str
- ig:
- description:
- - Initiator group name.
- type: str
- pg:
- description:
- - Port group name.
- type: str
-extends_documentation_fragment:
- - vexata.vx100
-'''
-
-EXAMPLES = r'''
-- name: Create export group named db_export.
- vexata_eg:
- name: db_export
- vg: dbvols
- ig: dbhosts
- pg: pg1
- state: present
- array: vx100_ultra.test.com
- user: admin
- password: secret
-
-- name: Delete export group named db_export
- vexata_eg:
- name: db_export
- state: absent
- array: vx100_ultra.test.com
- user: admin
- password: secret
-'''
-
-RETURN = r'''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.vexata import (
- argument_spec, get_array, required_together)
-
-
-def get_eg(module, array):
- """Retrieve a named vg if it exists, None if absent."""
- name = module.params['name']
- try:
- egs = array.list_egs()
- eg = filter(lambda eg: eg['name'] == name, egs)
- if len(eg) == 1:
- return eg[0]
- else:
- return None
- except Exception:
- module.fail_json(msg='Error while attempting to retrieve export groups.')
-
-
-def get_vg_id(module, array):
- """Retrieve a named vg's id if it exists, error if absent."""
- name = module.params['vg']
- try:
- vgs = array.list_vgs()
- vg = filter(lambda vg: vg['name'] == name, vgs)
- if len(vg) == 1:
- return vg[0]['id']
- else:
- module.fail_json(msg='Volume group {0} was not found.'.format(name))
- except Exception:
- module.fail_json(msg='Error while attempting to retrieve volume groups.')
-
-
-def get_ig_id(module, array):
- """Retrieve a named ig's id if it exists, error if absent."""
- name = module.params['ig']
- try:
- igs = array.list_igs()
- ig = filter(lambda ig: ig['name'] == name, igs)
- if len(ig) == 1:
- return ig[0]['id']
- else:
- module.fail_json(msg='Initiator group {0} was not found.'.format(name))
- except Exception:
- module.fail_json(msg='Error while attempting to retrieve initiator groups.')
-
-
-def get_pg_id(module, array):
- """Retrieve a named pg's id if it exists, error if absent."""
- name = module.params['pg']
- try:
- pgs = array.list_pgs()
- pg = filter(lambda pg: pg['name'] == name, pgs)
- if len(pg) == 1:
- return pg[0]['id']
- else:
- module.fail_json(msg='Port group {0} was not found.'.format(name))
- except Exception:
- module.fail_json(msg='Error while attempting to retrieve port groups.')
-
-
-def create_eg(module, array):
- """"Create a new export group."""
- changed = False
- eg_name = module.params['name']
- vg_id = get_vg_id(module, array)
- ig_id = get_ig_id(module, array)
- pg_id = get_pg_id(module, array)
- if module.check_mode:
- module.exit_json(changed=changed)
-
- try:
- eg = array.create_eg(
- eg_name,
- 'Ansible export group',
- (vg_id, ig_id, pg_id))
- if eg:
- module.log(msg='Created export group {0}'.format(eg_name))
- changed = True
- else:
- raise Exception
- except Exception:
- module.fail_json(msg='Export group {0} create failed.'.format(eg_name))
- module.exit_json(changed=changed)
-
-
-def delete_eg(module, array, eg):
- changed = False
- eg_name = eg['name']
- if module.check_mode:
- module.exit_json(changed=changed)
-
- try:
- ok = array.delete_eg(
- eg['id'])
- if ok:
- module.log(msg='Export group {0} deleted.'.format(eg_name))
- changed = True
- else:
- raise Exception
- except Exception:
- module.fail_json(msg='Export group {0} delete failed.'.format(eg_name))
- module.exit_json(changed=changed)
-
-
-def main():
- arg_spec = argument_spec()
- arg_spec.update(
- dict(
- name=dict(type='str', required=True),
- state=dict(type='str', default='present', choices=['present', 'absent']),
- vg=dict(type='str'),
- ig=dict(type='str'),
- pg=dict(type='str')
- )
- )
-
- module = AnsibleModule(arg_spec,
- supports_check_mode=True,
- required_together=required_together())
-
- state = module.params['state']
- array = get_array(module)
- eg = get_eg(module, array)
-
- if state == 'present' and not eg:
- create_eg(module, array)
- elif state == 'absent' and eg:
- delete_eg(module, array, eg)
- else:
- module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/vexata/vexata_volume.py b/lib/ansible/modules/storage/vexata/vexata_volume.py
deleted file mode 100644
index d26fb25a39..0000000000
--- a/lib/ansible/modules/storage/vexata/vexata_volume.py
+++ /dev/null
@@ -1,201 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2019, Sandeep Kasargod (sandeep@vexata.com)
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = r'''
----
-module: vexata_volume
-version_added: 2.8
-short_description: Manage volumes on Vexata VX100 storage arrays
-description:
- - Create, deletes or extend volumes on a Vexata VX100 array.
-author:
-- Sandeep Kasargod (@vexata)
-options:
- name:
- description:
- - Volume name.
- required: true
- type: str
- state:
- description:
- - Creates/Modifies volume when present or removes when absent.
- default: present
- choices: [ present, absent ]
- type: str
- size:
- description:
- - Volume size in M, G, T units. M=2^20, G=2^30, T=2^40 bytes.
- type: str
-extends_documentation_fragment:
- - vexata.vx100
-'''
-
-EXAMPLES = r'''
-- name: Create new 2 TiB volume named foo
- vexata_volume:
- name: foo
- size: 2T
- state: present
- array: vx100_ultra.test.com
- user: admin
- password: secret
-
-- name: Expand volume named foo to 4 TiB
- vexata_volume:
- name: foo
- size: 4T
- state: present
- array: vx100_ultra.test.com
- user: admin
- password: secret
-
-- name: Delete volume named foo
- vexata_volume:
- name: foo
- state: absent
- array: vx100_ultra.test.com
- user: admin
- password: secret
-'''
-
-RETURN = r'''
-'''
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.vexata import (
- argument_spec, get_array, required_together, size_to_MiB)
-
-
-def get_volume(module, array):
- """Retrieve a named volume if it exists, None if absent."""
- name = module.params['name']
- try:
- vols = array.list_volumes()
- vol = filter(lambda v: v['name'] == name, vols)
- if len(vol) == 1:
- return vol[0]
- else:
- return None
- except Exception:
- module.fail_json(msg='Error while attempting to retrieve volumes.')
-
-
-def validate_size(module, err_msg):
- size = module.params.get('size', False)
- if not size:
- module.fail_json(msg=err_msg)
- size = size_to_MiB(size)
- if size <= 0:
- module.fail_json(msg='Invalid volume size, must be <integer>[MGT].')
- return size
-
-
-def create_volume(module, array):
- """"Create a new volume."""
- changed = False
- size = validate_size(module, err_msg='Size is required to create volume.')
- if module.check_mode:
- module.exit_json(changed=changed)
-
- try:
- vol = array.create_volume(
- module.params['name'],
- 'Ansible volume',
- size)
- if vol:
- module.log(msg='Created volume {0}'.format(vol['id']))
- changed = True
- else:
- module.fail_json(msg='Volume create failed.')
- except Exception:
- pass
- module.exit_json(changed=changed)
-
-
-def update_volume(module, array, volume):
- """Expand the volume size."""
- changed = False
- size = validate_size(module, err_msg='Size is required to update volume')
- prev_size = volume['volSize']
- if size <= prev_size:
- module.log(msg='Volume expanded size needs to be larger '
- 'than current size.')
- if module.check_mode:
- module.exit_json(changed=changed)
-
- try:
- vol = array.grow_volume(
- volume['name'],
- volume['description'],
- volume['id'],
- size)
- if vol:
- changed = True
- except Exception:
- pass
-
- module.exit_json(changed=changed)
-
-
-def delete_volume(module, array, volume):
- changed = False
- vol_name = volume['name']
- if module.check_mode:
- module.exit_json(changed=changed)
-
- try:
- ok = array.delete_volume(
- volume['id'])
- if ok:
- module.log(msg='Volume {0} deleted.'.format(vol_name))
- changed = True
- else:
- raise Exception
- except Exception:
- pass
- module.exit_json(changed=changed)
-
-
-def main():
- arg_spec = argument_spec()
- arg_spec.update(
- dict(
- name=dict(type='str', required=True),
- state=dict(default='present', choices=['present', 'absent']),
- size=dict(type='str')
- )
- )
-
- module = AnsibleModule(arg_spec,
- supports_check_mode=True,
- required_together=required_together())
-
- state = module.params['state']
- array = get_array(module)
- volume = get_volume(module, array)
-
- if state == 'present':
- if not volume:
- create_volume(module, array)
- else:
- update_volume(module, array, volume)
- elif state == 'absent' and volume:
- delete_volume(module, array, volume)
- else:
- module.exit_json(changed=False)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/zfs/zfs.py b/lib/ansible/modules/storage/zfs/zfs.py
deleted file mode 100644
index 690fe53e98..0000000000
--- a/lib/ansible/modules/storage/zfs/zfs.py
+++ /dev/null
@@ -1,268 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>
-# Copyright: (c) 2017, Ansible Project
-# 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
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-DOCUMENTATION = '''
----
-module: zfs
-short_description: Manage zfs
-description:
- - Manages ZFS file systems, volumes, clones and snapshots
-version_added: "1.1"
-options:
- name:
- description:
- - File system, snapshot or volume name e.g. C(rpool/myfs).
- required: true
- state:
- description:
- - Whether to create (C(present)), or remove (C(absent)) a
- file system, snapshot or volume. All parents/children
- will be created/destroyed as needed to reach the desired state.
- choices: [ absent, present ]
- required: true
- origin:
- description:
- - Snapshot from which to create a clone.
- extra_zfs_properties:
- description:
- - A dictionary of zfs properties to be set.
- - See the zfs(8) man page for more information.
- version_added: "2.5"
-author:
-- Johan Wiren (@johanwiren)
-'''
-
-EXAMPLES = '''
-- name: Create a new file system called myfs in pool rpool with the setuid property turned off
- zfs:
- name: rpool/myfs
- state: present
- extra_zfs_properties:
- setuid: off
-
-- name: Create a new volume called myvol in pool rpool.
- zfs:
- name: rpool/myvol
- state: present
- extra_zfs_properties:
- volsize: 10M
-
-- name: Create a snapshot of rpool/myfs file system.
- zfs:
- name: rpool/myfs@mysnapshot
- state: present
-
-- name: Create a new file system called myfs2 with snapdir enabled
- zfs:
- name: rpool/myfs2
- state: present
- extra_zfs_properties:
- snapdir: enabled
-
-- name: Create a new file system by cloning a snapshot
- zfs:
- name: rpool/cloned_fs
- state: present
- origin: rpool/myfs@mysnapshot
-
-- name: Destroy a filesystem
- zfs:
- name: rpool/myfs
- state: absent
-'''
-
-import os
-
-from ansible.module_utils.basic import AnsibleModule
-
-
-class Zfs(object):
-
- def __init__(self, module, name, properties):
- self.module = module
- self.name = name
- self.properties = properties
- self.changed = False
- self.zfs_cmd = module.get_bin_path('zfs', True)
- self.zpool_cmd = module.get_bin_path('zpool', True)
- self.pool = name.split('/')[0]
- self.is_solaris = os.uname()[0] == 'SunOS'
- self.is_openzfs = self.check_openzfs()
- self.enhanced_sharing = self.check_enhanced_sharing()
-
- def check_openzfs(self):
- cmd = [self.zpool_cmd]
- cmd.extend(['get', 'version'])
- cmd.append(self.pool)
- (rc, out, err) = self.module.run_command(cmd, check_rc=True)
- version = out.splitlines()[-1].split()[2]
- if version == '-':
- return True
- if int(version) == 5000:
- return True
- return False
-
- def check_enhanced_sharing(self):
- if self.is_solaris and not self.is_openzfs:
- cmd = [self.zpool_cmd]
- cmd.extend(['get', 'version'])
- cmd.append(self.pool)
- (rc, out, err) = self.module.run_command(cmd, check_rc=True)
- version = out.splitlines()[-1].split()[2]
- if int(version) >= 34:
- return True
- return False
-
- def exists(self):
- cmd = [self.zfs_cmd, 'list', '-t', 'all', self.name]
- (rc, out, err) = self.module.run_command(' '.join(cmd))
- if rc == 0:
- return True
- else:
- return False
-
- def create(self):
- if self.module.check_mode:
- self.changed = True
- return
- properties = self.properties
- origin = self.module.params.get('origin', None)
- cmd = [self.zfs_cmd]
-
- if "@" in self.name:
- action = 'snapshot'
- elif origin:
- action = 'clone'
- else:
- action = 'create'
-
- cmd.append(action)
-
- if action in ['create', 'clone']:
- cmd += ['-p']
-
- if properties:
- for prop, value in properties.items():
- if prop == 'volsize':
- cmd += ['-V', value]
- elif prop == 'volblocksize':
- cmd += ['-b', value]
- else:
- cmd += ['-o', '%s="%s"' % (prop, value)]
- if origin and action == 'clone':
- cmd.append(origin)
- cmd.append(self.name)
- (rc, out, err) = self.module.run_command(' '.join(cmd))
- if rc == 0:
- self.changed = True
- else:
- self.module.fail_json(msg=err)
-
- def destroy(self):
- if self.module.check_mode:
- self.changed = True
- return
- cmd = [self.zfs_cmd, 'destroy', '-R', self.name]
- (rc, out, err) = self.module.run_command(' '.join(cmd))
- if rc == 0:
- self.changed = True
- else:
- self.module.fail_json(msg=err)
-
- def set_property(self, prop, value):
- if self.module.check_mode:
- self.changed = True
- return
- cmd = [self.zfs_cmd, 'set', prop + '=' + str(value), self.name]
- (rc, out, err) = self.module.run_command(cmd)
- if rc == 0:
- self.changed = True
- else:
- self.module.fail_json(msg=err)
-
- def set_properties_if_changed(self):
- current_properties = self.get_current_properties()
- for prop, value in self.properties.items():
- if current_properties.get(prop, None) != value:
- self.set_property(prop, value)
-
- def get_current_properties(self):
- cmd = [self.zfs_cmd, 'get', '-H']
- if self.enhanced_sharing:
- cmd += ['-e']
- cmd += ['all', self.name]
- rc, out, err = self.module.run_command(" ".join(cmd))
- properties = dict()
- for prop, value, source in [l.split('\t')[1:4] for l in out.splitlines()]:
- if source == 'local':
- properties[prop] = value
- # Add alias for enhanced sharing properties
- if self.enhanced_sharing:
- properties['sharenfs'] = properties.get('share.nfs', None)
- properties['sharesmb'] = properties.get('share.smb', None)
- return properties
-
-
-def main():
-
- module = AnsibleModule(
- argument_spec=dict(
- name=dict(type='str', required=True),
- state=dict(type='str', required=True, choices=['absent', 'present']),
- origin=dict(type='str', default=None),
- extra_zfs_properties=dict(type='dict', default={}),
- ),
- supports_check_mode=True,
- )
-
- state = module.params.get('state')
- name = module.params.get('name')
-
- if module.params.get('origin') and '@' in name:
- module.fail_json(msg='cannot specify origin when operating on a snapshot')
-
- # Reverse the boolification of zfs properties
- for prop, value in module.params['extra_zfs_properties'].items():
- if isinstance(value, bool):
- if value is True:
- module.params['extra_zfs_properties'][prop] = 'on'
- else:
- module.params['extra_zfs_properties'][prop] = 'off'
- else:
- module.params['extra_zfs_properties'][prop] = value
-
- result = dict(
- name=name,
- state=state,
- )
-
- zfs = Zfs(module, name, module.params['extra_zfs_properties'])
-
- if state == 'present':
- if zfs.exists():
- zfs.set_properties_if_changed()
- else:
- zfs.create()
-
- elif state == 'absent':
- if zfs.exists():
- zfs.destroy()
-
- result.update(zfs.properties)
- result['changed'] = zfs.changed
- module.exit_json(**result)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/zfs/zfs_delegate_admin.py b/lib/ansible/modules/storage/zfs/zfs_delegate_admin.py
deleted file mode 100644
index 31f8032376..0000000000
--- a/lib/ansible/modules/storage/zfs/zfs_delegate_admin.py
+++ /dev/null
@@ -1,268 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2015, Nate Coraor <nate@coraor.org>
-# 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
-
-ANSIBLE_METADATA = {'status': ['preview'],
- 'supported_by': 'community',
- 'metadata_version': '1.1'}
-
-DOCUMENTATION = r'''
----
-module: zfs_delegate_admin
-short_description: Manage ZFS delegated administration (user admin privileges)
-description:
- - Manages ZFS file system delegated administration permissions, which allow unprivileged users to perform ZFS
- operations normally restricted to the superuser.
- - See the C(zfs allow) section of C(zfs(1M)) for detailed explanations of options.
- - This module attempts to adhere to the behavior of the command line tool as much as possible.
-requirements:
- - "A ZFS/OpenZFS implementation that supports delegation with `zfs allow`, including: Solaris >= 10, illumos (all
- versions), FreeBSD >= 8.0R, ZFS on Linux >= 0.7.0."
-version_added: '2.8'
-options:
- name:
- description:
- - File system or volume name e.g. C(rpool/myfs).
- required: true
- type: str
- state:
- description:
- - Whether to allow (C(present)), or unallow (C(absent)) a permission.
- - When set to C(present), at least one "entity" param of I(users), I(groups), or I(everyone) are required.
- - When set to C(absent), removes permissions from the specified entities, or removes all permissions if no entity params are specified.
- required: true
- choices: [ absent, present ]
- default: present
- users:
- description:
- - List of users to whom permission(s) should be granted.
- type: list
- groups:
- description:
- - List of groups to whom permission(s) should be granted.
- type: list
- everyone:
- description:
- - Apply permissions to everyone.
- type: bool
- default: no
- permissions:
- description:
- - The list of permission(s) to delegate (required if C(state) is C(present)).
- type: list
- choices: [ allow, clone, create, destroy, mount, promote, readonly, receive, rename, rollback, send, share, snapshot, unallow ]
- local:
- description:
- - Apply permissions to C(name) locally (C(zfs allow -l)).
- type: bool
- descendents:
- description:
- - Apply permissions to C(name)'s descendents (C(zfs allow -d)).
- type: bool
- recursive:
- description:
- - Unallow permissions recursively (ignored when C(state) is C(present)).
- type: bool
- default: no
-author:
-- Nate Coraor (@natefoo)
-'''
-
-EXAMPLES = r'''
-- name: Grant `zfs allow` and `unallow` permission to the `adm` user with the default local+descendents scope
- zfs_delegate_admin:
- name: rpool/myfs
- users: adm
- permissions: allow,unallow
-
-- name: Grant `zfs send` to everyone, plus the group `backup`
- zfs_delegate_admin:
- name: rpool/myvol
- groups: backup
- everyone: yes
- permissions: send
-
-- name: Grant `zfs send,receive` to users `foo` and `bar` with local scope only
- zfs_delegate_admin:
- name: rpool/myfs
- users: foo,bar
- permissions: send,receive
- local: yes
-
-- name: Revoke all permissions from everyone (permissions specifically assigned to users and groups remain)
-- zfs_delegate_admin:
- name: rpool/myfs
- everyone: yes
- state: absent
-'''
-
-# This module does not return anything other than the standard
-# changed/state/msg/stdout
-RETURN = '''
-'''
-
-from itertools import product
-
-from ansible.module_utils.basic import AnsibleModule
-
-
-class ZfsDelegateAdmin(object):
- def __init__(self, module):
- self.module = module
- self.name = module.params.get('name')
- self.state = module.params.get('state')
- self.users = module.params.get('users')
- self.groups = module.params.get('groups')
- self.everyone = module.params.get('everyone')
- self.perms = module.params.get('permissions')
- self.scope = None
- self.changed = False
- self.initial_perms = None
- self.subcommand = 'allow'
- self.recursive_opt = []
- self.run_method = self.update
-
- self.setup(module)
-
- def setup(self, module):
- """ Validate params and set up for run.
- """
- if self.state == 'absent':
- self.subcommand = 'unallow'
- if module.params.get('recursive'):
- self.recursive_opt = ['-r']
-
- local = module.params.get('local')
- descendents = module.params.get('descendents')
- if (local and descendents) or (not local and not descendents):
- self.scope = 'ld'
- elif local:
- self.scope = 'l'
- elif descendents:
- self.scope = 'd'
- else:
- self.module.fail_json(msg='Impossible value for local and descendents')
-
- if not (self.users or self.groups or self.everyone):
- if self.state == 'present':
- self.module.fail_json(msg='One of `users`, `groups`, or `everyone` must be set')
- elif self.state == 'absent':
- self.run_method = self.clear
- # ansible ensures the else cannot happen here
-
- self.zfs_path = module.get_bin_path('zfs', True)
-
- @property
- def current_perms(self):
- """ Parse the output of `zfs allow <name>` to retrieve current permissions.
- """
- out = self.run_zfs_raw(subcommand='allow')
- perms = {
- 'l': {'u': {}, 'g': {}, 'e': []},
- 'd': {'u': {}, 'g': {}, 'e': []},
- 'ld': {'u': {}, 'g': {}, 'e': []},
- }
- linemap = {
- 'Local permissions:': 'l',
- 'Descendent permissions:': 'd',
- 'Local+Descendent permissions:': 'ld',
- }
- scope = None
- for line in out.splitlines():
- scope = linemap.get(line, scope)
- if not scope:
- continue
- try:
- if line.startswith('\tuser ') or line.startswith('\tgroup '):
- ent_type, ent, cur_perms = line.split()
- perms[scope][ent_type[0]][ent] = cur_perms.split(',')
- elif line.startswith('\teveryone '):
- perms[scope]['e'] = line.split()[1].split(',')
- except ValueError:
- self.module.fail_json(msg="Cannot parse user/group permission output by `zfs allow`: '%s'" % line)
- return perms
-
- def run_zfs_raw(self, subcommand=None, args=None):
- """ Run a raw zfs command, fail on error.
- """
- cmd = [self.zfs_path, subcommand or self.subcommand] + (args or []) + [self.name]
- rc, out, err = self.module.run_command(cmd)
- if rc:
- self.module.fail_json(msg='Command `%s` failed: %s' % (' '.join(cmd), err))
- return out
-
- def run_zfs(self, args):
- """ Run zfs allow/unallow with appropriate options as per module arguments.
- """
- args = self.recursive_opt + ['-' + self.scope] + args
- if self.perms:
- args.append(','.join(self.perms))
- return self.run_zfs_raw(args=args)
-
- def clear(self):
- """ Called by run() to clear all permissions.
- """
- changed = False
- stdout = ''
- for scope, ent_type in product(('ld', 'l', 'd'), ('u', 'g')):
- for ent in self.initial_perms[scope][ent_type].keys():
- stdout += self.run_zfs(['-%s' % ent_type, ent])
- changed = True
- for scope in ('ld', 'l', 'd'):
- if self.initial_perms[scope]['e']:
- stdout += self.run_zfs(['-e'])
- changed = True
- return (changed, stdout)
-
- def update(self):
- """ Update permissions as per module arguments.
- """
- stdout = ''
- for ent_type, entities in (('u', self.users), ('g', self.groups)):
- if entities:
- stdout += self.run_zfs(['-%s' % ent_type, ','.join(entities)])
- if self.everyone:
- stdout += self.run_zfs(['-e'])
- return (self.initial_perms != self.current_perms, stdout)
-
- def run(self):
- """ Run an operation, return results for Ansible.
- """
- exit_args = {'state': self.state}
- self.initial_perms = self.current_perms
- exit_args['changed'], stdout = self.run_method()
- if exit_args['changed']:
- exit_args['msg'] = 'ZFS delegated admin permissions updated'
- exit_args['stdout'] = stdout
- self.module.exit_json(**exit_args)
-
-
-def main():
- module = AnsibleModule(
- argument_spec=dict(
- name=dict(type='str', required=True),
- state=dict(type='str', default='present', choices=['absent', 'present']),
- users=dict(type='list'),
- groups=dict(type='list'),
- everyone=dict(type='bool', default=False),
- permissions=dict(type='list',
- choices=['allow', 'clone', 'create', 'destroy', 'mount', 'promote', 'readonly', 'receive',
- 'rename', 'rollback', 'send', 'share', 'snapshot', 'unallow']),
- local=dict(type='bool'),
- descendents=dict(type='bool'),
- recursive=dict(type='bool', default=False),
- ),
- supports_check_mode=False,
- required_if=[('state', 'present', ['permissions'])],
- )
- zfs_delegate_admin = ZfsDelegateAdmin(module)
- zfs_delegate_admin.run()
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/zfs/zfs_facts.py b/lib/ansible/modules/storage/zfs/zfs_facts.py
deleted file mode 100644
index dfccc359d8..0000000000
--- a/lib/ansible/modules/storage/zfs/zfs_facts.py
+++ /dev/null
@@ -1,266 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# (c) 2016, Adam Števko <adam.stevko@gmail.com>
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: zfs_facts
-short_description: Gather facts about ZFS datasets.
-description:
- - Gather facts from ZFS dataset properties.
-version_added: "2.3"
-author: Adam Števko (@xen0l)
-options:
- name:
- description:
- - ZFS dataset name.
- required: yes
- aliases: [ "ds", "dataset" ]
- recurse:
- description:
- - Specifies if properties for any children should be recursively
- displayed.
- type: bool
- default: 'no'
- parsable:
- description:
- - Specifies if property values should be displayed in machine
- friendly format.
- type: bool
- default: 'no'
- properties:
- description:
- - Specifies which dataset properties should be queried in comma-separated format.
- For more information about dataset properties, check zfs(1M) man page.
- default: all
- aliases: [ "props" ]
- type:
- description:
- - Specifies which datasets types to display. Multiple values have to be
- provided in comma-separated form.
- choices: [ 'all', 'filesystem', 'volume', 'snapshot', 'bookmark' ]
- default: all
- depth:
- description:
- - Specifies recursion depth.
-'''
-
-EXAMPLES = '''
-- name: Gather facts about ZFS dataset rpool/export/home
- zfs_facts:
- dataset: rpool/export/home
-
-- name: Report space usage on ZFS filesystems under data/home
- zfs_facts:
- name: data/home
- recurse: yes
- type: filesystem
-
-- debug:
- msg: 'ZFS dataset {{ item.name }} consumes {{ item.used }} of disk space.'
- with_items: '{{ ansible_zfs_datasets }}'
-'''
-
-RETURN = '''
-name:
- description: ZFS dataset name
- returned: always
- type: str
- sample: rpool/var/spool
-parsable:
- description: if parsable output should be provided in machine friendly format.
- returned: if 'parsable' is set to True
- type: bool
- sample: True
-recurse:
- description: if we should recurse over ZFS dataset
- returned: if 'recurse' is set to True
- type: bool
- sample: True
-zfs_datasets:
- description: ZFS dataset facts
- returned: always
- type: str
- sample:
- {
- "aclinherit": "restricted",
- "aclmode": "discard",
- "atime": "on",
- "available": "43.8G",
- "canmount": "on",
- "casesensitivity": "sensitive",
- "checksum": "on",
- "compression": "off",
- "compressratio": "1.00x",
- "copies": "1",
- "creation": "Thu Jun 16 11:37 2016",
- "dedup": "off",
- "devices": "on",
- "exec": "on",
- "filesystem_count": "none",
- "filesystem_limit": "none",
- "logbias": "latency",
- "logicalreferenced": "18.5K",
- "logicalused": "3.45G",
- "mlslabel": "none",
- "mounted": "yes",
- "mountpoint": "/rpool",
- "name": "rpool",
- "nbmand": "off",
- "normalization": "none",
- "org.openindiana.caiman:install": "ready",
- "primarycache": "all",
- "quota": "none",
- "readonly": "off",
- "recordsize": "128K",
- "redundant_metadata": "all",
- "refcompressratio": "1.00x",
- "referenced": "29.5K",
- "refquota": "none",
- "refreservation": "none",
- "reservation": "none",
- "secondarycache": "all",
- "setuid": "on",
- "sharenfs": "off",
- "sharesmb": "off",
- "snapdir": "hidden",
- "snapshot_count": "none",
- "snapshot_limit": "none",
- "sync": "standard",
- "type": "filesystem",
- "used": "4.41G",
- "usedbychildren": "4.41G",
- "usedbydataset": "29.5K",
- "usedbyrefreservation": "0",
- "usedbysnapshots": "0",
- "utf8only": "off",
- "version": "5",
- "vscan": "off",
- "written": "29.5K",
- "xattr": "on",
- "zoned": "off"
- }
-'''
-
-from collections import defaultdict
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils.six import iteritems
-
-
-SUPPORTED_TYPES = ['all', 'filesystem', 'volume', 'snapshot', 'bookmark']
-
-
-class ZFSFacts(object):
- def __init__(self, module):
-
- self.module = module
-
- self.name = module.params['name']
- self.recurse = module.params['recurse']
- self.parsable = module.params['parsable']
- self.properties = module.params['properties']
- self.type = module.params['type']
- self.depth = module.params['depth']
-
- self._datasets = defaultdict(dict)
- self.facts = []
-
- def dataset_exists(self):
- cmd = [self.module.get_bin_path('zfs')]
-
- cmd.append('list')
- cmd.append(self.name)
-
- (rc, out, err) = self.module.run_command(cmd)
-
- if rc == 0:
- return True
- else:
- return False
-
- def get_facts(self):
- cmd = [self.module.get_bin_path('zfs')]
-
- cmd.append('get')
- cmd.append('-H')
- if self.parsable:
- cmd.append('-p')
- if self.recurse:
- cmd.append('-r')
- if int(self.depth) != 0:
- cmd.append('-d')
- cmd.append('%s' % self.depth)
- if self.type:
- cmd.append('-t')
- cmd.append(self.type)
- cmd.append('-o')
- cmd.append('name,property,value')
- cmd.append(self.properties)
- cmd.append(self.name)
-
- (rc, out, err) = self.module.run_command(cmd)
-
- if rc == 0:
- for line in out.splitlines():
- dataset, property, value = line.split('\t')
-
- self._datasets[dataset].update({property: value})
-
- for k, v in iteritems(self._datasets):
- v.update({'name': k})
- self.facts.append(v)
-
- return {'ansible_zfs_datasets': self.facts}
- else:
- self.module.fail_json(msg='Error while trying to get facts about ZFS dataset: %s' % self.name,
- stderr=err,
- rc=rc)
-
-
-def main():
- module = AnsibleModule(
- argument_spec=dict(
- name=dict(required=True, aliases=['ds', 'dataset'], type='str'),
- recurse=dict(required=False, default=False, type='bool'),
- parsable=dict(required=False, default=False, type='bool'),
- properties=dict(required=False, default='all', type='str'),
- type=dict(required=False, default='all', type='str', choices=SUPPORTED_TYPES),
- depth=dict(required=False, default=0, type='int')
- ),
- supports_check_mode=True
- )
-
- zfs_facts = ZFSFacts(module)
-
- result = {}
- result['changed'] = False
- result['name'] = zfs_facts.name
-
- if zfs_facts.parsable:
- result['parsable'] = zfs_facts.parsable
-
- if zfs_facts.recurse:
- result['recurse'] = zfs_facts.recurse
-
- if zfs_facts.dataset_exists():
- result['ansible_facts'] = zfs_facts.get_facts()
- else:
- module.fail_json(msg='ZFS dataset %s does not exist!' % zfs_facts.name)
-
- module.exit_json(**result)
-
-
-if __name__ == '__main__':
- main()
diff --git a/lib/ansible/modules/storage/zfs/zpool_facts.py b/lib/ansible/modules/storage/zfs/zpool_facts.py
deleted file mode 100644
index 9a577badc6..0000000000
--- a/lib/ansible/modules/storage/zfs/zpool_facts.py
+++ /dev/null
@@ -1,215 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# (c) 2016, Adam Števko <adam.stevko@gmail.com>
-# 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
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
----
-module: zpool_facts
-short_description: Gather facts about ZFS pools.
-description:
- - Gather facts from ZFS pool properties.
-version_added: "2.3"
-author: Adam Števko (@xen0l)
-options:
- name:
- description:
- - ZFS pool name.
- aliases: [ "pool", "zpool" ]
- required: false
- parsable:
- description:
- - Specifies if property values should be displayed in machine
- friendly format.
- type: bool
- default: False
- required: false
- properties:
- description:
- - Specifies which dataset properties should be queried in comma-separated format.
- For more information about dataset properties, check zpool(1M) man page.
- aliases: [ "props" ]
- default: all
- required: false
-'''
-
-EXAMPLES = '''
-# Gather facts about ZFS pool rpool
-- zpool_facts: pool=rpool
-
-# Gather space usage about all imported ZFS pools
-- zpool_facts: properties='free,size'
-
-- debug: msg='ZFS pool {{ item.name }} has {{ item.free }} free space out of {{ item.size }}.'
- with_items: '{{ ansible_zfs_pools }}'
-'''
-
-RETURN = '''
-ansible_facts:
- description: Dictionary containing all the detailed information about the ZFS pool facts
- returned: always
- type: complex
- contains:
- ansible_zfs_pools:
- description: ZFS pool facts
- returned: always
- type: str
- sample:
- {
- "allocated": "3.46G",
- "altroot": "-",
- "autoexpand": "off",
- "autoreplace": "off",
- "bootfs": "rpool/ROOT/openindiana",
- "cachefile": "-",
- "capacity": "6%",
- "comment": "-",
- "dedupditto": "0",
- "dedupratio": "1.00x",
- "delegation": "on",
- "expandsize": "-",
- "failmode": "wait",
- "feature@async_destroy": "enabled",
- "feature@bookmarks": "enabled",
- "feature@edonr": "enabled",
- "feature@embedded_data": "active",
- "feature@empty_bpobj": "active",
- "feature@enabled_txg": "active",
- "feature@extensible_dataset": "enabled",
- "feature@filesystem_limits": "enabled",
- "feature@hole_birth": "active",
- "feature@large_blocks": "enabled",
- "feature@lz4_compress": "active",
- "feature@multi_vdev_crash_dump": "enabled",
- "feature@sha512": "enabled",
- "feature@skein": "enabled",
- "feature@spacemap_histogram": "active",
- "fragmentation": "3%",
- "free": "46.3G",
- "freeing": "0",
- "guid": "15729052870819522408",
- "health": "ONLINE",
- "leaked": "0",
- "listsnapshots": "off",
- "name": "rpool",
- "readonly": "off",
- "size": "49.8G",
- "version": "-"
- }
-name:
- description: ZFS pool name
- returned: always
- type: str
- sample: rpool
-parsable:
- description: if parsable output should be provided in machine friendly format.
- returned: if 'parsable' is set to True
- type: bool
- sample: True
-'''
-
-from collections import defaultdict
-
-from ansible.module_utils.six import iteritems
-from ansible.module_utils.basic import AnsibleModule
-
-
-class ZPoolFacts(object):
- def __init__(self, module):
-
- self.module = module
-
- self.name = module.params['name']
- self.parsable = module.params['parsable']
- self.properties = module.params['properties']
-
- self._pools = defaultdict(dict)
- self.facts = []
-
- def pool_exists(self):
- cmd = [self.module.get_bin_path('zpool')]
-
- cmd.append('list')
- cmd.append(self.name)
-
- (rc, out, err) = self.module.run_command(cmd)
-
- if rc == 0:
- return True
- else:
- return False
-
- def get_facts(self):
- cmd = [self.module.get_bin_path('zpool')]
-
- cmd.append('get')
- cmd.append('-H')
- if self.parsable:
- cmd.append('-p')
- cmd.append('-o')
- cmd.append('name,property,value')
- cmd.append(self.properties)
- if self.name:
- cmd.append(self.name)
-
- (rc, out, err) = self.module.run_command(cmd)
-
- if rc == 0:
- for line in out.splitlines():
- pool, property, value = line.split('\t')
-
- self._pools[pool].update({property: value})
-
- for k, v in iteritems(self._pools):
- v.update({'name': k})
- self.facts.append(v)
-
- return {'ansible_zfs_pools': self.facts}
- else:
- self.module.fail_json(msg='Error while trying to get facts about ZFS pool: %s' % self.name,
- stderr=err,
- rc=rc)
-
-
-def main():
- module = AnsibleModule(
- argument_spec=dict(
- name=dict(required=False, aliases=['pool', 'zpool'], type='str'),
- parsable=dict(required=False, default=False, type='bool'),
- properties=dict(required=False, default='all', type='str'),
- ),
- supports_check_mode=True
- )
-
- zpool_facts = ZPoolFacts(module)
-
- result = {}
- result['changed'] = False
- result['name'] = zpool_facts.name
-
- if zpool_facts.parsable:
- result['parsable'] = zpool_facts.parsable
-
- if zpool_facts.name is not None:
- if zpool_facts.pool_exists():
- result['ansible_facts'] = zpool_facts.get_facts()
- else:
- module.fail_json(msg='ZFS pool %s does not exist!' % zpool_facts.name)
- else:
- result['ansible_facts'] = zpool_facts.get_facts()
-
- module.exit_json(**result)
-
-
-if __name__ == '__main__':
- main()