diff --git a/lib/ansible/modules/storage/netapp/na_elementsw_access_group.py b/lib/ansible/modules/storage/netapp/na_elementsw_access_group.py deleted file mode 100644 index 2a1e8c7fee0..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_access_group.py +++ /dev/null @@ -1,378 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, destroy, or update access groups on Element Software Cluster. - -options: - - state: - description: - - Whether the specified access group should exist or not. - required: true - choices: ['present', 'absent'] - - 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' - - name: - description: - - Name for the access group for create, modify and delete operations. - required: True - aliases: - - src_access_group_id - - initiators: - description: - - List of initiators to include in the access group. If unspecified, the access group will start out without configured initiators. - - volumes: - description: - - List of volumes to initially include in the volume access group. If unspecified, the access group will start without any volumes. - - It accepts either volume_name or volume_id - - 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' - - virtual_network_id: - description: - - The ID of the Element SW Software Cluster Virtual Network ID to associate the access group with. - - virtual_network_tags: - description: - - The ID of the VLAN Virtual Network Tag to associate the access group with. - - attributes: - description: List of Name/Value pairs in JSON object format. - -''' - -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.module_utils.netapp as netapp_utils -from ansible.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=True, choices=['present', 'absent']), - from_name=dict(required=False, type='str'), - name=dict(required=True, aliases=["src_access_group_id"], type='str'), - initiators=dict(required=False, type='list'), - volumes=dict(required=False, type='list'), - account_id=dict(required=False, type='str'), - virtual_network_id=dict(required=False, type='list'), - virtual_network_tags=dict(required=False, type='list'), - 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_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, 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 - update_group = False - - 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_id() - 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": - self.delete_access_group() - changed = True - else: - # If state - present, check for any parameter of existing 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 - update_group = True - changed = True - else: - for volumeID in group_detail.volumes: - if volumeID not in self.volumes: - update_group = True - changed = True - break - - elif self.initiators is not None and group_detail.initiators != self.initiators: - update_group = True - changed = True - - elif self.virtual_network_id is not None or self.virtual_network_tags is not None: - update_group = True - changed = True - - if update_group: - self.update_access_group() - - 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 - self.rename_access_group() - 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. - self.create_access_group() - changed = True - - 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/lib/ansible/modules/storage/netapp/na_elementsw_account.py b/lib/ansible/modules/storage/netapp/na_elementsw_account.py deleted file mode 100644 index 7dcd2f76012..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_account.py +++ /dev/null @@ -1,333 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, destroy, or update accounts on Element SW - -options: - - state: - description: - - Whether the specified account should exist or not. - required: true - choices: ['present', 'absent'] - - element_username: - description: - - Unique username for this account. (May be 1 to 64 characters in length). - required: true - aliases: - - account_id - - 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' - - initiator_secret: - description: - - CHAP secret to use for the initiator. Should be 12-16 characters long and impenetrable. - - The CHAP initiator secrets must be unique and cannot be the same as the target CHAP secret. - - If not specified, a random secret is created. - - target_secret: - description: - - CHAP secret to use for the target (mutual CHAP authentication). - - Should be 12-16 characters long and impenetrable. - - The CHAP target secrets must be unique and cannot be the same as the initiator CHAP secret. - - If not specified, a random secret is created. - - attributes: - description: List of Name/Value pairs in JSON object format. - - status: - description: - - Status of the account. - -''' - -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.module_utils.netapp as netapp_utils -from ansible.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=True, choices=['present', 'absent']), - element_username=dict(required=True, aliases=["account_id"], type='str'), - from_name=dict(required=False, default=None), - initiator_secret=dict(required=False, type='str'), - target_secret=dict(required=False, type='str'), - attributes=dict(required=False, type='dict'), - status=dict(required=False, type='str'), - )) - - self.module = AnsibleModule( - argument_spec=self.argument_spec, - supports_check_mode=True - ) - - 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 existing 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/lib/ansible/modules/storage/netapp/na_elementsw_admin_users.py b/lib/ansible/modules/storage/netapp/na_elementsw_admin_users.py deleted file mode 100644 index a0e05c83a2f..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_admin_users.py +++ /dev/null @@ -1,229 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, destroy, or update admin users on SolidFire - -options: - - state: - description: - - Whether the specified account should exist or not. - required: true - choices: ['present', 'absent'] - - element_username: - description: - - Unique username for this account. (May be 1 to 64 characters in length). - required: true - - 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 - - acceptEula: - description: - - Boolean, true for accepting Eula, False Eula - type: bool - - access: - description: - - A list of type the admin has access to -''' - -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 = """ - -""" - -import traceback - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native -import ansible.module_utils.netapp as netapp_utils -from ansible.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=True, choices=['present', 'absent']), - 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') - )) - - 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: - 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: - self.add_admin_user() - changed = True - else: - if self.does_admin_user_exist(): - 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/lib/ansible/modules/storage/netapp/na_elementsw_backup.py b/lib/ansible/modules/storage/netapp/na_elementsw_backup.py deleted file mode 100644 index 74b5b02508d..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_backup.py +++ /dev/null @@ -1,235 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create backup - -options: - - src_volume_id: - description: - - ID of the backup source volume. - required: true - aliases: - - volume_id - - dest_hostname: - description: - - hostname for the backup source cluster - - will be set equal to hostname if not specified - required: false - - dest_username: - description: - - username for the backup destination cluster - - will be set equal to username if not specified - required: false - - dest_password: - description: - - password for the backup destination cluster - - will be set equal to password if not specified - required: false - - dest_volume_id: - description: - - ID of the backup destination volume - required: true - - format: - description: - - Backup format to use - choices: ['native','uncompressed'] - required: false - default: 'native' - - script: - description: - - the backup script to be executed - required: false - - script_parameters: - description: - - the backup script parameters - required: false - -''' - -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.module_utils.netapp as netapp_utils -from ansible.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/lib/ansible/modules/storage/netapp/na_elementsw_check_connections.py b/lib/ansible/modules/storage/netapp/na_elementsw_check_connections.py deleted file mode 100644 index f51c5fea7e1..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_check_connections.py +++ /dev/null @@ -1,152 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Used to test the management connection to the cluster. -- The test pings the MVIP and SVIP, and executes a simple API method to verify connectivity. - -options: - - skip: - description: - - Skip checking connection to SVIP or MVIP. - choices: ['svip', 'mvip'] - - mvip: - description: - - Optionally, use to test connection of a different MVIP. - - This is not needed to test the connection to the target cluster. - - svip: - description: - - Optionally, use to test connection of a different SVIP. - - This is not needed to test the connection to the target cluster. - -''' - - -EXAMPLES = """ - - name: Check connections to MVIP and SVIP - na_elementsw_check_connections: - hostname: "{{ solidfire_hostname }}" - username: "{{ solidfire_username }}" - password: "{{ solidfire_password }}" -""" - -RETURN = """ - -""" -import traceback - -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils._text import to_native -import ansible.module_utils.netapp as netapp_utils -from ansible.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/lib/ansible/modules/storage/netapp/na_elementsw_cluster.py b/lib/ansible/modules/storage/netapp/na_elementsw_cluster.py deleted file mode 100644 index 89a58ccb4b1..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_cluster.py +++ /dev/null @@ -1,222 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Initialize Element Software node ownership to form a cluster. - -options: - management_virtual_ip: - description: - - Floating (virtual) IP address for the cluster on the management network. - required: true - - storage_virtual_ip: - description: - - Floating (virtual) IP address for the cluster on the storage (iSCSI) network. - required: true - - replica_count: - description: - - Number of replicas of each piece of data to store in the cluster. - default: '2' - - cluster_admin_username: - description: - - Username for the cluster admin. - - If not provided, default to login username. - - cluster_admin_password: - description: - - Initial password for the cluster admin account. - - If not provided, default to login password. - - 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 - - attributes: - description: - - List of name-value pairs in JSON object format. -''' - -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.module_utils.netapp as netapp_utils -from ansible.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='str', 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), - attributes=dict(required=False, type='dict', default=None) - )) - - 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.get('cluster_admin_username') - self.cluster_admin_password = input_params.get('cluster_admin_password') - - 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_cluster')) - else: - self.attributes = self.elementsw_helper.set_element_attributes(source='na_elementsw_cluster') - - 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 - } - if self.cluster_admin_username is not None: - options['username'] = self.cluster_admin_username - if self.cluster_admin_password is not None: - options['password'] = self.cluster_admin_password - try: - self.sfe.create_cluster(**options) - except Exception as exception_object: - self.module.fail_json(msg='Error create cluster %s' % (to_native(exception_object)), - exception=traceback.format_exc()) - - def check_connection(self): - """ - Check connections to mvip, svip address. - :description: To test connection to given IP addressed for mvip and svip - - :rtype: bool - """ - try: - mvip_test = self.sfe.test_connect_mvip(mvip=self.management_virtual_ip) - svip_test = self.sfe.test_connect_svip(svip=self.storage_virtual_ip) - - if mvip_test.details.connected and svip_test.details.connected: - return True - else: - return False - except Exception as e: - return False - - def apply(self): - """ - Check connection and initialize node with cluster ownership - """ - changed = False - result_message = None - if self.module.supports_check_mode and self.accept_eula: - if self.check_connection(): - self.create_cluster() - changed = True - else: - self.module.fail_json(msg='Error connecting mvip and svip address') - else: - result_message = "Skipping changes, No change requested" - self.module.exit_json(changed=changed, msg=result_message) - - -def main(): - """ - Main function - """ - na_elementsw_cluster = ElementSWCluster() - na_elementsw_cluster.apply() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/storage/netapp/na_elementsw_cluster_config.py b/lib/ansible/modules/storage/netapp/na_elementsw_cluster_config.py deleted file mode 100644 index 64142c254d9..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_cluster_config.py +++ /dev/null @@ -1,327 +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.solidfire -version_added: '2.8' -author: NetApp Ansible Team (@carchi8py) -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 - - max_metadata_over_provision_factor: - description: - - The number of times metadata space can be overprovisioned relative to the amount of space available - - stage2_aware_threshold: - description: - - The number of nodes of capacity remaining in the cluster before the system triggers a notification - - encryption_at_rest: - description: - - enable or disable the Advanced Encryption Standard (AES) 256-bit encryption at rest on the cluster - choices: ['present', 'absent'] - - 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 - - broadcastclient: - type: bool - default: False - description: - - Enables every node in the cluster as a broadcast client - - 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.module_utils.netapp as netapp_utils -from ansible.module_utils.netapp_elementsw_module import NaElementSWModule -from ansible.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') - ) - ), - 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 functionality. - 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/lib/ansible/modules/storage/netapp/na_elementsw_cluster_pair.py b/lib/ansible/modules/storage/netapp/na_elementsw_cluster_pair.py deleted file mode 100644 index d4f733d2f08..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_cluster_pair.py +++ /dev/null @@ -1,202 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, delete cluster pair - -options: - - state: - description: - - Whether the specified cluster pair should exist or not. - choices: ['present', 'absent'] - default: present - - dest_mvip: - description: - - Destination IP address of the cluster to be paired. - required: true - - dest_username: - description: - - Destination username for the cluster to be paired. - - Optional if this is same as source cluster username. - - dest_password: - description: - - Destination password for the cluster to be paired. - - Optional if this is same as source cluster password. - -''' - -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.module_utils.netapp as netapp_utils -from ansible.module_utils.netapp_elementsw_module import NaElementSWModule -from ansible.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/lib/ansible/modules/storage/netapp/na_elementsw_cluster_snmp.py b/lib/ansible/modules/storage/netapp/na_elementsw_cluster_snmp.py deleted file mode 100644 index ecce3eb6612..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_cluster_snmp.py +++ /dev/null @@ -1,354 +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.solidfire -version_added: '2.8' -author: NetApp Ansible Team (@carchi8py) -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' - - 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'] - 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. - community: - description: - - SNMP community string. - 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. - - 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'] - name: - description: - - The name of the user. Must contain at least one character, but no more than 32 characters. - - Blank spaces are not allowed. - 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.' - 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.' - secLevel: - description: - - To define the security level of a user. - choices: ['noauth', 'auth', 'priv'] - -''' - -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.module_utils.netapp as netapp_utils -from ansible.module_utils.netapp_elementsw_module import NaElementSWModule -from ansible.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), - passphrase=dict(type='str', default=None), - 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.message), - 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/lib/ansible/modules/storage/netapp/na_elementsw_drive.py b/lib/ansible/modules/storage/netapp/na_elementsw_drive.py deleted file mode 100644 index c7db7895fe5..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_drive.py +++ /dev/null @@ -1,321 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: - - Add, Erase or Remove drive for nodes on Element Software Cluster. - -options: - drive_id: - description: - - Drive ID or Serial Name of Node drive. - - If not specified, add and remove action will be performed on all drives of node_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' - - node_id: - description: - - ID or Name of cluster node. - required: true - - force_during_upgrade: - description: - - Flag to force drive operation during upgrade. - type: 'bool' - - force_during_bin_sync: - description: - - Flag to force during a bin sync operation. - type: 'bool' -''' - -EXAMPLES = """ - - name: Add drive with status available to cluster - tags: - - elementsw_add_drive - na_element_drive: - hostname: "{{ elementsw_hostname }}" - username: "{{ elementsw_username }}" - password: "{{ elementsw_password }}" - state: present - drive_id: scsi-SATA_SAMSUNG_MZ7LM48S2UJNX0J3221807 - force_during_upgrade: false - force_during_bin_sync: false - node_id: sf4805-meg-03 - - - name: Remove active drive from cluster - tags: - - elementsw_remove_drive - na_element_drive: - hostname: "{{ elementsw_hostname }}" - username: "{{ elementsw_username }}" - password: "{{ elementsw_password }}" - state: absent - force_during_upgrade: false - node_id: sf4805-meg-03 - drive_id: 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_id: scsi-SATA_SAMSUNG_MZ7LM48S2UJNX0J432109 - node_id: sf4805-meg-03 - - - name: Add all the drives of a node 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 - node_id: sf4805-meg-03 - -""" - - -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.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_id=dict(required=False, type='str'), - node_id=dict(required=True, type='str'), - 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_id = input_params['drive_id'] - self.node_id = input_params['node_id'] - self.force_during_upgrade = input_params['force_during_upgrade'] - self.force_during_bin_sync = input_params['force_during_bin_sync'] - - 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_node_id(self): - """ - 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.node_id is not None: - list_nodes = self.sfe.list_active_nodes() - for current_node in list_nodes.nodes: - if self.node_id == str(current_node.node_id): - return current_node.node_id - elif current_node.name == self.node_id: - self.node_id = current_node.node_id - return current_node.node_id - self.node_id = None - return self.node_id - - def get_drives_listby_status(self): - """ - 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 - """ - if self.node_id is not None: - list_drives = self.sfe.list_drives() - for drive in list_drives.drives: - if drive.node_id == self.node_id: - 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 - return None - - def get_active_drives(self, drive_id=None): - """ - return a list of active drives - if drive_id is specified, only [] or [drive_id] is returned - else all available drives for this node are returned - """ - action_list = list() - if self.drive_id is not None: - if self.drive_id in self.active_drives.values(): - action_list.append(int(self.drive_id)) - if self.drive_id in self.active_drives: - action_list.append(self.active_drives[self.drive_id]) - else: - action_list.extend(self.active_drives.values()) - - return action_list - - def get_available_drives(self, drive_id=None): - """ - return a list of available drives (not active) - if drive_id is specified, only [] or [drive_id] is returned - else all available drives for this node are returned - """ - action_list = list() - if self.drive_id is not None: - if self.drive_id in self.available_drives.values(): - action_list.append(int(self.drive_id)) - if self.drive_id in self.available_drives: - action_list.append(self.available_drives[self.drive_id]) - else: - action_list.extend(self.available_drives.values()) - - return action_list - - def add_drive(self, drives=None): - """ - Add Drive available for Cluster storage expansion - """ - try: - self.sfe.add_drives(drives, - force_during_upgrade=self.force_during_upgrade, - force_during_bin_sync=self.force_during_bin_sync) - except Exception as exception_object: - self.module.fail_json(msg='Error add drive to cluster %s' % (to_native(exception_object)), - exception=traceback.format_exc()) - - def remove_drive(self, drives=None): - """ - Remove Drive active in Cluster - """ - try: - self.sfe.remove_drives(drives, - force_during_upgrade=self.force_during_upgrade) - except Exception as exception_object: - self.module.fail_json(msg='Error remove drive from cluster %s' % (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 clean data from drive %s' % (to_native(exception_object)), - exception=traceback.format_exc()) - - def apply(self): - """ - Check, process and initiate Drive operation - """ - changed = False - result_message = None - self.active_drives = {} - self.available_drives = {} - action_list = [] - self.get_node_id() - self.get_drives_listby_status() - - if self.module.check_mode is False and self.node_id is not None: - if self.state == "present": - action_list = self.get_available_drives(self.drive_id) - if len(action_list) > 0: - self.add_drive(action_list) - changed = True - elif self.drive_id is not None and (self.drive_id in self.active_drives.values() or self.drive_id in self.active_drives): - changed = False # No action, so setting changed to false - elif self.drive_id is None and len(self.active_drives) > 0: - changed = False # No action, so setting changed to false - else: - self.module.fail_json(msg='Error - no drive(s) in available state on node to be included in cluster') - - elif self.state == "absent": - action_list = self.get_active_drives(self.drive_id) - if len(action_list) > 0: - self.remove_drive(action_list) - changed = True - - elif self.state == "clean": - action_list = self.get_available_drives(self.drive_id) - if len(action_list) > 0: - self.secure_erase(action_list) - changed = True - else: - self.module.fail_json(msg='Error - no drive(s) in available state on node to be cleaned') - - else: - result_message = "Skipping changes, No change requested" - self.module.exit_json(changed=changed, msg=result_message) - - -def main(): - """ - Main function - """ - - na_elementsw_drive = ElementSWDrive() - na_elementsw_drive.apply() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/storage/netapp/na_elementsw_initiators.py b/lib/ansible/modules/storage/netapp/na_elementsw_initiators.py deleted file mode 100644 index 614ad26e914..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_initiators.py +++ /dev/null @@ -1,317 +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.solidfire -version_added: '2.8' -author: NetApp Ansible Team (@carchi8py) -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. - - alias: - description: The friendly name assigned to this initiator. - - initiator_id: - description: The numeric ID of the initiator. - - volume_access_groups: - description: A list of volumeAccessGroupIDs to which this initiator belongs. - - attributes: - description: A set of JSON attributes to assign to this initiator. - - state: - description: - - Whether the specified initiator should exist or not. - choices: ['present', 'absent'] - default: present -''' - -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_groups: - - 1 - - 2 - attributes: {"key": "value"} - - name: b - alias: b2 - initiator_id: 2 - volume_access_groups: - - 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.module_utils.netapp as netapp_utils -from ansible.module_utils.netapp_elementsw_module import NaElementSWModule -from ansible.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', - options=dict( - name=dict(type='str', required=True), - alias=dict(type='str', default=None), - initiator_id=dict(type='int', default=None), - volume_access_groups=dict(type='list', 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) - - 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: - 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 - for param in user_initiator: - # lookup initiator_name instead of name - if param == 'name': - if user_initiator['name'] == existing_initiator['initiator_name']: - pass - elif user_initiator[param] == existing_initiator[param]: - pass - else: - return True - return False - - 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) - 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 - - def create_initiators(self, initiator): - """ - create initiators - """ - # create_initiators needs an array - # so enclose this initiator in 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_initiators(self, initiator): - """ - delete initiators - """ - # delete_initiators needs an array - # so enclose this initiator in 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_initiators(self, initiator, existing_initiator): - """ - modify initiators - """ - # create the new initiator dict - # by merging old and new values - merged_initiator = existing_initiator.copy() - 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 - modify = None - 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_initiators(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_initiators(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_initiators(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_initiators(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_initiators(in_initiator['initiator_id']) - 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_initiators = ElementSWInitiators() - na_elementsw_initiators.apply() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/storage/netapp/na_elementsw_ldap.py b/lib/ansible/modules/storage/netapp/na_elementsw_ldap.py deleted file mode 100644 index 58b87b7da88..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_ldap.py +++ /dev/null @@ -1,243 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Enable, disable ldap, and add ldap users - -options: - - state: - description: - - Whether the specified volume should exist or not. - required: true - choices: ['present', 'absent'] - - authType: - description: - - Identifies which user authentication method to use. - choices: ['DirectBind', 'SearchAndBind'] - - groupSearchBaseDn: - description: - - The base DN of the tree to start the group search (will do a subtree search from here) - - groupSearchType: - description: - - Controls the default group search filter used - choices: ['NoGroup', 'ActiveDirectory', 'MemberDN'] - - serverURIs: - description: - - A comma-separated list of LDAP server URIs - - userSearchBaseDN: - description: - - The base DN of the tree to start the search (will do a subtree search from here) - - 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). - - searchBindPassword: - description: - - The password for the searchBindDN account used for searching - - userSearchFilter: - description: - - the LDAP Filter to use - - userDNTemplate: - description: - - A string that is used form a fully qualified user DN. - - groupSearchCustomFilter: - description: - - For use with the CustomFilter Search type -''' - -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.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(type='str', required=True, choices=['absent', '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: %s' % (self.account_id, 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/lib/ansible/modules/storage/netapp/na_elementsw_network_interfaces.py b/lib/ansible/modules/storage/netapp/na_elementsw_network_interfaces.py deleted file mode 100644 index e855e1e1608..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_network_interfaces.py +++ /dev/null @@ -1,297 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Configure Element SW Node Network Interfaces for Bond 1G and 10G IP address. - -options: - method: - description: - - Type of Method used to configure the interface. - - method depends on other settings such as the use of a static IP address, which will change the method to static. - - loopback - Used to define the IPv4 loopback interface. - - manual - Used to define interfaces for which no configuration is done by default. - - dhcp - May be used to obtain an IP address via DHCP. - - static - Used to define Ethernet interfaces with statically allocated IPv4 addresses. - choices: ['loopback', 'manual', 'dhcp', 'static'] - required: true - - ip_address_1g: - description: - - IP address for the 1G network. - required: true - - ip_address_10g: - description: - - IP address for the 10G network. - required: true - - subnet_1g: - description: - - 1GbE Subnet Mask. - required: true - - subnet_10g: - description: - - 10GbE Subnet Mask. - required: true - - gateway_address_1g: - description: - - Router network address to send packets out of the local network. - required: true - - gateway_address_10g: - description: - - Router network address to send packets out of the local network. - required: true - - mtu_1g: - description: - - Maximum Transmission Unit for 1GbE, Largest packet size that a network protocol can transmit. - - Must be greater than or equal to 1500 bytes. - default: '1500' - - mtu_10g: - description: - - Maximum Transmission Unit for 10GbE, Largest packet size that a network protocol can transmit. - - Must be greater than or equal to 1500 bytes. - default: '1500' - - dns_nameservers: - description: - - List of addresses for domain name servers. - - dns_search_domains: - description: - - List of DNS search domains. - - bond_mode_1g: - description: - - Bond mode for 1GbE configuration. - choices: ['ActivePassive', 'ALB', 'LACP'] - default: 'ActivePassive' - - bond_mode_10g: - description: - - Bond mode for 10GbE configuration. - choices: ['ActivePassive', 'ALB', 'LACP'] - default: 'ActivePassive' - - lacp_1g: - 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'] - default: 'Slow' - - lacp_10g: - 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'] - default: 'Slow' - - virtual_network_tag: - description: - - This is the primary network tag. All nodes in a cluster have the same VLAN tag. - -''' - -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 }}" - method: static - ip_address_1g: 10.226.109.68 - ip_address_10g: 10.226.201.72 - subnet_1g: 255.255.255.0 - subnet_10g: 255.255.255.0 - gateway_address_1g: 10.193.139.1 - gateway_address_10g: 10.193.140.1 - mtu_1g: 1500 - mtu_10g: 9000 - bond_mode_1g: ActivePassive - bond_mode_10g: LACP - lacp_10g: Fast -""" - -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.module_utils.netapp as netapp_utils - -HAS_SF_SDK = netapp_utils.has_sf_sdk() - -try: - from solidfire.models import Network, NetworkConfig - HAS_SF_SDK = True -except Exception: - 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( - method=dict(type='str', required=True, choices=['loopback', 'manual', 'dhcp', 'static']), - ip_address_1g=dict(type='str', required=True), - ip_address_10g=dict(type='str', required=True), - subnet_1g=dict(type='str', required=True), - subnet_10g=dict(type='str', required=True), - gateway_address_1g=dict(type='str', required=True), - gateway_address_10g=dict(type='str', required=True), - mtu_1g=dict(type='str', default='1500'), - mtu_10g=dict(type='str', default='1500'), - dns_nameservers=dict(type='list'), - dns_search_domains=dict(type='list'), - bond_mode_1g=dict(type='str', default='ActivePassive', choices=['ActivePassive', 'ALB', 'LACP']), - bond_mode_10g=dict(type='str', default='ActivePassive', choices=['ActivePassive', 'ALB', 'LACP']), - lacp_1g=dict(type='str', default='Slow', choices=['Fast', 'Slow']), - lacp_10g=dict(type='str', default='Slow', choices=['Fast', 'Slow']), - virtual_network_tag=dict(type='str'), - ) - - self.module = AnsibleModule( - argument_spec=self.argument_spec, - supports_check_mode=True, - ) - - input_params = self.module.params - - self.method = input_params['method'] - self.ip_address_1g = input_params['ip_address_1g'] - self.ip_address_10g = input_params['ip_address_10g'] - self.subnet_1g = input_params['subnet_1g'] - self.subnet_10g = input_params['subnet_10g'] - self.gateway_address_1g = input_params['gateway_address_1g'] - self.gateway_address_10g = input_params['gateway_address_10g'] - self.mtu_1g = input_params['mtu_1g'] - self.mtu_10g = input_params['mtu_10g'] - self.dns_nameservers = input_params['dns_nameservers'] - self.dns_search_domains = input_params['dns_search_domains'] - self.bond_mode_1g = input_params['bond_mode_1g'] - self.bond_mode_10g = input_params['bond_mode_10g'] - self.lacp_1g = input_params['lacp_1g'] - self.lacp_10g = input_params['lacp_10g'] - self.virtual_network_tag = input_params['virtual_network_tag'] - - 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, port=442) - - def set_network_config(self): - """ - set network configuration - """ - try: - self.sfe.set_network_config(network=self.network_object) - except Exception as exception_object: - self.module.fail_json(msg='Error network setting for node %s' % (to_native(exception_object)), - exception=traceback.format_exc()) - - def get_network_params_object(self): - """ - Get Element SW Network object - :description: get Network object - - :return: NetworkConfig object - :rtype: object(NetworkConfig object) - """ - try: - bond_1g_network = NetworkConfig(method=self.method, - address=self.ip_address_1g, - netmask=self.subnet_1g, - gateway=self.gateway_address_1g, - mtu=self.mtu_1g, - dns_nameservers=self.dns_nameservers, - dns_search=self.dns_search_domains, - bond_mode=self.bond_mode_1g, - bond_lacp_rate=self.lacp_1g, - virtual_network_tag=self.virtual_network_tag) - bond_10g_network = NetworkConfig(method=self.method, - address=self.ip_address_10g, - netmask=self.subnet_10g, - gateway=self.gateway_address_10g, - mtu=self.mtu_10g, - dns_nameservers=self.dns_nameservers, - dns_search=self.dns_search_domains, - bond_mode=self.bond_mode_10g, - bond_lacp_rate=self.lacp_10g, - virtual_network_tag=self.virtual_network_tag) - network_object = Network(bond1_g=bond_1g_network, - bond10_g=bond_10g_network) - return network_object - except Exception as e: - self.module.fail_json(msg='Error with setting up network object for node 1G and 10G configuration : %s' % to_native(e), - exception=to_native(e)) - - def apply(self): - """ - Check connection and initialize node with cluster ownership - """ - changed = False - result_message = None - self.network_object = self.get_network_params_object() - if self.network_object is not None: - self.set_network_config() - 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/lib/ansible/modules/storage/netapp/na_elementsw_node.py b/lib/ansible/modules/storage/netapp/na_elementsw_node.py deleted file mode 100644 index 28487501903..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_node.py +++ /dev/null @@ -1,239 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: - - Add, remove cluster node on Element Software Cluster. - -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' - - node_id: - description: - - List of IDs or Names or IP Address of nodes from cluster used for operation. - required: true - -''' - -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 -""" - - -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.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_id=dict(required=True, type='list'), - )) - - self.module = AnsibleModule( - argument_spec=self.argument_spec, - supports_check_mode=True - ) - - input_params = self.module.params - - self.state = input_params['state'] - self.node_id = input_params['node_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) - - 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 - - def get_node_list(self): - """ - Get Node List - :description: Find and retrieve node_id from the active cluster - - :return: None - :rtype: None - """ - if len(self.node_id) > 0: - unprocessed_node_list = self.node_id - list_nodes = [] - all_nodes = self.sfe.list_all_nodes() - # 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 \ - (current_node.node_id in self.node_id or current_node.name in self.node_id or current_node.mip in self.node_id): - 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: - self.action_nodes_list.append(current_node.node_id) - if self.state == "present" and \ - (current_node.pending_node_id in self.node_id or current_node.name in self.node_id or current_node.mip in self.node_id): - self.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 current_node.node_id in unprocessed_node_list: - unprocessed_node_list.remove(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 current_node.pending_node_id in unprocessed_node_list: - unprocessed_node_list.remove(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) - if len(unprocessed_node_list) > 0: - self.module.fail_json(msg='Error adding node %s: node not in pending or active lists' % to_native(unprocessed_node_list)) - return None - - 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 add node to cluster %s' % (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 remove node from cluster %s' % (to_native(exception_object)), - exception=traceback.format_exc()) - - def apply(self): - """ - Check, process and initiate Cluster Node operation - """ - changed = False - self.action_nodes_list = [] - if self.module.check_mode is False: - self.get_node_list() - if self.state == "present" and len(self.action_nodes_list) > 0: - self.add_node(self.action_nodes_list) - changed = True - elif self.state == "absent" and len(self.action_nodes_list) > 0: - self.remove_node(self.action_nodes_list) - changed = True - result_message = 'List of nodes : %s - %s' % (to_native(self.action_nodes_list), to_native(self.node_id)) - 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/lib/ansible/modules/storage/netapp/na_elementsw_snapshot.py b/lib/ansible/modules/storage/netapp/na_elementsw_snapshot.py deleted file mode 100644 index bf9c95bea7b..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_snapshot.py +++ /dev/null @@ -1,377 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -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. - - state: - description: - - Whether the specified snapshot should exist or not. - choices: ['present', 'absent'] - default: 'present' - - src_volume_id: - description: - - ID or Name of active volume. - required: true - - account_id: - description: - - Account ID or Name of Parent/Source Volume. - required: true - - retention: - description: - - Retention period for the snapshot. - - Format is 'HH:mm:ss'. - - 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. - - 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. - - expiration_time: - description: - - The date and time (format ISO 8601 date string) at which this snapshot will expire. - - password: - description: - - Element OS access account password - aliases: - - pass - - username: - description: - - Element OS access account user-name - aliases: - - user - -''' - -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.module_utils.netapp as netapp_utils -from ansible.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 - snapshot_delete = False - snapshot_create = False - snapshot_modify = 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/lib/ansible/modules/storage/netapp/na_elementsw_snapshot_restore.py b/lib/ansible/modules/storage/netapp/na_elementsw_snapshot_restore.py deleted file mode 100644 index 7fa66ee1ea5..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_snapshot_restore.py +++ /dev/null @@ -1,201 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Element OS Cluster restore snapshot to volume. - -options: - - src_volume_id: - description: - - ID or Name of source active volume. - required: true - - src_snapshot_id: - description: - - ID or Name of an existing snapshot. - required: true - - dest_volume_name: - description: - - New Name of destination for restoring the snapshot - required: true - - account_id: - description: - - Account ID or Name of Parent/Source Volume. - required: true - -''' - -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.module_utils.netapp as netapp_utils -from ansible.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 - snapshot_detail = 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/lib/ansible/modules/storage/netapp/na_elementsw_snapshot_schedule.py b/lib/ansible/modules/storage/netapp/na_elementsw_snapshot_schedule.py deleted file mode 100644 index 5e1d8274da2..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_snapshot_schedule.py +++ /dev/null @@ -1,555 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, destroy, or update accounts on ElementSW - -options: - - state: - description: - - Whether the specified schedule should exist or not. - required: true - choices: ['present', 'absent'] - - 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'] - - time_interval_days: - description: Time interval in days. - default: 1 - - time_interval_hours: - description: Time interval in hours. - default: 0 - - time_interval_minutes: - description: Time interval in minutes. - default: 0 - - days_of_week_weekdays: - description: List of days of the week (Sunday to Saturday) - - days_of_week_hours: - description: Time specified in hours - default: 0 - - days_of_week_minutes: - description: Time specified in minutes. - default: 0 - - days_of_month_monthdays: - description: List of days of the month (1-31) - - days_of_month_hours: - description: Time specified in hours - default: 0 - - days_of_month_minutes: - description: Time specified in minutes. - default: 0 - - 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 - - snapshot_name: - description: - - Name for the created snapshots. - - volumes: - description: - - Volume IDs that you want to set the snapshot schedule for. - - It accepts both volume_name and volume_id - - 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 - - retention: - description: - - Retention period for the snapshot. - - Format is 'HH:mm:ss'. - - starting_date: - description: - - Starting date for the schedule. - - Required when C(state=present). - - "Format: C(2016-12-01T00:00:00Z)" - - - password: - description: - - Element SW access account password - aliases: - - pass - - username: - description: - - Element SW access account user-name - aliases: - - user -''' - -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' - 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' - 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.module_utils.netapp as netapp_utils -from ansible.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 ApiServerError -except Exception: - HAS_SF_SDK = 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 parameters and ensure SDK is installed - """ - self.argument_spec = netapp_utils.ontap_sf_host_argument_spec() - self.argument_spec.update(dict( - state=dict(required=True, choices=['present', 'absent']), - name=dict(required=True, type='str'), - schedule_type=dict(required=False, choices=['DaysOfWeekFrequency', 'DaysOfMonthFrequency', 'TimeIntervalFrequency']), - - time_interval_days=dict(required=False, type='int', default=1), - time_interval_hours=dict(required=False, type='int', default=0), - time_interval_minutes=dict(required=False, type='int', default=0), - - days_of_week_weekdays=dict(required=False, type='list'), - days_of_week_hours=dict(required=False, type='int', default=0), - days_of_week_minutes=dict(required=False, type='int', default=0), - - days_of_month_monthdays=dict(required=False, type='list'), - days_of_month_hours=dict(required=False, type='int', default=0), - days_of_month_minutes=dict(required=False, type='int', default=0), - - 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'), - 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 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: - frequency = DaysOfWeekFrequency(weekdays=self.weekdays, - hours=self.days_of_week_hours, - minutes=self.days_of_week_minutes) - elif self.schedule_type is not None and self.schedule_type == 'DaysOfMonthFrequency': - if self.days_of_month_monthdays is not None: - frequency = DaysOfMonthFrequency(monthdays=self.days_of_month_monthdays, - hours=self.days_of_month_hours, - minutes=self.days_of_month_minutes) - elif self.schedule_type is not None and self.schedule_type == 'TimeIntervalFrequency': - frequency = netapp_utils.TimeIntervalFrequency(days=self.time_interval_days, - hours=self.time_interval_hours, - minutes=self.time_interval_minutes) - return frequency - - def is_same_schedule_type(self, schedule_detail): - # To check schedule type is same or not - if str(schedule_detail.frequency).split('(')[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 = netapp_utils.ScheduleInfo( - volume_ids=self.volumes, - snapshot_name=self.snapshot_name, - retention=self.retention - ) - - sched = netapp_utils.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 Exception as e: - self.module.fail_json(msg='Error creating schedule %s: %s' % (self.name, to_native(e.message)), - 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 Exception as e: - self.module.fail_json(msg='Error deleting schedule %s: %s' % (self.name, to_native(e.message)), - 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 Exception as e: - self.module.fail_json(msg='Error updating schedule %s: %s' % (self.name, to_native(e.message)), - 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 account - 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 volumeID in schedule_detail.schedule_info.volume_ids: - if volumeID 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(): - v = ElementSWSnapShotSchedule() - v.apply() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/storage/netapp/na_elementsw_vlan.py b/lib/ansible/modules/storage/netapp/na_elementsw_vlan.py deleted file mode 100644 index c52578a96fe..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_vlan.py +++ /dev/null @@ -1,261 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, delete, modify VLAN - -options: - - state: - description: - - Whether the specified vlan should exist or not. - choices: ['present', 'absent'] - default: present - - vlan_tag: - description: - - Virtual Network Tag - required: true - - name: - description: - - User defined name for the new VLAN - - Name of the vlan is unique - - Required for create - - svip: - description: - - Storage virtual IP which is unique - - Required for create - - 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 - - netmask: - description: - - Netmask for the VLAN - - Required for create - - gateway: - description: - - Gateway for the VLAN - - namespace: - description: - - Enable or disable namespaces - type: bool - - attributes: - description: - - Dictionary of attributes with name and value for each attribute - -''' - -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.module_utils.netapp as netapp_utils -from ansible.module_utils.netapp_module import NetAppModule -from ansible.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') - )) - - 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 cd_action == "create": - self.create_network() - elif cd_action == "delete": - self.delete_network() - elif modify: - 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/lib/ansible/modules/storage/netapp/na_elementsw_volume.py b/lib/ansible/modules/storage/netapp/na_elementsw_volume.py deleted file mode 100644 index 70d3398ea9f..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_volume.py +++ /dev/null @@ -1,396 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, destroy, or update volumes on ElementSW - -options: - - state: - description: - - Whether the specified volume should exist or not. - required: true - choices: ['present', 'absent'] - - name: - description: - - The name of the volume to manage. - - It accepts volume_name or volume_id - required: true - - account_id: - description: - - Account ID for the owner of this volume. - - It accepts Account_id or Account_name - required: true - - enable512e: - description: - - Required when C(state=present) - - Should the volume provide 512-byte sector emulation? - type: bool - aliases: - - 512emulation - - qos: - description: Initial quality of service settings for this volume. Configure as dict in playbooks. - - attributes: - description: A YAML dictionary of attributes that you would like to apply on this volume. - - size: - description: - - The size of the volume in (size_unit). - - Required when C(state = present). - - size_unit: - description: - - The unit used to interpret the size parameter. - choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'] - default: 'gb' - - access: - description: - - Access allowed for the volume. - - readOnly Only read operations are allowed. - - readWrite Reads and writes are allowed. - - locked No reads or writes are allowed. - - replicationTarget Identify a volume as the target volume for a paired set of volumes. - - If the volume is not paired, the access status is locked. - - If unspecified, the access settings of the clone will be the same as the source. - choices: ['readOnly', 'readWrite', 'locked', 'replicationTarget'] - - password: - description: - - ElementSW access account password - aliases: - - pass - - username: - description: - - ElementSW access account user-name - aliases: - - user - -''' - -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.module_utils.netapp as netapp_utils -from ansible.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 ElementOSVolume(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 parameters 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=True, choices=['present', 'absent']), - name=dict(required=True, type='str'), - account_id=dict(required=True), - enable512e=dict(type='bool', aliases=['512emulation']), - qos=dict(required=False, type='dict', 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']) - ], - 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.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) - 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_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): - """ - Create Volume - - :return: True if created, False if fails - """ - try: - self.sfe.create_volume(name=self.name, - account_id=self.account_id, - total_size=self.size, - enable512e=self.enable512e, - qos=self.qos, - attributes=self.attributes) - - except Exception as err: - self.module.fail_json(msg="Error provisioning volume %s of size %s" % (self.name, self.size), - exception=to_native(err)) - - def delete_volume(self, 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=err.message, - exception=to_native(err)) - - def update_volume(self, volume_id): - """ - - Update the volume with the specified param - - :return: Success : True, Failed : False - """ - try: - self.sfe.modify_volume(volume_id, - account_id=self.account_id, - access=self.access, - qos=self.qos, - total_size=self.size, - attributes=self.attributes) - - except Exception as err: - # Throwing the exact error message instead of generic error message - self.module.fail_json(msg=err.message, - exception=to_native(err)) - - def apply(self): - # Perform pre-checks, call functions and exit - changed = False - volume_exists = False - update_volume = False - - self.get_account_id() - volume_detail = self.get_volume() - - if volume_detail: - volume_exists = True - volume_id = volume_detail.volume_id - if self.state == 'absent': - # Checking for state change(s) here, and applying it later in the code allows us to support - # check_mode - - changed = True - - elif self.state == 'present': - # Checking all the params for update operation - if volume_detail.access is not None and self.access is not None and volume_detail.access != self.access: - update_volume = True - changed = True - - elif volume_detail.account_id is not None and self.account_id is not None \ - and volume_detail.account_id != self.account_id: - update_volume = True - changed = True - - elif volume_detail.qos is not None and self.qos is not None: - """ - 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 = volume_detail.qos.__dict__ - if volume_qos['min_iops'] != self.qos['minIOPS'] or volume_qos['max_iops'] != self.qos['maxIOPS'] \ - or volume_qos['burst_iops'] != self.qos['burstIOPS']: - update_volume = True - changed = True - else: - # If check fails, do nothing - pass - - if volume_detail.total_size is not None and volume_detail.total_size != self.size: - size_difference = abs(float(volume_detail.total_size - self.size)) - # Change size only if difference is bigger than 0.001 - if size_difference / self.size > 0.001: - update_volume = True - changed = True - - else: - # If check fails, do nothing - pass - - if volume_detail.attributes is not None and self.attributes is not None and \ - volume_detail.attributes != self.attributes: - update_volume = True - changed = True - else: - if self.state == 'present': - changed = True - - result_message = "" - - if changed: - if self.module.check_mode: - result_message = "Check mode, skipping changes" - else: - if self.state == 'present': - if not volume_exists: - self.create_volume() - result_message = "Volume created" - elif update_volume: - self.update_volume(volume_id) - result_message = "Volume updated" - - elif self.state == 'absent': - 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 = ElementOSVolume() - na_elementsw_volume.apply() - - -if __name__ == '__main__': - main() diff --git a/lib/ansible/modules/storage/netapp/na_elementsw_volume_clone.py b/lib/ansible/modules/storage/netapp/na_elementsw_volume_clone.py deleted file mode 100644 index a69ddbdf1f2..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_volume_clone.py +++ /dev/null @@ -1,271 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create volume clones on Element OS - -options: - - name: - description: - - The name of the clone. - required: true - - src_volume_id: - description: - - The id of the src volume to clone. id may be a numeric identifier or a volume name. - required: true - - src_snapshot_id: - description: - - The id of the snapshot to clone. id may be a numeric identifier or a snapshot name. - - account_id: - description: - - Account ID for the owner of this cloned volume. id may be a numeric identifier or an account name. - required: true - - attributes: - description: A YAML dictionary of attributes that you would like to apply on this cloned volume. - - size: - description: - - The size of the cloned volume in (size_unit). - - size_unit: - description: - - The unit used to interpret the size parameter. - choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'] - default: 'gb' - - access: - 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. - -''' - -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.module_utils.netapp as netapp_utils -from ansible.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 parameters 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/lib/ansible/modules/storage/netapp/na_elementsw_volume_pair.py b/lib/ansible/modules/storage/netapp/na_elementsw_volume_pair.py deleted file mode 100644 index 6e3c2168205..00000000000 --- a/lib/ansible/modules/storage/netapp/na_elementsw_volume_pair.py +++ /dev/null @@ -1,284 +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.solidfire -version_added: '2.7' -author: NetApp Ansible Team (@carchi8py) -description: -- Create, delete volume pair - -options: - - state: - description: - - Whether the specified volume pair should exist or not. - choices: ['present', 'absent'] - default: present - - src_volume: - description: - - Source volume name or volume ID - required: true - - src_account: - description: - - Source account name or ID - required: true - - dest_volume: - description: - - Destination volume name or volume ID - required: true - - dest_account: - description: - - Destination account name or ID - required: true - - mode: - description: - - Mode to start the volume pairing - choices: ['async', 'sync', 'snapshotsonly'] - default: async - - dest_mvip: - description: - - Destination IP address of the paired cluster. - required: true - - dest_username: - description: - - Destination username for the paired cluster - - Optional if this is same as source cluster username. - - dest_password: - description: - - Destination password for the paired cluster - - Optional if this is same as source cluster password. - -''' - -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.module_utils.netapp as netapp_utils -from ansible.module_utils.netapp_elementsw_module import NaElementSWModule -from ansible.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() diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 01975df01af..553372c58da 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -3324,53 +3324,6 @@ lib/ansible/modules/source_control/git.py validate-modules:parameter-type-not-in lib/ansible/modules/source_control/subversion.py validate-modules:doc-required-mismatch lib/ansible/modules/source_control/subversion.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/source_control/subversion.py validate-modules:undocumented-parameter -lib/ansible/modules/storage/netapp/na_elementsw_access_group.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_access_group.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_access_group.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_account.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_account.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_admin_users.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_admin_users.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_admin_users.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_backup.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_backup.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_check_connections.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_cluster.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_cluster_config.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_cluster_config.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_cluster_pair.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_cluster_pair.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_cluster_snmp.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_drive.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_drive.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_initiators.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_initiators.py validate-modules:doc-required-mismatch -lib/ansible/modules/storage/netapp/na_elementsw_initiators.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_initiators.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_initiators.py validate-modules:undocumented-parameter -lib/ansible/modules/storage/netapp/na_elementsw_ldap.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_network_interfaces.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_network_interfaces.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_node.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_node.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_node.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_snapshot.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_snapshot.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_snapshot_restore.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_snapshot_schedule.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_snapshot_schedule.py validate-modules:doc-required-mismatch -lib/ansible/modules/storage/netapp/na_elementsw_snapshot_schedule.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_snapshot_schedule.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_vlan.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_vlan.py validate-modules:parameter-list-no-elements -lib/ansible/modules/storage/netapp/na_elementsw_vlan.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_volume.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_volume.py validate-modules:parameter-invalid -lib/ansible/modules/storage/netapp/na_elementsw_volume.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_volume_clone.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_volume_clone.py validate-modules:parameter-type-not-in-doc -lib/ansible/modules/storage/netapp/na_elementsw_volume_pair.py validate-modules:doc-missing-type -lib/ansible/modules/storage/netapp/na_elementsw_volume_pair.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/storage/netapp/na_ontap_aggregate.py validate-modules:doc-missing-type lib/ansible/modules/storage/netapp/na_ontap_aggregate.py validate-modules:parameter-list-no-elements lib/ansible/modules/storage/netapp/na_ontap_aggregate.py validate-modules:parameter-type-not-in-doc @@ -3995,12 +3948,6 @@ test/units/modules/packaging/os/test_yum.py future-import-boilerplate test/units/modules/packaging/os/test_yum.py metaclass-boilerplate test/units/modules/remote_management/oneview/conftest.py future-import-boilerplate test/units/modules/remote_management/oneview/conftest.py metaclass-boilerplate -test/units/modules/storage/netapp/test_na_elementsw_cluster_config.py future-import-boilerplate -test/units/modules/storage/netapp/test_na_elementsw_cluster_config.py metaclass-boilerplate -test/units/modules/storage/netapp/test_na_elementsw_cluster_snmp.py future-import-boilerplate -test/units/modules/storage/netapp/test_na_elementsw_cluster_snmp.py metaclass-boilerplate -test/units/modules/storage/netapp/test_na_elementsw_initiators.py future-import-boilerplate -test/units/modules/storage/netapp/test_na_elementsw_initiators.py metaclass-boilerplate test/units/modules/storage/netapp/test_na_ontap_aggregate.py future-import-boilerplate test/units/modules/storage/netapp/test_na_ontap_aggregate.py metaclass-boilerplate test/units/modules/storage/netapp/test_na_ontap_autosupport.py future-import-boilerplate diff --git a/test/units/modules/storage/netapp/test_na_elementsw_access_group.py b/test/units/modules/storage/netapp/test_na_elementsw_access_group.py deleted file mode 100644 index b14f7e6df92..00000000000 --- a/test/units/modules/storage/netapp/test_na_elementsw_access_group.py +++ /dev/null @@ -1,175 +0,0 @@ -''' unit test for Ansible module: na_elementsw_account.py ''' - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -import json -import pytest - -from units.compat import unittest -from units.compat.mock import patch -from ansible.module_utils import basic -from ansible.module_utils._text import to_bytes -import ansible.module_utils.netapp as netapp_utils - -if not netapp_utils.has_sf_sdk(): - pytestmark = pytest.mark.skip('skipping as missing required SolidFire Python SDK') - -from ansible.modules.storage.netapp.na_elementsw_access_group \ - import ElementSWAccessGroup as my_module # module under test - - -def set_module_args(args): - """prepare arguments so that they will be picked up during module creation""" - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access - - -class AnsibleExitJson(Exception): - """Exception class to be raised by module.exit_json and caught by the test case""" - pass - - -class AnsibleFailJson(Exception): - """Exception class to be raised by module.fail_json and caught by the test case""" - pass - - -def exit_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over exit_json; package return data into an exception""" - if 'changed' not in kwargs: - kwargs['changed'] = False - raise AnsibleExitJson(kwargs) - - -def fail_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over fail_json; package return data into an exception""" - kwargs['failed'] = True - raise AnsibleFailJson(kwargs) - - -ADD_ERROR = 'some_error_in_add_access_group' - - -class MockSFConnection(object): - ''' mock connection to ElementSW host ''' - - class Bunch(object): # pylint: disable=too-few-public-methods - ''' create object with arbitrary attributes ''' - def __init__(self, **kw): - ''' called with (k1=v1, k2=v2), creates obj.k1, obj.k2 with values v1, v2 ''' - setattr(self, '__dict__', kw) - - def __init__(self, force_error=False, where=None): - ''' save arguments ''' - self.force_error = force_error - self.where = where - - def list_volume_access_groups(self, *args, **kwargs): # pylint: disable=unused-argument - ''' build access_group list: access_groups.name, access_groups.account_id ''' - access_groups = list() - access_group_list = self.Bunch(volume_access_groups=access_groups) - return access_group_list - - def create_volume_access_group(self, *args, **kwargs): # pylint: disable=unused-argument - ''' We don't check the return code, but could force an exception ''' - if self.force_error and 'add' in self.where: - # The module does not check for a specific exception :( - raise OSError(ADD_ERROR) - - def get_account_by_name(self, *args, **kwargs): # pylint: disable=unused-argument - ''' returns account_id ''' - if self.force_error and 'account_id' in self.where: - account_id = None - else: - account_id = 1 - print('account_id', account_id) - account = self.Bunch(account_id=account_id) - result = self.Bunch(account=account) - return result - - -class TestMyModule(unittest.TestCase): - ''' a group of related Unit Tests ''' - - def setUp(self): - self.mock_module_helper = patch.multiple(basic.AnsibleModule, - exit_json=exit_json, - fail_json=fail_json) - self.mock_module_helper.start() - self.addCleanup(self.mock_module_helper.stop) - - def test_module_fail_when_required_args_missing(self): - ''' required arguments are reported as errors ''' - with pytest.raises(AnsibleFailJson) as exc: - set_module_args({}) - my_module() - print('Info: %s' % exc.value.args[0]['msg']) - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_command_called(self, mock_create_sf_connection): - ''' a more interesting test ''' - set_module_args({ - 'state': 'present', - 'name': 'element_groupname', - 'account_id': 'element_account_id', - 'hostname': 'hostname', - 'username': 'username', - 'password': 'password', - }) - # my_obj.sfe will be assigned a MockSFConnection object: - mock_create_sf_connection.return_value = MockSFConnection() - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - # It may not be a good idea to start with apply - # More atomic methods can be easier to mock - my_obj.apply() - print(exc.value.args[0]) - assert exc.value.args[0]['changed'] - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_check_error_reporting_on_add_exception(self, mock_create_sf_connection): - ''' a more interesting test ''' - set_module_args({ - 'state': 'present', - 'name': 'element_groupname', - 'account_id': 'element_account_id', - 'hostname': 'hostname', - 'username': 'username', - 'password': 'password', - }) - # my_obj.sfe will be assigned a MockSFConnection object: - mock_create_sf_connection.return_value = MockSFConnection(force_error=True, where=['add']) - my_obj = my_module() - with pytest.raises(AnsibleFailJson) as exc: - # It may not be a good idea to start with apply - # More atomic methods can be easier to mock - # apply() is calling list_accounts() and add_account() - my_obj.apply() - print(exc.value.args[0]) - message = 'Error creating volume access group element_groupname: %s' % ADD_ERROR - assert exc.value.args[0]['msg'] == message - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_check_error_reporting_on_invalid_account_id(self, mock_create_sf_connection): - ''' a more interesting test ''' - set_module_args({ - 'state': 'present', - 'name': 'element_groupname', - 'account_id': 'element_account_id', - 'volumes': ['volume1'], - 'hostname': 'hostname', - 'username': 'username', - 'password': 'password', - }) - # my_obj.sfe will be assigned a MockSFConnection object: - mock_create_sf_connection.return_value = MockSFConnection(force_error=True, where=['account_id']) - my_obj = my_module() - with pytest.raises(AnsibleFailJson) as exc: - # It may not be a good idea to start with apply - # More atomic methods can be easier to mock - # apply() is calling list_accounts() and add_account() - my_obj.apply() - print(exc.value.args[0]) - message = 'Error: Specified account id "%s" does not exist.' % 'element_account_id' - assert exc.value.args[0]['msg'] == message diff --git a/test/units/modules/storage/netapp/test_na_elementsw_cluster_config.py b/test/units/modules/storage/netapp/test_na_elementsw_cluster_config.py deleted file mode 100644 index bebdc72d88e..00000000000 --- a/test/units/modules/storage/netapp/test_na_elementsw_cluster_config.py +++ /dev/null @@ -1,159 +0,0 @@ -# (c) 2019, NetApp, Inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -''' unit test for Ansible module: na_elementsw_cluster_config.py ''' - -from __future__ import print_function -import json -import pytest - -from units.compat import unittest -from units.compat.mock import patch -from ansible.module_utils import basic -from ansible.module_utils._text import to_bytes -import ansible.module_utils.netapp as netapp_utils - -if not netapp_utils.has_sf_sdk(): - pytestmark = pytest.mark.skip('skipping as missing required SolidFire Python SDK') - -from ansible.modules.storage.netapp.na_elementsw_cluster_config \ - import ElementSWClusterConfig as my_module # module under test - - -def set_module_args(args): - """prepare arguments so that they will be picked up during module creation""" - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access - - -class AnsibleExitJson(Exception): - """Exception class to be raised by module.exit_json and caught by the test case""" - pass - - -class AnsibleFailJson(Exception): - """Exception class to be raised by module.fail_json and caught by the test case""" - pass - - -def exit_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over exit_json; package return data into an exception""" - if 'changed' not in kwargs: - kwargs['changed'] = False - raise AnsibleExitJson(kwargs) - - -def fail_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over fail_json; package return data into an exception""" - kwargs['failed'] = True - raise AnsibleFailJson(kwargs) - - -GET_ERROR = 'some_error_in_get_ntp_info' - - -class MockSFConnection(object): - ''' mock connection to ElementSW host ''' - - class Bunch(object): # pylint: disable=too-few-public-methods - ''' create object with arbitrary attributes ''' - - def __init__(self, **kw): - ''' called with (k1=v1, k2=v2), creates obj.k1, obj.k2 with values v1, v2 ''' - setattr(self, '__dict__', kw) - - def __init__(self, force_error=False, where=None): - ''' save arguments ''' - self.force_error = force_error - self.where = where - - -class TestMyModule(unittest.TestCase): - ''' a group of related Unit Tests ''' - - def setUp(self): - self.mock_module_helper = patch.multiple(basic.AnsibleModule, - exit_json=exit_json, - fail_json=fail_json) - self.mock_module_helper.start() - self.addCleanup(self.mock_module_helper.stop) - - def set_default_args(self): - return dict({ - 'hostname': '10.253.168.129', - 'username': 'namburu', - 'password': 'SFlab1234', - }) - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_module_fail_when_required_args_missing(self, mock_create_sf_connection): - ''' required arguments are reported as errors ''' - with pytest.raises(AnsibleFailJson) as exc: - set_module_args({}) - my_module() - print('Info: %s' % exc.value.args[0]['msg']) - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_setup_ntp_info_called(self, mock_create_sf_connection): - ''' test if setup_ntp_info is called ''' - module_args = {} - module_args.update(self.set_default_args()) - ntp_dict = {'set_ntp_info': {'broadcastclient': None, - 'ntp_servers': ['1.1.1.1']}} - module_args.update(ntp_dict) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_setup_ntp_info: %s' % repr(exc.value)) - assert exc.value.args[0]['changed'] - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_set_encryption_at_rest_called(self, mock_create_sf_connection): - ''' test if set_encryption_at_rest is called ''' - module_args = {} - module_args.update(self.set_default_args()) - module_args.update({'encryption_at_rest': 'present'}) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_set_encryption_at_rest enable: %s' % repr(exc.value)) - assert not exc.value.args[0]['changed'] - module_args.update({'encryption_at_rest': 'absent'}) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_set_encryption_at_rest disable: %s' % repr(exc.value)) - assert not exc.value.args[0]['changed'] - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_enable_feature_called(self, mock_create_sf_connection): - ''' test if enable_feature for vvols is called ''' - module_args = {} - module_args.update(self.set_default_args()) - module_args.update({'enable_virtual_volumes': True}) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_enable_feature: %s' % repr(exc.value)) - assert not exc.value.args[0]['changed'] - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_set_cluster_full_threshold_called(self, mock_create_sf_connection): - ''' test if set_cluster_full threshold is called ''' - module_args = {} - module_args.update(self.set_default_args()) - cluster_mod_dict = \ - {'modify_cluster_full_threshold': {'stage2_aware_threshold': 2, - 'stage3_block_threshold_percent': 2, - 'max_metadata_over_provision_factor': 2}} - module_args.update(cluster_mod_dict) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_set_cluster_full_threshold: %s' % repr(exc.value)) - assert exc.value.args[0]['changed'] diff --git a/test/units/modules/storage/netapp/test_na_elementsw_cluster_snmp.py b/test/units/modules/storage/netapp/test_na_elementsw_cluster_snmp.py deleted file mode 100644 index 50f90a6cec4..00000000000 --- a/test/units/modules/storage/netapp/test_na_elementsw_cluster_snmp.py +++ /dev/null @@ -1,178 +0,0 @@ -# (c) 2019, NetApp, Inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -''' unit test for Ansible module: na_elementsw_cluster_snmp.py ''' - -from __future__ import print_function -import json -import pytest - -from units.compat import unittest -from units.compat.mock import patch -from ansible.module_utils import basic -from ansible.module_utils._text import to_bytes -import ansible.module_utils.netapp as netapp_utils - -if not netapp_utils.has_sf_sdk(): - pytestmark = pytest.mark.skip('skipping as missing required SolidFire Python SDK') - -from ansible.modules.storage.netapp.na_elementsw_cluster_snmp \ - import ElementSWClusterSnmp as my_module # module under test - - -def set_module_args(args): - """prepare arguments so that they will be picked up during module creation""" - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access - - -class AnsibleExitJson(Exception): - """Exception class to be raised by module.exit_json and caught by the test case""" - pass - - -class AnsibleFailJson(Exception): - """Exception class to be raised by module.fail_json and caught by the test case""" - pass - - -def exit_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over exit_json; package return data into an exception""" - if 'changed' not in kwargs: - kwargs['changed'] = False - raise AnsibleExitJson(kwargs) - - -def fail_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over fail_json; package return data into an exception""" - kwargs['failed'] = True - raise AnsibleFailJson(kwargs) - - -GET_ERROR = 'some_error_in_get_snmp_info' - - -class MockSFConnection(object): - ''' mock connection to ElementSW host ''' - - class Bunch(object): # pylint: disable=too-few-public-methods - ''' create object with arbitrary attributes ''' - def __init__(self, **kw): - ''' called with (k1=v1, k2=v2), creates obj.k1, obj.k2 with values v1, v2 ''' - setattr(self, '__dict__', kw) - - def __init__(self, force_error=False, where=None): - ''' save arguments ''' - self.force_error = force_error - self.where = where - - -class TestMyModule(unittest.TestCase): - ''' a group of related Unit Tests ''' - - def setUp(self): - self.mock_module_helper = patch.multiple(basic.AnsibleModule, - exit_json=exit_json, - fail_json=fail_json) - self.mock_module_helper.start() - self.addCleanup(self.mock_module_helper.stop) - - def set_default_args(self): - return dict({ - 'hostname': '10.117.78.131', - 'username': 'admin', - 'password': 'netapp1!', - }) - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_module_fail_when_required_args_missing(self, mock_create_sf_connection): - ''' required arguments are reported as errors ''' - with pytest.raises(AnsibleFailJson) as exc: - set_module_args({}) - my_module() - print('Info: %s' % exc.value) - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_enable_snmp_called(self, mock_create_sf_connection): - ''' test if enable_snmp is called ''' - module_args = {} - module_args.update(self.set_default_args()) - module_args.update({'snmp_v3_enabled': True, - 'state': 'present'}) - module_args.update({'usm_users': {'access': 'rouser', - 'name': 'TestUser', - 'password': 'ChangeMe@123', - 'passphrase': 'ChangeMe@123', - 'secLevel': 'auth', }}) - - module_args.update({'networks': {'access': 'ro', - 'cidr': 24, - 'community': 'TestNetwork', - 'network': '192.168.0.1', }}) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_if_enable_snmp_called: %s' % repr(exc.value)) - assert exc.value - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_configure_snmp_from_version_3_TO_version_2_called(self, mock_create_sf_connection): - ''' test if configure snmp from version_3 to version_2''' - module_args = {} - module_args.update(self.set_default_args()) - module_args.update({'snmp_v3_enabled': False, - 'state': 'present'}) - module_args.update({'usm_users': {'access': 'rouser', - 'name': 'TestUser', - 'password': 'ChangeMe@123', - 'passphrase': 'ChangeMe@123', - 'secLevel': 'auth', }}) - - module_args.update({'networks': {'access': 'ro', - 'cidr': 24, - 'community': 'TestNetwork', - 'network': '192.168.0.1', }}) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_ensure_configure_snmp_from_version_3_TO_version_2_called: %s' % repr(exc.value)) - assert exc.value - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_configure_snmp_from_version_2_TO_version_3_called(self, mock_create_sf_connection): - ''' test if configure snmp from version_2 to version_3''' - module_args = {} - module_args.update(self.set_default_args()) - module_args.update({'snmp_v3_enabled': True, - 'state': 'present'}) - module_args.update({'usm_users': {'access': 'rouser', - 'name': 'TestUser_sample', - 'password': 'ChangeMe@123', - 'passphrase': 'ChangeMe@123', - 'secLevel': 'auth', }}) - - module_args.update({'networks': {'access': 'ro', - 'cidr': 24, - 'community': 'TestNetwork', - 'network': '192.168.0.1', }}) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_ensure_configure_snmp_from_version_2_TO_version_3_called: %s' % repr(exc.value)) - assert exc.value - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_ensure_disable_snmp_called(self, mock_create_sf_connection): - ''' test if disable_snmp is called ''' - module_args = {} - module_args.update(self.set_default_args()) - module_args.update({'state': 'absent'}) - set_module_args(module_args) - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_if_disable_snmp_called: %s' % repr(exc.value)) - assert exc.value diff --git a/test/units/modules/storage/netapp/test_na_elementsw_initiators.py b/test/units/modules/storage/netapp/test_na_elementsw_initiators.py deleted file mode 100644 index 91972ae43bb..00000000000 --- a/test/units/modules/storage/netapp/test_na_elementsw_initiators.py +++ /dev/null @@ -1,176 +0,0 @@ -# (c) 2019, NetApp, Inc -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) -''' unit test for Ansible module: na_elementsw_initiators.py ''' - -from __future__ import print_function -import json -import pytest - -from units.compat import unittest -from units.compat.mock import patch -from ansible.module_utils import basic -from ansible.module_utils._text import to_bytes -import ansible.module_utils.netapp as netapp_utils - -if not netapp_utils.has_sf_sdk(): - pytestmark = pytest.mark.skip('skipping as missing required SolidFire Python SDK') - -from ansible.modules.storage.netapp.na_elementsw_initiators \ - import ElementSWInitiators as my_module # module under test - - -def set_module_args(args): - """prepare arguments so that they will be picked up during module creation""" - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) # pylint: disable=protected-access - - -class AnsibleExitJson(Exception): - """Exception class to be raised by module.exit_json and caught by the test case""" - pass - - -class AnsibleFailJson(Exception): - """Exception class to be raised by module.fail_json and caught by the test case""" - pass - - -def exit_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over exit_json; package return data into an exception""" - if 'changed' not in kwargs: - kwargs['changed'] = False - raise AnsibleExitJson(kwargs) - - -def fail_json(*args, **kwargs): # pylint: disable=unused-argument - """function to patch over fail_json; package return data into an exception""" - kwargs['failed'] = True - raise AnsibleFailJson(kwargs) - - -class MockSFConnection(object): - ''' mock connection to ElementSW host ''' - - class Bunch(object): # pylint: disable=too-few-public-methods - ''' create object with arbitrary attributes ''' - def __init__(self, **kw): - ''' called with (k1=v1, k2=v2), creates obj.k1, obj.k2 with values v1, v2 ''' - setattr(self, '__dict__', kw) - - class Initiator(object): - def __init__(self, entries): - self.__dict__.update(entries) - - def list_initiators(self): - ''' build initiator Obj ''' - all_initiators = { - "initiators": [{ - "initiator_name": "a", - "initiator_id": 13, - "alias": "a2", - "attributes": {"key": "value"} - }] - } - return json.loads(json.dumps(all_initiators), object_hook=self.Initiator) - - def create_initiators(self, *args, **kwargs): # pylint: disable=unused-argument - ''' mock method ''' - pass - - def delete_initiators(self, *args, **kwargs): # pylint: disable=unused-argument - ''' mock method ''' - pass - - def modify_initiators(self, *args, **kwargs): # pylint: disable=unused-argument - ''' mock method ''' - pass - - -class TestMyModule(unittest.TestCase): - ''' a group of related Unit Tests ''' - - def setUp(self): - self.mock_module_helper = patch.multiple(basic.AnsibleModule, - exit_json=exit_json, - fail_json=fail_json) - self.mock_module_helper.start() - self.addCleanup(self.mock_module_helper.stop) - - def set_default_args(self): - return dict({ - 'hostname': '10.253.168.129', - 'username': 'namburu', - 'password': 'SFlab1234', - }) - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_module_fail_when_required_args_missing(self, mock_create_sf_connection): - ''' required arguments are reported as errors ''' - with pytest.raises(AnsibleFailJson) as exc: - set_module_args({}) - my_module() - print('Info: %s' % exc.value.args[0]['msg']) - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_create_initiators(self, mock_create_sf_connection): - ''' test if create initiator is called ''' - module_args = {} - module_args.update(self.set_default_args()) - initiator_dict = { - "state": "present", - "initiators": [{ - "name": "newinitiator1", - "alias": "newinitiator1alias", - "attributes": {"key1": "value1"} - }] - } - module_args.update(initiator_dict) - set_module_args(module_args) - mock_create_sf_connection.return_value = MockSFConnection() - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_create_initiators: %s' % repr(exc.value)) - assert exc.value.args[0]['changed'] - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_delete_initiators(self, mock_create_sf_connection): - ''' test if delete initiator is called ''' - module_args = {} - module_args.update(self.set_default_args()) - initiator_dict = { - "state": "absent", - "initiators": [{ - "name": "a" - }] - } - module_args.update(initiator_dict) - set_module_args(module_args) - mock_create_sf_connection.return_value = MockSFConnection() - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_delete_initiators: %s' % repr(exc.value)) - assert exc.value.args[0]['changed'] - - @patch('ansible.module_utils.netapp.create_sf_connection') - def test_modify_initiators(self, mock_create_sf_connection): - ''' test if modify initiator is called ''' - module_args = {} - module_args.update(self.set_default_args()) - initiator_dict = { - "state": "present", - "initiators": [{ - "initiator_name": "a", - "alias": "a3", - "attributes": {"key": "value"} - }] - } - module_args.update(initiator_dict) - set_module_args(module_args) - mock_create_sf_connection.return_value = MockSFConnection() - my_obj = my_module() - with pytest.raises(AnsibleExitJson) as exc: - my_obj.apply() - print('Info: test_modify_initiators: %s' % repr(exc.value)) - assert exc.value.args[0]['changed']