summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/ansible/module_utils/netapp.py31
-rw-r--r--lib/ansible/module_utils/netapp_elementsw_module.py156
-rw-r--r--lib/ansible/module_utils/netapp_module.py149
-rw-r--r--lib/ansible/utils/module_docs_fragments/netapp.py27
-rw-r--r--test/sanity/validate-modules/ignore.txt4
5 files changed, 349 insertions, 18 deletions
diff --git a/lib/ansible/module_utils/netapp.py b/lib/ansible/module_utils/netapp.py
index 40bd6bb1ad..d60a91bd1a 100644
--- a/lib/ansible/module_utils/netapp.py
+++ b/lib/ansible/module_utils/netapp.py
@@ -33,6 +33,9 @@ from ansible.module_utils.six.moves.urllib.error import HTTPError
from ansible.module_utils.urls import open_url
from ansible.module_utils.api import basic_auth_argument_spec
+import os
+import ssl
+
HAS_NETAPP_LIB = False
try:
from netapp_lib.api.zapi import zapi
@@ -81,7 +84,9 @@ def na_ontap_host_argument_spec():
hostname=dict(required=True, type='str'),
username=dict(required=True, type='str', aliases=['user']),
password=dict(required=True, type='str', aliases=['pass'], no_log=True),
- https=dict(required=False, type='bool', default=False)
+ https=dict(required=False, type='bool', default=False),
+ validate_certs=dict(required=False, type='bool', default=True),
+ http_port=dict(required=False, type='int')
)
@@ -114,6 +119,8 @@ def setup_na_ontap_zapi(module, vserver=None):
username = module.params['username']
password = module.params['password']
https = module.params['https']
+ validate_certs = module.params['validate_certs']
+ port = module.params['http_port']
if HAS_NETAPP_LIB:
# set up zapi
@@ -123,14 +130,22 @@ def setup_na_ontap_zapi(module, vserver=None):
if vserver:
server.set_vserver(vserver)
# Todo : Replace hard-coded values with configurable parameters.
- server.set_api_version(major=1, minor=21)
+ server.set_api_version(major=1, minor=110)
# default is HTTP
- if https is True:
- server.set_port(443)
- server.set_transport_type('HTTPS')
+ if https:
+ if port is None:
+ port = 443
+ transport_type = 'HTTPS'
+ # HACK to bypass certificate verification
+ if validate_certs is True:
+ if not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None):
+ ssl._create_default_https_context = ssl._create_unverified_context
else:
- server.set_port(80)
- server.set_transport_type('HTTP')
+ if port is None:
+ port = 80
+ transport_type = 'HTTP'
+ server.set_transport_type(transport_type)
+ server.set_port(port)
server.set_server_type('FILER')
return server
else:
@@ -150,7 +165,7 @@ def setup_ontap_zapi(module, vserver=None):
if vserver:
server.set_vserver(vserver)
# Todo : Replace hard-coded values with configurable parameters.
- server.set_api_version(major=1, minor=21)
+ server.set_api_version(major=1, minor=110)
server.set_port(80)
server.set_server_type('FILER')
server.set_transport_type('HTTP')
diff --git a/lib/ansible/module_utils/netapp_elementsw_module.py b/lib/ansible/module_utils/netapp_elementsw_module.py
new file mode 100644
index 0000000000..b4fea3c88d
--- /dev/null
+++ b/lib/ansible/module_utils/netapp_elementsw_module.py
@@ -0,0 +1,156 @@
+# This code is part of Ansible, but is an independent component.
+# This particular file snippet, and this file snippet only, is BSD licensed.
+
+HAS_SF_SDK = False
+try:
+ import solidfire.common
+
+ HAS_SF_SDK = True
+except:
+ HAS_SF_SDK = False
+
+
+def has_sf_sdk():
+ return HAS_SF_SDK
+
+
+class NaElementSWModule(object):
+
+ def __init__(self, elem):
+ self.elem_connect = elem
+ self.parameters = dict()
+
+ def get_volume(self, volume_id):
+ """
+ Return volume details if volume exists for given volume_id
+
+ :param volume_id: volume ID
+ :type volume_id: int
+ :return: Volume dict if found, None if not found
+ :rtype: dict
+ """
+ volume_list = self.elem_connect.list_volumes(volume_ids=[volume_id])
+ for volume in volume_list.volumes:
+ if volume.volume_id == volume_id:
+ if str(volume.delete_time) == "":
+ return volume
+ return None
+
+ def get_volume_id(self, vol_name, account_id):
+ """
+ Return volume id from the given (valid) account_id if found
+ Return None if not found
+
+ :param vol_name: Name of the volume
+ :type vol_name: str
+ :param account_id: Account ID
+ :type account_id: int
+
+ :return: Volume ID of the first matching volume if found. None if not found.
+ :rtype: int
+ """
+ volume_list = self.elem_connect.list_volumes_for_account(account_id=account_id)
+ for volume in volume_list.volumes:
+ if volume.name == vol_name:
+ # return volume_id
+ if str(volume.delete_time) == "":
+ return volume.volume_id
+ return None
+
+ def volume_id_exists(self, volume_id):
+ """
+ Return volume_id if volume exists for given volume_id
+
+ :param volume_id: volume ID
+ :type volume_id: int
+ :return: Volume ID if found, None if not found
+ :rtype: int
+ """
+ volume_list = self.elem_connect.list_volumes(volume_ids=[volume_id])
+ for volume in volume_list.volumes:
+ if volume.volume_id == volume_id:
+ if str(volume.delete_time) == "":
+ return volume.volume_id
+ return None
+
+ def volume_exists(self, volume, account_id):
+ """
+ Return volume_id if exists, None if not found
+
+ :param volume: Volume ID or Name
+ :type volume: str
+ :param account_id: Account ID (valid)
+ :type account_id: int
+ :return: Volume ID if found, None if not found
+ """
+ # If volume is an integer, get_by_id
+ if str(volume).isdigit():
+ volume_id = int(volume)
+ try:
+ if self.volume_id_exists(volume_id):
+ return volume_id
+ except solidfire.common.ApiServerError:
+ # don't fail, continue and try get_by_name
+ pass
+ # get volume by name
+ volume_id = self.get_volume_id(volume, account_id)
+ return volume_id
+
+ def get_snapshot(self, snapshot_id, volume_id):
+ """
+ Return snapshot details if found
+
+ :param snapshot_id: Snapshot ID or Name
+ :type snapshot_id: str
+ :param volume_id: Account ID (valid)
+ :type volume_id: int
+ :return: Snapshot dict if found, None if not found
+ :rtype: dict
+ """
+ # mandate src_volume_id although not needed by sdk
+ snapshot_list = self.elem_connect.list_snapshots(
+ volume_id=volume_id)
+ for snapshot in snapshot_list.snapshots:
+ # if actual id is provided
+ if str(snapshot_id).isdigit() and snapshot.snapshot_id == int(snapshot_id):
+ return snapshot
+ # if snapshot name is provided
+ elif snapshot.name == snapshot_id:
+ return snapshot
+ return None
+
+ def account_exists(self, account):
+ """
+ Return account_id if account exists for given account id or name
+ Raises an exception if account does not exist
+
+ :param account: Account ID or Name
+ :type account: str
+ :return: Account ID if found, None if not found
+ """
+ # If account is an integer, get_by_id
+ if account.isdigit():
+ account_id = int(account)
+ try:
+ result = self.elem_connect.get_account_by_id(account_id=account_id)
+ if result.account.account_id == account_id:
+ return account_id
+ except solidfire.common.ApiServerError:
+ # don't fail, continue and try get_by_name
+ pass
+ # get account by name, the method returns an Exception if account doesn't exist
+ result = self.elem_connect.get_account_by_name(username=account)
+ return result.account.account_id
+
+ def set_element_attributes(self, source):
+ """
+ Return telemetry attributes for the current execution
+
+ :param source: name of the module
+ :type source: str
+ :return: a dict containing telemetry attributes
+ """
+ attributes = {}
+ attributes['config-mgmt'] = 'ansible'
+ attributes['event-source'] = source
+ return(attributes)
diff --git a/lib/ansible/module_utils/netapp_module.py b/lib/ansible/module_utils/netapp_module.py
new file mode 100644
index 0000000000..8e65e6c659
--- /dev/null
+++ b/lib/ansible/module_utils/netapp_module.py
@@ -0,0 +1,149 @@
+# This code is part of Ansible, but is an independent component.
+# This particular file snippet, and this file snippet only, is BSD licensed.
+# Modules you write using this snippet, which is embedded dynamically by Ansible
+# still belong to the author of the module, and may assign their own license
+# to the complete work.
+#
+# Copyright (c) 2018, Laurent Nicolas <laurentn@netapp.com>
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+''' Support class for NetApp ansible modules '''
+
+
+def cmp(a, b):
+ """
+ Python 3 does not have a cmp function, this will do the cmp.
+ :param a: first object to check
+ :param b: second object to check
+ :return:
+ """
+ return (a > b) - (a < b)
+
+
+class NetAppModule(object):
+ '''
+ Common class for NetApp modules
+ set of support functions to derive actions based
+ on the current state of the system, and a desired state
+ '''
+
+ def __init__(self):
+ self.log = list()
+ self.changed = False
+ self.parameters = {'name': 'not intialized'}
+
+ def set_parameters(self, ansible_params):
+ self.parameters = dict()
+ for param in ansible_params:
+ if ansible_params[param] is not None:
+ self.parameters[param] = ansible_params[param]
+ return self.parameters
+
+ def get_cd_action(self, current, desired):
+ ''' takes a desired state and a current state, and return an action:
+ create, delete, None
+ eg:
+ is_present = 'absent'
+ some_object = self.get_object(source)
+ if some_object is not None:
+ is_present = 'present'
+ action = cd_action(current=is_present, desired = self.desired.state())
+ '''
+ if 'state' in desired:
+ desired_state = desired['state']
+ else:
+ desired_state = 'present'
+
+ if current is None and desired_state == 'absent':
+ return None
+ if current is not None and desired_state == 'present':
+ return None
+ # change in state
+ self.changed = True
+ if current is not None:
+ return 'delete'
+ return 'create'
+
+ @staticmethod
+ def check_keys(current, desired):
+ ''' TODO: raise an error if keys do not match
+ with the exception of:
+ new_name, state in desired
+ '''
+ pass
+
+ def get_modified_attributes(self, current, desired):
+ ''' takes two lists of attributes and return a list of attributes that are
+ not in the desired state
+ It is expected that all attributes of interest are listed in current and
+ desired.
+
+ NOTE: depending on the attribute, the caller may need to do a modify or a
+ different operation (eg move volume if the modified attribute is an
+ aggregate name)
+ '''
+ # if the object does not exist, we can't modify it
+ modified = dict()
+ if current is None:
+ return modified
+
+ # error out if keys do not match
+ self.check_keys(current, desired)
+
+ # collect changed attributes
+ for key, value in current.items():
+ if key in desired and desired[key] is not None:
+ if type(value) is list:
+ value.sort()
+ desired[key].sort()
+ if cmp(value, desired[key]) != 0:
+ modified[key] = desired[key]
+ if modified:
+ self.changed = True
+ return modified
+
+ def is_rename_action(self, source, target):
+ ''' takes a source and target object, and returns True
+ if a rename is required
+ eg:
+ source = self.get_object(source_name)
+ target = self.get_object(target_name)
+ action = is_rename_action(source, target)
+ :return: None for error, True for rename action, False otherwise
+ '''
+ if source is None and target is None:
+ # error, do nothing
+ # cannot rename an non existent resource
+ # alternatively we could create B
+ return None
+ if source is not None and target is not None:
+ # error, do nothing
+ # idempotency (or) new_name_is_already_in_use
+ # alternatively we could delete B and rename A to B
+ return False
+ if source is None and target is not None:
+ # do nothing, maybe the rename was already done
+ return False
+ # source is not None and target is None:
+ # rename is in order
+ self.changed = True
+ return True
diff --git a/lib/ansible/utils/module_docs_fragments/netapp.py b/lib/ansible/utils/module_docs_fragments/netapp.py
index 653d4d1d20..8174f2e5f9 100644
--- a/lib/ansible/utils/module_docs_fragments/netapp.py
+++ b/lib/ansible/utils/module_docs_fragments/netapp.py
@@ -39,7 +39,7 @@ options:
required: true
description:
- This can be a Cluster-scoped or SVM-scoped account, depending on whether a Cluster-level or SVM-level API is required.
- For more information, please read the documentation U(https://goo.gl/BRu78Z).
+ For more information, please read the documentation U(https://mysupport.netapp.com/NOW/download/software/nmsdk/9.4/).
aliases: ['user']
password:
required: true
@@ -48,9 +48,20 @@ options:
aliases: ['pass']
https:
description:
- - Enable and disabled https
+ - Enable and disable https
type: bool
default: false
+ validate_certs:
+ description:
+ - If set to C(False), the SSL certificates will not be validated.
+ - This should only set to C(False) used on personally controlled sites using self-signed certificates.
+ default: true
+ type: bool
+ http_port:
+ description:
+ - Override the default port (80 or 443) with this port
+ type: int
+
requirements:
- A physical or virtual clustered Data ONTAP system. The modules were developed with Clustered Data ONTAP 9.3
@@ -74,7 +85,7 @@ options:
required: true
description:
- This can be a Cluster-scoped or SVM-scoped account, depending on whether a Cluster-level or SVM-level API is required.
- For more information, please read the documentation U(https://goo.gl/BRu78Z).
+ For more information, please read the documentation U(https://mysupport.netapp.com/NOW/download/software/nmsdk/9.4/).
aliases: ['user']
password:
required: true
@@ -101,17 +112,21 @@ options:
username:
required: true
description:
- - Please ensure that the user has the adequate permissions. For more information, please read the official documentation U(https://goo.gl/ddJa4Q).
+ - Please ensure that the user has the adequate permissions. For more information, please read the official documentation
+ U(https://mysupport.netapp.com/documentation/docweb/index.html?productID=62636&language=en-US).
+ aliases: ['user']
password:
required: true
description:
- Password for the specified user.
+ aliases: ['pass']
requirements:
- - solidfire-sdk-python (1.1.0.92)
+ - The modules were developed with SolidFire 10.1
+ - solidfire-sdk-python (1.1.0.92) or greater. Install using 'pip install solidfire-sdk-python'
notes:
- - The modules prefixed with C(sf\\_) are built to support the SolidFire storage platform.
+ - The modules prefixed with na\\_elementsw are built to support the SolidFire storage platform.
"""
diff --git a/test/sanity/validate-modules/ignore.txt b/test/sanity/validate-modules/ignore.txt
index 1a0f01fdea..29fb782817 100644
--- a/test/sanity/validate-modules/ignore.txt
+++ b/test/sanity/validate-modules/ignore.txt
@@ -1126,11 +1126,7 @@ lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py E323
lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py E324
lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py E325
lib/ansible/modules/storage/netapp/netapp_e_volume_copy.py E326
-lib/ansible/modules/storage/netapp/sf_account_manager.py E322
-lib/ansible/modules/storage/netapp/sf_check_connections.py E322
-lib/ansible/modules/storage/netapp/sf_snapshot_schedule_manager.py E322
lib/ansible/modules/storage/netapp/sf_snapshot_schedule_manager.py E325
-lib/ansible/modules/storage/netapp/sf_volume_access_group_manager.py E322
lib/ansible/modules/storage/netapp/sf_volume_manager.py E322
lib/ansible/modules/storage/netapp/sf_volume_manager.py E325
lib/ansible/modules/storage/purestorage/purefb_fs.py E324