summaryrefslogtreecommitdiffstats
path: root/ansible_collections/netapp/elementsw/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/netapp/elementsw/plugins')
-rw-r--r--ansible_collections/netapp/elementsw/plugins/doc_fragments/netapp.py51
-rw-r--r--ansible_collections/netapp/elementsw/plugins/module_utils/netapp.py107
-rw-r--r--ansible_collections/netapp/elementsw/plugins/module_utils/netapp_elementsw_module.py206
-rw-r--r--ansible_collections/netapp/elementsw/plugins/module_utils/netapp_module.py225
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group.py397
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group_volumes.py247
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_account.py340
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_admin_users.py233
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_backup.py243
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_check_connections.py154
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster.py372
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_config.py331
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_pair.py206
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_snmp.py365
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_drive.py368
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_info.py272
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_initiators.py343
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_ldap.py254
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_network_interfaces.py423
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_node.py357
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_qos_policy.py270
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot.py369
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_restore.py203
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_schedule.py586
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_vlan.py274
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume.py413
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_clone.py276
-rw-r--r--ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_pair.py293
28 files changed, 0 insertions, 8178 deletions
diff --git a/ansible_collections/netapp/elementsw/plugins/doc_fragments/netapp.py b/ansible_collections/netapp/elementsw/plugins/doc_fragments/netapp.py
deleted file mode 100644
index 229d03f7d..000000000
--- a/ansible_collections/netapp/elementsw/plugins/doc_fragments/netapp.py
+++ /dev/null
@@ -1,51 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright: (c) 2018, NetApp Ansible Team <ng-ansibleteam@netapp.com>
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-
-class ModuleDocFragment(object):
-
- DOCUMENTATION = r'''
-options:
- - See respective platform section for more details
-requirements:
- - See respective platform section for more details
-notes:
- - Ansible modules are available for the following NetApp Storage Platforms: E-Series, ONTAP, SolidFire
-'''
-
- # Documentation fragment for SolidFire
- SOLIDFIRE = r'''
-options:
- hostname:
- required: true
- description:
- - The hostname or IP address of the SolidFire cluster.
- - For na_elementsw_cluster, the Management IP (MIP) or hostname of the node to initiate the cluster creation from.
- type: str
- username:
- required: true
- description:
- - Please ensure that the user has the adequate permissions. For more information, please read the official documentation
- U(https://mysupport.netapp.com/documentation/docweb/index.html?productID=62636&language=en-US).
- aliases: ['user']
- type: str
- password:
- required: true
- description:
- - Password for the specified user.
- aliases: ['pass']
- type: str
-
-requirements:
- - The modules were developed with SolidFire 10.1
- - solidfire-sdk-python (1.1.0.92) or greater. Install using 'pip install solidfire-sdk-python'
-
-notes:
- - The modules prefixed with na\\_elementsw are built to support the SolidFire storage platform.
-
-'''
diff --git a/ansible_collections/netapp/elementsw/plugins/module_utils/netapp.py b/ansible_collections/netapp/elementsw/plugins/module_utils/netapp.py
deleted file mode 100644
index 4121bf8e7..000000000
--- a/ansible_collections/netapp/elementsw/plugins/module_utils/netapp.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# This code is part of Ansible, but is an independent component.
-# This particular file snippet, and this file snippet only, is BSD licensed.
-# Modules you write using this snippet, which is embedded dynamically by Ansible
-# still belong to the author of the module, and may assign their own license
-# to the complete work.
-#
-# Copyright (c) 2017, Sumit Kumar <sumit4@netapp.com>
-# Copyright (c) 2017, Michael Price <michael.price@netapp.com>
-# Copyright: (c) 2018, NetApp Ansible Team <ng-ansibleteam@netapp.com>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification,
-# are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
-# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-'''
-Common methods and constants
-'''
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-HAS_SF_SDK = False
-SF_BYTE_MAP = dict(
- # Management GUI displays 1024 ** 3 as 1.1 GB, thus use 1000.
- bytes=1,
- b=1,
- kb=1000,
- mb=1000 ** 2,
- gb=1000 ** 3,
- tb=1000 ** 4,
- pb=1000 ** 5,
- eb=1000 ** 6,
- zb=1000 ** 7,
- yb=1000 ** 8
-)
-
-# uncomment this to log API calls
-# import logging
-
-try:
- from solidfire.factory import ElementFactory
- import solidfire.common
- HAS_SF_SDK = True
-except ImportError:
- HAS_SF_SDK = False
-
-COLLECTION_VERSION = "21.7.0"
-
-
-def has_sf_sdk():
- return HAS_SF_SDK
-
-
-def ontap_sf_host_argument_spec():
-
- return dict(
- hostname=dict(required=True, type='str'),
- username=dict(required=True, type='str', aliases=['user']),
- password=dict(required=True, type='str', aliases=['pass'], no_log=True)
- )
-
-
-def create_sf_connection(module, hostname=None, port=None, raise_on_connection_error=False, timeout=None):
- if hostname is None:
- hostname = module.params['hostname']
- username = module.params['username']
- password = module.params['password']
- options = dict()
- if port is not None:
- options['port'] = port
- if timeout is not None:
- options['timeout'] = timeout
-
- if not HAS_SF_SDK:
- module.fail_json(msg="the python SolidFire SDK module is required")
-
- try:
- logging.basicConfig(filename='/tmp/elementsw_apis.log', level=logging.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s')
- except NameError:
- # logging was not imported
- pass
-
- try:
- return_val = ElementFactory.create(hostname, username, password, **options)
- except (solidfire.common.ApiConnectionError, solidfire.common.ApiServerError) as exc:
- if raise_on_connection_error:
- raise exc
- module.fail_json(msg=repr(exc))
- except Exception as exc:
- raise Exception("Unable to create SF connection: %s" % repr(exc))
- return return_val
diff --git a/ansible_collections/netapp/elementsw/plugins/module_utils/netapp_elementsw_module.py b/ansible_collections/netapp/elementsw/plugins/module_utils/netapp_elementsw_module.py
deleted file mode 100644
index 2d8b92cfa..000000000
--- a/ansible_collections/netapp/elementsw/plugins/module_utils/netapp_elementsw_module.py
+++ /dev/null
@@ -1,206 +0,0 @@
-# This code is part of Ansible, but is an independent component.
-# This particular file snippet, and this file snippet only, is BSD licensed.
-# Copyright: (c) 2018, NetApp Ansible Team <ng-ansibleteam@netapp.com>
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-from ansible.module_utils._text import to_native
-
-HAS_SF_SDK = False
-try:
- import solidfire.common
- HAS_SF_SDK = True
-except ImportError:
- HAS_SF_SDK = False
-
-
-def has_sf_sdk():
- return HAS_SF_SDK
-
-
-class NaElementSWModule(object):
- ''' Support class for common or shared functions '''
- def __init__(self, elem):
- self.elem_connect = elem
- self.parameters = dict()
-
- def get_volume(self, volume_id):
- """
- Return volume details if volume exists for given volume_id
-
- :param volume_id: volume ID
- :type volume_id: int
- :return: Volume dict if found, None if not found
- :rtype: dict
- """
- volume_list = self.elem_connect.list_volumes(volume_ids=[volume_id])
- for volume in volume_list.volumes:
- if volume.volume_id == volume_id:
- if str(volume.delete_time) == "":
- return volume
- return None
-
- def get_volume_id(self, vol_name, account_id):
- """
- Return volume id from the given (valid) account_id if found
- Return None if not found
-
- :param vol_name: Name of the volume
- :type vol_name: str
- :param account_id: Account ID
- :type account_id: int
-
- :return: Volume ID of the first matching volume if found. None if not found.
- :rtype: int
- """
- volume_list = self.elem_connect.list_volumes_for_account(account_id=account_id)
- for volume in volume_list.volumes:
- if volume.name == vol_name:
- # return volume_id
- if str(volume.delete_time) == "":
- return volume.volume_id
- return None
-
- def volume_id_exists(self, volume_id):
- """
- Return volume_id if volume exists for given volume_id
-
- :param volume_id: volume ID
- :type volume_id: int
- :return: Volume ID if found, None if not found
- :rtype: int
- """
- volume_list = self.elem_connect.list_volumes(volume_ids=[volume_id])
- for volume in volume_list.volumes:
- if volume.volume_id == volume_id:
- if str(volume.delete_time) == "":
- return volume.volume_id
- return None
-
- def volume_exists(self, volume, account_id):
- """
- Return volume_id if exists, None if not found
-
- :param volume: Volume ID or Name
- :type volume: str
- :param account_id: Account ID (valid)
- :type account_id: int
- :return: Volume ID if found, None if not found
- """
- # If volume is an integer, get_by_id
- if str(volume).isdigit():
- volume_id = int(volume)
- try:
- if self.volume_id_exists(volume_id):
- return volume_id
- except solidfire.common.ApiServerError:
- # don't fail, continue and try get_by_name
- pass
- # get volume by name
- volume_id = self.get_volume_id(volume, account_id)
- return volume_id
-
- def get_snapshot(self, snapshot_id, volume_id):
- """
- Return snapshot details if found
-
- :param snapshot_id: Snapshot ID or Name
- :type snapshot_id: str
- :param volume_id: Account ID (valid)
- :type volume_id: int
- :return: Snapshot dict if found, None if not found
- :rtype: dict
- """
- # mandate src_volume_id although not needed by sdk
- snapshot_list = self.elem_connect.list_snapshots(
- volume_id=volume_id)
- for snapshot in snapshot_list.snapshots:
- # if actual id is provided
- if str(snapshot_id).isdigit() and snapshot.snapshot_id == int(snapshot_id):
- return snapshot
- # if snapshot name is provided
- elif snapshot.name == snapshot_id:
- return snapshot
- return None
-
- @staticmethod
- def map_qos_obj_to_dict(qos_obj):
- ''' Take a QOS object and return a key, normalize the key names
- Interestingly, the APIs are using different ids for create and get
- '''
- mappings = [
- ('burst_iops', 'burstIOPS'),
- ('min_iops', 'minIOPS'),
- ('max_iops', 'maxIOPS'),
- ]
- qos_dict = vars(qos_obj)
- # Align names to create API and module interface
- for read, send in mappings:
- if read in qos_dict:
- qos_dict[send] = qos_dict.pop(read)
- return qos_dict
-
- def get_qos_policy(self, name):
- """
- Get QOS Policy
- :description: Get QOS Policy object for a given name
- :return: object, error
- Policy object converted to dict if found, else None
- Error text if error, else None
- :rtype: dict/None, str/None
- """
- try:
- qos_policy_list_obj = self.elem_connect.list_qos_policies()
- except (solidfire.common.ApiServerError, solidfire.common.ApiConnectionError) as exc:
- error = "Error getting list of qos policies: %s" % to_native(exc)
- return None, error
-
- policy_dict = dict()
- if hasattr(qos_policy_list_obj, 'qos_policies'):
- for policy in qos_policy_list_obj.qos_policies:
- # Check and get policy object for a given name
- if str(policy.qos_policy_id) == name:
- policy_dict = vars(policy)
- elif policy.name == name:
- policy_dict = vars(policy)
- if 'qos' in policy_dict:
- policy_dict['qos'] = self.map_qos_obj_to_dict(policy_dict['qos'])
-
- return policy_dict if policy_dict else None, None
-
- def account_exists(self, account):
- """
- Return account_id if account exists for given account id or name
- Raises an exception if account does not exist
-
- :param account: Account ID or Name
- :type account: str
- :return: Account ID if found, None if not found
- """
- # If account is an integer, get_by_id
- if account.isdigit():
- account_id = int(account)
- try:
- result = self.elem_connect.get_account_by_id(account_id=account_id)
- if result.account.account_id == account_id:
- return account_id
- except solidfire.common.ApiServerError:
- # don't fail, continue and try get_by_name
- pass
- # get account by name, the method returns an Exception if account doesn't exist
- result = self.elem_connect.get_account_by_name(username=account)
- return result.account.account_id
-
- def set_element_attributes(self, source):
- """
- Return telemetry attributes for the current execution
-
- :param source: name of the module
- :type source: str
- :return: a dict containing telemetry attributes
- """
- attributes = {}
- attributes['config-mgmt'] = 'ansible'
- attributes['event-source'] = source
- return attributes
diff --git a/ansible_collections/netapp/elementsw/plugins/module_utils/netapp_module.py b/ansible_collections/netapp/elementsw/plugins/module_utils/netapp_module.py
deleted file mode 100644
index c2b02d3d2..000000000
--- a/ansible_collections/netapp/elementsw/plugins/module_utils/netapp_module.py
+++ /dev/null
@@ -1,225 +0,0 @@
-# This code is part of Ansible, but is an independent component.
-# This particular file snippet, and this file snippet only, is BSD licensed.
-# Modules you write using this snippet, which is embedded dynamically by Ansible
-# still belong to the author of the module, and may assign their own license
-# to the complete work.
-#
-# Copyright (c) 2018, NetApp Ansible Team <ng-ansibleteam@netapp.com>
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification,
-# are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# * Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
-# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-''' Support class for NetApp ansible modules '''
-
-from __future__ import (absolute_import, division, print_function)
-__metaclass__ = type
-
-
-def cmp(a, b):
- """
- Python 3 does not have a cmp function, this will do the cmp.
- :param a: first object to check
- :param b: second object to check
- :return:
- """
- # convert to lower case for string comparison.
- if a is None:
- return -1
- if type(a) is str and type(b) is str:
- a = a.lower()
- b = b.lower()
- # if list has string element, convert string to lower case.
- if type(a) is list and type(b) is list:
- a = [x.lower() if type(x) is str else x for x in a]
- b = [x.lower() if type(x) is str else x for x in b]
- a.sort()
- b.sort()
- return (a > b) - (a < b)
-
-
-class NetAppModule(object):
- '''
- Common class for NetApp modules
- set of support functions to derive actions based
- on the current state of the system, and a desired state
- '''
-
- def __init__(self):
- self.log = list()
- self.changed = False
- self.parameters = {'name': 'not intialized'}
- # self.debug = list()
-
- def set_parameters(self, ansible_params):
- self.parameters = dict()
- for param in ansible_params:
- if ansible_params[param] is not None:
- self.parameters[param] = ansible_params[param]
- return self.parameters
-
- def get_cd_action(self, current, desired):
- ''' takes a desired state and a current state, and return an action:
- create, delete, None
- eg:
- is_present = 'absent'
- some_object = self.get_object(source)
- if some_object is not None:
- is_present = 'present'
- action = cd_action(current=is_present, desired = self.desired.state())
- '''
- if 'state' in desired:
- desired_state = desired['state']
- else:
- desired_state = 'present'
-
- if current is None and desired_state == 'absent':
- return None
- if current is not None and desired_state == 'present':
- return None
- # change in state
- self.changed = True
- if current is not None:
- return 'delete'
- return 'create'
-
- def compare_and_update_values(self, current, desired, keys_to_compare):
- updated_values = dict()
- is_changed = False
- for key in keys_to_compare:
- if key in current:
- if key in desired and desired[key] is not None:
- if current[key] != desired[key]:
- updated_values[key] = desired[key]
- is_changed = True
- else:
- updated_values[key] = current[key]
- else:
- updated_values[key] = current[key]
-
- return updated_values, is_changed
-
- @staticmethod
- def check_keys(current, desired):
- ''' TODO: raise an error if keys do not match
- with the exception of:
- new_name, state in desired
- '''
- pass
-
- @staticmethod
- def compare_lists(current, desired, get_list_diff):
- ''' compares two lists and return a list of elements that are either the desired elements or elements that are
- modified from the current state depending on the get_list_diff flag
- :param: current: current item attribute in ONTAP
- :param: desired: attributes from playbook
- :param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
- :return: list of attributes to be modified
- :rtype: list
- '''
- desired_diff_list = [item for item in desired if item not in current] # get what in desired and not in current
- current_diff_list = [item for item in current if item not in desired] # get what in current but not in desired
-
- if desired_diff_list or current_diff_list:
- # there are changes
- if get_list_diff:
- return desired_diff_list
- else:
- return desired
- else:
- return []
-
- def get_modified_attributes(self, current, desired, get_list_diff=False, additional_keys=False):
- ''' takes two dicts of attributes and return a dict of attributes that are
- not in the current state
- It is expected that all attributes of interest are listed in current and
- desired.
- The same assumption holds true for any nested directory.
- TODO: This is actually not true for the ElementSW 'attributes' directory.
- Practically it means you cannot add or remove a key in a modify.
- :param: current: current attributes in ONTAP
- :param: desired: attributes from playbook
- :param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
- :return: dict of attributes to be modified
- :rtype: dict
-
- NOTE: depending on the attribute, the caller may need to do a modify or a
- different operation (eg move volume if the modified attribute is an
- aggregate name)
- '''
- # uncomment these 2 lines if needed
- # self.log.append('current: %s' % repr(current))
- # self.log.append('desired: %s' % repr(desired))
- # if the object does not exist, we can't modify it
- modified = dict()
- if current is None:
- return modified
-
- # error out if keys do not match
- self.check_keys(current, desired)
-
- # collect changed attributes
- for key, value in current.items():
- if key in desired and desired[key] is not None:
- if type(value) is list:
- modified_list = self.compare_lists(value, desired[key], get_list_diff) # get modified list from current and desired
- if modified_list:
- modified[key] = modified_list
- elif type(value) is dict:
- modified_dict = self.get_modified_attributes(value, desired[key], get_list_diff, additional_keys=True)
- if modified_dict:
- modified[key] = modified_dict
- elif cmp(value, desired[key]) != 0:
- modified[key] = desired[key]
- if additional_keys:
- for key, value in desired.items():
- if key not in current:
- modified[key] = desired[key]
- if modified:
- self.changed = True
- # Uncomment this line if needed
- # self.log.append('modified: %s' % repr(modified))
- return modified
-
- def is_rename_action(self, source, target):
- ''' takes a source and target object, and returns True
- if a rename is required
- eg:
- source = self.get_object(source_name)
- target = self.get_object(target_name)
- action = is_rename_action(source, target)
- :return: None for error, True for rename action, False otherwise
- '''
- if source is None and target is None:
- # error, do nothing
- # cannot rename an non existent resource
- # alternatively we could create B
- return None
- if source is not None and target is not None:
- # error, do nothing
- # idempotency (or) new_name_is_already_in_use
- # alternatively we could delete B and rename A to B
- return False
- if source is None and target is not None:
- # do nothing, maybe the rename was already done
- return False
- # source is not None and target is None:
- # rename is in order
- self.changed = True
- return True
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group.py
deleted file mode 100644
index 467ca415c..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group.py
+++ /dev/null
@@ -1,397 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-"""
-Element Software Access Group Manager
-"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_access_group
-
-short_description: NetApp Element Software Manage Access Groups
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, destroy, or update access groups on Element Software Cluster.
-
-options:
-
- state:
- description:
- - Whether the specified access group should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- from_name:
- description:
- - ID or Name of the access group to rename.
- - Required to create a new access group called 'name' by renaming 'from_name'.
- version_added: 2.8.0
- type: str
-
- name:
- description:
- - Name for the access group for create, modify and delete operations.
- required: True
- aliases:
- - src_access_group_id
- type: str
-
- initiators:
- description:
- - List of initiators to include in the access group. If unspecified, the access group will start out without configured initiators.
- type: list
- elements: str
-
- volumes:
- description:
- - List of volumes to initially include in the volume access group. If unspecified, the access group will start without any volumes.
- - It accepts either volume_name or volume_id
- type: list
- elements: str
-
- account_id:
- description:
- - Account ID for the owner of this volume.
- - It accepts either account_name or account_id
- - if account_id is digit, it will consider as account_id
- - If account_id is string, it will consider as account_name
- version_added: 2.8.0
- type: str
-
- virtual_network_id:
- description:
- - The ID of the Element SW Software Cluster Virtual Network to associate the access group with.
- type: int
-
- virtual_network_tags:
- description:
- - The tags of VLAN Virtual Network Tag to associate the access group with.
- type: list
- elements: str
-
- attributes:
- description: List of Name/Value pairs in JSON object format.
- type: dict
-
-'''
-
-EXAMPLES = """
- - name: Create Access Group
- na_elementsw_access_group:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- name: AnsibleAccessGroup
- volumes: [7,8]
- account_id: 1
-
- - name: Modify Access Group
- na_elementsw_access_group:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- name: AnsibleAccessGroup-Renamed
- account_id: 1
- attributes: {"volumes": [1,2,3], "virtual_network_id": 12345}
-
- - name: Rename Access Group
- na_elementsw_access_group:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- from_name: AnsibleAccessGroup
- name: AnsibleAccessGroup-Renamed
-
- - name: Delete Access Group
- na_elementsw_access_group:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- name: 1
-"""
-
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWAccessGroup(object):
- """
- Element Software Volume Access Group
- """
-
- def __init__(self):
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- from_name=dict(required=False, type='str'),
- name=dict(required=True, aliases=["src_access_group_id"], type='str'),
- initiators=dict(required=False, type='list', elements='str'),
- volumes=dict(required=False, type='list', elements='str'),
- account_id=dict(required=False, type='str'),
- virtual_network_id=dict(required=False, type='int'),
- virtual_network_tags=dict(required=False, type='list', elements='str'),
- attributes=dict(required=False, type='dict'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['account_id'])
- ],
- supports_check_mode=True
- )
-
- input_params = self.module.params
-
- # Set up state variables
- self.state = input_params['state']
- self.from_name = input_params['from_name']
- self.access_group_name = input_params['name']
- self.initiators = input_params['initiators']
- self.volumes = input_params['volumes']
- self.account_id = input_params['account_id']
- self.virtual_network_id = input_params['virtual_network_id']
- self.virtual_network_tags = input_params['virtual_network_tags']
- self.attributes = input_params['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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- if self.attributes is not None:
- self.attributes.update(self.elementsw_helper.set_element_attributes(source='na_elementsw_access_group'))
- else:
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_access_group')
-
- def get_access_group(self, name):
- """
- Get Access Group
- :description: Get Access Group object for a given name
-
- :return: object (Group object)
- :rtype: object (Group object)
- """
- access_groups_list = self.sfe.list_volume_access_groups()
- group_obj = None
-
- for group in access_groups_list.volume_access_groups:
- # Check and get access_group object for a given name
- if str(group.volume_access_group_id) == name:
- group_obj = group
- elif group.name == name:
- group_obj = group
-
- return group_obj
-
- def get_account_id(self):
- # Validate account id
- # Return account_id if found, None otherwise
- try:
- account_id = self.elementsw_helper.account_exists(self.account_id)
- return account_id
- except solidfire.common.ApiServerError:
- return None
-
- def get_volume_ids(self):
- # Validate volume_ids
- # Return volume ids if found, fail if not found
- volume_ids = []
- for volume in self.volumes:
- volume_id = self.elementsw_helper.volume_exists(volume, self.account_id)
- if volume_id:
- volume_ids.append(volume_id)
- else:
- self.module.fail_json(msg='Specified volume %s does not exist' % volume)
- return volume_ids
-
- def create_access_group(self):
- """
- Create the Access Group
- """
- try:
- self.sfe.create_volume_access_group(name=self.access_group_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.access_group_name, to_native(e)), exception=traceback.format_exc())
-
- def delete_access_group(self):
- """
- Delete the Access Group
- """
- try:
- self.sfe.delete_volume_access_group(volume_access_group_id=self.group_id)
-
- except Exception as e:
- self.module.fail_json(msg="Error deleting volume access group %s: %s" %
- (self.access_group_name, to_native(e)),
- exception=traceback.format_exc())
-
- def update_access_group(self):
- """
- Update the Access Group if the access_group already exists
- """
- try:
- self.sfe.modify_volume_access_group(volume_access_group_id=self.group_id,
- virtual_network_id=self.virtual_network_id,
- virtual_network_tags=self.virtual_network_tags,
- 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.access_group_name, to_native(e)), exception=traceback.format_exc())
-
- def rename_access_group(self):
- """
- Rename the Access Group to the new name
- """
- try:
- self.sfe.modify_volume_access_group(volume_access_group_id=self.from_group_id,
- virtual_network_id=self.virtual_network_id,
- virtual_network_tags=self.virtual_network_tags,
- name=self.access_group_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.from_name, to_native(e)), exception=traceback.format_exc())
-
- def apply(self):
- """
- Process the access group operation on the Element Software Cluster
- """
- changed = False
- action = None
-
- input_account_id = self.account_id
- if self.account_id is not None:
- self.account_id = self.get_account_id()
- if self.state == 'present' and self.volumes is not None:
- if self.account_id:
- self.volumes = self.get_volume_ids()
- else:
- self.module.fail_json(msg='Error: Specified account id "%s" does not exist.' % str(input_account_id))
-
- group_detail = self.get_access_group(self.access_group_name)
-
- if group_detail is not None:
- # If access group found
- self.group_id = group_detail.volume_access_group_id
-
- if self.state == "absent":
- action = 'delete'
- changed = True
- else:
- # If state - present, check for any parameter of exising group needs modification.
- if self.volumes is not None and len(self.volumes) > 0:
- # Compare the volume list
- if not group_detail.volumes:
- # If access group does not have any volume attached
- action = 'update'
- changed = True
- else:
- for volumeID in group_detail.volumes:
- if volumeID not in self.volumes:
- action = 'update'
- changed = True
- break
-
- elif self.initiators is not None and group_detail.initiators != self.initiators:
- action = 'update'
- changed = True
-
- elif self.virtual_network_id is not None or self.virtual_network_tags is not None:
- action = 'update'
- changed = True
-
- else:
- # access_group does not exist
- if self.state == "present" and self.from_name is not None:
- group_detail = self.get_access_group(self.from_name)
- if group_detail is not None:
- # If resource pointed by from_name exists, rename the access_group to name
- self.from_group_id = group_detail.volume_access_group_id
- action = 'rename'
- changed = True
- else:
- # If resource pointed by from_name does not exists, error out
- self.module.fail_json(msg="Resource does not exist : %s" % self.from_name)
- elif self.state == "present":
- # If from_name is not defined, Create from scratch.
- action = 'create'
- changed = True
-
- if changed and not self.module.check_mode:
- if action == 'create':
- self.create_access_group()
- elif action == 'rename':
- self.rename_access_group()
- elif action == 'update':
- self.update_access_group()
- elif action == 'delete':
- self.delete_access_group()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_access_group = ElementSWAccessGroup()
- na_elementsw_access_group.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group_volumes.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group_volumes.py
deleted file mode 100644
index af9053a13..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_access_group_volumes.py
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2019, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-"""
-Element Software Access Group Volumes
-"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_access_group_volumes
-
-short_description: NetApp Element Software Add/Remove Volumes to/from Access Group
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 20.1.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Add or remove volumes to/from access group on Element Software Cluster.
-
-options:
-
- state:
- description:
- - Whether the specified volumes should exist or not for this access group.
- choices: ['present', 'absent']
- default: present
- type: str
-
- access_group:
- description:
- - Name or id for the access group to add volumes to, or remove volumes from
- required: true
- type: str
-
- volumes:
- description:
- - List of volumes to add/remove from/to the access group.
- - It accepts either volume_name or volume_id
- required: True
- type: list
- elements: str
-
- account_id:
- description:
- - Account ID for the owner of this volume.
- - It accepts either account_name or account_id
- - if account_id is numeric, look up for account_id first, then look up for account_name
- - If account_id is not numeric, look up for account_name
- required: true
- type: str
-'''
-
-EXAMPLES = """
- - name: Add Volumes to Access Group
- na_elementsw_access_group:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- access_group: AnsibleAccessGroup
- volumes: ['vol7','vol8','vol9']
- account_id: '1'
-
- - name: Remove Volumes from Access Group
- na_elementsw_access_group:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- access_group: AnsibleAccessGroup
- volumes: ['vol7','vol9']
- account_id: '1'
-"""
-
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWAccessGroupVolumes(object):
- """
- Element Software Access Group Volumes
- """
-
- def __init__(self):
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- access_group=dict(required=True, type='str'),
- volumes=dict(required=True, type='list', elements='str'),
- account_id=dict(required=True, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- input_params = self.module.params
-
- # Set up state variables
- self.state = input_params['state']
- self.access_group_name = input_params['access_group']
- self.volumes = input_params['volumes']
- self.account_id = input_params['account_id']
-
- 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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_access_group')
-
- def get_access_group(self, name):
- """
- Get Access Group
- :description: Get Access Group object for a given name
-
- :return: object (Group object)
- :rtype: object (Group object)
- """
- access_groups_list = self.sfe.list_volume_access_groups()
- group_obj = None
-
- for group in access_groups_list.volume_access_groups:
- # Check and get access_group object for a given name
- if str(group.volume_access_group_id) == name:
- group_obj = group
- elif group.name == name:
- group_obj = group
-
- return group_obj
-
- def get_account_id(self):
- # Validate account id
- # Return account_id if found, None otherwise
- try:
- account_id = self.elementsw_helper.account_exists(self.account_id)
- return account_id
- except solidfire.common.ApiServerError:
- return None
-
- def get_volume_ids(self):
- # Validate volume_ids
- # Return volume ids if found, fail if not found
- volume_ids = []
- for volume in self.volumes:
- volume_id = self.elementsw_helper.volume_exists(volume, self.account_id)
- if volume_id:
- volume_ids.append(volume_id)
- else:
- self.module.fail_json(msg='Error: Specified volume %s does not exist' % volume)
- return volume_ids
-
- def update_access_group(self, volumes):
- """
- Update the Access Group if the access_group already exists
- """
- try:
- self.sfe.modify_volume_access_group(volume_access_group_id=self.group_id,
- volumes=volumes)
- except Exception as e:
- self.module.fail_json(msg="Error updating volume access group %s: %s" %
- (self.access_group_name, to_native(e)), exception=traceback.format_exc())
-
- def apply(self):
- """
- Process the volume add/remove operations for the access group on the Element Software Cluster
- """
- changed = False
- input_account_id = self.account_id
-
- if self.account_id is not None:
- self.account_id = self.get_account_id()
- if self.account_id is None:
- self.module.fail_json(msg='Error: Specified account id "%s" does not exist.' % str(input_account_id))
-
- # get volume data
- self.volume_ids = self.get_volume_ids()
- group_detail = self.get_access_group(self.access_group_name)
- if group_detail is None:
- self.module.fail_json(msg='Error: Specified access group "%s" does not exist for account id: %s.' % (self.access_group_name, str(input_account_id)))
- self.group_id = group_detail.volume_access_group_id
- volumes = group_detail.volumes
-
- # compare expected list of volumes to existing one
- if self.state == "absent":
- # remove volumes if present in access group
- volumes = [vol for vol in group_detail.volumes if vol not in self.volume_ids]
- else:
- # add volumes if not already present
- volumes = [vol for vol in self.volume_ids if vol not in group_detail.volumes]
- volumes.extend(group_detail.volumes)
-
- # update if there is a change
- if len(volumes) != len(group_detail.volumes):
- if not self.module.check_mode:
- self.update_access_group(volumes)
- changed = True
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_access_group_volumes = ElementSWAccessGroupVolumes()
- na_elementsw_access_group_volumes.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_account.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_account.py
deleted file mode 100644
index 862753747..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_account.py
+++ /dev/null
@@ -1,340 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-"""
-Element Software Account Manager
-"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_account
-
-short_description: NetApp Element Software Manage Accounts
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, destroy, or update accounts on Element SW
-
-options:
-
- state:
- description:
- - Whether the specified account should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- element_username:
- description:
- - Unique username for this account. (May be 1 to 64 characters in length).
- required: true
- aliases:
- - account_id
- type: str
-
- from_name:
- description:
- - ID or Name of the account to rename.
- - Required to create an account called 'element_username' by renaming 'from_name'.
- version_added: 2.8.0
- type: str
-
- 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.
- type: str
-
- 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.
- type: str
-
- attributes:
- description: List of Name/Value pairs in JSON object format.
- type: dict
-
- status:
- description:
- - Status of the account.
- type: str
-
-'''
-
-EXAMPLES = """
-- name: Create Account
- na_elementsw_account:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- element_username: TenantA
-
-- name: Modify Account
- na_elementsw_account:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- status: locked
- element_username: TenantA
-
-- name: Rename Account
- na_elementsw_account:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- element_username: TenantA_Renamed
- from_name: TenantA
-
-- name: Rename and modify Account
- na_elementsw_account:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- status: locked
- element_username: TenantA_Renamed
- from_name: TenantA
-
-- name: Delete Account
- na_elementsw_account:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- element_username: TenantA_Renamed
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementSWAccount(object):
- """
- Element SW Account
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- element_username=dict(required=True, aliases=["account_id"], type='str'),
- from_name=dict(required=False, default=None),
- initiator_secret=dict(required=False, type='str', no_log=True),
- target_secret=dict(required=False, type='str', no_log=True),
- attributes=dict(required=False, type='dict'),
- status=dict(required=False, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- params = self.module.params
-
- # set up state variables
- self.state = params.get('state')
- self.element_username = params.get('element_username')
- self.from_name = params.get('from_name')
- self.initiator_secret = params.get('initiator_secret')
- self.target_secret = params.get('target_secret')
- self.attributes = params.get('attributes')
- self.status = params.get('status')
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the Element SW Python SDK")
- else:
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- if self.attributes is not None:
- self.attributes.update(self.elementsw_helper.set_element_attributes(source='na_elementsw_account'))
- else:
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_account')
-
- def get_account(self, username):
- """
- Get Account
- :description: Get Account object from account id or name
-
- :return: Details about the account. None if not found.
- :rtype: object (Account object)
- """
-
- account_list = self.sfe.list_accounts()
-
- for account in account_list.accounts:
- # Check and get account object for a given name
- if str(account.account_id) == username:
- return account
- elif account.username == username:
- return account
- return None
-
- def create_account(self):
- """
- Create the Account
- """
- try:
- self.sfe.add_account(username=self.element_username,
- 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.element_username, to_native(e)),
- exception=traceback.format_exc())
-
- def delete_account(self):
- """
- Delete the Account
- """
- 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 rename_account(self):
- """
- Rename the Account
- """
- try:
- self.sfe.modify_account(account_id=self.account_id,
- username=self.element_username,
- 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 renaming account %s: %s' % (self.account_id, to_native(e)),
- exception=traceback.format_exc())
-
- def update_account(self):
- """
- Update the Account if account already exists
- """
- try:
- self.sfe.modify_account(account_id=self.account_id,
- 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):
- """
- Process the account operation on the Element OS Cluster
- """
- changed = False
- update_account = False
- account_detail = self.get_account(self.element_username)
-
- if account_detail is None and self.state == 'present':
- changed = True
-
- elif account_detail is not None:
- # If account found
- self.account_id = account_detail.account_id
-
- if self.state == 'absent':
- changed = True
- else:
- # If state - present, check for any parameter of exising account needs modification.
- if account_detail.username is not None and self.element_username is not None and \
- account_detail.username != self.element_username:
- 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
- if changed:
- if self.module.check_mode:
- # Skipping the changes
- pass
- else:
- if self.state == 'present':
- if update_account:
- self.update_account()
- else:
- if self.from_name is not None:
- # If from_name is defined
- account_exists = self.get_account(self.from_name)
- if account_exists is not None:
- # If resource pointed by from_name exists, rename the account to name
- self.account_id = account_exists.account_id
- self.rename_account()
- else:
- # If resource pointed by from_name does not exists, error out
- self.module.fail_json(msg="Resource does not exist : %s" % self.from_name)
- else:
- # If from_name is not defined, create from scratch.
- self.create_account()
- elif self.state == 'absent':
- self.delete_account()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_account = ElementSWAccount()
- na_elementsw_account.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_admin_users.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_admin_users.py
deleted file mode 100644
index 7ad46648a..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_admin_users.py
+++ /dev/null
@@ -1,233 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_admin_users
-
-short_description: NetApp Element Software Manage Admin Users
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, destroy, or update admin users on SolidFire
-
-options:
-
- state:
- description:
- - Whether the specified account should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- element_username:
- description:
- - Unique username for this account. (May be 1 to 64 characters in length).
- required: true
- type: str
-
- element_password:
- description:
- - The password for the new admin account. Setting the password attribute will always reset your password, even if the password is the same
- type: str
-
- acceptEula:
- description:
- - Boolean, true for accepting Eula, False Eula
- type: bool
-
- access:
- description:
- - A list of types the admin has access to
- type: list
- elements: str
-'''
-
-EXAMPLES = """
- - name: Add admin user
- na_elementsw_admin_users:
- state: present
- username: "{{ admin_user_name }}"
- password: "{{ admin_password }}"
- hostname: "{{ hostname }}"
- element_username: carchi8py
- element_password: carchi8py
- acceptEula: True
- access: accounts,drives
-
- - name: modify admin user
- na_elementsw_admin_users:
- state: present
- username: "{{ admin_user_name }}"
- password: "{{ admin_password }}"
- hostname: "{{ hostname }}"
- element_username: carchi8py
- element_password: carchi8py12
- acceptEula: True
- access: accounts,drives,nodes
-
- - name: delete admin user
- na_elementsw_admin_users:
- state: absent
- username: "{{ admin_user_name }}"
- password: "{{ admin_password }}"
- hostname: "{{ hostname }}"
- element_username: carchi8py
-"""
-
-RETURN = """
-
-"""
-
-from ansible.module_utils.basic import AnsibleModule
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class NetAppElementSWAdminUser(object):
- """
- Class to set, modify and delete admin users on ElementSW box
- """
-
- def __init__(self):
- """
- Initialize the NetAppElementSWAdminUser class.
- """
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- element_username=dict(required=True, type='str'),
- element_password=dict(required=False, type='str', no_log=True),
- acceptEula=dict(required=False, type='bool'),
- access=dict(required=False, type='list', elements='str')
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- param = self.module.params
- # set up state variables
- self.state = param['state']
- self.element_username = param['element_username']
- self.element_password = param['element_password']
- self.acceptEula = param['acceptEula']
- self.access = param['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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_admin_users')
-
- def does_admin_user_exist(self):
- """
- Checks to see if an admin user exists or not
- :return: True if the user exist, False if it dose not exist
- """
- admins_list = self.sfe.list_cluster_admins()
- for admin in admins_list.cluster_admins:
- if admin.username == self.element_username:
- return True
- return False
-
- def get_admin_user(self):
- """
- Get the admin user object
- :return: the admin user object
- """
- admins_list = self.sfe.list_cluster_admins()
- for admin in admins_list.cluster_admins:
- if admin.username == self.element_username:
- return admin
- return None
-
- def modify_admin_user(self):
- """
- Modify a admin user. If a password is set the user will be modified as there is no way to
- compare a new password with an existing one
- :return: if a user was modified or not
- """
- changed = False
- admin_user = self.get_admin_user()
- if self.access is not None and len(self.access) > 0:
- for access in self.access:
- if access not in admin_user.access:
- changed = True
- if changed and not self.module.check_mode:
- self.sfe.modify_cluster_admin(cluster_admin_id=admin_user.cluster_admin_id,
- access=self.access,
- password=self.element_password,
- attributes=self.attributes)
-
- return changed
-
- def add_admin_user(self):
- """
- Add's a new admin user to the element cluster
- :return: nothing
- """
- self.sfe.add_cluster_admin(username=self.element_username,
- password=self.element_password,
- access=self.access,
- accept_eula=self.acceptEula,
- attributes=self.attributes)
-
- def delete_admin_user(self):
- """
- Deletes an existing admin user from the element cluster
- :return: nothing
- """
- admin_user = self.get_admin_user()
- self.sfe.remove_cluster_admin(cluster_admin_id=admin_user.cluster_admin_id)
-
- def apply(self):
- """
- determines which method to call to set, delete or modify admin users
- :return:
- """
- changed = False
- if self.state == "present":
- if self.does_admin_user_exist():
- changed = self.modify_admin_user()
- else:
- if not self.module.check_mode:
- self.add_admin_user()
- changed = True
- else:
- if self.does_admin_user_exist():
- if not self.module.check_mode:
- self.delete_admin_user()
- changed = True
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetAppElementSWAdminUser()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_backup.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_backup.py
deleted file mode 100644
index e81e7c5ea..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_backup.py
+++ /dev/null
@@ -1,243 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-"""
-Element Software Backup Manager
-"""
-from __future__ import absolute_import, division, print_function
-
-__metaclass__ = type
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-DOCUMENTATION = '''
-
-module: na_elementsw_backup
-
-short_description: NetApp Element Software Create Backups
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create backup
-
-options:
-
- src_volume_id:
- description:
- - ID of the backup source volume.
- required: true
- aliases:
- - volume_id
- type: str
-
- dest_hostname:
- description:
- - hostname for the backup source cluster
- - will be set equal to hostname if not specified
- required: false
- type: str
-
- dest_username:
- description:
- - username for the backup destination cluster
- - will be set equal to username if not specified
- required: false
- type: str
-
- dest_password:
- description:
- - password for the backup destination cluster
- - will be set equal to password if not specified
- required: false
- type: str
-
- dest_volume_id:
- description:
- - ID of the backup destination volume
- required: true
- type: str
-
- format:
- description:
- - Backup format to use
- choices: ['native','uncompressed']
- required: false
- default: 'native'
- type: str
-
- script:
- description:
- - the backup script to be executed
- required: false
- type: str
-
- script_parameters:
- description:
- - the backup script parameters
- required: false
- type: dict
-
-'''
-
-EXAMPLES = """
-na_elementsw_backup:
- hostname: "{{ source_cluster_hostname }}"
- username: "{{ source_cluster_username }}"
- password: "{{ source_cluster_password }}"
- src_volume_id: 1
- dest_hostname: "{{ destination_cluster_hostname }}"
- dest_username: "{{ destination_cluster_username }}"
- dest_password: "{{ destination_cluster_password }}"
- dest_volume_id: 3
- format: native
-"""
-
-RETURN = """
-
-"""
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-import time
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWBackup(object):
- ''' class to handle backup operations '''
-
- def __init__(self):
- """
- Setup Ansible parameters and SolidFire connection
- """
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
-
- self.argument_spec.update(dict(
-
- src_volume_id=dict(aliases=['volume_id'], required=True, type='str'),
- dest_hostname=dict(required=False, type='str'),
- dest_username=dict(required=False, type='str'),
- dest_password=dict(required=False, type='str', no_log=True),
- dest_volume_id=dict(required=True, type='str'),
- format=dict(required=False, choices=['native', 'uncompressed'], default='native'),
- script=dict(required=False, type='str'),
- script_parameters=dict(required=False, type='dict')
-
-
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_together=[['script', 'script_parameters']],
- supports_check_mode=True
- )
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
-
- # If destination cluster details are not specified , set the destination to be the same as the source
- if self.module.params["dest_hostname"] is None:
- self.module.params["dest_hostname"] = self.module.params["hostname"]
- if self.module.params["dest_username"] is None:
- self.module.params["dest_username"] = self.module.params["username"]
- if self.module.params["dest_password"] is None:
- self.module.params["dest_password"] = self.module.params["password"]
-
- params = self.module.params
-
- # establish a connection to both source and destination elementsw clusters
- self.src_connection = netapp_utils.create_sf_connection(self.module)
- self.module.params["username"] = params["dest_username"]
- self.module.params["password"] = params["dest_password"]
- self.module.params["hostname"] = params["dest_hostname"]
- self.dest_connection = netapp_utils.create_sf_connection(self.module)
-
- self.elementsw_helper = NaElementSWModule(self.src_connection)
-
- # add telemetry attributes
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_backup')
-
- def apply(self):
- """
- Apply backup creation logic
- """
- self.create_backup()
- self.module.exit_json(changed=True)
-
- def create_backup(self):
- """
- Create backup
- """
-
- # Start volume write on destination cluster
-
- try:
- write_obj = self.dest_connection.start_bulk_volume_write(volume_id=self.module.params["dest_volume_id"],
- format=self.module.params["format"],
- attributes=self.attributes)
- write_key = write_obj.key
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error starting bulk write on destination cluster", exception=to_native(err))
-
- # Set script parameters if not passed by user
- # These parameters are equivalent to the options used when a backup is executed via the GUI
-
- if self.module.params["script"] is None and self.module.params["script_parameters"] is None:
-
- self.module.params["script"] = 'bv_internal.py'
- self.module.params["script_parameters"] = {"write": {
- "mvip": self.module.params["dest_hostname"],
- "username": self.module.params["dest_username"],
- "password": self.module.params["dest_password"],
- "key": write_key,
- "endpoint": "solidfire",
- "format": self.module.params["format"]},
- "range": {"lba": 0, "blocks": 244224}}
-
- # Start volume read on source cluster
-
- try:
- read_obj = self.src_connection.start_bulk_volume_read(self.module.params["src_volume_id"],
- self.module.params["format"],
- script=self.module.params["script"],
- script_parameters=self.module.params["script_parameters"],
- attributes=self.attributes)
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error starting bulk read on source cluster", exception=to_native(err))
-
- # Poll job status until it has completed
- # SF will automatically timeout if the job is not successful after certain amount of time
-
- completed = False
- while completed is not True:
- # Sleep between polling iterations to reduce api load
- time.sleep(2)
- try:
- result = self.src_connection.get_async_result(read_obj.async_handle, True)
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Unable to check backup job status", exception=to_native(err))
-
- if result["status"] != 'running':
- completed = True
- if 'error' in result:
- self.module.fail_json(msg=result['error']['message'])
-
-
-def main():
- """ Run backup operation"""
- vol_obj = ElementSWBackup()
- vol_obj.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_check_connections.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_check_connections.py
deleted file mode 100644
index 2f288dc3a..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_check_connections.py
+++ /dev/null
@@ -1,154 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_check_connections
-
-short_description: NetApp Element Software Check connectivity to MVIP and SVIP.
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@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']
- type: str
-
- mvip:
- description:
- - Optionally, use to test connection of a different MVIP.
- - This is not needed to test the connection to the target cluster.
- type: str
-
- svip:
- description:
- - Optionally, use to test connection of a different SVIP.
- - This is not needed to test the connection to the target cluster.
- type: str
-
-'''
-
-
-EXAMPLES = """
- - name: Check connections to MVIP and SVIP
- na_elementsw_check_connections:
- hostname: "{{ solidfire_hostname }}"
- username: "{{ solidfire_username }}"
- password: "{{ solidfire_password }}"
-"""
-
-RETURN = """
-
-"""
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class NaElementSWConnection(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,
- required_if=[
- ('skip', 'svip', ['mvip']),
- ('skip', 'mvip', ['svip'])
- ],
- supports_check_mode=True
- )
-
- self.na_helper = NetAppModule()
- self.parameters = self.module.params.copy()
- self.msg = ""
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the ElementSW Python SDK")
- else:
- self.elem = netapp_utils.create_sf_connection(self.module, port=442)
-
- def check_mvip_connection(self):
- """
- Check connection to MVIP
-
- :return: true if connection was successful, false otherwise.
- :rtype: bool
- """
- try:
- test = self.elem.test_connect_mvip(mvip=self.parameters['mvip'])
- # Todo - Log details about the test
- return test.details.connected
-
- except Exception as e:
- self.msg += 'Error checking connection to MVIP: %s' % to_native(e)
- return False
-
- def check_svip_connection(self):
- """
- Check connection to SVIP
-
- :return: true if connection was successful, false otherwise.
- :rtype: bool
- """
- try:
- test = self.elem.test_connect_svip(svip=self.parameters['svip'])
- # Todo - Log details about the test
- return test.details.connected
- except Exception as e:
- self.msg += 'Error checking connection to SVIP: %s' % to_native(e)
- return False
-
- def apply(self):
- passed = False
- if self.parameters.get('skip') is None:
- # Set failed and msg
- passed = self.check_mvip_connection()
- # check if both connections have passed
- passed &= self.check_svip_connection()
- elif self.parameters['skip'] == 'mvip':
- passed |= self.check_svip_connection()
- elif self.parameters['skip'] == 'svip':
- passed |= self.check_mvip_connection()
- if not passed:
- self.module.fail_json(msg=self.msg)
- else:
- self.module.exit_json()
-
-
-def main():
- connect_obj = NaElementSWConnection()
- connect_obj.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster.py
deleted file mode 100644
index ede60cae3..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster.py
+++ /dev/null
@@ -1,372 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software Initialize Cluster
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_cluster
-
-short_description: NetApp Element Software Create Cluster
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
- - Initialize Element Software node ownership to form a cluster.
- - If the cluster does not exist, username/password are still required but ignored for initial creation.
- - username/password are used as the node credentials to see if the cluster already exists.
- - username/password can also be used to set the cluster credentials.
- - If the cluster already exists, no error is returned, but changed is set to false.
- - Cluster modifications are not supported and are ignored.
-
-options:
- management_virtual_ip:
- description:
- - Floating (virtual) IP address for the cluster on the management network.
- required: true
- type: str
-
- storage_virtual_ip:
- description:
- - Floating (virtual) IP address for the cluster on the storage (iSCSI) network.
- required: true
- type: str
-
- replica_count:
- description:
- - Number of replicas of each piece of data to store in the cluster.
- default: 2
- type: int
-
- cluster_admin_username:
- description:
- - Username for the cluster admin.
- - If not provided, default to username.
- type: str
-
- cluster_admin_password:
- description:
- - Initial password for the cluster admin account.
- - If not provided, default to password.
- type: str
-
- accept_eula:
- description:
- - Required to indicate your acceptance of the End User License Agreement when creating this cluster.
- - To accept the EULA, set this parameter to true.
- type: bool
-
- nodes:
- description:
- - Storage IP (SIP) addresses of the initial set of nodes making up the cluster.
- - nodes IP must be in the list.
- required: true
- type: list
- elements: str
-
- attributes:
- description:
- - List of name-value pairs in JSON object format.
- type: dict
-
- timeout:
- description:
- - Time to wait for cluster creation to complete.
- default: 100
- type: int
- version_added: 20.8.0
-
- fail_if_cluster_already_exists_with_larger_ensemble:
- description:
- - If the cluster exists, the default is to verify that I(nodes) is a superset of the existing ensemble.
- - A superset is accepted because some nodes may have a different role.
- - But the module reports an error if the existing ensemble contains a node not listed in I(nodes).
- - This checker is disabled when this option is set to false.
- default: true
- type: bool
- version_added: 20.8.0
-
- encryption:
- description: to enable or disable encryption at rest
- type: bool
- version_added: 20.10.0
-
- order_number:
- description: (experimental) order number as provided by NetApp
- type: str
- version_added: 20.10.0
-
- serial_number:
- description: (experimental) serial number as provided by NetApp
- type: str
- version_added: 20.10.0
-'''
-
-EXAMPLES = """
-
- - name: Initialize new cluster
- tags:
- - elementsw_cluster
- na_elementsw_cluster:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- management_virtual_ip: 10.226.108.32
- storage_virtual_ip: 10.226.109.68
- replica_count: 2
- accept_eula: true
- nodes:
- - 10.226.109.72
- - 10.226.109.74
-"""
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementSWCluster(object):
- """
- Element Software Initialize node with ownership for cluster formation
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- management_virtual_ip=dict(required=True, type='str'),
- storage_virtual_ip=dict(required=True, type='str'),
- replica_count=dict(required=False, type='int', default=2),
- cluster_admin_username=dict(required=False, type='str'),
- cluster_admin_password=dict(required=False, type='str', no_log=True),
- accept_eula=dict(required=False, type='bool'),
- nodes=dict(required=True, type='list', elements='str'),
- attributes=dict(required=False, type='dict', default=None),
- timeout=dict(required=False, type='int', default=100),
- fail_if_cluster_already_exists_with_larger_ensemble=dict(required=False, type='bool', default=True),
- encryption=dict(required=False, type='bool'),
- order_number=dict(required=False, type='str'),
- serial_number=dict(required=False, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- input_params = self.module.params
-
- self.management_virtual_ip = input_params['management_virtual_ip']
- self.storage_virtual_ip = input_params['storage_virtual_ip']
- self.replica_count = input_params['replica_count']
- self.accept_eula = input_params.get('accept_eula')
- self.attributes = input_params.get('attributes')
- self.nodes = input_params['nodes']
- self.cluster_admin_username = input_params['username'] if input_params.get('cluster_admin_username') is None else input_params['cluster_admin_username']
- self.cluster_admin_password = input_params['password'] if input_params.get('cluster_admin_password') is None else input_params['cluster_admin_password']
- self.fail_if_cluster_already_exists_with_larger_ensemble = input_params['fail_if_cluster_already_exists_with_larger_ensemble']
- self.encryption = input_params['encryption']
- self.order_number = input_params['order_number']
- self.serial_number = input_params['serial_number']
- self.debug = list()
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
-
- # 442 for node APIs, 443 (default) for cluster APIs
- for role, port in [('node', 442), ('cluster', 443)]:
- try:
- # even though username/password should be optional, create_sf_connection fails if not set
- conn = netapp_utils.create_sf_connection(module=self.module, raise_on_connection_error=True, port=port, timeout=input_params['timeout'])
- if role == 'node':
- self.sfe_node = conn
- else:
- self.sfe_cluster = conn
- except netapp_utils.solidfire.common.ApiConnectionError as exc:
- if str(exc) == "Bad Credentials":
- msg = 'Most likely the cluster is already created.'
- msg += ' Make sure to use valid %s credentials for username and password.' % 'node' if port == 442 else 'cluster'
- msg += ' Even though credentials are not required for the first create, they are needed to check whether the cluster already exists.'
- msg += ' Cluster reported: %s' % repr(exc)
- else:
- msg = 'Failed to create connection: %s' % repr(exc)
- self.module.fail_json(msg=msg)
- except Exception as exc:
- self.module.fail_json(msg='Failed to connect: %s' % repr(exc))
-
- self.elementsw_helper = NaElementSWModule(self.sfe_cluster)
-
- # add telemetry attributes
- if self.attributes is not None:
- self.attributes.update(self.elementsw_helper.set_element_attributes(source='na_elementsw_cluster'))
- else:
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_cluster')
-
- def get_node_cluster_info(self):
- """
- Get Cluster Info - using node API
- """
- try:
- info = self.sfe_node.get_config()
- self.debug.append(repr(info.config.cluster))
- return info.config.cluster
- except Exception as exc:
- self.debug.append("port: %s, %s" % (str(self.sfe_node._port), repr(exc)))
- return None
-
- def check_cluster_exists(self):
- """
- validate if cluster exists with list of nodes
- error out if something is found but with different nodes
- return a tuple (found, info)
- found is True if found, False if not found
- """
- info = self.get_node_cluster_info()
- if info is None:
- return False
- ensemble = getattr(info, 'ensemble', None)
- if not ensemble:
- return False
- # format is 'id:IP'
- nodes = [x.split(':', 1)[1] for x in ensemble]
- current_ensemble_nodes = set(nodes) if ensemble else set()
- requested_nodes = set(self.nodes) if self.nodes else set()
- extra_ensemble_nodes = current_ensemble_nodes - requested_nodes
- # TODO: the cluster may have more nodes than what is reported in ensemble:
- # nodes_not_in_ensemble = requested_nodes - current_ensemble_nodes
- # So it's OK to find some missing nodes, but not very deterministic.
- # eg some kind of backup nodes could be in nodes_not_in_ensemble.
- if extra_ensemble_nodes and self.fail_if_cluster_already_exists_with_larger_ensemble:
- msg = 'Error: found existing cluster with more nodes in ensemble. Cluster: %s, extra nodes: %s' %\
- (getattr(info, 'cluster', 'not found'), extra_ensemble_nodes)
- msg += '. Cluster info: %s' % repr(info)
- self.module.fail_json(msg=msg)
- if extra_ensemble_nodes:
- self.debug.append("Extra ensemble nodes: %s" % extra_ensemble_nodes)
- nodes_not_in_ensemble = requested_nodes - current_ensemble_nodes
- if nodes_not_in_ensemble:
- self.debug.append("Extra requested nodes not in ensemble: %s" % nodes_not_in_ensemble)
- return True
-
- def create_cluster_api(self, options):
- ''' Call send_request directly rather than using the SDK if new fields are present
- The new SDK will support these in version 1.17 (Nov or Feb)
- '''
- extra_options = ['enableSoftwareEncryptionAtRest', 'orderNumber', 'serialNumber']
- if not any((item in options for item in extra_options)):
- # use SDK
- return self.sfe_cluster.create_cluster(**options)
-
- # call directly the API as the SDK is not updated yet
- params = {
- "mvip": options['mvip'],
- "svip": options['svip'],
- "repCount": options['rep_count'],
- "username": options['username'],
- "password": options['password'],
- "nodes": options['nodes'],
- }
- if options['accept_eula'] is not None:
- params["acceptEula"] = options['accept_eula']
- if options['attributes'] is not None:
- params["attributes"] = options['attributes']
- for option in extra_options:
- if options.get(option):
- params[option] = options[option]
-
- # There is no adaptor.
- return self.sfe_cluster.send_request(
- 'CreateCluster',
- netapp_utils.solidfire.CreateClusterResult,
- params,
- since=None
- )
-
- def create_cluster(self):
- """
- Create Cluster
- """
- options = {
- 'mvip': self.management_virtual_ip,
- 'svip': self.storage_virtual_ip,
- 'rep_count': self.replica_count,
- 'accept_eula': self.accept_eula,
- 'nodes': self.nodes,
- 'attributes': self.attributes,
- 'username': self.cluster_admin_username,
- 'password': self.cluster_admin_password
- }
- if self.encryption is not None:
- options['enableSoftwareEncryptionAtRest'] = self.encryption
- if self.order_number is not None:
- options['orderNumber'] = self.order_number
- if self.serial_number is not None:
- options['serialNumber'] = self.serial_number
-
- return_msg = 'created'
- try:
- # does not work as node even though documentation says otherwise
- # running as node, this error is reported: 500 xUnknownAPIMethod method=CreateCluster
- self.create_cluster_api(options)
- except netapp_utils.solidfire.common.ApiServerError as exc:
- # not sure how this can happen, but the cluster may already exists
- if 'xClusterAlreadyCreated' not in str(exc.message):
- self.module.fail_json(msg='Error creating cluster %s' % to_native(exc), exception=traceback.format_exc())
- return_msg = 'already_exists: %s' % str(exc.message)
- except Exception as exc:
- self.module.fail_json(msg='Error creating cluster %s' % to_native(exc), exception=traceback.format_exc())
- return return_msg
-
- def apply(self):
- """
- Check connection and initialize node with cluster ownership
- """
- changed = False
- result_message = None
- exists = self.check_cluster_exists()
- if exists:
- result_message = "cluster already exists"
- else:
- changed = True
- if not self.module.check_mode:
- result_message = self.create_cluster()
- if result_message.startswith('already_exists:'):
- changed = False
- self.module.exit_json(changed=changed, msg=result_message, debug=self.debug)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_cluster = ElementSWCluster()
- na_elementsw_cluster.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_config.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_config.py
deleted file mode 100644
index 94b5c17dc..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_config.py
+++ /dev/null
@@ -1,331 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software Configure cluster
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_cluster_config
-
-short_description: Configure Element SW Cluster
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.8.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Configure Element Software cluster.
-
-options:
- modify_cluster_full_threshold:
- description:
- - The capacity level at which the cluster generates an event
- - Requires a stage3_block_threshold_percent or
- - max_metadata_over_provision_factor or
- - stage2_aware_threshold
- suboptions:
- stage3_block_threshold_percent:
- description:
- - The percentage below the "Error" threshold that triggers a cluster "Warning" alert
- type: int
- max_metadata_over_provision_factor:
- description:
- - The number of times metadata space can be overprovisioned relative to the amount of space available
- type: int
- stage2_aware_threshold:
- description:
- - The number of nodes of capacity remaining in the cluster before the system triggers a notification
- type: int
- type: dict
-
- encryption_at_rest:
- description:
- - enable or disable the Advanced Encryption Standard (AES) 256-bit encryption at rest on the cluster
- choices: ['present', 'absent']
- type: str
-
- set_ntp_info:
- description:
- - configure NTP on cluster node
- - Requires a list of one or more ntp_servers
- suboptions:
- ntp_servers:
- description:
- - list of NTP servers to add to each nodes NTP configuration
- type: list
- elements: str
- broadcastclient:
- type: bool
- default: False
- description:
- - Enables every node in the cluster as a broadcast client
- type: dict
-
- enable_virtual_volumes:
- type: bool
- default: True
- description:
- - Enable the NetApp SolidFire VVols cluster feature
-'''
-
-EXAMPLES = """
-
- - name: Configure cluster
- tags:
- - elementsw_cluster_config
- na_elementsw_cluster_config:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- modify_cluster_full_threshold:
- stage2_aware_threshold: 2
- stage3_block_threshold_percent: 10
- max_metadata_over_provision_factor: 2
- encryption_at_rest: absent
- set_ntp_info:
- broadcastclient: False
- ntp_servers:
- - 1.1.1.1
- - 2.2.2.2
- enable_virtual_volumes: True
-"""
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementSWClusterConfig(object):
- """
- Element Software Configure Element SW Cluster
- """
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
-
- self.argument_spec.update(dict(
- modify_cluster_full_threshold=dict(
- type='dict',
- options=dict(
- stage2_aware_threshold=dict(type='int', default=None),
- stage3_block_threshold_percent=dict(type='int', default=None),
- max_metadata_over_provision_factor=dict(type='int', default=None)
- )
- ),
- encryption_at_rest=dict(type='str', choices=['present', 'absent']),
- set_ntp_info=dict(
- type='dict',
- options=dict(
- broadcastclient=dict(type='bool', default=False),
- ntp_servers=dict(type='list', elements='str')
- )
- ),
- enable_virtual_volumes=dict(type='bool', default=True)
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
-
- 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_ntp_details(self):
- """
- get ntp info
- """
- # Get ntp details
- ntp_details = self.sfe.get_ntp_info()
- return ntp_details
-
- def cmp(self, provided_ntp_servers, existing_ntp_servers):
- # As python3 doesn't have default cmp function, defining manually to provide same fuctionality.
- return (provided_ntp_servers > existing_ntp_servers) - (provided_ntp_servers < existing_ntp_servers)
-
- def get_cluster_details(self):
- """
- get cluster info
- """
- cluster_details = self.sfe.get_cluster_info()
- return cluster_details
-
- def get_vvols_status(self):
- """
- get vvols status
- """
- feature_status = self.sfe.get_feature_status(feature='vvols')
- if feature_status is not None:
- return feature_status.features[0].enabled
- return None
-
- def get_cluster_full_threshold_status(self):
- """
- get cluster full threshold
- """
- cluster_full_threshold_status = self.sfe.get_cluster_full_threshold()
- return cluster_full_threshold_status
-
- def setup_ntp_info(self, servers, broadcastclient=None):
- """
- configure ntp
- """
- # Set ntp servers
- try:
- self.sfe.set_ntp_info(servers, broadcastclient)
- except Exception as exception_object:
- self.module.fail_json(msg='Error configuring ntp %s' % (to_native(exception_object)),
- exception=traceback.format_exc())
-
- def set_encryption_at_rest(self, state=None):
- """
- enable/disable encryption at rest
- """
- try:
- if state == 'present':
- encryption_state = 'enable'
- self.sfe.enable_encryption_at_rest()
- elif state == 'absent':
- encryption_state = 'disable'
- self.sfe.disable_encryption_at_rest()
- except Exception as exception_object:
- self.module.fail_json(msg='Failed to %s rest encryption %s' % (encryption_state,
- to_native(exception_object)),
- exception=traceback.format_exc())
-
- def enable_feature(self, feature):
- """
- enable feature
- """
- try:
- self.sfe.enable_feature(feature=feature)
- except Exception as exception_object:
- self.module.fail_json(msg='Error enabling %s %s' % (feature, to_native(exception_object)),
- exception=traceback.format_exc())
-
- def set_cluster_full_threshold(self, stage2_aware_threshold=None,
- stage3_block_threshold_percent=None,
- max_metadata_over_provision_factor=None):
- """
- modify cluster full threshold
- """
- try:
- self.sfe.modify_cluster_full_threshold(stage2_aware_threshold=stage2_aware_threshold,
- stage3_block_threshold_percent=stage3_block_threshold_percent,
- max_metadata_over_provision_factor=max_metadata_over_provision_factor)
- except Exception as exception_object:
- self.module.fail_json(msg='Failed to modify cluster full threshold %s' % (to_native(exception_object)),
- exception=traceback.format_exc())
-
- def apply(self):
- """
- Cluster configuration
- """
- changed = False
- result_message = None
-
- if self.parameters.get('modify_cluster_full_threshold') is not None:
- # get cluster full threshold
- cluster_full_threshold_details = self.get_cluster_full_threshold_status()
- # maxMetadataOverProvisionFactor
- current_mmopf = cluster_full_threshold_details.max_metadata_over_provision_factor
- # stage3BlockThresholdPercent
- current_s3btp = cluster_full_threshold_details.stage3_block_threshold_percent
- # stage2AwareThreshold
- current_s2at = cluster_full_threshold_details.stage2_aware_threshold
-
- # is cluster full threshold state change required?
- if self.parameters.get("modify_cluster_full_threshold")['max_metadata_over_provision_factor'] is not None and \
- current_mmopf != self.parameters['modify_cluster_full_threshold']['max_metadata_over_provision_factor'] or \
- self.parameters.get("modify_cluster_full_threshold")['stage3_block_threshold_percent'] is not None and \
- current_s3btp != self.parameters['modify_cluster_full_threshold']['stage3_block_threshold_percent'] or \
- self.parameters.get("modify_cluster_full_threshold")['stage2_aware_threshold'] is not None and \
- current_s2at != self.parameters['modify_cluster_full_threshold']['stage2_aware_threshold']:
- changed = True
- self.set_cluster_full_threshold(self.parameters['modify_cluster_full_threshold']['stage2_aware_threshold'],
- self.parameters['modify_cluster_full_threshold']['stage3_block_threshold_percent'],
- self.parameters['modify_cluster_full_threshold']['max_metadata_over_provision_factor'])
-
- if self.parameters.get('encryption_at_rest') is not None:
- # get all cluster info
- cluster_info = self.get_cluster_details()
- # register rest state
- current_encryption_at_rest_state = cluster_info.cluster_info.encryption_at_rest_state
-
- # is encryption state change required?
- if current_encryption_at_rest_state == 'disabled' and self.parameters['encryption_at_rest'] == 'present' or \
- current_encryption_at_rest_state == 'enabled' and self.parameters['encryption_at_rest'] == 'absent':
- changed = True
- self.set_encryption_at_rest(self.parameters['encryption_at_rest'])
-
- if self.parameters.get('set_ntp_info') is not None:
- # get all ntp details
- ntp_details = self.get_ntp_details()
- # register list of ntp servers
- ntp_servers = ntp_details.servers
- # broadcastclient
- broadcast_client = ntp_details.broadcastclient
-
- # has either the broadcastclient or the ntp server list changed?
-
- if self.parameters.get('set_ntp_info')['broadcastclient'] != broadcast_client or \
- self.cmp(self.parameters.get('set_ntp_info')['ntp_servers'], ntp_servers) != 0:
- changed = True
- self.setup_ntp_info(self.parameters.get('set_ntp_info')['ntp_servers'],
- self.parameters.get('set_ntp_info')['broadcastclient'])
-
- if self.parameters.get('enable_virtual_volumes') is not None:
- # check vvols status
- current_vvols_status = self.get_vvols_status()
-
- # has the vvols state changed?
- if current_vvols_status is False and self.parameters.get('enable_virtual_volumes') is True:
- changed = True
- self.enable_feature('vvols')
- elif current_vvols_status is True and self.parameters.get('enable_virtual_volumes') is not True:
- # vvols, once enabled, cannot be disabled
- self.module.fail_json(msg='Error disabling vvols: this feature cannot be undone')
-
- if self.module.check_mode is True:
- result_message = "Check mode, skipping changes"
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_cluster_config = ElementSWClusterConfig()
- na_elementsw_cluster_config.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_pair.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_pair.py
deleted file mode 100644
index af064e214..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_pair.py
+++ /dev/null
@@ -1,206 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_cluster_pair
-
-short_description: NetApp Element Software Manage Cluster Pair
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, delete cluster pair
-
-options:
-
- state:
- description:
- - Whether the specified cluster pair should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- dest_mvip:
- description:
- - Destination IP address of the cluster to be paired.
- required: true
- type: str
-
- dest_username:
- description:
- - Destination username for the cluster to be paired.
- - Optional if this is same as source cluster username.
- type: str
-
- dest_password:
- description:
- - Destination password for the cluster to be paired.
- - Optional if this is same as source cluster password.
- type: str
-
-'''
-
-EXAMPLES = """
- - name: Create cluster pair
- na_elementsw_cluster_pair:
- hostname: "{{ src_hostname }}"
- username: "{{ src_username }}"
- password: "{{ src_password }}"
- state: present
- dest_mvip: "{{ dest_hostname }}"
-
- - name: Delete cluster pair
- na_elementsw_cluster_pair:
- hostname: "{{ src_hostname }}"
- username: "{{ src_username }}"
- password: "{{ src_password }}"
- state: absent
- dest_mvip: "{{ dest_hostname }}"
- dest_username: "{{ dest_username }}"
- dest_password: "{{ dest_password }}"
-
-"""
-
-RETURN = """
-
-"""
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWClusterPair(object):
- """ class to handle cluster pairing operations """
-
- def __init__(self):
- """
- Setup Ansible parameters and ElementSW connection
- """
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, choices=['present', 'absent'],
- default='present'),
- dest_mvip=dict(required=True, type='str'),
- dest_username=dict(required=False, type='str'),
- dest_password=dict(required=False, type='str', no_log=True)
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.elem = netapp_utils.create_sf_connection(module=self.module)
-
- self.elementsw_helper = NaElementSWModule(self.elem)
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
- # get element_sw_connection for destination cluster
- # overwrite existing source host, user and password with destination credentials
- self.module.params['hostname'] = self.parameters['dest_mvip']
- # username and password is same as source,
- # if dest_username and dest_password aren't specified
- if self.parameters.get('dest_username'):
- self.module.params['username'] = self.parameters['dest_username']
- if self.parameters.get('dest_password'):
- self.module.params['password'] = self.parameters['dest_password']
- self.dest_elem = netapp_utils.create_sf_connection(module=self.module)
- self.dest_elementsw_helper = NaElementSWModule(self.dest_elem)
-
- def check_if_already_paired(self, paired_clusters, hostname):
- for pair in paired_clusters.cluster_pairs:
- if pair.mvip == hostname:
- return pair.cluster_pair_id
- return None
-
- def get_src_pair_id(self):
- """
- Check for idempotency
- """
- # src cluster and dest cluster exist
- paired_clusters = self.elem.list_cluster_pairs()
- return self.check_if_already_paired(paired_clusters, self.parameters['dest_mvip'])
-
- def get_dest_pair_id(self):
- """
- Getting destination cluster_pair_id
- """
- paired_clusters = self.dest_elem.list_cluster_pairs()
- return self.check_if_already_paired(paired_clusters, self.parameters['hostname'])
-
- def pair_clusters(self):
- """
- Start cluster pairing on source, and complete on target cluster
- """
- try:
- pair_key = self.elem.start_cluster_pairing()
- self.dest_elem.complete_cluster_pairing(
- cluster_pairing_key=pair_key.cluster_pairing_key)
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error pairing cluster %s and %s"
- % (self.parameters['hostname'],
- self.parameters['dest_mvip']),
- exception=to_native(err))
-
- def unpair_clusters(self, pair_id_source, pair_id_dest):
- """
- Delete cluster pair
- """
- try:
- self.elem.remove_cluster_pair(cluster_pair_id=pair_id_source)
- self.dest_elem.remove_cluster_pair(cluster_pair_id=pair_id_dest)
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error unpairing cluster %s and %s"
- % (self.parameters['hostname'],
- self.parameters['dest_mvip']),
- exception=to_native(err))
-
- def apply(self):
- """
- Call create / delete cluster pair methods
- """
- pair_id_source = self.get_src_pair_id()
- # If already paired, find the cluster_pair_id of destination cluster
- if pair_id_source:
- pair_id_dest = self.get_dest_pair_id()
- # calling helper to determine action
- cd_action = self.na_helper.get_cd_action(pair_id_source, self.parameters)
- if cd_action == "create":
- self.pair_clusters()
- elif cd_action == "delete":
- self.unpair_clusters(pair_id_source, pair_id_dest)
- self.module.exit_json(changed=self.na_helper.changed)
-
-
-def main():
- """ Apply cluster pair actions """
- cluster_obj = ElementSWClusterPair()
- cluster_obj.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_snmp.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_snmp.py
deleted file mode 100644
index 847700197..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_cluster_snmp.py
+++ /dev/null
@@ -1,365 +0,0 @@
-#!/usr/bin/python
-# (c) 2019, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software Configure SNMP
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_cluster_snmp
-
-short_description: Configure Element SW Cluster SNMP
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.8.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Configure Element Software cluster SNMP.
-
-options:
-
- state:
- description:
- - This module enables you to enable SNMP on cluster nodes. When you enable SNMP, \
- the action applies to all nodes in the cluster, and the values that are passed replace, \
- in whole, all values set in any previous call to this module.
- choices: ['present', 'absent']
- default: present
- type: str
-
- snmp_v3_enabled:
- description:
- - Which version of SNMP has to be enabled.
- type: bool
-
- networks:
- description:
- - List of networks and what type of access they have to the SNMP servers running on the cluster nodes.
- - This parameter is required if SNMP v3 is disabled.
- suboptions:
- access:
- description:
- - ro for read-only access.
- - rw for read-write access.
- - rosys for read-only access to a restricted set of system information.
- choices: ['ro', 'rw', 'rosys']
- type: str
- cidr:
- description:
- - A CIDR network mask. This network mask must be an integer greater than or equal to 0, \
- and less than or equal to 32. It must also not be equal to 31.
- type: int
- community:
- description:
- - SNMP community string.
- type: str
- network:
- description:
- - This parameter along with the cidr variable is used to control which network the access and \
- community string apply to.
- - The special value of 'default' is used to specify an entry that applies to all networks.
- - The cidr mask is ignored when network value is either a host name or default.
- type: str
- type: dict
-
- usm_users:
- description:
- - List of users and the type of access they have to the SNMP servers running on the cluster nodes.
- - This parameter is required if SNMP v3 is enabled.
- suboptions:
- access:
- description:
- - rouser for read-only access.
- - rwuser for read-write access.
- - rosys for read-only access to a restricted set of system information.
- choices: ['rouser', 'rwuser', 'rosys']
- type: str
- name:
- description:
- - The name of the user. Must contain at least one character, but no more than 32 characters.
- - Blank spaces are not allowed.
- type: str
- password:
- description:
- - The password of the user. Must be between 8 and 255 characters long (inclusive).
- - Blank spaces are not allowed.
- - Required if 'secLevel' is 'auth' or 'priv.'
- type: str
- passphrase:
- description:
- - The passphrase of the user. Must be between 8 and 255 characters long (inclusive).
- - Blank spaces are not allowed.
- - Required if 'secLevel' is 'priv.'
- type: str
- secLevel:
- description:
- - To define the security level of a user.
- choices: ['noauth', 'auth', 'priv']
- type: str
- type: dict
-
-'''
-
-EXAMPLES = """
-
- - name: configure SnmpNetwork
- tags:
- - elementsw_cluster_snmp
- na_elementsw_cluster_snmp:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- snmp_v3_enabled: True
- usm_users:
- access: rouser
- name: testuser
- password: ChangeMe123
- passphrase: ChangeMe123
- secLevel: auth
- networks:
- access: ro
- cidr: 24
- community: TestNetwork
- network: 192.168.0.1
-
- - name: Disable SnmpNetwork
- tags:
- - elementsw_cluster_snmp
- na_elementsw_cluster_snmp:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
-
-"""
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementSWClusterSnmp(object):
- """
- Element Software Configure Element SW Cluster SnmpNetwork
- """
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
-
- self.argument_spec.update(dict(
- state=dict(type='str', choices=['present', 'absent'], default='present'),
- snmp_v3_enabled=dict(type='bool'),
- networks=dict(
- type='dict',
- options=dict(
- access=dict(type='str', choices=['ro', 'rw', 'rosys']),
- cidr=dict(type='int', default=None),
- community=dict(type='str', default=None),
- network=dict(type='str', default=None)
- )
- ),
- usm_users=dict(
- type='dict',
- options=dict(
- access=dict(type='str', choices=['rouser', 'rwuser', 'rosys']),
- name=dict(type='str', default=None),
- password=dict(type='str', default=None, no_log=True),
- passphrase=dict(type='str', default=None, no_log=True),
- secLevel=dict(type='str', choices=['auth', 'noauth', 'priv'])
- )
- ),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['snmp_v3_enabled']),
- ('snmp_v3_enabled', True, ['usm_users']),
- ('snmp_v3_enabled', False, ['networks'])
- ],
- supports_check_mode=True
- )
-
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
-
- if self.parameters.get('state') == "present":
- if self.parameters.get('usm_users') is not None:
- # Getting the configuration details to configure SNMP Version3
- self.access_usm = self.parameters.get('usm_users')['access']
- self.name = self.parameters.get('usm_users')['name']
- self.password = self.parameters.get('usm_users')['password']
- self.passphrase = self.parameters.get('usm_users')['passphrase']
- self.secLevel = self.parameters.get('usm_users')['secLevel']
- if self.parameters.get('networks') is not None:
- # Getting the configuration details to configure SNMP Version2
- self.access_network = self.parameters.get('networks')['access']
- self.cidr = self.parameters.get('networks')['cidr']
- self.community = self.parameters.get('networks')['community']
- self.network = self.parameters.get('networks')['network']
-
- 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 enable_snmp(self):
- """
- enable snmp feature
- """
- try:
- self.sfe.enable_snmp(snmp_v3_enabled=self.parameters.get('snmp_v3_enabled'))
- except Exception as exception_object:
- self.module.fail_json(msg='Error enabling snmp feature %s' % to_native(exception_object),
- exception=traceback.format_exc())
-
- def disable_snmp(self):
- """
- disable snmp feature
- """
- try:
- self.sfe.disable_snmp()
- except Exception as exception_object:
- self.module.fail_json(msg='Error disabling snmp feature %s' % to_native(exception_object),
- exception=traceback.format_exc())
-
- def configure_snmp(self, actual_networks, actual_usm_users):
- """
- Configure snmp
- """
- try:
- self.sfe.set_snmp_acl(networks=[actual_networks], usm_users=[actual_usm_users])
-
- except Exception as exception_object:
- self.module.fail_json(msg='Error Configuring snmp feature %s' % to_native(exception_object),
- exception=traceback.format_exc())
-
- def apply(self):
- """
- Cluster SNMP configuration
- """
- changed = False
- result_message = None
- update_required = False
- version_change = False
- is_snmp_enabled = self.sfe.get_snmp_state().enabled
-
- if is_snmp_enabled is True:
- # IF SNMP is already enabled
- if self.parameters.get('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.parameters.get('state') == 'present':
- # Checking if SNMP configuration needs to be updated,
- is_snmp_v3_enabled = self.sfe.get_snmp_state().snmp_v3_enabled
-
- if is_snmp_v3_enabled != self.parameters.get('snmp_v3_enabled'):
- # Checking if there any version changes required
- version_change = True
- changed = True
-
- if is_snmp_v3_enabled is True:
- # Checking If snmp configuration for usm_users needs modification
- if len(self.sfe.get_snmp_info().usm_users) == 0:
- # If snmp is getting configured for first time
- update_required = True
- changed = True
- else:
- for usm_user in self.sfe.get_snmp_info().usm_users:
- if usm_user.access != self.access_usm or usm_user.name != self.name or usm_user.password != self.password or \
- usm_user.passphrase != self.passphrase or usm_user.sec_level != self.secLevel:
- update_required = True
- changed = True
- else:
- # Checking If snmp configuration for networks needs modification
- for snmp_network in self.sfe.get_snmp_info().networks:
- if snmp_network.access != self.access_network or snmp_network.cidr != self.cidr or \
- snmp_network.community != self.community or snmp_network.network != self.network:
- update_required = True
- changed = True
-
- else:
- if self.parameters.get('state') == 'present':
- changed = True
-
- result_message = ""
-
- if changed:
- if self.module.check_mode is True:
- result_message = "Check mode, skipping changes"
-
- else:
- if self.parameters.get('state') == "present":
- # IF snmp is not enabled, then enable and configure snmp
- if self.parameters.get('snmp_v3_enabled') is True:
- # IF SNMP is enabled with version 3
- usm_users = {'access': self.access_usm,
- 'name': self.name,
- 'password': self.password,
- 'passphrase': self.passphrase,
- 'secLevel': self.secLevel}
- networks = None
- else:
- # IF SNMP is enabled with version 2
- usm_users = None
- networks = {'access': self.access_network,
- 'cidr': self.cidr,
- 'community': self.community,
- 'network': self.network}
-
- if is_snmp_enabled is False or version_change is True:
- # Enable and configure snmp
- self.enable_snmp()
- self.configure_snmp(networks, usm_users)
- result_message = "SNMP is enabled and configured"
-
- elif update_required is True:
- # If snmp is already enabled, update the configuration if required
- self.configure_snmp(networks, usm_users)
- result_message = "SNMP is configured"
-
- elif is_snmp_enabled is True and self.parameters.get('state') == "absent":
- # If snmp is enabled and state is absent, disable snmp
- self.disable_snmp()
- result_message = "SNMP is disabled"
-
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_cluster_snmp = ElementSWClusterSnmp()
- na_elementsw_cluster_snmp.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_drive.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_drive.py
deleted file mode 100644
index f0fd7e38b..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_drive.py
+++ /dev/null
@@ -1,368 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software Node Drives
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_drive
-
-short_description: NetApp Element Software Manage Node Drives
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
- - Add, Erase or Remove drive for nodes on Element Software Cluster.
-
-options:
- drive_ids:
- description:
- - List of Drive IDs or Serial Names of Node drives.
- - If not specified, add and remove action will be performed on all drives of node_id
- type: list
- elements: str
- aliases: ['drive_id']
-
- state:
- description:
- - Element SW Storage Drive operation state.
- - present - To add drive of node to participate in cluster data storage.
- - absent - To remove the drive from being part of active cluster.
- - clean - Clean-up any residual data persistent on a *removed* drive in a secured method.
- choices: ['present', 'absent', 'clean']
- default: 'present'
- type: str
-
- node_ids:
- description:
- - List of IDs or Names of cluster nodes.
- - If node_ids and drive_ids are not specified, all available drives in the cluster are added if state is present.
- - If node_ids and drive_ids are not specified, all active drives in the cluster are removed if state is absent.
- required: false
- type: list
- elements: str
- aliases: ['node_id']
-
- force_during_upgrade:
- description:
- - Flag to force drive operation during upgrade.
- - Not supported with latest version of SolidFire SDK (1.7.0.152)
- type: 'bool'
-
- force_during_bin_sync:
- description:
- - Flag to force during a bin sync operation.
- - Not supported with latest version of SolidFire SDK (1.7.0.152)
- type: 'bool'
-'''
-
-EXAMPLES = """
- - name: Add drive with status available to cluster
- tags:
- - elementsw_add_drive
- na_elementsw_drive:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- drive_ids: scsi-SATA_SAMSUNG_MZ7LM48S2UJNX0J3221807
- force_during_upgrade: false
- force_during_bin_sync: false
- node_ids: sf4805-meg-03
-
- - name: Remove active drive from cluster
- tags:
- - elementsw_remove_drive
- na_elementsw_drive:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- force_during_upgrade: false
- drive_ids: scsi-SATA_SAMSUNG_MZ7LM48S2UJNX0J321208
-
- - name: Secure Erase drive
- tags:
- - elemensw_clean_drive
- na_elementsw_drive:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: clean
- drive_ids: scsi-SATA_SAMSUNG_MZ7LM48S2UJNX0J432109
- node_ids: sf4805-meg-03
-
- - name: Add all the drives of all nodes to cluster
- tags:
- - elementsw_add_node
- na_elementsw_drive:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- force_during_upgrade: false
- force_during_bin_sync: false
-
-"""
-
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementSWDrive(object):
- """
- Element Software Storage Drive operations
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, choices=['present', 'absent', 'clean'], default='present'),
- drive_ids=dict(required=False, type='list', elements='str', aliases=['drive_id']),
- node_ids=dict(required=False, type='list', elements='str', aliases=['node_id']),
- force_during_upgrade=dict(required=False, type='bool'),
- force_during_bin_sync=dict(required=False, type='bool')
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- input_params = self.module.params
-
- self.state = input_params['state']
- self.drive_ids = input_params['drive_ids']
- self.node_ids = input_params['node_ids']
- self.force_during_upgrade = input_params['force_during_upgrade']
- self.force_during_bin_sync = input_params['force_during_bin_sync']
- self.list_nodes = None
- self.debug = list()
-
- if HAS_SF_SDK is False:
- self.module.fail_json(
- msg="Unable to import the SolidFire Python SDK")
- else:
- # increase timeout, as removing a disk takes some time
- self.sfe = netapp_utils.create_sf_connection(module=self.module, timeout=120)
-
- def get_node_id(self, node_id):
- """
- Get Node ID
- :description: Find and retrieve node_id from the active cluster
-
- :return: node_id (None if not found)
- :rtype: node_id
- """
- if self.list_nodes is None:
- self.list_nodes = self.sfe.list_active_nodes()
- for current_node in self.list_nodes.nodes:
- if node_id == str(current_node.node_id):
- return current_node.node_id
- elif node_id == current_node.name:
- return current_node.node_id
- self.module.fail_json(msg='unable to find node for node_id=%s' % node_id)
-
- def get_drives_listby_status(self, node_num_ids):
- """
- Capture list of drives based on status for a given node_id
- :description: Capture list of active, failed and available drives from a given node_id
-
- :return: None
- """
- self.active_drives = dict()
- self.available_drives = dict()
- self.other_drives = dict()
- self.all_drives = self.sfe.list_drives()
-
- for drive in self.all_drives.drives:
- # get all drives if no node is given, or match the node_ids
- if node_num_ids is None or drive.node_id in node_num_ids:
- if drive.status in ['active', 'failed']:
- self.active_drives[drive.serial] = drive.drive_id
- elif drive.status == "available":
- self.available_drives[drive.serial] = drive.drive_id
- else:
- self.other_drives[drive.serial] = (drive.drive_id, drive.status)
-
- self.debug.append('available: %s' % self.available_drives)
- self.debug.append('active: %s' % self.active_drives)
- self.debug.append('other: %s' % self.other_drives)
-
- def get_drive_id(self, drive_id, node_num_ids):
- """
- Get Drive ID
- :description: Find and retrieve drive_id from the active cluster
- Assumes self.all_drives is already populated
-
- :return: node_id (None if not found)
- :rtype: node_id
- """
- for drive in self.all_drives.drives:
- if drive_id == str(drive.drive_id):
- break
- if drive_id == drive.serial:
- break
- else:
- self.module.fail_json(msg='unable to find drive for drive_id=%s. Debug=%s' % (drive_id, self.debug))
- if node_num_ids and drive.node_id not in node_num_ids:
- self.module.fail_json(msg='drive for drive_id=%s belongs to another node, with node_id=%d. Debug=%s' % (drive_id, drive.node_id, self.debug))
- return drive.drive_id, drive.status
-
- def get_active_drives(self, drives):
- """
- return a list of active drives
- if drives is specified, only [] or a subset of disks in drives are returned
- else all available drives for this node or cluster are returned
- """
- if drives is None:
- return list(self.active_drives.values())
- return [drive_id for drive_id, status in drives if status in ['active', 'failed']]
-
- def get_available_drives(self, drives, action):
- """
- return a list of available drives (not active)
- if drives is specified, only [] or a subset of disks in drives are returned
- else all available drives for this node or cluster are returned
- """
- if drives is None:
- return list(self.available_drives.values())
- action_list = list()
- for drive_id, drive_status in drives:
- if drive_status == 'available':
- action_list.append(drive_id)
- elif drive_status in ['active', 'failed']:
- # already added
- pass
- elif drive_status == 'erasing' and action == 'erase':
- # already erasing
- pass
- elif drive_status == 'removing':
- self.module.fail_json(msg='Error - cannot %s drive while it is being removed. Debug: %s' % (action, self.debug))
- elif drive_status == 'erasing' and action == 'add':
- self.module.fail_json(msg='Error - cannot %s drive while it is being erased. Debug: %s' % (action, self.debug))
- else:
- self.module.fail_json(msg='Error - cannot %s drive while it is in %s state. Debug: %s' % (action, drive_status, self.debug))
- return action_list
-
- def add_drive(self, drives=None):
- """
- Add Drive available for Cluster storage expansion
- """
- kwargs = dict()
- if self.force_during_upgrade is not None:
- kwargs['force_during_upgrade'] = self.force_during_upgrade
- if self.force_during_bin_sync is not None:
- kwargs['force_during_bin_sync'] = self.force_during_bin_sync
- try:
- self.sfe.add_drives(drives, **kwargs)
- except Exception as exception_object:
- self.module.fail_json(msg='Error adding drive%s: %s: %s' %
- ('s' if len(drives) > 1 else '',
- str(drives),
- to_native(exception_object)),
- exception=traceback.format_exc())
-
- def remove_drive(self, drives=None):
- """
- Remove Drive active in Cluster
- """
- kwargs = dict()
- if self.force_during_upgrade is not None:
- kwargs['force_during_upgrade'] = self.force_during_upgrade
- try:
- self.sfe.remove_drives(drives, **kwargs)
- except Exception as exception_object:
- self.module.fail_json(msg='Error removing drive%s: %s: %s' %
- ('s' if len(drives) > 1 else '',
- str(drives),
- to_native(exception_object)),
- exception=traceback.format_exc())
-
- def secure_erase(self, drives=None):
- """
- Secure Erase any residual data existing on a drive
- """
- try:
- self.sfe.secure_erase_drives(drives)
- except Exception as exception_object:
- self.module.fail_json(msg='Error cleaning data from drive%s: %s: %s' %
- ('s' if len(drives) > 1 else '',
- str(drives),
- to_native(exception_object)),
- exception=traceback.format_exc())
-
- def apply(self):
- """
- Check, process and initiate Drive operation
- """
- changed = False
-
- action_list = []
- node_num_ids = None
- drives = None
- if self.node_ids:
- node_num_ids = [self.get_node_id(node_id) for node_id in self.node_ids]
-
- self.get_drives_listby_status(node_num_ids)
- if self.drive_ids:
- drives = [self.get_drive_id(drive_id, node_num_ids) for drive_id in self.drive_ids]
-
- if self.state == "present":
- action_list = self.get_available_drives(drives, 'add')
- elif self.state == "absent":
- action_list = self.get_active_drives(drives)
- elif self.state == "clean":
- action_list = self.get_available_drives(drives, 'erase')
-
- if len(action_list) > 0:
- changed = True
- if not self.module.check_mode and changed:
- if self.state == "present":
- self.add_drive(action_list)
- elif self.state == "absent":
- self.remove_drive(action_list)
- elif self.state == "clean":
- self.secure_erase(action_list)
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- """
- Main function
- """
-
- na_elementsw_drive = ElementSWDrive()
- na_elementsw_drive.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_info.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_info.py
deleted file mode 100644
index fde928784..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_info.py
+++ /dev/null
@@ -1,272 +0,0 @@
-#!/usr/bin/python
-# (c) 2020, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software Info
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_info
-short_description: NetApp Element Software Info
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 20.10.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
- - Collect cluster and node information.
- - Use a MVIP as hostname for cluster and node scope.
- - Use a MIP as hostname for node scope.
- - When using MIPs, cluster APIs are expected to fail with 'xUnknownAPIMethod method=ListAccounts'
-
-options:
- gather_subsets:
- description:
- - list of subsets to gather from target cluster or node
- - supported values
- - node_config, cluster_accounts, cluster_nodes, cluster_drives.
- - additional values
- - all - for all subsets,
- - all_clusters - all subsets at cluster scope,
- - all_nodes - all subsets at node scope
- type: list
- elements: str
- default: ['all']
- aliases: ['gather_subset']
-
- filter:
- description:
- - When a list of records is returned, this can be used to limit the records to be returned.
- - If more than one key is used, all keys must match.
- type: dict
-
- fail_on_error:
- description:
- - by default, errors are not fatal when collecting a subset. The subset will show on error in the info output.
- - if set to True, the module fails on the first error.
- type: bool
- default: false
-
- fail_on_key_not_found:
- description:
- - force an error when filter is used and a key is not present in records.
- type: bool
- default: true
-
- fail_on_record_not_found:
- description:
- - force an error when filter is used and no record is matched.
- type: bool
- default: false
-'''
-
-EXAMPLES = """
-
- - name: get all available subsets
- na_elementsw_info:
- hostname: "{{ elementsw_mvip }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- gather_subsets: all
- register: result
-
- - name: collect data for elementsw accounts using a filter
- na_elementsw_info:
- hostname: "{{ elementsw_mvip }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- gather_subsets: 'cluster_accounts'
- filter:
- username: "{{ username_to_find }}"
- register: result
-"""
-
-RETURN = """
-
-info:
- description:
- - a dictionary of collected subsets
- - each subset if in JSON format
- returned: success
- type: dict
-
-debug:
- description:
- - a list of detailed error messages if some subsets cannot be collected
- returned: success
- type: list
-
-"""
-from ansible.module_utils.basic import AnsibleModule
-
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementSWInfo(object):
- '''
- Element Software Initialize node with ownership for cluster formation
- '''
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- gather_subsets=dict(type='list', elements='str', aliases=['gather_subset'], default='all'),
- filter=dict(type='dict'),
- fail_on_error=dict(type='bool', default=False),
- fail_on_key_not_found=dict(type='bool', default=True),
- fail_on_record_not_found=dict(type='bool', default=False),
- ))
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
- self.debug = list()
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
-
- # 442 for node APIs, 443 (default) for cluster APIs
- for role, port in [('node', 442), ('cluster', 443)]:
- try:
- conn = netapp_utils.create_sf_connection(module=self.module, raise_on_connection_error=True, port=port)
- if role == 'node':
- self.sfe_node = conn
- else:
- self.sfe_cluster = conn
- except netapp_utils.solidfire.common.ApiConnectionError as exc:
- if str(exc) == "Bad Credentials":
- msg = ' Make sure to use valid %s credentials for username and password.' % 'node' if port == 442 else 'cluster'
- msg += '%s reported: %s' % ('Node' if port == 442 else 'Cluster', repr(exc))
- else:
- msg = 'Failed to create connection for %s:%d - %s' % (self.parameters['hostname'], port, repr(exc))
- self.module.fail_json(msg=msg)
- except Exception as exc:
- self.module.fail_json(msg='Failed to connect for %s:%d - %s' % (self.parameters['hostname'], port, repr(exc)))
-
- # TODO: add new node methods here
- self.node_methods = dict(
- node_config=self.sfe_node.get_config,
- )
- # TODO: add new cluster methods here
- self.cluster_methods = dict(
- cluster_accounts=self.sfe_cluster.list_accounts,
- cluster_drives=self.sfe_cluster.list_drives,
- cluster_nodes=self.sfe_cluster.list_all_nodes
- )
- self.methods = dict(self.node_methods)
- self.methods.update(self.cluster_methods)
-
- # add telemetry attributes - does not matter if we are using cluster or node here
- # TODO: most if not all get and list APIs do not have an attributes parameter
-
- def get_info(self, name):
- '''
- Get Element Info
- run a cluster or node list method
- return output as json
- '''
- info = None
- if name not in self.methods:
- msg = 'Error: unknown subset %s.' % name
- msg += ' Known_subsets: %s' % ', '.join(self.methods.keys())
- self.module.fail_json(msg=msg, debug=self.debug)
- try:
- info = self.methods[name]()
- return info.to_json()
- except netapp_utils.solidfire.common.ApiServerError as exc:
- # the new SDK rearranged the fields in a different order
- if all(x in str(exc) for x in ('err_json', '500', 'xUnknownAPIMethod', 'method=')):
- info = 'Error (API not in scope?)'
- else:
- info = 'Error'
- msg = '%s for subset: %s: %s' % (info, name, repr(exc))
- if self.parameters['fail_on_error']:
- self.module.fail_json(msg=msg)
- self.debug.append(msg)
- return info
-
- def filter_list_of_dict_by_key(self, records, key, value):
- matched = list()
- for record in records:
- if key in record and record[key] == value:
- matched.append(record)
- if key not in record and self.parameters['fail_on_key_not_found']:
- msg = 'Error: key %s not found in %s' % (key, repr(record))
- self.module.fail_json(msg=msg)
- return matched
-
- def filter_records(self, records, filter_dict):
-
- if isinstance(records, dict):
- if len(records) == 1:
- key, value = list(records.items())[0]
- return dict({key: self.filter_records(value, filter_dict)})
- if not isinstance(records, list):
- return records
- matched = records
- for key, value in filter_dict.items():
- matched = self.filter_list_of_dict_by_key(matched, key, value)
- if self.parameters['fail_on_record_not_found'] and len(matched) == 0:
- msg = 'Error: no match for %s out of %d records' % (repr(self.parameters['filter']), len(records))
- self.debug.append('Unmatched records: %s' % repr(records))
- self.module.fail_json(msg=msg, debug=self.debug)
- return matched
-
- def get_and_filter_info(self, name):
- '''
- Get data
- If filter is present, only return the records that are matched
- return output as json
- '''
- records = self.get_info(name)
- if self.parameters.get('filter') is None:
- return records
- matched = self.filter_records(records, self.parameters.get('filter'))
- return matched
-
- def apply(self):
- '''
- Check connection and initialize node with cluster ownership
- '''
- changed = False
- info = dict()
- my_subsets = ('all', 'all_clusters', 'all_nodes')
- if any(x in self.parameters['gather_subsets'] for x in my_subsets) and len(self.parameters['gather_subsets']) > 1:
- msg = 'When any of %s is used, no other subset is allowed' % repr(my_subsets)
- self.module.fail_json(msg=msg)
- if 'all' in self.parameters['gather_subsets']:
- self.parameters['gather_subsets'] = self.methods.keys()
- if 'all_clusters' in self.parameters['gather_subsets']:
- self.parameters['gather_subsets'] = self.cluster_methods.keys()
- if 'all_nodes' in self.parameters['gather_subsets']:
- self.parameters['gather_subsets'] = self.node_methods.keys()
- for name in self.parameters['gather_subsets']:
- info[name] = self.get_and_filter_info(name)
- self.module.exit_json(changed=changed, info=info, debug=self.debug)
-
-
-def main():
- '''
- Main function
- '''
- na_elementsw_cluster = ElementSWInfo()
- na_elementsw_cluster.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_initiators.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_initiators.py
deleted file mode 100644
index 9bef345b4..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_initiators.py
+++ /dev/null
@@ -1,343 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software manage initiators
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'community'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_initiators
-
-short_description: Manage Element SW initiators
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.8.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Manage Element Software initiators that allow external clients access to volumes.
-
-options:
- initiators:
- description: A list of objects containing characteristics of each initiator.
- suboptions:
- name:
- description: The name of the initiator.
- type: str
- required: true
-
- alias:
- description: The friendly name assigned to this initiator.
- type: str
-
- initiator_id:
- description: The numeric ID of the initiator.
- type: int
-
- volume_access_group_id:
- description: volumeAccessGroupID to which this initiator belongs.
- type: int
-
- attributes:
- description: A set of JSON attributes to assign to this initiator.
- type: dict
- type: list
- elements: dict
-
- state:
- description:
- - Whether the specified initiator should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-'''
-
-EXAMPLES = """
-
- - name: Manage initiators
- tags:
- - na_elementsw_initiators
- na_elementsw_initiators:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- initiators:
- - name: a
- alias: a1
- initiator_id: 1
- volume_access_group_id: 1
- attributes: {"key": "value"}
- - name: b
- alias: b2
- initiator_id: 2
- volume_access_group_id: 2
- state: present
-"""
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-if HAS_SF_SDK:
- from solidfire.models import ModifyInitiator
-
-
-class ElementSWInitiators(object):
- """
- Element Software Manage Element SW initiators
- """
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
-
- self.argument_spec.update(dict(
- initiators=dict(
- type='list',
- elements='dict',
- options=dict(
- name=dict(type='str', required=True),
- alias=dict(type='str', default=None),
- initiator_id=dict(type='int', default=None),
- volume_access_group_id=dict(type='int', default=None),
- attributes=dict(type='dict', default=None),
- )
- ),
- state=dict(choices=['present', 'absent'], default='present'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
- self.debug = list()
-
- 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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # iterate over each user-provided initiator
- for initiator in self.parameters.get('initiators'):
- # add telemetry attributes
- if 'attributes' in initiator and initiator['attributes']:
- initiator['attributes'].update(self.elementsw_helper.set_element_attributes(source='na_elementsw_initiators'))
- else:
- initiator['attributes'] = self.elementsw_helper.set_element_attributes(source='na_elementsw_initiators')
-
- def compare_initiators(self, user_initiator, existing_initiator):
- """
- compare user input initiator with existing dict
- :return: True if matched, False otherwise
- """
- if user_initiator is None or existing_initiator is None:
- return False
- changed = False
- for param in user_initiator:
- # lookup initiator_name instead of name
- if param == 'name':
- if user_initiator['name'] == existing_initiator['initiator_name']:
- pass
- elif param == 'initiator_id':
- # can't change the key
- pass
- elif user_initiator[param] == existing_initiator[param]:
- pass
- else:
- self.debug.append('Initiator: %s. Changed: %s from: %s to %s' %
- (user_initiator['name'], param, str(existing_initiator[param]), str(user_initiator[param])))
- changed = True
- return changed
-
- def initiator_to_dict(self, initiator_obj):
- """
- converts initiator class object to dict
- :return: reconstructed initiator dict
- """
- known_params = ['initiator_name',
- 'alias',
- 'initiator_id',
- 'volume_access_groups',
- 'volume_access_group_id',
- 'attributes']
- initiator_dict = {}
-
- # missing parameter cause error
- # so assign defaults
- for param in known_params:
- initiator_dict[param] = getattr(initiator_obj, param, None)
- if initiator_dict['volume_access_groups'] is not None:
- if len(initiator_dict['volume_access_groups']) == 1:
- initiator_dict['volume_access_group_id'] = initiator_dict['volume_access_groups'][0]
- elif len(initiator_dict['volume_access_groups']) > 1:
- self.module.fail_json(msg="Only 1 access group is supported, found: %s" % repr(initiator_obj))
- del initiator_dict['volume_access_groups']
- return initiator_dict
-
- def find_initiator(self, id=None, name=None):
- """
- find a specific initiator
- :return: initiator dict
- """
- initiator_details = None
- if self.all_existing_initiators is None:
- return initiator_details
- for initiator in self.all_existing_initiators:
- # if name is provided or
- # if id is provided
- if name is not None:
- if initiator.initiator_name == name:
- initiator_details = self.initiator_to_dict(initiator)
- elif id is not None:
- if initiator.initiator_id == id:
- initiator_details = self.initiator_to_dict(initiator)
- else:
- # if neither id nor name provided
- # return everything
- initiator_details = self.all_existing_initiators
- return initiator_details
-
- @staticmethod
- def rename_key(obj, old_name, new_name):
- obj[new_name] = obj.pop(old_name)
-
- def create_initiator(self, initiator):
- """
- create initiator
- """
- # SF SDK is using camelCase for this one
- self.rename_key(initiator, 'volume_access_group_id', 'volumeAccessGroupID')
- # create_initiators needs an array
- initiator_list = [initiator]
- try:
- self.sfe.create_initiators(initiator_list)
- except Exception as exception_object:
- self.module.fail_json(msg='Error creating initiator %s' % (to_native(exception_object)),
- exception=traceback.format_exc())
-
- def delete_initiator(self, initiator):
- """
- delete initiator
- """
- # delete_initiators needs an array
- initiator_id_array = [initiator]
- try:
- self.sfe.delete_initiators(initiator_id_array)
- except Exception as exception_object:
- self.module.fail_json(msg='Error deleting initiator %s' % (to_native(exception_object)),
- exception=traceback.format_exc())
-
- def modify_initiator(self, initiator, existing_initiator):
- """
- modify initiator
- """
- # create the new initiator dict
- # by merging old and new values
- merged_initiator = existing_initiator.copy()
- # can't change the key
- del initiator['initiator_id']
- merged_initiator.update(initiator)
-
- # we MUST create an object before sending
- # the new initiator to modify_initiator
- initiator_object = ModifyInitiator(initiator_id=merged_initiator['initiator_id'],
- alias=merged_initiator['alias'],
- volume_access_group_id=merged_initiator['volume_access_group_id'],
- attributes=merged_initiator['attributes'])
- initiator_list = [initiator_object]
- try:
- self.sfe.modify_initiators(initiators=initiator_list)
- except Exception as exception_object:
- self.module.fail_json(msg='Error modifying initiator: %s' % (to_native(exception_object)),
- exception=traceback.format_exc())
-
- def apply(self):
- """
- configure initiators
- """
- changed = False
- result_message = None
-
- # get all user provided initiators
- input_initiators = self.parameters.get('initiators')
-
- # get all initiators
- # store in a cache variable
- self.all_existing_initiators = self.sfe.list_initiators().initiators
-
- # iterate over each user-provided initiator
- for in_initiator in input_initiators:
- if self.parameters.get('state') == 'present':
- # check if initiator_id is provided and exists
- if 'initiator_id' in in_initiator and in_initiator['initiator_id'] is not None and \
- self.find_initiator(id=in_initiator['initiator_id']) is not None:
- if self.compare_initiators(in_initiator, self.find_initiator(id=in_initiator['initiator_id'])):
- changed = True
- result_message = 'modifying initiator(s)'
- self.modify_initiator(in_initiator, self.find_initiator(id=in_initiator['initiator_id']))
- # otherwise check if name is provided and exists
- elif 'name' in in_initiator and in_initiator['name'] is not None and self.find_initiator(name=in_initiator['name']) is not None:
- if self.compare_initiators(in_initiator, self.find_initiator(name=in_initiator['name'])):
- changed = True
- result_message = 'modifying initiator(s)'
- self.modify_initiator(in_initiator, self.find_initiator(name=in_initiator['name']))
- # this is a create op if initiator doesn't exist
- else:
- changed = True
- result_message = 'creating initiator(s)'
- self.create_initiator(in_initiator)
- elif self.parameters.get('state') == 'absent':
- # delete_initiators only processes ids
- # so pass ids of initiators to method
- if 'name' in in_initiator and in_initiator['name'] is not None and \
- self.find_initiator(name=in_initiator['name']) is not None:
- changed = True
- result_message = 'deleting initiator(s)'
- self.delete_initiator(self.find_initiator(name=in_initiator['name'])['initiator_id'])
- elif 'initiator_id' in in_initiator and in_initiator['initiator_id'] is not None and \
- self.find_initiator(id=in_initiator['initiator_id']) is not None:
- changed = True
- result_message = 'deleting initiator(s)'
- self.delete_initiator(in_initiator['initiator_id'])
- if self.module.check_mode is True:
- result_message = "Check mode, skipping changes"
- if self.debug:
- result_message += ". %s" % self.debug
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_initiators = ElementSWInitiators()
- na_elementsw_initiators.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_ldap.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_ldap.py
deleted file mode 100644
index a71ddf564..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_ldap.py
+++ /dev/null
@@ -1,254 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2017, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_ldap
-
-short_description: NetApp Element Software Manage ldap admin users
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Enable, disable ldap, and add ldap users
-
-options:
-
- state:
- description:
- - Whether the specified volume should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- authType:
- description:
- - Identifies which user authentication method to use.
- choices: ['DirectBind', 'SearchAndBind']
- type: str
-
- groupSearchBaseDn:
- description:
- - The base DN of the tree to start the group search (will do a subtree search from here)
- type: str
-
- groupSearchType:
- description:
- - Controls the default group search filter used
- choices: ['NoGroup', 'ActiveDirectory', 'MemberDN']
- type: str
-
- serverURIs:
- description:
- - A comma-separated list of LDAP server URIs
- type: str
-
- userSearchBaseDN:
- description:
- - The base DN of the tree to start the search (will do a subtree search from here)
- type: str
-
- searchBindDN:
- description:
- - A dully qualified DN to log in with to perform an LDAp search for the user (needs read access to the LDAP directory).
- type: str
-
- searchBindPassword:
- description:
- - The password for the searchBindDN account used for searching
- type: str
-
- userSearchFilter:
- description:
- - the LDAP Filter to use
- type: str
-
- userDNTemplate:
- description:
- - A string that is used form a fully qualified user DN.
- type: str
-
- groupSearchCustomFilter:
- description:
- - For use with the CustomFilter Search type
- type: str
-'''
-
-EXAMPLES = """
- - name: disable ldap authentication
- na_elementsw_ldap:
- state: absent
- username: "{{ admin username }}"
- password: "{{ admin password }}"
- hostname: "{{ hostname }}"
-
- - name: Enable ldap authentication
- na_elementsw_ldap:
- state: present
- username: "{{ admin username }}"
- password: "{{ admin password }}"
- hostname: "{{ hostname }}"
- authType: DirectBind
- serverURIs: ldap://svmdurlabesx01spd_ldapclnt
- groupSearchType: MemberDN
- userDNTemplate: uid=%USERNAME%,cn=users,cn=accounts,dc=corp,dc="{{ company name }}",dc=com
-
-
-"""
-
-RETURN = """
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except Exception:
- HAS_SF_SDK = False
-
-
-class NetappElementLdap(object):
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(
- state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- authType=dict(type='str', choices=['DirectBind', 'SearchAndBind']),
- groupSearchBaseDn=dict(type='str'),
- groupSearchType=dict(type='str', choices=['NoGroup', 'ActiveDirectory', 'MemberDN']),
- serverURIs=dict(type='str'),
- userSearchBaseDN=dict(type='str'),
- searchBindDN=dict(type='str'),
- searchBindPassword=dict(type='str', no_log=True),
- userSearchFilter=dict(type='str'),
- userDNTemplate=dict(type='str'),
- groupSearchCustomFilter=dict(type='str'),
- )
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True,
- )
-
- param = self.module.params
-
- # set up state variables
- self.state = param['state']
- self.authType = param['authType']
- self.groupSearchBaseDn = param['groupSearchBaseDn']
- self.groupSearchType = param['groupSearchType']
- self.serverURIs = param['serverURIs']
- if self.serverURIs is not None:
- self.serverURIs = self.serverURIs.split(',')
- self.userSearchBaseDN = param['userSearchBaseDN']
- self.searchBindDN = param['searchBindDN']
- self.searchBindPassword = param['searchBindPassword']
- self.userSearchFilter = param['userSearchFilter']
- self.userDNTemplate = param['userDNTemplate']
- self.groupSearchCustomFilter = param['groupSearchCustomFilter']
-
- 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_ldap_configuration(self):
- """
- Return ldap configuration if found
-
- :return: Details about the ldap configuration. None if not found.
- :rtype: solidfire.models.GetLdapConfigurationResult
- """
- ldap_config = self.sfe.get_ldap_configuration()
- return ldap_config
-
- def enable_ldap(self):
- """
- Enable LDAP
- :return: nothing
- """
- try:
- self.sfe.enable_ldap_authentication(self.serverURIs, auth_type=self.authType,
- group_search_base_dn=self.groupSearchBaseDn,
- group_search_type=self.groupSearchType,
- group_search_custom_filter=self.groupSearchCustomFilter,
- search_bind_dn=self.searchBindDN,
- search_bind_password=self.searchBindPassword,
- user_search_base_dn=self.userSearchBaseDN,
- user_search_filter=self.userSearchFilter,
- user_dntemplate=self.userDNTemplate)
- except solidfire.common.ApiServerError as error:
- self.module.fail_json(msg='Error enabling LDAP: %s' % (to_native(error)),
- exception=traceback.format_exc())
-
- def check_config(self, ldap_config):
- """
- Check to see if the ldap config has been modified.
- :param ldap_config: The LDAP configuration
- :return: False if the config is the same as the playbook, True if it is not
- """
- if self.authType != ldap_config.ldap_configuration.auth_type:
- return True
- if self.serverURIs != ldap_config.ldap_configuration.server_uris:
- return True
- if self.groupSearchBaseDn != ldap_config.ldap_configuration.group_search_base_dn:
- return True
- if self.groupSearchType != ldap_config.ldap_configuration.group_search_type:
- return True
- if self.groupSearchCustomFilter != ldap_config.ldap_configuration.group_search_custom_filter:
- return True
- if self.searchBindDN != ldap_config.ldap_configuration.search_bind_dn:
- return True
- if self.searchBindPassword != ldap_config.ldap_configuration.search_bind_password:
- return True
- if self.userSearchBaseDN != ldap_config.ldap_configuration.user_search_base_dn:
- return True
- if self.userSearchFilter != ldap_config.ldap_configuration.user_search_filter:
- return True
- if self.userDNTemplate != ldap_config.ldap_configuration.user_dntemplate:
- return True
- return False
-
- def apply(self):
- changed = False
- ldap_config = self.get_ldap_configuration()
- if self.state == 'absent':
- if ldap_config and ldap_config.ldap_configuration.enabled:
- changed = True
- if self.state == 'present' and self.check_config(ldap_config):
- changed = True
- if changed:
- if self.module.check_mode:
- pass
- else:
- if self.state == 'present':
- self.enable_ldap()
- elif self.state == 'absent':
- self.sfe.disable_ldap_authentication()
-
- self.module.exit_json(changed=changed)
-
-
-def main():
- v = NetappElementLdap()
- v.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_network_interfaces.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_network_interfaces.py
deleted file mode 100644
index a9151a620..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_network_interfaces.py
+++ /dev/null
@@ -1,423 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software Node Network Interfaces - Bond 1G and 10G configuration
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_network_interfaces
-
-short_description: NetApp Element Software Configure Node Network Interfaces
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
- - Configure Element SW Node Network Interfaces for Bond 1G and 10G IP addresses.
- - This module does not create interfaces, it expects the interfaces to already exists and can only modify them.
- - This module cannot set or modify the method (Loopback, manual, dhcp, static).
- - This module is not idempotent and does not support check_mode.
-
-options:
- method:
- description:
- - deprecated, this option would trigger a 'updated failed' error
- type: str
-
- ip_address_1g:
- description:
- - deprecated, use bond_1g option.
- type: str
-
- ip_address_10g:
- description:
- - deprecated, use bond_10g option.
- type: str
-
- subnet_1g:
- description:
- - deprecated, use bond_1g option.
- type: str
-
- subnet_10g:
- description:
- - deprecated, use bond_10g option.
- type: str
-
- gateway_address_1g:
- description:
- - deprecated, use bond_1g option.
- type: str
-
- gateway_address_10g:
- description:
- - deprecated, use bond_10g option.
- type: str
-
- mtu_1g:
- description:
- - deprecated, use bond_1g option.
- type: str
-
- mtu_10g:
- description:
- - deprecated, use bond_10g option.
- type: str
-
- dns_nameservers:
- description:
- - deprecated, use bond_1g and bond_10g options.
- type: list
- elements: str
-
- dns_search_domains:
- description:
- - deprecated, use bond_1g and bond_10g options.
- type: list
- elements: str
-
- bond_mode_1g:
- description:
- - deprecated, use bond_1g option.
- type: str
-
- bond_mode_10g:
- description:
- - deprecated, use bond_10g option.
- type: str
-
- lacp_1g:
- description:
- - deprecated, use bond_1g option.
- type: str
-
- lacp_10g:
- description:
- - deprecated, use bond_10g option.
- type: str
-
- virtual_network_tag:
- description:
- - deprecated, use bond_1g and bond_10g options.
- type: str
-
- bond_1g:
- description:
- - settings for the Bond1G interface.
- type: dict
- suboptions:
- address:
- description:
- - IP address for the interface.
- type: str
- netmask:
- description:
- - subnet mask for the interface.
- type: str
- gateway:
- description:
- - IP router network address to send packets out of the local network.
- type: str
- mtu:
- description:
- - The largest packet size (in bytes) that the interface can transmit..
- - Must be greater than or equal to 1500 bytes.
- type: str
- dns_nameservers:
- description:
- - List of addresses for domain name servers.
- type: list
- elements: str
- dns_search:
- description:
- - List of DNS search domains.
- type: list
- elements: str
- bond_mode:
- description:
- - Bonding mode.
- choices: ['ActivePassive', 'ALB', 'LACP']
- type: str
- bond_lacp_rate:
- description:
- - Link Aggregation Control Protocol - useful only if LACP is selected as the Bond Mode.
- - Slow - Packets are transmitted at 30 second intervals.
- - Fast - Packets are transmitted in 1 second intervals.
- choices: ['Fast', 'Slow']
- type: str
- virtual_network_tag:
- description:
- - The virtual network identifier of the interface (VLAN tag).
- type: str
-
- bond_10g:
- description:
- - settings for the Bond10G interface.
- type: dict
- suboptions:
- address:
- description:
- - IP address for the interface.
- type: str
- netmask:
- description:
- - subnet mask for the interface.
- type: str
- gateway:
- description:
- - IP router network address to send packets out of the local network.
- type: str
- mtu:
- description:
- - The largest packet size (in bytes) that the interface can transmit..
- - Must be greater than or equal to 1500 bytes.
- type: str
- dns_nameservers:
- description:
- - List of addresses for domain name servers.
- type: list
- elements: str
- dns_search:
- description:
- - List of DNS search domains.
- type: list
- elements: str
- bond_mode:
- description:
- - Bonding mode.
- choices: ['ActivePassive', 'ALB', 'LACP']
- type: str
- bond_lacp_rate:
- description:
- - Link Aggregation Control Protocol - useful only if LACP is selected as the Bond Mode.
- - Slow - Packets are transmitted at 30 second intervals.
- - Fast - Packets are transmitted in 1 second intervals.
- choices: ['Fast', 'Slow']
- type: str
- virtual_network_tag:
- description:
- - The virtual network identifier of the interface (VLAN tag).
- type: str
-
-'''
-
-EXAMPLES = """
-
- - name: Set Node network interfaces configuration for Bond 1G and 10G properties
- tags:
- - elementsw_network_interfaces
- na_elementsw_network_interfaces:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- bond_1g:
- address: 10.253.168.131
- netmask: 255.255.248.0
- gateway: 10.253.168.1
- mtu: '1500'
- bond_mode: ActivePassive
- dns_nameservers: dns1,dns2
- dns_search: domain1,domain2
- bond_10g:
- address: 10.253.1.202
- netmask: 255.255.255.192
- gateway: 10.253.1.193
- mtu: '9000'
- bond_mode: LACP
- bond_lacp_rate: Fast
- virtual_network_tag: vnet_tag
-"""
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-try:
- from solidfire.models import Network, NetworkConfig
- from solidfire.common import ApiConnectionError as sf_ApiConnectionError, ApiServerError as sf_ApiServerError
- HAS_SF_SDK = True
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWNetworkInterfaces(object):
- """
- Element Software Network Interfaces - Bond 1G and 10G Network configuration
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- method=dict(required=False, type='str'),
- ip_address_1g=dict(required=False, type='str'),
- ip_address_10g=dict(required=False, type='str'),
- subnet_1g=dict(required=False, type='str'),
- subnet_10g=dict(required=False, type='str'),
- gateway_address_1g=dict(required=False, type='str'),
- gateway_address_10g=dict(required=False, type='str'),
- mtu_1g=dict(required=False, type='str'),
- mtu_10g=dict(required=False, type='str'),
- dns_nameservers=dict(required=False, type='list', elements='str'),
- dns_search_domains=dict(required=False, type='list', elements='str'),
- bond_mode_1g=dict(required=False, type='str'),
- bond_mode_10g=dict(required=False, type='str'),
- lacp_1g=dict(required=False, type='str'),
- lacp_10g=dict(required=False, type='str'),
- virtual_network_tag=dict(required=False, type='str'),
- bond_1g=dict(required=False, type='dict', options=dict(
- address=dict(required=False, type='str'),
- netmask=dict(required=False, type='str'),
- gateway=dict(required=False, type='str'),
- mtu=dict(required=False, type='str'),
- dns_nameservers=dict(required=False, type='list', elements='str'),
- dns_search=dict(required=False, type='list', elements='str'),
- bond_mode=dict(required=False, type='str', choices=['ActivePassive', 'ALB', 'LACP']),
- bond_lacp_rate=dict(required=False, type='str', choices=['Fast', 'Slow']),
- virtual_network_tag=dict(required=False, type='str'),
- )),
- bond_10g=dict(required=False, type='dict', options=dict(
- address=dict(required=False, type='str'),
- netmask=dict(required=False, type='str'),
- gateway=dict(required=False, type='str'),
- mtu=dict(required=False, type='str'),
- dns_nameservers=dict(required=False, type='list', elements='str'),
- dns_search=dict(required=False, type='list', elements='str'),
- bond_mode=dict(required=False, type='str', choices=['ActivePassive', 'ALB', 'LACP']),
- bond_lacp_rate=dict(required=False, type='str', choices=['Fast', 'Slow']),
- virtual_network_tag=dict(required=False, type='str'),
- )),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=False
- )
-
- input_params = self.module.params
- self.fail_when_deprecated_options_are_set(input_params)
-
- self.bond1g = input_params['bond_1g']
- self.bond10g = input_params['bond_10g']
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- # increase time out, as it may take 30 seconds when making a change
- self.sfe = netapp_utils.create_sf_connection(module=self.module, port=442, timeout=90)
-
- def fail_when_deprecated_options_are_set(self, input_params):
- ''' report an error and exit if any deprecated options is set '''
-
- dparms_1g = [x for x in ('ip_address_1g', 'subnet_1g', 'gateway_address_1g', 'mtu_1g', 'bond_mode_1g', 'lacp_1g')
- if input_params[x] is not None]
- dparms_10g = [x for x in ('ip_address_10g', 'subnet_10g', 'gateway_address_10g', 'mtu_10g', 'bond_mode_10g', 'lacp_10g')
- if input_params[x] is not None]
- dparms_common = [x for x in ('dns_nameservers', 'dns_search_domains', 'virtual_network_tag')
- if input_params[x] is not None]
-
- error_msg = ''
- if dparms_1g and dparms_10g:
- error_msg = 'Please use the new bond_1g and bond_10g options to configure the bond interfaces.'
- elif dparms_1g:
- error_msg = 'Please use the new bond_1g option to configure the bond 1G interface.'
- elif dparms_10g:
- error_msg = 'Please use the new bond_10g option to configure the bond 10G interface.'
- elif dparms_common:
- error_msg = 'Please use the new bond_1g or bond_10g options to configure the bond interfaces.'
- if input_params['method']:
- error_msg = 'This module cannot set or change "method". ' + error_msg
- dparms_common.append('method')
- if error_msg:
- error_msg += ' The following parameters are deprecated and cannot be used: '
- dparms = dparms_1g
- dparms.extend(dparms_10g)
- dparms.extend(dparms_common)
- error_msg += ', '.join(dparms)
- self.module.fail_json(msg=error_msg)
-
- def set_network_config(self, network_object):
- """
- set network configuration
- """
- try:
- self.sfe.set_network_config(network=network_object)
- except (sf_ApiConnectionError, sf_ApiServerError) as exception_object:
- self.module.fail_json(msg='Error setting network config for node %s' % (to_native(exception_object)),
- exception=traceback.format_exc())
-
- def set_network_config_object(self, network_params):
- ''' set SolidFire network config object '''
- network_config = dict()
- if network_params is not None:
- for key in network_params:
- if network_params[key] is not None:
- network_config[key] = network_params[key]
- if network_config:
- return NetworkConfig(**network_config)
- return None
-
- def set_network_object(self):
- """
- Set Element SW Network object
- :description: set Network object
-
- :return: Network object
- :rtype: object(Network object)
- """
- bond_1g_network = self.set_network_config_object(self.bond1g)
- bond_10g_network = self.set_network_config_object(self.bond10g)
- network_object = None
- if bond_1g_network is not None or bond_10g_network is not None:
- network_object = Network(bond1_g=bond_1g_network,
- bond10_g=bond_10g_network)
- return network_object
-
- def apply(self):
- """
- Check connection and initialize node with cluster ownership
- """
- changed = False
- result_message = None
- network_object = self.set_network_object()
- if network_object is not None:
- if not self.module.check_mode:
- self.set_network_config(network_object)
- changed = True
- else:
- result_message = "Skipping changes, No change requested"
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """
- Main function
- """
- elementsw_network_interfaces = ElementSWNetworkInterfaces()
- elementsw_network_interfaces.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_node.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_node.py
deleted file mode 100644
index d1412f2d4..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_node.py
+++ /dev/null
@@ -1,357 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element Software Node Operation
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_node
-
-short_description: NetApp Element Software Node Operation
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
- - Add, remove cluster node on Element Software Cluster.
- - Set cluster name on node.
- - When using the preset_only option, hostname/username/password are required but not used.
-
-options:
- state:
- description:
- - Element Software Storage Node operation state.
- - present - To add pending node to participate in cluster data storage.
- - absent - To remove node from active cluster. A node cannot be removed if active drives are present.
- choices: ['present', 'absent']
- default: 'present'
- type: str
-
- node_ids:
- description:
- - List of IDs or Names or IP Addresses of nodes to add or remove.
- - If cluster_name is set, node MIPs are required.
- type: list
- elements: str
- required: true
- aliases: ['node_id']
-
- cluster_name:
- description:
- - If set, the current node configuration is updated with this name before adding the node to the cluster.
- - This requires the node_ids to be specified as MIPs (Management IP Adresses)
- type: str
- version_added: 20.9.0
-
- preset_only:
- description:
- - If true and state is 'present', set the cluster name for each node in node_ids, but do not add the nodes.
- - They can be added using na_elementsw_cluster for initial cluster creation.
- - If false, proceed with addition/removal.
- type: bool
- default: false
- version_added: 20.9.0
-'''
-
-EXAMPLES = """
- - name: Add node from pending to active cluster
- tags:
- - elementsw_add_node
- na_elementsw_node:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- node_id: sf4805-meg-03
-
- - name: Remove active node from cluster
- tags:
- - elementsw_remove_node
- na_elementsw_node:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- node_id: 13
-
- - name: Add node from pending to active cluster using node IP
- tags:
- - elementsw_add_node_ip
- na_elementsw_node:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- node_id: 10.109.48.65
- cluster_name: sfcluster01
-
- - name: Only set cluster name
- tags:
- - elementsw_add_node_ip
- na_elementsw_node:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- node_ids: 10.109.48.65,10.109.48.66
- cluster_name: sfcluster01
- preset_only: true
-"""
-
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementSWNode(object):
- """
- Element SW Storage Node operations
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, choices=['present', 'absent'], default='present'),
- node_ids=dict(required=True, type='list', elements='str', aliases=['node_id']),
- cluster_name=dict(required=False, type='str'),
- preset_only=dict(required=False, type='bool', default=False),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- input_params = self.module.params
-
- self.state = input_params['state']
- self.node_ids = input_params['node_ids']
- self.cluster_name = input_params['cluster_name']
- self.preset_only = input_params['preset_only']
-
- if HAS_SF_SDK is False:
- self.module.fail_json(
- msg="Unable to import the SolidFire Python SDK")
- elif not self.preset_only:
- # Cluster connection is only needed for add/delete operations
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
-
- def check_node_has_active_drives(self, node_id=None):
- """
- Check if node has active drives attached to cluster
- :description: Validate if node have active drives in cluster
-
- :return: True or False
- :rtype: bool
- """
- if node_id is not None:
- cluster_drives = self.sfe.list_drives()
- for drive in cluster_drives.drives:
- if drive.node_id == node_id and drive.status == "active":
- return True
- return False
-
- @staticmethod
- def extract_node_info(node_list):
- summary = list()
- for node in node_list:
- node_dict = dict()
- for key, value in vars(node).items():
- if key in ['assigned_node_id', 'cip', 'mip', 'name', 'node_id', 'pending_node_id', 'sip']:
- node_dict[key] = value
- summary.append(node_dict)
- return summary
-
- def get_node_list(self):
- """
- Get Node List
- :description: Find and retrieve node_ids from the active cluster
-
- :return: None
- :rtype: None
- """
- action_nodes_list = list()
- if len(self.node_ids) > 0:
- unprocessed_node_list = list(self.node_ids)
- list_nodes = []
- try:
- all_nodes = self.sfe.list_all_nodes()
- except netapp_utils.solidfire.common.ApiServerError as exception_object:
- self.module.fail_json(msg='Error getting list of nodes from cluster: %s' % to_native(exception_object),
- exception=traceback.format_exc())
-
- # For add operation lookup for nodes list with status pendingNodes list
- # else nodes will have to be traverse through active cluster
- if self.state == "present":
- list_nodes = all_nodes.pending_nodes
- else:
- list_nodes = all_nodes.nodes
-
- for current_node in list_nodes:
- if self.state == "absent" and \
- (str(current_node.node_id) in self.node_ids or current_node.name in self.node_ids or current_node.mip in self.node_ids):
- if self.check_node_has_active_drives(current_node.node_id):
- self.module.fail_json(msg='Error deleting node %s: node has active drives' % current_node.name)
- else:
- action_nodes_list.append(current_node.node_id)
- if self.state == "present" and \
- (str(current_node.pending_node_id) in self.node_ids or current_node.name in self.node_ids or current_node.mip in self.node_ids):
- action_nodes_list.append(current_node.pending_node_id)
-
- # report an error if state == present and node is unknown
- if self.state == "present":
- for current_node in all_nodes.nodes:
- if str(current_node.node_id) in unprocessed_node_list:
- unprocessed_node_list.remove(str(current_node.node_id))
- elif current_node.name in unprocessed_node_list:
- unprocessed_node_list.remove(current_node.name)
- elif current_node.mip in unprocessed_node_list:
- unprocessed_node_list.remove(current_node.mip)
- for current_node in all_nodes.pending_nodes:
- if str(current_node.pending_node_id) in unprocessed_node_list:
- unprocessed_node_list.remove(str(current_node.pending_node_id))
- elif current_node.name in unprocessed_node_list:
- unprocessed_node_list.remove(current_node.name)
- elif current_node.mip in unprocessed_node_list:
- unprocessed_node_list.remove(current_node.mip)
- if len(unprocessed_node_list) > 0:
- summary = dict(
- nodes=self.extract_node_info(all_nodes.nodes),
- pending_nodes=self.extract_node_info(all_nodes.pending_nodes),
- pending_active_nodes=self.extract_node_info(all_nodes.pending_active_nodes)
- )
- self.module.fail_json(msg='Error adding nodes %s: nodes not in pending or active lists: %s' %
- (to_native(unprocessed_node_list), repr(summary)))
- return action_nodes_list
-
- def add_node(self, nodes_list=None):
- """
- Add Node that are on PendingNodes list available on Cluster
- """
- try:
- self.sfe.add_nodes(nodes_list, auto_install=True)
- except Exception as exception_object:
- self.module.fail_json(msg='Error adding nodes %s to cluster: %s' % (nodes_list, to_native(exception_object)),
- exception=traceback.format_exc())
-
- def remove_node(self, nodes_list=None):
- """
- Remove active node from Cluster
- """
- try:
- self.sfe.remove_nodes(nodes_list)
- except Exception as exception_object:
- self.module.fail_json(msg='Error removing nodes %s from cluster %s' % (nodes_list, to_native(exception_object)),
- exception=traceback.format_exc())
-
- def set_cluster_name(self, node):
- ''' set up cluster name for the node using its MIP '''
- cluster = dict(cluster=self.cluster_name)
- port = 442
- try:
- node_cx = netapp_utils.create_sf_connection(module=self.module, raise_on_connection_error=True, hostname=node, port=port)
- except netapp_utils.solidfire.common.ApiConnectionError as exc:
- if str(exc) == "Bad Credentials":
- msg = 'Most likely the node %s is already in a cluster.' % node
- msg += ' Make sure to use valid node credentials for username and password.'
- msg += ' Node reported: %s' % repr(exc)
- else:
- msg = 'Failed to create connection: %s' % repr(exc)
- self.module.fail_json(msg=msg)
- except Exception as exc:
- self.module.fail_json(msg='Failed to connect to %s:%d - %s' % (node, port, to_native(exc)),
- exception=traceback.format_exc())
-
- try:
- cluster_config = node_cx.get_cluster_config()
- except netapp_utils.solidfire.common.ApiServerError as exc:
- self.module.fail_json(msg='Error getting cluster config: %s' % to_native(exc),
- exception=traceback.format_exc())
-
- if cluster_config.cluster.cluster == self.cluster_name:
- return False
- if cluster_config.cluster.state == 'Active':
- self.module.fail_json(msg="Error updating cluster name for node %s, already in 'Active' state"
- % node, cluster_config=repr(cluster_config))
- if self.module.check_mode:
- return True
-
- try:
- node_cx.set_cluster_config(cluster)
- except netapp_utils.solidfire.common.ApiServerError as exc:
- self.module.fail_json(msg='Error updating cluster name: %s' % to_native(exc),
- cluster_config=repr(cluster_config),
- exception=traceback.format_exc())
- return True
-
- def apply(self):
- """
- Check, process and initiate Cluster Node operation
- """
- changed = False
- updated_nodes = list()
- result_message = ''
- if self.state == "present" and self.cluster_name is not None:
- for node in self.node_ids:
- if self.set_cluster_name(node):
- changed = True
- updated_nodes.append(node)
- if not self.preset_only:
- # let's see if there is anything to add or remove
- action_nodes_list = self.get_node_list()
- action = None
- if self.state == "present" and len(action_nodes_list) > 0:
- changed = True
- action = 'added'
- if not self.module.check_mode:
- self.add_node(action_nodes_list)
- elif self.state == "absent" and len(action_nodes_list) > 0:
- changed = True
- action = 'removed'
- if not self.module.check_mode:
- self.remove_node(action_nodes_list)
- if action:
- result_message = 'List of %s nodes: %s - requested: %s' % (action, to_native(action_nodes_list), to_native(self.node_ids))
- if updated_nodes:
- result_message += '\n' if result_message else ''
- result_message += 'List of updated nodes with %s: %s' % (self.cluster_name, updated_nodes)
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """
- Main function
- """
-
- na_elementsw_node = ElementSWNode()
- na_elementsw_node.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_qos_policy.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_qos_policy.py
deleted file mode 100644
index 9d9e16994..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_qos_policy.py
+++ /dev/null
@@ -1,270 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2020, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-"""
-Element Software QOS Policy
-"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_qos_policy
-
-short_description: NetApp Element Software create/modify/rename/delete QOS Policy
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 20.9.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, modify, rename, or delete QOS policy on Element Software Cluster.
-
-options:
-
- state:
- description:
- - Whether the specified QOS policy should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- name:
- description:
- - Name or id for the QOS policy.
- required: true
- type: str
-
- from_name:
- description:
- - Name or id for the QOS policy to be renamed.
- type: str
-
- qos:
- description:
- - The quality of service (QQOS) for the policy.
- - Required for create
- - Supported keys are minIOPS, maxIOPS, burstIOPS
- type: dict
- suboptions:
- minIOPS:
- description: The minimum number of IOPS guaranteed for the volume.
- type: int
- version_added: 21.3.0
- maxIOPS:
- description: The maximum number of IOPS allowed for the volume.
- type: int
- version_added: 21.3.0
- burstIOPS:
- description: The maximum number of IOPS allowed over a short period of time for the volume.
- type: int
- version_added: 21.3.0
- debug:
- description: report additional information when set to true.
- type: bool
- default: false
- version_added: 21.3.0
-'''
-
-EXAMPLES = """
- - name: Add QOS Policy
- na_elementsw_qos_policy:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- name: gold
- qos: {minIOPS: 1000, maxIOPS: 20000, burstIOPS: 50000}
-
- - name: Modify QOS Policy
- na_elementsw_qos_policy:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- name: gold
- qos: {minIOPS: 100, maxIOPS: 5000, burstIOPS: 20000}
-
- - name: Rename QOS Policy
- na_elementsw_qos_policy:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- from_name: gold
- name: silver
-
- - name: Remove QOS Policy
- na_elementsw_qos_policy:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- name: silver
-"""
-
-
-RETURN = """
-"""
-
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWQosPolicy(object):
- """
- Element Software QOS Policy
- """
-
- def __init__(self):
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- name=dict(required=True, type='str'),
- from_name=dict(required=False, type='str'),
- qos=dict(required=False, type='dict', options=dict(
- minIOPS=dict(type='int'),
- maxIOPS=dict(type='int'),
- burstIOPS=dict(type='int'),
- )),
- debug=dict(required=False, type='bool', default=False)
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- # Set up state variables
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
- self.qos_policy_id = None
- self.debug = dict()
-
- 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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_qos_policy')
-
- def get_qos_policy(self, name):
- """
- Get QOS Policy
- """
- policy, error = self.elementsw_helper.get_qos_policy(name)
- if error is not None:
- self.module.fail_json(msg=error, exception=traceback.format_exc())
- self.debug['current_policy'] = policy
- return policy
-
- def create_qos_policy(self, name, qos):
- """
- Create the QOS Policy
- """
- try:
- self.sfe.create_qos_policy(name=name, qos=qos)
- except (solidfire.common.ApiServerError, solidfire.common.ApiConnectionError) as exc:
- self.module.fail_json(msg="Error creating qos policy: %s: %s" %
- (name, to_native(exc)), exception=traceback.format_exc())
-
- def update_qos_policy(self, qos_policy_id, modify, name=None):
- """
- Update the QOS Policy if the policy already exists
- """
- options = dict(
- qos_policy_id=qos_policy_id
- )
- if name is not None:
- options['name'] = name
- if 'qos' in modify:
- options['qos'] = modify['qos']
-
- try:
- self.sfe.modify_qos_policy(**options)
- except (solidfire.common.ApiServerError, solidfire.common.ApiConnectionError) as exc:
- self.module.fail_json(msg="Error updating qos policy: %s: %s" %
- (self.parameters['from_name'] if name is not None else self.parameters['name'], to_native(exc)),
- exception=traceback.format_exc())
-
- def delete_qos_policy(self, qos_policy_id):
- """
- Delete the QOS Policy
- """
- try:
- self.sfe.delete_qos_policy(qos_policy_id=qos_policy_id)
- except (solidfire.common.ApiServerError, solidfire.common.ApiConnectionError) as exc:
- self.module.fail_json(msg="Error deleting qos policy: %s: %s" %
- (self.parameters['name'], to_native(exc)), exception=traceback.format_exc())
-
- def apply(self):
- """
- Process the create/delete/rename/modify actions for qos policy on the Element Software Cluster
- """
- modify = dict()
- current = self.get_qos_policy(self.parameters['name'])
- qos_policy_id = None if current is None else current['qos_policy_id']
- cd_action = self.na_helper.get_cd_action(current, self.parameters)
- modify = self.na_helper.get_modified_attributes(current, self.parameters)
- if cd_action == 'create' and self.parameters.get('from_name') is not None:
- from_qos_policy = self.get_qos_policy(self.parameters['from_name'])
- if from_qos_policy is None:
- self.module.fail_json(msg="Error renaming qos policy, no existing policy with name/id: %s" % self.parameters['from_name'])
- cd_action = 'rename'
- qos_policy_id = from_qos_policy['qos_policy_id']
- self.na_helper.changed = True
- modify = self.na_helper.get_modified_attributes(from_qos_policy, self.parameters)
- if cd_action == 'create' and 'qos' not in self.parameters:
- self.module.fail_json(msg="Error creating qos policy: %s, 'qos:' option is required" % self.parameters['name'])
- self.debug['modify'] = modify
-
- if not self.module.check_mode:
- if cd_action == 'create':
- self.create_qos_policy(self.parameters['name'], self.parameters['qos'])
- elif cd_action == 'delete':
- self.delete_qos_policy(qos_policy_id)
- elif cd_action == 'rename':
- self.update_qos_policy(qos_policy_id, modify, name=self.parameters['name'])
- elif modify:
- self.update_qos_policy(qos_policy_id, modify)
-
- results = dict(changed=self.na_helper.changed)
- if self.parameters['debug']:
- results['debug'] = self.debug
- self.module.exit_json(**results)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_qos_policy = ElementSWQosPolicy()
- na_elementsw_qos_policy.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot.py
deleted file mode 100644
index 23144e42e..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot.py
+++ /dev/null
@@ -1,369 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-'''
-Element OS Software Snapshot Manager
-'''
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_snapshot
-
-short_description: NetApp Element Software Manage Snapshots
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
- - Create, Modify or Delete Snapshot on Element OS Cluster.
-
-options:
- name:
- description:
- - Name of new snapshot create.
- - If unspecified, date and time when the snapshot was taken is used.
- type: str
-
- state:
- description:
- - Whether the specified snapshot should exist or not.
- choices: ['present', 'absent']
- default: 'present'
- type: str
-
- src_volume_id:
- description:
- - ID or Name of active volume.
- required: true
- type: str
-
- account_id:
- description:
- - Account ID or Name of Parent/Source Volume.
- required: true
- type: str
-
- retention:
- description:
- - Retention period for the snapshot.
- - Format is 'HH:mm:ss'.
- type: str
-
- src_snapshot_id:
- description:
- - ID or Name of an existing snapshot.
- - Required when C(state=present), to modify snapshot properties.
- - Required when C(state=present), to create snapshot from another snapshot in the volume.
- - Required when C(state=absent), to delete snapshot.
- type: str
-
- enable_remote_replication:
- description:
- - Flag, whether to replicate the snapshot created to a remote replication cluster.
- - To enable specify 'true' value.
- type: bool
-
- snap_mirror_label:
- description:
- - Label used by SnapMirror software to specify snapshot retention policy on SnapMirror endpoint.
- type: str
-
- expiration_time:
- description:
- - The date and time (format ISO 8601 date string) at which this snapshot will expire.
- type: str
-'''
-
-EXAMPLES = """
- - name: Create snapshot
- tags:
- - elementsw_create_snapshot
- na_elementsw_snapshot:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- src_volume_id: 118
- account_id: sagarsh
- name: newsnapshot-1
-
- - name: Modify Snapshot
- tags:
- - elementsw_modify_snapshot
- na_elementsw_snapshot:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- src_volume_id: sagarshansivolume
- src_snapshot_id: test1
- account_id: sagarsh
- expiration_time: '2018-06-16T12:24:56Z'
- enable_remote_replication: false
-
- - name: Delete Snapshot
- tags:
- - elementsw_delete_snapshot
- na_elementsw_snapshot:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- src_snapshot_id: deltest1
- account_id: sagarsh
- src_volume_id: sagarshansivolume
-"""
-
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementOSSnapshot(object):
- """
- Element OS Snapshot Manager
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, choices=['present', 'absent'], default='present'),
- account_id=dict(required=True, type='str'),
- name=dict(required=False, type='str'),
- src_volume_id=dict(required=True, type='str'),
- retention=dict(required=False, type='str'),
- src_snapshot_id=dict(required=False, type='str'),
- enable_remote_replication=dict(required=False, type='bool'),
- expiration_time=dict(required=False, type='str'),
- snap_mirror_label=dict(required=False, type='str')
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- input_params = self.module.params
-
- self.state = input_params['state']
- self.name = input_params['name']
- self.account_id = input_params['account_id']
- self.src_volume_id = input_params['src_volume_id']
- self.src_snapshot_id = input_params['src_snapshot_id']
- self.retention = input_params['retention']
- self.properties_provided = False
-
- self.expiration_time = input_params['expiration_time']
- if input_params['expiration_time'] is not None:
- self.properties_provided = True
-
- self.enable_remote_replication = input_params['enable_remote_replication']
- if input_params['enable_remote_replication'] is not None:
- self.properties_provided = True
-
- self.snap_mirror_label = input_params['snap_mirror_label']
- if input_params['snap_mirror_label'] is not None:
- self.properties_provided = True
-
- if self.state == 'absent' and self.src_snapshot_id is None:
- self.module.fail_json(
- msg="Please provide required parameter : snapshot_id")
-
- 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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_snapshot')
-
- def get_account_id(self):
- """
- Return account id if found
- """
- try:
- # Update and return self.account_id
- self.account_id = self.elementsw_helper.account_exists(self.account_id)
- return self.account_id
- except Exception as err:
- self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err))
-
- def get_src_volume_id(self):
- """
- Return volume id if found
- """
- src_vol_id = self.elementsw_helper.volume_exists(self.src_volume_id, self.account_id)
- if src_vol_id is not None:
- # Update and return self.volume_id
- self.src_volume_id = src_vol_id
- # Return src_volume_id
- return self.src_volume_id
- return None
-
- def get_snapshot(self, name=None):
- """
- Return snapshot details if found
- """
- src_snapshot = None
- if name is not None:
- src_snapshot = self.elementsw_helper.get_snapshot(name, self.src_volume_id)
- elif self.src_snapshot_id is not None:
- src_snapshot = self.elementsw_helper.get_snapshot(self.src_snapshot_id, self.src_volume_id)
- if src_snapshot is not None:
- # Update self.src_snapshot_id
- self.src_snapshot_id = src_snapshot.snapshot_id
- # Return src_snapshot
- return src_snapshot
-
- def create_snapshot(self):
- """
- Create Snapshot
- """
- try:
- self.sfe.create_snapshot(volume_id=self.src_volume_id,
- snapshot_id=self.src_snapshot_id,
- name=self.name,
- enable_remote_replication=self.enable_remote_replication,
- retention=self.retention,
- snap_mirror_label=self.snap_mirror_label,
- attributes=self.attributes)
- except Exception as exception_object:
- self.module.fail_json(
- msg='Error creating snapshot %s' % (
- to_native(exception_object)),
- exception=traceback.format_exc())
-
- def modify_snapshot(self):
- """
- Modify Snapshot Properties
- """
- try:
- self.sfe.modify_snapshot(snapshot_id=self.src_snapshot_id,
- expiration_time=self.expiration_time,
- enable_remote_replication=self.enable_remote_replication,
- snap_mirror_label=self.snap_mirror_label)
- except Exception as exception_object:
- self.module.fail_json(
- msg='Error modify snapshot %s' % (
- to_native(exception_object)),
- exception=traceback.format_exc())
-
- def delete_snapshot(self):
- """
- Delete Snapshot
- """
- try:
- self.sfe.delete_snapshot(snapshot_id=self.src_snapshot_id)
- except Exception as exception_object:
- self.module.fail_json(
- msg='Error delete snapshot %s' % (
- to_native(exception_object)),
- exception=traceback.format_exc())
-
- def apply(self):
- """
- Check, process and initiate snapshot operation
- """
- changed = False
- result_message = None
- self.get_account_id()
-
- # Dont proceed if source volume is not found
- if self.get_src_volume_id() is None:
- self.module.fail_json(msg="Volume id not found %s" % self.src_volume_id)
-
- # Get snapshot details using source volume
- snapshot_detail = self.get_snapshot()
-
- if snapshot_detail:
- if self.properties_provided:
- if self.expiration_time != snapshot_detail.expiration_time:
- changed = True
- else: # To preserve value in case parameter expiration_time is not defined/provided.
- self.expiration_time = snapshot_detail.expiration_time
-
- if self.enable_remote_replication != snapshot_detail.enable_remote_replication:
- changed = True
- else: # To preserve value in case parameter enable_remote_Replication is not defined/provided.
- self.enable_remote_replication = snapshot_detail.enable_remote_replication
-
- if self.snap_mirror_label != snapshot_detail.snap_mirror_label:
- changed = True
- else: # To preserve value in case parameter snap_mirror_label is not defined/provided.
- self.snap_mirror_label = snapshot_detail.snap_mirror_label
-
- if self.account_id is None or self.src_volume_id is None or self.module.check_mode:
- changed = False
- result_message = "Check mode, skipping changes"
- elif self.state == 'absent' and snapshot_detail is not None:
- self.delete_snapshot()
- changed = True
- elif self.state == 'present' and snapshot_detail is not None:
- if changed:
- self.modify_snapshot() # Modify Snapshot properties
- elif not self.properties_provided:
- if self.name is not None:
- snapshot = self.get_snapshot(self.name)
- # If snapshot with name already exists return without performing any action
- if snapshot is None:
- self.create_snapshot() # Create Snapshot using parent src_snapshot_id
- changed = True
- else:
- self.create_snapshot()
- changed = True
- elif self.state == 'present':
- if self.name is not None:
- snapshot = self.get_snapshot(self.name)
- # If snapshot with name already exists return without performing any action
- if snapshot is None:
- self.create_snapshot() # Create Snapshot using parent src_snapshot_id
- changed = True
- else:
- self.create_snapshot()
- changed = True
- else:
- changed = False
- result_message = "No changes requested, skipping changes"
-
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """
- Main function
- """
-
- na_elementsw_snapshot = ElementOSSnapshot()
- na_elementsw_snapshot.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_restore.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_restore.py
deleted file mode 100644
index 1e9d8e59a..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_restore.py
+++ /dev/null
@@ -1,203 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-"""
-Element Software Snapshot Restore
-"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_snapshot_restore
-
-short_description: NetApp Element Software Restore Snapshot
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Element OS Cluster restore snapshot to volume.
-
-options:
-
- src_volume_id:
- description:
- - ID or Name of source active volume.
- required: true
- type: str
-
- src_snapshot_id:
- description:
- - ID or Name of an existing snapshot.
- required: true
- type: str
-
- dest_volume_name:
- description:
- - New Name of destination for restoring the snapshot
- required: true
- type: str
-
- account_id:
- description:
- - Account ID or Name of Parent/Source Volume.
- required: true
- type: str
-'''
-
-EXAMPLES = """
- - name: Restore snapshot to volume
- tags:
- - elementsw_create_snapshot_restore
- na_elementsw_snapshot_restore:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- account_id: ansible-1
- src_snapshot_id: snapshot_20171021
- src_volume_id: volume-playarea
- dest_volume_name: dest-volume-area
-
-"""
-
-
-RETURN = """
-
-msg:
- description: Success message
- returned: success
- type: str
-
-"""
-import traceback
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementOSSnapshotRestore(object):
- """
- Element OS Restore from snapshot
- """
-
- def __init__(self):
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- account_id=dict(required=True, type='str'),
- src_volume_id=dict(required=True, type='str'),
- dest_volume_name=dict(required=True, type='str'),
- src_snapshot_id=dict(required=True, type='str')
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- input_params = self.module.params
-
- self.account_id = input_params['account_id']
- self.src_volume_id = input_params['src_volume_id']
- self.dest_volume_name = input_params['dest_volume_name']
- self.src_snapshot_id = input_params['src_snapshot_id']
-
- 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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_snapshot_restore')
-
- def get_account_id(self):
- """
- Get account id if found
- """
- try:
- # Update and return self.account_id
- self.account_id = self.elementsw_helper.account_exists(self.account_id)
- return self.account_id
- except Exception as err:
- self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err))
-
- def get_snapshot_id(self):
- """
- Return snapshot details if found
- """
- src_snapshot = self.elementsw_helper.get_snapshot(self.src_snapshot_id, self.src_volume_id)
- # Update and return self.src_snapshot_id
- if src_snapshot:
- self.src_snapshot_id = src_snapshot.snapshot_id
- # Return self.src_snapshot_id
- return self.src_snapshot_id
- return None
-
- def restore_snapshot(self):
- """
- Restore Snapshot to Volume
- """
- try:
- self.sfe.clone_volume(volume_id=self.src_volume_id,
- name=self.dest_volume_name,
- snapshot_id=self.src_snapshot_id,
- attributes=self.attributes)
- except Exception as exception_object:
- self.module.fail_json(
- msg='Error restore snapshot %s' % (to_native(exception_object)),
- exception=traceback.format_exc())
-
- def apply(self):
- """
- Check, process and initiate restore snapshot to volume operation
- """
- changed = False
- result_message = None
- self.get_account_id()
- src_vol_id = self.elementsw_helper.volume_exists(self.src_volume_id, self.account_id)
-
- if src_vol_id is not None:
- # Update self.src_volume_id
- self.src_volume_id = src_vol_id
- if self.get_snapshot_id() is not None:
- # Addressing idempotency by comparing volume does not exist with same volume name
- if self.elementsw_helper.volume_exists(self.dest_volume_name, self.account_id) is None:
- self.restore_snapshot()
- changed = True
- else:
- result_message = "No changes requested, Skipping changes"
- else:
- self.module.fail_json(msg="Snapshot id not found %s" % self.src_snapshot_id)
- else:
- self.module.fail_json(msg="Volume id not found %s" % self.src_volume_id)
-
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """
- Main function
- """
- na_elementsw_snapshot_restore = ElementOSSnapshotRestore()
- na_elementsw_snapshot_restore.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_schedule.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_schedule.py
deleted file mode 100644
index 2ace1bd4b..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_snapshot_schedule.py
+++ /dev/null
@@ -1,586 +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)
-
-"""Element SW Software Snapshot Schedule"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_snapshot_schedule
-
-short_description: NetApp Element Software Snapshot Schedules
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, destroy, or update snapshot schedules on ElementSW
-
-options:
-
- state:
- description:
- - Whether the specified schedule should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- paused:
- description:
- - Pause / Resume a schedule.
- type: bool
-
- recurring:
- description:
- - Should the schedule recur?
- type: bool
-
- schedule_type:
- description:
- - Schedule type for creating schedule.
- choices: ['DaysOfWeekFrequency','DaysOfMonthFrequency','TimeIntervalFrequency']
- type: str
-
- time_interval_days:
- description: Time interval in days.
- type: int
-
- time_interval_hours:
- description: Time interval in hours.
- type: int
-
- time_interval_minutes:
- description: Time interval in minutes.
- type: int
-
- days_of_week_weekdays:
- description: List of days of the week (Sunday to Saturday)
- type: list
- elements: str
-
- days_of_week_hours:
- description: Time specified in hours
- type: int
-
- days_of_week_minutes:
- description: Time specified in minutes.
- type: int
-
- days_of_month_monthdays:
- description: List of days of the month (1-31)
- type: list
- elements: int
-
- days_of_month_hours:
- description: Time specified in hours
- type: int
-
- days_of_month_minutes:
- description: Time specified in minutes.
- type: int
-
- name:
- description:
- - Name for the snapshot schedule.
- - It accepts either schedule_id or schedule_name
- - if name is digit, it will consider as schedule_id
- - If name is string, it will consider as schedule_name
- required: true
- type: str
-
- snapshot_name:
- description:
- - Name for the created snapshots.
- type: str
-
- volumes:
- description:
- - Volume IDs that you want to set the snapshot schedule for.
- - It accepts both volume_name and volume_id
- type: list
- elements: str
-
- account_id:
- description:
- - Account ID for the owner of this volume.
- - It accepts either account_name or account_id
- - if account_id is digit, it will consider as account_id
- - If account_id is string, it will consider as account_name
- type: str
-
- retention:
- description:
- - Retention period for the snapshot.
- - Format is 'HH:mm:ss'.
- type: str
-
- starting_date:
- description:
- - Starting date for the schedule.
- - Required when C(state=present).
- - "Format: C(2016-12-01T00:00:00Z)"
- type: str
-'''
-
-EXAMPLES = """
- - name: Create Snapshot schedule
- na_elementsw_snapshot_schedule:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- name: Schedule_A
- schedule_type: TimeIntervalFrequency
- time_interval_days: 1
- starting_date: '2016-12-01T00:00:00Z'
- retention: '24:00:00'
- volumes:
- - 7
- - test
- account_id: 1
-
- - name: Update Snapshot schedule
- na_elementsw_snapshot_schedule:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- name: Schedule_A
- schedule_type: TimeIntervalFrequency
- time_interval_days: 1
- starting_date: '2016-12-01T00:00:00Z'
- retention: '24:00:00'
- volumes:
- - 8
- - test1
- account_id: 1
-
- - name: Delete Snapshot schedule
- na_elementsw_snapshot_schedule:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: absent
- name: 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.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- from solidfire.custom.models import DaysOfWeekFrequency, Weekday, DaysOfMonthFrequency
- from solidfire.common import ApiConnectionError, ApiServerError
- from solidfire.custom.models import TimeIntervalFrequency
- from solidfire.models import Schedule, ScheduleInfo
-except ImportError:
- HAS_SF_SDK = False
-
-try:
- # Hack to see if we we have the 1.7 version of the SDK, or later
- from solidfire.common.model import VER3
- HAS_SF_SDK_1_7 = True
- del VER3
-except ImportError:
- HAS_SF_SDK_1_7 = False
-
-
-class ElementSWSnapShotSchedule(object):
- """
- Contains methods to parse arguments,
- derive details of ElementSW objects
- and send requests to ElementSW via
- the ElementSW SDK
- """
-
- def __init__(self):
- """
- Parse arguments, setup state variables,
- check paramenters and ensure SDK is installed
- """
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
- name=dict(required=True, type='str'),
- schedule_type=dict(required=False, choices=['DaysOfWeekFrequency', 'DaysOfMonthFrequency', 'TimeIntervalFrequency']),
-
- time_interval_days=dict(required=False, type='int'),
- time_interval_hours=dict(required=False, type='int'),
- time_interval_minutes=dict(required=False, type='int'),
-
- days_of_week_weekdays=dict(required=False, type='list', elements='str'),
- days_of_week_hours=dict(required=False, type='int'),
- days_of_week_minutes=dict(required=False, type='int'),
-
- days_of_month_monthdays=dict(required=False, type='list', elements='int'),
- days_of_month_hours=dict(required=False, type='int'),
- days_of_month_minutes=dict(required=False, type='int'),
-
- paused=dict(required=False, type='bool'),
- recurring=dict(required=False, type='bool'),
-
- starting_date=dict(required=False, type='str'),
-
- snapshot_name=dict(required=False, type='str'),
- volumes=dict(required=False, type='list', elements='str'),
- account_id=dict(required=False, type='str'),
- retention=dict(required=False, type='str'),
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- required_if=[
- ('state', 'present', ['account_id', 'volumes', 'schedule_type']),
- ('schedule_type', 'DaysOfMonthFrequency', ['days_of_month_monthdays']),
- ('schedule_type', 'DaysOfWeekFrequency', ['days_of_week_weekdays'])
-
- ],
- supports_check_mode=True
- )
-
- param = self.module.params
-
- # set up state variables
- self.state = param['state']
- self.name = param['name']
- self.schedule_type = param['schedule_type']
- self.days_of_week_weekdays = param['days_of_week_weekdays']
- self.days_of_week_hours = param['days_of_week_hours']
- self.days_of_week_minutes = param['days_of_week_minutes']
- self.days_of_month_monthdays = param['days_of_month_monthdays']
- self.days_of_month_hours = param['days_of_month_hours']
- self.days_of_month_minutes = param['days_of_month_minutes']
- self.time_interval_days = param['time_interval_days']
- self.time_interval_hours = param['time_interval_hours']
- self.time_interval_minutes = param['time_interval_minutes']
- self.paused = param['paused']
- self.recurring = param['recurring']
- if self.schedule_type == 'DaysOfWeekFrequency':
- # Create self.weekday list if self.schedule_type is days_of_week
- if self.days_of_week_weekdays is not None:
- # Create self.weekday list if self.schedule_type is days_of_week
- self.weekdays = []
- for day in self.days_of_week_weekdays:
- if str(day).isdigit():
- # If id specified, return appropriate day
- self.weekdays.append(Weekday.from_id(int(day)))
- else:
- # If name specified, return appropriate day
- self.weekdays.append(Weekday.from_name(day.capitalize()))
-
- if self.state == 'present' and self.schedule_type is None:
- # Mandate schedule_type for create operation
- self.module.fail_json(
- msg="Please provide required parameter: schedule_type")
-
- # Mandate schedule name for delete operation
- if self.state == 'absent' and self.name is None:
- self.module.fail_json(
- msg="Please provide required parameter: name")
-
- self.starting_date = param['starting_date']
- self.snapshot_name = param['snapshot_name']
- self.volumes = param['volumes']
- self.account_id = param['account_id']
- self.retention = param['retention']
- self.create_schedule_result = None
-
- if HAS_SF_SDK is False:
- # Create ElementSW connection
- self.module.fail_json(msg="Unable to import the ElementSW Python SDK")
- else:
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- def get_schedule(self):
- # Checking whether schedule id is exist or not
- # Return schedule details if found, None otherwise
- # If exist set variable self.name
- try:
- schedule_list = self.sfe.list_schedules()
- except ApiServerError:
- return None
-
- for schedule in schedule_list.schedules:
- if schedule.to_be_deleted:
- # skip this schedule if it is being deleted, it can as well not exist
- continue
- if str(schedule.schedule_id) == self.name:
- self.name = schedule.name
- return schedule
- elif schedule.name == self.name:
- return schedule
- return None
-
- def get_account_id(self):
- # Validate account id
- # Return account_id if found, None otherwise
- try:
- account_id = self.elementsw_helper.account_exists(self.account_id)
- return account_id
- except ApiServerError:
- return None
-
- def get_volume_id(self):
- # Validate volume_ids
- # Return volume ids if found, fail if not found
- volume_ids = []
- for volume in self.volumes:
- volume_id = self.elementsw_helper.volume_exists(volume.strip(), self.account_id)
- if volume_id:
- volume_ids.append(volume_id)
- else:
- self.module.fail_json(msg='Specified volume %s does not exist' % volume)
- return volume_ids
-
- def get_frequency(self):
- # Configuring frequency depends on self.schedule_type
- frequency = None
- if self.schedule_type is not None and self.schedule_type == 'DaysOfWeekFrequency':
- if self.weekdays is not None:
- params = dict(weekdays=self.weekdays)
- if self.days_of_week_hours is not None:
- params['hours'] = self.days_of_week_hours
- if self.days_of_week_minutes is not None:
- params['minutes'] = self.days_of_week_minutes
- frequency = DaysOfWeekFrequency(**params)
- elif self.schedule_type is not None and self.schedule_type == 'DaysOfMonthFrequency':
- if self.days_of_month_monthdays is not None:
- params = dict(monthdays=self.days_of_month_monthdays)
- if self.days_of_month_hours is not None:
- params['hours'] = self.days_of_month_hours
- if self.days_of_month_minutes is not None:
- params['minutes'] = self.days_of_month_minutes
- frequency = DaysOfMonthFrequency(**params)
- elif self.schedule_type is not None and self.schedule_type == 'TimeIntervalFrequency':
- params = dict()
- if self.time_interval_days is not None:
- params['days'] = self.time_interval_days
- if self.time_interval_hours is not None:
- params['hours'] = self.time_interval_hours
- if self.time_interval_minutes is not None:
- params['minutes'] = self.time_interval_minutes
- if not params or sum(params.values()) == 0:
- self.module.fail_json(msg='Specify at least one non zero value with TimeIntervalFrequency.')
- frequency = TimeIntervalFrequency(**params)
- return frequency
-
- def is_same_schedule_type(self, schedule_detail):
- # To check schedule type is same or not
- if str(schedule_detail.frequency).split('(', maxsplit=1)[0] == self.schedule_type:
- return True
- else:
- return False
-
- def create_schedule(self):
- # Create schedule
- try:
- frequency = self.get_frequency()
- if frequency is None:
- self.module.fail_json(msg='Failed to create schedule frequency object - type %s parameters' % self.schedule_type)
-
- # Create schedule
- name = self.name
- schedule_info = ScheduleInfo(
- volume_ids=self.volumes,
- snapshot_name=self.snapshot_name,
- retention=self.retention
- )
- if HAS_SF_SDK_1_7:
- sched = Schedule(frequency, name, schedule_info)
- else:
- sched = Schedule(schedule_info, name, frequency)
- sched.paused = self.paused
- sched.recurring = self.recurring
- sched.starting_date = self.starting_date
-
- self.create_schedule_result = self.sfe.create_schedule(sched)
-
- except (ApiServerError, ApiConnectionError) as exc:
- self.module.fail_json(msg='Error creating schedule %s: %s' % (self.name, to_native(exc)),
- exception=traceback.format_exc())
-
- def delete_schedule(self, schedule_id):
- # delete schedule
- try:
- get_schedule_result = self.sfe.get_schedule(schedule_id=schedule_id)
- sched = get_schedule_result.schedule
- sched.to_be_deleted = True
- self.sfe.modify_schedule(schedule=sched)
-
- except (ApiServerError, ApiConnectionError) as exc:
- self.module.fail_json(msg='Error deleting schedule %s: %s' % (self.name, to_native(exc)),
- exception=traceback.format_exc())
-
- def update_schedule(self, schedule_id):
- # Update schedule
- try:
- get_schedule_result = self.sfe.get_schedule(schedule_id=schedule_id)
- sched = get_schedule_result.schedule
- # Update schedule properties
- sched.frequency = self.get_frequency()
- if sched.frequency is None:
- self.module.fail_json(msg='Failed to create schedule frequency object - type %s parameters' % self.schedule_type)
-
- if self.volumes is not None and len(self.volumes) > 0:
- 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 (ApiServerError, ApiConnectionError) as exc:
- self.module.fail_json(msg='Error updating schedule %s: %s' % (self.name, to_native(exc)),
- exception=traceback.format_exc())
-
- def apply(self):
- # Perform pre-checks, call functions and exit
-
- changed = False
- update_schedule = False
-
- if self.account_id is not None:
- self.account_id = self.get_account_id()
-
- if self.state == 'present' and self.volumes is not None:
- if self.account_id:
- self.volumes = self.get_volume_id()
- else:
- self.module.fail_json(msg='Specified account id does not exist')
-
- # Getting the schedule details
- schedule_detail = self.get_schedule()
-
- if schedule_detail is None and self.state == 'present':
- if len(self.volumes) > 0:
- changed = True
- else:
- self.module.fail_json(msg='Specified volumes not on cluster')
- elif schedule_detail is not None:
- # Getting the schedule id
- if self.state == 'absent':
- changed = True
- else:
- # Check if we need to update the snapshot schedule
- if self.retention is not None and schedule_detail.schedule_info.retention != self.retention:
- 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.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.volumes is not None and len(self.volumes) > 0:
- for volume_id in schedule_detail.schedule_info.volume_ids:
- if volume_id not in self.volumes:
- update_schedule = True
- changed = True
-
- temp_frequency = self.get_frequency()
- if temp_frequency is not None:
- # Checking schedule_type changes
- if self.is_same_schedule_type(schedule_detail):
- # If same schedule type
- if self.schedule_type == "TimeIntervalFrequency":
- # Check if there is any change in schedule.frequency, If schedule_type is time_interval
- 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
- elif self.schedule_type == "DaysOfMonthFrequency":
- # Check if there is any change in schedule.frequency, If schedule_type is days_of_month
- if len(schedule_detail.frequency.monthdays) != len(temp_frequency.monthdays) or \
- schedule_detail.frequency.hours != temp_frequency.hours or \
- schedule_detail.frequency.minutes != temp_frequency.minutes:
- update_schedule = True
- changed = True
- elif len(schedule_detail.frequency.monthdays) == len(temp_frequency.monthdays):
- actual_frequency_monthday = schedule_detail.frequency.monthdays
- temp_frequency_monthday = temp_frequency.monthdays
- for monthday in actual_frequency_monthday:
- if monthday not in temp_frequency_monthday:
- update_schedule = True
- changed = True
- elif self.schedule_type == "DaysOfWeekFrequency":
- # Check if there is any change in schedule.frequency, If schedule_type is days_of_week
- if len(schedule_detail.frequency.weekdays) != len(temp_frequency.weekdays) or \
- schedule_detail.frequency.hours != temp_frequency.hours or \
- schedule_detail.frequency.minutes != temp_frequency.minutes:
- update_schedule = True
- changed = True
- elif len(schedule_detail.frequency.weekdays) == len(temp_frequency.weekdays):
- actual_frequency_weekdays = schedule_detail.frequency.weekdays
- temp_frequency_weekdays = temp_frequency.weekdays
- if len([actual_weekday for actual_weekday, temp_weekday in
- zip(actual_frequency_weekdays, temp_frequency_weekdays) if actual_weekday != temp_weekday]) != 0:
- update_schedule = True
- changed = True
- else:
- update_schedule = True
- changed = True
- else:
- self.module.fail_json(msg='Failed to create schedule frequency object - type %s parameters' % self.schedule_type)
-
- result_message = " "
- if changed:
- if self.module.check_mode:
- # Skip changes
- result_message = "Check mode, skipping changes"
- else:
- if self.state == 'present':
- if update_schedule:
- self.update_schedule(schedule_detail.schedule_id)
- result_message = "Snapshot Schedule modified"
- else:
- self.create_schedule()
- result_message = "Snapshot Schedule created"
- elif self.state == 'absent':
- self.delete_schedule(schedule_detail.schedule_id)
- result_message = "Snapshot Schedule deleted"
-
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- sss = ElementSWSnapShotSchedule()
- sss.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_vlan.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_vlan.py
deleted file mode 100644
index 299338ad5..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_vlan.py
+++ /dev/null
@@ -1,274 +0,0 @@
-#!/usr/bin/python
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_vlan
-
-short_description: NetApp Element Software Manage VLAN
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, delete, modify VLAN
-
-options:
-
- state:
- description:
- - Whether the specified vlan should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- vlan_tag:
- description:
- - Virtual Network Tag
- required: true
- type: str
-
- name:
- description:
- - User defined name for the new VLAN
- - Name of the vlan is unique
- - Required for create
- type: str
-
- svip:
- description:
- - Storage virtual IP which is unique
- - Required for create
- type: str
-
- address_blocks:
- description:
- - List of address blocks for the VLAN
- - Each address block contains the starting IP address and size for the block
- - Required for create
- type: list
- elements: dict
-
- netmask:
- description:
- - Netmask for the VLAN
- - Required for create
- type: str
-
- gateway:
- description:
- - Gateway for the VLAN
- type: str
-
- namespace:
- description:
- - Enable or disable namespaces
- type: bool
-
- attributes:
- description:
- - Dictionary of attributes with name and value for each attribute
- type: dict
-
-'''
-
-EXAMPLES = """
-- name: Create vlan
- na_elementsw_vlan:
- state: present
- name: test
- vlan_tag: 1
- svip: "{{ ip address }}"
- netmask: "{{ netmask }}"
- address_blocks:
- - start: "{{ starting ip_address }}"
- size: 5
- - start: "{{ starting ip_address }}"
- size: 5
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-
-- name: Delete Lun
- na_elementsw_vlan:
- state: absent
- vlan_tag: 1
- hostname: "{{ netapp_hostname }}"
- username: "{{ netapp_username }}"
- password: "{{ netapp_password }}"
-"""
-
-RETURN = """
-
-"""
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWVlan(object):
- """ class to handle VLAN operations """
-
- def __init__(self):
- """
- Setup Ansible parameters and ElementSW connection
- """
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, choices=['present', 'absent'],
- default='present'),
- name=dict(required=False, type='str'),
- vlan_tag=dict(required=True, type='str'),
- svip=dict(required=False, type='str'),
- netmask=dict(required=False, type='str'),
- gateway=dict(required=False, type='str'),
- namespace=dict(required=False, type='bool'),
- attributes=dict(required=False, type='dict'),
- address_blocks=dict(required=False, type='list', elements='dict')
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.elem = netapp_utils.create_sf_connection(module=self.module)
-
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
-
- self.elementsw_helper = NaElementSWModule(self.elem)
-
- # add telemetry attributes
- if self.parameters.get('attributes') is not None:
- self.parameters['attributes'].update(self.elementsw_helper.set_element_attributes(source='na_elementsw_vlan'))
- else:
- self.parameters['attributes'] = self.elementsw_helper.set_element_attributes(source='na_elementsw_vlan')
-
- def validate_keys(self):
- """
- Validate if all required keys are present before creating
- """
- required_keys = ['address_blocks', 'svip', 'netmask', 'name']
- if all(item in self.parameters.keys() for item in required_keys) is False:
- self.module.fail_json(msg="One or more required fields %s for creating VLAN is missing"
- % required_keys)
- addr_blk_fields = ['start', 'size']
- for address in self.parameters['address_blocks']:
- if 'start' not in address or 'size' not in address:
- self.module.fail_json(msg="One or more required fields %s for address blocks is missing"
- % addr_blk_fields)
-
- def create_network(self):
- """
- Add VLAN
- """
- try:
- self.validate_keys()
- create_params = self.parameters.copy()
- for key in ['username', 'hostname', 'password', 'state', 'vlan_tag']:
- del create_params[key]
- self.elem.add_virtual_network(virtual_network_tag=self.parameters['vlan_tag'], **create_params)
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error creating VLAN %s"
- % self.parameters['vlan_tag'],
- exception=to_native(err))
-
- def delete_network(self):
- """
- Remove VLAN
- """
- try:
- self.elem.remove_virtual_network(virtual_network_tag=self.parameters['vlan_tag'])
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error deleting VLAN %s"
- % self.parameters['vlan_tag'],
- exception=to_native(err))
-
- def modify_network(self, modify):
- """
- Modify the VLAN
- """
- try:
- self.elem.modify_virtual_network(virtual_network_tag=self.parameters['vlan_tag'], **modify)
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error modifying VLAN %s"
- % self.parameters['vlan_tag'],
- exception=to_native(err))
-
- def get_network_details(self):
- """
- Check existing VLANs
- :return: vlan details if found, None otherwise
- :type: dict
- """
- vlans = self.elem.list_virtual_networks(virtual_network_tag=self.parameters['vlan_tag'])
- vlan_details = dict()
- for vlan in vlans.virtual_networks:
- if vlan is not None:
- vlan_details['name'] = vlan.name
- vlan_details['address_blocks'] = list()
- for address in vlan.address_blocks:
- vlan_details['address_blocks'].append({
- 'start': address.start,
- 'size': address.size
- })
- vlan_details['svip'] = vlan.svip
- vlan_details['gateway'] = vlan.gateway
- vlan_details['netmask'] = vlan.netmask
- vlan_details['namespace'] = vlan.namespace
- vlan_details['attributes'] = vlan.attributes
- return vlan_details
- return None
-
- def apply(self):
- """
- Call create / delete / modify vlan methods
- """
- network = self.get_network_details()
- # calling helper to determine action
- cd_action = self.na_helper.get_cd_action(network, self.parameters)
- modify = self.na_helper.get_modified_attributes(network, self.parameters)
- if not self.module.check_mode:
- if cd_action == "create":
- self.create_network()
- elif cd_action == "delete":
- self.delete_network()
- elif modify:
- if 'attributes' in modify:
- # new attributes will replace existing ones
- modify['attributes'] = self.parameters['attributes']
- self.modify_network(modify)
- self.module.exit_json(changed=self.na_helper.changed)
-
-
-def main():
- """ Apply vlan actions """
- network_obj = ElementSWVlan()
- network_obj.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume.py
deleted file mode 100644
index 3fcaf00ce..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume.py
+++ /dev/null
@@ -1,413 +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)
-
-"""Element OS Software Volume Manager"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_volume
-
-short_description: NetApp Element Software Manage Volumes
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, destroy, or update volumes on ElementSW
-
-options:
-
- state:
- description:
- - Whether the specified volume should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- name:
- description:
- - The name of the volume to manage.
- - It accepts volume_name or volume_id
- required: true
- type: str
-
- account_id:
- description:
- - Account ID for the owner of this volume.
- - It accepts Account_id or Account_name
- required: true
- type: str
-
- enable512e:
- description:
- - Required when C(state=present)
- - Should the volume provide 512-byte sector emulation?
- type: bool
- aliases:
- - enable512emulation
-
- qos:
- description: Initial quality of service settings for this volume. Configure as dict in playbooks.
- type: dict
-
- qos_policy_name:
- description:
- - Quality of service policy for this volume.
- - It can be a name or an id.
- - Mutually exclusive with C(qos) option.
- type: str
-
- attributes:
- description: A YAML dictionary of attributes that you would like to apply on this volume.
- type: dict
-
- size:
- description:
- - The size of the volume in (size_unit).
- - Required when C(state = present).
- type: int
-
- size_unit:
- description:
- - The unit used to interpret the size parameter.
- choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
- default: 'gb'
- type: str
-
- 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']
- type: str
-'''
-
-EXAMPLES = """
- - name: Create Volume
- na_elementsw_volume:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_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
- na_elementsw_volume:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- state: present
- name: AnsibleVol
- account_id: 3
- access: readWrite
-
- - name: Delete Volume
- na_elementsw_volume:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_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.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWVolume(object):
- """
- Contains methods to parse arguments,
- derive details of ElementSW objects
- and send requests to ElementOS via
- the ElementSW SDK
- """
-
- def __init__(self):
- """
- Parse arguments, setup state variables,
- check paramenters and ensure SDK is installed
- """
- 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=False, type='str', choices=['present', 'absent'], default='present'),
- name=dict(required=True, type='str'),
- account_id=dict(required=True),
- enable512e=dict(required=False, type='bool', aliases=['enable512emulation']),
- qos=dict(required=False, type='dict', default=None),
- qos_policy_name=dict(required=False, type='str', default=None),
- attributes=dict(required=False, type='dict', 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'])
- ],
- mutually_exclusive=[
- ('qos', 'qos_policy_name'),
- ],
- supports_check_mode=True
- )
-
- param = self.module.params
-
- # set up state variables
- self.state = param['state']
- self.name = param['name']
- self.account_id = param['account_id']
- self.enable512e = param['enable512e']
- self.qos = param['qos']
- self.qos_policy_name = param['qos_policy_name']
- self.attributes = param['attributes']
- self.access = param['access']
- self.size_unit = param['size_unit']
- if param['size'] is not None:
- self.size = param['size'] * self._size_unit_map[self.size_unit]
- else:
- self.size = None
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the ElementSW Python SDK")
- else:
- try:
- self.sfe = netapp_utils.create_sf_connection(module=self.module)
- except solidfire.common.ApiServerError:
- self.module.fail_json(msg="Unable to create the connection")
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- if self.attributes is not None:
- self.attributes.update(self.elementsw_helper.set_element_attributes(source='na_elementsw_volume'))
- else:
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_volume')
-
- def get_account_id(self):
- """
- Return account id if found
- """
- try:
- # Update and return self.account_id
- self.account_id = self.elementsw_helper.account_exists(self.account_id)
- except Exception as err:
- self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err))
- return self.account_id
-
- def get_qos_policy(self, name):
- """
- Get QOS Policy
- """
- policy, error = self.elementsw_helper.get_qos_policy(name)
- if error is not None:
- self.module.fail_json(msg=error)
- return policy
-
- def get_volume(self):
- """
- Return volume details if found
- """
- # Get volume details
- volume_id = self.elementsw_helper.volume_exists(self.name, self.account_id)
-
- if volume_id is not None:
- # Return volume_details
- volume_details = self.elementsw_helper.get_volume(volume_id)
- if volume_details is not None:
- return volume_details
- return None
-
- def create_volume(self, qos_policy_id):
- """
- Create Volume
- :return: True if created, False if fails
- """
- options = dict(
- name=self.name,
- account_id=self.account_id,
- total_size=self.size,
- enable512e=self.enable512e,
- attributes=self.attributes
- )
- if qos_policy_id is not None:
- options['qos_policy_id'] = qos_policy_id
- if self.qos is not None:
- options['qos'] = self.qos
- try:
- self.sfe.create_volume(**options)
- 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, volume_id):
- """
- Delete and purge the volume using volume id
- :return: Success : True , Failed : False
- """
- try:
- self.sfe.delete_volume(volume_id=volume_id)
- self.sfe.purge_deleted_volume(volume_id=volume_id)
- # Delete method will delete and also purge the volume instead of moving the volume state to inactive.
-
- except Exception as err:
- # Throwing the exact error message instead of generic error message
- self.module.fail_json(msg='Error deleting volume: %s, %s' % (str(volume_id), to_native(err)),
- exception=to_native(err))
-
- def update_volume(self, volume_id, qos_policy_id):
- """
- Update the volume with the specified param
- :return: Success : True, Failed : False
- """
- options = dict(
- attributes=self.attributes
- )
- if self.access is not None:
- options['access'] = self.access
- if self.account_id is not None:
- options['account_id'] = self.account_id
- if self.qos is not None:
- options['qos'] = self.qos
- if qos_policy_id is not None:
- options['qos_policy_id'] = qos_policy_id
- if self.size is not None:
- options['total_size'] = self.size
- try:
- self.sfe.modify_volume(volume_id, **options)
- except Exception as err:
- # Throwing the exact error message instead of generic error message
- self.module.fail_json(msg='Error updating volume: %s, %s' % (str(volume_id), to_native(err)),
- exception=to_native(err))
-
- def apply(self):
- # Perform pre-checks, call functions and exit
- changed = False
- qos_policy_id = None
- action = None
-
- self.get_account_id()
- volume_detail = self.get_volume()
-
- if self.state == 'present' and self.qos_policy_name is not None:
- policy = self.get_qos_policy(self.qos_policy_name)
- if policy is None:
- error = 'Cannot find qos policy with name/id: %s' % self.qos_policy_name
- self.module.fail_json(msg=error)
- qos_policy_id = policy['qos_policy_id']
-
- if volume_detail:
- volume_id = volume_detail.volume_id
- if self.state == 'absent':
- action = 'delete'
-
- elif self.state == 'present':
- # Checking all the params for update operation
- if self.access is not None and volume_detail.access != self.access:
- action = 'update'
-
- if self.account_id is not None and volume_detail.account_id != self.account_id:
- action = 'update'
-
- if qos_policy_id is not None and volume_detail.qos_policy_id != qos_policy_id:
- # volume_detail.qos_policy_id may be None if no policy is associated with the volume
- action = 'update'
-
- if self.qos is not None and volume_detail.qos_policy_id is not None:
- # remove qos_policy
- action = 'update'
-
- if self.qos is not None:
- # Actual volume_detail.qos has ['burst_iops', 'burst_time', 'curve', 'max_iops', 'min_iops'] keys.
- # As only minOPS, maxOPS, burstOPS is important to consider, checking only these values.
- volume_qos = vars(volume_detail.qos)
- if volume_qos['min_iops'] != self.qos['minIOPS'] or volume_qos['max_iops'] != self.qos['maxIOPS'] \
- or volume_qos['burst_iops'] != self.qos['burstIOPS']:
- action = 'update'
-
- if self.size is not None and 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:
- action = 'update'
-
- if self.attributes is not None and volume_detail.attributes != self.attributes:
- action = 'update'
-
- elif self.state == 'present':
- action = 'create'
-
- result_message = ""
-
- if action is not None:
- changed = True
- if self.module.check_mode:
- result_message = "Check mode, skipping changes"
- else:
- if action == 'create':
- self.create_volume(qos_policy_id)
- result_message = "Volume created"
- elif action == 'update':
- self.update_volume(volume_id, qos_policy_id)
- result_message = "Volume updated"
- elif action == 'delete':
- self.delete_volume(volume_id)
- result_message = "Volume deleted"
-
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- # Create object and call apply
- na_elementsw_volume = ElementSWVolume()
- na_elementsw_volume.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_clone.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_clone.py
deleted file mode 100644
index 186ca85bc..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_clone.py
+++ /dev/null
@@ -1,276 +0,0 @@
-#!/usr/bin/python
-
-# (c) 2018, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or
-# https://www.gnu.org/licenses/gpl-3.0.txt)
-
-"""Element Software volume clone"""
-
-from __future__ import absolute_import, division, print_function
-__metaclass__ = type
-
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-
-DOCUMENTATION = '''
-
-module: na_elementsw_volume_clone
-
-short_description: NetApp Element Software Create Volume Clone
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create volume clones on Element OS
-
-options:
-
- name:
- description:
- - The name of the clone.
- required: true
- type: str
-
- src_volume_id:
- description:
- - The id of the src volume to clone. id may be a numeric identifier or a volume name.
- required: true
- type: str
-
- src_snapshot_id:
- description:
- - The id of the snapshot to clone. id may be a numeric identifier or a snapshot name.
- type: str
-
- account_id:
- description:
- - Account ID for the owner of this cloned volume. id may be a numeric identifier or an account name.
- required: true
- type: str
-
- attributes:
- description: A YAML dictionary of attributes that you would like to apply on this cloned volume.
- type: dict
-
- size:
- description:
- - The size of the cloned volume in (size_unit).
- type: int
-
- size_unit:
- description:
- - The unit used to interpret the size parameter.
- choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
- default: 'gb'
- type: str
-
- access:
- choices: ['readOnly', 'readWrite', 'locked', 'replicationTarget']
- description:
- - Access allowed for the volume.
- - If unspecified, the access settings of the clone will be the same as the source.
- - 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.
- type: str
-
-'''
-
-EXAMPLES = """
- - name: Clone Volume
- na_elementsw_volume_clone:
- hostname: "{{ elementsw_hostname }}"
- username: "{{ elementsw_username }}"
- password: "{{ elementsw_password }}"
- name: CloneAnsibleVol
- src_volume_id: 123
- src_snapshot_id: 41
- account_id: 3
- size: 1
- size_unit: gb
- access: readWrite
- attributes: {"virtual_network_id": 12345}
-
-"""
-
-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.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-
-
-class ElementOSVolumeClone(object):
- """
- Contains methods to parse arguments,
- derive details of Element Software objects
- and send requests to Element OS via
- the Solidfire SDK
- """
-
- def __init__(self):
- """
- Parse arguments, setup state variables,
- check paramenters and ensure SDK is installed
- """
- self._size_unit_map = netapp_utils.SF_BYTE_MAP
-
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- name=dict(required=True),
- src_volume_id=dict(required=True),
- src_snapshot_id=dict(),
- account_id=dict(required=True),
- attributes=dict(type='dict', 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(type='str',
- default=None, choices=['readOnly', 'readWrite',
- 'locked', 'replicationTarget']),
-
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- parameters = self.module.params
-
- # set up state variables
- self.name = parameters['name']
- self.src_volume_id = parameters['src_volume_id']
- self.src_snapshot_id = parameters['src_snapshot_id']
- self.account_id = parameters['account_id']
- self.attributes = parameters['attributes']
-
- self.size_unit = parameters['size_unit']
- if parameters['size'] is not None:
- self.size = parameters['size'] * \
- self._size_unit_map[self.size_unit]
- else:
- self.size = None
- self.access = parameters['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)
-
- self.elementsw_helper = NaElementSWModule(self.sfe)
-
- # add telemetry attributes
- if self.attributes is not None:
- self.attributes.update(self.elementsw_helper.set_element_attributes(source='na_elementsw_volume_clone'))
- else:
- self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_volume_clone')
-
- def get_account_id(self):
- """
- Return account id if found
- """
- try:
- # Update and return self.account_id
- self.account_id = self.elementsw_helper.account_exists(self.account_id)
- return self.account_id
- except Exception as err:
- self.module.fail_json(msg="Error: account_id %s does not exist" % self.account_id, exception=to_native(err))
-
- def get_snapshot_id(self):
- """
- Return snapshot details if found
- """
- src_snapshot = self.elementsw_helper.get_snapshot(self.src_snapshot_id, self.src_volume_id)
- # Update and return self.src_snapshot_id
- if src_snapshot is not None:
- self.src_snapshot_id = src_snapshot.snapshot_id
- # Return src_snapshot
- return self.src_snapshot_id
- return None
-
- def get_src_volume_id(self):
- """
- Return volume id if found
- """
- src_vol_id = self.elementsw_helper.volume_exists(self.src_volume_id, self.account_id)
- if src_vol_id is not None:
- # Update and return self.volume_id
- self.src_volume_id = src_vol_id
- # Return src_volume_id
- return self.src_volume_id
- return None
-
- def clone_volume(self):
- """Clone Volume from source"""
- try:
- self.sfe.clone_volume(volume_id=self.src_volume_id,
- name=self.name,
- new_account_id=self.account_id,
- new_size=self.size,
- access=self.access,
- snapshot_id=self.src_snapshot_id,
- attributes=self.attributes)
-
- except Exception as err:
- self.module.fail_json(msg="Error creating clone %s of size %s" % (self.name, self.size), exception=to_native(err))
-
- def apply(self):
- """Perform pre-checks, call functions and exit"""
- changed = False
- result_message = ""
-
- if self.get_account_id() is None:
- self.module.fail_json(msg="Account id not found: %s" % (self.account_id))
-
- # there is only one state. other operations
- # are part of the volume module
-
- # ensure that a volume with the clone name
- # isn't already present
- if self.elementsw_helper.volume_exists(self.name, self.account_id) is None:
- # check for the source volume
- if self.get_src_volume_id() is not None:
- # check for a valid snapshot
- if self.src_snapshot_id and not self.get_snapshot_id():
- self.module.fail_json(msg="Snapshot id not found: %s" % (self.src_snapshot_id))
- # change required
- changed = True
- else:
- self.module.fail_json(msg="Volume id not found %s" % (self.src_volume_id))
-
- if changed:
- if self.module.check_mode:
- result_message = "Check mode, skipping changes"
- else:
- self.clone_volume()
- result_message = "Volume cloned"
-
- self.module.exit_json(changed=changed, msg=result_message)
-
-
-def main():
- """Create object and call apply"""
- volume_clone = ElementOSVolumeClone()
- volume_clone.apply()
-
-
-if __name__ == '__main__':
- main()
diff --git a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_pair.py b/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_pair.py
deleted file mode 100644
index 0d5b38a0d..000000000
--- a/ansible_collections/netapp/elementsw/plugins/modules/na_elementsw_volume_pair.py
+++ /dev/null
@@ -1,293 +0,0 @@
-#!/usr/bin/python
-# (c) 2017, NetApp, Inc
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-
-from __future__ import absolute_import, division, print_function
-
-__metaclass__ = type
-
-ANSIBLE_METADATA = {'metadata_version': '1.1',
- 'status': ['preview'],
- 'supported_by': 'certified'}
-
-DOCUMENTATION = '''
-
-module: na_elementsw_volume_pair
-
-short_description: NetApp Element Software Volume Pair
-extends_documentation_fragment:
- - netapp.elementsw.netapp.solidfire
-version_added: 2.7.0
-author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
-description:
-- Create, delete volume pair
-
-options:
-
- state:
- description:
- - Whether the specified volume pair should exist or not.
- choices: ['present', 'absent']
- default: present
- type: str
-
- src_volume:
- description:
- - Source volume name or volume ID
- required: true
- type: str
-
- src_account:
- description:
- - Source account name or ID
- required: true
- type: str
-
- dest_volume:
- description:
- - Destination volume name or volume ID
- required: true
- type: str
-
- dest_account:
- description:
- - Destination account name or ID
- required: true
- type: str
-
- mode:
- description:
- - Mode to start the volume pairing
- choices: ['async', 'sync', 'snapshotsonly']
- default: async
- type: str
-
- dest_mvip:
- description:
- - Destination IP address of the paired cluster.
- required: true
- type: str
-
- dest_username:
- description:
- - Destination username for the paired cluster
- - Optional if this is same as source cluster username.
- type: str
-
- dest_password:
- description:
- - Destination password for the paired cluster
- - Optional if this is same as source cluster password.
- type: str
-
-'''
-
-EXAMPLES = """
- - name: Create volume pair
- na_elementsw_volume_pair:
- hostname: "{{ src_cluster_hostname }}"
- username: "{{ src_cluster_username }}"
- password: "{{ src_cluster_password }}"
- state: present
- src_volume: test1
- src_account: test2
- dest_volume: test3
- dest_account: test4
- mode: sync
- dest_mvip: "{{ dest_cluster_hostname }}"
-
- - name: Delete volume pair
- na_elementsw_volume_pair:
- hostname: "{{ src_cluster_hostname }}"
- username: "{{ src_cluster_username }}"
- password: "{{ src_cluster_password }}"
- state: absent
- src_volume: 3
- src_account: 1
- dest_volume: 2
- dest_account: 1
- dest_mvip: "{{ dest_cluster_hostname }}"
- dest_username: "{{ dest_cluster_username }}"
- dest_password: "{{ dest_cluster_password }}"
-
-"""
-
-RETURN = """
-
-"""
-
-from ansible.module_utils.basic import AnsibleModule
-from ansible.module_utils._text import to_native
-import ansible_collections.netapp.elementsw.plugins.module_utils.netapp as netapp_utils
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_elementsw_module import NaElementSWModule
-from ansible_collections.netapp.elementsw.plugins.module_utils.netapp_module import NetAppModule
-
-HAS_SF_SDK = netapp_utils.has_sf_sdk()
-try:
- import solidfire.common
-except ImportError:
- HAS_SF_SDK = False
-
-
-class ElementSWVolumePair(object):
- ''' class to handle volume pairing operations '''
-
- def __init__(self):
- """
- Setup Ansible parameters and SolidFire connection
- """
- self.argument_spec = netapp_utils.ontap_sf_host_argument_spec()
- self.argument_spec.update(dict(
- state=dict(required=False, choices=['present', 'absent'],
- default='present'),
- src_volume=dict(required=True, type='str'),
- src_account=dict(required=True, type='str'),
- dest_volume=dict(required=True, type='str'),
- dest_account=dict(required=True, type='str'),
- mode=dict(required=False, type='str',
- choices=['async', 'sync', 'snapshotsonly'],
- default='async'),
- dest_mvip=dict(required=True, type='str'),
- dest_username=dict(required=False, type='str'),
- dest_password=dict(required=False, type='str', no_log=True)
- ))
-
- self.module = AnsibleModule(
- argument_spec=self.argument_spec,
- supports_check_mode=True
- )
-
- if HAS_SF_SDK is False:
- self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
- else:
- self.elem = netapp_utils.create_sf_connection(module=self.module)
-
- self.elementsw_helper = NaElementSWModule(self.elem)
- self.na_helper = NetAppModule()
- self.parameters = self.na_helper.set_parameters(self.module.params)
- # get element_sw_connection for destination cluster
- # overwrite existing source host, user and password with destination credentials
- self.module.params['hostname'] = self.parameters['dest_mvip']
- # username and password is same as source,
- # if dest_username and dest_password aren't specified
- if self.parameters.get('dest_username'):
- self.module.params['username'] = self.parameters['dest_username']
- if self.parameters.get('dest_password'):
- self.module.params['password'] = self.parameters['dest_password']
- self.dest_elem = netapp_utils.create_sf_connection(module=self.module)
- self.dest_elementsw_helper = NaElementSWModule(self.dest_elem)
-
- def check_if_already_paired(self, vol_id):
- """
- Check for idempotency
- A volume can have only one pair
- Return paired-volume-id if volume is paired already
- None if volume is not paired
- """
- paired_volumes = self.elem.list_volumes(volume_ids=[vol_id],
- is_paired=True)
- for vol in paired_volumes.volumes:
- for pair in vol.volume_pairs:
- if pair is not None:
- return pair.remote_volume_id
- return None
-
- def pair_volumes(self):
- """
- Start volume pairing on source, and complete on target volume
- """
- try:
- pair_key = self.elem.start_volume_pairing(
- volume_id=self.parameters['src_vol_id'],
- mode=self.parameters['mode'])
- self.dest_elem.complete_volume_pairing(
- volume_pairing_key=pair_key.volume_pairing_key,
- volume_id=self.parameters['dest_vol_id'])
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error pairing volume id %s"
- % (self.parameters['src_vol_id']),
- exception=to_native(err))
-
- def pairing_exists(self, src_id, dest_id):
- src_paired = self.check_if_already_paired(self.parameters['src_vol_id'])
- dest_paired = self.check_if_already_paired(self.parameters['dest_vol_id'])
- if src_paired is not None or dest_paired is not None:
- return True
- return None
-
- def unpair_volumes(self):
- """
- Delete volume pair
- """
- try:
- self.elem.remove_volume_pair(volume_id=self.parameters['src_vol_id'])
- self.dest_elem.remove_volume_pair(volume_id=self.parameters['dest_vol_id'])
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error unpairing volume ids %s and %s"
- % (self.parameters['src_vol_id'],
- self.parameters['dest_vol_id']),
- exception=to_native(err))
-
- def get_account_id(self, account, type):
- """
- Get source and destination account IDs
- """
- try:
- if type == 'src':
- self.parameters['src_account_id'] = self.elementsw_helper.account_exists(account)
- elif type == 'dest':
- self.parameters['dest_account_id'] = self.dest_elementsw_helper.account_exists(account)
- except solidfire.common.ApiServerError as err:
- self.module.fail_json(msg="Error: either account %s or %s does not exist"
- % (self.parameters['src_account'],
- self.parameters['dest_account']),
- exception=to_native(err))
-
- def get_volume_id(self, volume, type):
- """
- Get source and destination volume IDs
- """
- if type == 'src':
- self.parameters['src_vol_id'] = self.elementsw_helper.volume_exists(volume, self.parameters['src_account_id'])
- if self.parameters['src_vol_id'] is None:
- self.module.fail_json(msg="Error: source volume %s does not exist"
- % (self.parameters['src_volume']))
- elif type == 'dest':
- self.parameters['dest_vol_id'] = self.dest_elementsw_helper.volume_exists(volume, self.parameters['dest_account_id'])
- if self.parameters['dest_vol_id'] is None:
- self.module.fail_json(msg="Error: destination volume %s does not exist"
- % (self.parameters['dest_volume']))
-
- def get_ids(self):
- """
- Get IDs for volumes and accounts
- """
- self.get_account_id(self.parameters['src_account'], 'src')
- self.get_account_id(self.parameters['dest_account'], 'dest')
- self.get_volume_id(self.parameters['src_volume'], 'src')
- self.get_volume_id(self.parameters['dest_volume'], 'dest')
-
- def apply(self):
- """
- Call create / delete volume pair methods
- """
- self.get_ids()
- paired = self.pairing_exists(self.parameters['src_vol_id'],
- self.parameters['dest_vol_id'])
- # calling helper to determine action
- cd_action = self.na_helper.get_cd_action(paired, self.parameters)
- if cd_action == "create":
- self.pair_volumes()
- elif cd_action == "delete":
- self.unpair_volumes()
- self.module.exit_json(changed=self.na_helper.changed)
-
-
-def main():
- """ Apply volume pair actions """
- vol_obj = ElementSWVolumePair()
- vol_obj.apply()
-
-
-if __name__ == '__main__':
- main()