mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 04:40:22 -07:00
Remove deprecated modules and plugins. (#1347)
* Remove deprecated modules and plugins. * Clean up BOTMETA. * Re-add _netapp doc fragment and module utils. This is needed by the remaining na_ontap_gather_facts module scheduled for removal in 3.0.0.
This commit is contained in:
parent
19b5fceeab
commit
3d66ed3ae3
41 changed files with 58 additions and 5079 deletions
|
@ -1,228 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_aggregate
|
||||
|
||||
short_description: Manage NetApp cDOT aggregates.
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
community.general.na_cdot_aggregate:
|
||||
state: present
|
||||
name: ansibleAggr
|
||||
disk_count: 1
|
||||
hostname: "{{ netapp_hostname }}"
|
||||
username: "{{ netapp_username }}"
|
||||
password: "{{ netapp_password }}"
|
||||
|
||||
- name: Manage Aggregates
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,296 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_license
|
||||
|
||||
short_description: Manage NetApp cDOT protocol and feature licenses
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
default: false
|
||||
|
||||
remove_expired:
|
||||
description:
|
||||
- Remove licenses that have expired in the cluster.
|
||||
type: bool
|
||||
default: false
|
||||
|
||||
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
|
||||
community.general.na_cdot_license:
|
||||
hostname: "{{ netapp_hostname }}"
|
||||
username: "{{ netapp_username }}"
|
||||
password: "{{ netapp_password }}"
|
||||
serial_number: #################
|
||||
licenses:
|
||||
nfs: #################
|
||||
cifs: #################
|
||||
iscsi: #################
|
||||
fcp: #################
|
||||
snaprestore: #################
|
||||
flexclone: #################
|
||||
|
||||
- name: Remove licenses
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,373 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_lun
|
||||
|
||||
short_description: Manage NetApp cDOT luns
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
community.general.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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,234 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_qtree
|
||||
|
||||
short_description: Manage qtrees
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
community.general.na_cdot_qtree:
|
||||
state: present
|
||||
name: ansibleQTree
|
||||
flexvol_name: ansibleVolume
|
||||
vserver: ansibleVServer
|
||||
hostname: "{{ netapp_hostname }}"
|
||||
username: "{{ netapp_username }}"
|
||||
password: "{{ netapp_password }}"
|
||||
|
||||
- name: Rename QTree
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,246 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_svm
|
||||
|
||||
short_description: Manage NetApp cDOT svm
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,301 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_user
|
||||
|
||||
short_description: useradmin configuration and management
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,227 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_user_role
|
||||
|
||||
short_description: useradmin configuration and management
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,437 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: na_cdot_volume
|
||||
|
||||
short_description: Manage NetApp cDOT volumes
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.ontap
|
||||
|
||||
author: Sumit Kumar (@timuster) <sumit4@netapp.com>
|
||||
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: Updated modules released with increased functionality
|
||||
alternative: Use M(netapp.ontap.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
|
||||
|
||||
export_policy:
|
||||
description:
|
||||
- Export policy to set for the specified junction path.
|
||||
required: false
|
||||
default: default
|
||||
|
||||
snapshot_policy:
|
||||
description:
|
||||
- Snapshot policy to set for the specified volume.
|
||||
required: false
|
||||
default: default
|
||||
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
|
||||
- name: Create FlexVol
|
||||
community.general.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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,263 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: sf_account_manager
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: This Module has been replaced
|
||||
alternative: please use M(netapp.elementsw.na_elementsw_account)
|
||||
short_description: Manage SolidFire accounts
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.solidfire
|
||||
|
||||
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
|
||||
community.general.sf_account_manager:
|
||||
hostname: "{{ solidfire_hostname }}"
|
||||
username: "{{ solidfire_username }}"
|
||||
password: "{{ solidfire_password }}"
|
||||
state: present
|
||||
name: TenantA
|
||||
|
||||
- name: Modify Account
|
||||
community.general.sf_account_manager:
|
||||
hostname: "{{ solidfire_hostname }}"
|
||||
username: "{{ solidfire_username }}"
|
||||
password: "{{ solidfire_password }}"
|
||||
state: present
|
||||
name: TenantA
|
||||
new_name: TenantA-Renamed
|
||||
|
||||
- name: Delete Account
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,179 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: sf_check_connections
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: This Module has been replaced
|
||||
alternative: please use M(netapp.elementsw.na_elementsw_check_connections)
|
||||
short_description: Check connectivity to MVIP and SVIP.
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.solidfire
|
||||
|
||||
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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,384 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: sf_snapshot_schedule_manager
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: This Module has been replaced
|
||||
alternative: please use M(netapp.elementsw.na_elementsw_snapshot_schedule)
|
||||
short_description: Manage SolidFire snapshot schedules
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.solidfire
|
||||
|
||||
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
|
||||
community.general.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
|
||||
community.general.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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,244 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: sf_volume_access_group_manager
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: This Module has been replaced
|
||||
alternative: please use M(netapp.elementsw.na_elementsw_access_group)
|
||||
short_description: Manage SolidFire Volume Access Groups
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.solidfire
|
||||
|
||||
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
|
||||
community.general.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
|
||||
community.general.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
|
||||
community.general.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_collections.community.general.plugins.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()
|
|
@ -1,315 +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
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
module: sf_volume_manager
|
||||
deprecated:
|
||||
removed_in: 2.0.0 # was Ansible 2.11
|
||||
why: This Module has been replaced
|
||||
alternative: please use M(netapp.elementsw.na_elementsw_volume)
|
||||
short_description: Manage SolidFire volumes
|
||||
extends_documentation_fragment:
|
||||
- community.general._netapp.solidfire
|
||||
|
||||
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
|
||||
community.general.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
|
||||
community.general.sf_volume_manager:
|
||||
hostname: "{{ solidfire_hostname }}"
|
||||
username: "{{ solidfire_username }}"
|
||||
password: "{{ solidfire_password }}"
|
||||
state: present
|
||||
name: AnsibleVol
|
||||
account_id: 3
|
||||
access: readWrite
|
||||
|
||||
- name: Delete Volume
|
||||
community.general.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_collections.community.general.plugins.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()
|
Loading…
Add table
Add a link
Reference in a new issue