summaryrefslogtreecommitdiff
path: root/lib/ansible/module_utils
diff options
context:
space:
mode:
authorAnsible Core Team <info@ansible.com>2020-03-09 09:40:33 +0000
committerAnsible Core Team <info@ansible.com>2020-03-09 09:40:33 +0000
commit741ff30da275a421dde72d80b53e02cca56ed7e3 (patch)
treeec179fa62278b1a160f633cc87d6c1d18dac3016 /lib/ansible/module_utils
parentf1c4ac5056704bde3ca936d492d2a731ab38db52 (diff)
downloadansible-741ff30da275a421dde72d80b53e02cca56ed7e3.tar.gz
Migrated to netapp.ontap
Diffstat (limited to 'lib/ansible/module_utils')
-rw-r--r--lib/ansible/module_utils/netapp.py744
-rw-r--r--lib/ansible/module_utils/netapp_elementsw_module.py156
-rw-r--r--lib/ansible/module_utils/netapp_module.py270
3 files changed, 0 insertions, 1170 deletions
diff --git a/lib/ansible/module_utils/netapp.py b/lib/ansible/module_utils/netapp.py
deleted file mode 100644
index 5c4d0abd9f..0000000000
--- a/lib/ansible/module_utils/netapp.py
+++ /dev/null
@@ -1,744 +0,0 @@
-# This code is part of Ansible, but is an independent component.
-# This particular file snippet, and this file snippet only, is BSD licensed.
-# Modules you write using this snippet, which is embedded dynamically by Ansible
-# still belong to the author of the module, and may assign their own license
-# to the complete work.
-#
-# Copyright (c) 2017, Sumit Kumar <sumit4@netapp.com>
-# Copyright (c) 2017, Michael Price <michael.price@netapp.com>
-# 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.
-
-import json
-import os
-import random
-import mimetypes
-
-from pprint import pformat
-from ansible.module_utils import six
-from ansible.module_utils.basic import AnsibleModule, missing_required_lib
-from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
-from ansible.module_utils.urls import open_url
-from ansible.module_utils.api import basic_auth_argument_spec
-from ansible.module_utils._text import to_native
-
-try:
- from ansible.module_utils.ansible_release import __version__ as ansible_version
-except ImportError:
- ansible_version = 'unknown'
-
-try:
- from netapp_lib.api.zapi import zapi
- HAS_NETAPP_LIB = True
-except ImportError:
- HAS_NETAPP_LIB = False
-
-try:
- import requests
- HAS_REQUESTS = True
-except ImportError:
- HAS_REQUESTS = False
-
-import ssl
-try:
- from urlparse import urlparse, urlunparse
-except ImportError:
- from urllib.parse import urlparse, urlunparse
-
-
-HAS_SF_SDK = False
-SF_BYTE_MAP = dict(
- # Management GUI displays 1024 ** 3 as 1.1 GB, thus use 1000.
- bytes=1,
- b=1,
- kb=1000,
- mb=1000 ** 2,
- gb=1000 ** 3,
- tb=1000 ** 4,
- pb=1000 ** 5,
- eb=1000 ** 6,
- zb=1000 ** 7,
- yb=1000 ** 8
-)
-
-POW2_BYTE_MAP = dict(
- # Here, 1 kb = 1024
- bytes=1,
- b=1,
- kb=1024,
- mb=1024 ** 2,
- gb=1024 ** 3,
- tb=1024 ** 4,
- pb=1024 ** 5,
- eb=1024 ** 6,
- zb=1024 ** 7,
- yb=1024 ** 8
-)
-
-try:
- from solidfire.factory import ElementFactory
- from solidfire.custom.models import TimeIntervalFrequency
- from solidfire.models import Schedule, ScheduleInfo
-
- HAS_SF_SDK = True
-except Exception:
- HAS_SF_SDK = False
-
-
-def has_netapp_lib():
- return HAS_NETAPP_LIB
-
-
-def has_sf_sdk():
- return HAS_SF_SDK
-
-
-def na_ontap_host_argument_spec():
-
- return dict(
- hostname=dict(required=True, type='str'),
- username=dict(required=True, type='str', aliases=['user']),
- password=dict(required=True, type='str', aliases=['pass'], no_log=True),
- https=dict(required=False, type='bool', default=False),
- validate_certs=dict(required=False, type='bool', default=True),
- http_port=dict(required=False, type='int'),
- ontapi=dict(required=False, type='int'),
- use_rest=dict(required=False, type='str', default='Auto', choices=['Never', 'Always', 'Auto'])
- )
-
-
-def ontap_sf_host_argument_spec():
-
- return dict(
- hostname=dict(required=True, type='str'),
- username=dict(required=True, type='str', aliases=['user']),
- password=dict(required=True, type='str', aliases=['pass'], no_log=True)
- )
-
-
-def aws_cvs_host_argument_spec():
-
- return dict(
- api_url=dict(required=True, type='str'),
- validate_certs=dict(required=False, type='bool', default=True),
- api_key=dict(required=True, type='str'),
- secret_key=dict(required=True, type='str')
- )
-
-
-def create_sf_connection(module, port=None):
- hostname = module.params['hostname']
- username = module.params['username']
- password = module.params['password']
-
- if HAS_SF_SDK and hostname and username and password:
- try:
- return_val = ElementFactory.create(hostname, username, password, port=port)
- return return_val
- except Exception:
- raise Exception("Unable to create SF connection")
- else:
- module.fail_json(msg="the python SolidFire SDK module is required")
-
-
-def setup_na_ontap_zapi(module, vserver=None):
- hostname = module.params['hostname']
- username = module.params['username']
- password = module.params['password']
- https = module.params['https']
- validate_certs = module.params['validate_certs']
- port = module.params['http_port']
- version = module.params['ontapi']
-
- if HAS_NETAPP_LIB:
- # set up zapi
- server = zapi.NaServer(hostname)
- server.set_username(username)
- server.set_password(password)
- if vserver:
- server.set_vserver(vserver)
- if version:
- minor = version
- else:
- minor = 110
- server.set_api_version(major=1, minor=minor)
- # default is HTTP
- if https:
- if port is None:
- port = 443
- transport_type = 'HTTPS'
- # HACK to bypass certificate verification
- if validate_certs is False:
- if not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None):
- ssl._create_default_https_context = ssl._create_unverified_context
- else:
- 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:
- module.fail_json(msg="the python NetApp-Lib module is required")
-
-
-def setup_ontap_zapi(module, vserver=None):
- hostname = module.params['hostname']
- username = module.params['username']
- password = module.params['password']
-
- if HAS_NETAPP_LIB:
- # set up zapi
- server = zapi.NaServer(hostname)
- server.set_username(username)
- server.set_password(password)
- if vserver:
- server.set_vserver(vserver)
- # Todo : Replace hard-coded values with configurable parameters.
- server.set_api_version(major=1, minor=110)
- server.set_port(80)
- server.set_server_type('FILER')
- server.set_transport_type('HTTP')
- return server
- else:
- module.fail_json(msg="the python NetApp-Lib module is required")
-
-
-def eseries_host_argument_spec():
- """Retrieve a base argument specification common to all NetApp E-Series modules"""
- argument_spec = basic_auth_argument_spec()
- argument_spec.update(dict(
- api_username=dict(type='str', required=True),
- api_password=dict(type='str', required=True, no_log=True),
- api_url=dict(type='str', required=True),
- ssid=dict(type='str', required=False, default='1'),
- validate_certs=dict(type='bool', required=False, default=True)
- ))
- return argument_spec
-
-
-class NetAppESeriesModule(object):
- """Base class for all NetApp E-Series modules.
-
- Provides a set of common methods for NetApp E-Series modules, including version checking, mode (proxy, embedded)
- verification, http requests, secure http redirection for embedded web services, and logging setup.
-
- Be sure to add the following lines in the module's documentation section:
- extends_documentation_fragment:
- - netapp.eseries
-
- :param dict(dict) ansible_options: dictionary of ansible option definitions
- :param str web_services_version: minimally required web services rest api version (default value: "02.00.0000.0000")
- :param bool supports_check_mode: whether the module will support the check_mode capabilities (default=False)
- :param list(list) mutually_exclusive: list containing list(s) of mutually exclusive options (optional)
- :param list(list) required_if: list containing list(s) containing the option, the option value, and then
- a list of required options. (optional)
- :param list(list) required_one_of: list containing list(s) of options for which at least one is required. (optional)
- :param list(list) required_together: list containing list(s) of options that are required together. (optional)
- :param bool log_requests: controls whether to log each request (default: True)
- """
- DEFAULT_TIMEOUT = 60
- DEFAULT_SECURE_PORT = "8443"
- DEFAULT_REST_API_PATH = "devmgr/v2/"
- DEFAULT_REST_API_ABOUT_PATH = "devmgr/utils/about"
- DEFAULT_HEADERS = {"Content-Type": "application/json", "Accept": "application/json",
- "netapp-client-type": "Ansible-%s" % ansible_version}
- HTTP_AGENT = "Ansible / %s" % ansible_version
- SIZE_UNIT_MAP = dict(bytes=1, b=1, kb=1024, mb=1024**2, gb=1024**3, tb=1024**4,
- pb=1024**5, eb=1024**6, zb=1024**7, yb=1024**8)
-
- def __init__(self, ansible_options, web_services_version=None, supports_check_mode=False,
- mutually_exclusive=None, required_if=None, required_one_of=None, required_together=None,
- log_requests=True):
- argument_spec = eseries_host_argument_spec()
- argument_spec.update(ansible_options)
-
- self.module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=supports_check_mode,
- mutually_exclusive=mutually_exclusive, required_if=required_if,
- required_one_of=required_one_of, required_together=required_together)
-
- args = self.module.params
- self.web_services_version = web_services_version if web_services_version else "02.00.0000.0000"
- self.ssid = args["ssid"]
- self.url = args["api_url"]
- self.log_requests = log_requests
- self.creds = dict(url_username=args["api_username"],
- url_password=args["api_password"],
- validate_certs=args["validate_certs"])
-
- if not self.url.endswith("/"):
- self.url += "/"
-
- self.is_embedded_mode = None
- self.is_web_services_valid_cache = None
-
- def _check_web_services_version(self):
- """Verify proxy or embedded web services meets minimum version required for module.
-
- The minimum required web services version is evaluated against version supplied through the web services rest
- api. AnsibleFailJson exception will be raised when the minimum is not met or exceeded.
-
- This helper function will update the supplied api url if secure http is not used for embedded web services
-
- :raise AnsibleFailJson: raised when the contacted api service does not meet the minimum required version.
- """
- if not self.is_web_services_valid_cache:
-
- url_parts = urlparse(self.url)
- if not url_parts.scheme or not url_parts.netloc:
- self.module.fail_json(msg="Failed to provide valid API URL. Example: https://192.168.1.100:8443/devmgr/v2. URL [%s]." % self.url)
-
- if url_parts.scheme not in ["http", "https"]:
- self.module.fail_json(msg="Protocol must be http or https. URL [%s]." % self.url)
-
- self.url = "%s://%s/" % (url_parts.scheme, url_parts.netloc)
- about_url = self.url + self.DEFAULT_REST_API_ABOUT_PATH
- rc, data = request(about_url, timeout=self.DEFAULT_TIMEOUT, headers=self.DEFAULT_HEADERS, ignore_errors=True, **self.creds)
-
- if rc != 200:
- self.module.warn("Failed to retrieve web services about information! Retrying with secure ports. Array Id [%s]." % self.ssid)
- self.url = "https://%s:8443/" % url_parts.netloc.split(":")[0]
- about_url = self.url + self.DEFAULT_REST_API_ABOUT_PATH
- try:
- rc, data = request(about_url, timeout=self.DEFAULT_TIMEOUT, headers=self.DEFAULT_HEADERS, **self.creds)
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve the webservices about information! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(error)))
-
- major, minor, other, revision = data["version"].split(".")
- minimum_major, minimum_minor, other, minimum_revision = self.web_services_version.split(".")
-
- if not (major > minimum_major or
- (major == minimum_major and minor > minimum_minor) or
- (major == minimum_major and minor == minimum_minor and revision >= minimum_revision)):
- self.module.fail_json(msg="Web services version does not meet minimum version required. Current version: [%s]."
- " Version required: [%s]." % (data["version"], self.web_services_version))
-
- self.module.log("Web services rest api version met the minimum required version.")
- self.is_web_services_valid_cache = True
-
- def is_embedded(self):
- """Determine whether web services server is the embedded web services.
-
- If web services about endpoint fails based on an URLError then the request will be attempted again using
- secure http.
-
- :raise AnsibleFailJson: raised when web services about endpoint failed to be contacted.
- :return bool: whether contacted web services is running from storage array (embedded) or from a proxy.
- """
- self._check_web_services_version()
-
- if self.is_embedded_mode is None:
- about_url = self.url + self.DEFAULT_REST_API_ABOUT_PATH
- try:
- rc, data = request(about_url, timeout=self.DEFAULT_TIMEOUT, headers=self.DEFAULT_HEADERS, **self.creds)
- self.is_embedded_mode = not data["runningAsProxy"]
- except Exception as error:
- self.module.fail_json(msg="Failed to retrieve the webservices about information! Array Id [%s]. Error [%s]."
- % (self.ssid, to_native(error)))
-
- return self.is_embedded_mode
-
- def request(self, path, data=None, method='GET', headers=None, ignore_errors=False):
- """Issue an HTTP request to a url, retrieving an optional JSON response.
-
- :param str path: web services rest api endpoint path (Example: storage-systems/1/graph). Note that when the
- full url path is specified then that will be used without supplying the protocol, hostname, port and rest path.
- :param data: data required for the request (data may be json or any python structured data)
- :param str method: request method such as GET, POST, DELETE.
- :param dict headers: dictionary containing request headers.
- :param bool ignore_errors: forces the request to ignore any raised exceptions.
- """
- self._check_web_services_version()
-
- if headers is None:
- headers = self.DEFAULT_HEADERS
-
- if not isinstance(data, str) and headers["Content-Type"] == "application/json":
- data = json.dumps(data)
-
- if path.startswith("/"):
- path = path[1:]
- request_url = self.url + self.DEFAULT_REST_API_PATH + path
-
- if self.log_requests or True:
- self.module.log(pformat(dict(url=request_url, data=data, method=method)))
-
- return request(url=request_url, data=data, method=method, headers=headers, use_proxy=True, force=False, last_mod_time=None,
- timeout=self.DEFAULT_TIMEOUT, http_agent=self.HTTP_AGENT, force_basic_auth=True, ignore_errors=ignore_errors, **self.creds)
-
-
-def create_multipart_formdata(files, fields=None, send_8kb=False):
- """Create the data for a multipart/form request.
-
- :param list(list) files: list of lists each containing (name, filename, path).
- :param list(list) fields: list of lists each containing (key, value).
- :param bool send_8kb: only sends the first 8kb of the files (default: False).
- """
- boundary = "---------------------------" + "".join([str(random.randint(0, 9)) for x in range(27)])
- data_parts = list()
- data = None
-
- if six.PY2: # Generate payload for Python 2
- newline = "\r\n"
- if fields is not None:
- for key, value in fields:
- data_parts.extend(["--%s" % boundary,
- 'Content-Disposition: form-data; name="%s"' % key,
- "",
- value])
-
- for name, filename, path in files:
- with open(path, "rb") as fh:
- value = fh.read(8192) if send_8kb else fh.read()
-
- data_parts.extend(["--%s" % boundary,
- 'Content-Disposition: form-data; name="%s"; filename="%s"' % (name, filename),
- "Content-Type: %s" % (mimetypes.guess_type(path)[0] or "application/octet-stream"),
- "",
- value])
- data_parts.extend(["--%s--" % boundary, ""])
- data = newline.join(data_parts)
-
- else:
- newline = six.b("\r\n")
- if fields is not None:
- for key, value in fields:
- data_parts.extend([six.b("--%s" % boundary),
- six.b('Content-Disposition: form-data; name="%s"' % key),
- six.b(""),
- six.b(value)])
-
- for name, filename, path in files:
- with open(path, "rb") as fh:
- value = fh.read(8192) if send_8kb else fh.read()
-
- data_parts.extend([six.b("--%s" % boundary),
- six.b('Content-Disposition: form-data; name="%s"; filename="%s"' % (name, filename)),
- six.b("Content-Type: %s" % (mimetypes.guess_type(path)[0] or "application/octet-stream")),
- six.b(""),
- value])
- data_parts.extend([six.b("--%s--" % boundary), b""])
- data = newline.join(data_parts)
-
- headers = {
- "Content-Type": "multipart/form-data; boundary=%s" % boundary,
- "Content-Length": str(len(data))}
-
- return headers, data
-
-
-def request(url, data=None, headers=None, method='GET', use_proxy=True,
- force=False, last_mod_time=None, timeout=10, validate_certs=True,
- url_username=None, url_password=None, http_agent=None, force_basic_auth=True, ignore_errors=False):
- """Issue an HTTP request to a url, retrieving an optional JSON response."""
-
- if headers is None:
- headers = {"Content-Type": "application/json", "Accept": "application/json"}
- headers.update({"netapp-client-type": "Ansible-%s" % ansible_version})
-
- if not http_agent:
- http_agent = "Ansible / %s" % ansible_version
-
- try:
- r = open_url(url=url, data=data, headers=headers, method=method, use_proxy=use_proxy,
- force=force, last_mod_time=last_mod_time, timeout=timeout, validate_certs=validate_certs,
- url_username=url_username, url_password=url_password, http_agent=http_agent,
- force_basic_auth=force_basic_auth)
- except HTTPError as err:
- r = err.fp
-
- try:
- raw_data = r.read()
- if raw_data:
- data = json.loads(raw_data)
- else:
- raw_data = None
- except Exception:
- if ignore_errors:
- pass
- else:
- raise Exception(raw_data)
-
- resp_code = r.getcode()
-
- if resp_code >= 400 and not ignore_errors:
- raise Exception(resp_code, data)
- else:
- return resp_code, data
-
-
-def ems_log_event(source, server, name="Ansible", id="12345", version=ansible_version,
- category="Information", event="setup", autosupport="false"):
- ems_log = zapi.NaElement('ems-autosupport-log')
- # Host name invoking the API.
- ems_log.add_new_child("computer-name", name)
- # ID of event. A user defined event-id, range [0..2^32-2].
- ems_log.add_new_child("event-id", id)
- # Name of the application invoking the API.
- ems_log.add_new_child("event-source", source)
- # Version of application invoking the API.
- ems_log.add_new_child("app-version", version)
- # Application defined category of the event.
- ems_log.add_new_child("category", category)
- # Description of event to log. An application defined message to log.
- ems_log.add_new_child("event-description", event)
- ems_log.add_new_child("log-level", "6")
- ems_log.add_new_child("auto-support", autosupport)
- server.invoke_successfully(ems_log, True)
-
-
-def get_cserver_zapi(server):
- vserver_info = zapi.NaElement('vserver-get-iter')
- query_details = zapi.NaElement.create_node_with_children('vserver-info', **{'vserver-type': 'admin'})
- query = zapi.NaElement('query')
- query.add_child_elem(query_details)
- vserver_info.add_child_elem(query)
- result = server.invoke_successfully(vserver_info,
- enable_tunneling=False)
- attribute_list = result.get_child_by_name('attributes-list')
- vserver_list = attribute_list.get_child_by_name('vserver-info')
- return vserver_list.get_child_content('vserver-name')
-
-
-def get_cserver(connection, is_rest=False):
- if not is_rest:
- return get_cserver_zapi(connection)
-
- params = {'fields': 'type'}
- api = "private/cli/vserver"
- json, error = connection.get(api, params)
- if json is None or error is not None:
- # exit if there is an error or no data
- return None
- vservers = json.get('records')
- if vservers is not None:
- for vserver in vservers:
- if vserver['type'] == 'admin': # cluster admin
- return vserver['vserver']
- if len(vservers) == 1: # assume vserver admin
- return vservers[0]['vserver']
-
- return None
-
-
-class OntapRestAPI(object):
- def __init__(self, module, timeout=60):
- self.module = module
- self.username = self.module.params['username']
- self.password = self.module.params['password']
- self.hostname = self.module.params['hostname']
- self.use_rest = self.module.params['use_rest']
- self.verify = self.module.params['validate_certs']
- self.timeout = timeout
- self.url = 'https://' + self.hostname + '/api/'
- self.errors = list()
- self.debug_logs = list()
- self.check_required_library()
-
- def check_required_library(self):
- if not HAS_REQUESTS:
- self.module.fail_json(msg=missing_required_lib('requests'))
-
- def send_request(self, method, api, params, json=None, return_status_code=False):
- ''' send http request and process reponse, including error conditions '''
- url = self.url + api
- status_code = None
- content = None
- json_dict = None
- json_error = None
- error_details = None
-
- def get_json(response):
- ''' extract json, and error message if present '''
- try:
- json = response.json()
- except ValueError:
- return None, None
- error = json.get('error')
- return json, error
-
- try:
- response = requests.request(method, url, verify=self.verify, auth=(self.username, self.password), params=params, timeout=self.timeout, json=json)
- content = response.content # for debug purposes
- status_code = response.status_code
- # If the response was successful, no Exception will be raised
- response.raise_for_status()
- json_dict, json_error = get_json(response)
- except requests.exceptions.HTTPError as err:
- __, json_error = get_json(response)
- if json_error is None:
- self.log_error(status_code, 'HTTP error: %s' % err)
- error_details = str(err)
- # If an error was reported in the json payload, it is handled below
- except requests.exceptions.ConnectionError as err:
- self.log_error(status_code, 'Connection error: %s' % err)
- error_details = str(err)
- except Exception as err:
- self.log_error(status_code, 'Other error: %s' % err)
- error_details = str(err)
- if json_error is not None:
- self.log_error(status_code, 'Endpoint error: %d: %s' % (status_code, json_error))
- error_details = json_error
- self.log_debug(status_code, content)
- if return_status_code:
- return status_code, error_details
- return json_dict, error_details
-
- def get(self, api, params):
- method = 'GET'
- return self.send_request(method, api, params)
-
- def post(self, api, data, params=None):
- method = 'POST'
- return self.send_request(method, api, params, json=data)
-
- def patch(self, api, data, params=None):
- method = 'PATCH'
- return self.send_request(method, api, params, json=data)
-
- def delete(self, api, data, params=None):
- method = 'DELETE'
- return self.send_request(method, api, params, json=data)
-
- def _is_rest(self, used_unsupported_rest_properties=None):
- if self.use_rest == "Always":
- if used_unsupported_rest_properties:
- error = "REST API currently does not support '%s'" % \
- ', '.join(used_unsupported_rest_properties)
- return True, error
- else:
- return True, None
- if self.use_rest == 'Never' or used_unsupported_rest_properties:
- # force ZAPI if requested or if some parameter requires it
- return False, None
- method = 'HEAD'
- api = 'cluster/software'
- status_code, __ = self.send_request(method, api, params=None, return_status_code=True)
- if status_code == 200:
- return True, None
- return False, None
-
- def is_rest(self, used_unsupported_rest_properties=None):
- ''' only return error if there is a reason to '''
- use_rest, error = self._is_rest(used_unsupported_rest_properties)
- if used_unsupported_rest_properties is None:
- return use_rest
- return use_rest, error
-
- def log_error(self, status_code, message):
- self.errors.append(message)
- self.debug_logs.append((status_code, message))
-
- def log_debug(self, status_code, content):
- self.debug_logs.append((status_code, content))
-
-
-class AwsCvsRestAPI(object):
- def __init__(self, module, timeout=60):
- self.module = module
- self.api_key = self.module.params['api_key']
- self.secret_key = self.module.params['secret_key']
- self.api_url = self.module.params['api_url']
- self.verify = self.module.params['validate_certs']
- self.timeout = timeout
- self.url = 'https://' + self.api_url + '/v1/'
- self.check_required_library()
-
- def check_required_library(self):
- if not HAS_REQUESTS:
- self.module.fail_json(msg=missing_required_lib('requests'))
-
- def send_request(self, method, api, params, json=None):
- ''' send http request and process reponse, including error conditions '''
- url = self.url + api
- status_code = None
- content = None
- json_dict = None
- json_error = None
- error_details = None
- headers = {
- 'Content-type': "application/json",
- 'api-key': self.api_key,
- 'secret-key': self.secret_key,
- 'Cache-Control': "no-cache",
- }
-
- def get_json(response):
- ''' extract json, and error message if present '''
- try:
- json = response.json()
-
- except ValueError:
- return None, None
- success_code = [200, 201, 202]
- if response.status_code not in success_code:
- error = json.get('message')
- else:
- error = None
- return json, error
- try:
- response = requests.request(method, url, headers=headers, timeout=self.timeout, json=json)
- status_code = response.status_code
- # If the response was successful, no Exception will be raised
- json_dict, json_error = get_json(response)
- except requests.exceptions.HTTPError as err:
- __, json_error = get_json(response)
- if json_error is None:
- error_details = str(err)
- except requests.exceptions.ConnectionError as err:
- error_details = str(err)
- except Exception as err:
- error_details = str(err)
- if json_error is not None:
- error_details = json_error
-
- return json_dict, error_details
-
- # If an error was reported in the json payload, it is handled below
- def get(self, api, params=None):
- method = 'GET'
- return self.send_request(method, api, params)
-
- def post(self, api, data, params=None):
- method = 'POST'
- return self.send_request(method, api, params, json=data)
-
- def patch(self, api, data, params=None):
- method = 'PATCH'
- return self.send_request(method, api, params, json=data)
-
- def put(self, api, data, params=None):
- method = 'PUT'
- return self.send_request(method, api, params, json=data)
-
- def delete(self, api, data, params=None):
- method = 'DELETE'
- return self.send_request(method, api, params, json=data)
-
- def get_state(self, jobId):
- """ Method to get the state of the job """
- method = 'GET'
- response, status_code = self.get('Jobs/%s' % jobId)
- while str(response['state']) not in 'done':
- response, status_code = self.get('Jobs/%s' % jobId)
- return 'done'
diff --git a/lib/ansible/module_utils/netapp_elementsw_module.py b/lib/ansible/module_utils/netapp_elementsw_module.py
deleted file mode 100644
index c9baa0aeb9..0000000000
--- a/lib/ansible/module_utils/netapp_elementsw_module.py
+++ /dev/null
@@ -1,156 +0,0 @@
-# This code is part of Ansible, but is an independent component.
-# This particular file snippet, and this file snippet only, is BSD licensed.
-
-HAS_SF_SDK = False
-try:
- import solidfire.common
-
- HAS_SF_SDK = True
-except Exception:
- 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
deleted file mode 100644
index 914236b349..0000000000
--- a/lib/ansible/module_utils/netapp_module.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# This code is part of Ansible, but is an independent component.
-# This particular file snippet, and this file snippet only, is BSD licensed.
-# Modules you write using this snippet, which is embedded dynamically by Ansible
-# still belong to the author of the module, and may assign their own license
-# to the complete work.
-#
-# Copyright (c) 2018, 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 '''
-
-import ansible.module_utils.netapp as netapp_utils
-
-
-def cmp(a, b):
- """
- Python 3 does not have a cmp function, this will do the cmp.
- :param a: first object to check
- :param b: second object to check
- :return:
- """
- # convert to lower case for string comparison.
- if a is None:
- return -1
- if type(a) is str and type(b) is str:
- a = a.lower()
- b = b.lower()
- # if list has string element, convert string to lower case.
- if type(a) is list and type(b) is list:
- a = [x.lower() if type(x) is str else x for x in a]
- b = [x.lower() if type(x) is str else x for x in b]
- a.sort()
- b.sort()
- return (a > b) - (a < b)
-
-
-class NetAppModule(object):
- '''
- Common class for NetApp modules
- set of support functions to derive actions based
- on the current state of the system, and a desired state
- '''
-
- def __init__(self):
- self.log = list()
- self.changed = False
- self.parameters = {'name': 'not intialized'}
- self.zapi_string_keys = dict()
- self.zapi_bool_keys = dict()
- self.zapi_list_keys = dict()
- self.zapi_int_keys = dict()
- self.zapi_required = dict()
-
- 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_value_for_bool(self, from_zapi, value):
- """
- Convert boolean values to string or vice-versa
- If from_zapi = True, value is converted from string (as it appears in ZAPI) to boolean
- If from_zapi = False, value is converted from boolean to string
- For get() method, from_zapi = True
- For modify(), create(), from_zapi = False
- :param from_zapi: convert the value from ZAPI or to ZAPI acceptable type
- :param value: value of the boolean attribute
- :return: string or boolean
- """
- if value is None:
- return None
- if from_zapi:
- return True if value == 'true' else False
- else:
- return 'true' if value else 'false'
-
- def get_value_for_int(self, from_zapi, value):
- """
- Convert integer values to string or vice-versa
- If from_zapi = True, value is converted from string (as it appears in ZAPI) to integer
- If from_zapi = False, value is converted from integer to string
- For get() method, from_zapi = True
- For modify(), create(), from_zapi = False
- :param from_zapi: convert the value from ZAPI or to ZAPI acceptable type
- :param value: value of the integer attribute
- :return: string or integer
- """
- if value is None:
- return None
- if from_zapi:
- return int(value)
- else:
- return str(value)
-
- def get_value_for_list(self, from_zapi, zapi_parent, zapi_child=None, data=None):
- """
- Convert a python list() to NaElement or vice-versa
- If from_zapi = True, value is converted from NaElement (parent-children structure) to list()
- If from_zapi = False, value is converted from list() to NaElement
- :param zapi_parent: ZAPI parent key or the ZAPI parent NaElement
- :param zapi_child: ZAPI child key
- :param data: list() to be converted to NaElement parent-children object
- :param from_zapi: convert the value from ZAPI or to ZAPI acceptable type
- :return: list() or NaElement
- """
- if from_zapi:
- if zapi_parent is None:
- return []
- else:
- return [zapi_child.get_content() for zapi_child in zapi_parent.get_children()]
- else:
- zapi_parent = netapp_utils.zapi.NaElement(zapi_parent)
- for item in data:
- zapi_parent.add_new_child(zapi_child, item)
- return zapi_parent
-
- def get_cd_action(self, current, desired):
- ''' takes a desired state and a current state, and return an action:
- create, delete, None
- eg:
- is_present = 'absent'
- some_object = self.get_object(source)
- if some_object is not None:
- is_present = 'present'
- action = cd_action(current=is_present, desired = self.desired.state())
- '''
- if 'state' in desired:
- desired_state = desired['state']
- else:
- desired_state = 'present'
-
- if current is None and desired_state == 'absent':
- return None
- if current is not None and desired_state == 'present':
- return None
- # change in state
- self.changed = True
- if current is not None:
- return 'delete'
- return 'create'
-
- def compare_and_update_values(self, current, desired, keys_to_compare):
- updated_values = dict()
- is_changed = False
- for key in keys_to_compare:
- if key in current:
- if key in desired and desired[key] is not None:
- if current[key] != desired[key]:
- updated_values[key] = desired[key]
- is_changed = True
- else:
- updated_values[key] = current[key]
- else:
- updated_values[key] = current[key]
-
- return updated_values, is_changed
-
- @staticmethod
- def check_keys(current, desired):
- ''' TODO: raise an error if keys do not match
- with the exception of:
- new_name, state in desired
- '''
- pass
-
- @staticmethod
- def compare_lists(current, desired, get_list_diff):
- ''' compares two lists and return a list of elements that are either the desired elements or elements that are
- modified from the current state depending on the get_list_diff flag
- :param: current: current item attribute in ONTAP
- :param: desired: attributes from playbook
- :param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
- :return: list of attributes to be modified
- :rtype: list
- '''
- desired_diff_list = [item for item in desired if item not in current] # get what in desired and not in current
- current_diff_list = [item for item in current if item not in desired] # get what in current but not in desired
-
- if desired_diff_list or current_diff_list:
- # there are changes
- if get_list_diff:
- return desired_diff_list
- else:
- return desired
- else:
- return []
-
- def get_modified_attributes(self, current, desired, get_list_diff=False):
- ''' takes two dicts of attributes and return a dict of attributes that are
- not in the current state
- It is expected that all attributes of interest are listed in current and
- desired.
- :param: current: current attributes in ONTAP
- :param: desired: attributes from playbook
- :param: get_list_diff: specifies whether to have a diff of desired list w.r.t current list for an attribute
- :return: dict of attributes to be modified
- :rtype: dict
-
- NOTE: depending on the attribute, the caller may need to do a modify or a
- different operation (eg move volume if the modified attribute is an
- aggregate name)
- '''
- # if the object does not exist, we can't modify it
- modified = dict()
- if current is None:
- return modified
-
- # error out if keys do not match
- self.check_keys(current, desired)
-
- # collect changed attributes
- for key, value in current.items():
- if key in desired and desired[key] is not None:
- if type(value) is list:
- modified_list = self.compare_lists(value, desired[key], get_list_diff) # get modified list from current and desired
- if modified_list:
- modified[key] = modified_list
- elif 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