Migrated to netapp.ontap

pull/68298/head
Ansible Core Team 6 years ago committed by Matt Martz
parent cae9d633b0
commit f0cf2c159b

@ -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'

@ -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)

@ -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

@ -1,474 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_aggregate
short_description: NetApp ONTAP manage aggregates.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create, delete, or manage aggregates on ONTAP.
options:
state:
description:
- Whether the specified aggregate should exist or not.
choices: ['present', 'absent']
default: 'present'
service_state:
description:
- Whether the specified aggregate should be enabled or disabled. Creates aggregate if doesnt exist.
choices: ['online', 'offline']
name:
required: true
description:
- The name of the aggregate to manage.
from_name:
description:
- Name of the aggregate to be renamed.
version_added: '2.7'
nodes:
description:
- Node(s) for the aggregate to be created on. If no node specified, mgmt lif home will be used.
- If multiple nodes specified an aggr stripe will be made.
disk_type:
description:
- Type of disk to use to build aggregate
choices: ['ATA', 'BSAS', 'FCAL', 'FSAS', 'LUN', 'MSATA', 'SAS', 'SSD', 'VMDISK']
version_added: '2.7'
disk_count:
description:
- Number of disks to place into the aggregate, including parity disks.
- The disks in this newly-created aggregate come from the spare disk pool.
- The smallest disks in this pool join the aggregate first, unless the C(disk-size) argument is provided.
- Either C(disk-count) or C(disks) must be supplied. Range [0..2^31-1].
- Required when C(state=present).
disk_size:
description:
- Disk size to use in 4K block size. Disks within 10% of specified size will be used.
version_added: '2.7'
raid_size:
description:
- Sets the maximum number of drives per raid group.
version_added: '2.7'
raid_type:
description:
- Specifies the type of RAID groups to use in the new aggregate.
choices: ['raid4', 'raid_dp', 'raid_tec']
version_added: '2.7'
unmount_volumes:
type: bool
description:
- If set to "TRUE", this option specifies that all of the volumes hosted by the given aggregate are to be unmounted
- before the offline operation is executed.
- By default, the system will reject any attempt to offline an aggregate that hosts one or more online volumes.
disks:
type: list
description:
- Specific list of disks to use for the new aggregate.
- To create a "mirrored" aggregate with a specific list of disks, both 'disks' and 'mirror_disks' options must be supplied.
Additionally, the same number of disks must be supplied in both lists.
version_added: '2.8'
is_mirrored:
type: bool
description:
- Specifies that the new aggregate be mirrored (have two plexes).
- If set to true, then the indicated disks will be split across the two plexes. By default, the new aggregate will not be mirrored.
- This option cannot be used when a specific list of disks is supplied with either the 'disks' or 'mirror_disks' options.
version_added: '2.8'
mirror_disks:
type: list
description:
- List of mirror disks to use. It must contain the same number of disks specified in 'disks'.
version_added: '2.8'
spare_pool:
description:
- Specifies the spare pool from which to select spare disks to use in creation of a new aggregate.
choices: ['Pool0', 'Pool1']
version_added: '2.8'
wait_for_online:
description:
- Set this parameter to 'true' for synchronous execution during create (wait until aggregate status is online)
- Set this parameter to 'false' for asynchronous execution
- For asynchronous, execution exits as soon as the request is sent, without checking aggregate status
type: bool
default: false
version_added: '2.8'
time_out:
description:
- time to wait for aggregate creation in seconds
- default is set to 100 seconds
default: 100
version_added: "2.8"
'''
EXAMPLES = """
- name: Create Aggregates and wait 5 minutes until aggregate is online
na_ontap_aggregate:
state: present
service_state: online
name: ansibleAggr
disk_count: 1
wait_for_online: True
time_out: 300
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Manage Aggregates
na_ontap_aggregate:
state: present
service_state: offline
unmount_volumes: true
name: ansibleAggr
disk_count: 1
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Rename Aggregates
na_ontap_aggregate:
state: present
service_state: online
from_name: ansibleAggr
name: ansibleAggr2
disk_count: 1
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete Aggregates
na_ontap_aggregate:
state: absent
service_state: offline
unmount_volumes: true
name: ansibleAggr
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import time
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapAggregate(object):
''' object initialize and class methods '''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
name=dict(required=True, type='str'),
disks=dict(required=False, type='list'),
disk_count=dict(required=False, type='int', default=None),
disk_size=dict(required=False, type='int'),
disk_type=dict(required=False, choices=['ATA', 'BSAS', 'FCAL', 'FSAS', 'LUN', 'MSATA', 'SAS', 'SSD', 'VMDISK']),
from_name=dict(required=False, type='str'),
mirror_disks=dict(required=False, type='list'),
nodes=dict(required=False, type='list'),
is_mirrored=dict(required=False, type='bool'),
raid_size=dict(required=False, type='int'),
raid_type=dict(required=False, choices=['raid4', 'raid_dp', 'raid_tec']),
service_state=dict(required=False, choices=['online', 'offline']),
spare_pool=dict(required=False, choices=['Pool0', 'Pool1']),
state=dict(required=False, choices=['present', 'absent'], default='present'),
unmount_volumes=dict(required=False, type='bool'),
wait_for_online=dict(required=False, type='bool', default=False),
time_out=dict(required=False, type='int', default=100)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('service_state', 'offline', ['unmount_volumes']),
],
mutually_exclusive=[
('is_mirrored', 'disks'),
('is_mirrored', 'mirror_disks'),
('is_mirrored', 'spare_pool'),
('spare_pool', 'disks')
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if self.parameters.get('mirror_disks') is not None and self.parameters.get('disks') is None:
self.module.fail_json(mgs="mirror_disks require disks options to be set")
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def aggr_get_iter(self, name):
"""
Return aggr-get-iter query results
:param name: Name of the aggregate
:return: NaElement if aggregate found, None otherwise
"""
aggr_get_iter = netapp_utils.zapi.NaElement('aggr-get-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'aggr-attributes', **{'aggregate-name': name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
aggr_get_iter.add_child_elem(query)
result = None
try:
result = self.server.invoke_successfully(aggr_get_iter, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
# Error 13040 denotes an aggregate not being found.
if to_native(error.code) == "13040":
pass
else:
self.module.fail_json(msg=to_native(error), exception=traceback.format_exc())
return result
def get_aggr(self, name=None):
"""
Fetch details if aggregate exists.
:param name: Name of the aggregate to be fetched
:return:
Dictionary of current details if aggregate found
None if aggregate is not found
"""
if name is None:
name = self.parameters['name']
aggr_get = self.aggr_get_iter(name)
if (aggr_get and aggr_get.get_child_by_name('num-records') and
int(aggr_get.get_child_content('num-records')) >= 1):
current_aggr = dict()
attr = aggr_get.get_child_by_name('attributes-list').get_child_by_name('aggr-attributes')
current_aggr['service_state'] = attr.get_child_by_name('aggr-raid-attributes').get_child_content('state')
return current_aggr
return None
def aggregate_online(self):
"""
Set state of an offline aggregate to online
:return: None
"""
online_aggr = netapp_utils.zapi.NaElement.create_node_with_children(
'aggr-online', **{'aggregate': self.parameters['name'],
'force-online': 'true'})
try:
self.server.invoke_successfully(online_aggr,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error changing the state of aggregate %s to %s: %s' %
(self.parameters['name'], self.parameters['service_state'], to_native(error)),
exception=traceback.format_exc())
def aggregate_offline(self):
"""
Set state of an online aggregate to offline
:return: None
"""
offline_aggr = netapp_utils.zapi.NaElement.create_node_with_children(
'aggr-offline', **{'aggregate': self.parameters['name'],
'force-offline': 'false',
'unmount-volumes': str(self.parameters['unmount_volumes'])})
try:
self.server.invoke_successfully(offline_aggr, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error changing the state of aggregate %s to %s: %s' %
(self.parameters['name'], self.parameters['service_state'], to_native(error)),
exception=traceback.format_exc())
def create_aggr(self):
"""
Create aggregate
:return: None
"""
if not self.parameters.get('disk_count'):
self.module.fail_json(msg='Error provisioning aggregate %s: \
disk_count is required' % self.parameters['name'])
options = {'aggregate': self.parameters['name'],
'disk-count': str(self.parameters['disk_count'])
}
if self.parameters.get('disk_type'):
options['disk-type'] = self.parameters['disk_type']
if self.parameters.get('raid_size'):
options['raid-size'] = str(self.parameters['raid_size'])
if self.parameters.get('raid_type'):
options['raid-type'] = self.parameters['raid_type']
if self.parameters.get('disk_size'):
options['disk-size'] = str(self.parameters['disk_size'])
if self.parameters.get('is_mirrored'):
options['is-mirrored'] = str(self.parameters['is_mirrored'])
if self.parameters.get('spare_pool'):
options['spare-pool'] = self.parameters['spare_pool']
if self.parameters.get('raid_type'):
options['raid-type'] = self.parameters['raid_type']
aggr_create = netapp_utils.zapi.NaElement.create_node_with_children('aggr-create', **options)
if self.parameters.get('nodes'):
nodes_obj = netapp_utils.zapi.NaElement('nodes')
aggr_create.add_child_elem(nodes_obj)
for node in self.parameters['nodes']:
nodes_obj.add_new_child('node-name', node)
if self.parameters.get('disks'):
disks_obj = netapp_utils.zapi.NaElement('disk-info')
for disk in self.parameters.get('disks'):
disks_obj.add_new_child('name', disk)
aggr_create.add_child_elem(disks_obj)
if self.parameters.get('mirror_disks'):
mirror_disks_obj = netapp_utils.zapi.NaElement('disk-info')
for disk in self.parameters.get('mirror_disks'):
mirror_disks_obj.add_new_child('name', disk)
aggr_create.add_child_elem(mirror_disks_obj)
try:
self.server.invoke_successfully(aggr_create, enable_tunneling=False)
if self.parameters.get('wait_for_online'):
# round off time_out
retries = (self.parameters['time_out'] + 5) / 10
current = self.get_aggr()
status = None if current is None else current['service_state']
while status != 'online' and retries > 0:
time.sleep(10)
retries = retries - 1
current = self.get_aggr()
status = None if current is None else current['service_state']
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error provisioning aggregate %s: %s"
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_aggr(self):
"""
Delete aggregate.
:return: None
"""
aggr_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
'aggr-destroy', **{'aggregate': self.parameters['name']})
try:
self.server.invoke_successfully(aggr_destroy,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error removing aggregate %s: %s" % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def rename_aggregate(self):
"""
Rename aggregate.
"""
aggr_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'aggr-rename', **{'aggregate': self.parameters['from_name'],
'new-aggregate-name': self.parameters['name']})
try:
self.server.invoke_successfully(aggr_rename, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error renaming aggregate %s: %s"
% (self.parameters['from_name'], to_native(error)),
exception=traceback.format_exc())
def modify_aggr(self, modify):
"""
Modify state of the aggregate
:param modify: dictionary of parameters to be modified
:return: None
"""
if modify['service_state'] == 'offline':
self.aggregate_offline()
elif modify['service_state'] == 'online':
self.aggregate_online()
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def apply(self):
"""
Apply action to the aggregate
:return: None
"""
self.asup_log_for_cserver("na_ontap_aggregate")
current = self.get_aggr()
# rename and create are mutually exclusive
rename, cd_action = None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(self.get_aggr(self.parameters['from_name']), current)
if rename is None:
self.module.fail_json(msg="Error renaming: aggregate %s does not exist" % self.parameters['from_name'])
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_aggregate()
elif cd_action == 'create':
self.create_aggr()
elif cd_action == 'delete':
self.delete_aggr()
elif modify:
self.modify_aggr(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Create Aggregate class instance and invoke apply
:return: None
"""
obj_aggr = NetAppOntapAggregate()
obj_aggr.apply()
if __name__ == '__main__':
main()

@ -1,275 +0,0 @@
#!/usr/bin/python
"""
create Autosupport module to enable, disable or modify
"""
# (c) 2018-2019, 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 = """
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- "Enable/Disable Autosupport"
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_autosupport
options:
state:
description:
- Specifies whether the AutoSupport daemon is present or absent.
- When this setting is absent, delivery of all AutoSupport messages is turned off.
choices: ['present', 'absent']
default: present
node_name:
description:
- The name of the filer that owns the AutoSupport Configuration.
required: true
transport:
description:
- The name of the transport protocol used to deliver AutoSupport messages
choices: ['http', 'https', 'smtp']
noteto:
description:
- Specifies up to five recipients of short AutoSupport e-mail messages.
post_url:
description:
- The URL used to deliver AutoSupport messages via HTTP POST
mail_hosts:
description:
- List of mail server(s) used to deliver AutoSupport messages via SMTP.
- Both host names and IP addresses may be used as valid input.
support:
description:
- Specifies whether AutoSupport notification to technical support is enabled.
type: bool
from_address:
description:
- specify the e-mail address from which the node sends AutoSupport messages
version_added: 2.8
partner_addresses:
description:
- Specifies up to five partner vendor recipients of full AutoSupport e-mail messages.
version_added: 2.8
to_addresses:
description:
- Specifies up to five recipients of full AutoSupport e-mail messages.
version_added: 2.8
proxy_url:
description:
- specify an HTTP or HTTPS proxy if the 'transport' parameter is set to HTTP or HTTPS and your organization uses a proxy.
- If authentication is required, use the format "username:password@host:port".
version_added: 2.8
hostname_in_subject:
description:
- Specify whether the hostname of the node is included in the subject line of the AutoSupport message.
type: bool
version_added: 2.8
short_description: NetApp ONTAP Autosupport
version_added: "2.7"
"""
EXAMPLES = """
- name: Enable autosupport
na_ontap_autosupport:
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
state: present
node_name: test
transport: https
noteto: abc@def.com,def@ghi.com
mail_hosts: 1.2.3.4,5.6.7.8
support: False
post_url: url/1.0/post
- name: Modify autosupport proxy_url with password
na_ontap_autosupport:
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
state: present
node_name: test
transport: https
proxy_url: username:password@host.com:8000
- name: Modify autosupport proxy_url without password
na_ontap_autosupport:
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
state: present
node_name: test
transport: https
proxy_url: username@host.com:8000
- name: Disable autosupport
na_ontap_autosupport:
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
state: absent
node_name: test
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPasup(object):
"""Class with autosupport methods"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
node_name=dict(required=True, type='str'),
transport=dict(required=False, type='str', choices=['smtp', 'http', 'https']),
noteto=dict(required=False, type='list'),
post_url=dict(required=False, type='str'),
support=dict(required=False, type='bool'),
mail_hosts=dict(required=False, type='list'),
from_address=dict(required=False, type='str'),
partner_addresses=dict(required=False, type='list'),
to_addresses=dict(required=False, type='list'),
proxy_url=dict(required=False, type='str'),
hostname_in_subject=dict(required=False, type='bool'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=False
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
# present or absent requires modifying state to enabled or disabled
self.parameters['service_state'] = 'started' if self.parameters['state'] == 'present' else 'stopped'
self.set_playbook_zapi_key_map()
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def set_playbook_zapi_key_map(self):
self.na_helper.zapi_string_keys = {
'node_name': 'node-name',
'transport': 'transport',
'post_url': 'post-url',
'from_address': 'from',
'proxy_url': 'proxy-url'
}
self.na_helper.zapi_list_keys = {
'noteto': ('noteto', 'mail-address'),
'mail_hosts': ('mail-hosts', 'string'),
'partner_addresses': ('partner-address', 'mail-address'),
'to_addresses': ('to', 'mail-address'),
}
self.na_helper.zapi_bool_keys = {
'support': 'is-support-enabled',
'hostname_in_subject': 'is-node-in-subject'
}
def get_autosupport_config(self):
"""
Invoke zapi - get current autosupport details
:return: dict()
"""
asup_details = netapp_utils.zapi.NaElement('autosupport-config-get')
asup_details.add_new_child('node-name', self.parameters['node_name'])
asup_info = dict()
try:
result = self.server.invoke_successfully(asup_details, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='%s' % to_native(error),
exception=traceback.format_exc())
# zapi invoke successful
asup_attr_info = result.get_child_by_name('attributes').get_child_by_name('autosupport-config-info')
asup_info['service_state'] = 'started' if asup_attr_info['is-enabled'] == 'true' else 'stopped'
for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
asup_info[item_key] = asup_attr_info[zapi_key]
for item_key, zapi_key in self.na_helper.zapi_bool_keys.items():
asup_info[item_key] = self.na_helper.get_value_for_bool(from_zapi=True,
value=asup_attr_info[zapi_key])
for item_key, zapi_key in self.na_helper.zapi_list_keys.items():
parent, dummy = zapi_key
asup_info[item_key] = self.na_helper.get_value_for_list(from_zapi=True,
zapi_parent=asup_attr_info.get_child_by_name(parent)
)
return asup_info
def modify_autosupport_config(self, modify):
"""
Invoke zapi - modify autosupport config
@return: NaElement object / FAILURE with an error_message
"""
asup_details = {'node-name': self.parameters['node_name']}
if modify.get('service_state'):
asup_details['is-enabled'] = 'true' if modify.get('service_state') == 'started' else 'false'
asup_config = netapp_utils.zapi.NaElement('autosupport-config-modify')
for item_key in modify:
if item_key in self.na_helper.zapi_string_keys:
zapi_key = self.na_helper.zapi_string_keys.get(item_key)
asup_details[zapi_key] = modify[item_key]
elif item_key in self.na_helper.zapi_bool_keys:
zapi_key = self.na_helper.zapi_bool_keys.get(item_key)
asup_details[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False,
value=modify[item_key])
elif item_key in self.na_helper.zapi_list_keys:
parent_key, child_key = self.na_helper.zapi_list_keys.get(item_key)
asup_config.add_child_elem(self.na_helper.get_value_for_list(from_zapi=False,
zapi_parent=parent_key,
zapi_child=child_key,
data=modify.get(item_key)))
asup_config.translate_struct(asup_details)
try:
return self.server.invoke_successfully(asup_config, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='%s' % to_native(error), exception=traceback.format_exc())
def autosupport_log(self):
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_autosupport", cserver)
def apply(self):
"""
Apply action to autosupport
"""
current = self.get_autosupport_config()
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
self.modify_autosupport_config(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""Execute action"""
asup_obj = NetAppONTAPasup()
asup_obj.apply()
if __name__ == '__main__':
main()

@ -1,436 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_broadcast_domain
short_description: NetApp ONTAP manage broadcast domains.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Modify a ONTAP broadcast domain.
options:
state:
description:
- Whether the specified broadcast domain should exist or not.
choices: ['present', 'absent']
default: present
name:
description:
- Specify the broadcast domain name.
required: true
aliases:
- broadcast_domain
from_name:
description:
- Specify the broadcast domain name to be split into new broadcast domain.
version_added: "2.8"
mtu:
description:
- Specify the required mtu for the broadcast domain.
ipspace:
description:
- Specify the required ipspace for the broadcast domain.
- A domain ipspace can not be modified after the domain has been created.
ports:
description:
- Specify the ports associated with this broadcast domain. Should be comma separated.
- It represents the expected state of a list of ports at any time.
- Add a port if it is specified in expected state but not in current state.
- Delete a port if it is specified in current state but not in expected state.
- For split action, it represents the ports to be split from current broadcast domain and added to the new broadcast domain.
- if all ports are removed or split from a broadcast domain, the broadcast domain will be deleted automatically.
'''
EXAMPLES = """
- name: create broadcast domain
na_ontap_broadcast_domain:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
name: ansible_domain
mtu: 1000
ipspace: Default
ports: ["khutton-vsim1:e0d-12", "khutton-vsim1:e0d-13"]
- name: modify broadcast domain
na_ontap_broadcast_domain:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
name: ansible_domain
mtu: 1100
ipspace: Default
ports: ["khutton-vsim1:e0d-12", "khutton-vsim1:e0d-13"]
- name: split broadcast domain
na_ontap_broadcast_domain:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
from_name: ansible_domain
name: new_ansible_domain
mtu: 1200
ipspace: Default
ports: khutton-vsim1:e0d-12
- name: delete broadcast domain
na_ontap_broadcast_domain:
state: absent
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
name: ansible_domain
ipspace: Default
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapBroadcastDomain(object):
"""
Create, Modifies and Destroys a Broadcast domain
"""
def __init__(self):
"""
Initialize the ONTAP Broadcast Domain class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str', aliases=["broadcast_domain"]),
ipspace=dict(required=False, type='str'),
mtu=dict(required=False, type='str'),
ports=dict(required=False, type='list'),
from_name=dict(required=False, type='str'),
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def get_broadcast_domain(self, broadcast_domain=None):
"""
Return details about the broadcast domain
:param broadcast_domain: specific broadcast domain to get.
:return: Details about the broadcast domain. None if not found.
:rtype: dict
"""
if broadcast_domain is None:
broadcast_domain = self.parameters['name']
domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter')
broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info')
broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(broadcast_domain_info)
domain_get_iter.add_child_elem(query)
result = self.server.invoke_successfully(domain_get_iter, True)
domain_exists = None
# check if broadcast_domain exists
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
domain_info = result.get_child_by_name('attributes-list').\
get_child_by_name('net-port-broadcast-domain-info')
domain_name = domain_info.get_child_content('broadcast-domain')
domain_mtu = domain_info.get_child_content('mtu')
domain_ipspace = domain_info.get_child_content('ipspace')
domain_ports = domain_info.get_child_by_name('ports')
if domain_ports is not None:
ports = [port.get_child_content('port') for port in domain_ports.get_children()]
else:
ports = []
domain_exists = {
'domain-name': domain_name,
'mtu': domain_mtu,
'ipspace': domain_ipspace,
'ports': ports
}
return domain_exists
def create_broadcast_domain(self):
"""
Creates a new broadcast domain
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-create')
domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
if self.parameters.get('mtu'):
domain_obj.add_new_child("mtu", self.parameters['mtu'])
if self.parameters.get('ports'):
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in self.parameters['ports']:
ports_obj.add_new_child('net-qualified-port-name', port)
try:
self.server.invoke_successfully(domain_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating broadcast domain %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_broadcast_domain(self, broadcast_domain=None):
"""
Deletes a broadcast domain
"""
if broadcast_domain is None:
broadcast_domain = self.parameters['name']
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-destroy')
domain_obj.add_new_child("broadcast-domain", broadcast_domain)
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
try:
self.server.invoke_successfully(domain_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting broadcast domain %s: %s' %
(broadcast_domain, to_native(error)),
exception=traceback.format_exc())
def modify_broadcast_domain(self):
"""
Modifies ipspace and mtu options of a broadcast domain
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-modify')
domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
if self.parameters.get('mtu'):
domain_obj.add_new_child("mtu", self.parameters['mtu'])
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
try:
self.server.invoke_successfully(domain_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying broadcast domain %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def split_broadcast_domain(self):
"""
split broadcast domain
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-split')
domain_obj.add_new_child("broadcast-domain", self.parameters['from_name'])
domain_obj.add_new_child("new-broadcast-domain", self.parameters['name'])
if self.parameters.get('ports'):
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in self.parameters['ports']:
ports_obj.add_new_child('net-qualified-port-name', port)
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
try:
self.server.invoke_successfully(domain_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error splitting broadcast domain %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
if len(self.get_broadcast_domain_ports(self.parameters['from_name'])) == 0:
self.delete_broadcast_domain(self.parameters['from_name'])
def modify_redirect(self, modify):
"""
:param modify: modify attributes.
"""
for attribute in modify.keys():
if attribute == 'mtu':
self.modify_broadcast_domain()
if attribute == 'ports':
self.modify_broadcast_domain_ports()
def get_modify_attributes(self, current, split):
"""
:param current: current state.
:param split: True or False of split action.
:return: list of modified attributes.
"""
modify = None
if self.parameters['state'] == 'present':
# split already handled ipspace and ports.
if self.parameters.get('from_name'):
current = self.get_broadcast_domain(self.parameters['from_name'])
if split:
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if modify.get('ipspace'):
del modify['ipspace']
if modify.get('ports'):
del modify['ports']
# ipspace can not be modified.
else:
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if modify.get('ipspace'):
self.module.fail_json(msg='A domain ipspace can not be modified after the domain has been created.',
exception=traceback.format_exc())
return modify
def modify_broadcast_domain_ports(self):
"""
compare current and desire ports. Call add or remove ports methods if needed.
:return: None.
"""
current_ports = self.get_broadcast_domain_ports()
expect_ports = self.parameters['ports']
# if want to remove all ports, simply delete the broadcast domain.
if len(expect_ports) == 0:
self.delete_broadcast_domain()
return
ports_to_remove = list(set(current_ports) - set(expect_ports))
ports_to_add = list(set(expect_ports) - set(current_ports))
if len(ports_to_add) > 0:
self.add_broadcast_domain_ports(ports_to_add)
if len(ports_to_remove) > 0:
self.delete_broadcast_domain_ports(ports_to_remove)
def add_broadcast_domain_ports(self, ports):
"""
Creates new broadcast domain ports
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-add-ports')
domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
if ports:
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in ports:
ports_obj.add_new_child('net-qualified-port-name', port)
try:
self.server.invoke_successfully(domain_obj, True)
return True
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating port for broadcast domain %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_broadcast_domain_ports(self, ports):
"""
Deletes broadcast domain ports
:param: ports to be deleted.
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-remove-ports')
domain_obj.add_new_child("broadcast-domain", self.parameters['name'])
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
if ports:
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in ports:
ports_obj.add_new_child('net-qualified-port-name', port)
try:
self.server.invoke_successfully(domain_obj, True)
return True
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting port for broadcast domain %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def get_broadcast_domain_ports(self, broadcast_domain=None):
"""
Return details about the broadcast domain ports.
:return: Details about the broadcast domain ports. None if not found.
:rtype: list
"""
if broadcast_domain is None:
broadcast_domain = self.parameters['name']
domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter')
broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info')
broadcast_domain_info.add_new_child('broadcast-domain', broadcast_domain)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(broadcast_domain_info)
domain_get_iter.add_child_elem(query)
result = self.server.invoke_successfully(domain_get_iter, True)
ports = []
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
domain_info = result.get_child_by_name('attributes-list').get_child_by_name('net-port-broadcast-domain-info')
domain_ports = domain_info.get_child_by_name('ports')
if domain_ports is not None:
ports = [port.get_child_content('port') for port in domain_ports.get_children()]
return ports
def apply(self):
"""
Run Module based on play book
"""
self.asup_log_for_cserver("na_ontap_broadcast_domain")
current = self.get_broadcast_domain()
cd_action, split = None, None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action == 'create':
# either create new domain or split domain.
if self.parameters.get('from_name'):
split = self.na_helper.is_rename_action(self.get_broadcast_domain(self.parameters['from_name']), current)
if split is None:
self.module.fail_json(msg='A domain can not be split if it does not exist.',
exception=traceback.format_exc())
if split:
cd_action = None
modify = self.get_modify_attributes(current, split)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if split:
self.split_broadcast_domain()
if cd_action == 'create':
self.create_broadcast_domain()
elif cd_action == 'delete':
self.delete_broadcast_domain()
elif modify:
self.modify_redirect(modify)
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
"""
Creates the NetApp ONTAP Broadcast Domain Object that can be created, deleted and modified.
"""
obj = NetAppOntapBroadcastDomain()
obj.apply()
if __name__ == '__main__':
main()

@ -1,215 +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_ontap_broadcast_domain_ports
short_description: NetApp ONTAP manage broadcast domain ports
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Add or remove ONTAP broadcast domain ports. Existing ports that are not listed are kept.
options:
state:
description:
- Whether the specified broadcast domain should exist or not.
choices: ['present', 'absent']
default: present
broadcast_domain:
description:
- Specify the broadcast_domain name
required: true
ipspace:
description:
- Specify the ipspace for the broadcast domain
ports:
description:
- Specify the list of ports to add to or remove from this broadcast domain.
'''
EXAMPLES = """
- name: create broadcast domain ports
na_ontap_broadcast_domain_ports:
state=present
username={{ netapp_username }}
password={{ netapp_password }}
hostname={{ netapp_hostname }}
broadcast_domain=123kevin
ports=khutton-vsim1:e0d-13
- name: delete broadcast domain ports
na_ontap_broadcast_domain_ports:
state=absent
username={{ netapp_username }}
password={{ netapp_password }}
hostname={{ netapp_hostname }}
broadcast_domain=123kevin
ports=khutton-vsim1:e0d-13
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapBroadcastDomainPorts(object):
"""
Create and Destroys Broadcast Domain Ports
"""
def __init__(self):
"""
Initialize the Ontap Net Route class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
broadcast_domain=dict(required=True, type='str'),
ipspace=dict(required=False, type='str', default=None),
ports=dict(required=True, type='list'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
# set up state variables
self.state = parameters['state']
self.broadcast_domain = parameters['broadcast_domain']
self.ipspace = parameters['ipspace']
self.ports = parameters['ports']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def get_broadcast_domain_ports(self):
"""
Return details about the broadcast domain ports
:param:
name : broadcast domain name
:return: Details about the broadcast domain. None if not found.
:rtype: dict
"""
domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter')
broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info')
broadcast_domain_info.add_new_child('broadcast-domain', self.broadcast_domain)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(broadcast_domain_info)
domain_get_iter.add_child_elem(query)
result = self.server.invoke_successfully(domain_get_iter, True)
domain_exists = None
# check if broadcast domain exists
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
domain_info = result.get_child_by_name('attributes-list').get_child_by_name('net-port-broadcast-domain-info')
domain_name = domain_info.get_child_content('broadcast-domain')
domain_ports = domain_info.get_child_by_name('ports')
if domain_ports is not None:
ports = [port.get_child_content('port') for port in domain_ports.get_children()]
else:
ports = []
domain_exists = {
'domain-name': domain_name,
'ports': ports
}
return domain_exists
def create_broadcast_domain_ports(self, ports):
"""
Creates new broadcast domain ports
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-add-ports')
domain_obj.add_new_child("broadcast-domain", self.broadcast_domain)
if self.ipspace:
domain_obj.add_new_child("ipspace", self.ipspace)
if ports:
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in ports:
ports_obj.add_new_child('net-qualified-port-name', port)
try:
self.server.invoke_successfully(domain_obj, True)
return True
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating port for broadcast domain %s: %s' %
(self.broadcast_domain, to_native(error)),
exception=traceback.format_exc())
def delete_broadcast_domain_ports(self, ports):
"""
Deletes broadcast domain ports
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-remove-ports')
domain_obj.add_new_child("broadcast-domain", self.broadcast_domain)
if self.ipspace:
domain_obj.add_new_child("ipspace", self.ipspace)
if ports:
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in ports:
ports_obj.add_new_child('net-qualified-port-name', port)
try:
self.server.invoke_successfully(domain_obj, True)
return True
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting port for broadcast domain %s: %s' %
(self.broadcast_domain, to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Run Module based on play book
"""
changed = False
broadcast_domain_details = self.get_broadcast_domain_ports()
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_broadcast_domain_ports", cserver)
if broadcast_domain_details is None:
self.module.fail_json(msg='Error broadcast domain not found: %s' % self.broadcast_domain)
if self.module.check_mode:
pass
else:
if self.state == 'present': # execute create
ports_to_add = [port for port in self.ports if port not in broadcast_domain_details['ports']]
if len(ports_to_add) > 0:
changed = self.create_broadcast_domain_ports(ports_to_add)
elif self.state == 'absent': # execute delete
ports_to_delete = [port for port in self.ports if port in broadcast_domain_details['ports']]
if len(ports_to_delete) > 0:
changed = self.delete_broadcast_domain_ports(ports_to_delete)
self.module.exit_json(changed=changed)
def main():
"""
Creates the NetApp Ontap Net Route object and runs the correct play task
"""
obj = NetAppOntapBroadcastDomainPorts()
obj.apply()
if __name__ == '__main__':
main()

@ -1,219 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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 = '''
short_description: NetApp ONTAP manage consistency group snapshot
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create consistency group snapshot for ONTAP volumes.
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_cg_snapshot
options:
state:
description:
- If you want to create a snapshot.
default: present
vserver:
required: true
description:
- Name of the vserver.
volumes:
required: true
description:
- A list of volumes in this filer that is part of this CG operation.
snapshot:
required: true
description:
- The provided name of the snapshot that is created in each volume.
timeout:
description:
- Timeout selector.
choices: ['urgent', 'medium', 'relaxed']
default: medium
snapmirror_label:
description:
- A human readable SnapMirror label to be attached with the consistency group snapshot copies.
version_added: "2.7"
'''
EXAMPLES = """
- name:
na_ontap_cg_snapshot:
state: present
vserver: vserver_name
snapshot: snapshot name
volumes: vol_name
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPCGSnapshot(object):
"""
Methods to create CG snapshots
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, default='present'),
vserver=dict(required=True, type='str'),
volumes=dict(required=True, type='list'),
snapshot=dict(required=True, type='str'),
timeout=dict(required=False, type='str', choices=[
'urgent', 'medium', 'relaxed'], default='medium'),
snapmirror_label=dict(required=False, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
# set up variables
self.state = parameters['state']
self.vserver = parameters['vserver']
self.volumes = parameters['volumes']
self.snapshot = parameters['snapshot']
self.timeout = parameters['timeout']
self.snapmirror_label = parameters['snapmirror_label']
self.cgid = None
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.vserver)
def does_snapshot_exist(self, volume):
"""
This is duplicated from na_ontap_snapshot
Checks to see if a snapshot exists or not
:return: Return True if a snapshot exists, false if it doesn't
"""
# TODO: Remove this method and import snapshot module and
# call get after re-factoring __init__ across all the modules
# we aren't importing now, since __init__ does a lot of Ansible setup
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter")
desired_attr = netapp_utils.zapi.NaElement("desired-attributes")
snapshot_info = netapp_utils.zapi.NaElement('snapshot-info')
comment = netapp_utils.zapi.NaElement('comment')
# add more desired attributes that are allowed to be modified
snapshot_info.add_child_elem(comment)
desired_attr.add_child_elem(snapshot_info)
snapshot_obj.add_child_elem(desired_attr)
# compose query
query = netapp_utils.zapi.NaElement("query")
snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
snapshot_info_obj.add_new_child("name", self.snapshot)
snapshot_info_obj.add_new_child("volume", volume)
snapshot_info_obj.add_new_child("vserver", self.vserver)
query.add_child_elem(snapshot_info_obj)
snapshot_obj.add_child_elem(query)
result = self.server.invoke_successfully(snapshot_obj, True)
return_value = None
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
attributes_list = result.get_child_by_name('attributes-list')
snap_info = attributes_list.get_child_by_name('snapshot-info')
return_value = {'comment': snap_info.get_child_content('comment')}
return return_value
def cgcreate(self):
"""
Calls cg-start and cg-commit (when cg-start succeeds)
"""
started = self.cg_start()
if started:
if self.cgid is not None:
self.cg_commit()
else:
self.module.fail_json(msg="Error fetching CG ID for CG commit %s" % self.snapshot,
exception=traceback.format_exc())
return started
def cg_start(self):
"""
For the given list of volumes, creates cg-snapshot
"""
snapshot_started = False
cgstart = netapp_utils.zapi.NaElement("cg-start")
cgstart.add_new_child("snapshot", self.snapshot)
cgstart.add_new_child("timeout", self.timeout)
volume_list = netapp_utils.zapi.NaElement("volumes")
cgstart.add_child_elem(volume_list)
for vol in self.volumes:
snapshot_exists = self.does_snapshot_exist(vol)
if snapshot_exists is None:
snapshot_started = True
volume_list.add_new_child("volume-name", vol)
if snapshot_started:
if self.snapmirror_label:
cgstart.add_new_child("snapmirror-label",
self.snapmirror_label)
try:
cgresult = self.server.invoke_successfully(
cgstart, enable_tunneling=True)
if cgresult.get_child_by_name('cg-id'):
self.cgid = cgresult['cg-id']
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error creating CG snapshot %s: %s" %
(self.snapshot, to_native(error)),
exception=traceback.format_exc())
return snapshot_started
def cg_commit(self):
"""
When cg-start is successful, performs a cg-commit with the cg-id
"""
cgcommit = netapp_utils.zapi.NaElement.create_node_with_children(
'cg-commit', **{'cg-id': self.cgid})
try:
self.server.invoke_successfully(cgcommit,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error committing CG snapshot %s: %s" %
(self.snapshot, to_native(error)),
exception=traceback.format_exc())
def apply(self):
'''Applies action from playbook'''
netapp_utils.ems_log_event("na_ontap_cg_snapshot", self.server)
changed = self.cgcreate()
self.module.exit_json(changed=changed)
def main():
'''Execute action from playbook'''
cg_obj = NetAppONTAPCGSnapshot()
cg_obj.apply()
if __name__ == '__main__':
main()

@ -1,306 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# import untangle
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- "Create or destroy or modify(path) cifs-share on ONTAP"
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_cifs
options:
path:
description:
The file system path that is shared through this CIFS share. The path is the full, user visible path relative
to the vserver root, and it might be crossing junction mount points. The path is in UTF8 and uses forward
slash as directory separator
required: false
vserver:
description:
- "Vserver containing the CIFS share."
required: true
share_name:
description:
The name of the CIFS share. The CIFS share name is a UTF-8 string with the following characters being
illegal; control characters from 0x00 to 0x1F, both inclusive, 0x22 (double quotes)
required: true
share_properties:
description:
- The list of properties for the CIFS share
required: false
version_added: '2.8'
symlink_properties:
description:
- The list of symlink properties for this CIFS share
required: false
version_added: '2.8'
state:
choices: ['present', 'absent']
description:
- "Whether the specified CIFS share should exist or not."
required: false
default: present
vscan_fileop_profile:
choices: ['no_scan', 'standard', 'strict', 'writes_only']
description:
- Profile_set of file_ops to which vscan on access scanning is applicable.
required: false
version_added: '2.9'
short_description: NetApp ONTAP Manage cifs-share
version_added: "2.6"
'''
EXAMPLES = """
- name: Create CIFS share
na_ontap_cifs:
state: present
share_name: cifsShareName
path: /
vserver: vserverName
share_properties: browsable,oplocks
symlink_properties: read_only,enable
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete CIFS share
na_ontap_cifs:
state: absent
share_name: cifsShareName
vserver: vserverName
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Modify path CIFS share
na_ontap_cifs:
state: present
share_name: pb_test
vserver: vserverName
path: /
share_properties: show_previous_versions
symlink_properties: disable
vscan_fileop_profile: no_scan
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPCifsShare(object):
"""
Methods to create/delete/modify(path) CIFS share
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=[
'present', 'absent'], default='present'),
share_name=dict(required=True, type='str'),
path=dict(required=False, type='str'),
vserver=dict(required=True, type='str'),
share_properties=dict(required=False, type='list'),
symlink_properties=dict(required=False, type='list'),
vscan_fileop_profile=dict(required=False, type='str', choices=['no_scan', 'standard', 'strict', 'writes_only'])
))
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.parameters.get('vserver'))
def get_cifs_share(self):
"""
Return details about the cifs-share
:param:
name : Name of the cifs-share
:return: Details about the cifs-share. None if not found.
:rtype: dict
"""
cifs_iter = netapp_utils.zapi.NaElement('cifs-share-get-iter')
cifs_info = netapp_utils.zapi.NaElement('cifs-share')
cifs_info.add_new_child('share-name', self.parameters.get('share_name'))
cifs_info.add_new_child('vserver', self.parameters.get('vserver'))
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(cifs_info)
cifs_iter.add_child_elem(query)
result = self.server.invoke_successfully(cifs_iter, True)
return_value = None
# check if query returns the expected cifs-share
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
properties_list = []
symlink_list = []
cifs_attrs = result.get_child_by_name('attributes-list').\
get_child_by_name('cifs-share')
if cifs_attrs.get_child_by_name('share-properties'):
properties_attrs = cifs_attrs['share-properties']
if properties_attrs is not None:
properties_list = [property.get_content() for property in properties_attrs.get_children()]
if cifs_attrs.get_child_by_name('symlink-properties'):
symlink_attrs = cifs_attrs['symlink-properties']
if symlink_attrs is not None:
symlink_list = [symlink.get_content() for symlink in symlink_attrs.get_children()]
return_value = {
'share': cifs_attrs.get_child_content('share-name'),
'path': cifs_attrs.get_child_content('path'),
'share_properties': properties_list,
'symlink_properties': symlink_list
}
if cifs_attrs.get_child_by_name('vscan-fileop-profile'):
return_value['vscan_fileop_profile'] = cifs_attrs['vscan-fileop-profile']
return return_value
def create_cifs_share(self):
"""
Create CIFS share
"""
options = {'share-name': self.parameters.get('share_name'),
'path': self.parameters.get('path')}
cifs_create = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-share-create', **options)
if self.parameters.get('share_properties'):
property_attrs = netapp_utils.zapi.NaElement('share-properties')
cifs_create.add_child_elem(property_attrs)
for property in self.parameters.get('share_properties'):
property_attrs.add_new_child('cifs-share-properties', property)
if self.parameters.get('symlink_properties'):
symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties')
cifs_create.add_child_elem(symlink_attrs)
for symlink in self.parameters.get('symlink_properties'):
symlink_attrs.add_new_child('cifs-share-symlink-properties', symlink)
if self.parameters.get('vscan_fileop_profile'):
fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile')
fileop_attrs.set_content(self.parameters['vscan_fileop_profile'])
cifs_create.add_child_elem(fileop_attrs)
try:
self.server.invoke_successfully(cifs_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating cifs-share %s: %s'
% (self.parameters.get('share_name'), to_native(error)),
exception=traceback.format_exc())
def delete_cifs_share(self):
"""
Delete CIFS share
"""
cifs_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-share-delete', **{'share-name': self.parameters.get('share_name')})
try:
self.server.invoke_successfully(cifs_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting cifs-share %s: %s'
% (self.parameters.get('share_name'), to_native(error)),
exception=traceback.format_exc())
def modify_cifs_share(self):
"""
modify path for the given CIFS share
"""
options = {'share-name': self.parameters.get('share_name')}
cifs_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-share-modify', **options)
if self.parameters.get('path'):
cifs_modify.add_new_child('path', self.parameters.get('path'))
if self.parameters.get('share_properties'):
property_attrs = netapp_utils.zapi.NaElement('share-properties')
cifs_modify.add_child_elem(property_attrs)
for property in self.parameters.get('share_properties'):
property_attrs.add_new_child('cifs-share-properties', property)
if self.parameters.get('symlink_properties'):
symlink_attrs = netapp_utils.zapi.NaElement('symlink-properties')
cifs_modify.add_child_elem(symlink_attrs)
for property in self.parameters.get('symlink_properties'):
symlink_attrs.add_new_child('cifs-share-symlink-properties', property)
if self.parameters.get('vscan_fileop_profile'):
fileop_attrs = netapp_utils.zapi.NaElement('vscan-fileop-profile')
fileop_attrs.set_content(self.parameters['vscan_fileop_profile'])
cifs_modify.add_child_elem(fileop_attrs)
try:
self.server.invoke_successfully(cifs_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying cifs-share %s:%s'
% (self.parameters.get('share_name'), to_native(error)),
exception=traceback.format_exc())
def apply(self):
'''Apply action to cifs share'''
netapp_utils.ems_log_event("na_ontap_cifs", self.server)
current = self.get_cifs_share()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None:
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_cifs_share()
elif cd_action == 'delete':
self.delete_cifs_share()
elif modify:
self.modify_cifs_share()
self.module.exit_json(changed=self.na_helper.changed)
def main():
'''Execute action from playbook'''
cifs_obj = NetAppONTAPCifsShare()
cifs_obj.apply()
if __name__ == '__main__':
main()

@ -1,238 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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 = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- "Create or destroy or modify cifs-share-access-controls on ONTAP"
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_cifs_acl
options:
permission:
choices: ['no_access', 'read', 'change', 'full_control']
description:
-"The access rights that the user or group has on the defined CIFS share."
share_name:
description:
- "The name of the cifs-share-access-control to manage."
required: true
state:
choices: ['present', 'absent']
description:
- "Whether the specified CIFS share acl should exist or not."
default: present
vserver:
description:
- Name of the vserver to use.
required: true
user_or_group:
description:
- "The user or group name for which the permissions are listed."
required: true
short_description: NetApp ONTAP manage cifs-share-access-control
version_added: "2.6"
'''
EXAMPLES = """
- name: Create CIFS share acl
na_ontap_cifs_acl:
state: present
share_name: cifsShareName
user_or_group: Everyone
permission: read
vserver: "{{ netapp_vserver }}"
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Modify CIFS share acl permission
na_ontap_cifs_acl:
state: present
share_name: cifsShareName
user_or_group: Everyone
permission: change
vserver: "{{ netapp_vserver }}"
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPCifsAcl(object):
"""
Methods to create/delete/modify CIFS share/user access-control
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
share_name=dict(required=True, type='str'),
user_or_group=dict(required=True, type='str'),
permission=dict(required=False, type='str', choices=['no_access', 'read', 'change', 'full_control'])
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['share_name', 'user_or_group', 'permission'])
],
supports_check_mode=True
)
parameters = self.module.params
# set up state variables
self.state = parameters['state']
self.vserver = parameters['vserver']
self.share_name = parameters['share_name']
self.user_or_group = parameters['user_or_group']
self.permission = parameters['permission']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def get_cifs_acl(self):
"""
Return details about the cifs-share-access-control
:param:
name : Name of the cifs-share-access-control
:return: Details about the cifs-share-access-control. None if not found.
:rtype: dict
"""
cifs_acl_iter = netapp_utils.zapi.NaElement('cifs-share-access-control-get-iter')
cifs_acl_info = netapp_utils.zapi.NaElement('cifs-share-access-control')
cifs_acl_info.add_new_child('share', self.share_name)
cifs_acl_info.add_new_child('user-or-group', self.user_or_group)
cifs_acl_info.add_new_child('vserver', self.vserver)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(cifs_acl_info)
cifs_acl_iter.add_child_elem(query)
result = self.server.invoke_successfully(cifs_acl_iter, True)
return_value = None
# check if query returns the expected cifs-share-access-control
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
cifs_acl = result.get_child_by_name('attributes-list').get_child_by_name('cifs-share-access-control')
return_value = {
'share': cifs_acl.get_child_content('share'),
'user-or-group': cifs_acl.get_child_content('user-or-group'),
'permission': cifs_acl.get_child_content('permission')
}
return return_value
def create_cifs_acl(self):
"""
Create access control for the given CIFS share/user-group
"""
cifs_acl_create = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-share-access-control-create', **{'share': self.share_name,
'user-or-group': self.user_or_group,
'permission': self.permission})
try:
self.server.invoke_successfully(cifs_acl_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating cifs-share-access-control %s: %s'
% (self.share_name, to_native(error)),
exception=traceback.format_exc())
def delete_cifs_acl(self):
"""
Delete access control for the given CIFS share/user-group
"""
cifs_acl_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-share-access-control-delete', **{'share': self.share_name,
'user-or-group': self.user_or_group})
try:
self.server.invoke_successfully(cifs_acl_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting cifs-share-access-control %s: %s'
% (self.share_name, to_native(error)),
exception=traceback.format_exc())
def modify_cifs_acl_permission(self):
"""
Change permission for the given CIFS share/user-group
"""
cifs_acl_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-share-access-control-modify', **{'share': self.share_name,
'user-or-group': self.user_or_group,
'permission': self.permission})
try:
self.server.invoke_successfully(cifs_acl_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying cifs-share-access-control permission %s:%s'
% (self.share_name, to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to cifs-share-access-control
"""
changed = False
cifs_acl_exists = False
netapp_utils.ems_log_event("na_ontap_cifs_acl", self.server)
cifs_acl_details = self.get_cifs_acl()
if cifs_acl_details:
cifs_acl_exists = True
if self.state == 'absent': # delete
changed = True
elif self.state == 'present':
if cifs_acl_details['permission'] != self.permission: # rename
changed = True
else:
if self.state == 'present': # create
changed = True
if changed:
if self.module.check_mode:
pass
else:
if self.state == 'present': # execute create
if not cifs_acl_exists:
self.create_cifs_acl()
else: # execute modify
self.modify_cifs_acl_permission()
elif self.state == 'absent': # execute delete
self.delete_cifs_acl()
self.module.exit_json(changed=changed)
def main():
"""
Execute action from playbook
"""
cifs_acl = NetAppONTAPCifsAcl()
cifs_acl.apply()
if __name__ == '__main__':
main()

@ -1,329 +0,0 @@
#!/usr/bin/python
""" this is cifs_server module
(c) 2018-2019, 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_ontap_cifs_server
short_description: NetApp ONTAP CIFS server configuration
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Creating / deleting and modifying the CIFS server .
options:
state:
description:
- Whether the specified cifs_server should exist or not.
default: present
choices: ['present', 'absent']
service_state:
description:
- CIFS Server Administrative Status.
choices: ['stopped', 'started']
name:
description:
- Specifies the cifs_server name.
required: true
aliases: ['cifs_server_name']
admin_user_name:
description:
- Specifies the cifs server admin username.
admin_password:
description:
- Specifies the cifs server admin password.
domain:
description:
- The Fully Qualified Domain Name of the Windows Active Directory this CIFS server belongs to.
workgroup:
description:
- The NetBIOS name of the domain or workgroup this CIFS server belongs to.
ou:
description:
- The Organizational Unit (OU) within the Windows Active Directory
this CIFS server belongs to.
version_added: '2.7'
force:
type: bool
description:
- If this is set and a machine account with the same name as
specified in 'name' exists in the Active Directory, it
will be overwritten and reused.
version_added: '2.7'
vserver:
description:
- The name of the vserver to use.
required: true
'''
EXAMPLES = '''
- name: Create cifs_server
na_ontap_cifs_server:
state: present
vserver: svm1
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete cifs_server
na_ontap_cifs_server:
state: absent
name: data2
vserver: svm1
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = '''
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapcifsServer(object):
"""
object to describe cifs_server info
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
service_state=dict(required=False, choices=['stopped', 'started']),
name=dict(required=True, type='str', aliases=['cifs_server_name']),
workgroup=dict(required=False, type='str', default=None),
domain=dict(required=False, type='str'),
admin_user_name=dict(required=False, type='str'),
admin_password=dict(required=False, type='str', no_log=True),
ou=dict(required=False, type='str'),
force=dict(required=False, type='bool'),
vserver=dict(required=True, 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['state']
self.cifs_server_name = params['cifs_server_name']
self.workgroup = params['workgroup']
self.domain = params['domain']
self.vserver = params['vserver']
self.service_state = params['service_state']
self.admin_user_name = params['admin_user_name']
self.admin_password = params['admin_password']
self.ou = params['ou']
self.force = params['force']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def get_cifs_server(self):
"""
Return details about the CIFS-server
:param:
name : Name of the name of the cifs_server
:return: Details about the cifs_server. None if not found.
:rtype: dict
"""
cifs_server_info = netapp_utils.zapi.NaElement('cifs-server-get-iter')
cifs_server_attributes = netapp_utils.zapi.NaElement('cifs-server-config')
cifs_server_attributes.add_new_child('cifs-server', self.cifs_server_name)
cifs_server_attributes.add_new_child('vserver', self.vserver)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(cifs_server_attributes)
cifs_server_info.add_child_elem(query)
result = self.server.invoke_successfully(cifs_server_info, True)
return_value = None
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
cifs_server_attributes = result.get_child_by_name('attributes-list').\
get_child_by_name('cifs-server-config')
return_value = {
'cifs_server_name': self.cifs_server_name,
'administrative-status': cifs_server_attributes.get_child_content('administrative-status')
}
return return_value
def create_cifs_server(self):
"""
calling zapi to create cifs_server
"""
options = {'cifs-server': self.cifs_server_name, 'administrative-status': 'up'
if self.service_state == 'started' else 'down'}
if self.workgroup is not None:
options['workgroup'] = self.workgroup
if self.domain is not None:
options['domain'] = self.domain
if self.admin_user_name is not None:
options['admin-username'] = self.admin_user_name
if self.admin_password is not None:
options['admin-password'] = self.admin_password
if self.ou is not None:
options['organizational-unit'] = self.ou
if self.force is not None:
options['force-account-overwrite'] = str(self.force).lower()
cifs_server_create = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-server-create', **options)
try:
self.server.invoke_successfully(cifs_server_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as exc:
self.module.fail_json(msg='Error Creating cifs_server %s: %s' %
(self.cifs_server_name, to_native(exc)), exception=traceback.format_exc())
def delete_cifs_server(self):
"""
calling zapi to create cifs_server
"""
if self.cifs_server_name == 'up':
self.modify_cifs_server(admin_status='down')
cifs_server_delete = netapp_utils.zapi.NaElement.create_node_with_children('cifs-server-delete')
try:
self.server.invoke_successfully(cifs_server_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as exc:
self.module.fail_json(msg='Error deleting cifs_server %s: %s' % (self.cifs_server_name, to_native(exc)),
exception=traceback.format_exc())
def modify_cifs_server(self, admin_status):
"""
RModify the cifs_server.
"""
cifs_server_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-server-modify', **{'cifs-server': self.cifs_server_name,
'administrative-status': admin_status, 'vserver': self.vserver})
try:
self.server.invoke_successfully(cifs_server_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error modifying cifs_server %s: %s' % (self.cifs_server_name, to_native(e)),
exception=traceback.format_exc())
def start_cifs_server(self):
"""
RModify the cifs_server.
"""
cifs_server_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-server-start')
try:
self.server.invoke_successfully(cifs_server_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error modifying cifs_server %s: %s' % (self.cifs_server_name, to_native(e)),
exception=traceback.format_exc())
def stop_cifs_server(self):
"""
RModify the cifs_server.
"""
cifs_server_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'cifs-server-stop')
try:
self.server.invoke_successfully(cifs_server_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error modifying cifs_server %s: %s' % (self.cifs_server_name, to_native(e)),
exception=traceback.format_exc())
def apply(self):
"""
calling all cifs_server features
"""
changed = False
cifs_server_exists = False
netapp_utils.ems_log_event("na_ontap_cifs_server", self.server)
cifs_server_detail = self.get_cifs_server()
if cifs_server_detail:
cifs_server_exists = True
if self.state == 'present':
administrative_status = cifs_server_detail['administrative-status']
if self.service_state == 'started' and administrative_status == 'down':
changed = True
if self.service_state == 'stopped' and administrative_status == 'up':
changed = True
else:
# we will delete the CIFs server
changed = True
else:
if self.state == 'present':
changed = True
if changed:
if self.module.check_mode:
pass
else:
if self.state == 'present':
if not cifs_server_exists:
self.create_cifs_server()
elif self.service_state == 'stopped':
self.stop_cifs_server()
elif self.service_state == 'started':
self.start_cifs_server()
elif self.state == 'absent':
self.delete_cifs_server()
self.module.exit_json(changed=changed)
def main():
cifs_server = NetAppOntapcifsServer()
cifs_server.apply()
if __name__ == '__main__':
main()

@ -1,287 +0,0 @@
#!/usr/bin/python
# (c) 2017-2019, 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_ontap_cluster
short_description: NetApp ONTAP cluster - create, join, add license
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or join or apply licenses to ONTAP clusters
- Cluster join can be performed using only one of the parameters, either cluster_name or cluster_ip_address
options:
state:
description:
- Whether the specified cluster should exist or not.
choices: ['present']
default: present
cluster_name:
description:
- The name of the cluster to manage.
cluster_ip_address:
description:
- IP address of cluster to be joined
license_code:
description:
- License code to be applied to the cluster
license_package:
description:
- License package name of the license to be removed
node_serial_number:
description:
- Serial number of the cluster node
'''
EXAMPLES = """
- name: Create cluster
na_ontap_cluster:
state: present
cluster_name: new_cluster
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Add license from cluster
na_ontap_cluster:
state: present
cluster_name: FPaaS-A300-01
license_code: SGHLQDBBVAAAAAAAAAAAAAAAAAAA
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Join cluster
na_ontap_cluster:
state: present
cluster_ip_address: 10.61.184.181
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Join cluster
na_ontap_cluster:
state: present
cluster_name: FPaaS-A300-01
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
def local_cmp(a, b):
"""
compares with only values and not keys, keys should be the same for both dicts
:param a: dict 1
:param b: dict 2
:return: difference of values in both dicts
"""
diff = [key for key in a if a[key] != b[key]]
return len(diff)
class NetAppONTAPCluster(object):
"""
object initialize and class methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present'], default='present'),
cluster_name=dict(required=False, type='str'),
cluster_ip_address=dict(required=False, type='str'),
license_code=dict(required=False, type='str'),
license_package=dict(required=False, type='str'),
node_serial_number=dict(required=False, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True,
required_together=[
['license_package', 'node_serial_number']
]
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_licensing_status(self):
"""
Check licensing status
:return: package (key) and licensing status (value)
:rtype: dict
"""
license_status = netapp_utils.zapi.NaElement(
'license-v2-status-list-info')
try:
result = self.server.invoke_successfully(license_status,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error checking license status: %s" %
to_native(error), exception=traceback.format_exc())
return_dictionary = {}
license_v2_status = result.get_child_by_name('license-v2-status')
if license_v2_status:
for license_v2_status_info in license_v2_status.get_children():
package = license_v2_status_info.get_child_content('package')
status = license_v2_status_info.get_child_content('method')
return_dictionary[package] = status
return return_dictionary
def create_cluster(self):
"""
Create a cluster
"""
cluster_create = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-create', **{'cluster-name': self.parameters['cluster_name']})
try:
self.server.invoke_successfully(cluster_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
# Error 36503 denotes node already being used.
if to_native(error.code) == "36503":
return False
else:
self.module.fail_json(msg='Error creating cluster %s: %s'
% (self.parameters['cluster_name'], to_native(error)),
exception=traceback.format_exc())
return True
def cluster_join(self):
"""
Add a node to an existing cluster
"""
if self.parameters.get('cluster_ip_address') is not None:
cluster_add_node = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-join', **{'cluster-ip-address': self.parameters['cluster_ip_address']})
for_fail_attribute = self.parameters.get('cluster_ip_address')
elif self.parameters.get('cluster_name') is not None:
cluster_add_node = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-join', **{'cluster-name': self.parameters['cluster_name']})
for_fail_attribute = self.parameters.get('cluster_name')
else:
return False
try:
self.server.invoke_successfully(cluster_add_node, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
# Error 36503 denotes node already being used.
if to_native(error.code) == "36503":
return False
else:
self.module.fail_json(msg='Error adding node to cluster %s: %s'
% (for_fail_attribute, to_native(error)),
exception=traceback.format_exc())
return True
def license_v2_add(self):
"""
Apply a license to cluster
"""
license_add = netapp_utils.zapi.NaElement.create_node_with_children('license-v2-add')
license_add.add_node_with_children('codes', **{'license-code-v2': self.parameters['license_code']})
try:
self.server.invoke_successfully(license_add, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error adding license %s: %s'
% (self.parameters['license_code'], to_native(error)),
exception=traceback.format_exc())
def license_v2_delete(self):
"""
Delete license from cluster
"""
license_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'license-v2-delete', **{'package': self.parameters['license_package'],
'serial-number': self.parameters['node_serial_number']})
try:
self.server.invoke_successfully(license_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting license : %s' % (to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
"""
Autosupport log for cluster
:return:
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_cluster", cserver)
def apply(self):
"""
Apply action to cluster
"""
property_changed = False
create_flag = False
join_flag = False
self.autosupport_log()
license_status = self.get_licensing_status()
if self.module.check_mode:
pass
else:
if self.parameters.get('state') == 'present':
if self.parameters.get('cluster_name') is not None:
create_flag = self.create_cluster()
if not create_flag:
join_flag = self.cluster_join()
if self.parameters.get('license_code') is not None:
self.license_v2_add()
property_changed = True
if self.parameters.get('license_package') is not None and\
self.parameters.get('node_serial_number') is not None:
if license_status.get(str(self.parameters.get('license_package')).lower()) != 'none':
self.license_v2_delete()
property_changed = True
if property_changed:
new_license_status = self.get_licensing_status()
if local_cmp(license_status, new_license_status) == 0:
property_changed = False
changed = property_changed or create_flag or join_flag
self.module.exit_json(changed=changed)
def main():
"""
Create object and call apply
"""
cluster_obj = NetAppONTAPCluster()
cluster_obj.apply()
if __name__ == '__main__':
main()

@ -1,133 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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 = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- "Enable or disable HA on a cluster"
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_cluster_ha
options:
state:
choices: ['present', 'absent']
description:
- "Whether HA on cluster should be enabled or disabled."
default: present
short_description: NetApp ONTAP Manage HA status for cluster
version_added: "2.6"
'''
EXAMPLES = """
- name: "Enable HA status for cluster"
na_ontap_cluster_ha:
state: present
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapClusterHA(object):
"""
object initialize and class methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, 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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def modify_cluster_ha(self, configure):
"""
Enable or disable HA on cluster
:return: None
"""
cluster_ha_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-ha-modify', **{'ha-configured': configure})
try:
self.server.invoke_successfully(cluster_ha_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying cluster HA to %s: %s'
% (configure, to_native(error)),
exception=traceback.format_exc())
def get_cluster_ha_enabled(self):
"""
Get current cluster HA details
:return: dict if enabled, None if disabled
"""
cluster_ha_get = netapp_utils.zapi.NaElement('cluster-ha-get')
try:
result = self.server.invoke_successfully(cluster_ha_get,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching cluster HA details',
exception=traceback.format_exc())
cluster_ha_info = result.get_child_by_name('attributes').get_child_by_name('cluster-ha-info')
if cluster_ha_info.get_child_content('ha-configured') == 'true':
return {'ha-configured': True}
return None
def apply(self):
"""
Apply action to cluster HA
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_cluster_ha", cserver)
current = self.get_cluster_ha_enabled()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action == 'create':
self.modify_cluster_ha("true")
elif cd_action == 'delete':
self.modify_cluster_ha("false")
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Create object and call apply
"""
ha_obj = NetAppOntapClusterHA()
ha_obj.apply()
if __name__ == '__main__':
main()

@ -1,295 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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 = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete cluster peer relations on ONTAP
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_cluster_peer
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified cluster peer should exist or not.
default: present
source_intercluster_lifs:
description:
- List of intercluster addresses of the source cluster.
- Used as peer-addresses in destination cluster.
- All these intercluster lifs should belong to the source cluster.
version_added: "2.8"
aliases:
- source_intercluster_lif
dest_intercluster_lifs:
description:
- List of intercluster addresses of the destination cluster.
- Used as peer-addresses in source cluster.
- All these intercluster lifs should belong to the destination cluster.
version_added: "2.8"
aliases:
- dest_intercluster_lif
passphrase:
description:
- The arbitrary passphrase that matches the one given to the peer cluster.
source_cluster_name:
description:
- The name of the source cluster name in the peer relation to be deleted.
dest_cluster_name:
description:
- The name of the destination cluster name in the peer relation to be deleted.
- Required for delete
dest_hostname:
description:
- Destination cluster IP or hostname which needs to be peered
- Required to complete the peering process at destination cluster.
required: True
dest_username:
description:
- Destination username.
- Optional if this is same as source username.
dest_password:
description:
- Destination password.
- Optional if this is same as source password.
short_description: NetApp ONTAP Manage Cluster peering
version_added: "2.7"
'''
EXAMPLES = """
- name: Create cluster peer
na_ontap_cluster_peer:
state: present
source_intercluster_lifs: 1.2.3.4,1.2.3.5
dest_intercluster_lifs: 1.2.3.6,1.2.3.7
passphrase: XXXX
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
dest_hostname: "{{ dest_netapp_hostname }}"
- name: Delete cluster peer
na_ontap_cluster_peer:
state: absent
source_cluster_name: test-source-cluster
dest_cluster_name: test-dest-cluster
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
dest_hostname: "{{ dest_netapp_hostname }}"
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPClusterPeer(object):
"""
Class with cluster peer methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
source_intercluster_lifs=dict(required=False, type='list', aliases=['source_intercluster_lif']),
dest_intercluster_lifs=dict(required=False, type='list', aliases=['dest_intercluster_lif']),
passphrase=dict(required=False, type='str', no_log=True),
dest_hostname=dict(required=True, type='str'),
dest_username=dict(required=False, type='str'),
dest_password=dict(required=False, type='str', no_log=True),
source_cluster_name=dict(required=False, type='str'),
dest_cluster_name=dict(required=False, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_together=[['source_intercluster_lifs', 'dest_intercluster_lifs']],
required_if=[('state', 'absent', ['source_cluster_name', 'dest_cluster_name'])],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
# set destination server connection
self.module.params['hostname'] = self.parameters['dest_hostname']
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_server = netapp_utils.setup_na_ontap_zapi(module=self.module)
# reset to source host connection for asup logs
self.module.params['hostname'] = self.parameters['hostname']
def cluster_peer_get_iter(self, cluster):
"""
Compose NaElement object to query current source cluster using peer-cluster-name and peer-addresses parameters
:param cluster: type of cluster (source or destination)
:return: NaElement object for cluster-get-iter with query
"""
cluster_peer_get = netapp_utils.zapi.NaElement('cluster-peer-get-iter')
query = netapp_utils.zapi.NaElement('query')
cluster_peer_info = netapp_utils.zapi.NaElement('cluster-peer-info')
if cluster == 'source':
peer_lifs, peer_cluster = 'dest_intercluster_lifs', 'dest_cluster_name'
else:
peer_lifs, peer_cluster = 'source_intercluster_lifs', 'source_cluster_name'
if self.parameters.get(peer_lifs):
peer_addresses = netapp_utils.zapi.NaElement('peer-addresses')
for peer in self.parameters.get(peer_lifs):
peer_addresses.add_new_child('remote-inet-address', peer)
cluster_peer_info.add_child_elem(peer_addresses)
if self.parameters.get(peer_cluster):
cluster_peer_info.add_new_child('cluster-name', self.parameters[peer_cluster])
query.add_child_elem(cluster_peer_info)
cluster_peer_get.add_child_elem(query)
return cluster_peer_get
def cluster_peer_get(self, cluster):
"""
Get current cluster peer info
:param cluster: type of cluster (source or destination)
:return: Dictionary of current cluster peer details if query successful, else return None
"""
cluster_peer_get_iter = self.cluster_peer_get_iter(cluster)
result, cluster_info = None, dict()
if cluster == 'source':
server = self.server
else:
server = self.dest_server
try:
result = server.invoke_successfully(cluster_peer_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching cluster peer %s: %s'
% (self.parameters['dest_cluster_name'], to_native(error)),
exception=traceback.format_exc())
# return cluster peer details
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
cluster_peer_info = result.get_child_by_name('attributes-list').get_child_by_name('cluster-peer-info')
cluster_info['cluster_name'] = cluster_peer_info.get_child_content('cluster-name')
peers = cluster_peer_info.get_child_by_name('peer-addresses')
cluster_info['peer-addresses'] = [peer.get_content() for peer in peers.get_children()]
return cluster_info
return None
def cluster_peer_delete(self, cluster):
"""
Delete a cluster peer on source or destination
For source cluster, peer cluster-name = destination cluster name and vice-versa
:param cluster: type of cluster (source or destination)
:return:
"""
if cluster == 'source':
server, peer_cluster_name = self.server, self.parameters['dest_cluster_name']
else:
server, peer_cluster_name = self.dest_server, self.parameters['source_cluster_name']
cluster_peer_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-peer-delete', **{'cluster-name': peer_cluster_name})
try:
server.invoke_successfully(cluster_peer_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting cluster peer %s: %s'
% (peer_cluster_name, to_native(error)),
exception=traceback.format_exc())
def cluster_peer_create(self, cluster):
"""
Create a cluster peer on source or destination
For source cluster, peer addresses = destination inter-cluster LIFs and vice-versa
:param cluster: type of cluster (source or destination)
:return: None
"""
cluster_peer_create = netapp_utils.zapi.NaElement.create_node_with_children('cluster-peer-create')
if self.parameters.get('passphrase') is not None:
cluster_peer_create.add_new_child('passphrase', self.parameters['passphrase'])
peer_addresses = netapp_utils.zapi.NaElement('peer-addresses')
if cluster == 'source':
server, peer_address = self.server, self.parameters['dest_intercluster_lifs']
else:
server, peer_address = self.dest_server, self.parameters['source_intercluster_lifs']
for each in peer_address:
peer_addresses.add_new_child('remote-inet-address', each)
cluster_peer_create.add_child_elem(peer_addresses)
try:
server.invoke_successfully(cluster_peer_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating cluster peer %s: %s'
% (peer_address, to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to cluster peer
:return: None
"""
self.asup_log_for_cserver("na_ontap_cluster_peer")
source = self.cluster_peer_get('source')
destination = self.cluster_peer_get('destination')
source_action = self.na_helper.get_cd_action(source, self.parameters)
destination_action = self.na_helper.get_cd_action(destination, self.parameters)
self.na_helper.changed = False
# create only if expected cluster peer relation is not present on both source and destination clusters
if source_action == 'create' and destination_action == 'create':
self.cluster_peer_create('source')
self.cluster_peer_create('destination')
self.na_helper.changed = True
# delete peer relation in cluster where relation is present
else:
if source_action == 'delete':
self.cluster_peer_delete('source')
self.na_helper.changed = True
if destination_action == 'delete':
self.cluster_peer_delete('destination')
self.na_helper.changed = True
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
"""
Execute action
:return: None
"""
community_obj = NetAppONTAPClusterPeer()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,228 +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 = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- "Run system-cli commands on ONTAP"
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_command
short_description: NetApp ONTAP Run any cli command, the username provided needs to have console login permission.
version_added: "2.7"
options:
command:
description:
- a comma separated list containing the command and arguments.
required: true
privilege:
description:
- privilege level at which to run the command.
choices: ['admin', 'advanced']
default: admin
version_added: "2.8"
return_dict:
description:
- returns a parsesable dictionary instead of raw XML output
type: bool
default: false
version_added: "2.9"
'''
EXAMPLES = """
- name: run ontap cli command
na_ontap_command:
hostname: "{{ hostname }}"
username: "{{ admin username }}"
password: "{{ admin password }}"
command: ['version']
- name: run ontap cli command
na_ontap_command:
hostname: "{{ hostname }}"
username: "{{ admin username }}"
password: "{{ admin password }}"
command: ['network', 'interface', 'show']
privilege: 'admin'
return_dict: true
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPCommand(object):
''' calls a CLI command '''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
command=dict(required=True, type='list'),
privilege=dict(required=False, type='str', choices=['admin', 'advanced'], default='admin'),
return_dict=dict(required=False, type='bool', default=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
# set up state variables
self.command = parameters['command']
self.privilege = parameters['privilege']
self.return_dict = parameters['return_dict']
self.result_dict = dict()
self.result_dict['status'] = ""
self.result_dict['result_value'] = 0
self.result_dict['invoked_command'] = " ".join(self.command)
self.result_dict['stdout'] = ""
self.result_dict['stdout_lines'] = []
self.result_dict['xml_dict'] = dict()
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def run_command(self):
''' calls the ZAPI '''
self.asup_log_for_cserver("na_ontap_command: " + str(self.command))
command_obj = netapp_utils.zapi.NaElement("system-cli")
args_obj = netapp_utils.zapi.NaElement("args")
if self.return_dict:
args_obj.add_new_child('arg', 'set')
args_obj.add_new_child('arg', '-showseparator')
args_obj.add_new_child('arg', '"###"')
args_obj.add_new_child('arg', ';')
for arg in self.command:
args_obj.add_new_child('arg', arg)
command_obj.add_child_elem(args_obj)
command_obj.add_new_child('priv', self.privilege)
try:
output = self.server.invoke_successfully(command_obj, True)
if self.return_dict:
# Parseable dict output
retval = self.parse_xml_to_dict(output.to_string())
else:
# Raw XML output
retval = output.to_string()
return retval
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error running command %s: %s' %
(self.command, to_native(error)),
exception=traceback.format_exc())
def apply(self):
''' calls the command and returns raw output '''
changed = True
output = self.run_command()
self.module.exit_json(changed=changed, msg=output)
def parse_xml_to_dict(self, xmldata):
'''Parse raw XML from system-cli and create an Ansible parseable dictionary'''
xml_import_ok = True
xml_parse_ok = True
try:
import xml.parsers.expat
except ImportError:
self.result_dict['status'] = "XML parsing failed. Cannot import xml.parsers.expat!"
self.result_dict['stdout'] = str(xmldata)
xml_import_ok = False
if xml_import_ok:
xml_str = xmldata.decode('utf-8').replace('\n', '---')
xml_parser = xml.parsers.expat.ParserCreate()
xml_parser.StartElementHandler = self._start_element
xml_parser.CharacterDataHandler = self._char_data
xml_parser.EndElementHandler = self._end_element
try:
xml_parser.Parse(xml_str)
except xml.parsers.expat.ExpatError as errcode:
self.result_dict['status'] = "XML parsing failed: " + str(errcode)
self.result_dict['stdout'] = str(xmldata)
xml_parse_ok = False
if xml_parse_ok:
self.result_dict['status'] = self.result_dict['xml_dict']['results']['attrs']['status']
stdout_string = self._format_escaped_data(self.result_dict['xml_dict']['cli-output']['data'])
self.result_dict['stdout'] = stdout_string
for line in stdout_string.split('\n'):
stripped_line = line.strip()
if len(stripped_line) > 1:
self.result_dict['stdout_lines'].append(stripped_line)
self.result_dict['xml_dict']['cli-output']['data'] = stdout_string
self.result_dict['result_value'] = int(str(self.result_dict['xml_dict']['cli-result-value']['data']).replace("'", ""))
return self.result_dict
def _start_element(self, name, attrs):
''' Start XML element '''
self.result_dict['xml_dict'][name] = dict()
self.result_dict['xml_dict'][name]['attrs'] = attrs
self.result_dict['xml_dict'][name]['data'] = ""
self.result_dict['xml_dict']['active_element'] = name
self.result_dict['xml_dict']['last_element'] = ""
def _char_data(self, data):
''' Dump XML element data '''
self.result_dict['xml_dict'][str(self.result_dict['xml_dict']['active_element'])]['data'] = repr(data)
def _end_element(self, name):
self.result_dict['xml_dict']['last_element'] = name
self.result_dict['xml_dict']['active_element'] = ""
@classmethod
def _format_escaped_data(cls, datastring):
''' replace helper escape sequences '''
formatted_string = datastring.replace('------', '---').replace('---', '\n').replace("###", " ").strip()
retval_string = ""
for line in formatted_string.split('\n'):
stripped_line = line.strip()
if len(stripped_line) > 1:
retval_string += stripped_line + "\n"
return retval_string
def main():
"""
Execute action from playbook
"""
command = NetAppONTAPCommand()
command.apply()
if __name__ == '__main__':
main()

@ -1,205 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_disks
short_description: NetApp ONTAP Assign disks to nodes
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.7'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Assign all or part of disks to nodes.
options:
node:
required: true
description:
- It specifies the node to assign all visible unowned disks.
disk_count:
required: false
type: int
description:
- Total number of disks a node should own
version_added: '2.9'
'''
EXAMPLES = """
- name: Assign unowned disks
na_ontap_disks:
node: cluster-01
hostname: "{{ hostname }}"
username: "{{ admin username }}"
password: "{{ admin password }}"
- name: Assign specified total disks
na_ontap_disks:
node: cluster-01
disk_count: 56
hostname: "{{ hostname }}"
username: "{{ admin username }}"
password: "{{ admin 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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapDisks(object):
''' object initialize and class methods '''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
node=dict(required=True, type='str'),
disk_count=dict(required=False, type='int')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_unassigned_disk_count(self):
"""
Check for free disks
"""
disk_iter = netapp_utils.zapi.NaElement('storage-disk-get-iter')
disk_storage_info = netapp_utils.zapi.NaElement('storage-disk-info')
disk_raid_info = netapp_utils.zapi.NaElement('disk-raid-info')
disk_raid_info.add_new_child('container-type', 'unassigned')
disk_storage_info.add_child_elem(disk_raid_info)
disk_query = netapp_utils.zapi.NaElement('query')
disk_query.add_child_elem(disk_storage_info)
disk_iter.add_child_elem(disk_query)
try:
result = self.server.invoke_successfully(disk_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting disk information: %s'
% (to_native(error)),
exception=traceback.format_exc())
return int(result.get_child_content('num-records'))
def get_owned_disk_count(self):
"""
Check for owned disks
"""
disk_iter = netapp_utils.zapi.NaElement('storage-disk-get-iter')
disk_storage_info = netapp_utils.zapi.NaElement('storage-disk-info')
disk_ownership_info = netapp_utils.zapi.NaElement('disk-ownership-info')
disk_ownership_info.add_new_child('home-node-name', self.parameters['node'])
disk_storage_info.add_child_elem(disk_ownership_info)
disk_query = netapp_utils.zapi.NaElement('query')
disk_query.add_child_elem(disk_storage_info)
disk_iter.add_child_elem(disk_query)
try:
result = self.server.invoke_successfully(disk_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting disk information: %s'
% (to_native(error)),
exception=traceback.format_exc())
return int(result.get_child_content('num-records'))
def disk_assign(self, needed_disks):
"""
Set node as disk owner.
"""
if needed_disks > 0:
assign_disk = netapp_utils.zapi.NaElement.create_node_with_children(
'disk-sanown-assign', **{'owner': self.parameters['node'],
'disk-count': str(needed_disks)})
else:
assign_disk = netapp_utils.zapi.NaElement.create_node_with_children(
'disk-sanown-assign', **{'node-name': self.parameters['node'],
'all': 'true'})
try:
self.server.invoke_successfully(assign_disk,
enable_tunneling=True)
return True
except netapp_utils.zapi.NaApiError as error:
if to_native(error.code) == "13001":
# Error 13060 denotes aggregate is already online
return False
else:
self.module.fail_json(msg='Error assigning disks %s' %
(to_native(error)),
exception=traceback.format_exc())
def apply(self):
'''Apply action to disks'''
changed = False
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_disks", cserver)
# check if anything needs to be changed (add/delete/update)
unowned_disks = self.get_unassigned_disk_count()
owned_disks = self.get_owned_disk_count()
if 'disk_count' in self.parameters:
if self.parameters['disk_count'] < owned_disks:
self.module.fail_json(msg="Fewer disks than are currently owned was requested. "
"This module does not do any disk removing. "
"All disk removing will need to be done manually.")
if self.parameters['disk_count'] > owned_disks + unowned_disks:
self.module.fail_json(msg="Not enough unowned disks remain to fulfill request")
if unowned_disks >= 1:
if 'disk_count' in self.parameters:
if self.parameters['disk_count'] > owned_disks:
needed_disks = self.parameters['disk_count'] - owned_disks
self.disk_assign(needed_disks)
changed = True
else:
self.disk_assign(0)
changed = True
self.module.exit_json(changed=changed)
def main():
''' Create object and call apply '''
obj_aggr = NetAppOntapDisks()
obj_aggr.apply()
if __name__ == '__main__':
main()

@ -1,294 +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_ontap_dns
short_description: NetApp ONTAP Create, delete, modify DNS servers.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.7'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create, delete, modify DNS servers.
options:
state:
description:
- Whether the DNS servers should be enabled for the given vserver.
choices: ['present', 'absent']
default: present
vserver:
description:
- The name of the vserver to use.
required: true
domains:
description:
- List of DNS domains such as 'sales.bar.com'. The first domain is the one that the Vserver belongs to.
nameservers:
description:
- List of IPv4 addresses of name servers such as '123.123.123.123'.
skip_validation:
type: bool
description:
- By default, all nameservers are checked to validate they are available to resolve.
- If you DNS servers are not yet installed or momentarily not available, you can set this option to 'true'
- to bypass the check for all servers specified in nameservers field.
version_added: '2.8'
'''
EXAMPLES = """
- name: create DNS
na_ontap_dns:
state: present
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
vserver: "{{vservername}}"
domains: sales.bar.com
nameservers: 10.193.0.250,10.192.0.250
skip_validation: true
"""
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
from ansible.module_utils.netapp import OntapRestAPI
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapDns(object):
"""
Enable and Disable dns
"""
def __init__(self):
self.use_rest = False
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
domains=dict(required=False, type='list'),
nameservers=dict(required=False, type='list'),
skip_validation=dict(required=False, type='bool')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[('state', 'present', ['domains', 'nameservers'])],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
# REST API should be used for ONTAP 9.6 or higher, ZAPI for lower version
self.restApi = OntapRestAPI(self.module)
# some attributes are not supported in earlier REST implementation
unsupported_rest_properties = ['skip_validation']
used_unsupported_rest_properties = [x for x in unsupported_rest_properties if x in self.parameters]
self.use_rest, error = self.restApi.is_rest(used_unsupported_rest_properties)
if error is not None:
self.module.fail_json(msg=error)
if not self.use_rest:
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
return
def create_dns(self):
"""
Create DNS server
:return: none
"""
if self.use_rest:
api = 'name-services/dns'
params = {}
params['domains'] = self.parameters['domains']
params['servers'] = self.parameters['nameservers']
params['svm'] = {'name': self.parameters['vserver']}
message, error = self.restApi.post(api, params)
if error:
self.module.fail_json(msg=error)
else:
dns = netapp_utils.zapi.NaElement('net-dns-create')
nameservers = netapp_utils.zapi.NaElement('name-servers')
domains = netapp_utils.zapi.NaElement('domains')
for each in self.parameters['nameservers']:
ip_address = netapp_utils.zapi.NaElement('ip-address')
ip_address.set_content(each)
nameservers.add_child_elem(ip_address)
dns.add_child_elem(nameservers)
for each in self.parameters['domains']:
domain = netapp_utils.zapi.NaElement('string')
domain.set_content(each)
domains.add_child_elem(domain)
dns.add_child_elem(domains)
if self.parameters.get('skip_validation'):
validation = netapp_utils.zapi.NaElement('skip-config-validation')
validation.set_content(str(self.parameters['skip_validation']))
dns.add_child_elem(validation)
try:
self.server.invoke_successfully(dns, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating dns: %s' %
(to_native(error)),
exception=traceback.format_exc())
def destroy_dns(self, dns_attrs):
"""
Destroys an already created dns
:return:
"""
if self.use_rest:
uuid = dns_attrs['records'][0]['svm']['uuid']
api = 'name-services/dns/' + uuid
data = None
message, error = self.restApi.delete(api, data)
if error:
self.module.fail_json(msg=error)
else:
try:
self.server.invoke_successfully(netapp_utils.zapi.NaElement('net-dns-destroy'), True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error destroying dns %s' %
(to_native(error)),
exception=traceback.format_exc())
def get_dns(self):
if self.use_rest:
api = "name-services/dns"
params = {'fields': 'domains,servers,svm',
"svm.name": self.parameters['vserver']}
message, error = self.restApi.get(api, params)
if error:
self.module.fail_json(msg=error)
if len(message.keys()) == 0:
message = None
elif 'records' in message and len(message['records']) == 0:
message = None
elif 'records' not in message or len(message['records']) != 1:
error = "Unexpected response from %s: %s" % (api, repr(message))
self.module.fail_json(msg=error)
return message
else:
dns_obj = netapp_utils.zapi.NaElement('net-dns-get')
try:
result = self.server.invoke_successfully(dns_obj, True)
except netapp_utils.zapi.NaApiError as error:
if to_native(error.code) == "15661":
# 15661 is object not found
return None
else:
self.module.fail_json(msg=to_native(
error), exception=traceback.format_exc())
# read data for modify
attrs = dict()
attributes = result.get_child_by_name('attributes')
dns_info = attributes.get_child_by_name('net-dns-info')
nameservers = dns_info.get_child_by_name('name-servers')
attrs['nameservers'] = [each.get_content() for each in nameservers.get_children()]
domains = dns_info.get_child_by_name('domains')
attrs['domains'] = [each.get_content() for each in domains.get_children()]
attrs['skip_validation'] = dns_info.get_child_by_name('skip-config-validation')
return attrs
def modify_dns(self, dns_attrs):
if self.use_rest:
changed = False
params = {}
if dns_attrs['records'][0]['servers'] != self.parameters['nameservers']:
changed = True
params['servers'] = self.parameters['nameservers']
if dns_attrs['records'][0]['domains'] != self.parameters['domains']:
changed = True
params['domains'] = self.parameters['domains']
if changed:
uuid = dns_attrs['records'][0]['svm']['uuid']
api = "name-services/dns/" + uuid
message, error = self.restApi.patch(api, params)
if error:
self.module.fail_json(msg=error)
else:
changed = False
dns = netapp_utils.zapi.NaElement('net-dns-modify')
if dns_attrs['nameservers'] != self.parameters['nameservers']:
changed = True
nameservers = netapp_utils.zapi.NaElement('name-servers')
for each in self.parameters['nameservers']:
ip_address = netapp_utils.zapi.NaElement('ip-address')
ip_address.set_content(each)
nameservers.add_child_elem(ip_address)
dns.add_child_elem(nameservers)
if dns_attrs['domains'] != self.parameters['domains']:
changed = True
domains = netapp_utils.zapi.NaElement('domains')
for each in self.parameters['domains']:
domain = netapp_utils.zapi.NaElement('string')
domain.set_content(each)
domains.add_child_elem(domain)
dns.add_child_elem(domains)
if changed:
if self.parameters.get('skip_validation'):
validation = netapp_utils.zapi.NaElement('skip-config-validation')
validation.set_content(str(self.parameters['skip_validation']))
dns.add_child_elem(validation)
try:
self.server.invoke_successfully(dns, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying dns %s' %
(to_native(error)), exception=traceback.format_exc())
return changed
def apply(self):
# asup logging
if not self.use_rest:
netapp_utils.ems_log_event("na_ontap_dns", self.server)
dns_attrs = self.get_dns()
changed = False
if self.parameters['state'] == 'present':
if dns_attrs is not None:
changed = self.modify_dns(dns_attrs)
else:
self.create_dns()
changed = True
else:
if dns_attrs is not None:
self.destroy_dns(dns_attrs)
changed = True
self.module.exit_json(changed=changed)
def main():
"""
Create, Delete, Modify DNS servers.
"""
obj = NetAppOntapDns()
obj.apply()
if __name__ == '__main__':
main()

@ -1,231 +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_ontap_export_policy
short_description: NetApp ONTAP manage export-policy
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or destroy or rename export-policies on ONTAP
options:
state:
description:
- Whether the specified export policy should exist or not.
choices: ['present', 'absent']
default: present
name:
description:
- The name of the export-policy to manage.
required: true
from_name:
description:
- The name of the export-policy to be renamed.
version_added: '2.7'
vserver:
description:
- Name of the vserver to use.
'''
EXAMPLES = """
- name: Create Export Policy
na_ontap_export_policy:
state: present
name: ansiblePolicyName
vserver: vs_hack
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Rename Export Policy
na_ontap_export_policy:
action: present
from_name: ansiblePolicyName
vserver: vs_hack
name: newPolicyName
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete Export Policy
na_ontap_export_policy:
state: absent
name: ansiblePolicyName
vserver: vs_hack
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPExportPolicy(object):
"""
Class with export policy methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str', default=None),
vserver=dict(required=False, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['vserver'])
],
supports_check_mode=True
)
parameters = self.module.params
# set up state variables
self.state = parameters['state']
self.name = parameters['name']
self.from_name = parameters['from_name']
self.vserver = parameters['vserver']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def get_export_policy(self, name=None):
"""
Return details about the export-policy
:param:
name : Name of the export-policy
:return: Details about the export-policy. None if not found.
:rtype: dict
"""
if name is None:
name = self.name
export_policy_iter = netapp_utils.zapi.NaElement('export-policy-get-iter')
export_policy_info = netapp_utils.zapi.NaElement('export-policy-info')
export_policy_info.add_new_child('policy-name', name)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(export_policy_info)
export_policy_iter.add_child_elem(query)
result = self.server.invoke_successfully(export_policy_iter, True)
return_value = None
# check if query returns the expected export-policy
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
export_policy = result.get_child_by_name('attributes-list').get_child_by_name('export-policy-info').get_child_by_name('policy-name')
return_value = {
'policy-name': export_policy
}
return return_value
def create_export_policy(self):
"""
Creates an export policy
"""
export_policy_create = netapp_utils.zapi.NaElement.create_node_with_children(
'export-policy-create', **{'policy-name': self.name})
try:
self.server.invoke_successfully(export_policy_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating export-policy %s: %s'
% (self.name, to_native(error)),
exception=traceback.format_exc())
def delete_export_policy(self):
"""
Delete export-policy
"""
export_policy_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'export-policy-destroy', **{'policy-name': self.name, })
try:
self.server.invoke_successfully(export_policy_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting export-policy %s: %s'
% (self.name,
to_native(error)), exception=traceback.format_exc())
def rename_export_policy(self):
"""
Rename the export-policy.
"""
export_policy_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'export-policy-rename', **{'policy-name': self.from_name,
'new-policy-name': self.name})
try:
self.server.invoke_successfully(export_policy_rename,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error renaming export-policy %s:%s'
% (self.name, to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to export-policy
"""
changed = False
export_policy_exists = False
netapp_utils.ems_log_event("na_ontap_export_policy", self.server)
rename_flag = False
export_policy_details = self.get_export_policy()
if export_policy_details:
export_policy_exists = True
if self.state == 'absent': # delete
changed = True
else:
if self.state == 'present': # create or rename
if self.from_name is not None:
if self.get_export_policy(self.from_name):
changed = True
rename_flag = True
else:
self.module.fail_json(msg='Error renaming export-policy %s: does not exists' % self.from_name)
else: # create
changed = True
if changed:
if self.module.check_mode:
pass
else:
if self.state == 'present': # execute create or rename_export_policy
if rename_flag:
self.rename_export_policy()
else:
self.create_export_policy()
elif self.state == 'absent': # execute delete
self.delete_export_policy()
self.module.exit_json(changed=changed)
def main():
"""
Execute action
"""
export_policy = NetAppONTAPExportPolicy()
export_policy.apply()
if __name__ == '__main__':
main()

@ -1,431 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_export_policy_rule
short_description: NetApp ONTAP manage export policy rules
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or delete or modify export rules in ONTAP
options:
state:
description:
- Whether the specified export policy rule should exist or not.
required: false
choices: ['present', 'absent']
default: present
name:
description:
- The name of the export rule to manage.
required: True
aliases:
- policy_name
client_match:
description:
- List of Client Match host names, IP Addresses, Netgroups, or Domains
- If rule_index is not provided, client_match is used as a key to fetch current rule to determine create,delete,modify actions.
If a rule with provided client_match exists, a new rule will not be created, but the existing rule will be modified or deleted.
If a rule with provided client_match doesn't exist, a new rule will be created if state is present.
ro_rule:
description:
- List of Read only access specifications for the rule
choices: ['any','none','never','krb5','krb5i','krb5p','ntlm','sys']
rw_rule:
description:
- List of Read Write access specifications for the rule
choices: ['any','none','never','krb5','krb5i','krb5p','ntlm','sys']
super_user_security:
description:
- List of Read Write access specifications for the rule
choices: ['any','none','never','krb5','krb5i','krb5p','ntlm','sys']
allow_suid:
description:
- If 'true', NFS server will honor SetUID bits in SETATTR operation. Default value on creation is 'true'
type: bool
protocol:
description:
- List of Client access protocols.
- Default value is set to 'any' during create.
choices: [any,nfs,nfs3,nfs4,cifs,flexcache]
rule_index:
description:
- rule index of the export policy
vserver:
description:
- Name of the vserver to use.
required: true
'''
EXAMPLES = """
- name: Create ExportPolicyRule
na_ontap_export_policy_rule:
state: present
name: default123
vserver: ci_dev
client_match: 0.0.0.0/0,1.1.1.0/24
ro_rule: krb5,krb5i
rw_rule: any
protocol: nfs,nfs3
super_user_security: any
allow_suid: true
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Modify ExportPolicyRule
na_ontap_export_policy_rule:
state: present
name: default123
rule_index: 100
client_match: 0.0.0.0/0
ro_rule: ntlm
rw_rule: any
protocol: any
allow_suid: false
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete ExportPolicyRule
na_ontap_export_policy_rule:
state: absent
name: default123
rule_index: 100
vserver: ci_dev
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
import json
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppontapExportRule(object):
''' object initialize and class methods '''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str', aliases=['policy_name']),
protocol=dict(required=False,
type='list', default=None,
choices=['any', 'nfs', 'nfs3', 'nfs4', 'cifs', 'flexcache']),
client_match=dict(required=False, type='list'),
ro_rule=dict(required=False,
type='list', default=None,
choices=['any', 'none', 'never', 'krb5', 'krb5i', 'krb5p', 'ntlm', 'sys']),
rw_rule=dict(required=False,
type='list', default=None,
choices=['any', 'none', 'never', 'krb5', 'krb5i', 'krb5p', 'ntlm', 'sys']),
super_user_security=dict(required=False,
type='list', default=None,
choices=['any', 'none', 'never', 'krb5', 'krb5i', 'krb5p', 'ntlm', 'sys']),
allow_suid=dict(required=False, type='bool'),
rule_index=dict(required=False, type='int'),
vserver=dict(required=True, type='str'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.set_playbook_zapi_key_map()
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.parameters['vserver'])
def set_playbook_zapi_key_map(self):
self.na_helper.zapi_string_keys = {
'client_match': 'client-match',
'name': 'policy-name'
}
self.na_helper.zapi_list_keys = {
'protocol': ('protocol', 'access-protocol'),
'ro_rule': ('ro-rule', 'security-flavor'),
'rw_rule': ('rw-rule', 'security-flavor'),
'super_user_security': ('super-user-security', 'security-flavor'),
}
self.na_helper.zapi_bool_keys = {
'allow_suid': 'is-allow-set-uid-enabled'
}
self.na_helper.zapi_int_keys = {
'rule_index': 'rule-index'
}
def set_query_parameters(self):
"""
Return dictionary of query parameters and
:return:
"""
query = {
'policy-name': self.parameters['name'],
'vserver': self.parameters['vserver']
}
if self.parameters.get('rule_index'):
query['rule-index'] = self.parameters['rule_index']
elif self.parameters.get('client_match'):
query['client-match'] = self.parameters['client_match']
else:
self.module.fail_json(
msg="Need to specify at least one of the rule_index and client_match option.")
attributes = {
'query': {
'export-rule-info': query
}
}
return attributes
def get_export_policy_rule(self):
"""
Return details about the export policy rule
:param:
name : Name of the export_policy
:return: Details about the export_policy. None if not found.
:rtype: dict
"""
current, result = None, None
rule_iter = netapp_utils.zapi.NaElement('export-rule-get-iter')
rule_iter.translate_struct(self.set_query_parameters())
try:
result = self.server.invoke_successfully(rule_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting export policy rule %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
if result is not None and \
result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
current = dict()
rule_info = result.get_child_by_name('attributes-list').get_child_by_name('export-rule-info')
for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
current[item_key] = rule_info.get_child_content(zapi_key)
for item_key, zapi_key in self.na_helper.zapi_bool_keys.items():
current[item_key] = self.na_helper.get_value_for_bool(from_zapi=True,
value=rule_info[zapi_key])
for item_key, zapi_key in self.na_helper.zapi_int_keys.items():
current[item_key] = self.na_helper.get_value_for_int(from_zapi=True,
value=rule_info[zapi_key])
for item_key, zapi_key in self.na_helper.zapi_list_keys.items():
parent, dummy = zapi_key
current[item_key] = self.na_helper.get_value_for_list(from_zapi=True,
zapi_parent=rule_info.get_child_by_name(parent))
current['num_records'] = int(result.get_child_content('num-records'))
if not self.parameters.get('rule_index'):
self.parameters['rule_index'] = current['rule_index']
return current
def get_export_policy(self):
"""
Return details about the export-policy
:param:
name : Name of the export-policy
:return: Details about the export-policy. None if not found.
:rtype: dict
"""
export_policy_iter = netapp_utils.zapi.NaElement('export-policy-get-iter')
attributes = {
'query': {
'export-policy-info': {
'policy-name': self.parameters['name'],
'vserver': self.parameters['vserver']
}
}
}
export_policy_iter.translate_struct(attributes)
try:
result = self.server.invoke_successfully(export_policy_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting export policy %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) == 1:
return result
return None
def add_parameters_for_create_or_modify(self, na_element_object, values):
"""
Add children node for create or modify NaElement object
:param na_element_object: modify or create NaElement object
:param values: dictionary of cron values to be added
:return: None
"""
for key in values:
if key in self.na_helper.zapi_string_keys:
zapi_key = self.na_helper.zapi_string_keys.get(key)
na_element_object[zapi_key] = values[key]
elif key in self.na_helper.zapi_list_keys:
parent_key, child_key = self.na_helper.zapi_list_keys.get(key)
na_element_object.add_child_elem(self.na_helper.get_value_for_list(from_zapi=False,
zapi_parent=parent_key,
zapi_child=child_key,
data=values[key]))
elif key in self.na_helper.zapi_int_keys:
zapi_key = self.na_helper.zapi_int_keys.get(key)
na_element_object[zapi_key] = self.na_helper.get_value_for_int(from_zapi=False,
value=values[key])
elif key in self.na_helper.zapi_bool_keys:
zapi_key = self.na_helper.zapi_bool_keys.get(key)
na_element_object[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False,
value=values[key])
def create_export_policy_rule(self):
"""
create rule for the export policy.
"""
for key in ['client_match', 'ro_rule', 'rw_rule']:
if self.parameters.get(key) is None:
self.module.fail_json(msg='Error: Missing required param for creating export policy rule %s' % key)
export_rule_create = netapp_utils.zapi.NaElement('export-rule-create')
self.add_parameters_for_create_or_modify(export_rule_create, self.parameters)
try:
self.server.invoke_successfully(export_rule_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating export policy rule %s: %s'
% (self.parameters['name'], to_native(error)), exception=traceback.format_exc())
def create_export_policy(self):
"""
Creates an export policy
"""
export_policy_create = netapp_utils.zapi.NaElement.create_node_with_children(
'export-policy-create', **{'policy-name': self.parameters['name']})
try:
self.server.invoke_successfully(export_policy_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating export-policy %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_export_policy_rule(self, rule_index):
"""
delete rule for the export policy.
"""
export_rule_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'export-rule-destroy', **{'policy-name': self.parameters['name'],
'rule-index': str(rule_index)})
try:
self.server.invoke_successfully(export_rule_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting export policy rule %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_export_policy_rule(self, params):
'''
Modify an existing export policy rule
:param params: dict() of attributes with desired values
:return: None
'''
export_rule_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'export-rule-modify', **{'policy-name': self.parameters['name'],
'rule-index': str(self.parameters['rule_index'])})
self.add_parameters_for_create_or_modify(export_rule_modify, params)
try:
self.server.invoke_successfully(export_rule_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying allow_suid %s: %s'
% (self.parameters['allow_suid'], to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
netapp_utils.ems_log_event("na_ontap_export_policy_rules", self.server)
def apply(self):
''' Apply required action from the play'''
self.autosupport_log()
# convert client_match list to comma-separated string
if self.parameters.get('client_match') is not None:
self.parameters['client_match'] = ','.join(self.parameters['client_match'])
self.parameters['client_match'] = self.parameters['client_match'].replace(' ', '')
current, modify = self.get_export_policy_rule(), None
action = self.na_helper.get_cd_action(current, self.parameters)
if action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
# create export policy (if policy doesn't exist) only when changed=True
if not self.get_export_policy():
self.create_export_policy()
if action == 'create':
self.create_export_policy_rule()
elif action == 'delete':
if current['num_records'] > 1:
self.module.fail_json(msg='Multiple export policy rules exist.'
'Please specify a rule_index to delete')
self.delete_export_policy_rule(current['rule_index'])
elif modify:
self.modify_export_policy_rule(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
''' Create object and call apply '''
rule_obj = NetAppontapExportRule()
rule_obj.apply()
if __name__ == '__main__':
main()

@ -1,210 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_fcp
short_description: NetApp ONTAP Start, Stop and Enable FCP services.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.7'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Start, Stop and Enable FCP services.
options:
state:
description:
- Whether the FCP should be enabled or not.
choices: ['present', 'absent']
default: present
status:
description:
- Whether the FCP should be up or down
choices: ['up', 'down']
default: up
vserver:
description:
- The name of the vserver to use.
required: true
'''
EXAMPLES = """
- name: create FCP
na_ontap_fcp:
state: present
status: down
hostname: "{{hostname}}"
username: "{{username}}"
password: "{{password}}"
vserver: "{{vservername}}"
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapFCP(object):
"""
Enable and Disable FCP
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
status=dict(required=False, choices=['up', 'down'], default='up')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
return
def create_fcp(self):
"""
Create's and Starts an FCP
:return: none
"""
try:
self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-create'), True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating FCP: %s' %
(to_native(error)),
exception=traceback.format_exc())
def start_fcp(self):
"""
Starts an existing FCP
:return: none
"""
try:
self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-start'), True)
except netapp_utils.zapi.NaApiError as error:
# Error 13013 denotes fcp service already started.
if to_native(error.code) == "13013":
return None
else:
self.module.fail_json(msg='Error starting FCP %s' % (to_native(error)),
exception=traceback.format_exc())
def stop_fcp(self):
"""
Steps an Existing FCP
:return: none
"""
try:
self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-stop'), True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error Stopping FCP %s' %
(to_native(error)),
exception=traceback.format_exc())
def destroy_fcp(self):
"""
Destroys an already stopped FCP
:return:
"""
try:
self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-destroy'), True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error destroying FCP %s' %
(to_native(error)),
exception=traceback.format_exc())
def get_fcp(self):
fcp_obj = netapp_utils.zapi.NaElement('fcp-service-get-iter')
fcp_info = netapp_utils.zapi.NaElement('fcp-service-info')
fcp_info.add_new_child('vserver', self.parameters['vserver'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(fcp_info)
fcp_obj.add_child_elem(query)
result = self.server.invoke_successfully(fcp_obj, True)
# There can only be 1 FCP per vserver. If true, one is set up, else one isn't set up
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
return True
else:
return False
def current_status(self):
try:
status = self.server.invoke_successfully(netapp_utils.zapi.NaElement('fcp-service-status'), True)
return status.get_child_content('is-available') == 'true'
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error destroying FCP: %s' %
(to_native(error)),
exception=traceback.format_exc())
def apply(self):
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_fcp", cserver)
exists = self.get_fcp()
changed = False
if self.parameters['state'] == 'present':
if exists:
if self.parameters['status'] == 'up':
if not self.current_status():
self.start_fcp()
changed = True
else:
if self.current_status():
self.stop_fcp()
changed = True
else:
self.create_fcp()
if self.parameters['status'] == 'up':
self.start_fcp()
elif self.parameters['status'] == 'down':
self.stop_fcp()
changed = True
else:
if exists:
if self.current_status():
self.stop_fcp()
self.destroy_fcp()
changed = True
self.module.exit_json(changed=changed)
def main():
"""
Start, Stop and Enable FCP services.
"""
obj = NetAppOntapFCP()
obj.apply()
if __name__ == '__main__':
main()

@ -1,351 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_firewall_policy
short_description: NetApp ONTAP Manage a firewall policy
version_added: '2.7'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Configure firewall on an ONTAP node and manage firewall policy for an ONTAP SVM
extends_documentation_fragment:
- netapp.na_ontap
requirements:
- Python package ipaddress. Install using 'pip install ipaddress'
options:
state:
description:
- Whether to set up a firewall policy or not
choices: ['present', 'absent']
default: present
allow_list:
description:
- A list of IPs and masks to use.
- The host bits of the IP addresses used in this list must be set to 0.
policy:
description:
- A policy name for the firewall policy
service:
description:
- The service to apply the policy to
choices: ['dns', 'http', 'https', 'ndmp', 'ndmps', 'ntp', 'rsh', 'snmp', 'ssh', 'telnet']
vserver:
description:
- The Vserver to apply the policy to.
enable:
description:
- enable firewall on a node
choices: ['enable', 'disable']
logging:
description:
- enable logging for firewall on a node
choices: ['enable', 'disable']
node:
description:
- The node to run the firewall configuration on
'''
EXAMPLES = """
- name: create firewall Policy
na_ontap_firewall_policy:
state: present
allow_list: [1.2.3.0/24,1.3.0.0/16]
policy: pizza
service: http
vserver: ci_dev
hostname: "{{ netapp hostname }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
- name: Modify firewall Policy
na_ontap_firewall_policy:
state: present
allow_list: [1.5.3.0/24]
policy: pizza
service: http
vserver: ci_dev
hostname: "{{ netapp hostname }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
- name: Destroy firewall Policy
na_ontap_firewall_policy:
state: absent
policy: pizza
service: http
vserver: ci_dev
hostname: "{{ netapp hostname }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
- name: Enable firewall and logging on a node
na_ontap_firewall_policy:
node: test-vsim1
enable: enable
logging: enable
hostname: "{{ netapp hostname }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
try:
import ipaddress
HAS_IPADDRESS_LIB = True
except ImportError:
HAS_IPADDRESS_LIB = False
import sys
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPFirewallPolicy(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
allow_list=dict(required=False, type="list"),
policy=dict(required=False, type='str'),
service=dict(required=False, type='str', choices=['dns', 'http', 'https', 'ndmp',
'ndmps', 'ntp', 'rsh', 'snmp', 'ssh', 'telnet']),
vserver=dict(required=False, type="str"),
enable=dict(required=False, type="str", choices=['enable', 'disable']),
logging=dict(required=False, type="str", choices=['enable', 'disable']),
node=dict(required=False, type="str")
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_together=(['policy', 'service', 'vserver'],
['enable', 'node']
),
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
if HAS_IPADDRESS_LIB is False:
self.module.fail_json(msg="the python ipaddress lib is required for this module")
return
def validate_ip_addresses(self):
'''
Validate if the given IP address is a network address (i.e. it's host bits are set to 0)
ONTAP doesn't validate if the host bits are set,
and hence doesn't add a new address unless the IP is from a different network.
So this validation allows the module to be idempotent.
:return: None
'''
for ip in self.parameters['allow_list']:
# create an IPv4 object for current IP address
if sys.version_info[0] >= 3:
ip_addr = str(ip)
else:
ip_addr = unicode(ip) # pylint: disable=undefined-variable
# get network address from netmask, throw exception if address is not a network address
try:
ipaddress.ip_network(ip_addr)
except ValueError as exc:
self.module.fail_json(msg='Error: Invalid IP address value for allow_list parameter.'
'Please specify a network address without host bits set: %s'
% (to_native(exc)))
def get_firewall_policy(self):
"""
Get a firewall policy
:return: returns a firewall policy object, or returns False if there are none
"""
net_firewall_policy_obj = netapp_utils.zapi.NaElement("net-firewall-policy-get-iter")
attributes = {
'query': {
'net-firewall-policy-info': self.firewall_policy_attributes()
}
}
net_firewall_policy_obj.translate_struct(attributes)
try:
result = self.server.invoke_successfully(net_firewall_policy_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error getting firewall policy %s:%s" % (self.parameters['policy'],
to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
attributes_list = result.get_child_by_name('attributes-list')
policy_info = attributes_list.get_child_by_name('net-firewall-policy-info')
ips = self.na_helper.get_value_for_list(from_zapi=True,
zapi_parent=policy_info.get_child_by_name('allow-list'))
return {
'service': policy_info['service'],
'allow_list': ips}
return None
def create_firewall_policy(self):
"""
Create a firewall policy for given vserver
:return: None
"""
net_firewall_policy_obj = netapp_utils.zapi.NaElement("net-firewall-policy-create")
net_firewall_policy_obj.translate_struct(self.firewall_policy_attributes())
if self.parameters.get('allow_list'):
self.validate_ip_addresses()
net_firewall_policy_obj.add_child_elem(self.na_helper.get_value_for_list(from_zapi=False,
zapi_parent='allow-list',
zapi_child='ip-and-mask',
data=self.parameters['allow_list'])
)
try:
self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error creating Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc())
def destroy_firewall_policy(self):
"""
Destroy a Firewall Policy from a vserver
:return: None
"""
net_firewall_policy_obj = netapp_utils.zapi.NaElement("net-firewall-policy-destroy")
net_firewall_policy_obj.translate_struct(self.firewall_policy_attributes())
try:
self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error destroying Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc())
def modify_firewall_policy(self, modify):
"""
Modify a firewall Policy on a vserver
:return: none
"""
self.validate_ip_addresses()
net_firewall_policy_obj = netapp_utils.zapi.NaElement("net-firewall-policy-modify")
net_firewall_policy_obj.translate_struct(self.firewall_policy_attributes())
net_firewall_policy_obj.add_child_elem(self.na_helper.get_value_for_list(from_zapi=False,
zapi_parent='allow-list',
zapi_child='ip-and-mask',
data=modify['allow_list']))
try:
self.server.invoke_successfully(net_firewall_policy_obj, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error modifying Firewall Policy: %s" % (to_native(error)), exception=traceback.format_exc())
def firewall_policy_attributes(self):
return {
'policy': self.parameters['policy'],
'service': self.parameters['service'],
'vserver': self.parameters['vserver'],
}
def get_firewall_config_for_node(self):
"""
Get firewall configuration on the node
:return: dict() with firewall config details
"""
if self.parameters.get('logging'):
if self.parameters.get('node') is None:
self.module.fail_json(msg='Error: Missing parameter \'node\' to modify firewall logging')
net_firewall_config_obj = netapp_utils.zapi.NaElement("net-firewall-config-get")
net_firewall_config_obj.add_new_child('node-name', self.parameters['node'])
try:
result = self.server.invoke_successfully(net_firewall_config_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error getting Firewall Configuration: %s" % (to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('attributes'):
firewall_info = result['attributes'].get_child_by_name('net-firewall-config-info')
return {'enable': self.change_status_to_bool(firewall_info.get_child_content('is-enabled'), to_zapi=False),
'logging': self.change_status_to_bool(firewall_info.get_child_content('is-logging'), to_zapi=False)}
return None
def modify_firewall_config(self, modify):
"""
Modify the configuration of a firewall on node
:return: None
"""
net_firewall_config_obj = netapp_utils.zapi.NaElement("net-firewall-config-modify")
net_firewall_config_obj.add_new_child('node-name', self.parameters['node'])
if modify.get('enable'):
net_firewall_config_obj.add_new_child('is-enabled', self.change_status_to_bool(self.parameters['enable']))
if modify.get('logging'):
net_firewall_config_obj.add_new_child('is-logging', self.change_status_to_bool(self.parameters['logging']))
try:
self.server.invoke_successfully(net_firewall_config_obj, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error modifying Firewall Config: %s" % (to_native(error)),
exception=traceback.format_exc())
def change_status_to_bool(self, input, to_zapi=True):
if to_zapi:
return 'true' if input == 'enable' else 'false'
else:
return 'enable' if input == 'true' else 'disable'
def autosupport_log(self):
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_firewall_policy", cserver)
def apply(self):
self.autosupport_log()
cd_action, modify, modify_config = None, None, None
if self.parameters.get('policy'):
current = self.get_firewall_policy()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.parameters.get('node'):
current_config = self.get_firewall_config_for_node()
# firewall config for a node is always present, we cannot create or delete a firewall on a node
modify_config = self.na_helper.get_modified_attributes(current_config, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_firewall_policy()
elif cd_action == 'delete':
self.destroy_firewall_policy()
else:
if modify:
self.modify_firewall_policy(modify)
if modify_config:
self.modify_firewall_config(modify_config)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Execute action from playbook
:return: nothing
"""
cg_obj = NetAppONTAPFirewallPolicy()
cg_obj.apply()
if __name__ == '__main__':
main()

@ -1,461 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Update ONTAP service-prosessor firmware
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_firmware_upgrade
options:
state:
description:
- Whether the specified ONTAP firmware should be upgraded or not.
default: present
type: str
node:
description:
- Node on which the device is located.
type: str
required: true
clear_logs:
description:
- Clear logs on the device after update. Default value is true
type: bool
default: true
package:
description:
- Name of the package file containing the firmware to be installed. Not required when -baseline is true.
type: str
shelf_module_fw:
description:
- Shelf module firmware to be updated to.
type: str
disk_fw:
description:
- disk firmware to be updated to.
type: str
update_type:
description:
- Type of firmware update to be performed. Options include serial_full, serial_differential, network_full.
type: str
install_baseline_image:
description:
- Install the version packaged with ONTAP if this parameter is set to true. Otherwise, package must be used to specify the package to install.
type: bool
default: false
firmware_type:
description:
- Type of firmware to be upgraded. Options include shelf, ACP, service-processor, and disk.
- For shelf firmware upgrade the operation is asynchronous, and therefore returns no errors that might occur during the download process.
- Shelf firmware upgrade is idempotent if shelf_module_fw is provided .
- disk firmware upgrade is idempotent if disk_fw is provided .
- With check mode, SP, ACP, disk, and shelf firmware upgrade is not idempotent.
- This operation will only update firmware on shelves/disk that do not have the latest firmware-revision.
choices: ['service-processor', 'shelf', 'acp', 'disk']
type: str
short_description: NetApp ONTAP firmware upgrade for SP, shelf, ACP, and disk.
version_added: "2.9"
'''
EXAMPLES = """
- name: SP firmware upgrade
na_ontap_firmware_upgrade:
state: present
node: vsim1
package: "{{ file name }}"
clear_logs: True
install_baseline_image: False
update_type: serial_full
firmware_type: service-processor
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: ACP firmware upgrade
na_ontap_firmware_upgrade:
state: present
node: vsim1
firmware_type: acp
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: shelf firmware upgrade
na_ontap_firmware_upgrade:
state: present
firmware_type: shelf
shelf_module_fw: 1221
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: disk firmware upgrade
na_ontap_firmware_upgrade:
state: present
firmware_type: disk
disk_fw: NA02
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
import time
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPFirmwareUpgrade(object):
"""
Class with ONTAP firmware upgrade methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', default='present'),
node=dict(required=False, type='str'),
firmware_type=dict(required=True, type='str', choices=['service-processor', 'shelf', 'acp', 'disk']),
clear_logs=dict(required=False, type='bool', default=True),
package=dict(required=False, type='str'),
install_baseline_image=dict(required=False, type='bool', default=False),
update_type=dict(required=False, type='str'),
shelf_module_fw=dict(required=False, type='str'),
disk_fw=dict(required=False, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('firmware_type', 'acp', ['node']),
('firmware_type', 'disk', ['node']),
('firmware_type', 'service-processor', ['node', 'update_type']),
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if self.parameters.get('firmware_type') == 'service-processor':
if self.parameters.get('install_baseline_image') and self.parameters.get('package') is not None:
self.module.fail_json(msg='Do not specify both package and install_baseline_image: true')
if not self.parameters.get('package') and self.parameters.get('install_baseline_image') == 'False':
self.module.fail_json(msg='Specify at least one of package or install_baseline_image')
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def firmware_image_get_iter(self):
"""
Compose NaElement object to query current firmware version
:return: NaElement object for firmware_image_get_iter with query
"""
firmware_image_get = netapp_utils.zapi.NaElement('service-processor-get-iter')
query = netapp_utils.zapi.NaElement('query')
firmware_image_info = netapp_utils.zapi.NaElement('service-processor-info')
firmware_image_info.add_new_child('node', self.parameters['node'])
query.add_child_elem(firmware_image_info)
firmware_image_get.add_child_elem(query)
return firmware_image_get
def firmware_image_get(self, node_name):
"""
Get current firmware image info
:return: True if query successful, else return None
"""
firmware_image_get_iter = self.firmware_image_get_iter()
try:
result = self.server.invoke_successfully(firmware_image_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching firmware image details: %s: %s'
% (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
# return firmware image details
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
sp_info = result.get_child_by_name('attributes-list').get_child_by_name('service-processor-info')
firmware_version = sp_info.get_child_content('firmware-version')
return firmware_version
return None
def acp_firmware_required_get(self):
"""
where acp firmware upgrade is required
:return: True is firmware upgrade is required else return None
"""
acp_firmware_get_iter = netapp_utils.zapi.NaElement('storage-shelf-acp-module-get-iter')
query = netapp_utils.zapi.NaElement('query')
acp_info = netapp_utils.zapi.NaElement('storage-shelf-acp-module')
query.add_child_elem(acp_info)
acp_firmware_get_iter.add_child_elem(query)
try:
result = self.server.invoke_successfully(acp_firmware_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching acp firmware details details: %s'
% (to_native(error)), exception=traceback.format_exc())
if result.get_child_by_name('attributes-list').get_child_by_name('storage-shelf-acp-module'):
acp_module_info = result.get_child_by_name('attributes-list').get_child_by_name(
'storage-shelf-acp-module')
state = acp_module_info.get_child_content('state')
if state == 'firmware_update_required':
# acp firmware version upgrade required
return True
return False
def sp_firmware_image_update_progress_get(self, node_name):
"""
Get current firmware image update progress info
:return: Dictionary of firmware image update progress if query successful, else return None
"""
firmware_update_progress_get = netapp_utils.zapi.NaElement('service-processor-image-update-progress-get')
firmware_update_progress_get.add_new_child('node', self.parameters['node'])
firmware_update_progress_info = dict()
try:
result = self.server.invoke_successfully(firmware_update_progress_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching firmware image upgrade progress details: %s'
% (to_native(error)), exception=traceback.format_exc())
# return firmware image update progress details
if result.get_child_by_name('attributes').get_child_by_name('service-processor-image-update-progress-info'):
update_progress_info = result.get_child_by_name('attributes').get_child_by_name('service-processor-image-update-progress-info')
firmware_update_progress_info['is-in-progress'] = update_progress_info.get_child_content('is-in-progress')
firmware_update_progress_info['node'] = update_progress_info.get_child_content('node')
return firmware_update_progress_info
def shelf_firmware_info_get(self):
"""
Get the current firmware of shelf module
:return:dict with module id and firmware info
"""
shelf_id_fw_info = dict()
shelf_firmware_info_get = netapp_utils.zapi.NaElement('storage-shelf-info-get-iter')
desired_attributes = netapp_utils.zapi.NaElement('desired-attributes')
storage_shelf_info = netapp_utils.zapi.NaElement('storage-shelf-info')
shelf_module = netapp_utils.zapi.NaElement('shelf-modules')
shelf_module_info = netapp_utils.zapi.NaElement('storage-shelf-module-info')
shelf_module.add_child_elem(shelf_module_info)
storage_shelf_info.add_child_elem(shelf_module)
desired_attributes.add_child_elem(storage_shelf_info)
shelf_firmware_info_get.add_child_elem(desired_attributes)
try:
result = self.server.invoke_successfully(shelf_firmware_info_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching shelf module firmware details: %s'
% (to_native(error)), exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
shelf_info = result.get_child_by_name('attributes-list').get_child_by_name('storage-shelf-info')
if (shelf_info.get_child_by_name('shelf-modules') and
shelf_info.get_child_by_name('shelf-modules').get_child_by_name('storage-shelf-module-info')):
shelves = shelf_info['shelf-modules'].get_children()
for shelf in shelves:
shelf_id_fw_info[shelf.get_child_content('module-id')] = shelf.get_child_content('module-fw-revision')
return shelf_id_fw_info
def disk_firmware_info_get(self):
"""
Get the current firmware of disks module
:return:
"""
disk_id_fw_info = dict()
disk_firmware_info_get = netapp_utils.zapi.NaElement('storage-disk-get-iter')
desired_attributes = netapp_utils.zapi.NaElement('desired-attributes')
storage_disk_info = netapp_utils.zapi.NaElement('storage-disk-info')
disk_inv = netapp_utils.zapi.NaElement('disk-inventory-info')
storage_disk_info.add_child_elem(disk_inv)
desired_attributes.add_child_elem(storage_disk_info)
disk_firmware_info_get.add_child_elem(desired_attributes)
try:
result = self.server.invoke_successfully(disk_firmware_info_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching disk module firmware details: %s'
% (to_native(error)), exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
disk_info = result.get_child_by_name('attributes-list')
disks = disk_info.get_children()
for disk in disks:
disk_id_fw_info[disk.get_child_content('disk-uid')] = disk.get_child_by_name('disk-inventory-info').get_child_content('firmware-revision')
return disk_id_fw_info
def disk_firmware_required_get(self):
"""
Check weather disk firmware upgrade is required or not
:return: True if the firmware upgrade is required
"""
disk_firmware_info = self.disk_firmware_info_get()
for disk in disk_firmware_info:
if (disk_firmware_info[disk]) != self.parameters['disk_fw']:
return True
return False
def shelf_firmware_required_get(self):
"""
Check weather shelf firmware upgrade is required or not
:return: True if the firmware upgrade is required
"""
shelf_firmware_info = self.shelf_firmware_info_get()
for module in shelf_firmware_info:
if (shelf_firmware_info[module]) != self.parameters['shelf_module_fw']:
return True
return False
def sp_firmware_image_update(self):
"""
Update current firmware image
"""
firmware_update_info = netapp_utils.zapi.NaElement('service-processor-image-update')
if self.parameters.get('package') is not None:
firmware_update_info.add_new_child('package', self.parameters['package'])
if self.parameters.get('clear_logs') is not None:
firmware_update_info.add_new_child('clear-logs', str(self.parameters['clear_logs']))
if self.parameters.get('install_baseline_image') is not None:
firmware_update_info.add_new_child('install-baseline-image', str(self.parameters['install_baseline_image']))
firmware_update_info.add_new_child('node', self.parameters['node'])
firmware_update_info.add_new_child('update-type', self.parameters['update_type'])
try:
self.server.invoke_successfully(firmware_update_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
# Current firmware version matches the version to be installed
if to_native(error.code) == '13001' and (error.message.startswith('Service Processor update skipped')):
return False
self.module.fail_json(msg='Error updating firmware image for %s: %s'
% (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
return True
def shelf_firmware_upgrade(self):
"""
Upgrade shelf firmware image
"""
shelf_firmware_update_info = netapp_utils.zapi.NaElement('storage-shelf-firmware-update')
try:
self.server.invoke_successfully(shelf_firmware_update_info, enable_tunneling=True)
return True
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error updating shelf firmware image : %s'
% (to_native(error)), exception=traceback.format_exc())
def acp_firmware_upgrade(self):
"""
Upgrade shelf firmware image
"""
acp_firmware_update_info = netapp_utils.zapi.NaElement('storage-shelf-acp-firmware-update')
acp_firmware_update_info.add_new_child('node-name', self.parameters['node'])
try:
self.server.invoke_successfully(acp_firmware_update_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error updating acp firmware image : %s'
% (to_native(error)), exception=traceback.format_exc())
def disk_firmware_upgrade(self):
"""
Upgrade disk firmware
"""
disk_firmware_update_info = netapp_utils.zapi.NaElement('disk-update-disk-fw')
disk_firmware_update_info.add_new_child('node-name', self.parameters['node'])
try:
self.server.invoke_successfully(disk_firmware_update_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error updating disk firmware image : %s'
% (to_native(error)), exception=traceback.format_exc())
return True
def autosupport_log(self):
"""
Autosupport log for software_update
:return:
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_firmware_upgrade", cserver)
def apply(self):
"""
Apply action to upgrade firmware
"""
changed = False
self.autosupport_log()
firmware_update_progress = dict()
if self.parameters.get('firmware_type') == 'service-processor':
# service-processor firmware upgrade
current = self.firmware_image_get(self.parameters['node'])
if self.parameters.get('state') == 'present' and current:
if not self.module.check_mode:
if self.sp_firmware_image_update():
changed = True
firmware_update_progress = self.sp_firmware_image_update_progress_get(self.parameters['node'])
while firmware_update_progress.get('is-in-progress') == 'true':
time.sleep(25)
firmware_update_progress = self.sp_firmware_image_update_progress_get(self.parameters['node'])
else:
# we don't know until we try the upgrade
changed = True
elif self.parameters.get('firmware_type') == 'shelf':
# shelf firmware upgrade
if self.parameters.get('shelf_module_fw'):
if self.shelf_firmware_required_get():
if not self.module.check_mode:
changed = self.shelf_firmware_upgrade()
else:
changed = True
else:
if not self.module.check_mode:
changed = self.shelf_firmware_upgrade()
else:
# we don't know until we try the upgrade -- assuming the worst
changed = True
elif self.parameters.get('firmware_type') == 'acp':
# acp firmware upgrade
if self.acp_firmware_required_get():
if not self.module.check_mode:
self.acp_firmware_upgrade()
changed = True
elif self.parameters.get('firmware_type') == 'disk':
# Disk firmware upgrade
if self.parameters.get('disk_fw'):
if self.disk_firmware_required_get():
if not self.module.check_mode:
changed = self.disk_firmware_upgrade()
else:
changed = True
else:
if not self.module.check_mode:
changed = self.disk_firmware_upgrade()
else:
# we don't know until we try the upgrade -- assuming the worst
changed = True
self.module.exit_json(changed=changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPFirmwareUpgrade()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,474 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = '''
short_description: NetApp ONTAP FlexCache - create/delete relationship
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete FlexCache volume relationships
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_flexcache
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified relationship should exist or not.
default: present
origin_volume:
description:
- Name of the origin volume for the FlexCache.
- Required for creation.
origin_vserver:
description:
- Name of the origin vserver for the FlexCache.
- Required for creation.
origin_cluster:
description:
- Name of the origin cluster for the FlexCache.
- Defaults to cluster associated with target vserver if absent.
- Not used for creation.
volume:
description:
- Name of the target volume for the FlexCache.
required: true
junction_path:
description:
- Junction path of the cache volume.
auto_provision_as:
description:
- Use this parameter to automatically select existing aggregates for volume provisioning.Eg flexgroup
- Note that the fastest aggregate type with at least one aggregate on each node of the cluster will be selected.
size:
description:
- Size of cache volume.
size_unit:
description:
- The unit used to interpret the size parameter.
choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
default: gb
vserver:
description:
- Name of the target vserver for the FlexCache.
- Note that hostname, username, password are intended for the target vserver.
required: true
aggr_list:
description:
- List of aggregates to host target FlexCache volume.
aggr_list_multiplier:
description:
- Aggregate list repeat count.
force_unmount:
description:
- Unmount FlexCache volume. Delete the junction path at which the volume is mounted before deleting the FlexCache relationship.
type: bool
default: false
force_offline:
description:
- Offline FlexCache volume before deleting the FlexCache relationship.
- The volume will be destroyed and data can be lost.
type: bool
default: false
time_out:
description:
- time to wait for flexcache creation or deletion in seconds
- if 0, the request is asynchronous
- default is set to 3 minutes
default: 180
version_added: "2.8"
'''
EXAMPLES = """
- name: Create FlexCache
na_ontap_FlexCache:
state: present
origin_volume: test_src
volume: test_dest
origin_vserver: ansible_src
vserver: ansible_dest
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete FlexCache
na_ontap_FlexCache:
state: absent
volume: test_dest
vserver: ansible_dest
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import time
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPFlexCache(object):
"""
Class with FlexCache methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'],
default='present'),
origin_volume=dict(required=False, type='str'),
origin_vserver=dict(required=False, type='str'),
origin_cluster=dict(required=False, type='str'),
auto_provision_as=dict(required=False, type='str'),
volume=dict(required=True, type='str'),
junction_path=dict(required=False, type='str'),
size=dict(required=False, type='int'),
size_unit=dict(default='gb',
choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
'pb', 'eb', 'zb', 'yb'], type='str'),
vserver=dict(required=True, type='str'),
aggr_list=dict(required=False, type='list'),
aggr_list_multiplier=dict(required=False, type='int'),
force_offline=dict(required=False, type='bool', default=False),
force_unmount=dict(required=False, type='bool', default=False),
time_out=dict(required=False, type='int', default=180),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
mutually_exclusive=[
('aggr_list', 'auto_provision_as'),
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if self.parameters.get('size'):
self.parameters['size'] = self.parameters['size'] * \
netapp_utils.POW2_BYTE_MAP[self.parameters['size_unit']]
# setup later if required
self.origin_server = None
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def add_parameter_to_dict(self, adict, name, key=None, tostr=False):
''' add defined parameter (not None) to adict using key '''
if key is None:
key = name
if self.parameters.get(name) is not None:
if tostr:
adict[key] = str(self.parameters.get(name))
else:
adict[key] = self.parameters.get(name)
def get_job(self, jobid, server):
"""
Get job details by id
"""
job_get = netapp_utils.zapi.NaElement('job-get')
job_get.add_new_child('job-id', jobid)
try:
result = server.invoke_successfully(job_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if to_native(error.code) == "15661":
# Not found
return None
self.module.fail_json(msg='Error fetching job info: %s' % to_native(error),
exception=traceback.format_exc())
results = dict()
job_info = result.get_child_by_name('attributes').get_child_by_name('job-info')
results = {
'job-progress': job_info['job-progress'],
'job-state': job_info['job-state']
}
if job_info.get_child_by_name('job-completion') is not None:
results['job-completion'] = job_info['job-completion']
else:
results['job-completion'] = None
return results
def check_job_status(self, jobid):
"""
Loop until job is complete
"""
server = self.server
sleep_time = 5
time_out = self.parameters['time_out']
while time_out > 0:
results = self.get_job(jobid, server)
# If running as cluster admin, the job is owned by cluster vserver
# rather than the target vserver.
if results is None and server == self.server:
results = netapp_utils.get_cserver(self.server)
server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
continue
if results is None:
error = 'cannot locate job with id: %d' % jobid
break
if results['job-state'] in ('queued', 'running'):
time.sleep(sleep_time)
time_out -= sleep_time
continue
if results['job-state'] in ('success', 'failure'):
break
else:
self.module.fail_json(msg='Unexpected job status in: %s' % repr(results))
if results is not None:
if results['job-state'] == 'success':
error = None
elif results['job-state'] in ('queued', 'running'):
error = 'job completion exceeded expected timer of: %s seconds' % \
self.parameters['time_out']
else:
if results['job-completion'] is not None:
error = results['job-completion']
else:
error = results['job-progress']
return error
def flexcache_get_iter(self):
"""
Compose NaElement object to query current FlexCache relation
"""
options = {'volume': self.parameters['volume']}
self.add_parameter_to_dict(options, 'origin_volume', 'origin-volume')
self.add_parameter_to_dict(options, 'origin_vserver', 'origin-vserver')
self.add_parameter_to_dict(options, 'origin_cluster', 'origin-cluster')
flexcache_info = netapp_utils.zapi.NaElement.create_node_with_children(
'flexcache-info', **options)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(flexcache_info)
flexcache_get_iter = netapp_utils.zapi.NaElement('flexcache-get-iter')
flexcache_get_iter.add_child_elem(query)
return flexcache_get_iter
def flexcache_get(self):
"""
Get current FlexCache relations
:return: Dictionary of current FlexCache details if query successful, else None
"""
flexcache_get_iter = self.flexcache_get_iter()
flex_info = dict()
try:
result = self.server.invoke_successfully(flexcache_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching FlexCache info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
flexcache_info = result.get_child_by_name('attributes-list') \
.get_child_by_name('flexcache-info')
flex_info['origin_cluster'] = flexcache_info.get_child_content('origin-cluster')
flex_info['origin_volume'] = flexcache_info.get_child_content('origin-volume')
flex_info['origin_vserver'] = flexcache_info.get_child_content('origin-vserver')
flex_info['size'] = flexcache_info.get_child_content('size')
flex_info['volume'] = flexcache_info.get_child_content('volume')
flex_info['vserver'] = flexcache_info.get_child_content('vserver')
flex_info['auto_provision_as'] = flexcache_info.get_child_content('auto-provision-as')
return flex_info
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) > 1:
msg = 'Multiple records found for %s:' % self.parameters['volume']
self.module.fail_json(msg='Error fetching FlexCache info: %s' % msg)
return None
def flexcache_create_async(self):
"""
Create a FlexCache relationship
"""
options = {'origin-volume': self.parameters['origin_volume'],
'origin-vserver': self.parameters['origin_vserver'],
'volume': self.parameters['volume']}
self.add_parameter_to_dict(options, 'junction_path', 'junction-path')
self.add_parameter_to_dict(options, 'auto_provision_as', 'auto-provision-as')
self.add_parameter_to_dict(options, 'size', 'size', tostr=True)
if self.parameters.get('aggr_list'):
if self.parameters.get('aggr_list_multiplier'):
self.tobytes_aggr_list_multiplier = bytes(self.parameters['aggr_list_multiplier'])
self.add_parameter_to_dict(options, 'tobytes_aggr_list_multiplier', 'aggr-list-multiplier')
flexcache_create = netapp_utils.zapi.NaElement.create_node_with_children(
'flexcache-create-async', **options)
if self.parameters.get('aggr_list'):
aggregates = netapp_utils.zapi.NaElement('aggr-list')
for aggregate in self.parameters['aggr_list']:
aggregates.add_new_child('aggr-name', aggregate)
flexcache_create.add_child_elem(aggregates)
try:
result = self.server.invoke_successfully(flexcache_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating FlexCache %s' % to_native(error),
exception=traceback.format_exc())
results = dict()
for key in ('result-status', 'result-jobid'):
if result.get_child_by_name(key):
results[key] = result[key]
return results
def flexcache_create(self):
"""
Create a FlexCache relationship
Check job status
"""
results = self.flexcache_create_async()
status = results.get('result-status')
if status == 'in_progress' and 'result-jobid' in results:
if self.parameters['time_out'] == 0:
# asynchronous call, assuming success!
return
error = self.check_job_status(results['result-jobid'])
if error is None:
return
else:
self.module.fail_json(msg='Error when creating flexcache: %s' % error)
self.module.fail_json(msg='Unexpected error when creating flexcache: results is: %s' % repr(results))
def flexcache_delete_async(self):
"""
Delete FlexCache relationship at destination cluster
"""
options = {'volume': self.parameters['volume']}
flexcache_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'flexcache-destroy-async', **options)
try:
result = self.server.invoke_successfully(flexcache_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting FlexCache : %s'
% (to_native(error)),
exception=traceback.format_exc())
results = dict()
for key in ('result-status', 'result-jobid'):
if result.get_child_by_name(key):
results[key] = result[key]
return results
def volume_offline(self):
"""
Offline FlexCache volume at destination cluster
"""
options = {'name': self.parameters['volume']}
xml = netapp_utils.zapi.NaElement.create_node_with_children(
'volume-offline', **options)
try:
self.server.invoke_successfully(xml, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error offlining FlexCache volume: %s'
% (to_native(error)),
exception=traceback.format_exc())
def volume_unmount(self):
"""
Unmount FlexCache volume at destination cluster
"""
options = {'volume-name': self.parameters['volume']}
xml = netapp_utils.zapi.NaElement.create_node_with_children(
'volume-unmount', **options)
try:
self.server.invoke_successfully(xml, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error unmounting FlexCache volume: %s'
% (to_native(error)),
exception=traceback.format_exc())
def flexcache_delete_async(self):
"""
Delete FlexCache relationship at destination cluster
"""
options = {'volume': self.parameters['volume']}
flexcache_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'flexcache-destroy-async', **options)
try:
result = self.server.invoke_successfully(flexcache_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting FlexCache : %s'
% (to_native(error)),
exception=traceback.format_exc())
results = dict()
for key in ('result-status', 'result-jobid'):
if result.get_child_by_name(key):
results[key] = result[key]
return results
def flexcache_delete(self):
"""
Delete FlexCache relationship at destination cluster
Check job status
"""
if self.parameters['force_unmount']:
self.volume_unmount()
if self.parameters['force_offline']:
self.volume_offline()
results = self.flexcache_delete_async()
status = results.get('result-status')
if status == 'in_progress' and 'result-jobid' in results:
if self.parameters['time_out'] == 0:
# asynchronous call, assuming success!
return
error = self.check_job_status(results['result-jobid'])
if error is None:
return
else:
self.module.fail_json(msg='Error when deleting flexcache: %s' % error)
self.module.fail_json(msg='Unexpected error when deleting flexcache: results is: %s' % repr(results))
def check_parameters(self):
"""
Validate parameters and fail if one or more required params are missing
"""
missings = list()
expected = ('origin_volume', 'origin_vserver')
if self.parameters['state'] == 'present':
for param in expected:
if not self.parameters.get(param):
missings.append(param)
if missings:
plural = 's' if len(missings) > 1 else ''
msg = 'Missing parameter%s: %s' % (plural, ', '.join(missings))
self.module.fail_json(msg=msg)
def apply(self):
"""
Apply action to FlexCache
"""
netapp_utils.ems_log_event("na_ontap_flexcache", self.server)
current = self.flexcache_get()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action == 'create':
self.check_parameters()
self.flexcache_create()
elif cd_action == 'delete':
self.flexcache_delete()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPFlexCache()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,346 +0,0 @@
#!/usr/bin/python
''' this is igroup module
(c) 2018-2019, 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_ontap_igroup
short_description: NetApp ONTAP iSCSI or FC igroup configuration
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete/Rename Igroups and Modify initiators belonging to an igroup
options:
state:
description:
- Whether the specified Igroup should exist or not.
choices: ['present', 'absent']
default: present
name:
description:
- The name of the igroup to manage.
required: true
initiator_group_type:
description:
- Type of the initiator group.
- Required when C(state=present).
choices: ['fcp', 'iscsi', 'mixed']
from_name:
description:
- Name of igroup to rename to name.
version_added: '2.7'
ostype:
description:
- OS type of the initiators within the group.
initiators:
description:
- List of initiators to be mapped to the igroup.
- WWPN, WWPN Alias, or iSCSI name of Initiator to add or remove.
- For a modify operation, this list replaces the existing initiators
- This module does not add or remove specific initiator(s) in an igroup
aliases:
- initiator
bind_portset:
description:
- Name of a current portset to bind to the newly created igroup.
force_remove_initiator:
description:
- Forcibly remove the initiator even if there are existing LUNs mapped to this initiator group.
type: bool
vserver:
description:
- The name of the vserver to use.
required: true
'''
EXAMPLES = '''
- name: Create iSCSI Igroup
na_ontap_igroup:
state: present
name: ansibleIgroup3
initiator_group_type: iscsi
ostype: linux
initiators: iqn.1994-05.com.redhat:scspa0395855001.rtp.openenglab.netapp.com,abc.com:redhat.com
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Create FC Igroup
na_ontap_igroup:
state: present
name: ansibleIgroup4
initiator_group_type: fcp
ostype: linux
initiators: 20:00:00:50:56:9f:19:82
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: rename Igroup
na_ontap_igroup:
state: present
from_name: ansibleIgroup3
name: testexamplenewname
initiator_group_type: iscsi
ostype: linux
initiators: iqn.1994-05.com.redhat:scspa0395855001.rtp.openenglab.netapp.com
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Modify Igroup Initiators (replaces existing initiators)
na_ontap_igroup:
state: present
name: ansibleIgroup3
initiator_group_type: iscsi
ostype: linux
initiator: iqn.1994-05.com.redhat:scspa0395855001.rtp.openenglab.netapp.com
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete Igroup
na_ontap_igroup:
state: absent
name: ansibleIgroup3
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = '''
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapIgroup(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str', default=None),
ostype=dict(required=False, type='str'),
initiator_group_type=dict(required=False, type='str',
choices=['fcp', 'iscsi', 'mixed']),
initiators=dict(required=False, type='list', aliases=['initiator']),
vserver=dict(required=True, type='str'),
force_remove_initiator=dict(required=False, type='bool', default=False),
bind_portset=dict(required=False, type='str')
))
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_igroup(self, name):
"""
Return details about the igroup
:param:
name : Name of the igroup
:return: Details about the igroup. None if not found.
:rtype: dict
"""
igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter')
attributes = dict(query={'initiator-group-info': {'initiator-group-name': name,
'vserver': self.parameters['vserver']}})
igroup_info.translate_struct(attributes)
result, current = None, None
try:
result = self.server.invoke_successfully(igroup_info, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching igroup info %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
igroup = result.get_child_by_name('attributes-list').get_child_by_name('initiator-group-info')
initiators = []
if igroup.get_child_by_name('initiators'):
current_initiators = igroup['initiators'].get_children()
for initiator in current_initiators:
initiators.append(initiator['initiator-name'])
current = {
'initiators': initiators
}
return current
def add_initiators(self):
"""
Add the list of initiators to igroup
:return: None
"""
# don't add if initiators is empty string
if self.parameters.get('initiators') == [''] or self.parameters.get('initiators') is None:
return
for initiator in self.parameters['initiators']:
self.modify_initiator(initiator, 'igroup-add')
def remove_initiators(self, initiators):
"""
Removes all existing initiators from igroup
:return: None
"""
for initiator in initiators:
self.modify_initiator(initiator, 'igroup-remove')
def modify_initiator(self, initiator, zapi):
"""
Add or remove an initiator to/from an igroup
"""
initiator.strip() # remove leading spaces if any (eg: if user types a space after comma in initiators list)
options = {'initiator-group-name': self.parameters['name'],
'initiator': initiator}
igroup_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)
try:
self.server.invoke_successfully(igroup_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying igroup initiator %s: %s' % (self.parameters['name'],
to_native(error)),
exception=traceback.format_exc())
def create_igroup(self):
"""
Create the igroup.
"""
options = {'initiator-group-name': self.parameters['name']}
if self.parameters.get('ostype') is not None:
options['os-type'] = self.parameters['ostype']
if self.parameters.get('initiator_group_type') is not None:
options['initiator-group-type'] = self.parameters['initiator_group_type']
if self.parameters.get('bind_portset') is not None:
options['bind-portset'] = self.parameters['bind_portset']
igroup_create = netapp_utils.zapi.NaElement.create_node_with_children(
'igroup-create', **options)
try:
self.server.invoke_successfully(igroup_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error provisioning igroup %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
self.add_initiators()
def delete_igroup(self):
"""
Delete the igroup.
"""
igroup_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'igroup-destroy', **{'initiator-group-name': self.parameters['name'],
'force': 'true' if self.parameters['force_remove_initiator'] else 'false'})
try:
self.server.invoke_successfully(igroup_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting igroup %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def rename_igroup(self):
"""
Rename the igroup.
"""
igroup_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'igroup-rename', **{'initiator-group-name': self.parameters['from_name'],
'initiator-group-new-name': str(self.parameters['name'])})
try:
self.server.invoke_successfully(igroup_rename,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error renaming igroup %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
netapp_utils.ems_log_event("na_ontap_igroup", self.server)
def apply(self):
self.autosupport_log()
current = self.get_igroup(self.parameters['name'])
# rename and create are mutually exclusive
rename, cd_action, modify = None, None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(self.get_igroup(self.parameters['from_name']), current)
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_igroup()
elif cd_action == 'create':
self.create_igroup()
elif cd_action == 'delete':
self.delete_igroup()
if modify:
self.remove_initiators(current['initiators'])
self.add_initiators()
self.module.exit_json(changed=self.na_helper.changed)
def main():
obj = NetAppOntapIgroup()
obj.apply()
if __name__ == '__main__':
main()

@ -1,183 +0,0 @@
#!/usr/bin/python
''' This is an Ansible module for ONTAP, to manage initiators in an Igroup
(c) 2019, 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': 'community'
}
DOCUMENTATION = '''
module: na_ontap_igroup_initiator
short_description: NetApp ONTAP igroup initiator configuration
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Add/Remove initiators from an igroup
options:
state:
description:
- Whether the specified initiator should exist or not in an igroup.
choices: ['present', 'absent']
default: present
names:
description:
- List of initiators to manage.
required: true
aliases:
- name
initiator_group:
description:
- Name of the initiator group to which the initiator belongs.
required: true
vserver:
description:
- The name of the vserver to use.
required: true
'''
EXAMPLES = '''
- name: Add initiators to an igroup
na_ontap_igroup_initiator:
names: abc.test:def.com,def.test:efg.com
initiator_group: test_group
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Remove an initiator from an igroup
na_ontap_igroup_initiator:
state: absent
names: abc.test:def.com
initiator_group: test_group
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = '''
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapIgroupInitiator(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
names=dict(required=True, type='list', aliases=['name']),
initiator_group=dict(required=True, type='str'),
vserver=dict(required=True, type='str'),
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_initiators(self):
"""
Get the existing list of initiators from an igroup
:rtype: list() or None
"""
igroup_info = netapp_utils.zapi.NaElement('igroup-get-iter')
attributes = dict(query={'initiator-group-info': {'initiator-group-name': self.parameters['initiator_group'],
'vserver': self.parameters['vserver']}})
igroup_info.translate_struct(attributes)
result, current = None, []
try:
result = self.server.invoke_successfully(igroup_info, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching igroup info %s: %s' % (self.parameters['initiator_group'],
to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
igroup_info = result.get_child_by_name('attributes-list').get_child_by_name('initiator-group-info')
if igroup_info.get_child_by_name('initiators') is not None:
current = [initiator['initiator-name'] for initiator in igroup_info['initiators'].get_children()]
return current
def modify_initiator(self, initiator_name, zapi):
"""
Add or remove an initiator to/from an igroup
"""
options = {'initiator-group-name': self.parameters['initiator_group'],
'initiator': initiator_name}
initiator_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)
try:
self.server.invoke_successfully(initiator_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying igroup initiator %s: %s' % (initiator_name,
to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
netapp_utils.ems_log_event("na_ontap_igroup_initiator", self.server)
def apply(self):
self.autosupport_log()
initiators = self.get_initiators()
for initiator in self.parameters['names']:
present = None
if initiator in initiators:
present = True
cd_action = self.na_helper.get_cd_action(present, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.modify_initiator(initiator, 'igroup-add')
elif cd_action == 'delete':
self.modify_initiator(initiator, 'igroup-remove')
self.module.exit_json(changed=self.na_helper.changed)
def main():
obj = NetAppOntapIgroupInitiator()
obj.apply()
if __name__ == '__main__':
main()

@ -1,619 +0,0 @@
#!/usr/bin/python
# (c) 2018 Piotr Olczak <piotr.olczak@redhat.com>
# (c) 2018-2019, 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_ontap_info
author: Piotr Olczak (@dprts) <polczak@redhat.com>
extends_documentation_fragment:
- netapp.na_ontap
short_description: NetApp information gatherer
description:
- This module allows you to gather various information about ONTAP configuration
version_added: "2.9"
requirements:
- netapp_lib
options:
state:
type: str
description:
- Returns "info"
default: "info"
choices: ['info']
gather_subset:
type: list
description:
- When supplied, this argument will restrict the information collected
to a given subset. Possible values for this argument include
"aggregate_info", "cluster_node_info", "igroup_info", "lun_info", "net_dns_info",
"net_ifgrp_info",
"net_interface_info", "net_port_info", "nvme_info", "nvme_interface_info",
"nvme_namespace_info", "nvme_subsystem_info", "ontap_version",
"qos_adaptive_policy_info", "qos_policy_info", "security_key_manager_key_info",
"security_login_account_info", "storage_failover_info", "volume_info",
"vserver_info", "vserver_login_banner_info", "vserver_motd_info", "vserver_nfs_info"
Can specify a list of values to include a larger subset. Values can also be used
with an initial C(M(!)) to specify that a specific subset should
not be collected.
- nvme is supported with ONTAP 9.4 onwards.
- use "help" to get a list of supported information for your system.
default: "all"
'''
EXAMPLES = '''
- name: Get NetApp info (Password Authentication)
na_ontap_info:
state: info
hostname: "na-vsim"
username: "admin"
password: "admins_password"
register: ontap_info
- debug:
msg: "{{ ontap_info.ontap_info }}"
- name: Limit Info Gathering to Aggregate Information
na_ontap_info:
state: info
hostname: "na-vsim"
username: "admin"
password: "admins_password"
gather_subset: "aggregate_info"
register: ontap_info
- name: Limit Info Gathering to Volume and Lun Information
na_ontap_info:
state: info
hostname: "na-vsim"
username: "admin"
password: "admins_password"
gather_subset:
- volume_info
- lun_info
register: ontap_info
- name: Gather all info except for volume and lun information
na_ontap_info:
state: info
hostname: "na-vsim"
username: "admin"
password: "admins_password"
gather_subset:
- "!volume_info"
- "!lun_info"
register: ontap_info
'''
RETURN = '''
ontap_info:
description: Returns various information about NetApp cluster configuration
returned: always
type: dict
sample: '{
"ontap_info": {
"aggregate_info": {...},
"cluster_node_info": {...},
"net_dns_info": {...},
"net_ifgrp_info": {...},
"net_interface_info": {...},
"net_port_info": {...},
"security_key_manager_key_info": {...},
"security_login_account_info": {...},
"volume_info": {...},
"lun_info": {...},
"storage_failover_info": {...},
"vserver_login_banner_info": {...},
"vserver_motd_info": {...},
"vserver_info": {...},
"vserver_nfs_info": {...},
"ontap_version": {...},
"igroup_info": {...},
"qos_policy_info": {...},
"qos_adaptive_policy_info": {...}
}'
'''
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
try:
import xmltodict
HAS_XMLTODICT = True
except ImportError:
HAS_XMLTODICT = False
try:
import json
HAS_JSON = True
except ImportError:
HAS_JSON = False
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPGatherInfo(object):
'''Class with gather info methods'''
def __init__(self, module):
self.module = module
self.netapp_info = dict()
# thanks to coreywan (https://github.com/ansible/ansible/pull/47016)
# for starting this
# min_version identifies the ontapi version which supports this ZAPI
# use 0 if it is supported since 9.1
self.info_subsets = {
'net_dns_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'net-dns-get-iter',
'attribute': 'net-dns-info',
'field': 'vserver-name',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'net_interface_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'net-interface-get-iter',
'attribute': 'net-interface-info',
'field': 'interface-name',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'net_port_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'net-port-get-iter',
'attribute': 'net-port-info',
'field': ('node', 'port'),
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'cluster_node_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'cluster-node-get-iter',
'attribute': 'cluster-node-info',
'field': 'node-name',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'security_login_account_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'security-login-get-iter',
'attribute': 'security-login-account-info',
'field': ('vserver', 'user-name', 'application', 'authentication-method'),
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'aggregate_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'aggr-get-iter',
'attribute': 'aggr-attributes',
'field': 'aggregate-name',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'volume_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'volume-get-iter',
'attribute': 'volume-attributes',
'field': ('name', 'owning-vserver-name'),
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'lun_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'lun-get-iter',
'attribute': 'lun-info',
'field': ('vserver', 'path'),
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'storage_failover_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'cf-get-iter',
'attribute': 'storage-failover-info',
'field': 'node',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'vserver_motd_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'vserver-motd-get-iter',
'attribute': 'vserver-motd-info',
'field': 'vserver',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'vserver_login_banner_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'vserver-login-banner-get-iter',
'attribute': 'vserver-login-banner-info',
'field': 'vserver',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'security_key_manager_key_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'security-key-manager-key-get-iter',
'attribute': 'security-key-manager-key-info',
'field': ('node', 'key-id'),
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'vserver_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'vserver-get-iter',
'attribute': 'vserver-info',
'field': 'vserver-name',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'vserver_nfs_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'nfs-service-get-iter',
'attribute': 'nfs-info',
'field': 'vserver',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'net_ifgrp_info': {
'method': self.get_ifgrp_info,
'kwargs': {},
'min_version': '0',
},
'ontap_version': {
'method': self.ontapi,
'kwargs': {},
'min_version': '0',
},
'system_node_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'system-node-get-iter',
'attribute': 'node-details-info',
'field': 'node',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'igroup_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'igroup-get-iter',
'attribute': 'initiator-group-info',
'field': ('vserver', 'initiator-group-name'),
'query': {'max-records': '1024'},
},
'min_version': '0',
},
'qos_policy_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'qos-policy-group-get-iter',
'attribute': 'qos-policy-group-info',
'field': 'policy-group',
'query': {'max-records': '1024'},
},
'min_version': '0',
},
# supported in ONTAP 9.3 and onwards
'qos_adaptive_policy_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'qos-adaptive-policy-group-get-iter',
'attribute': 'qos-adaptive-policy-group-info',
'field': 'policy-group',
'query': {'max-records': '1024'},
},
'min_version': '130',
},
# supported in ONTAP 9.4 and onwards
'nvme_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'nvme-get-iter',
'attribute': 'nvme-target-service-info',
'field': 'vserver',
'query': {'max-records': '1024'},
},
'min_version': '140',
},
'nvme_interface_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'nvme-interface-get-iter',
'attribute': 'nvme-interface-info',
'field': 'vserver',
'query': {'max-records': '1024'},
},
'min_version': '140',
},
'nvme_subsystem_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'nvme-subsystem-get-iter',
'attribute': 'nvme-subsystem-info',
'field': 'subsystem',
'query': {'max-records': '1024'},
},
'min_version': '140',
},
'nvme_namespace_info': {
'method': self.get_generic_get_iter,
'kwargs': {
'call': 'nvme-namespace-get-iter',
'attribute': 'nvme-namespace-info',
'field': 'path',
'query': {'max-records': '1024'},
},
'min_version': '140',
},
}
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def ontapi(self):
'''Method to get ontapi version'''
api = 'system-get-ontapi-version'
api_call = netapp_utils.zapi.NaElement(api)
try:
results = self.server.invoke_successfully(api_call, enable_tunneling=False)
ontapi_version = results.get_child_content('minor-version')
return ontapi_version if ontapi_version is not None else '0'
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error calling API %s: %s" %
(api, to_native(error)), exception=traceback.format_exc())
def call_api(self, call, query=None):
'''Main method to run an API call'''
api_call = netapp_utils.zapi.NaElement(call)
result = None
if query:
for key, val in query.items():
# Can val be nested?
api_call.add_new_child(key, val)
try:
result = self.server.invoke_successfully(api_call, enable_tunneling=False)
return result
except netapp_utils.zapi.NaApiError as error:
if call in ['security-key-manager-key-get-iter']:
return result
else:
self.module.fail_json(msg="Error calling API %s: %s"
% (call, to_native(error)), exception=traceback.format_exc())
def get_ifgrp_info(self):
'''Method to get network port ifgroups info'''
try:
net_port_info = self.netapp_info['net_port_info']
except KeyError:
net_port_info_calls = self.info_subsets['net_port_info']
net_port_info = net_port_info_calls['method'](**net_port_info_calls['kwargs'])
interfaces = net_port_info.keys()
ifgrps = []
for ifn in interfaces:
if net_port_info[ifn]['port_type'] == 'if_group':
ifgrps.append(ifn)
net_ifgrp_info = dict()
for ifgrp in ifgrps:
query = dict()
query['node'], query['ifgrp-name'] = ifgrp.split(':')
tmp = self.get_generic_get_iter('net-port-ifgrp-get', field=('node', 'ifgrp-name'),
attribute='net-ifgrp-info', query=query)
net_ifgrp_info = net_ifgrp_info.copy()
net_ifgrp_info.update(tmp)
return net_ifgrp_info
def get_generic_get_iter(self, call, attribute=None, field=None, query=None):
'''Method to run a generic get-iter call'''
generic_call = self.call_api(call, query)
if call == 'net-port-ifgrp-get':
children = 'attributes'
else:
children = 'attributes-list'
if generic_call is None:
return None
if field is None:
out = []
else:
out = {}
attributes_list = generic_call.get_child_by_name(children)
if attributes_list is None:
return None
for child in attributes_list.get_children():
dic = xmltodict.parse(child.to_string(), xml_attribs=False)
if attribute is not None:
dic = dic[attribute]
if isinstance(field, str):
unique_key = _finditem(dic, field)
out = out.copy()
out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))})
elif isinstance(field, tuple):
unique_key = ':'.join([_finditem(dic, el) for el in field])
out = out.copy()
out.update({unique_key: convert_keys(json.loads(json.dumps(dic)))})
else:
out.append(convert_keys(json.loads(json.dumps(dic))))
return out
def get_all(self, gather_subset):
'''Method to get all subsets'''
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_info", cserver)
self.netapp_info['ontap_version'] = self.ontapi()
run_subset = self.get_subset(gather_subset, self.netapp_info['ontap_version'])
if 'help' in gather_subset:
self.netapp_info['help'] = sorted(run_subset)
else:
for subset in run_subset:
call = self.info_subsets[subset]
self.netapp_info[subset] = call['method'](**call['kwargs'])
return self.netapp_info
def get_subset(self, gather_subset, version):
'''Method to get a single subset'''
runable_subsets = set()
exclude_subsets = set()
usable_subsets = [key for key in self.info_subsets.keys() if version >= self.info_subsets[key]['min_version']]
if 'help' in gather_subset:
return usable_subsets
for subset in gather_subset:
if subset == 'all':
runable_subsets.update(usable_subsets)
return runable_subsets
if subset.startswith('!'):
subset = subset[1:]
if subset == 'all':
return set()
exclude = True
else:
exclude = False
if subset not in usable_subsets:
if subset not in self.info_subsets.keys():
self.module.fail_json(msg='Bad subset: %s' % subset)
self.module.fail_json(msg='Remote system at version %s does not support %s' %
(version, subset))
if exclude:
exclude_subsets.add(subset)
else:
runable_subsets.add(subset)
if not runable_subsets:
runable_subsets.update(usable_subsets)
runable_subsets.difference_update(exclude_subsets)
return runable_subsets
# https://stackoverflow.com/questions/14962485/finding-a-key-recursively-in-a-dictionary
def __finditem(obj, key):
if key in obj:
return obj[key]
for dummy, val in obj.items():
if isinstance(val, dict):
item = __finditem(val, key)
if item is not None:
return item
return None
def _finditem(obj, key):
value = __finditem(obj, key)
if value is not None:
return value
raise KeyError(key)
def convert_keys(d_param):
'''Method to convert hyphen to underscore'''
out = {}
if isinstance(d_param, dict):
for key, val in d_param.items():
val = convert_keys(val)
out[key.replace('-', '_')] = val
else:
return d_param
return out
def main():
'''Execute action'''
argument_spec = netapp_utils.na_ontap_host_argument_spec()
argument_spec.update(dict(
state=dict(type='str', default='info', choices=['info']),
gather_subset=dict(default=['all'], type='list'),
))
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
if not HAS_XMLTODICT:
module.fail_json(msg="xmltodict missing")
if not HAS_JSON:
module.fail_json(msg="json missing")
state = module.params['state']
gather_subset = module.params['gather_subset']
if gather_subset is None:
gather_subset = ['all']
gf_obj = NetAppONTAPGatherInfo(module)
gf_all = gf_obj.get_all(gather_subset)
result = {'state': state, 'changed': False}
module.exit_json(ontap_info=gf_all, **result)
if __name__ == '__main__':
main()

@ -1,449 +0,0 @@
#!/usr/bin/python
""" this is interface module
(c) 2018-2019, 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_ontap_interface
short_description: NetApp ONTAP LIF configuration
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Creating / deleting and modifying the LIF.
options:
state:
description:
- Whether the specified interface should exist or not.
choices: ['present', 'absent']
default: present
interface_name:
description:
- Specifies the logical interface (LIF) name.
required: true
home_node:
description:
- Specifies the LIF's home node.
- By default, the first node from the cluster is considered as home node
home_port:
description:
- Specifies the LIF's home port.
- Required when C(state=present)
role:
description:
- Specifies the role of the LIF.
- When setting role as "intercluster", setting protocol is not supported.
- Required when C(state=present).
address:
description:
- Specifies the LIF's IP address.
- Required when C(state=present)
netmask:
description:
- Specifies the LIF's netmask.
- Required when C(state=present).
vserver:
description:
- The name of the vserver to use.
required: true
firewall_policy:
description:
- Specifies the firewall policy for the LIF.
failover_policy:
choices: ['disabled', 'system-defined', 'local-only', 'sfo-partner-only', 'broadcast-domain-wide']
description:
- Specifies the failover policy for the LIF.
subnet_name:
description:
- Subnet where the interface address is allocated from.
If the option is not used, the IP address will need to be provided by
the administrator during configuration.
version_added: '2.8'
admin_status:
choices: ['up', 'down']
description:
- Specifies the administrative status of the LIF.
is_auto_revert:
description:
If true, data LIF will revert to its home node under certain circumstances such as startup, and load balancing
migration capability is disabled automatically
type: bool
force_subnet_association:
description:
Set this to true to acquire the address from the named subnet and assign the subnet to the LIF.
type: bool
version_added: '2.9'
protocols:
description:
- Specifies the list of data protocols configured on the LIF. By default, the values in this element are nfs, cifs and fcache.
- Other supported protocols are iscsi and fcp. A LIF can be configured to not support any data protocols by specifying 'none'.
- Protocol values of none, iscsi, fc-nvme or fcp can't be combined with any other data protocol(s).
- address, netmask and firewall_policy parameters are not supported for 'fc-nvme' option.
dns_domain_name:
description:
- Specifies the unique, fully qualified domain name of the DNS zone of this LIF.
type: str
version_added: '2.9'
listen_for_dns_query:
description:
- If True, this IP address will listen for DNS queries for the dnszone specified.
type: bool
version_added: '2.9'
is_dns_update_enabled:
description:
- Specifies if DNS update is enabled for this LIF. Dynamic updates will be sent for this LIF if updates are enabled at Vserver level.
type: bool
version_added: '2.9'
'''
EXAMPLES = '''
- name: Create interface
na_ontap_interface:
state: present
interface_name: data2
home_port: e0d
home_node: laurentn-vsim1
role: data
protocols: nfs
admin_status: up
failover_policy: local-only
firewall_policy: mgmt
is_auto_revert: true
address: 10.10.10.10
netmask: 255.255.255.0
force_subnet_association: false
dns_domain_name: test.com
listen_for_dns_query: true
is_dns_update_enabled: true
vserver: svm1
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete interface
na_ontap_interface:
state: absent
interface_name: data2
vserver: svm1
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapInterface(object):
''' object to describe interface info '''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
interface_name=dict(required=True, type='str'),
home_node=dict(required=False, type='str', default=None),
home_port=dict(required=False, type='str'),
role=dict(required=False, type='str'),
address=dict(required=False, type='str'),
netmask=dict(required=False, type='str'),
vserver=dict(required=True, type='str'),
firewall_policy=dict(required=False, type='str', default=None),
failover_policy=dict(required=False, type='str', default=None,
choices=['disabled', 'system-defined',
'local-only', 'sfo-partner-only', 'broadcast-domain-wide']),
admin_status=dict(required=False, choices=['up', 'down']),
subnet_name=dict(required=False, type='str'),
is_auto_revert=dict(required=False, type='bool', default=None),
protocols=dict(required=False, type='list'),
force_subnet_association=dict(required=False, type='bool', default=None),
dns_domain_name=dict(required=False, type='str'),
listen_for_dns_query=dict(required=False, type='bool'),
is_dns_update_enabled=dict(required=False, type='bool')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
mutually_exclusive=[
['subnet_name', 'address'],
['subnet_name', 'netmask']
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_interface(self):
"""
Return details about the interface
:param:
name : Name of the name of the interface
:return: Details about the interface. None if not found.
:rtype: dict
"""
interface_info = netapp_utils.zapi.NaElement('net-interface-get-iter')
interface_attributes = netapp_utils.zapi.NaElement('net-interface-info')
interface_attributes.add_new_child('interface-name', self.parameters['interface_name'])
interface_attributes.add_new_child('vserver', self.parameters['vserver'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(interface_attributes)
interface_info.add_child_elem(query)
result = self.server.invoke_successfully(interface_info, True)
return_value = None
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
interface_attributes = result.get_child_by_name('attributes-list').\
get_child_by_name('net-interface-info')
return_value = {
'interface_name': self.parameters['interface_name'],
'admin_status': interface_attributes['administrative-status'],
'home_port': interface_attributes['home-port'],
'home_node': interface_attributes['home-node'],
'failover_policy': interface_attributes['failover-policy'].replace('_', '-'),
'is_auto_revert': True if interface_attributes['is-auto-revert'] == 'true' else False,
}
if interface_attributes.get_child_by_name('address'):
return_value['address'] = interface_attributes['address']
if interface_attributes.get_child_by_name('netmask'):
return_value['netmask'] = interface_attributes['netmask']
if interface_attributes.get_child_by_name('firewall-policy'):
return_value['firewall_policy'] = interface_attributes['firewall-policy']
if interface_attributes.get_child_by_name('dns-domain-name') != 'none':
return_value['dns_domain_name'] = interface_attributes['dns-domain-name']
else:
return_value['dns_domain_name'] = None
if interface_attributes.get_child_by_name('listen-for-dns-query'):
return_value['listen_for_dns_query'] = self.na_helper.get_value_for_bool(True, interface_attributes['listen-for-dns-query'])
if interface_attributes.get_child_by_name('is-dns-update-enabled'):
return_value['is_dns_update_enabled'] = self.na_helper.get_value_for_bool(True, interface_attributes['is-dns-update-enabled'])
return return_value
@staticmethod
def set_options(options, parameters):
""" set attributes for create or modify """
if parameters.get('home_port') is not None:
options['home-port'] = parameters['home_port']
if parameters.get('subnet_name') is not None:
options['subnet-name'] = parameters['subnet_name']
if parameters.get('address') is not None:
options['address'] = parameters['address']
if parameters.get('netmask') is not None:
options['netmask'] = parameters['netmask']
if parameters.get('failover_policy') is not None:
options['failover-policy'] = parameters['failover_policy']
if parameters.get('firewall_policy') is not None:
options['firewall-policy'] = parameters['firewall_policy']
if parameters.get('is_auto_revert') is not None:
options['is-auto-revert'] = 'true' if parameters['is_auto_revert'] is True else 'false'
if parameters.get('admin_status') is not None:
options['administrative-status'] = parameters['admin_status']
if parameters.get('force_subnet_association') is not None:
options['force-subnet-association'] = 'true' if parameters['force_subnet_association'] else 'false'
if parameters.get('dns_domain_name') is not None:
options['dns-domain-name'] = parameters['dns_domain_name']
if parameters.get('listen_for_dns_query') is not None:
options['listen-for-dns-query'] = str(parameters['listen_for_dns_query'])
if parameters.get('is_dns_update_enabled') is not None:
options['is-dns-update-enabled'] = str(parameters['is_dns_update_enabled'])
def set_protocol_option(self, required_keys):
""" set protocols for create """
if self.parameters.get('protocols') is not None:
data_protocols_obj = netapp_utils.zapi.NaElement('data-protocols')
for protocol in self.parameters.get('protocols'):
if protocol.lower() in ['fc-nvme', 'fcp']:
if 'address' in required_keys:
required_keys.remove('address')
if 'home_port' in required_keys:
required_keys.remove('home_port')
if 'netmask' in required_keys:
required_keys.remove('netmask')
not_required_params = set(['address', 'netmask', 'firewall_policy'])
if not not_required_params.isdisjoint(set(self.parameters.keys())):
self.module.fail_json(msg='Error: Following parameters for creating interface are not supported'
' for data-protocol fc-nvme: %s' % ', '.join(not_required_params))
data_protocols_obj.add_new_child('data-protocol', protocol)
return data_protocols_obj
return None
def get_home_node_for_cluster(self):
''' get the first node name from this cluster '''
get_node = netapp_utils.zapi.NaElement('cluster-node-get-iter')
attributes = {
'query': {
'cluster-node-info': {}
}
}
get_node.translate_struct(attributes)
try:
result = self.server.invoke_successfully(get_node, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as exc:
self.module.fail_json(msg='Error fetching node for interface %s: %s' %
(self.parameters['interface_name'], to_native(exc)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
attributes = result.get_child_by_name('attributes-list')
return attributes.get_child_by_name('cluster-node-info').get_child_content('node-name')
return None
def validate_create_parameters(self, keys):
'''
Validate if required parameters for create are present.
Parameter requirement might vary based on given data-protocol.
:return: None
'''
if self.parameters.get('home_node') is None:
node = self.get_home_node_for_cluster()
if node is not None:
self.parameters['home_node'] = node
# validate if mandatory parameters are present for create
if not keys.issubset(set(self.parameters.keys())) and self.parameters.get('subnet_name') is None:
self.module.fail_json(msg='Error: Missing one or more required parameters for creating interface: %s'
% ', '.join(keys))
# if role is intercluster, protocol cannot be specified
if self.parameters['role'] == "intercluster" and self.parameters.get('protocols') is not None:
self.module.fail_json(msg='Error: Protocol cannot be specified for intercluster role,'
'failed to create interface')
def create_interface(self):
''' calling zapi to create interface '''
required_keys = set(['role', 'home_port'])
data_protocols_obj = None
if self.parameters.get('subnet_name') is None:
required_keys.add('address')
required_keys.add('netmask')
data_protocols_obj = self.set_protocol_option(required_keys)
self.validate_create_parameters(required_keys)
options = {'interface-name': self.parameters['interface_name'],
'role': self.parameters['role'],
'home-node': self.parameters.get('home_node'),
'vserver': self.parameters['vserver']}
NetAppOntapInterface.set_options(options, self.parameters)
interface_create = netapp_utils.zapi.NaElement.create_node_with_children('net-interface-create', **options)
if data_protocols_obj is not None:
interface_create.add_child_elem(data_protocols_obj)
try:
self.server.invoke_successfully(interface_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as exc:
self.module.fail_json(msg='Error Creating interface %s: %s' %
(self.parameters['interface_name'], to_native(exc)), exception=traceback.format_exc())
def delete_interface(self, current_status):
''' calling zapi to delete interface '''
if current_status == 'up':
self.parameters['admin_status'] = 'down'
self.modify_interface({'admin_status': 'down'})
interface_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'net-interface-delete', **{'interface-name': self.parameters['interface_name'],
'vserver': self.parameters['vserver']})
try:
self.server.invoke_successfully(interface_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as exc:
self.module.fail_json(msg='Error deleting interface %s: %s' % (self.parameters['interface_name'], to_native(exc)),
exception=traceback.format_exc())
def modify_interface(self, modify):
"""
Modify the interface.
"""
options = {'interface-name': self.parameters['interface_name'],
'vserver': self.parameters['vserver']
}
NetAppOntapInterface.set_options(options, modify)
interface_modify = netapp_utils.zapi.NaElement.create_node_with_children('net-interface-modify', **options)
try:
self.server.invoke_successfully(interface_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as err:
self.module.fail_json(msg='Error modifying interface %s: %s' % (self.parameters['interface_name'],
to_native(err)), exception=traceback.format_exc())
def autosupport_log(self):
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_interface", cserver)
def apply(self):
''' calling all interface features '''
self.autosupport_log()
current = self.get_interface()
# rename and create are mutually exclusive
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_interface()
elif cd_action == 'delete':
self.delete_interface(current['admin_status'])
elif modify:
self.modify_interface(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
interface = NetAppOntapInterface()
interface.apply()
if __name__ == '__main__':
main()

@ -1,258 +0,0 @@
#!/usr/bin/python
"""
this is ipspace module
# (c) 2018, NTT Europe Ltd.
# 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': 'community'
}
DOCUMENTATION = '''
---
module: na_ontap_ipspace
short_description: NetApp ONTAP Manage an ipspace
version_added: '2.9'
author:
- NTTE Storage Engineering (@vicmunoz) <cl.eng.sto@ntt.eu>
description:
- Manage an ipspace for an Ontap Cluster
extends_documentation_fragment:
- netapp.na_ontap
options:
state:
description:
- Whether the specified ipspace should exist or not
choices: ['present', 'absent']
default: present
name:
description:
- The name of the ipspace to manage
required: true
from_name:
description:
- Name of the existing ipspace to be renamed to name
'''
EXAMPLES = """
- name: Create ipspace
na_ontap_ipspace:
state: present
name: ansibleIpspace
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete ipspace
na_ontap_ipspace:
state: absent
name: ansibleIpspace
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Rename ipspace
na_ontap_ipspace:
state: present
name: ansibleIpspace_newname
from_name: ansibleIpspace
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapIpspace(object):
'''Class with ipspace operations'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(
required=False, choices=['present', 'absent'],
default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
))
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def ipspace_get_iter(self, name):
"""
Return net-ipspaces-get-iter query results
:param name: Name of the ipspace
:return: NaElement if ipspace found, None otherwise
"""
ipspace_get_iter = netapp_utils.zapi.NaElement('net-ipspaces-get-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'net-ipspaces-info', **{'ipspace': name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
ipspace_get_iter.add_child_elem(query)
try:
result = self.server.invoke_successfully(
ipspace_get_iter, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
# Error 14636 denotes an ipspace does not exist
# Error 13073 denotes an ipspace not found
if to_native(error.code) == "14636" or to_native(error.code) == "13073":
return None
else:
self.module.self.fail_json(
msg=to_native(error),
exception=traceback.format_exc())
return result
def get_ipspace(self, name=None):
"""
Fetch details if ipspace exists
:param name: Name of the ipspace to be fetched
:return:
Dictionary of current details if ipspace found
None if ipspace is not found
"""
if name is None:
name = self.parameters['name']
ipspace_get = self.ipspace_get_iter(name)
if (ipspace_get and ipspace_get.get_child_by_name('num-records') and
int(ipspace_get.get_child_content('num-records')) >= 1):
current_ipspace = dict()
attr_list = ipspace_get.get_child_by_name('attributes-list')
attr = attr_list.get_child_by_name('net-ipspaces-info')
current_ipspace['name'] = attr.get_child_content('ipspace')
return current_ipspace
return None
def create_ipspace(self):
"""
Create ipspace
:return: None
"""
ipspace_create = netapp_utils.zapi.NaElement.create_node_with_children(
'net-ipspaces-create', **{'ipspace': self.parameters['name']})
try:
self.server.invoke_successfully(ipspace_create,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.self.fail_json(
msg="Error provisioning ipspace %s: %s" % (
self.parameters['name'],
to_native(error)),
exception=traceback.format_exc())
def delete_ipspace(self):
"""
Destroy ipspace
:return: None
"""
ipspace_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
'net-ipspaces-destroy',
**{'ipspace': self.parameters['name']})
try:
self.server.invoke_successfully(
ipspace_destroy, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.self.fail_json(
msg="Error removing ipspace %s: %s" % (
self.parameters['name'],
to_native(error)),
exception=traceback.format_exc())
def rename_ipspace(self):
"""
Rename an ipspace
:return: Nothing
"""
ipspace_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'net-ipspaces-rename',
**{'ipspace': self.parameters['from_name'],
'new-name': self.parameters['name']})
try:
self.server.invoke_successfully(ipspace_rename,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(
msg="Error renaming ipspace %s: %s" % (
self.parameters['from_name'],
to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to the ipspace
:return: Nothing
"""
current = self.get_ipspace()
# rename and create are mutually exclusive
rename, cd_action = None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(
self.get_ipspace(self.parameters['from_name']),
current)
if rename is None:
self.module.fail_json(
msg="Error renaming: ipspace %s does not exist" %
self.parameters['from_name'])
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_ipspace()
elif cd_action == 'create':
self.create_ipspace()
elif cd_action == 'delete':
self.delete_ipspace()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Execute action
:return: nothing
"""
obj = NetAppOntapIpspace()
obj.apply()
if __name__ == '__main__':
main()

@ -1,272 +0,0 @@
#!/usr/bin/python
# (c) 2017-2019, 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_ontap_iscsi
short_description: NetApp ONTAP manage iSCSI service
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- create, delete, start, stop iSCSI service on SVM.
options:
state:
description:
- Whether the service should be present or deleted.
choices: ['present', 'absent']
default: present
service_state:
description:
- Whether the specified service should running .
choices: ['started', 'stopped']
vserver:
required: true
description:
- The name of the vserver to use.
'''
EXAMPLES = """
- name: Create iscsi service
na_ontap_iscsi:
state: present
service_state: started
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Stop Iscsi service
na_ontap_iscsi:
state: present
service_state: stopped
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete Iscsi service
na_ontap_iscsi:
state: absent
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapISCSI(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
service_state=dict(required=False, choices=[
'started', 'stopped'], default=None),
vserver=dict(required=True, 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['state']
self.service_state = params['service_state']
if self.state == 'present' and self.service_state is None:
self.service_state = 'started'
self.vserver = params['vserver']
self.is_started = None
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.vserver)
def get_iscsi(self):
"""
Return details about the iscsi service
:return: Details about the iscsi service
:rtype: dict
"""
iscsi_info = netapp_utils.zapi.NaElement('iscsi-service-get-iter')
iscsi_attributes = netapp_utils.zapi.NaElement('iscsi-service-info')
iscsi_attributes.add_new_child('vserver', self.vserver)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(iscsi_attributes)
iscsi_info.add_child_elem(query)
result = self.server.invoke_successfully(iscsi_info, True)
return_value = None
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
iscsi = result.get_child_by_name(
'attributes-list').get_child_by_name('iscsi-service-info')
if iscsi:
is_started = iscsi.get_child_content('is-available') == 'true'
return_value = {
'is_started': is_started
}
return return_value
def create_iscsi_service(self):
"""
Create iscsi service and start if requested
"""
iscsi_service = netapp_utils.zapi.NaElement.create_node_with_children(
'iscsi-service-create',
**{'start': 'true' if self.state == 'started' else 'false'
})
try:
self.server.invoke_successfully(
iscsi_service, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error creating iscsi service: % s"
% (to_native(e)),
exception=traceback.format_exc())
def delete_iscsi_service(self):
"""
Delete the iscsi service
"""
if self.is_started:
self.stop_iscsi_service()
iscsi_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'iscsi-service-destroy')
try:
self.server.invoke_successfully(
iscsi_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error deleting iscsi service \
on vserver %s: %s"
% (self.vserver, to_native(e)),
exception=traceback.format_exc())
def stop_iscsi_service(self):
"""
Stop iscsi service
"""
iscsi_stop = netapp_utils.zapi.NaElement.create_node_with_children(
'iscsi-service-stop')
try:
self.server.invoke_successfully(iscsi_stop, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error Stopping iscsi service \
on vserver %s: %s"
% (self.vserver, to_native(e)),
exception=traceback.format_exc())
def start_iscsi_service(self):
"""
Start iscsi service
"""
iscsi_start = netapp_utils.zapi.NaElement.create_node_with_children(
'iscsi-service-start')
try:
self.server.invoke_successfully(iscsi_start, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error starting iscsi service \
on vserver %s: %s"
% (self.vserver, to_native(e)),
exception=traceback.format_exc())
def apply(self):
property_changed = False
iscsi_service_exists = False
netapp_utils.ems_log_event("na_ontap_iscsi", self.server)
iscsi_service_detail = self.get_iscsi()
if iscsi_service_detail:
self.is_started = iscsi_service_detail['is_started']
iscsi_service_exists = True
if self.state == 'absent':
property_changed = True
elif self.state == 'present':
is_started = 'started' if self.is_started else 'stopped'
property_changed = is_started != self.service_state
else:
if self.state == 'present':
property_changed = True
if property_changed:
if self.module.check_mode:
pass
else:
if self.state == 'present':
if not iscsi_service_exists:
self.create_iscsi_service() # the service is stopped when initially created
if self.service_state == 'started':
self.start_iscsi_service()
if iscsi_service_exists and self.service_state == 'stopped':
self.stop_iscsi_service()
elif self.state == 'absent':
self.delete_iscsi_service()
changed = property_changed
# TODO: include other details about the lun (size, etc.)
self.module.exit_json(changed=changed)
def main():
v = NetAppOntapISCSI()
v.apply()
if __name__ == '__main__':
main()

@ -1,297 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_job_schedule
short_description: NetApp ONTAP Job Schedule
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete/Modify job-schedules on ONTAP
options:
state:
description:
- Whether the specified job schedule should exist or not.
choices: ['present', 'absent']
default: present
name:
description:
- The name of the job-schedule to manage.
required: true
job_minutes:
description:
- The minute(s) of each hour when the job should be run.
Job Manager cron scheduling minute.
-1 represents all minutes and is
only supported for cron schedule create and modify.
Range is [-1..59]
type: list
job_hours:
version_added: '2.8'
description:
- The hour(s) of the day when the job should be run.
Job Manager cron scheduling hour.
-1 represents all hours and is
only supported for cron schedule create and modify.
Range is [-1..23]
type: list
job_months:
version_added: '2.8'
description:
- The month(s) when the job should be run.
Job Manager cron scheduling month.
-1 represents all months and is
only supported for cron schedule create and modify.
Range is [-1..11]
type: list
job_days_of_month:
version_added: '2.8'
description:
- The day(s) of the month when the job should be run.
Job Manager cron scheduling day of month.
-1 represents all days of a month from 1 to 31, and is
only supported for cron schedule create and modify.
Range is [-1..31]
type: list
job_days_of_week:
version_added: '2.8'
description:
- The day(s) in the week when the job should be run.
Job Manager cron scheduling day of week.
Zero represents Sunday. -1 represents all days of a week and is
only supported for cron schedule create and modify.
Range is [-1..6]
type: list
'''
EXAMPLES = """
- name: Create Job for 11.30PM at 10th of every month
na_ontap_job_schedule:
state: present
name: jobName
job_minutes: 30
job_hours: 23
job_days_of_month: 10
job_months: -1
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete Job
na_ontap_job_schedule:
state: absent
name: jobName
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPJob(object):
'''Class with job schedule cron methods'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
name=dict(required=True, type='str'),
job_minutes=dict(required=False, type='list'),
job_months=dict(required=False, type='list'),
job_hours=dict(required=False, type='list'),
job_days_of_month=dict(required=False, type='list'),
job_days_of_week=dict(required=False, type='list')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.set_playbook_zapi_key_map()
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def set_playbook_zapi_key_map(self):
self.na_helper.zapi_string_keys = {
'name': 'job-schedule-name',
}
self.na_helper.zapi_list_keys = {
'job_minutes': ('job-schedule-cron-minute', 'cron-minute'),
'job_months': ('job-schedule-cron-month', 'cron-month'),
'job_hours': ('job-schedule-cron-hour', 'cron-hour'),
'job_days_of_month': ('job-schedule-cron-day', 'cron-day-of-month'),
'job_days_of_week': ('job-schedule-cron-day-of-week', 'cron-day-of-week')
}
def get_job_schedule(self):
"""
Return details about the job
:param:
name : Job name
:return: Details about the Job. None if not found.
:rtype: dict
"""
job_get_iter = netapp_utils.zapi.NaElement('job-schedule-cron-get-iter')
job_get_iter.translate_struct({
'query': {
'job-schedule-cron-info': {
'job-schedule-name': self.parameters['name']
}
}
})
result = self.server.invoke_successfully(job_get_iter, True)
job_details = None
# check if job exists
if result.get_child_by_name('num-records') and int(result['num-records']) >= 1:
job_info = result['attributes-list']['job-schedule-cron-info']
job_details = dict()
for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
job_details[item_key] = job_info[zapi_key]
for item_key, zapi_key in self.na_helper.zapi_list_keys.items():
parent, dummy = zapi_key
job_details[item_key] = self.na_helper.get_value_for_list(from_zapi=True,
zapi_parent=job_info.get_child_by_name(parent)
)
# if any of the job_hours, job_minutes, job_months, job_days are empty:
# it means the value is -1 for ZAPI
if not job_details[item_key]:
job_details[item_key] = ['-1']
return job_details
def add_job_details(self, na_element_object, values):
"""
Add children node for create or modify NaElement object
:param na_element_object: modify or create NaElement object
:param values: dictionary of cron values to be added
:return: None
"""
for item_key in values:
if item_key in self.na_helper.zapi_string_keys:
zapi_key = self.na_helper.zapi_string_keys.get(item_key)
na_element_object[zapi_key] = values[item_key]
elif item_key in self.na_helper.zapi_list_keys:
parent_key, child_key = self.na_helper.zapi_list_keys.get(item_key)
na_element_object.add_child_elem(self.na_helper.get_value_for_list(from_zapi=False,
zapi_parent=parent_key,
zapi_child=child_key,
data=values.get(item_key)))
def create_job_schedule(self):
"""
Creates a job schedule
"""
# job_minutes is mandatory for create
if self.parameters.get('job_minutes') is None:
self.module.fail_json(msg='Error: missing required parameter job_minutes for create')
job_schedule_create = netapp_utils.zapi.NaElement('job-schedule-cron-create')
self.add_job_details(job_schedule_create, self.parameters)
try:
self.server.invoke_successfully(job_schedule_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating job schedule %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_job_schedule(self):
"""
Delete a job schedule
"""
job_schedule_delete = netapp_utils.zapi.NaElement('job-schedule-cron-destroy')
self.add_job_details(job_schedule_delete, self.parameters)
try:
self.server.invoke_successfully(job_schedule_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting job schedule %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_job_schedule(self, params):
"""
modify a job schedule
"""
job_schedule_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'job-schedule-cron-modify', **{'job-schedule-name': self.parameters['name']})
self.add_job_details(job_schedule_modify, params)
try:
self.server.invoke_successfully(job_schedule_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying job schedule %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
"""
Autosupport log for job_schedule
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_job_schedule", cserver)
def apply(self):
"""
Apply action to job-schedule
"""
self.autosupport_log()
current = self.get_job_schedule()
action = self.na_helper.get_cd_action(current, self.parameters)
if action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if action == 'create':
self.create_job_schedule()
elif action == 'delete':
self.delete_job_schedule()
elif modify:
self.modify_job_schedule(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
'''Execute action'''
job_obj = NetAppONTAPJob()
job_obj.apply()
if __name__ == '__main__':
main()

@ -1,318 +0,0 @@
#!/usr/bin/python
'''
(c) 2019, Red Hat, 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_ontap_kerberos_realm
short_description: NetApp ONTAP vserver nfs kerberos realm
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: Milan Zink (@zeten30) <zeten30@gmail.com>,<mzink@redhat.com>
description:
- Create, modify or delete vserver kerberos realm configuration
options:
state:
description:
- Whether the Kerberos realm is present or absent.
choices: ['present', 'absent']
default: 'present'
type: str
vserver:
description:
- vserver/svm with kerberos realm configured
required: true
type: str
realm:
description:
- Kerberos realm name
required: true
type: str
kdc_vendor:
description:
- The vendor of the Key Distribution Centre (KDC) server
- Required if I(state=present)
choices: ['Other', 'Microsoft']
type: str
kdc_ip:
description:
- IP address of the Key Distribution Centre (KDC) server
- Required if I(state=present)
type: str
kdc_port:
description:
- TCP port on the KDC to be used for Kerberos communication.
- The default for this parameter is '88'.
type: str
clock_skew:
description:
- The clock skew in minutes is the tolerance for accepting tickets with time stamps that do not exactly match the host's system clock.
- The default for this parameter is '5' minutes.
type: str
comment:
description:
- Optional comment
type: str
admin_server_ip:
description:
- IP address of the host where the Kerberos administration daemon is running. This is usually the master KDC.
- If this parameter is omitted, the address specified in kdc_ip is used.
type: str
admin_server_port:
description:
- The TCP port on the Kerberos administration server where the Kerberos administration service is running.
- The default for this parameter is '749'
type: str
pw_server_ip:
description:
- IP address of the host where the Kerberos password-changing server is running.
- Typically, this is the same as the host indicated in the adminserver-ip.
- If this parameter is omitted, the IP address in kdc-ip is used.
type: str
pw_server_port:
description:
- The TCP port on the Kerberos password-changing server where the Kerberos password-changing service is running.
- The default for this parameter is '464'.
type: str
'''
EXAMPLES = '''
- name: Create kerberos realm
na_ontap_kerberos_realm:
state: present
realm: 'EXAMPLE.COM'
vserver: 'vserver1'
kdc_ip: '1.2.3.4'
kdc_vendor: 'Other'
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = '''
'''
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapKerberosRealm(object):
'''
Kerberos Realm definition class
'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
admin_server_ip=dict(required=False, default=None, type='str'),
admin_server_port=dict(required=False, default=None, type='str'),
clock_skew=dict(required=False, default=None, type='str'),
comment=dict(required=False, default=None, type='str'),
kdc_ip=dict(required_if=[["state", "present"]], default=None, type='str'),
kdc_port=dict(required=False, default=None, type='str'),
kdc_vendor=dict(required_if=[["state", "present"]], default=None, type='str', choices=['Microsoft', 'Other']),
pw_server_ip=dict(required=False, default=None, type='str'),
pw_server_port=dict(required=False, default=None, type='str'),
realm=dict(required=True, type='str'),
state=dict(required=False, choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True,
required_if=[('state', 'present', ['kdc_vendor', 'kdc_ip'])],
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
self.simple_attributes = [
'admin_server_ip',
'admin_server_port',
'clock_skew',
'kdc_ip',
'kdc_port',
'kdc_vendor',
]
def get_krbrealm(self, realm_name=None, vserver_name=None):
'''
Checks if Kerberos Realm config exists.
:return:
kerberos realm object if found
None if not found
:rtype: object/None
'''
# Make query
krbrealm_info = netapp_utils.zapi.NaElement('kerberos-realm-get-iter')
if realm_name is None:
realm_name = self.parameters['realm']
if vserver_name is None:
vserver_name = self.parameters['vserver']
query_details = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm', **{'realm': realm_name, 'vserver-name': vserver_name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
krbrealm_info.add_child_elem(query)
result = self.server.invoke_successfully(krbrealm_info, enable_tunneling=True)
# Get Kerberos Realm details
krbrealm_details = None
if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1):
attributes_list = result.get_child_by_name('attributes-list')
config_info = attributes_list.get_child_by_name('kerberos-realm')
krbrealm_details = {
'admin_server_ip': config_info.get_child_content('admin-server-ip'),
'admin_server_port': config_info.get_child_content('admin-server-port'),
'clock_skew': config_info.get_child_content('clock-skew'),
'kdc_ip': config_info.get_child_content('kdc-ip'),
'kdc_port': config_info.get_child_content('kdc-port'),
'kdc_vendor': config_info.get_child_content('kdc-vendor'),
'pw_server_ip': config_info.get_child_content('password-server-ip'),
'pw_server_port': config_info.get_child_content('password-server-port'),
'realm': config_info.get_child_content('realm'),
'vserver': config_info.get_child_content('vserver'),
}
return krbrealm_details
def create_krbrealm(self):
'''supported
Create Kerberos Realm configuration
'''
options = {
'realm': self.parameters['realm']
}
# Other options/attributes
for attribute in self.simple_attributes:
if self.parameters.get(attribute) is not None:
options[str(attribute).replace('_', '-')] = self.parameters[attribute]
if self.parameters.get('pw_server_ip') is not None:
options['password-server-ip'] = self.parameters['pw_server_ip']
if self.parameters.get('pw_server_port') is not None:
options['password-server-port'] = self.parameters['pw_server_port']
# Initialize NaElement
krbrealm_create = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm-create', **options)
# Try to create Kerberos Realm configuration
try:
self.server.invoke_successfully(krbrealm_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error creating Kerberos Realm configuration %s: %s' % (self.parameters['realm'], to_native(errcatch)),
exception=traceback.format_exc())
def delete_krbrealm(self):
'''
Delete Kerberos Realm configuration
'''
krbrealm_delete = netapp_utils.zapi.NaElement.create_node_with_children('kerberos-realm-delete', **{'realm': self.parameters['realm']})
try:
self.server.invoke_successfully(krbrealm_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error deleting Kerberos Realm configuration %s: %s' % (
self.parameters['realm'], to_native(errcatch)), exception=traceback.format_exc())
def modify_krbrealm(self, modify):
'''
Modify Kerberos Realm
:param modify: list of modify attributes
'''
krbrealm_modify = netapp_utils.zapi.NaElement('kerberos-realm-modify')
krbrealm_modify.add_new_child('realm', self.parameters['realm'])
for attribute in modify:
if attribute in self.simple_attributes:
krbrealm_modify.add_new_child(str(attribute).replace('_', '-'), self.parameters[attribute])
if attribute == 'pw_server_ip':
krbrealm_modify.add_new_child('password-server-ip', self.parameters['pw_server_ip'])
if attribute == 'pw_server_port':
krbrealm_modify.add_new_child('password-server-port', self.parameters['pw_server_port'])
# Try to modify Kerberos Realm
try:
self.server.invoke_successfully(krbrealm_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error modifying Kerberos Realm %s: %s' % (self.parameters['realm'], to_native(errcatch)),
exception=traceback.format_exc())
def apply(self):
'''Call create/modify/delete operations.'''
current = self.get_krbrealm()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
# create an ems log event for users with auto support turned on
netapp_utils.ems_log_event("na_ontap_kerberos_realm", self.server)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_krbrealm()
elif cd_action == 'delete':
self.delete_krbrealm()
elif modify:
self.modify_krbrealm(modify)
self.module.exit_json(changed=self.na_helper.changed)
#
# MAIN
#
def main():
'''ONTAP Kerberos Realm'''
krbrealm = NetAppOntapKerberosRealm()
krbrealm.apply()
if __name__ == '__main__':
main()

@ -1,228 +0,0 @@
#!/usr/bin/python
'''
(c) 2018-2019, 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_ontap_ldap
short_description: NetApp ONTAP LDAP
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: Milan Zink (@zeten30) <zeten30@gmail.com>/<mzink@redhat.com>
description:
- Create, modify or delete LDAP on NetApp ONTAP SVM/vserver
options:
state:
description:
- Whether the LDAP is present or not.
choices: ['present', 'absent']
default: 'present'
type: str
vserver:
description:
- vserver/svm configured to use LDAP
required: true
type: str
name:
description:
- The name of LDAP client configuration
required: true
type: str
skip_config_validation:
description:
- Skip LDAP validation
choices: ['true', 'false']
type: str
'''
EXAMPLES = '''
- name: Enable LDAP on SVM
na_ontap_ldap:
state: present
name: 'example_ldap'
vserver: 'vserver1'
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = '''
'''
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapLDAP(object):
'''
LDAP Client definition class
'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
name=dict(required=True, type='str'),
skip_config_validation=dict(required=False, default=None, choices=['true', 'false']),
state=dict(required=False, choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str')
))
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_ldap(self, client_config_name=None):
'''
Checks if LDAP config exists.
:return:
ldap config object if found
None if not found
:rtype: object/None
'''
# Make query
config_info = netapp_utils.zapi.NaElement('ldap-config-get-iter')
if client_config_name is None:
client_config_name = self.parameters['name']
query_details = netapp_utils.zapi.NaElement.create_node_with_children('ldap-config', **{'client-config': client_config_name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
config_info.add_child_elem(query)
result = self.server.invoke_successfully(config_info, enable_tunneling=True)
# Get LDAP configuration details
config_details = None
if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1):
attributes_list = result.get_child_by_name('attributes-list')
config_info = attributes_list.get_child_by_name('ldap-config')
# Define config details structure
config_details = {'client_config': config_info.get_child_content('client-config'),
'skip_config_validation': config_info.get_child_content('skip-config-validation'),
'vserver': config_info.get_child_content('vserver')}
return config_details
def create_ldap(self):
'''
Create LDAP configuration
'''
options = {
'client-config': self.parameters['name'],
'client-enabled': 'true'
}
if self.parameters.get('skip_config_validation') is not None:
options['skip-config-validation'] = self.parameters['skip_config_validation']
# Initialize NaElement
ldap_create = netapp_utils.zapi.NaElement.create_node_with_children('ldap-config-create', **options)
# Try to create LDAP configuration
try:
self.server.invoke_successfully(ldap_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error creating LDAP configuration %s: %s' % (self.parameters['name'], to_native(errcatch)),
exception=traceback.format_exc())
def delete_ldap(self):
'''
Delete LDAP configuration
'''
ldap_client_delete = netapp_utils.zapi.NaElement.create_node_with_children('ldap-config-delete', **{})
try:
self.server.invoke_successfully(ldap_client_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error deleting LDAP configuration %s: %s' % (
self.parameters['name'], to_native(errcatch)), exception=traceback.format_exc())
def modify_ldap(self, modify):
'''
Modify LDAP
:param modify: list of modify attributes
'''
ldap_modify = netapp_utils.zapi.NaElement('ldap-config-modify')
ldap_modify.add_new_child('client-config', self.parameters['name'])
for attribute in modify:
if attribute == 'skip_config_validation':
ldap_modify.add_new_child('skip-config-validation', self.parameters[attribute])
# Try to modify LDAP
try:
self.server.invoke_successfully(ldap_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error modifying LDAP %s: %s' % (self.parameters['name'], to_native(errcatch)),
exception=traceback.format_exc())
def apply(self):
'''Call create/modify/delete operations.'''
current = self.get_ldap()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
# create an ems log event for users with auto support turned on
netapp_utils.ems_log_event("na_ontap_ldap", self.server)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_ldap()
elif cd_action == 'delete':
self.delete_ldap()
elif modify:
self.modify_ldap(modify)
self.module.exit_json(changed=self.na_helper.changed)
#
# MAIN
#
def main():
'''ONTAP LDAP client configuration'''
ldapclient = NetAppOntapLDAP()
ldapclient.apply()
if __name__ == '__main__':
main()

@ -1,359 +0,0 @@
#!/usr/bin/python
'''
(c) 2018-2019, 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_ontap_ldap_client
short_description: NetApp ONTAP LDAP client
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: Milan Zink (@zeten30) <zeten30@gmail.com>/<mzink@redhat.com>
description:
- Create, modify or delete LDAP client on NetApp ONTAP
options:
state:
description:
- Whether the specified LDAP client configuration exist or not.
choices: ['present', 'absent']
default: 'present'
type: str
vserver:
description:
- vserver/svm that holds LDAP client configuration
required: true
type: str
name:
description:
- The name of LDAP client configuration
required: true
type: str
ldap_servers:
description:
- Comma separated list of LDAP servers. FQDN's or IP addresses
- Required if I(state=present).
type: list
schema:
description:
- LDAP schema
- Required if I(state=present).
choices: ['AD-IDMU', 'AD-SFU', 'MS-AD-BIS', 'RFC-2307']
type: str
base_dn:
description:
- LDAP base DN
type: str
base_scope:
description:
- LDAP search scope
choices: ['subtree', 'onelevel', 'base']
type: str
port:
description:
- LDAP server port
type: int
query_timeout:
description:
- LDAP server query timeout
type: int
min_bind_level:
description:
- Minimal LDAP server bind level.
choices: ['anonymous', 'simple', 'sasl']
type: str
bind_dn:
description:
- LDAP bind user DN
type: str
bind_password:
description:
- LDAP bind user password
type: str
use_start_tls:
description:
- Start TLS on LDAP connection
choices: ['true', 'false']
type: str
referral_enabled:
description:
- LDAP Referral Chasing
choices: ['true', 'false']
type: str
session_security:
description:
- Client Session Security
choices: ['true', 'false']
type: str
'''
EXAMPLES = '''
- name: Create LDAP client
na_ontap_ldap_client:
state: present
name: 'example_ldap'
vserver: 'vserver1'
ldap_servers: 'ldap1.example.company.com,ldap2.example.company.com'
base_dn: 'dc=example,dc=company,dc=com'
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = '''
'''
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapLDAPClient(object):
'''
LDAP Client definition class
'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
base_dn=dict(required=False, type='str'),
base_scope=dict(required=False, default=None, choices=['subtree', 'onelevel', 'base']),
bind_dn=dict(required=False, default=None, type='str'),
bind_password=dict(type='str', required=False, default=None, no_log=True),
name=dict(required=True, type='str'),
ldap_servers=dict(required_if=[["state", "present"]], type='list'),
min_bind_level=dict(required=False, default=None, choices=['anonymous', 'simple', 'sasl']),
port=dict(required=False, default=None, type='int'),
query_timeout=dict(required=False, default=None, type='int'),
referral_enabled=dict(required=False, default=None, choices=['true', 'false']),
schema=dict(required_if=[["state", "present"]], default=None, type='str', choices=['AD-IDMU', 'AD-SFU', 'MS-AD-BIS', 'RFC-2307']),
session_security=dict(required=False, default=None, choices=['true', 'false']),
state=dict(required=False, choices=['present', 'absent'], default='present'),
use_start_tls=dict(required=False, default=None, choices=['true', 'false']),
vserver=dict(required=True, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True,
required_if=[('state', 'present', ['ldap_servers', 'schema'])],
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
self.simple_attributes = [
'base_dn',
'base_scope',
'bind_dn',
'bind_password',
'min_bind_level',
'port',
'query_timeout',
'referral_enabled',
'session_security',
'use_start_tls'
]
def get_ldap_client(self, client_config_name=None, vserver_name=None):
'''
Checks if LDAP client config exists.
:return:
ldap client config object if found
None if not found
:rtype: object/None
'''
# Make query
client_config_info = netapp_utils.zapi.NaElement('ldap-client-get-iter')
if client_config_name is None:
client_config_name = self.parameters['name']
if vserver_name is None:
vserver_name = '*'
query_details = netapp_utils.zapi.NaElement.create_node_with_children('ldap-client',
**{'ldap-client-config': client_config_name, 'vserver': vserver_name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
client_config_info.add_child_elem(query)
result = self.server.invoke_successfully(client_config_info, enable_tunneling=False)
# Get LDAP client configuration details
client_config_details = None
if (result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1):
attributes_list = result.get_child_by_name('attributes-list')
client_config_info = attributes_list.get_child_by_name('ldap-client')
# Get LDAP servers list
ldap_server_list = list()
get_list = client_config_info.get_child_by_name('ldap-servers')
if get_list is not None:
ldap_servers = get_list.get_children()
for ldap_server in ldap_servers:
ldap_server_list.append(ldap_server.get_content())
# Define config details structure
client_config_details = {'name': client_config_info.get_child_content('ldap-client-config'),
'ldap_servers': client_config_info.get_child_content('ldap-servers'),
'base_dn': client_config_info.get_child_content('base-dn'),
'base_scope': client_config_info.get_child_content('base-scope'),
'bind_dn': client_config_info.get_child_content('bind-dn'),
'bind_password': client_config_info.get_child_content('bind-password'),
'min_bind_level': client_config_info.get_child_content('min-bind-level'),
'port': client_config_info.get_child_content('port'),
'query_timeout': client_config_info.get_child_content('query-timeout'),
'referral_enabled': client_config_info.get_child_content('referral-enabled'),
'schema': client_config_info.get_child_content('schema'),
'session_security': client_config_info.get_child_content('session-security'),
'use_start_tls': client_config_info.get_child_content('use-start-tls'),
'vserver': client_config_info.get_child_content('vserver')}
return client_config_details
def create_ldap_client(self):
'''
Create LDAP client configuration
'''
# LDAP servers NaElement
ldap_servers_element = netapp_utils.zapi.NaElement('ldap-servers')
# Mandatory options
for ldap_server_name in self.parameters['ldap_servers']:
ldap_servers_element.add_new_child('string', ldap_server_name)
options = {
'ldap-client-config': self.parameters['name'],
'schema': self.parameters['schema'],
}
# Other options/attributes
for attribute in self.simple_attributes:
if self.parameters.get(attribute) is not None:
options[str(attribute).replace('_', '-')] = self.parameters[attribute]
# Initialize NaElement
ldap_client_create = netapp_utils.zapi.NaElement.create_node_with_children('ldap-client-create', **options)
ldap_client_create.add_child_elem(ldap_servers_element)
# Try to create LDAP configuration
try:
self.server.invoke_successfully(ldap_client_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error creating LDAP client %s: %s' % (self.parameters['name'], to_native(errcatch)),
exception=traceback.format_exc())
def delete_ldap_client(self):
'''
Delete LDAP client configuration
'''
ldap_client_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'ldap-client-delete', **{'ldap-client-config': self.parameters['name']})
try:
self.server.invoke_successfully(ldap_client_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error deleting LDAP client configuration %s: %s' % (
self.parameters['name'], to_native(errcatch)), exception=traceback.format_exc())
def modify_ldap_client(self, modify):
'''
Modify LDAP client
:param modify: list of modify attributes
'''
ldap_client_modify = netapp_utils.zapi.NaElement('ldap-client-modify')
ldap_client_modify.add_new_child('ldap-client-config', self.parameters['name'])
for attribute in modify:
# LDAP_servers
if attribute == 'ldap_servers':
ldap_servers_element = netapp_utils.zapi.NaElement('ldap-servers')
for ldap_server_name in self.parameters['ldap_servers']:
ldap_servers_element.add_new_child('string', ldap_server_name)
ldap_client_modify.add_child_elem(ldap_servers_element)
# Simple attributes
if attribute in self.simple_attributes:
ldap_client_modify.add_new_child(str(attribute).replace('_', '-'), self.parameters[attribute])
# Try to modify LDAP client
try:
self.server.invoke_successfully(ldap_client_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as errcatch:
self.module.fail_json(msg='Error modifying LDAP client %s: %s' % (self.parameters['name'], to_native(errcatch)),
exception=traceback.format_exc())
def apply(self):
'''Call create/modify/delete operations.'''
current = self.get_ldap_client()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
# create an ems log event for users with auto support turned on
netapp_utils.ems_log_event("na_ontap_ldap_client", self.server)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_ldap_client()
elif cd_action == 'delete':
self.delete_ldap_client()
elif modify:
self.modify_ldap_client(modify)
self.module.exit_json(changed=self.na_helper.changed)
#
# MAIN
#
def main():
'''ONTAP LDAP client configuration'''
ldapclient = NetAppOntapLDAPClient()
ldapclient.apply()
if __name__ == '__main__':
main()

@ -1,326 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_license
short_description: NetApp ONTAP protocol and feature licenses
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Add or remove licenses on NetApp ONTAP.
options:
state:
description:
- Whether the specified license should exist or not.
choices: ['present', 'absent']
default: present
remove_unused:
description:
- Remove licenses that have no controller affiliation in the cluster.
type: bool
remove_expired:
description:
- Remove licenses that have expired in the cluster.
type: bool
serial_number:
description:
Serial number of the node associated with the license.
This parameter is used primarily when removing license for a specific service.
license_names:
description:
- List of license-names to delete.
suboptions:
base:
description:
- Cluster Base License
nfs:
description:
- NFS License
cifs:
description:
- CIFS License
iscsi:
description:
- iSCSI License
fcp:
description:
- FCP License
cdmi:
description:
- CDMI License
snaprestore:
description:
- SnapRestore License
snapmirror:
description:
- SnapMirror License
flexclone:
description:
- FlexClone License
snapvault:
description:
- SnapVault License
snaplock:
description:
- SnapLock License
snapmanagersuite:
description:
- SnapManagerSuite License
snapprotectapps:
description:
- SnapProtectApp License
v_storageattach:
description:
- Virtual Attached Storage License
license_codes:
description:
- List of license codes to be added.
'''
EXAMPLES = """
- name: Add licenses
na_ontap_license:
state: present
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
serial_number: #################
license_codes: CODE1,CODE2
- name: Remove licenses
na_ontap_license:
state: absent
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
remove_unused: false
remove_expired: true
serial_number: #################
license_names: nfs,cifs
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
def local_cmp(a, b):
"""
compares with only values and not keys, keys should be the same for both dicts
:param a: dict 1
:param b: dict 2
:return: difference of values in both dicts
"""
diff = [key for key in a if a[key] != b[key]]
return len(diff)
class NetAppOntapLicense(object):
'''ONTAP license class'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
serial_number=dict(required=False, type='str'),
remove_unused=dict(default=None, type='bool'),
remove_expired=dict(default=None, type='bool'),
license_codes=dict(default=None, type='list'),
license_names=dict(default=None, type='list'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=False,
required_if=[
('state', 'absent', ['serial_number', 'license_names'])]
)
parameters = self.module.params
# set up state variables
self.state = parameters['state']
self.serial_number = parameters['serial_number']
self.remove_unused = parameters['remove_unused']
self.remove_expired = parameters['remove_expired']
self.license_codes = parameters['license_codes']
self.license_names = parameters['license_names']
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_licensing_status(self):
"""
Check licensing status
:return: package (key) and licensing status (value)
:rtype: dict
"""
license_status = netapp_utils.zapi.NaElement(
'license-v2-status-list-info')
result = None
try:
result = self.server.invoke_successfully(license_status,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error checking license status: %s" %
to_native(error), exception=traceback.format_exc())
return_dictionary = {}
license_v2_status = result.get_child_by_name('license-v2-status')
if license_v2_status:
for license_v2_status_info in license_v2_status.get_children():
package = license_v2_status_info.get_child_content('package')
status = license_v2_status_info.get_child_content('method')
return_dictionary[package] = status
return return_dictionary
def remove_licenses(self, package_name):
"""
Remove requested licenses
:param:
package_name: Name of the license to be deleted
"""
license_delete = netapp_utils.zapi.NaElement('license-v2-delete')
license_delete.add_new_child('serial-number', self.serial_number)
license_delete.add_new_child('package', package_name)
try:
self.server.invoke_successfully(license_delete,
enable_tunneling=False)
return True
except netapp_utils.zapi.NaApiError as error:
# Error 15661 - Object not found
if to_native(error.code) == "15661":
return False
else:
self.module.fail_json(msg="Error removing license %s" %
to_native(error), exception=traceback.format_exc())
def remove_unused_licenses(self):
"""
Remove unused licenses
"""
remove_unused = netapp_utils.zapi.NaElement('license-v2-delete-unused')
try:
self.server.invoke_successfully(remove_unused,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error removing unused licenses: %s" %
to_native(error), exception=traceback.format_exc())
def remove_expired_licenses(self):
"""
Remove expired licenses
"""
remove_expired = netapp_utils.zapi.NaElement(
'license-v2-delete-expired')
try:
self.server.invoke_successfully(remove_expired,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error removing expired licenses: %s" %
to_native(error), exception=traceback.format_exc())
def add_licenses(self):
"""
Add licenses
"""
license_add = netapp_utils.zapi.NaElement('license-v2-add')
codes = netapp_utils.zapi.NaElement('codes')
for code in self.license_codes:
codes.add_new_child('license-code-v2', str(code.strip().lower()))
license_add.add_child_elem(codes)
try:
self.server.invoke_successfully(license_add,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error adding licenses: %s" %
to_native(error), exception=traceback.format_exc())
def apply(self):
'''Call add, delete or modify methods'''
changed = False
create_license = False
remove_license = False
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_license", cserver)
# Add / Update licenses.
license_status = self.get_licensing_status()
if self.state == 'absent': # delete
changed = True
else: # add or update
if self.license_codes is not None:
create_license = True
changed = True
if self.remove_unused is not None:
remove_license = True
changed = True
if self.remove_expired is not None:
remove_license = True
changed = True
if changed:
if self.state == 'present': # execute create
if create_license:
self.add_licenses()
if self.remove_unused is not None:
self.remove_unused_licenses()
if self.remove_expired is not None:
self.remove_expired_licenses()
if create_license or remove_license:
new_license_status = self.get_licensing_status()
if local_cmp(license_status, new_license_status) == 0:
changed = False
else: # execute delete
license_deleted = False
for package in self.license_names:
license_deleted |= self.remove_licenses(package)
changed = license_deleted
self.module.exit_json(changed=changed)
def main():
'''Apply license operations'''
obj = NetAppOntapLicense()
obj.apply()
if __name__ == '__main__':
main()

@ -1,406 +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_ontap_lun
short_description: NetApp ONTAP manage LUNs
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create, destroy, resize LUNs on NetApp ONTAP.
options:
state:
description:
- Whether the specified LUN should exist or not.
choices: ['present', 'absent']
default: present
name:
description:
- The name of the LUN to manage.
required: true
flexvol_name:
description:
- The name of the FlexVol the LUN should exist on.
required: true
size:
description:
- The size of the LUN in C(size_unit).
- Required when C(state=present).
size_unit:
description:
- The unit used to interpret the size parameter.
choices: ['bytes', 'b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb']
default: 'gb'
force_resize:
description:
Forcibly reduce the size. This is required for reducing the size of the LUN to avoid accidentally
reducing the LUN size.
type: bool
default: false
force_remove:
description:
- If "true", override checks that prevent a LUN from being destroyed if it is online and mapped.
- If "false", destroying an online and mapped LUN will fail.
type: bool
default: false
force_remove_fenced:
description:
- If "true", override checks that prevent a LUN from being destroyed while it is fenced.
- If "false", attempting to destroy a fenced LUN will fail.
- The default if not specified is "false". This field is available in Data ONTAP 8.2 and later.
type: bool
default: false
vserver:
required: true
description:
- The name of the vserver to use.
ostype:
description:
- The os type for the LUN.
default: 'image'
space_reserve:
description:
- This can be set to "false" which will create a LUN without any space being reserved.
type: bool
default: True
space_allocation:
description:
- This enables support for the SCSI Thin Provisioning features. If the Host and file system do
not support this do not enable it.
type: bool
default: False
version_added: '2.7'
'''
EXAMPLES = """
- name: Create LUN
na_ontap_lun:
state: present
name: ansibleLUN
flexvol_name: ansibleVolume
vserver: ansibleVServer
size: 5
size_unit: mb
ostype: linux
space_reserve: True
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Resize LUN
na_ontap_lun:
state: present
name: ansibleLUN
force_resize: True
flexvol_name: ansibleVolume
vserver: ansibleVServer
size: 5
size_unit: gb
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapLUN(object):
def __init__(self):
self._size_unit_map = dict(
bytes=1,
b=1,
kb=1024,
mb=1024 ** 2,
gb=1024 ** 3,
tb=1024 ** 4,
pb=1024 ** 5,
eb=1024 ** 6,
zb=1024 ** 7,
yb=1024 ** 8
)
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
size=dict(type='int'),
size_unit=dict(default='gb',
choices=['bytes', 'b', 'kb', 'mb', 'gb', 'tb',
'pb', 'eb', 'zb', 'yb'], type='str'),
force_resize=dict(default=False, type='bool'),
force_remove=dict(default=False, type='bool'),
force_remove_fenced=dict(default=False, type='bool'),
flexvol_name=dict(required=True, type='str'),
vserver=dict(required=True, type='str'),
ostype=dict(required=False, type='str', default='image'),
space_reserve=dict(required=False, type='bool', default=True),
space_allocation=dict(required=False, type='bool', default=False),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['size'])
],
supports_check_mode=True
)
parameters = self.module.params
# set up state variables
self.state = parameters['state']
self.name = parameters['name']
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.force_resize = parameters['force_resize']
self.force_remove = parameters['force_remove']
self.force_remove_fenced = parameters['force_remove_fenced']
self.flexvol_name = parameters['flexvol_name']
self.vserver = parameters['vserver']
self.ostype = parameters['ostype']
self.space_reserve = parameters['space_reserve']
self.space_allocation = parameters['space_allocation']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def get_lun(self):
"""
Return details about the LUN
:return: Details about the lun
:rtype: dict
"""
luns = []
tag = None
while True:
lun_info = netapp_utils.zapi.NaElement('lun-get-iter')
if tag:
lun_info.add_new_child('tag', tag, True)
query_details = netapp_utils.zapi.NaElement('lun-info')
query_details.add_new_child('vserver', self.vserver)
query_details.add_new_child('volume', self.flexvol_name)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
lun_info.add_child_elem(query)
result = self.server.invoke_successfully(lun_info, True)
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
attr_list = result.get_child_by_name('attributes-list')
luns.extend(attr_list.get_children())
tag = result.get_child_content('next-tag')
if tag is None:
break
# The LUNs have been extracted.
# Find the specified lun and extract details.
return_value = None
for lun in luns:
path = lun.get_child_content('path')
_rest, _splitter, found_name = path.rpartition('/')
if found_name == self.name:
size = lun.get_child_content('size')
# Find out if the lun is attached
attached_to = None
lun_id = None
if lun.get_child_content('mapped') == 'true':
lun_map_list = netapp_utils.zapi.NaElement.create_node_with_children(
'lun-map-list-info', **{'path': path})
result = self.server.invoke_successfully(
lun_map_list, enable_tunneling=True)
igroups = result.get_child_by_name('initiator-groups')
if igroups:
for igroup_info in igroups.get_children():
igroup = igroup_info.get_child_content(
'initiator-group-name')
attached_to = igroup
lun_id = igroup_info.get_child_content('lun-id')
return_value = {
'name': found_name,
'size': size,
'attached_to': attached_to,
'lun_id': lun_id
}
else:
continue
return return_value
def create_lun(self):
"""
Create LUN with requested name and size
"""
path = '/vol/%s/%s' % (self.flexvol_name, self.name)
lun_create = netapp_utils.zapi.NaElement.create_node_with_children(
'lun-create-by-size', **{'path': path,
'size': str(self.size),
'ostype': self.ostype,
'space-reservation-enabled': str(self.space_reserve),
'space-allocation-enabled': str(self.space_allocation)})
try:
self.server.invoke_successfully(lun_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error provisioning lun %s of size %s: %s" % (self.name, self.size, to_native(e)),
exception=traceback.format_exc())
def delete_lun(self):
"""
Delete requested LUN
"""
path = '/vol/%s/%s' % (self.flexvol_name, self.name)
lun_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'lun-destroy', **{'path': path,
'force': str(self.force_remove),
'destroy-fenced-lun':
str(self.force_remove_fenced)})
try:
self.server.invoke_successfully(lun_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error deleting lun %s: %s" % (path, to_native(e)),
exception=traceback.format_exc())
def resize_lun(self):
"""
Resize requested LUN.
:return: True if LUN was actually re-sized, false otherwise.
:rtype: bool
"""
path = '/vol/%s/%s' % (self.flexvol_name, self.name)
lun_resize = netapp_utils.zapi.NaElement.create_node_with_children(
'lun-resize', **{'path': path,
'size': str(self.size),
'force': str(self.force_resize)})
try:
self.server.invoke_successfully(lun_resize, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
if to_native(e.code) == "9042":
# Error 9042 denotes the new LUN size being the same as the
# old LUN size. This happens when there's barely any difference
# in the two sizes. For example, from 8388608 bytes to
# 8194304 bytes. This should go away if/when the default size
# requested/reported to/from the controller is changed to a
# larger unit (MB/GB/TB).
return False
else:
self.module.fail_json(msg="Error resizing lun %s: %s" % (path, to_native(e)),
exception=traceback.format_exc())
return True
def apply(self):
property_changed = False
size_changed = False
lun_exists = False
netapp_utils.ems_log_event("na_ontap_lun", self.server)
lun_detail = self.get_lun()
if lun_detail:
lun_exists = True
current_size = lun_detail['size']
if self.state == 'absent':
property_changed = True
elif self.state == 'present':
if not int(current_size) == self.size:
size_changed = True
property_changed = True
else:
if self.state == 'present':
property_changed = True
if property_changed:
if self.module.check_mode:
pass
else:
if self.state == 'present':
if not lun_exists:
self.create_lun()
else:
if size_changed:
# Ensure that size was actually changed. Please
# read notes in 'resize_lun' function for details.
size_changed = self.resize_lun()
if not size_changed:
property_changed = False
elif self.state == 'absent':
self.delete_lun()
changed = property_changed or size_changed
# TODO: include other details about the lun (size, etc.)
self.module.exit_json(changed=changed)
def main():
v = NetAppOntapLUN()
v.apply()
if __name__ == '__main__':
main()

@ -1,182 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: na_ontap_lun_copy
short_description: NetApp ONTAP copy LUNs
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Copy LUNs on NetApp ONTAP.
options:
state:
description:
- Whether the specified LUN should exist or not.
choices: ['present']
default: present
destination_vserver:
description:
- Specifies the name of the Vserver that will host the new LUN.
required: true
destination_path:
description:
- Specifies the full path to the new LUN.
required: true
source_path:
description:
- Specifies the full path to the source LUN.
required: true
source_vserver:
description:
- Specifies the name of the vserver hosting the LUN to be copied.
'''
EXAMPLES = """
- name: Copy LUN
na_ontap_lun_copy:
destination_vserver: ansible
destination_path: /vol/test/test_copy_dest_dest_new
source_path: /vol/test/test_copy_1
source_vserver: ansible
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.netapp_module import NetAppModule
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapLUNCopy(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present'], default='present'),
destination_vserver=dict(required=True, type='str'),
destination_path=dict(required=True, type='str'),
source_path=dict(required=True, type='str'),
source_vserver=dict(required=False, type='str'),
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['destination_vserver'])
def get_lun(self):
"""
Check if the LUN exists
:return: true is it exists, false otherwise
:rtype: bool
"""
return_value = False
lun_info = netapp_utils.zapi.NaElement('lun-get-iter')
query_details = netapp_utils.zapi.NaElement('lun-info')
query_details.add_new_child('path', self.parameters['destination_path'])
query_details.add_new_child('vserver', self.parameters['destination_vserver'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
lun_info.add_child_elem(query)
try:
result = self.server.invoke_successfully(lun_info, True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error getting lun info %s for verver %s: %s" %
(self.parameters['destination_path'], self.parameters['destination_vserver'], to_native(e)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
return_value = True
return return_value
def copy_lun(self):
"""
Copy LUN with requested path and vserver
"""
lun_copy = netapp_utils.zapi.NaElement.create_node_with_children(
'lun-copy-start', **{'source-vserver': self.parameters['source_vserver']})
path_obj = netapp_utils.zapi.NaElement('paths')
pair = netapp_utils.zapi.NaElement('lun-path-pair')
pair.add_new_child('destination-path', self.parameters['destination_path'])
pair.add_new_child('source-path', self.parameters['source_path'])
path_obj.add_child_elem(pair)
lun_copy.add_child_elem(path_obj)
try:
self.server.invoke_successfully(lun_copy, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error copying lun from %s to vserver %s: %s" %
(self.parameters['source_vserver'], self.parameters['destination_vserver'], to_native(e)),
exception=traceback.format_exc())
def apply(self):
netapp_utils.ems_log_event("na_ontap_lun_copy", self.server)
if self.get_lun(): # lun already exists at destination
changed = False
else:
changed = True
if self.module.check_mode:
pass
else:
# need to copy lun
if self.parameters['state'] == 'present':
self.copy_lun()
self.module.exit_json(changed=changed)
def main():
v = NetAppOntapLUNCopy()
v.apply()
if __name__ == '__main__':
main()

@ -1,282 +0,0 @@
#!/usr/bin/python
""" this is lun mapping module
(c) 2018-2019, 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_ontap_lun_map
short_description: NetApp ONTAP LUN maps
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Map and unmap LUNs on NetApp ONTAP.
options:
state:
description:
- Whether the specified LUN should exist or not.
choices: ['present', 'absent']
default: present
initiator_group_name:
description:
- Initiator group to map to the given LUN.
required: true
path:
description:
- Path of the LUN..
required: true
vserver:
required: true
description:
- The name of the vserver to use.
lun_id:
description:
- LUN ID assigned for the map.
"""
EXAMPLES = """
- name: Create LUN mapping
na_ontap_lun_map:
state: present
initiator_group_name: ansibleIgroup3234
path: /vol/iscsi_path/iscsi_lun
vserver: ci_dev
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Unmap LUN
na_ontap_lun_map:
state: absent
initiator_group_name: ansibleIgroup3234
path: /vol/iscsi_path/iscsi_lun
vserver: ci_dev
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
lun_node:
description: NetApp controller that is hosting the LUN.
returned: success
type: str
sample: node01
lun_ostype:
description: Specifies the OS of the host accessing the LUN.
returned: success
type: str
sample: vmware
lun_serial:
description: A unique, 12-byte, ASCII string used to identify the LUN.
returned: success
type: str
sample: 80E7/]LZp1Tt
lun_naa_id:
description: The Network Address Authority (NAA) identifier for the LUN.
returned: success
type: str
sample: 600a0980383045372f5d4c5a70315474
lun_state:
description: Online or offline status of the LUN.
returned: success
type: str
sample: online
lun_size:
description: Size of the LUN in bytes.
returned: success
type: int
sample: 2199023255552
"""
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
import codecs
from ansible.module_utils._text import to_text, to_bytes
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapLUNMap(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
initiator_group_name=dict(required=True, type='str'),
path=dict(required=True, type='str'),
vserver=dict(required=True, type='str'),
lun_id=dict(required=False, type='str', default=None),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['path'])
],
supports_check_mode=True
)
self.result = dict(
changed=False,
)
p = self.module.params
# set up state variables
self.state = p['state']
self.initiator_group_name = p['initiator_group_name']
self.path = p['path']
self.vserver = p['vserver']
self.lun_id = p['lun_id']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def get_lun_map(self):
"""
Return details about the LUN map
:return: Details about the lun map
:rtype: dict
"""
lun_info = netapp_utils.zapi.NaElement('lun-map-list-info')
lun_info.add_new_child('path', self.path)
result = self.server.invoke_successfully(lun_info, True)
return_value = None
igroups = result.get_child_by_name('initiator-groups')
if igroups:
for igroup_info in igroups.get_children():
initiator_group_name = igroup_info.get_child_content('initiator-group-name')
lun_id = igroup_info.get_child_content('lun-id')
if initiator_group_name == self.initiator_group_name:
return_value = {
'lun_id': lun_id
}
break
return return_value
def get_lun(self):
"""
Return details about the LUN
:return: Details about the lun
:rtype: dict
"""
# build the lun query
query_details = netapp_utils.zapi.NaElement('lun-info')
query_details.add_new_child('path', self.path)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
lun_query = netapp_utils.zapi.NaElement('lun-get-iter')
lun_query.add_child_elem(query)
# find lun using query
result = self.server.invoke_successfully(lun_query, True)
return_value = None
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
lun = result.get_child_by_name('attributes-list').get_child_by_name('lun-info')
# extract and assign lun information to return value
hexlify = codecs.getencoder('hex')
naa_hex = to_text(hexlify(to_bytes(lun.get_child_content('serial-number')))[0])
return_value = {
'lun_node': lun.get_child_content('node'),
'lun_ostype': lun.get_child_content('multiprotocol-type'),
'lun_serial': lun.get_child_content('serial-number'),
'lun_naa_id': '600a0980' + naa_hex,
'lun_state': lun.get_child_content('state'),
'lun_size': lun.get_child_content('size'),
}
return return_value
def create_lun_map(self):
"""
Create LUN map
"""
options = {'path': self.path, 'initiator-group': self.initiator_group_name}
if self.lun_id is not None:
options['lun-id'] = self.lun_id
lun_map_create = netapp_utils.zapi.NaElement.create_node_with_children('lun-map', **options)
try:
self.server.invoke_successfully(lun_map_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error mapping lun %s of initiator_group_name %s: %s" %
(self.path, self.initiator_group_name, to_native(e)),
exception=traceback.format_exc())
def delete_lun_map(self):
"""
Unmap LUN map
"""
lun_map_delete = netapp_utils.zapi.NaElement.create_node_with_children('lun-unmap', **{'path': self.path, 'initiator-group': self.initiator_group_name})
try:
self.server.invoke_successfully(lun_map_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg="Error unmapping lun %s of initiator_group_name %s: %s" %
(self.path, self.initiator_group_name, to_native(e)),
exception=traceback.format_exc())
def apply(self):
netapp_utils.ems_log_event("na_ontap_lun_map", self.server)
lun_details = self.get_lun()
lun_map_details = self.get_lun_map()
if self.state == 'present' and lun_details:
self.result.update(lun_details)
if self.state == 'present' and not lun_map_details:
self.result['changed'] = True
if not self.module.check_mode:
self.create_lun_map()
elif self.state == 'absent' and lun_map_details:
self.result['changed'] = True
if not self.module.check_mode:
self.delete_lun_map()
self.module.exit_json(**self.result)
def main():
v = NetAppOntapLUNMap()
v.apply()
if __name__ == '__main__':
main()

@ -1,209 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, NetApp, Inc
# (c) 2018 Piotr Olczak <piotr.olczak@redhat.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = '''
module: na_ontap_motd
author:
- Piotr Olczak (@dprts) <polczak@redhat.com>
- NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
extends_documentation_fragment:
- netapp.na_ontap
short_description: Setup motd
description:
- This module allows you to manipulate motd for a vserver
- It also allows to manipulate motd at the cluster level by using the cluster vserver (cserver)
version_added: "2.7"
requirements:
- netapp_lib
options:
state:
description:
- If C(state=present) sets MOTD given in I(message) C(state=absent) removes it.
choices: ['present', 'absent']
default: present
message:
description:
- MOTD Text message, required when C(state=present).
type: str
vserver:
description:
- The name of the SVM motd should be set for.
required: true
type: str
show_cluster_motd:
description:
- Set to I(false) if Cluster-level Message of the Day should not be shown
type: bool
default: True
'''
EXAMPLES = '''
- name: Set Cluster-Level MOTD
na_ontap_motd:
vserver: my_ontap_cluster
message: "Cluster wide MOTD"
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
state: present
https: true
- name: Set MOTD for I(rhev_nfs_krb) SVM, do not show Cluster-Level MOTD
na_ontap_motd:
vserver: rhev_nfs_krb
message: "Access to rhev_nfs_krb is also restricted"
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
state: present
show_cluster_motd: False
https: true
- name: Remove Cluster-Level MOTD
na_ontap_motd:
vserver: my_ontap_cluster
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
state: absent
https: true
'''
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPMotd(object):
def __init__(self):
argument_spec = netapp_utils.na_ontap_host_argument_spec()
argument_spec.update(dict(
state=dict(required=False, default='present', choices=['present', 'absent']),
vserver=dict(required=True, type='str'),
message=dict(default='', type='str'),
show_cluster_motd=dict(default=True, type='bool')
))
self.module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def motd_get_iter(self):
"""
Compose NaElement object to query current motd
:return: NaElement object for vserver-motd-get-iter
"""
motd_get_iter = netapp_utils.zapi.NaElement('vserver-motd-get-iter')
query = netapp_utils.zapi.NaElement('query')
motd_info = netapp_utils.zapi.NaElement('vserver-motd-info')
motd_info.add_new_child('is-cluster-message-enabled', str(self.parameters['show_cluster_motd']))
motd_info.add_new_child('vserver', self.parameters['vserver'])
query.add_child_elem(motd_info)
motd_get_iter.add_child_elem(query)
return motd_get_iter
def motd_get(self):
"""
Get current motd
:return: Dictionary of current motd details if query successful, else None
"""
motd_get_iter = self.motd_get_iter()
motd_result = dict()
try:
result = self.server.invoke_successfully(motd_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching motd info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) > 0:
motd_info = result.get_child_by_name('attributes-list').get_child_by_name(
'vserver-motd-info')
motd_result['message'] = motd_info.get_child_content('message')
motd_result['message'] = str(motd_result['message']).rstrip()
motd_result['show_cluster_motd'] = True if motd_info.get_child_content(
'is-cluster-message-enabled') == 'true' else False
motd_result['vserver'] = motd_info.get_child_content('vserver')
return motd_result
return None
def modify_motd(self):
motd_create = netapp_utils.zapi.NaElement('vserver-motd-modify-iter')
motd_create.add_new_child('message', self.parameters['message'])
motd_create.add_new_child(
'is-cluster-message-enabled', 'true' if self.parameters['show_cluster_motd'] is True else 'false')
query = netapp_utils.zapi.NaElement('query')
motd_info = netapp_utils.zapi.NaElement('vserver-motd-info')
motd_info.add_new_child('vserver', self.parameters['vserver'])
query.add_child_elem(motd_info)
motd_create.add_child_elem(query)
try:
self.server.invoke_successfully(motd_create, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as err:
self.module.fail_json(msg="Error creating motd: %s" % (to_native(err)), exception=traceback.format_exc())
return motd_create
def apply(self):
"""
Applies action from playbook
"""
netapp_utils.ems_log_event("na_ontap_motd", self.server)
current = self.motd_get()
if self.parameters['state'] == 'present' and self.parameters['message'] == "":
self.module.fail_json(msg="message parameter cannot be empty")
if self.parameters['state'] == 'absent':
# Just make sure it is empty
self.parameters['message'] = ''
if current['message'] == 'None':
current = None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
self.modify_motd()
self.module.exit_json(changed=self.na_helper.changed)
def main():
motd_obj = NetAppONTAPMotd()
motd_obj.apply()
if __name__ == '__main__':
main()

@ -1,342 +0,0 @@
#!/usr/bin/python
""" this is ndmp module
(c) 2019, 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': 'community'
}
DOCUMENTATION = '''
---
module: na_ontap_ndmp
short_description: NetApp ONTAP NDMP services configuration
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Modify NDMP Services.
options:
vserver:
description:
- Name of the vserver.
required: true
type: str
abort_on_disk_error:
description:
- Enable abort on disk error.
type: bool
authtype:
description:
- Authentication type.
type: list
backup_log_enable:
description:
- Enable backup log.
type: bool
data_port_range:
description:
- Data port range.
type: str
debug_enable:
description:
- Enable debug.
type: bool
debug_filter:
description:
- Debug filter.
type: str
dump_detailed_stats:
description:
- Enable logging of VM stats for dump.
type: bool
dump_logical_find:
description:
- Enable logical find for dump.
type: str
enable:
description:
- Enable NDMP on vserver.
type: bool
fh_dir_retry_interval:
description:
- FH throttle value for dir.
type: int
fh_node_retry_interval:
description:
- FH throttle value for node.
type: int
ignore_ctime_enabled:
description:
- Ignore ctime.
type: bool
is_secure_control_connection_enabled:
description:
- Is secure control connection enabled.
type: bool
offset_map_enable:
description:
- Enable offset map.
type: bool
per_qtree_exclude_enable:
description:
- Enable per qtree exclusion.
type: bool
preferred_interface_role:
description:
- Preferred interface role.
type: list
restore_vm_cache_size:
description:
- Restore VM file cache size.
type: int
secondary_debug_filter:
description:
- Secondary debug filter.
type: str
tcpnodelay:
description:
- Enable TCP nodelay.
type: bool
tcpwinsize:
description:
- TCP window size.
type: int
'''
EXAMPLES = '''
- name: modify ndmp
na_ontap_ndmp:
vserver: ansible
hostname: "{{ hostname }}"
abort_on_disk_error: true
authtype: plaintext,challenge
backup_log_enable: true
data_port_range: 8000-9000
debug_enable: true
debug_filter: filter
dump_detailed_stats: true
dump_logical_find: default
enable: true
fh_dir_retry_interval: 100
fh_node_retry_interval: 100
ignore_ctime_enabled: true
is_secure_control_connection_enabled: true
offset_map_enable: true
per_qtree_exclude_enable: true
preferred_interface_role: node_mgmt,intercluster
restore_vm_cache_size: 1000
secondary_debug_filter: filter
tcpnodelay: true
tcpwinsize: 10000
username: user
password: pass
https: False
'''
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPNdmp(object):
'''
modify vserver cifs security
'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.modifiable_options = dict(
abort_on_disk_error=dict(required=False, type='bool'),
authtype=dict(required=False, type='list'),
backup_log_enable=dict(required=False, type='bool'),
data_port_range=dict(required=False, type='str'),
debug_enable=dict(required=False, type='bool'),
debug_filter=dict(required=False, type='str'),
dump_detailed_stats=dict(required=False, type='bool'),
dump_logical_find=dict(required=False, type='str'),
enable=dict(required=False, type='bool'),
fh_dir_retry_interval=dict(required=False, type='int'),
fh_node_retry_interval=dict(required=False, type='int'),
ignore_ctime_enabled=dict(required=False, type='bool'),
is_secure_control_connection_enabled=dict(required=False, type='bool'),
offset_map_enable=dict(required=False, type='bool'),
per_qtree_exclude_enable=dict(required=False, type='bool'),
preferred_interface_role=dict(required=False, type='list'),
restore_vm_cache_size=dict(required=False, type='int'),
secondary_debug_filter=dict(required=False, type='str'),
tcpnodelay=dict(required=False, type='bool'),
tcpwinsize=dict(required=False, type='int')
)
self.argument_spec.update(dict(
vserver=dict(required=True, type='str')
))
self.argument_spec.update(self.modifiable_options)
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def ndmp_get_iter(self):
"""
get current vserver ndmp attributes.
:return: a dict of ndmp attributes.
"""
ndmp_get = netapp_utils.zapi.NaElement('ndmp-vserver-attributes-get-iter')
query = netapp_utils.zapi.NaElement('query')
ndmp_info = netapp_utils.zapi.NaElement('ndmp-vserver-attributes-info')
ndmp_info.add_new_child('vserver', self.parameters['vserver'])
query.add_child_elem(ndmp_info)
ndmp_get.add_child_elem(query)
ndmp_details = dict()
try:
result = self.server.invoke_successfully(ndmp_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching ndmp from %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
ndmp_attributes = result.get_child_by_name('attributes-list').get_child_by_name('ndmp-vserver-attributes-info')
self.get_ndmp_details(ndmp_details, ndmp_attributes)
return ndmp_details
def get_ndmp_details(self, ndmp_details, ndmp_attributes):
"""
:param ndmp_details: a dict of current ndmp.
:param ndmp_attributes: ndmp returned from api call in xml format.
:return: None
"""
for option in self.modifiable_options.keys():
option_type = self.modifiable_options[option]['type']
if option_type == 'bool':
ndmp_details[option] = self.str_to_bool(ndmp_attributes.get_child_content(self.attribute_to_name(option)))
elif option_type == 'int':
ndmp_details[option] = int(ndmp_attributes.get_child_content(self.attribute_to_name(option)))
elif option_type == 'list':
child_list = ndmp_attributes.get_child_by_name(self.attribute_to_name(option))
values = [child.get_content() for child in child_list.get_children()]
ndmp_details[option] = values
else:
ndmp_details[option] = ndmp_attributes.get_child_content(self.attribute_to_name(option))
def modify_ndmp(self, modify):
"""
:param modify: A list of attributes to modify
:return: None
"""
ndmp_modify = netapp_utils.zapi.NaElement('ndmp-vserver-attributes-modify')
for attribute in modify:
if attribute == 'authtype':
authtypes = netapp_utils.zapi.NaElement('authtype')
types = self.parameters['authtype']
for authtype in types:
authtypes.add_new_child('ndmpd-authtypes', authtype)
ndmp_modify.add_child_elem(authtypes)
elif attribute == 'preferred_interface_role':
preferred_interface_roles = netapp_utils.zapi.NaElement('preferred-interface-role')
roles = self.parameters['preferred_interface_role']
for role in roles:
preferred_interface_roles.add_new_child('netport-role', role)
ndmp_modify.add_child_elem(preferred_interface_roles)
else:
ndmp_modify.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute]))
try:
self.server.invoke_successfully(ndmp_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error modifying ndmp on %s: %s'
% (self.parameters['vserver'], to_native(e)),
exception=traceback.format_exc())
@staticmethod
def attribute_to_name(attribute):
return str.replace(attribute, '_', '-')
@staticmethod
def str_to_bool(s):
if s == 'true':
return True
else:
return False
def apply(self):
"""Call modify operations."""
self.asup_log_for_cserver("na_ontap_ndmp")
current = self.ndmp_get_iter()
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if modify:
self.modify_ndmp(modify)
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
obj = NetAppONTAPNdmp()
obj.apply()
if __name__ == '__main__':
main()

@ -1,307 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_net_ifgrp
short_description: NetApp Ontap modify network interface group
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create, modify ports, destroy the network interface group
options:
state:
description:
- Whether the specified network interface group should exist or not.
choices: ['present', 'absent']
default: present
distribution_function:
description:
- Specifies the traffic distribution function for the ifgrp.
choices: ['mac', 'ip', 'sequential', 'port']
name:
description:
- Specifies the interface group name.
required: true
mode:
description:
- Specifies the link policy for the ifgrp.
node:
description:
- Specifies the name of node.
required: true
ports:
aliases:
- port
description:
- List of expected ports to be present in the interface group.
- If a port is present in this list, but not on the target, it will be added.
- If a port is not in the list, but present on the target, it will be removed.
- Make sure the list contains all ports you want to see on the target.
version_added: '2.8'
"""
EXAMPLES = """
- name: create ifgrp
na_ontap_net_ifgrp:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
distribution_function: ip
name: a0c
ports: [e0a]
mode: multimode
node: "{{ Vsim node name }}"
- name: modify ports in an ifgrp
na_ontap_net_ifgrp:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
distribution_function: ip
name: a0c
port: [e0a, e0c]
mode: multimode
node: "{{ Vsim node name }}"
- name: delete ifgrp
na_ontap_net_ifgrp:
state: absent
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
name: a0c
node: "{{ Vsim node name }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapIfGrp(object):
"""
Create, Modifies and Destroys a IfGrp
"""
def __init__(self):
"""
Initialize the Ontap IfGrp class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
distribution_function=dict(required=False, type='str', choices=['mac', 'ip', 'sequential', 'port']),
name=dict(required=True, type='str'),
mode=dict(required=False, type='str'),
node=dict(required=True, type='str'),
ports=dict(required=False, type='list', aliases=["port"]),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['distribution_function', 'mode'])
],
supports_check_mode=True
)
# set up variables
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def get_if_grp(self):
"""
Return details about the if_group
:param:
name : Name of the if_group
:return: Details about the if_group. None if not found.
:rtype: dict
"""
if_group_iter = netapp_utils.zapi.NaElement('net-port-get-iter')
if_group_info = netapp_utils.zapi.NaElement('net-port-info')
if_group_info.add_new_child('port', self.parameters['name'])
if_group_info.add_new_child('port-type', 'if_group')
if_group_info.add_new_child('node', self.parameters['node'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(if_group_info)
if_group_iter.add_child_elem(query)
try:
result = self.server.invoke_successfully(if_group_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting if_group %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
return_value = None
if result.get_child_by_name('num-records') and int(result['num-records']) >= 1:
if_group_attributes = result['attributes-list']['net-port-info']
return_value = {
'name': if_group_attributes['port'],
'distribution_function': if_group_attributes['ifgrp-distribution-function'],
'mode': if_group_attributes['ifgrp-mode'],
'node': if_group_attributes['node'],
}
return return_value
def get_if_grp_ports(self):
"""
Return ports of the if_group
:param:
name : Name of the if_group
:return: Ports of the if_group. None if not found.
:rtype: dict
"""
if_group_iter = netapp_utils.zapi.NaElement('net-port-ifgrp-get')
if_group_iter.add_new_child('ifgrp-name', self.parameters['name'])
if_group_iter.add_new_child('node', self.parameters['node'])
try:
result = self.server.invoke_successfully(if_group_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting if_group ports %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
port_list = []
if result.get_child_by_name('attributes'):
if_group_attributes = result['attributes']['net-ifgrp-info']
if if_group_attributes.get_child_by_name('ports'):
ports = if_group_attributes.get_child_by_name('ports').get_children()
for each in ports:
port_list.append(each.get_content())
return {'ports': port_list}
def create_if_grp(self):
"""
Creates a new ifgrp
"""
route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-create")
route_obj.add_new_child("distribution-function", self.parameters['distribution_function'])
route_obj.add_new_child("ifgrp-name", self.parameters['name'])
route_obj.add_new_child("mode", self.parameters['mode'])
route_obj.add_new_child("node", self.parameters['node'])
try:
self.server.invoke_successfully(route_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating if_group %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
for port in self.parameters.get('ports'):
self.add_port_to_if_grp(port)
def delete_if_grp(self):
"""
Deletes a ifgrp
"""
route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-destroy")
route_obj.add_new_child("ifgrp-name", self.parameters['name'])
route_obj.add_new_child("node", self.parameters['node'])
try:
self.server.invoke_successfully(route_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting if_group %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def add_port_to_if_grp(self, port):
"""
adds port to a ifgrp
"""
route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-add-port")
route_obj.add_new_child("ifgrp-name", self.parameters['name'])
route_obj.add_new_child("port", port)
route_obj.add_new_child("node", self.parameters['node'])
try:
self.server.invoke_successfully(route_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error adding port %s to if_group %s: %s' %
(port, self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_ports(self, current_ports):
add_ports = set(self.parameters['ports']) - set(current_ports)
remove_ports = set(current_ports) - set(self.parameters['ports'])
for port in add_ports:
self.add_port_to_if_grp(port)
for port in remove_ports:
self.remove_port_to_if_grp(port)
def remove_port_to_if_grp(self, port):
"""
removes port from a ifgrp
"""
route_obj = netapp_utils.zapi.NaElement("net-port-ifgrp-remove-port")
route_obj.add_new_child("ifgrp-name", self.parameters['name'])
route_obj.add_new_child("port", port)
route_obj.add_new_child("node", self.parameters['node'])
try:
self.server.invoke_successfully(route_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing port %s to if_group %s: %s' %
(port, self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_net_ifgrp", cserver)
def apply(self):
self.autosupport_log()
current, modify = self.get_if_grp(), None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
current_ports = self.get_if_grp_ports()
modify = self.na_helper.get_modified_attributes(current_ports, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_if_grp()
elif cd_action == 'delete':
self.delete_if_grp()
elif modify:
self.modify_ports(current_ports['ports'])
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Creates the NetApp Ontap Net Route object and runs the correct play task
"""
obj = NetAppOntapIfGrp()
obj.apply()
if __name__ == '__main__':
main()

@ -1,226 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_net_port
short_description: NetApp ONTAP network ports.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Modify a ONTAP network port.
options:
state:
description:
- Whether the specified net port should exist or not.
choices: ['present']
default: present
node:
description:
- Specifies the name of node.
required: true
ports:
aliases:
- port
description:
- Specifies the name of port(s).
required: true
mtu:
description:
- Specifies the maximum transmission unit (MTU) reported by the port.
autonegotiate_admin:
description:
- Enables or disables Ethernet auto-negotiation of speed,
duplex and flow control.
duplex_admin:
description:
- Specifies the user preferred duplex setting of the port.
- Valid values auto, half, full
speed_admin:
description:
- Specifies the user preferred speed setting of the port.
flowcontrol_admin:
description:
- Specifies the user preferred flow control setting of the port.
ipspace:
description:
- Specifies the port's associated IPspace name.
- The 'Cluster' ipspace is reserved for cluster ports.
"""
EXAMPLES = """
- name: Modify Net Port
na_ontap_net_port:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
node: "{{ node_name }}"
ports: e0d,e0c
autonegotiate_admin: true
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapNetPort(object):
"""
Modify a Net port
"""
def __init__(self):
"""
Initialize the Ontap Net Port Class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present'], default='present'),
node=dict(required=True, type="str"),
ports=dict(required=True, type="list", aliases=['port']),
mtu=dict(required=False, type="str", default=None),
autonegotiate_admin=dict(required=False, type="str", default=None),
duplex_admin=dict(required=False, type="str", default=None),
speed_admin=dict(required=False, type="str", default=None),
flowcontrol_admin=dict(required=False, type="str", default=None),
ipspace=dict(required=False, type="str", default=None),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.set_playbook_zapi_key_map()
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def set_playbook_zapi_key_map(self):
self.na_helper.zapi_string_keys = {
'mtu': 'mtu',
'autonegotiate_admin': 'is-administrative-auto-negotiate',
'duplex_admin': 'administrative-duplex',
'speed_admin': 'administrative-speed',
'flowcontrol_admin': 'administrative-flowcontrol',
'ipspace': 'ipspace'
}
def get_net_port(self, port):
"""
Return details about the net port
:param: port: Name of the port
:return: Dictionary with current state of the port. None if not found.
:rtype: dict
"""
net_port_get = netapp_utils.zapi.NaElement('net-port-get-iter')
attributes = {
'query': {
'net-port-info': {
'node': self.parameters['node'],
'port': port
}
}
}
net_port_get.translate_struct(attributes)
try:
result = self.server.invoke_successfully(net_port_get, True)
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
port_info = result['attributes-list']['net-port-info']
port_details = dict()
else:
return None
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting net ports for %s: %s' % (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
port_details[item_key] = port_info.get_child_content(zapi_key)
return port_details
def modify_net_port(self, port, modify):
"""
Modify a port
:param port: Name of the port
:param modify: dict with attributes to be modified
:return: None
"""
port_modify = netapp_utils.zapi.NaElement('net-port-modify')
port_attributes = {'node': self.parameters['node'],
'port': port}
for key in modify:
if key in self.na_helper.zapi_string_keys:
zapi_key = self.na_helper.zapi_string_keys.get(key)
port_attributes[zapi_key] = modify[key]
port_modify.translate_struct(port_attributes)
try:
self.server.invoke_successfully(port_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying net ports for %s: %s' % (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
"""
AutoSupport log for na_ontap_net_port
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_net_port", cserver)
def apply(self):
"""
Run Module based on play book
"""
self.autosupport_log()
# Run the task for all ports in the list of 'ports'
for port in self.parameters['ports']:
current = self.get_net_port(port)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if modify:
self.modify_net_port(port, modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Create the NetApp Ontap Net Port Object and modify it
"""
obj = NetAppOntapNetPort()
obj.apply()
if __name__ == '__main__':
main()

@ -1,324 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_net_routes
short_description: NetApp ONTAP network routes
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Modify ONTAP network routes.
options:
state:
description:
- Whether you want to create or delete a network route.
choices: ['present', 'absent']
default: present
vserver:
description:
- The name of the vserver.
required: true
destination:
description:
- Specify the route destination.
- Example 10.7.125.5/20, fd20:13::/64.
required: true
gateway:
description:
- Specify the route gateway.
- Example 10.7.125.1, fd20:13::1.
required: true
metric:
description:
- Specify the route metric.
- If this field is not provided the default will be set to 20.
from_destination:
description:
- Specify the route destination that should be changed.
- new_destination was removed to fix idempotency issues. To rename destination the original goes to from_destination and the new goes to destination.
version_added: '2.8'
from_gateway:
description:
- Specify the route gateway that should be changed.
version_added: '2.8'
from_metric:
description:
- Specify the route metric that should be changed.
version_added: '2.8'
'''
EXAMPLES = """
- name: create route
na_ontap_net_routes:
state: present
vserver: "{{ Vserver name }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
destination: 10.7.125.5/20
gateway: 10.7.125.1
metric: 30
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapNetRoutes(object):
"""
Create, Modifies and Destroys a Net Route
"""
def __init__(self):
"""
Initialize the Ontap Net Route class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
destination=dict(required=True, type='str'),
gateway=dict(required=True, type='str'),
metric=dict(required=False, type='str'),
from_destination=dict(required=False, type='str', default=None),
from_gateway=dict(required=False, type='str', default=None),
from_metric=dict(required=False, type='str', default=None),
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
return
def create_net_route(self, current_metric=None):
"""
Creates a new Route
"""
route_obj = netapp_utils.zapi.NaElement('net-routes-create')
route_obj.add_new_child("destination", self.parameters['destination'])
route_obj.add_new_child("gateway", self.parameters['gateway'])
if current_metric is None and self.parameters.get('metric') is not None:
metric = self.parameters['metric']
else:
metric = current_metric
# Metric can be None, Can't set metric to none
if metric is not None:
route_obj.add_new_child("metric", metric)
try:
self.server.invoke_successfully(route_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating net route: %s'
% (to_native(error)),
exception=traceback.format_exc())
def delete_net_route(self, params=None):
"""
Deletes a given Route
"""
route_obj = netapp_utils.zapi.NaElement('net-routes-destroy')
if params is None:
params = self.parameters
route_obj.add_new_child("destination", params['destination'])
route_obj.add_new_child("gateway", params['gateway'])
try:
self.server.invoke_successfully(route_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting net route: %s'
% (to_native(error)),
exception=traceback.format_exc())
def modify_net_route(self, current, desired):
"""
Modify a net route
"""
# return if there is nothing to change
for key, val in desired.items():
if val != current[key]:
self.na_helper.changed = True
break
if not self.na_helper.changed:
return
# delete and re-create with new params
self.delete_net_route(current)
route_obj = netapp_utils.zapi.NaElement('net-routes-create')
for attribute in ['metric', 'destination', 'gateway']:
if desired.get(attribute) is not None:
value = desired[attribute]
else:
value = current[attribute]
route_obj.add_new_child(attribute, value)
try:
result = self.server.invoke_successfully(route_obj, True)
except netapp_utils.zapi.NaApiError as error:
# restore the old route, create the route with the existing metric
self.create_net_route(current['metric'])
# return if desired route already exists
if to_native(error.code) == '13001':
return
# Invalid value specified for any of the attributes
self.module.fail_json(msg='Error modifying net route: %s'
% (to_native(error)),
exception=traceback.format_exc())
def get_net_route(self, params=None):
"""
Checks to see if a route exist or not
:return: NaElement object if a route exists, None otherwise
"""
if params is not None:
# we need at least on of the new_destination or new_gateway to fetch desired route
if params.get('destination') is None and params.get('gateway') is None:
return None
current = None
route_obj = netapp_utils.zapi.NaElement('net-routes-get')
for attr in ['destination', 'gateway']:
if params and params.get(attr) is not None:
value = params[attr]
else:
value = self.parameters[attr]
route_obj.add_new_child(attr, value)
try:
result = self.server.invoke_successfully(route_obj, True)
if result.get_child_by_name('attributes') is not None:
route_info = result.get_child_by_name('attributes').get_child_by_name('net-vs-routes-info')
current = {
'destination': route_info.get_child_content('destination'),
'gateway': route_info.get_child_content('gateway'),
'metric': route_info.get_child_content('metric')
}
except netapp_utils.zapi.NaApiError as error:
# Error 13040 denotes a route doesn't exist.
if to_native(error.code) == "15661":
return None
self.module.fail_json(msg='Error fetching net route: %s'
% (to_native(error)),
exception=traceback.format_exc())
return current
def is_modify_action(self, current, desired):
"""
Get desired action to be applied for net routes
Destination and gateway are unique params for a route and cannot be duplicated
So if a route with desired destination or gateway exists already, we don't try to modify
:param current: current details
:param desired: desired details
:return: create / delete / modify / None
"""
if current is None and desired is None:
# this is invalid
# cannot modify a non existent resource
return None
if current is None and desired is not None:
# idempotency or duplication
# we need not create
return False
if current is not None and desired is not None:
# we can't modify an ambiguous route (idempotency/duplication)
return False
return True
def get_params_to_be_modified(self, current):
"""
Get parameters and values that need to be modified
:param current: current details
:return: dict(), None
"""
if current is None:
return None
desired = dict()
if self.parameters.get('new_destination') is not None and \
self.parameters['new_destination'] != current['destination']:
desired['destination'] = self.parameters['new_destination']
if self.parameters.get('new_gateway') is not None and \
self.parameters['new_gateway'] != current['gateway']:
desired['gateway'] = self.parameters['new_gateway']
if self.parameters.get('new_metric') is not None and \
self.parameters['new_metric'] != current['metric']:
desired['metric'] = self.parameters['new_metric']
return desired
def apply(self):
"""
Run Module based on play book
"""
netapp_utils.ems_log_event("na_ontap_net_routes", self.server)
current = self.get_net_route()
modify, cd_action = None, None
modify_params = {'destination': self.parameters.get('from_destination'),
'gateway': self.parameters.get('from_gateway'),
'metric': self.parameters.get('from_metric')}
# if any from_* param is present in playbook, check for modify action
if any(modify_params.values()):
# destination and gateway combination is unique, and is considered like a id. so modify destination
# or gateway is considered a rename action. metric is considered an attribute of the route so it is
# considered as modify.
if modify_params.get('metric') is not None:
modify = True
old_params = current
else:
# get parameters that are eligible for modify
old_params = self.get_net_route(modify_params)
modify = self.na_helper.is_rename_action(old_params, current)
if modify is None:
self.module.fail_json(msg="Error modifying: route %s does not exist" % self.parameters['from_destination'])
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action == 'create':
self.create_net_route()
elif cd_action == 'delete':
self.delete_net_route()
elif modify:
desired = {}
for key, value in old_params.items():
desired[key] = value
for key, value in modify_params.items():
if value is not None:
desired[key] = self.parameters.get(key)
self.modify_net_route(old_params, desired)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Creates the NetApp Ontap Net Route object and runs the correct play task
"""
obj = NetAppOntapNetRoutes()
obj.apply()
if __name__ == '__main__':
main()

@ -1,326 +0,0 @@
#!/usr/bin/python
# 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_ontap_net_subnet
short_description: NetApp ONTAP Create, delete, modify network subnets.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: Storage Engineering (@Albinpopote) <ansible@black-perl.fr>
description:
- Create, modify, destroy the network subnet
options:
state:
description:
- Whether the specified network interface group should exist or not.
choices: ['present', 'absent']
default: present
broadcast_domain:
description:
- Specify the required broadcast_domain name for the subnet.
- A broadcast domain can not be modified after the subnet has been created
required: true
name:
description:
- Specify the subnet name.
required: true
from_name:
description:
- Name of the subnet to be renamed
gateway:
description:
- Specify the gateway for the default route of the subnet.
ipspace:
description:
- Specify the ipspace for the subnet.
- The default value for this parameter is the default IPspace, named 'Default'.
ip_ranges:
description:
- Specify the list of IP address ranges associated with the subnet.
subnet:
description:
- Specify the subnet (ip and mask).
required: true
"""
EXAMPLES = """
- name: create subnet
na_ontap_net_subnet:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
subnet: 10.10.10.0/24
name: subnet-adm
ip_ranges: [ '10.10.10.30-10.10.10.40', '10.10.10.51' ]
gateway: 10.10.10.254
ipspace: Default
broadcast_domain: Default
- name: delete subnet
na_ontap_net_subnet:
state: absent
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
name: subnet-adm
ipspace: Default
- name: rename subnet
na_ontap_net_subnet:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
name: subnet-adm-new
from_name: subnet-adm
ipspace: Default
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapSubnet(object):
"""
Create, Modifies and Destroys a subnet
"""
def __init__(self):
"""
Initialize the ONTAP Subnet class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
broadcast_domain=dict(required=False, type='str'),
gateway=dict(required=False, type='str'),
ip_ranges=dict(required=False, type=list),
ipspace=dict(required=False, type='str'),
subnet=dict(required=False, type='str')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def get_subnet(self, name=None):
"""
Return details about the subnet
:param:
name : Name of the subnet
:return: Details about the subnet. None if not found.
:rtype: dict
"""
if name is None:
name = self.parameters.get('name')
subnet_iter = netapp_utils.zapi.NaElement('net-subnet-get-iter')
subnet_info = netapp_utils.zapi.NaElement('net-subnet-info')
subnet_info.add_new_child('subnet-name', name)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(subnet_info)
subnet_iter.add_child_elem(query)
result = self.server.invoke_successfully(subnet_iter, True)
return_value = None
# check if query returns the expected subnet
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
subnet_attributes = result.get_child_by_name('attributes-list').get_child_by_name('net-subnet-info')
broadcast_domain = subnet_attributes.get_child_content('broadcast-domain')
gateway = subnet_attributes.get_child_content('gateway')
ipspace = subnet_attributes.get_child_content('ipspace')
subnet = subnet_attributes.get_child_content('subnet')
name = subnet_attributes.get_child_content('subnet-name')
ip_ranges = []
range_obj = subnet_attributes.get_child_by_name('ip-ranges').get_children()
for elem in range_obj:
ip_ranges.append(elem.get_content())
return_value = {
'name': name,
'broadcast_domain': broadcast_domain,
'gateway': gateway,
'ip_ranges': ip_ranges,
'ipspace': ipspace,
'subnet': subnet
}
return return_value
def create_subnet(self):
"""
Creates a new subnet
"""
options = {'subnet-name': self.parameters.get('name'),
'broadcast-domain': self.parameters.get('broadcast_domain'),
'subnet': self.parameters.get('subnet')}
subnet_create = netapp_utils.zapi.NaElement.create_node_with_children(
'net-subnet-create', **options)
if self.parameters.get('gateway'):
subnet_create.add_new_child('gateway', self.parameters.get('gateway'))
if self.parameters.get('ip_ranges'):
subnet_ips = netapp_utils.zapi.NaElement('ip-ranges')
subnet_create.add_child_elem(subnet_ips)
for ip_range in self.parameters.get('ip_ranges'):
subnet_ips.add_new_child('ip-range', ip_range)
if self.parameters.get('ipspace'):
subnet_create.add_new_child('ipspace', self.parameters.get('ipspace'))
try:
self.server.invoke_successfully(subnet_create, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating subnet %s: %s' % (self.parameters.get('name'), to_native(error)),
exception=traceback.format_exc())
def delete_subnet(self):
"""
Deletes a subnet
"""
subnet_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'net-subnet-destroy', **{'subnet-name': self.parameters.get('name')})
try:
self.server.invoke_successfully(subnet_delete, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting subnet %s: %s' % (self.parameters.get('name'), to_native(error)),
exception=traceback.format_exc())
def modify_subnet(self):
"""
Modifies a subnet
"""
options = {'subnet-name': self.parameters.get('name')}
subnet_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'net-subnet-modify', **options)
if self.parameters.get('gateway'):
subnet_modify.add_new_child('gateway', self.parameters.get('gateway'))
if self.parameters.get('ip_ranges'):
subnet_ips = netapp_utils.zapi.NaElement('ip-ranges')
subnet_modify.add_child_elem(subnet_ips)
for ip_range in self.parameters.get('ip_ranges'):
subnet_ips.add_new_child('ip-range', ip_range)
if self.parameters.get('ipspace'):
subnet_modify.add_new_child('ipspace', self.parameters.get('ipspace'))
if self.parameters.get('subnet'):
subnet_modify.add_new_child('subnet', self.parameters.get('subnet'))
try:
self.server.invoke_successfully(subnet_modify, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying subnet %s: %s' % (self.parameters.get('name'), to_native(error)),
exception=traceback.format_exc())
def rename_subnet(self):
"""
TODO
"""
options = {'subnet-name': self.parameters.get('from_name'),
'new-name': self.parameters.get('name')}
subnet_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'net-subnet-rename', **options)
if self.parameters.get('ipspace'):
subnet_rename.add_new_child('ipspace', self.parameters.get('ipspace'))
try:
self.server.invoke_successfully(subnet_rename, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error renaming subnet %s: %s' % (self.parameters.get('name'), to_native(error)),
exception=traceback.format_exc())
def apply(self):
'''Apply action to subnet'''
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_net_subnet", cserver)
current = self.get_subnet()
cd_action, rename = None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(self.get_subnet(self.parameters.get('from_name')), current)
if rename is False:
self.module.fail_json(msg="Error renaming: subnet %s does not exist" %
self.parameters.get('from_name'))
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
for attribute in modify:
if attribute in ['broadcast_domain']:
self.module.fail_json(msg='Error modifying subnet %s: cannot modify broadcast_domain parameter.' % self.parameters.get('name'))
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_subnet()
# If rename is True, cd_action is NOne but modify could be true
if cd_action == 'create':
for attribute in ['subnet', 'broadcast_domain']:
if not self.parameters.get(attribute):
self.module.fail_json(msg='Error - missing required arguments: %s.' % attribute)
self.create_subnet()
elif cd_action == 'delete':
self.delete_subnet()
elif modify:
self.modify_subnet()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Creates the NetApp ONTAP Net Route object and runs the correct play task
"""
subnet_obj = NetAppOntapSubnet()
subnet_obj.apply()
if __name__ == '__main__':
main()

@ -1,186 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_net_vlan
short_description: NetApp ONTAP network VLAN
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or Delete a network VLAN
options:
state:
description:
- Whether the specified network VLAN should exist or not
choices: ['present', 'absent']
default: present
parent_interface:
description:
- The interface that hosts the VLAN interface.
required: true
vlanid:
description:
- The VLAN id. Ranges from 1 to 4094.
required: true
node:
description:
- Node name of VLAN interface.
required: true
notes:
- The C(interface_name) option has been removed and should be deleted from playbooks
'''
EXAMPLES = """
- name: create VLAN
na_ontap_net_vlan:
state: present
vlanid: 13
node: "{{ vlan node }}"
parent_interface: "{{ vlan parent interface name }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
"""
RETURN = """
"""
from ansible.module_utils.basic import AnsibleModule
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapVlan(object):
"""
Created, and destorys Net Vlans's
"""
def __init__(self):
"""
Initializes the NetAppOntapVlan function
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
parent_interface=dict(required=True, type='str'),
vlanid=dict(required=True, type='str'),
node=dict(required=True, type='str'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
p = self.module.params
# set up state variables
self.state = p['state']
self.parent_interface = p['parent_interface']
self.vlanid = p['vlanid']
self.node = p['node']
self.interface_name = str(p['parent_interface']) + '-' + str(self.vlanid)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def create_vlan(self):
"""
Creates a new vlan
"""
vlan_obj = netapp_utils.zapi.NaElement("net-vlan-create")
vlan_info = self.create_vlan_info()
vlan_obj.add_child_elem(vlan_info)
self.server.invoke_successfully(vlan_obj, True)
def delete_vlan(self):
"""
Deletes a vland
"""
vlan_obj = netapp_utils.zapi.NaElement("net-vlan-delete")
vlan_info = self.create_vlan_info()
vlan_obj.add_child_elem(vlan_info)
self.server.invoke_successfully(vlan_obj, True)
def does_vlan_exist(self):
"""
Checks to see if a vlan already exists or not
:return: Returns True if the vlan exists, false if it doesn't
"""
vlan_obj = netapp_utils.zapi.NaElement("net-vlan-get")
vlan_obj.add_new_child("interface-name", self.interface_name)
vlan_obj.add_new_child("node", self.node)
try:
result = self.server.invoke_successfully(vlan_obj, True)
result.get_child_by_name("attributes").get_child_by_name("vlan-info").get_child_by_name("interface-name")
except netapp_utils.zapi.NaApiError:
return False
return True
def create_vlan_info(self):
"""
Create a vlan_info object to be used in a create/delete
:return:
"""
vlan_info = netapp_utils.zapi.NaElement("vlan-info")
# set up the vlan_info object:
vlan_info.add_new_child("parent-interface", self.parent_interface)
vlan_info.add_new_child("vlanid", self.vlanid)
vlan_info.add_new_child("node", self.node)
return vlan_info
def apply(self):
"""
check the option in the playbook to see what needs to be done
:return:
"""
changed = False
result = None
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_net_vlan", cserver)
existing_vlan = self.does_vlan_exist()
if existing_vlan:
if self.state == 'absent': # delete
changed = True
else:
if self.state == 'present': # create
changed = True
if changed:
if self.module.check_mode:
pass
else:
if self.state == 'present':
self.create_vlan()
elif self.state == 'absent':
self.delete_vlan()
self.module.exit_json(changed=changed, meta=result)
def main():
"""
Creates the NetApp Ontap vlan object, and runs the correct play task.
"""
v = NetAppOntapVlan()
v.apply()
if __name__ == '__main__':
main()

@ -1,576 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_nfs
short_description: NetApp ONTAP NFS status
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Enable or disable NFS on ONTAP
options:
state:
description:
- Whether NFS should exist or not.
choices: ['present', 'absent']
default: present
service_state:
description:
- Whether the specified NFS should be enabled or disabled. Creates NFS service if does not exist.
choices: ['started', 'stopped']
vserver:
description:
- Name of the vserver to use.
required: true
nfsv3:
description:
- status of NFSv3.
choices: ['enabled', 'disabled']
nfsv3_fsid_change:
description:
- status of if NFSv3 clients see change in FSID as they traverse filesystems.
choices: ['enabled', 'disabled']
version_added: '2.7'
nfsv4_fsid_change:
description:
- status of if NFSv4 clients see change in FSID as they traverse filesystems.
choices: ['enabled', 'disabled']
version_added: '2.9'
nfsv4:
description:
- status of NFSv4.
choices: ['enabled', 'disabled']
nfsv41:
description:
- status of NFSv41.
aliases: ['nfsv4.1']
choices: ['enabled', 'disabled']
nfsv41_pnfs:
description:
- status of NFSv41 pNFS.
choices: ['enabled', 'disabled']
version_added: '2.9'
nfsv4_numeric_ids:
description:
- status of NFSv4 numeric ID's.
choices: ['enabled', 'disabled']
version_added: '2.9'
vstorage_state:
description:
- status of vstorage_state.
choices: ['enabled', 'disabled']
nfsv4_id_domain:
description:
- Name of the nfsv4_id_domain to use.
nfsv40_acl:
description:
- status of NFS v4.0 ACL feature
choices: ['enabled', 'disabled']
version_added: '2.7'
nfsv40_read_delegation:
description:
- status for NFS v4.0 read delegation feature.
choices: ['enabled', 'disabled']
version_added: '2.7'
nfsv40_write_delegation:
description:
- status for NFS v4.0 write delegation feature.
choices: ['enabled', 'disabled']
version_added: '2.7'
nfsv41_acl:
description:
- status of NFS v4.1 ACL feature
choices: ['enabled', 'disabled']
version_added: '2.7'
nfsv41_read_delegation:
description:
- status for NFS v4.1 read delegation feature.
choices: ['enabled', 'disabled']
version_added: '2.7'
nfsv41_write_delegation:
description:
- status for NFS v4.1 write delegation feature.
choices: ['enabled', 'disabled']
version_added: '2.7'
nfsv40_referrals:
description:
- status for NFS v4.0 referrals.
choices: ['enabled', 'disabled']
version_added: '2.9'
nfsv41_referrals:
description:
- status for NFS v4.1 referrals.
choices: ['enabled', 'disabled']
version_added: '2.9'
tcp:
description:
- Enable TCP (support from ONTAP 9.3 onward).
choices: ['enabled', 'disabled']
udp:
description:
- Enable UDP (support from ONTAP 9.3 onward).
choices: ['enabled', 'disabled']
showmount:
description:
- Whether SVM allows showmount
choices: ['enabled', 'disabled']
version_added: '2.7'
tcp_max_xfer_size:
description:
- TCP Maximum Transfer Size (bytes). The default value is 65536.
version_added: '2.8'
type: int
"""
EXAMPLES = """
- name: change nfs status
na_ontap_nfs:
state: present
service_state: stopped
vserver: vs_hack
nfsv3: disabled
nfsv4: disabled
nfsv41: enabled
tcp: disabled
udp: disabled
vstorage_state: disabled
nfsv4_id_domain: example.com
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPNFS(object):
""" object initialize and class methods """
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
service_state=dict(required=False, choices=['started', 'stopped']),
vserver=dict(required=True, type='str'),
nfsv3=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv3_fsid_change=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv4_fsid_change=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv4=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv41=dict(required=False, default=None, choices=['enabled', 'disabled'], aliases=['nfsv4.1']),
nfsv41_pnfs=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv4_numeric_ids=dict(required=False, default=None, choices=['enabled', 'disabled']),
vstorage_state=dict(required=False, default=None, choices=['enabled', 'disabled']),
tcp=dict(required=False, default=None, choices=['enabled', 'disabled']),
udp=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv4_id_domain=dict(required=False, type='str', default=None),
nfsv40_acl=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv40_read_delegation=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv40_referrals=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv40_write_delegation=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv41_acl=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv41_read_delegation=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv41_referrals=dict(required=False, default=None, choices=['enabled', 'disabled']),
nfsv41_write_delegation=dict(required=False, default=None, choices=['enabled', 'disabled']),
showmount=dict(required=False, default=None, choices=['enabled', 'disabled']),
tcp_max_xfer_size=dict(required=False, default=None, type='int')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
# set up service_state variables
self.state = parameters['state']
self.service_state = parameters['service_state']
self.vserver = parameters['vserver']
self.nfsv3 = parameters['nfsv3']
self.nfsv3_fsid_change = parameters['nfsv3_fsid_change']
self.nfsv4_fsid_change = parameters['nfsv4_fsid_change']
self.nfsv4 = parameters['nfsv4']
self.nfsv41 = parameters['nfsv41']
self.vstorage_state = parameters['vstorage_state']
self.nfsv4_id_domain = parameters['nfsv4_id_domain']
self.udp = parameters['udp']
self.tcp = parameters['tcp']
self.nfsv40_acl = parameters['nfsv40_acl']
self.nfsv40_read_delegation = parameters['nfsv40_read_delegation']
self.nfsv40_referrals = parameters['nfsv40_referrals']
self.nfsv40_write_delegation = parameters['nfsv40_write_delegation']
self.nfsv41_acl = parameters['nfsv41_acl']
self.nfsv41_read_delegation = parameters['nfsv41_read_delegation']
self.nfsv41_referrals = parameters['nfsv41_referrals']
self.nfsv41_write_delegation = parameters['nfsv41_write_delegation']
self.nfsv41_pnfs = parameters['nfsv41_pnfs']
self.nfsv4_numeric_ids = parameters['nfsv4_numeric_ids']
self.showmount = parameters['showmount']
self.tcp_max_xfer_size = parameters['tcp_max_xfer_size']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def get_nfs_service(self):
"""
Return details about nfs
:param:
name : name of the vserver
:return: Details about nfs. None if not found.
:rtype: dict
"""
nfs_get_iter = netapp_utils.zapi.NaElement('nfs-service-get-iter')
nfs_info = netapp_utils.zapi.NaElement('nfs-info')
nfs_info.add_new_child('vserver', self.vserver)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(nfs_info)
nfs_get_iter.add_child_elem(query)
result = self.server.invoke_successfully(nfs_get_iter, True)
nfs_details = None
# check if job exists
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
attributes_list = result.get_child_by_name('attributes-list').get_child_by_name('nfs-info')
is_nfsv3_enabled = attributes_list.get_child_content('is-nfsv3-enabled')
is_nfsv3_fsid_change_enabled = attributes_list.get_child_content('is-nfsv3-fsid-change-enabled')
is_nfsv4_fsid_change_enabled = attributes_list.get_child_content('is-nfsv4-fsid-change-enabled')
is_nfsv40_enabled = attributes_list.get_child_content('is-nfsv40-enabled')
is_nfsv41_enabled = attributes_list.get_child_content('is-nfsv41-enabled')
is_vstorage_enabled = attributes_list.get_child_content('is-vstorage-enabled')
nfsv4_id_domain_value = attributes_list.get_child_content('nfsv4-id-domain')
is_tcp_enabled = attributes_list.get_child_content('is-tcp-enabled')
is_udp_enabled = attributes_list.get_child_content('is-udp-enabled')
is_nfsv40_acl_enabled = attributes_list.get_child_content('is-nfsv40-acl-enabled')
is_nfsv40_write_delegation_enabled = attributes_list.get_child_content('is-nfsv40-write-delegation-enabled')
is_nfsv40_read_delegation_enabled = attributes_list.get_child_content('is-nfsv40-read-delegation-enabled')
is_nfsv40_referrals_enabled = attributes_list.get_child_content('is-nfsv40-referrals-enabled')
is_nfsv41_acl_enabled = attributes_list.get_child_content('is-nfsv41-acl-enabled')
is_nfsv41_write_delegation_enabled = attributes_list.get_child_content('is-nfsv41-write-delegation-enabled')
is_nfsv41_read_delegation_enabled = attributes_list.get_child_content('is-nfsv41-read-delegation-enabled')
is_nfsv41_referrals_enabled = attributes_list.get_child_content('is-nfsv41-referrals-enabled')
is_nfsv41_pnfs_enabled = attributes_list.get_child_content('is-nfsv41-pnfs-enabled')
is_nfsv4_numeric_ids_enabled = attributes_list.get_child_content('is-nfsv4-numeric-ids-enabled')
is_showmount_enabled = attributes_list.get_child_content('showmount')
tcp_max_xfer_size = attributes_list.get_child_content('tcp-max-xfer-size')
nfs_details = {
'is_nfsv3_enabled': is_nfsv3_enabled,
'is_nfsv3_fsid_change_enabled': is_nfsv3_fsid_change_enabled,
'is_nfsv4_fsid_change_enabled': is_nfsv4_fsid_change_enabled,
'is_nfsv40_enabled': is_nfsv40_enabled,
'is_nfsv41_enabled': is_nfsv41_enabled,
'is_nfsv41_pnfs_enabled': is_nfsv41_pnfs_enabled,
'is_nfsv4_numeric_ids_enabled': is_nfsv4_numeric_ids_enabled,
'is_vstorage_enabled': is_vstorage_enabled,
'nfsv4_id_domain': nfsv4_id_domain_value,
'is_tcp_enabled': is_tcp_enabled,
'is_udp_enabled': is_udp_enabled,
'is_nfsv40_acl_enabled': is_nfsv40_acl_enabled,
'is_nfsv40_read_delegation_enabled': is_nfsv40_read_delegation_enabled,
'is_nfsv40_referrals_enabled': is_nfsv40_referrals_enabled,
'is_nfsv40_write_delegation_enabled': is_nfsv40_write_delegation_enabled,
'is_nfsv41_acl_enabled': is_nfsv41_acl_enabled,
'is_nfsv41_read_delegation_enabled': is_nfsv41_read_delegation_enabled,
'is_nfsv41_referrals_enabled': is_nfsv41_referrals_enabled,
'is_nfsv41_write_delegation_enabled': is_nfsv41_write_delegation_enabled,
'is_showmount_enabled': is_showmount_enabled,
'tcp_max_xfer_size': tcp_max_xfer_size
}
return nfs_details
def get_nfs_status(self):
"""
Return status of nfs
:param:
name : Name of the vserver
:return: status of nfs. None if not found.
:rtype: bool
"""
nfs_status = netapp_utils.zapi.NaElement('nfs-status')
result = self.server.invoke_successfully(nfs_status, True)
return_value = result.get_child_content('is-enabled')
return return_value
def enable_nfs(self):
"""
enable nfs (online). If the NFS service was not explicitly created,
this API will create one with default options.
"""
nfs_enable = netapp_utils.zapi.NaElement.create_node_with_children('nfs-enable')
try:
self.server.invoke_successfully(nfs_enable,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error changing the service_state of nfs %s to %s: %s' %
(self.vserver, self.service_state, to_native(error)),
exception=traceback.format_exc())
def disable_nfs(self):
"""
disable nfs (offline).
"""
nfs_disable = netapp_utils.zapi.NaElement.create_node_with_children('nfs-disable')
try:
self.server.invoke_successfully(nfs_disable,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error changing the service_state of nfs %s to %s: %s' %
(self.vserver, self.service_state, to_native(error)),
exception=traceback.format_exc())
def modify_nfs(self):
"""
modify nfs service
"""
nfs_modify = netapp_utils.zapi.NaElement('nfs-service-modify')
if self.nfsv3 == 'enabled':
nfs_modify.add_new_child('is-nfsv3-enabled', 'true')
elif self.nfsv3 == 'disabled':
nfs_modify.add_new_child('is-nfsv3-enabled', 'false')
if self.nfsv3_fsid_change == 'enabled':
nfs_modify.add_new_child('is-nfsv3-fsid-change-enabled', 'true')
elif self.nfsv3_fsid_change == 'disabled':
nfs_modify.add_new_child('is-nfsv3-fsid-change-enabled', 'false')
if self.nfsv4_fsid_change == 'enabled':
nfs_modify.add_new_child('is-nfsv4-fsid-change-enabled', 'true')
elif self.nfsv4_fsid_change == 'disabled':
nfs_modify.add_new_child('is-nfsv4-fsid-change-enabled', 'false')
if self.nfsv4 == 'enabled':
nfs_modify.add_new_child('is-nfsv40-enabled', 'true')
elif self.nfsv4 == 'disabled':
nfs_modify.add_new_child('is-nfsv40-enabled', 'false')
if self.nfsv41 == 'enabled':
nfs_modify.add_new_child('is-nfsv41-enabled', 'true')
elif self.nfsv41 == 'disabled':
nfs_modify.add_new_child('is-nfsv41-enabled', 'false')
if self.vstorage_state == 'enabled':
nfs_modify.add_new_child('is-vstorage-enabled', 'true')
elif self.vstorage_state == 'disabled':
nfs_modify.add_new_child('is-vstorage-enabled', 'false')
if self.tcp == 'enabled':
nfs_modify.add_new_child('is-tcp-enabled', 'true')
elif self.tcp == 'disabled':
nfs_modify.add_new_child('is-tcp-enabled', 'false')
if self.udp == 'enabled':
nfs_modify.add_new_child('is-udp-enabled', 'true')
elif self.udp == 'disabled':
nfs_modify.add_new_child('is-udp-enabled', 'false')
if self.nfsv40_acl == 'enabled':
nfs_modify.add_new_child('is-nfsv40-acl-enabled', 'true')
elif self.nfsv40_acl == 'disabled':
nfs_modify.add_new_child('is-nfsv40-acl-enabled', 'false')
if self.nfsv40_read_delegation == 'enabled':
nfs_modify.add_new_child('is-nfsv40-read-delegation-enabled', 'true')
elif self.nfsv40_read_delegation == 'disabled':
nfs_modify.add_new_child('is-nfsv40-read-delegation-enabled', 'false')
if self.nfsv40_referrals == 'enabled':
nfs_modify.add_new_child('is-nfsv40-referrals-enabled', 'true')
elif self.nfsv40_referrals == 'disabled':
nfs_modify.add_new_child('is-nfsv40-referrals-enabled', 'false')
if self.nfsv40_write_delegation == 'enabled':
nfs_modify.add_new_child('is-nfsv40-write-delegation-enabled', 'true')
elif self.nfsv40_write_delegation == 'disabled':
nfs_modify.add_new_child('is-nfsv40-write-delegation-enabled', 'false')
if self.nfsv41_acl == 'enabled':
nfs_modify.add_new_child('is-nfsv41-acl-enabled', 'true')
elif self.nfsv41_acl == 'disabled':
nfs_modify.add_new_child('is-nfsv41-acl-enabled', 'false')
if self.nfsv41_read_delegation == 'enabled':
nfs_modify.add_new_child('is-nfsv41-read-delegation-enabled', 'true')
elif self.nfsv41_read_delegation == 'disabled':
nfs_modify.add_new_child('is-nfsv41-read-delegation-enabled', 'false')
if self.nfsv41_referrals == 'enabled':
nfs_modify.add_new_child('is-nfsv41-referrals-enabled', 'true')
elif self.nfsv41_referrals == 'disabled':
nfs_modify.add_new_child('is-nfsv41-referrals-enabled', 'false')
if self.nfsv41_write_delegation == 'enabled':
nfs_modify.add_new_child('is-nfsv41-write-delegation-enabled', 'true')
elif self.nfsv41_write_delegation == 'disabled':
nfs_modify.add_new_child('is-nfsv41-write-delegation-enabled', 'false')
if self.nfsv41_pnfs == 'enabled':
nfs_modify.add_new_child('is-nfsv41-pnfs-enabled', 'true')
elif self.nfsv41_pnfs == 'disabled':
nfs_modify.add_new_child('is-nfsv41-pnfs-enabled', 'false')
if self.nfsv4_numeric_ids == 'enabled':
nfs_modify.add_new_child('is-nfsv4-numeric-ids-enabled', 'true')
elif self.nfsv4_numeric_ids == 'disabled':
nfs_modify.add_new_child('is-nfsv4-numeric-ids-enabled', 'false')
if self.showmount == 'enabled':
nfs_modify.add_new_child('showmount', 'true')
elif self.showmount == 'disabled':
nfs_modify.add_new_child('showmount', 'false')
if self.tcp_max_xfer_size is not None:
nfs_modify.add_new_child('tcp-max-xfer-size', str(self.tcp_max_xfer_size))
try:
self.server.invoke_successfully(nfs_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying nfs: %s'
% (to_native(error)),
exception=traceback.format_exc())
def modify_nfsv4_id_domain(self):
"""
modify nfs service
"""
nfsv4_id_domain_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'nfs-service-modify', **{'nfsv4-id-domain': self.nfsv4_id_domain})
if nfsv4_id_domain_modify is not None:
try:
self.server.invoke_successfully(nfsv4_id_domain_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying nfs: %s'
% (to_native(error)),
exception=traceback.format_exc())
def delete_nfs(self):
"""
delete nfs service.
"""
nfs_delete = netapp_utils.zapi.NaElement.create_node_with_children('nfs-service-destroy')
try:
self.server.invoke_successfully(nfs_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting nfs: %s' %
(to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""Apply action to nfs"""
changed = False
nfs_exists = False
modify_nfs = False
enable_nfs = False
disable_nfs = False
netapp_utils.ems_log_event("na_ontap_nfs", self.server)
nfs_enabled = self.get_nfs_status()
nfs_service_details = self.get_nfs_service()
is_nfsv4_id_domain_changed = False
def state_changed(expected, current):
if expected == "enabled" and current == "true":
return False
if expected == "disabled" and current == "false":
return False
return True
def is_modify_needed():
if (((self.nfsv3 is not None) and state_changed(self.nfsv3, nfs_service_details['is_nfsv3_enabled'])) or
((self.nfsv3_fsid_change is not None) and state_changed(self.nfsv3_fsid_change, nfs_service_details['is_nfsv3_fsid_change_enabled'])) or
((self.nfsv4_fsid_change is not None) and state_changed(self.nfsv4_fsid_change, nfs_service_details['is_nfsv4_fsid_change_enabled'])) or
((self.nfsv4 is not None) and state_changed(self.nfsv4, nfs_service_details['is_nfsv40_enabled'])) or
((self.nfsv41 is not None) and state_changed(self.nfsv41, nfs_service_details['is_nfsv41_enabled'])) or
((self.nfsv41_pnfs is not None) and state_changed(self.nfsv41_pnfs, nfs_service_details['is_nfsv41_pnfs_enabled'])) or
((self.nfsv4_numeric_ids is not None) and state_changed(self.nfsv4_numeric_ids, nfs_service_details['is_nfsv4_numeric_ids_enabled'])) or
((self.tcp is not None) and state_changed(self.tcp, nfs_service_details['is_tcp_enabled'])) or
((self.udp is not None) and state_changed(self.udp, nfs_service_details['is_udp_enabled'])) or
((self.nfsv40_acl is not None) and state_changed(self.nfsv40_acl, nfs_service_details['is_nfsv40_acl_enabled'])) or
((self.nfsv40_read_delegation is not None) and state_changed(self.nfsv40_read_delegation,
nfs_service_details['is_nfsv40_read_delegation_enabled'])) or
((self.nfsv40_write_delegation is not None) and state_changed(self.nfsv40_write_delegation,
nfs_service_details['is_nfsv40_write_delegation_enabled'])) or
((self.nfsv41_acl is not None) and state_changed(self.nfsv41_acl, nfs_service_details['is_nfsv41_acl_enabled'])) or
((self.nfsv41_read_delegation is not None) and state_changed(self.nfsv41_read_delegation,
nfs_service_details['is_nfsv41_read_delegation_enabled'])) or
((self.nfsv41_write_delegation is not None) and state_changed(self.nfsv41_write_delegation,
nfs_service_details['is_nfsv41_write_delegation_enabled'])) or
((self.nfsv40_referrals is not None) and state_changed(self.nfsv40_referrals,
nfs_service_details['is_nfsv40_referrals_enabled'])) or
((self.nfsv41_referrals is not None) and state_changed(self.nfsv41_referrals,
nfs_service_details['is_nfsv41_referrals_enabled'])) or
((self.showmount is not None) and state_changed(self.showmount, nfs_service_details['is_showmount_enabled'])) or
((self.vstorage_state is not None) and state_changed(self.vstorage_state, nfs_service_details['is_vstorage_enabled'])) or
((self.tcp_max_xfer_size is not None) and int(self.tcp_max_xfer_size) != int(nfs_service_details['tcp_max_xfer_size']))):
return True
return False
def is_domain_changed():
if (self.nfsv4_id_domain is not None) and (self.nfsv4_id_domain != nfs_service_details['nfsv4_id_domain']):
return True
return False
if nfs_service_details:
nfs_exists = True
if self.state == 'absent': # delete
changed = True
elif self.state == 'present': # modify
if self.service_state == 'started' and nfs_enabled == 'false':
enable_nfs = True
changed = True
elif self.service_state == 'stopped' and nfs_enabled == 'true':
disable_nfs = True
changed = True
if is_modify_needed():
modify_nfs = True
changed = True
if is_domain_changed():
is_nfsv4_id_domain_changed = True
changed = True
else:
if self.state == 'present': # create
changed = True
if changed:
if self.module.check_mode:
pass
else:
if self.state == 'present': # execute create
if not nfs_exists:
self.enable_nfs()
nfs_service_details = self.get_nfs_service()
if self.service_state == 'stopped':
self.disable_nfs()
if is_modify_needed():
self.modify_nfs()
if is_domain_changed():
self.modify_nfsv4_id_domain()
else:
if enable_nfs:
self.enable_nfs()
elif disable_nfs:
self.disable_nfs()
if modify_nfs:
self.modify_nfs()
if is_nfsv4_id_domain_changed:
self.modify_nfsv4_id_domain()
elif self.state == 'absent': # execute delete
self.delete_nfs()
self.module.exit_json(changed=changed)
def main():
""" Create object and call apply """
obj = NetAppONTAPNFS()
obj.apply()
if __name__ == '__main__':
main()

@ -1,144 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_node
short_description: NetApp ONTAP Rename a node.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.7'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Rename an ONTAP node.
options:
name:
description:
- The new name for the node
required: true
from_name:
description:
- The name of the node to be renamed. If I(name) already exists, no action will be performed.
required: true
'''
EXAMPLES = """
- name: rename node
na_ontap_node:
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
from_name: laurentn-vsim1
name: laurentncluster-2
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapNode(object):
"""
Rename node
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
name=dict(required=True, type='str'),
from_name=dict(required=True, type='str'),
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.cluster = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def rename_node(self):
"""
Rename an existing node
:return: none
"""
node_obj = netapp_utils.zapi.NaElement('system-node-rename')
node_obj.add_new_child('node', self.parameters['from_name'])
node_obj.add_new_child('new-name', self.parameters['name'])
try:
self.cluster.invoke_successfully(node_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating node: %s' %
(to_native(error)),
exception=traceback.format_exc())
def get_node(self, name):
node_obj = netapp_utils.zapi.NaElement('system-node-get')
node_obj.add_new_child('node', name)
try:
self.cluster.invoke_successfully(node_obj, True)
except netapp_utils.zapi.NaApiError as error:
if to_native(error.code) == "13115":
# 13115 (EINVALIDINPUTERROR) if the node does not exist
return None
else:
self.module.fail_json(msg=to_native(
error), exception=traceback.format_exc())
return True
def apply(self):
# logging ems event
results = netapp_utils.get_cserver(self.cluster)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_node", cserver)
exists = self.get_node(self.parameters['name'])
from_exists = self.get_node(self.parameters['from_name'])
changed = False
if exists:
pass
else:
if from_exists:
self.rename_node()
changed = True
else:
self.module.fail_json(msg='Error renaming node, from_name %s does not exist' % self.parameters['from_name'])
self.module.exit_json(changed=changed)
def main():
"""
Start, Stop and Enable node services.
"""
obj = NetAppOntapNode()
obj.apply()
if __name__ == '__main__':
main()

@ -1,226 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_ntp
short_description: NetApp ONTAP NTP server
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or delete or modify NTP server in ONTAP
options:
state:
description:
- Whether the specified NTP server should exist or not.
choices: ['present', 'absent']
default: 'present'
server_name:
description:
- The name of the NTP server to manage.
required: True
version:
description:
- give version for NTP server
choices: ['auto', '3', '4']
default: 'auto'
"""
EXAMPLES = """
- name: Create NTP server
na_ontap_ntp:
state: present
version: auto
server_name: "{{ server_name }}"
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete NTP server
na_ontap_ntp:
state: absent
server_name: "{{ server_name }}"
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapNTPServer(object):
""" object initialize and class methods """
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
server_name=dict(required=True, type='str'),
version=dict(required=False, type='str', default='auto',
choices=['auto', '3', '4']),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
# set up state variables
self.state = parameters['state']
self.server_name = parameters['server_name']
self.version = parameters['version']
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_ntp_server(self):
"""
Return details about the ntp server
:param:
name : Name of the server_name
:return: Details about the ntp server. None if not found.
:rtype: dict
"""
ntp_iter = netapp_utils.zapi.NaElement('ntp-server-get-iter')
ntp_info = netapp_utils.zapi.NaElement('ntp-server-info')
ntp_info.add_new_child('server-name', self.server_name)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(ntp_info)
ntp_iter.add_child_elem(query)
result = self.server.invoke_successfully(ntp_iter, True)
return_value = None
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
ntp_server_name = result.get_child_by_name('attributes-list').\
get_child_by_name('ntp-server-info').\
get_child_content('server-name')
server_version = result.get_child_by_name('attributes-list').\
get_child_by_name('ntp-server-info').\
get_child_content('version')
return_value = {
'server-name': ntp_server_name,
'version': server_version
}
return return_value
def create_ntp_server(self):
"""
create ntp server.
"""
ntp_server_create = netapp_utils.zapi.NaElement.create_node_with_children(
'ntp-server-create', **{'server-name': self.server_name,
'version': self.version
})
try:
self.server.invoke_successfully(ntp_server_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating ntp server %s: %s'
% (self.server_name, to_native(error)),
exception=traceback.format_exc())
def delete_ntp_server(self):
"""
delete ntp server.
"""
ntp_server_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'ntp-server-delete', **{'server-name': self.server_name})
try:
self.server.invoke_successfully(ntp_server_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting ntp server %s: %s'
% (self.server_name, to_native(error)),
exception=traceback.format_exc())
def modify_version(self):
"""
modify the version.
"""
ntp_modify_versoin = netapp_utils.zapi.NaElement.create_node_with_children(
'ntp-server-modify',
**{'server-name': self.server_name, 'version': self.version})
try:
self.server.invoke_successfully(ntp_modify_versoin,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying version for ntp server %s: %s'
% (self.server_name, to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""Apply action to ntp-server"""
changed = False
ntp_modify = False
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_ntp", cserver)
ntp_server_details = self.get_ntp_server()
if ntp_server_details is not None:
if self.state == 'absent': # delete
changed = True
elif self.state == 'present' and self.version:
# modify version
if self.version != ntp_server_details['version']:
ntp_modify = True
changed = True
else:
if self.state == 'present': # create
changed = True
if changed:
if self.module.check_mode:
pass
else:
if self.state == 'present':
if ntp_server_details is None:
self.create_ntp_server()
elif ntp_modify:
self.modify_version()
elif self.state == 'absent':
self.delete_ntp_server()
self.module.exit_json(changed=changed)
def main():
""" Create object and call apply """
ntp_obj = NetAppOntapNTPServer()
ntp_obj.apply()
if __name__ == '__main__':
main()

@ -1,209 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete NVMe Service
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_nvme
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified NVMe should exist or not.
default: present
vserver:
description:
- Name of the vserver to use.
required: true
status_admin:
description:
- Whether the status of NVMe should be up or down
type: bool
short_description: "NetApp ONTAP Manage NVMe Service"
version_added: "2.8"
'''
EXAMPLES = """
- name: Create NVMe
na_ontap_nvme:
state: present
status_admin: False
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
- name: Modify NVMe
na_ontap_nvme:
state: present
status_admin: True
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
- name: Delete NVMe
na_ontap_nvme:
state: absent
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ 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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPNVMe(object):
"""
Class with NVMe service methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
status_admin=dict(required=False, type='bool')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_nvme(self):
"""
Get current nvme details
:return: dict if nvme exists, None otherwise
"""
nvme_get = netapp_utils.zapi.NaElement('nvme-get-iter')
query = {
'query': {
'nvme-target-service-info': {
'vserver': self.parameters['vserver']
}
}
}
nvme_get.translate_struct(query)
try:
result = self.server.invoke_successfully(nvme_get, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching nvme info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
attributes_list = result.get_child_by_name('attributes-list')
nvme_info = attributes_list.get_child_by_name('nvme-target-service-info')
return_value = {'status_admin': nvme_info.get_child_content('is-available')}
return return_value
return None
def create_nvme(self):
"""
Create NVMe service
"""
nvme_create = netapp_utils.zapi.NaElement('nvme-create')
if self.parameters.get('status_admin') is not None:
options = {'is-available': self.parameters['status_admin']}
nvme_create.translate_struct(options)
try:
self.server.invoke_successfully(nvme_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating nvme for vserver %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
def delete_nvme(self):
"""
Delete NVMe service
"""
nvme_delete = netapp_utils.zapi.NaElement('nvme-delete')
try:
self.server.invoke_successfully(nvme_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting nvme for vserver %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
def modify_nvme(self, status=None):
"""
Modify NVMe service
"""
if status is None:
status = self.parameters['status_admin']
options = {'is-available': status}
nvme_modify = netapp_utils.zapi.NaElement('nvme-modify')
nvme_modify.translate_struct(options)
try:
self.server.invoke_successfully(nvme_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying nvme for vserver %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to NVMe service
"""
netapp_utils.ems_log_event("na_ontap_nvme", self.server)
current = self.get_nvme()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.parameters.get('status_admin') is not None:
self.parameters['status_admin'] = self.na_helper.get_value_for_bool(False, self.parameters['status_admin'])
if cd_action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_nvme()
elif cd_action == 'delete':
# NVMe status_admin needs to be down before deleting it
self.modify_nvme('false')
self.delete_nvme()
elif modify:
self.modify_nvme()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPNVMe()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,195 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete NVME namespace
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_nvme_namespace
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified namespace should exist or not.
default: present
vserver:
description:
- Name of the vserver to use.
required: true
ostype:
description:
- Specifies the ostype for initiators
choices: ['windows', 'linux', 'vmware', 'xen', 'hyper_v']
size:
description:
- Size in bytes.
Range is [0..2^63-1].
type: int
path:
description:
- Namespace path.
type: str
short_description: "NetApp ONTAP Manage NVME Namespace"
version_added: "2.8"
'''
EXAMPLES = """
- name: Create NVME Namespace
na_ontap_nvme_namespace:
state: present
ostype: linux
path: /vol/ansible/test
size: 20
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
- name: Create NVME Namespace (Idempotency)
na_ontap_nvme_namespace:
state: present
ostype: linux
path: /vol/ansible/test
size: 20
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ 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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPNVMENamespace(object):
"""
Class with NVME namespace methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
ostype=dict(required=False, type='str', choices=['windows', 'linux', 'vmware', 'xen', 'hyper_v']),
path=dict(required=True, type='str'),
size=dict(required=False, type='int')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[('state', 'present', ['ostype', 'size'])],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_namespace(self):
"""
Get current namespace details
:return: dict if namespace exists, None otherwise
"""
namespace_get = netapp_utils.zapi.NaElement('nvme-namespace-get-iter')
query = {
'query': {
'nvme-namespace-info': {
'path': self.parameters['path'],
'vserver': self.parameters['vserver']
}
}
}
namespace_get.translate_struct(query)
try:
result = self.server.invoke_successfully(namespace_get, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching namespace info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
return result
return None
def create_namespace(self):
"""
Create a NVME Namespace
"""
options = {'path': self.parameters['path'],
'ostype': self.parameters['ostype'],
'size': self.parameters['size']
}
namespace_create = netapp_utils.zapi.NaElement('nvme-namespace-create')
namespace_create.translate_struct(options)
try:
self.server.invoke_successfully(namespace_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating namespace for path %s: %s'
% (self.parameters.get('path'), to_native(error)),
exception=traceback.format_exc())
def delete_namespace(self):
"""
Delete a NVME Namespace
"""
options = {'path': self.parameters['path']
}
namespace_delete = netapp_utils.zapi.NaElement.create_node_with_children('nvme-namespace-delete', **options)
try:
self.server.invoke_successfully(namespace_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting namespace for path %s: %s'
% (self.parameters.get('path'), to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to NVME Namespace
"""
netapp_utils.ems_log_event("na_ontap_nvme_namespace", self.server)
current = self.get_namespace()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_namespace()
elif cd_action == 'delete':
self.delete_namespace()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPNVMENamespace()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,355 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete NVME subsystem
- Associate(modify) host/map to NVME subsystem
- NVMe service should be existing in the data vserver with NVMe protocol as a pre-requisite
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_nvme_subsystem
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified subsystem should exist or not.
default: present
vserver:
description:
- Name of the vserver to use.
required: true
subsystem:
description:
- Specifies the subsystem
required: true
ostype:
description:
- Specifies the ostype for initiators
choices: ['windows', 'linux', 'vmware', 'xen', 'hyper_v']
skip_host_check:
description:
- Skip host check
- Required to delete an NVMe Subsystem with attached NVMe namespaces
default: false
type: bool
skip_mapped_check:
description:
- Skip mapped namespace check
- Required to delete an NVMe Subsystem with attached NVMe namespaces
default: false
type: bool
hosts:
description:
- List of host NQNs (NVMe Qualification Name) associated to the controller.
type: list
paths:
description:
- List of Namespace paths to be associated with the subsystem.
type: list
short_description: "NetApp ONTAP Manage NVME Subsystem"
version_added: "2.8"
'''
EXAMPLES = """
- name: Create NVME Subsystem
na_ontap_nvme_subsystem:
state: present
subsystem: test_sub
vserver: test_dest
ostype: linux
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete NVME Subsystem
na_ontap_nvme_subsystem:
state: absent
subsystem: test_sub
vserver: test_dest
skip_host_check: True
skip_mapped_check: True
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Associate NVME Subsystem host/map
na_ontap_nvme_subsystem:
state: present
subsystem: "{{ subsystem }}"
ostype: linux
hosts: nqn.1992-08.com.netapp:sn.3017cfc1e2ba11e89c55005056b36338:subsystem.ansible
paths: /vol/ansible/test,/vol/ansible/test1
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
- name: Modify NVME subsystem map
na_ontap_nvme_subsystem:
state: present
subsystem: test_sub
vserver: test_dest
skip_host_check: True
skip_mapped_check: True
paths: /vol/ansible/test
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPNVMESubsystem(object):
"""
Class with NVME subsytem methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
subsystem=dict(required=True, type='str'),
ostype=dict(required=False, type='str', choices=['windows', 'linux', 'vmware', 'xen', 'hyper_v']),
skip_host_check=dict(required=False, type='bool', default=False),
skip_mapped_check=dict(required=False, type='bool', default=False),
hosts=dict(required=False, type='list'),
paths=dict(required=False, type='list')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_subsystem(self):
"""
Get current subsystem details
:return: dict if subsystem exists, None otherwise
"""
subsystem_get = netapp_utils.zapi.NaElement('nvme-subsystem-get-iter')
query = {
'query': {
'nvme-subsytem-info': {
'subsystem': self.parameters.get('subsystem')
}
}
}
subsystem_get.translate_struct(query)
try:
result = self.server.invoke_successfully(subsystem_get, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching subsystem info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
return True
return None
def create_subsystem(self):
"""
Create a NVME Subsystem
"""
if self.parameters.get('ostype') is None:
self.module.fail_json(msg="Error: Missing required parameter 'os_type' for creating subsystem")
options = {'subsystem': self.parameters['subsystem'],
'ostype': self.parameters['ostype']
}
subsystem_create = netapp_utils.zapi.NaElement('nvme-subsystem-create')
subsystem_create.translate_struct(options)
try:
self.server.invoke_successfully(subsystem_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating subsystem for %s: %s'
% (self.parameters.get('subsystem'), to_native(error)),
exception=traceback.format_exc())
def delete_subsystem(self):
"""
Delete a NVME subsystem
"""
options = {'subsystem': self.parameters['subsystem'],
'skip-host-check': 'true' if self.parameters.get('skip_host_check') else 'false',
'skip-mapped-check': 'true' if self.parameters.get('skip_mapped_check') else 'false',
}
subsystem_delete = netapp_utils.zapi.NaElement.create_node_with_children('nvme-subsystem-delete', **options)
try:
self.server.invoke_successfully(subsystem_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting subsystem for %s: %s'
% (self.parameters.get('subsystem'), to_native(error)),
exception=traceback.format_exc())
def get_subsystem_host_map(self, type):
"""
Get current subsystem host details
:return: list if host exists, None otherwise
"""
if type == 'hosts':
zapi_get, zapi_info, zapi_type = 'nvme-subsystem-host-get-iter', 'nvme-target-subsystem-host-info',\
'host-nqn'
elif type == 'paths':
zapi_get, zapi_info, zapi_type = 'nvme-subsystem-map-get-iter', 'nvme-target-subsystem-map-info', 'path'
subsystem_get = netapp_utils.zapi.NaElement(zapi_get)
query = {
'query': {
zapi_info: {
'subsystem': self.parameters.get('subsystem')
}
}
}
subsystem_get.translate_struct(query)
try:
result = self.server.invoke_successfully(subsystem_get, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching subsystem info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
attrs_list = result.get_child_by_name('attributes-list')
return_list = []
for item in attrs_list.get_children():
return_list.append(item[zapi_type])
return {type: return_list}
return None
def add_subsystem_host_map(self, data, type):
"""
Add a NVME Subsystem host/map
:param: data: list of hosts/paths to be added
:param: type: hosts/paths
"""
if type == 'hosts':
zapi_add, zapi_type = 'nvme-subsystem-host-add', 'host-nqn'
elif type == 'paths':
zapi_add, zapi_type = 'nvme-subsystem-map-add', 'path'
for item in data:
options = {'subsystem': self.parameters['subsystem'],
zapi_type: item
}
subsystem_add = netapp_utils.zapi.NaElement.create_node_with_children(zapi_add, **options)
try:
self.server.invoke_successfully(subsystem_add, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error adding %s for subsystem %s: %s'
% (item, self.parameters.get('subsystem'), to_native(error)),
exception=traceback.format_exc())
def remove_subsystem_host_map(self, data, type):
"""
Remove a NVME Subsystem host/map
:param: data: list of hosts/paths to be added
:param: type: hosts/paths
"""
if type == 'hosts':
zapi_remove, zapi_type = 'nvme-subsystem-host-remove', 'host-nqn'
elif type == 'paths':
zapi_remove, zapi_type = 'nvme-subsystem-map-remove', 'path'
for item in data:
options = {'subsystem': self.parameters['subsystem'],
zapi_type: item
}
subsystem_remove = netapp_utils.zapi.NaElement.create_node_with_children(zapi_remove, **options)
try:
self.server.invoke_successfully(subsystem_remove, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing %s for subsystem %s: %s'
% (item, self.parameters.get('subsystem'), to_native(error)),
exception=traceback.format_exc())
def associate_host_map(self, types):
"""
Check if there are hosts or paths to be associated with the subsystem
"""
action_add_dict = {}
action_remove_dict = {}
for type in types:
if self.parameters.get(type):
current = self.get_subsystem_host_map(type)
if current:
add_items = self.na_helper.\
get_modified_attributes(current, self.parameters, get_list_diff=True).get(type)
remove_items = [item for item in current[type] if item not in self.parameters.get(type)]
else:
add_items = self.parameters[type]
remove_items = {}
if add_items:
action_add_dict[type] = add_items
self.na_helper.changed = True
if remove_items:
action_remove_dict[type] = remove_items
self.na_helper.changed = True
return action_add_dict, action_remove_dict
def modify_host_map(self, add_host_map, remove_host_map):
for type, data in add_host_map.items():
self.add_subsystem_host_map(data, type)
for type, data in remove_host_map.items():
self.remove_subsystem_host_map(data, type)
def apply(self):
"""
Apply action to NVME subsystem
"""
netapp_utils.ems_log_event("na_ontap_nvme_subsystem", self.server)
types = ['hosts', 'paths']
current = self.get_subsystem()
add_host_map, remove_host_map = dict(), dict()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action != 'delete' and self.parameters['state'] == 'present':
add_host_map, remove_host_map = self.associate_host_map(types)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_subsystem()
self.modify_host_map(add_host_map, remove_host_map)
elif cd_action == 'delete':
self.delete_subsystem()
elif cd_action is None:
self.modify_host_map(add_host_map, remove_host_map)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPNVMESubsystem()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,237 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'certified'}
DOCUMENTATION = '''
module: na_ontap_object_store
short_description: NetApp ONTAP manage object store config.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or delete object store config on ONTAP.
options:
state:
description:
- Whether the specified object store config should exist or not.
choices: ['present', 'absent']
default: 'present'
type: str
name:
required: true
description:
- The name of the object store config to manage.
type: str
provider_type:
required: false
description:
- The name of the object store config provider.
type: str
server:
required: false
description:
- Fully qualified domain name of the object store config.
type: str
container:
required: false
description:
- Data bucket/container name used in S3 requests.
type: str
access_key:
required: false
description:
- Access key ID for AWS_S3 and SGWS provider types.
type: str
secret_password:
required: false
description:
- Secret access key for AWS_S3 and SGWS provider types.
type: str
'''
EXAMPLES = """
- name: object store Create
na_ontap_object_store:
state: present
name: ansible
provider_type: SGWS
server: abc
container: abc
access_key: s3.amazonaws.com
secret_password: abc
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ password }}"
- name: object store Create
na_ontap_object_store:
state: absent
name: ansible
hostname: "{{ hostname }}"
username: "{{ username }}"
password: "{{ 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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapObjectStoreConfig(object):
''' object initialize and class methods '''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
name=dict(required=True, type='str'),
state=dict(required=False, choices=['present', 'absent'], default='present'),
provider_type=dict(required=False, type='str'),
server=dict(required=False, type='str'),
container=dict(required=False, type='str'),
access_key=dict(required=False, type='str'),
secret_password=dict(required=False, type='str', no_log=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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_aggr_object_store(self):
"""
Fetch details if object store config exists.
:return:
Dictionary of current details if object store config found
None if object store config is not found
"""
aggr_object_store_get_iter = netapp_utils.zapi.NaElement.create_node_with_children(
'aggr-object-store-config-get', **{'object-store-name': self.parameters['name']})
result = None
try:
result = self.server.invoke_successfully(aggr_object_store_get_iter, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
# Error 15661 denotes an object store not being found.
if to_native(error.code) == "15661":
pass
else:
self.module.fail_json(msg=to_native(error), exception=traceback.format_exc())
return result
def create_aggr_object_store(self):
"""
Create aggregate object store config
:return: None
"""
required_keys = set(['provider_type', 'server', 'container', 'access_key'])
if not required_keys.issubset(set(self.parameters.keys())):
self.module.fail_json(msg='Error provisioning object store %s: one of the following parameters are missing '
'%s' % (self.parameters['name'], ', '.join(required_keys)))
options = {'object-store-name': self.parameters['name'],
'provider-type': self.parameters['provider_type'],
'server': self.parameters['server'],
's3-name': self.parameters['container'],
'access-key': self.parameters['access_key']}
if self.parameters.get('secret_password'):
options['secret-password'] = self.parameters['secret_password']
object_store_create = netapp_utils.zapi.NaElement.create_node_with_children('aggr-object-store-config-create', **options)
try:
self.server.invoke_successfully(object_store_create, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error provisioning object store config %s: %s"
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_aggr_object_store(self):
"""
Delete aggregate object store config
:return: None
"""
object_store_destroy = netapp_utils.zapi.NaElement.create_node_with_children(
'aggr-object-store-config-delete', **{'object-store-name': self.parameters['name']})
try:
self.server.invoke_successfully(object_store_destroy,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error removing object store config %s: %s" %
(self.parameters['name'], to_native(error)), exception=traceback.format_exc())
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def apply(self):
"""
Apply action to the object store config
:return: None
"""
self.asup_log_for_cserver("na_ontap_object_store_config")
current = self.get_aggr_object_store()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_aggr_object_store()
elif cd_action == 'delete':
self.delete_aggr_object_store()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Create Object Store Config class instance and invoke apply
:return: None
"""
obj_store = NetAppOntapObjectStoreConfig()
obj_store.apply()
if __name__ == '__main__':
main()

@ -1,380 +0,0 @@
#!/usr/bin/python
''' This is an Ansible module for ONTAP to manage ports for various resources.
(c) 2019, 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': 'community'
}
DOCUMENTATION = '''
module: na_ontap_ports
short_description: NetApp ONTAP add/remove ports
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Add or remove ports for broadcast domain and portset.
options:
state:
description:
- Whether the specified port should be added or removed.
choices: ['present', 'absent']
default: present
type: str
vserver:
description:
- Name of the SVM.
- Specify this option when operating on portset.
type: str
names:
description:
- List of ports.
type: list
required: true
resource_name:
description:
- name of the portset or broadcast domain.
type: str
required: true
resource_type:
description:
- type of the resource to add a port to or remove a port from.
choices: ['broadcast_domain', 'portset']
required: true
type: str
ipspace:
description:
- Specify the required ipspace for the broadcast domain.
- A domain ipspace can not be modified after the domain has been created.
type: str
portset_type:
description:
- Protocols accepted for portset.
choices: ['fcp', 'iscsi', 'mixed']
type: str
'''
EXAMPLES = '''
- name: broadcast domain remove port
tags:
- remove
na_ontap_ports:
state: absent
names: test-vsim1:e0d-1,test-vsim1:e0d-2
resource_type: broadcast_domain
resource_name: ansible_domain
hostname: "{{ hostname }}"
username: user
password: password
https: False
- name: broadcast domain add port
tags:
- add
na_ontap_ports:
state: present
names: test-vsim1:e0d-1,test-vsim1:e0d-2
resource_type: broadcast_domain
resource_name: ansible_domain
ipspace: Default
hostname: "{{ hostname }}"
username: user
password: password
https: False
- name: portset remove port
tags:
- remove
na_ontap_ports:
state: absent
names: lif_2
resource_type: portset
resource_name: portset_1
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: user
password: password
https: False
- name: portset add port
tags:
- add
na_ontap_ports:
state: present
names: lif_2
resource_type: portset
resource_name: portset_1
portset_type: iscsi
vserver: "{{ vserver }}"
hostname: "{{ hostname }}"
username: user
password: password
https: False
'''
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapPorts(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
vserver=dict(required=False, type='str'),
names=dict(required=True, type='list'),
resource_name=dict(required=True, type='str'),
resource_type=dict(required=True, type='str', choices=['broadcast_domain', 'portset']),
ipspace=dict(required=False, type='str'),
portset_type=dict(required=False, type='str', choices=['fcp', 'iscsi', 'mixed']),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('resource_type', 'portset', ['vserver']),
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
if self.parameters['resource_type'] == 'broadcast_domain':
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
elif self.parameters['resource_type'] == 'portset':
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.parameters['vserver'])
def add_broadcast_domain_ports(self, ports):
"""
Add broadcast domain ports
:param: ports to be added.
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-add-ports')
domain_obj.add_new_child("broadcast-domain", self.parameters['resource_name'])
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in ports:
ports_obj.add_new_child('net-qualified-port-name', port)
try:
self.server.invoke_successfully(domain_obj, True)
return True
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error adding port for broadcast domain %s: %s' %
(self.parameters['resource_name'], to_native(error)),
exception=traceback.format_exc())
def remove_broadcast_domain_ports(self, ports):
"""
Deletes broadcast domain ports
:param: ports to be removed.
"""
domain_obj = netapp_utils.zapi.NaElement('net-port-broadcast-domain-remove-ports')
domain_obj.add_new_child("broadcast-domain", self.parameters['resource_name'])
if self.parameters.get('ipspace'):
domain_obj.add_new_child("ipspace", self.parameters['ipspace'])
ports_obj = netapp_utils.zapi.NaElement('ports')
domain_obj.add_child_elem(ports_obj)
for port in ports:
ports_obj.add_new_child('net-qualified-port-name', port)
try:
self.server.invoke_successfully(domain_obj, True)
return True
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing port for broadcast domain %s: %s' %
(self.parameters['resource_name'], to_native(error)),
exception=traceback.format_exc())
def get_broadcast_domain_ports(self):
"""
Return details about the broadcast domain ports.
:return: Details about the broadcast domain ports. [] if not found.
:rtype: list
"""
domain_get_iter = netapp_utils.zapi.NaElement('net-port-broadcast-domain-get-iter')
broadcast_domain_info = netapp_utils.zapi.NaElement('net-port-broadcast-domain-info')
broadcast_domain_info.add_new_child('broadcast-domain', self.parameters['resource_name'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(broadcast_domain_info)
domain_get_iter.add_child_elem(query)
result = self.server.invoke_successfully(domain_get_iter, True)
ports = []
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
domain_info = result.get_child_by_name('attributes-list').get_child_by_name('net-port-broadcast-domain-info')
domain_ports = domain_info.get_child_by_name('ports')
if domain_ports is not None:
ports = [port.get_child_content('port') for port in domain_ports.get_children()]
return ports
def remove_portset_ports(self, port):
"""
Removes all existing ports from portset
:return: None
"""
options = {'portset-name': self.parameters['resource_name'],
'portset-port-name': port.strip()}
portset_modify = netapp_utils.zapi.NaElement.create_node_with_children('portset-remove', **options)
try:
self.server.invoke_successfully(portset_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing port in portset %s: %s' %
(self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc())
def add_portset_ports(self, port):
"""
Add the list of ports to portset
:return: None
"""
options = {'portset-name': self.parameters['resource_name'],
'portset-port-name': port.strip()}
portset_modify = netapp_utils.zapi.NaElement.create_node_with_children('portset-add', **options)
try:
self.server.invoke_successfully(portset_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error adding port in portset %s: %s' %
(self.parameters['resource_name'], to_native(error)), exception=traceback.format_exc())
def portset_get_iter(self):
"""
Compose NaElement object to query current portset using vserver, portset-name and portset-type parameters
:return: NaElement object for portset-get-iter with query
"""
portset_get = netapp_utils.zapi.NaElement('portset-get-iter')
query = netapp_utils.zapi.NaElement('query')
portset_info = netapp_utils.zapi.NaElement('portset-info')
portset_info.add_new_child('vserver', self.parameters['vserver'])
portset_info.add_new_child('portset-name', self.parameters['resource_name'])
if self.parameters.get('portset_type'):
portset_info.add_new_child('portset-type', self.parameters['portset_type'])
query.add_child_elem(portset_info)
portset_get.add_child_elem(query)
return portset_get
def portset_get(self):
"""
Get current portset info
:return: List of current ports if query successful, else return []
"""
portset_get_iter = self.portset_get_iter()
result, ports = None, []
try:
result = self.server.invoke_successfully(portset_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching portset %s: %s'
% (self.parameters['resource_name'], to_native(error)),
exception=traceback.format_exc())
# return portset details
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
portset_get_info = result.get_child_by_name('attributes-list').get_child_by_name('portset-info')
if int(portset_get_info.get_child_content('portset-port-total')) > 0:
port_info = portset_get_info.get_child_by_name('portset-port-info')
ports = [port.get_content() for port in port_info.get_children()]
return ports
def modify_broadcast_domain_ports(self):
"""
compare current and desire ports. Call add or remove ports methods if needed.
:return: None.
"""
current_ports = self.get_broadcast_domain_ports()
cd_ports = self.parameters['names']
if self.parameters['state'] == 'present':
ports_to_add = [port for port in cd_ports if port not in current_ports]
if len(ports_to_add) > 0:
self.add_broadcast_domain_ports(ports_to_add)
self.na_helper.changed = True
if self.parameters['state'] == 'absent':
ports_to_remove = [port for port in cd_ports if port in current_ports]
if len(ports_to_remove) > 0:
self.remove_broadcast_domain_ports(ports_to_remove)
self.na_helper.changed = True
def modify_portset_ports(self):
current_ports = self.portset_get()
cd_ports = self.parameters['names']
if self.parameters['state'] == 'present':
ports_to_add = [port for port in cd_ports if port not in current_ports]
if len(ports_to_add) > 0:
for port in ports_to_add:
self.add_portset_ports(port)
self.na_helper.changed = True
if self.parameters['state'] == 'absent':
ports_to_remove = [port for port in cd_ports if port in current_ports]
if len(ports_to_remove) > 0:
for port in ports_to_remove:
self.remove_portset_ports(port)
self.na_helper.changed = True
def apply(self):
self.asup_log_for_cserver("na_ontap_ports")
if self.parameters['resource_type'] == 'broadcast_domain':
self.modify_broadcast_domain_ports()
elif self.parameters['resource_type'] == 'portset':
self.modify_portset_ports()
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
portset_obj = NetAppOntapPorts()
portset_obj.apply()
if __name__ == '__main__':
main()

@ -1,278 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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': 'community'}
DOCUMENTATION = '''
short_description: NetApp ONTAP Create/Delete portset
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete ONTAP portset, modify ports in a portset.
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_portset
options:
state:
description:
- If you want to create a portset.
default: present
vserver:
required: true
description:
- Name of the SVM.
name:
required: true
description:
- Name of the port set to create.
type:
description:
- Required for create.
- Protocols accepted for this portset.
choices: ['fcp', 'iscsi', 'mixed']
force:
description:
- If 'false' or not specified, the request will fail if there are any igroups bound to this portset.
- If 'true', forcibly destroy the portset, even if there are existing igroup bindings.
type: bool
default: False
ports:
description:
- Specify the ports associated with this portset. Should be comma separated.
- It represents the expected state of a list of ports at any time, and replaces the current value of ports.
- Adds a port if it is specified in expected state but not in current state.
- Deletes a port if it is in current state but not in expected state.
version_added: "2.8"
'''
EXAMPLES = """
- name: Create Portset
na_ontap_portset:
state: present
vserver: vserver_name
name: portset_name
ports: a1
type: "{{ protocol type }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
- name: Modify ports in portset
na_ontap_portset:
state: present
vserver: vserver_name
name: portset_name
ports: a1,a2
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
- name: Delete Portset
na_ontap_portset:
state: absent
vserver: vserver_name
name: portset_name
force: True
type: "{{ protocol type }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPPortset(object):
"""
Methods to create or delete portset
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, default='present'),
vserver=dict(required=True, type='str'),
name=dict(required=True, type='str'),
type=dict(required=False, type='str', choices=[
'fcp', 'iscsi', 'mixed']),
force=dict(required=False, type='bool', default=False),
ports=dict(required=False, type='list')
))
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.parameters['vserver'])
def portset_get_iter(self):
"""
Compose NaElement object to query current portset using vserver, portset-name and portset-type parameters
:return: NaElement object for portset-get-iter with query
"""
portset_get = netapp_utils.zapi.NaElement('portset-get-iter')
query = netapp_utils.zapi.NaElement('query')
portset_info = netapp_utils.zapi.NaElement('portset-info')
portset_info.add_new_child('vserver', self.parameters['vserver'])
portset_info.add_new_child('portset-name', self.parameters['name'])
if self.parameters.get('type'):
portset_info.add_new_child('portset-type', self.parameters['type'])
query.add_child_elem(portset_info)
portset_get.add_child_elem(query)
return portset_get
def portset_get(self):
"""
Get current portset info
:return: Dictionary of current portset details if query successful, else return None
"""
portset_get_iter = self.portset_get_iter()
result, portset_info = None, dict()
try:
result = self.server.invoke_successfully(portset_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching portset %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
# return portset details
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
portset_get_info = result.get_child_by_name('attributes-list').get_child_by_name('portset-info')
if int(portset_get_info.get_child_content('portset-port-total')) > 0:
ports = portset_get_info.get_child_by_name('portset-port-info')
portset_info['ports'] = [port.get_content() for port in ports.get_children()]
else:
portset_info['ports'] = []
return portset_info
return None
def create_portset(self):
"""
Create a portset
"""
if self.parameters.get('type') is None:
self.module.fail_json(msg='Error: Missing required parameter for create (type)')
portset_info = netapp_utils.zapi.NaElement("portset-create")
portset_info.add_new_child("portset-name", self.parameters['name'])
portset_info.add_new_child("portset-type", self.parameters['type'])
try:
self.server.invoke_successfully(
portset_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error creating portset %s: %s" %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_portset(self):
"""
Delete a portset
"""
portset_info = netapp_utils.zapi.NaElement("portset-destroy")
portset_info.add_new_child("portset-name", self.parameters['name'])
if self.parameters.get('force'):
portset_info.add_new_child("force", str(self.parameters['force']))
try:
self.server.invoke_successfully(
portset_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error deleting portset %s: %s" %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def remove_ports(self, ports):
"""
Removes all existing ports from portset
:return: None
"""
for port in ports:
self.modify_port(port, 'portset-remove', 'removing')
def add_ports(self):
"""
Add the list of ports to portset
:return: None
"""
# don't add if ports is empty string
if self.parameters.get('ports') == [''] or self.parameters.get('ports') is None:
return
for port in self.parameters['ports']:
self.modify_port(port, 'portset-add', 'adding')
def modify_port(self, port, zapi, action):
"""
Add or remove an port to/from a portset
"""
port.strip() # remove leading spaces if any (eg: if user types a space after comma in initiators list)
options = {'portset-name': self.parameters['name'],
'portset-port-name': port}
portset_modify = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)
try:
self.server.invoke_successfully(portset_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error %s port in portset %s: %s' % (action, self.parameters['name'],
to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Applies action from playbook
"""
netapp_utils.ems_log_event("na_ontap_autosupport", self.server)
current, modify = self.portset_get(), None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_portset()
self.add_ports()
elif cd_action == 'delete':
self.delete_portset()
elif modify:
self.remove_ports(current['ports'])
self.add_ports()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Execute action from playbook
"""
portset_obj = NetAppONTAPPortset()
portset_obj.apply()
if __name__ == '__main__':
main()

@ -1,335 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_qos_adaptive_policy_group
short_description: NetApp ONTAP Adaptive Quality of Service policy group.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: NetApp Ansible Team (@joshedmonds) <ng-ansibleteam@netapp.com>
description:
- Create, destroy, modify, or rename an Adaptive QoS policy group on NetApp ONTAP. Module is based on the standard QoS policy group module.
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified policy group should exist or not.
default: 'present'
type: str
name:
description:
- The name of the policy group to manage.
type: str
required: true
vserver:
description:
- Name of the vserver to use.
type: str
required: true
from_name:
description:
- Name of the existing policy group to be renamed to name.
type: str
absolute_min_iops:
description:
- Absolute minimum IOPS defined by this policy.
type: str
expected_iops:
description:
- Minimum expected IOPS defined by this policy.
type: str
peak_iops:
description:
- Maximum possible IOPS per allocated or used TB|GB.
type: str
peak_iops_allocation:
choices: ['allocated_space', 'used_space']
description:
- Whether peak_iops is specified by allocated or used space.
default: 'used_space'
type: str
force:
type: bool
default: False
description:
- Setting to 'true' forces the deletion of the workloads associated with the policy group along with the policy group.
'''
EXAMPLES = """
- name: create adaptive qos policy group
na_ontap_qos_adaptive_policy_group:
state: present
name: aq_policy_1
vserver: policy_vserver
absolute_min_iops: 70IOPS
expected_iops: 100IOPS/TB
peak_iops: 250IOPS/TB
peak_iops_allocation: allocated_space
hostname: 10.193.78.30
username: admin
password: netapp1!
- name: modify adaptive qos policy group expected iops
na_ontap_qos_adaptive_policy_group:
state: present
name: aq_policy_1
vserver: policy_vserver
absolute_min_iops: 70IOPS
expected_iops: 125IOPS/TB
peak_iops: 250IOPS/TB
peak_iops_allocation: allocated_space
hostname: 10.193.78.30
username: admin
password: netapp1!
- name: modify adaptive qos policy group peak iops allocation
na_ontap_qos_adaptive_policy_group:
state: present
name: aq_policy_1
vserver: policy_vserver
absolute_min_iops: 70IOPS
expected_iops: 125IOPS/TB
peak_iops: 250IOPS/TB
peak_iops_allocation: used_space
hostname: 10.193.78.30
username: admin
password: netapp1!
- name: delete qos policy group
na_ontap_qos_adaptive_policy_group:
state: absent
name: aq_policy_1
vserver: policy_vserver
hostname: 10.193.78.30
username: admin
password: netapp1!
"""
RETURN = """
"""
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapAdaptiveQosPolicyGroup(object):
"""
Create, delete, modify and rename a policy group.
"""
def __init__(self):
"""
Initialize the Ontap qos policy group class.
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
vserver=dict(required=True, type='str'),
absolute_min_iops=dict(required=False, type='str'),
expected_iops=dict(required=False, type='str'),
peak_iops=dict(required=False, type='str'),
peak_iops_allocation=dict(choices=['allocated_space', 'used_space'], default='used_space'),
force=dict(required=False, type='bool', default=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module)
def get_policy_group(self, policy_group_name=None):
"""
Return details of a policy group.
:param policy_group_name: policy group name
:return: policy group details.
:rtype: dict.
"""
if policy_group_name is None:
policy_group_name = self.parameters['name']
policy_group_get_iter = netapp_utils.zapi.NaElement('qos-adaptive-policy-group-get-iter')
policy_group_info = netapp_utils.zapi.NaElement('qos-adaptive-policy-group-info')
policy_group_info.add_new_child('policy-group', policy_group_name)
policy_group_info.add_new_child('vserver', self.parameters['vserver'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(policy_group_info)
policy_group_get_iter.add_child_elem(query)
result = self.server.invoke_successfully(policy_group_get_iter, True)
policy_group_detail = None
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) == 1:
policy_info = result.get_child_by_name('attributes-list').get_child_by_name('qos-adaptive-policy-group-info')
policy_group_detail = {
'name': policy_info.get_child_content('policy-group'),
'vserver': policy_info.get_child_content('vserver'),
'absolute_min_iops': policy_info.get_child_content('absolute-min-iops'),
'expected_iops': policy_info.get_child_content('expected-iops'),
'peak_iops': policy_info.get_child_content('peak-iops'),
'peak_iops_allocation': policy_info.get_child_content('peak-iops-allocation')
}
return policy_group_detail
def create_policy_group(self):
"""
create a policy group name.
"""
policy_group = netapp_utils.zapi.NaElement('qos-adaptive-policy-group-create')
policy_group.add_new_child('policy-group', self.parameters['name'])
policy_group.add_new_child('vserver', self.parameters['vserver'])
if self.parameters.get('absolute_min_iops'):
policy_group.add_new_child('absolute-min-iops', self.parameters['absolute_min_iops'])
if self.parameters.get('expected_iops'):
policy_group.add_new_child('expected-iops', self.parameters['expected_iops'])
if self.parameters.get('peak_iops'):
policy_group.add_new_child('peak-iops', self.parameters['peak_iops'])
if self.parameters.get('peak_iops_allocation'):
policy_group.add_new_child('peak-iops-allocation', self.parameters['peak_iops_allocation'])
try:
self.server.invoke_successfully(policy_group, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating adaptive qos policy group %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_policy_group(self, policy_group=None):
"""
delete an existing policy group.
:param policy_group: policy group name.
"""
if policy_group is None:
policy_group = self.parameters['name']
policy_group_obj = netapp_utils.zapi.NaElement('qos-adaptive-policy-group-delete')
policy_group_obj.add_new_child('policy-group', policy_group)
if self.parameters.get('force'):
policy_group_obj.add_new_child('force', str(self.parameters['force']))
try:
self.server.invoke_successfully(policy_group_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting adaptive qos policy group %s: %s' %
(policy_group, to_native(error)),
exception=traceback.format_exc())
def modify_policy_group(self):
"""
Modify policy group.
"""
policy_group_obj = netapp_utils.zapi.NaElement('qos-adaptive-policy-group-modify')
policy_group_obj.add_new_child('policy-group', self.parameters['name'])
if self.parameters.get('absolute_min_iops'):
policy_group_obj.add_new_child('absolute-min-iops', self.parameters['absolute_min_iops'])
if self.parameters.get('expected_iops'):
policy_group_obj.add_new_child('expected-iops', self.parameters['expected_iops'])
if self.parameters.get('peak_iops'):
policy_group_obj.add_new_child('peak-iops', self.parameters['peak_iops'])
if self.parameters.get('peak_iops_allocation'):
policy_group_obj.add_new_child('peak-iops-allocation', self.parameters['peak_iops_allocation'])
try:
self.server.invoke_successfully(policy_group_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying adaptive qos policy group %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def rename_policy_group(self):
"""
Rename policy group name.
"""
rename_obj = netapp_utils.zapi.NaElement('qos-adaptive-policy-group-rename')
rename_obj.add_new_child('new-name', self.parameters['name'])
rename_obj.add_new_child('policy-group-name', self.parameters['from_name'])
try:
self.server.invoke_successfully(rename_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error renaming adaptive qos policy group %s: %s' %
(self.parameters['from_name'], to_native(error)),
exception=traceback.format_exc())
def modify_helper(self, modify):
"""
helper method to modify policy group.
:param modify: modified attributes.
"""
for attribute in modify.keys():
if attribute in ['absolute_min_iops', 'expected_iops', 'peak_iops', 'peak_iops_allocation']:
self.modify_policy_group()
def apply(self):
"""
Run module based on playbook
"""
self.autosupport_log("na_ontap_qos_policy_group")
current = self.get_policy_group()
rename, cd_action = None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(self.get_policy_group(self.parameters['from_name']), current)
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_policy_group()
if cd_action == 'create':
self.create_policy_group()
elif cd_action == 'delete':
self.delete_policy_group()
elif modify:
self.modify_helper(modify)
self.module.exit_json(changed=self.na_helper.changed)
def autosupport_log(self, event_name):
"""
Create a log event against the provided vserver
"""
server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
netapp_utils.ems_log_event(event_name, server)
def main():
'''Apply vserver operations from playbook'''
qos_policy_group = NetAppOntapAdaptiveQosPolicyGroup()
qos_policy_group.apply()
if __name__ == '__main__':
main()

@ -1,290 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_qos_policy_group
short_description: NetApp ONTAP manage policy group in Quality of Service.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create, destroy, modify, or rename QoS policy group on NetApp ONTAP.
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified policy group should exist or not.
default: 'present'
name:
description:
- The name of the policy group to manage.
vserver:
description:
- Name of the vserver to use.
from_name:
description:
- Name of the existing policy group to be renamed to name.
max_throughput:
description:
- Maximum throughput defined by this policy.
min_throughput:
description:
- Minimum throughput defined by this policy.
force:
type: bool
default: False
description:
- Setting to 'true' forces the deletion of the workloads associated with the policy group along with the policy group.
'''
EXAMPLES = """
- name: create qos policy group
na_ontap_qos_policy_group:
state: present
name: policy_1
vserver: policy_vserver
max_throughput: 800KB/s,800iops
min_throughput: 100iops
hostname: 10.193.78.30
username: admin
password: netapp1!
- name: modify qos policy group max throughput
na_ontap_qos_policy_group:
state: present
name: policy_1
vserver: policy_vserver
max_throughput: 900KB/s,800iops
min_throughput: 100iops
hostname: 10.193.78.30
username: admin
password: netapp1!
- name: delete qos policy group
na_ontap_qos_policy_group:
state: absent
name: policy_1
vserver: policy_vserver
hostname: 10.193.78.30
username: admin
password: netapp1!
"""
RETURN = """
"""
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapQosPolicyGroup(object):
"""
Create, delete, modify and rename a policy group.
"""
def __init__(self):
"""
Initialize the Ontap qos policy group class.
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
vserver=dict(required=True, type='str'),
max_throughput=dict(required=False, type='str'),
min_throughput=dict(required=False, type='str'),
force=dict(required=False, type='bool', default=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module)
def get_policy_group(self, policy_group_name=None):
"""
Return details of a policy group.
:param policy_group_name: policy group name
:return: policy group details.
:rtype: dict.
"""
if policy_group_name is None:
policy_group_name = self.parameters['name']
policy_group_get_iter = netapp_utils.zapi.NaElement('qos-policy-group-get-iter')
policy_group_info = netapp_utils.zapi.NaElement('qos-policy-group-info')
policy_group_info.add_new_child('policy-group', policy_group_name)
policy_group_info.add_new_child('vserver', self.parameters['vserver'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(policy_group_info)
policy_group_get_iter.add_child_elem(query)
result = self.server.invoke_successfully(policy_group_get_iter, True)
policy_group_detail = None
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) == 1:
policy_info = result.get_child_by_name('attributes-list').get_child_by_name('qos-policy-group-info')
policy_group_detail = {
'name': policy_info.get_child_content('policy-group'),
'vserver': policy_info.get_child_content('vserver'),
'max_throughput': policy_info.get_child_content('max-throughput'),
'min_throughput': policy_info.get_child_content('min-throughput')
}
return policy_group_detail
def create_policy_group(self):
"""
create a policy group name.
"""
policy_group = netapp_utils.zapi.NaElement('qos-policy-group-create')
policy_group.add_new_child('policy-group', self.parameters['name'])
policy_group.add_new_child('vserver', self.parameters['vserver'])
if self.parameters.get('max_throughput'):
policy_group.add_new_child('max-throughput', self.parameters['max_throughput'])
if self.parameters.get('min_throughput'):
policy_group.add_new_child('min-throughput', self.parameters['min_throughput'])
try:
self.server.invoke_successfully(policy_group, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating qos policy group %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_policy_group(self, policy_group=None):
"""
delete an existing policy group.
:param policy_group: policy group name.
"""
if policy_group is None:
policy_group = self.parameters['name']
policy_group_obj = netapp_utils.zapi.NaElement('qos-policy-group-delete')
policy_group_obj.add_new_child('policy-group', policy_group)
if self.parameters.get('force'):
policy_group_obj.add_new_child('force', str(self.parameters['force']))
try:
self.server.invoke_successfully(policy_group_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting qos policy group %s: %s' %
(policy_group, to_native(error)),
exception=traceback.format_exc())
def modify_policy_group(self):
"""
Modify policy group.
"""
policy_group_obj = netapp_utils.zapi.NaElement('qos-policy-group-modify')
policy_group_obj.add_new_child('policy-group', self.parameters['name'])
if self.parameters.get('max_throughput'):
policy_group_obj.add_new_child('max-throughput', self.parameters['max_throughput'])
if self.parameters.get('min_throughput'):
policy_group_obj.add_new_child('min-throughput', self.parameters['min_throughput'])
try:
self.server.invoke_successfully(policy_group_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying qos policy group %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def rename_policy_group(self):
"""
Rename policy group name.
"""
rename_obj = netapp_utils.zapi.NaElement('qos-policy-group-rename')
rename_obj.add_new_child('new-name', self.parameters['name'])
rename_obj.add_new_child('policy-group-name', self.parameters['from_name'])
try:
self.server.invoke_successfully(rename_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error renaming qos policy group %s: %s' %
(self.parameters['from_name'], to_native(error)),
exception=traceback.format_exc())
def modify_helper(self, modify):
"""
helper method to modify policy group.
:param modify: modified attributes.
"""
for attribute in modify.keys():
if attribute in ['max_throughput', 'min_throughput']:
self.modify_policy_group()
def apply(self):
"""
Run module based on playbook
"""
self.asup_log_for_cserver("na_ontap_qos_policy_group")
current = self.get_policy_group()
rename, cd_action = None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(self.get_policy_group(self.parameters['from_name']), current)
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_policy_group()
if cd_action == 'create':
self.create_policy_group()
elif cd_action == 'delete':
self.delete_policy_group()
elif modify:
self.modify_helper(modify)
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
'''Apply vserver operations from playbook'''
qos_policy_group = NetAppOntapQosPolicyGroup()
qos_policy_group.apply()
if __name__ == '__main__':
main()

@ -1,303 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_qtree
short_description: NetApp ONTAP manage qtrees
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or destroy Qtrees.
options:
state:
description:
- Whether the specified qtree should exist or not.
choices: ['present', 'absent']
default: 'present'
name:
description:
- The name of the qtree to manage.
required: true
type: str
from_name:
description:
- Name of the qtree to be renamed.
version_added: '2.7'
type: str
flexvol_name:
description:
- The name of the FlexVol the qtree should exist on. Required when C(state=present).
required: true
type: str
vserver:
description:
- The name of the vserver to use.
required: true
type: str
export_policy:
description:
- The name of the export policy to apply.
version_added: '2.9'
type: str
security_style:
description:
- The security style for the qtree.
choices: ['unix', 'ntfs', 'mixed']
version_added: '2.9'
oplocks:
description:
- Whether the oplocks should be enabled or not for the qtree.
choices: ['enabled', 'disabled']
version_added: '2.9'
unix_permissions:
description:
- File permissions bits of the qtree.
version_added: '2.9'
type: str
'''
EXAMPLES = """
- name: Create Qtrees
na_ontap_qtree:
state: present
name: ansibleQTree
flexvol_name: ansibleVolume
export_policy: policyName
security_style: mixed
oplocks: disabled
unix_permissions:
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Rename Qtrees
na_ontap_qtree:
state: present
from_name: ansibleQTree_rename
name: ansibleQTree
flexvol_name: ansibleVolume
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapQTree(object):
'''Class with qtree operations'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False,
choices=['present', 'absent'],
default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
flexvol_name=dict(type='str'),
vserver=dict(required=True, type='str'),
export_policy=dict(required=False, type='str'),
security_style=dict(required=False, choices=['unix', 'ntfs', 'mixed']),
oplocks=dict(required=False, choices=['enabled', 'disabled']),
unix_permissions=dict(required=False, type='str'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['flexvol_name'])
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.parameters['vserver'])
def get_qtree(self, name=None):
"""
Checks if the qtree exists.
:param:
name : qtree name
:return:
Details about the qtree
False if qtree is not found
:rtype: bool
"""
if name is None:
name = self.parameters['name']
qtree_list_iter = netapp_utils.zapi.NaElement('qtree-list-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-info', **{'vserver': self.parameters['vserver'],
'volume': self.parameters['flexvol_name'],
'qtree': name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
qtree_list_iter.add_child_elem(query)
result = self.server.invoke_successfully(qtree_list_iter,
enable_tunneling=True)
return_q = None
if (result.get_child_by_name('num-records') and
int(result.get_child_content('num-records')) >= 1):
return_q = {'export_policy': result['attributes-list']['qtree-info']['export-policy'],
'unix_permissions': result['attributes-list']['qtree-info']['mode'],
'oplocks': result['attributes-list']['qtree-info']['oplocks'],
'security_style': result['attributes-list']['qtree-info']['security-style']}
return return_q
def create_qtree(self):
"""
Create a qtree
"""
options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']}
if self.parameters.get('export_policy'):
options['export-policy'] = self.parameters['export_policy']
if self.parameters.get('security_style'):
options['security-style'] = self.parameters['security_style']
if self.parameters.get('oplocks'):
options['oplocks'] = self.parameters['oplocks']
if self.parameters.get('unix_permissions'):
options['mode'] = self.parameters['unix_permissions']
qtree_create = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-create', **options)
try:
self.server.invoke_successfully(qtree_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error provisioning qtree %s: %s"
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_qtree(self):
"""
Delete a qtree
"""
path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
qtree_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-delete', **{'qtree': path})
try:
self.server.invoke_successfully(qtree_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error deleting qtree %s: %s" % (path, to_native(error)),
exception=traceback.format_exc())
def rename_qtree(self):
"""
Rename a qtree
"""
path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['from_name'])
new_path = '/vol/%s/%s' % (self.parameters['flexvol_name'], self.parameters['name'])
qtree_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-rename', **{'qtree': path,
'new-qtree-name': new_path})
try:
self.server.invoke_successfully(qtree_rename,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error renaming qtree %s: %s"
% (self.parameters['from_name'], to_native(error)),
exception=traceback.format_exc())
def modify_qtree(self):
"""
Modify a qtree
"""
options = {'qtree': self.parameters['name'], 'volume': self.parameters['flexvol_name']}
if self.parameters.get('export_policy'):
options['export-policy'] = self.parameters['export_policy']
if self.parameters.get('security_style'):
options['security-style'] = self.parameters['security_style']
if self.parameters.get('oplocks'):
options['oplocks'] = self.parameters['oplocks']
if self.parameters.get('unix_permissions'):
options['mode'] = self.parameters['unix_permissions']
qtree_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'qtree-modify', **options)
try:
self.server.invoke_successfully(qtree_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying qtree %s: %s'
% (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
'''Call create/delete/modify/rename operations'''
netapp_utils.ems_log_event("na_ontap_qtree", self.server)
current = self.get_qtree()
rename, cd_action, modify = None, None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(self.get_qtree(self.parameters['from_name']), current)
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_qtree()
if cd_action == 'create':
self.create_qtree()
elif cd_action == 'delete':
self.delete_qtree()
elif modify:
self.modify_qtree()
self.module.exit_json(changed=self.na_helper.changed)
def main():
'''Apply qtree operations from playbook'''
qtree_obj = NetAppOntapQTree()
qtree_obj.apply()
if __name__ == '__main__':
main()

@ -1,345 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_quotas
short_description: NetApp ONTAP Quotas
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Set/Modify/Delete quota on ONTAP
options:
state:
description:
- Whether the specified quota should exist or not.
choices: ['present', 'absent']
default: present
type: str
vserver:
required: true
description:
- Name of the vserver to use.
type: str
volume:
description:
- The name of the volume that the quota resides on.
required: true
type: str
quota_target:
description:
- The quota target of the type specified.
required: true
type: str
qtree:
description:
- Name of the qtree for the quota.
- For user or group rules, it can be the qtree name or "" if no qtree.
- For tree type rules, this field must be "".
default: ""
type: str
type:
description:
- The type of quota rule
choices: ['user', 'group', 'tree']
required: true
type: str
policy:
description:
- Name of the quota policy from which the quota rule should be obtained.
type: str
set_quota_status:
description:
- Whether the specified volume should have quota status on or off.
type: bool
file_limit:
description:
- The number of files that the target can have.
default: '-'
type: str
disk_limit:
description:
- The amount of disk space that is reserved for the target.
default: '-'
type: str
threshold:
description:
- The amount of disk space the target would have to exceed before a message is logged.
default: '-'
type: str
'''
EXAMPLES = """
- name: Add/Set quota
na_ontap_quotas:
state: present
vserver: ansible
volume: ansible
quota_target: /vol/ansible
type: user
policy: ansible
file_limit: 2
disk_limit: 3
set_quota_status: True
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: modify quota
na_ontap_quotas:
state: present
vserver: ansible
volume: ansible
quota_target: /vol/ansible
type: user
policy: ansible
file_limit: 2
disk_limit: 3
threshold: 3
set_quota_status: False
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete quota
na_ontap_quotas:
state: absent
vserver: ansible
volume: ansible
quota_target: /vol/ansible
type: user
policy: ansible
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPQuotas(object):
'''Class with quotas methods'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
volume=dict(required=True, type='str'),
quota_target=dict(required=True, type='str'),
qtree=dict(required=False, type='str', default=""),
type=dict(required=True, type='str', choices=['user', 'group', 'tree']),
policy=dict(required=False, type='str'),
set_quota_status=dict(required=False, type='bool'),
file_limit=dict(required=False, type='str', default='-'),
disk_limit=dict(required=False, type='str', default='-'),
threshold=dict(required=False, type='str', default='-')
))
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_quota_status(self):
"""
Return details about the quota status
:param:
name : volume name
:return: status of the quota. None if not found.
:rtype: dict
"""
quota_status_get = netapp_utils.zapi.NaElement('quota-status')
quota_status_get.translate_struct({
'volume': self.parameters['volume']
})
try:
result = self.server.invoke_successfully(quota_status_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching quotas status info: %s' % to_native(error),
exception=traceback.format_exc())
if result:
return result['status']
return None
def get_quotas(self):
"""
Get quota details
:return: name of volume if quota exists, None otherwise
"""
quota_get = netapp_utils.zapi.NaElement('quota-list-entries-iter')
query = {
'query': {
'quota-entry': {
'volume': self.parameters['volume'],
'quota-target': self.parameters['quota_target'],
'quota-type': self.parameters['type'],
'vserver': self.parameters['vserver']
}
}
}
quota_get.translate_struct(query)
if self.parameters.get('policy'):
quota_get['query']['quota-entry'].add_new_child('policy', self.parameters['policy'])
try:
result = self.server.invoke_successfully(quota_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching quotas info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
return_values = {'volume': result['attributes-list']['quota-entry']['volume'],
'file_limit': result['attributes-list']['quota-entry']['file-limit'],
'disk_limit': result['attributes-list']['quota-entry']['disk-limit'],
'threshold': result['attributes-list']['quota-entry']['threshold']}
return return_values
return None
def quota_entry_set(self):
"""
Adds a quota entry
"""
options = {'volume': self.parameters['volume'],
'quota-target': self.parameters['quota_target'],
'quota-type': self.parameters['type'],
'qtree': self.parameters['qtree'],
'file-limit': self.parameters['file_limit'],
'disk-limit': self.parameters['disk_limit'],
'threshold': self.parameters['threshold']}
if self.parameters.get('policy'):
options['policy'] = self.parameters['policy']
set_entry = netapp_utils.zapi.NaElement.create_node_with_children(
'quota-set-entry', **options)
try:
self.server.invoke_successfully(set_entry, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error adding/modifying quota entry %s: %s'
% (self.parameters['volume'], to_native(error)),
exception=traceback.format_exc())
def quota_entry_delete(self):
"""
Deletes a quota entry
"""
options = {'volume': self.parameters['volume'],
'quota-target': self.parameters['quota_target'],
'quota-type': self.parameters['type'],
'qtree': self.parameters['qtree']}
set_entry = netapp_utils.zapi.NaElement.create_node_with_children(
'quota-delete-entry', **options)
if self.parameters.get('policy'):
set_entry.add_new_child('policy', self.parameters['policy'])
try:
self.server.invoke_successfully(set_entry, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting quota entry %s: %s'
% (self.parameters['volume'], to_native(error)),
exception=traceback.format_exc())
def quota_entry_modify(self, modify_attrs):
"""
Modifies a quota entry
"""
options = {'volume': self.parameters['volume'],
'quota-target': self.parameters['quota_target'],
'quota-type': self.parameters['type'],
'qtree': self.parameters['qtree']}
options.update(modify_attrs)
if self.parameters.get('policy'):
options['policy'] = str(self.parameters['policy'])
modify_entry = netapp_utils.zapi.NaElement.create_node_with_children(
'quota-modify-entry', **options)
try:
self.server.invoke_successfully(modify_entry, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying quota entry %s: %s'
% (self.parameters['volume'], to_native(error)),
exception=traceback.format_exc())
def on_or_off_quota(self, status):
"""
on or off quota
"""
quota = netapp_utils.zapi.NaElement.create_node_with_children(
status, **{'volume': self.parameters['volume']})
try:
self.server.invoke_successfully(quota,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error setting %s for %s: %s'
% (status, self.parameters['volume'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to quotas
"""
netapp_utils.ems_log_event("na_ontap_quotas", self.server)
modify_quota_status = None
modify_quota = None
current = self.get_quotas()
if 'set_quota_status' in self.parameters:
quota_status = self.get_quota_status()
if quota_status is not None:
quota_status_action = self.na_helper.get_modified_attributes(
{'set_quota_status': True if quota_status == 'on' else False}, self.parameters)
if quota_status_action:
modify_quota_status = 'quota-on' if quota_status_action['set_quota_status'] else 'quota-off'
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None:
modify_quota = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.quota_entry_set()
elif cd_action == 'delete':
self.quota_entry_delete()
elif modify_quota is not None:
for key in list(modify_quota):
modify_quota[key.replace("_", "-")] = modify_quota.pop(key)
self.quota_entry_modify(modify_quota)
if modify_quota_status is not None:
self.on_or_off_quota(modify_quota_status)
self.module.exit_json(changed=self.na_helper.changed)
def main():
'''Execute action'''
quota_obj = NetAppONTAPQuotas()
quota_obj.apply()
if __name__ == '__main__':
main()

@ -1,229 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: na_ontap_security_key_manager
short_description: NetApp ONTAP security key manager.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Add or delete or setup key management on NetApp ONTAP.
options:
state:
description:
- Whether the specified key manager should exist or not.
choices: ['present', 'absent']
default: 'present'
ip_address:
description:
- The IP address of the key management server.
required: true
tcp_port:
description:
- The TCP port on which the key management server listens for incoming connections.
default: 5696
node:
description:
- The node which key management server runs on.
'''
EXAMPLES = """
- name: Delete Key Manager
tags:
- delete
na_ontap_security_key_manager:
state: absent
node: swenjun-vsim1
hostname: "{{ hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
https: False
ip_address: 0.0.0.0
- name: Add Key Manager
tags:
- add
na_ontap_security_key_manager:
state: present
node: swenjun-vsim1
hostname: "{{ hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
https: False
ip_address: 0.0.0.0
"""
RETURN = """
"""
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapSecurityKeyManager(object):
'''class with key manager operations'''
def __init__(self):
'''Initialize module parameters'''
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(
state=dict(required=False, choices=['present', 'absent'], default='present'),
ip_address=dict(required=True, type='str'),
node=dict(required=False, type='str'),
tcp_port=dict(required=False, type='int', default=5696)
)
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required"
)
else:
self.cluster = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_key_manager(self):
"""
get key manager by ip address.
:return: a dict of key manager
"""
key_manager_info = netapp_utils.zapi.NaElement('security-key-manager-get-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'key-manager-info', **{'key-manager-ip-address': self.parameters['ip_address']})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
key_manager_info.add_child_elem(query)
try:
result = self.cluster.invoke_successfully(key_manager_info, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching key manager %s : %s'
% (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
return_value = None
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
key_manager = result.get_child_by_name('attributes-list').get_child_by_name('key-manager-info')
return_value = {}
if key_manager.get_child_by_name('key-manager-ip-address'):
return_value['ip_address'] = key_manager.get_child_content('key-manager-ip-address')
if key_manager.get_child_by_name('key-manager-server-status'):
return_value['server_status'] = key_manager.get_child_content('key-manager-server-status')
if key_manager.get_child_by_name('key-manager-tcp-port'):
return_value['tcp_port'] = key_manager.get_child_content('key-manager-tcp-port')
if key_manager.get_child_by_name('node-name'):
return_value['node'] = key_manager.get_child_content('node-name')
return return_value
def key_manager_setup(self):
"""
set up external key manager.
"""
key_manager_setup = netapp_utils.zapi.NaElement('security-key-manager-setup')
# if specify on-boarding passphrase, it is on-boarding key management.
# it not, then it's external key management.
try:
self.cluster.invoke_successfully(key_manager_setup, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error setting up key manager %s : %s'
% (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
def create_key_manager(self):
"""
add key manager.
"""
key_manager_create = netapp_utils.zapi.NaElement('security-key-manager-add')
key_manager_create.add_new_child('key-manager-ip-address', self.parameters['ip_address'])
if self.parameters.get('tcp_port'):
key_manager_create.add_new_child('key-manager-tcp-port', str(self.parameters['tcp_port']))
try:
self.cluster.invoke_successfully(key_manager_create, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating key manager %s : %s'
% (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
def delete_key_manager(self):
"""
delete key manager.
"""
key_manager_delete = netapp_utils.zapi.NaElement('security-key-manager-delete')
key_manager_delete.add_new_child('key-manager-ip-address', self.parameters['ip_address'])
try:
self.cluster.invoke_successfully(key_manager_delete, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting key manager %s : %s'
% (self.parameters['node'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
self.asup_log_for_cserver("na_ontap_security_key_manager")
self.key_manager_setup()
current = self.get_key_manager()
cd_action = None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_key_manager()
elif cd_action == 'delete':
self.delete_key_manager()
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.cluster)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
'''Apply volume operations from playbook'''
obj = NetAppOntapSecurityKeyManager()
obj.apply()
if __name__ == '__main__':
main()

@ -1,284 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_service_processor_network
short_description: NetApp ONTAP service processor network
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Modify a ONTAP service processor network
options:
state:
description:
- Whether the specified service processor network should exist or not.
choices: ['present']
default: present
address_type:
description:
- Specify address class.
required: true
choices: ['ipv4', 'ipv6']
is_enabled:
description:
- Specify whether to enable or disable the service processor network.
required: true
type: bool
node:
description:
- The node where the service processor network should be enabled
required: true
dhcp:
description:
- Specify dhcp type.
choices: ['v4', 'none']
gateway_ip_address:
description:
- Specify the gateway ip.
ip_address:
description:
- Specify the service processor ip address.
netmask:
description:
- Specify the service processor netmask.
prefix_length:
description:
- Specify the service processor prefix_length.
wait_for_completion:
description:
- Set this parameter to 'true' for synchronous execution (wait until SP status is successfully updated)
- Set this parameter to 'false' for asynchronous execution
- For asynchronous, execution exits as soon as the request is sent, without checking SP status
type: bool
default: false
version_added: '2.8'
'''
EXAMPLES = """
- name: Modify Service Processor Network
na_ontap_service_processor_network:
state: present
address_type: ipv4
is_enabled: true
dhcp: v4
node: "{{ netapp_node }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
"""
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
import time
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapServiceProcessorNetwork(object):
"""
Modify a Service Processor Network
"""
def __init__(self):
"""
Initialize the NetAppOntapServiceProcessorNetwork class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present'], default='present'),
address_type=dict(required=True, choices=['ipv4', 'ipv6']),
is_enabled=dict(required=True, type='bool'),
node=dict(required=True, type='str'),
dhcp=dict(required=False, choices=['v4', 'none']),
gateway_ip_address=dict(required=False, type='str'),
ip_address=dict(required=False, type='str'),
netmask=dict(required=False, type='str'),
prefix_length=dict(required=False, type='int'),
wait_for_completion=dict(required=False, type='bool', default=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.set_playbook_zapi_key_map()
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=None)
return
def set_playbook_zapi_key_map(self):
self.na_helper.zapi_string_keys = {
'address_type': 'address-type',
'node': 'node',
'dhcp': 'dhcp',
'gateway_ip_address': 'gateway-ip-address',
'ip_address': 'ip-address',
'netmask': 'netmask'
}
self.na_helper.zapi_int_keys = {
'prefix_length': 'prefix-length'
}
self.na_helper.zapi_bool_keys = {
'is_enabled': 'is-enabled',
}
self.na_helper.zapi_required = {
'address_type': 'address-type',
'node': 'node',
'is_enabled': 'is-enabled'
}
def get_sp_network_status(self):
"""
Return status of service processor network
:param:
name : name of the node
:return: Status of the service processor network
:rtype: dict
"""
spn_get_iter = netapp_utils.zapi.NaElement('service-processor-network-get-iter')
query_info = {
'query': {
'service-processor-network-info': {
'node': self.parameters['node'],
'address-type': self.parameters['address_type']
}
}
}
spn_get_iter.translate_struct(query_info)
result = self.server.invoke_successfully(spn_get_iter, True)
if int(result['num-records']) >= 1:
sp_attr_info = result['attributes-list']['service-processor-network-info']
return sp_attr_info.get_child_content('setup-status')
return None
def get_service_processor_network(self):
"""
Return details about service processor network
:param:
name : name of the node
:return: Details about service processor network. None if not found.
:rtype: dict
"""
spn_get_iter = netapp_utils.zapi.NaElement('service-processor-network-get-iter')
query_info = {
'query': {
'service-processor-network-info': {
'node': self.parameters['node']
}
}
}
spn_get_iter.translate_struct(query_info)
result = self.server.invoke_successfully(spn_get_iter, True)
sp_details = None
# check if job exists
if int(result['num-records']) >= 1:
sp_details = dict()
sp_attr_info = result['attributes-list']['service-processor-network-info']
for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
sp_details[item_key] = sp_attr_info.get_child_content(zapi_key)
for item_key, zapi_key in self.na_helper.zapi_bool_keys.items():
sp_details[item_key] = self.na_helper.get_value_for_bool(from_zapi=True,
value=sp_attr_info.get_child_content(zapi_key))
for item_key, zapi_key in self.na_helper.zapi_int_keys.items():
sp_details[item_key] = self.na_helper.get_value_for_int(from_zapi=True,
value=sp_attr_info.get_child_content(zapi_key))
return sp_details
def modify_service_processor_network(self, params=None):
"""
Modify a service processor network.
:param params: A dict of modified options.
When dhcp is not set to v4, ip_address, netmask, and gateway_ip_address must be specified even if remains the same.
"""
if self.parameters['is_enabled'] is False:
if params.get('is_enabled') and len(params) > 1:
self.module.fail_json(msg='Error: Cannot modify any other parameter for a service processor network if option "is_enabled" is set to false.')
elif params.get('is_enabled') is None and len(params) > 0:
self.module.fail_json(msg='Error: Cannot modify a service processor network if it is disabled.')
sp_modify = netapp_utils.zapi.NaElement('service-processor-network-modify')
sp_modify.add_new_child("node", self.parameters['node'])
sp_modify.add_new_child("address-type", self.parameters['address_type'])
sp_attributes = dict()
for item_key in self.parameters:
if item_key in self.na_helper.zapi_string_keys:
zapi_key = self.na_helper.zapi_string_keys.get(item_key)
sp_attributes[zapi_key] = self.parameters[item_key]
elif item_key in self.na_helper.zapi_bool_keys:
zapi_key = self.na_helper.zapi_bool_keys.get(item_key)
sp_attributes[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False, value=self.parameters[item_key])
elif item_key in self.na_helper.zapi_int_keys:
zapi_key = self.na_helper.zapi_int_keys.get(item_key)
sp_attributes[zapi_key] = self.na_helper.get_value_for_int(from_zapi=False, value=self.parameters[item_key])
sp_modify.translate_struct(sp_attributes)
try:
self.server.invoke_successfully(sp_modify, enable_tunneling=True)
if self.parameters.get('wait_for_completion'):
retries = 10
while self.get_sp_network_status() == 'in_progress' and retries > 0:
time.sleep(10)
retries = retries - 1
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying service processor network: %s' % (to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_service_processor_network", cserver)
def apply(self):
"""
Run Module based on play book
"""
self.autosupport_log()
current = self.get_service_processor_network()
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if not current:
self.module.fail_json(msg='Error No Service Processor for node: %s' % self.parameters['node'])
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
self.modify_service_processor_network(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Create the NetApp Ontap Service Processor Network Object and modify it
"""
obj = NetAppOntapServiceProcessorNetwork()
obj.apply()
if __name__ == '__main__':
main()

@ -1,716 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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 = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete/Initialize SnapMirror volume/vserver relationships for ONTAP/ONTAP
- Create/Delete/Initialize SnapMirror volume relationship between ElementSW and ONTAP
- Modify schedule for a SnapMirror relationship for ONTAP/ONTAP and ElementSW/ONTAP
- Pre-requisite for ElementSW to ONTAP relationship or vice-versa is an established SnapMirror endpoint for ONTAP cluster with ElementSW UI
- Pre-requisite for ElementSW to ONTAP relationship or vice-versa is to have SnapMirror enabled in the ElementSW volume
- For creating a SnapMirror ElementSW/ONTAP relationship, an existing ONTAP/ElementSW relationship should be present
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_snapmirror
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified relationship should exist or not.
default: present
source_volume:
description:
- Specifies the name of the source volume for the SnapMirror.
destination_volume:
description:
- Specifies the name of the destination volume for the SnapMirror.
source_vserver:
description:
- Name of the source vserver for the SnapMirror.
destination_vserver:
description:
- Name of the destination vserver for the SnapMirror.
source_path:
description:
- Specifies the source endpoint of the SnapMirror relationship.
- If the source is an ONTAP volume, format should be <[vserver:][volume]> or <[[cluster:]//vserver/]volume>
- If the source is an ElementSW volume, format should be <[Element_SVIP]:/lun/[Element_VOLUME_ID]>
- If the source is an ElementSW volume, the volume should have SnapMirror enabled.
destination_path:
description:
- Specifies the destination endpoint of the SnapMirror relationship.
relationship_type:
choices: ['data_protection', 'load_sharing', 'vault', 'restore', 'transition_data_protection',
'extended_data_protection']
description:
- Specify the type of SnapMirror relationship.
schedule:
description:
- Specify the name of the current schedule, which is used to update the SnapMirror relationship.
- Optional for create, modifiable.
policy:
description:
- Specify the name of the SnapMirror policy that applies to this relationship.
version_added: "2.8"
source_hostname:
description:
- Source hostname or management IP address for ONTAP or ElementSW cluster.
- Required for SnapMirror delete
source_username:
description:
- Source username for ONTAP or ElementSW cluster.
- Optional if this is same as destination username.
source_password:
description:
- Source password for ONTAP or ElementSW cluster.
- Optional if this is same as destination password.
connection_type:
description:
- Type of SnapMirror relationship.
- Pre-requisite for either elementsw_ontap or ontap_elementsw the ElementSW volume should have enableSnapmirror option set to true.
- For using ontap_elementsw, elementsw_ontap snapmirror relationship should exist.
choices: ['ontap_ontap', 'elementsw_ontap', 'ontap_elementsw']
default: ontap_ontap
version_added: '2.9'
max_transfer_rate:
description:
- Specifies the upper bound, in kilobytes per second, at which data is transferred.
- Default is unlimited, it can be explicitly set to 0 as unlimited.
type: int
version_added: '2.9'
identity_preserve:
description:
- Specifies whether or not the identity of the source Vserver is replicated to the destination Vserver.
- If this parameter is set to true, the source Vserver's configuration will additionally be replicated to the destination.
- If the parameter is set to false, then only the source Vserver's volumes and RBAC configuration are replicated to the destination.
type: bool
version_added: '2.9'
short_description: "NetApp ONTAP or ElementSW Manage SnapMirror"
version_added: "2.7"
'''
EXAMPLES = """
# creates and initializes the snapmirror
- name: Create ONTAP/ONTAP SnapMirror
na_ontap_snapmirror:
state: present
source_volume: test_src
destination_volume: test_dest
source_vserver: ansible_src
destination_vserver: ansible_dest
schedule: hourly
policy: MirrorAllSnapshots
max_transfer_rate: 1000
hostname: "{{ destination_cluster_hostname }}"
username: "{{ destination_cluster_username }}"
password: "{{ destination_cluster_password }}"
# creates and initializes the snapmirror between vservers
- name: Create ONTAP/ONTAP vserver SnapMirror
na_ontap_snapmirror:
state: present
source_vserver: ansible_src
destination_vserver: ansible_dest
identity_preserve: true
hostname: "{{ destination_cluster_hostname }}"
username: "{{ destination_cluster_username }}"
password: "{{ destination_cluster_password }}"
# existing snapmirror relation with status 'snapmirrored' will be initialized
- name: Initialize ONTAP/ONTAP SnapMirror
na_ontap_snapmirror:
state: present
source_path: 'ansible:test'
destination_path: 'ansible:dest'
hostname: "{{ destination_cluster_hostname }}"
username: "{{ destination_cluster_username }}"
password: "{{ destination_cluster_password }}"
- name: Delete SnapMirror
na_ontap_snapmirror:
state: absent
destination_path: <path>
source_hostname: "{{ source_hostname }}"
hostname: "{{ destination_cluster_hostname }}"
username: "{{ destination_cluster_username }}"
password: "{{ destination_cluster_password }}"
- name: Set schedule to NULL
na_ontap_snapmirror:
state: present
destination_path: <path>
schedule: ""
hostname: "{{ destination_cluster_hostname }}"
username: "{{ destination_cluster_username }}"
password: "{{ destination_cluster_password }}"
- name: Create SnapMirror from ElementSW to ONTAP
na_ontap_snapmirror:
state: present
connection_type: elementsw_ontap
source_path: '10.10.10.10:/lun/300'
destination_path: 'ansible_test:ansible_dest_vol'
schedule: hourly
policy: MirrorLatest
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
source_hostname: " {{ Element_cluster_mvip }}"
source_username: "{{ Element_cluster_username }}"
source_password: "{{ Element_cluster_password }}"
- name: Create SnapMirror from ONTAP to ElementSW
na_ontap_snapmirror:
state: present
connection_type: ontap_elementsw
destination_path: '10.10.10.10:/lun/300'
source_path: 'ansible_test:ansible_dest_vol'
policy: MirrorLatest
hostname: "{{ Element_cluster_mvip }}"
username: "{{ Element_cluster_username }}"
password: "{{ Element_cluster_password }}"
source_hostname: " {{ netapp_hostname }}"
source_username: "{{ netapp_username }}"
source_password: "{{ netapp_password }}"
"""
RETURN = """
"""
import re
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
HAS_SF_SDK = netapp_utils.has_sf_sdk()
try:
import solidfire.common
except ImportError:
HAS_SF_SDK = False
class NetAppONTAPSnapmirror(object):
"""
Class with Snapmirror methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
source_vserver=dict(required=False, type='str'),
destination_vserver=dict(required=False, type='str'),
source_volume=dict(required=False, type='str'),
destination_volume=dict(required=False, type='str'),
source_path=dict(required=False, type='str'),
destination_path=dict(required=False, type='str'),
schedule=dict(required=False, type='str'),
policy=dict(required=False, type='str'),
relationship_type=dict(required=False, type='str',
choices=['data_protection', 'load_sharing',
'vault', 'restore',
'transition_data_protection',
'extended_data_protection']
),
source_hostname=dict(required=False, type='str'),
connection_type=dict(required=False, type='str',
choices=['ontap_ontap', 'elementsw_ontap', 'ontap_elementsw'],
default='ontap_ontap'),
source_username=dict(required=False, type='str'),
source_password=dict(required=False, type='str', no_log=True),
max_transfer_rate=dict(required=False, type='int'),
identity_preserve=dict(required=False, type='bool')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_together=(['source_volume', 'destination_volume'],
['source_vserver', 'destination_vserver']),
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
# setup later if required
self.source_server = None
# only for ElementSW -> ONTAP snapmirroring, validate if ElementSW SDK is available
if self.parameters.get('connection_type') in ['elementsw_ontap', 'ontap_elementsw']:
if HAS_SF_SDK is False:
self.module.fail_json(msg="Unable to import the SolidFire Python SDK")
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
if self.parameters.get('connection_type') != 'ontap_elementsw':
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
else:
if self.parameters.get('source_username'):
self.module.params['username'] = self.parameters['source_username']
if self.parameters.get('source_password'):
self.module.params['password'] = self.parameters['source_password']
self.module.params['hostname'] = self.parameters['source_hostname']
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def set_element_connection(self, kind):
if kind == 'source':
self.module.params['hostname'] = self.parameters['source_hostname']
self.module.params['username'] = self.parameters['source_username']
self.module.params['password'] = self.parameters['source_password']
elif kind == 'destination':
self.module.params['hostname'] = self.parameters['hostname']
self.module.params['username'] = self.parameters['username']
self.module.params['password'] = self.parameters['password']
elem = netapp_utils.create_sf_connection(module=self.module)
elementsw_helper = NaElementSWModule(elem)
return elementsw_helper, elem
def snapmirror_get_iter(self, destination=None):
"""
Compose NaElement object to query current SnapMirror relations using destination-path
SnapMirror relation for a destination path is unique
:return: NaElement object for SnapMirror-get-iter
"""
snapmirror_get_iter = netapp_utils.zapi.NaElement('snapmirror-get-iter')
query = netapp_utils.zapi.NaElement('query')
snapmirror_info = netapp_utils.zapi.NaElement('snapmirror-info')
if destination is None:
destination = self.parameters['destination_path']
snapmirror_info.add_new_child('destination-location', destination)
query.add_child_elem(snapmirror_info)
snapmirror_get_iter.add_child_elem(query)
return snapmirror_get_iter
def snapmirror_get(self, destination=None):
"""
Get current SnapMirror relations
:return: Dictionary of current SnapMirror details if query successful, else None
"""
snapmirror_get_iter = self.snapmirror_get_iter(destination)
snap_info = dict()
try:
result = self.server.invoke_successfully(snapmirror_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching snapmirror info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) > 0:
snapmirror_info = result.get_child_by_name('attributes-list').get_child_by_name(
'snapmirror-info')
snap_info['mirror_state'] = snapmirror_info.get_child_content('mirror-state')
snap_info['status'] = snapmirror_info.get_child_content('relationship-status')
snap_info['schedule'] = snapmirror_info.get_child_content('schedule')
snap_info['policy'] = snapmirror_info.get_child_content('policy')
snap_info['relationship'] = snapmirror_info.get_child_content('relationship-type')
if snapmirror_info.get_child_by_name('max-transfer-rate'):
snap_info['max_transfer_rate'] = int(snapmirror_info.get_child_content('max-transfer-rate'))
if snap_info['schedule'] is None:
snap_info['schedule'] = ""
return snap_info
return None
def check_if_remote_volume_exists(self):
"""
Validate existence of source volume
:return: True if volume exists, False otherwise
"""
self.set_source_cluster_connection()
# do a get volume to check if volume exists or not
volume_info = netapp_utils.zapi.NaElement('volume-get-iter')
volume_attributes = netapp_utils.zapi.NaElement('volume-attributes')
volume_id_attributes = netapp_utils.zapi.NaElement('volume-id-attributes')
volume_id_attributes.add_new_child('name', self.parameters['source_volume'])
# if source_volume is present, then source_vserver is also guaranteed to be present
volume_id_attributes.add_new_child('vserver-name', self.parameters['source_vserver'])
volume_attributes.add_child_elem(volume_id_attributes)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(volume_attributes)
volume_info.add_child_elem(query)
try:
result = self.source_server.invoke_successfully(volume_info, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching source volume details %s : %s'
% (self.parameters['source_volume'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
return True
return False
def snapmirror_create(self):
"""
Create a SnapMirror relationship
"""
if self.parameters.get('source_hostname') and self.parameters.get('source_volume'):
if not self.check_if_remote_volume_exists():
self.module.fail_json(msg='Source volume does not exist. Please specify a volume that exists')
options = {'source-location': self.parameters['source_path'],
'destination-location': self.parameters['destination_path']}
snapmirror_create = netapp_utils.zapi.NaElement.create_node_with_children('snapmirror-create', **options)
if self.parameters.get('relationship_type'):
snapmirror_create.add_new_child('relationship-type', self.parameters['relationship_type'])
if self.parameters.get('schedule'):
snapmirror_create.add_new_child('schedule', self.parameters['schedule'])
if self.parameters.get('policy'):
snapmirror_create.add_new_child('policy', self.parameters['policy'])
if self.parameters.get('max_transfer_rate'):
snapmirror_create.add_new_child('max-transfer-rate', str(self.parameters['max_transfer_rate']))
if self.parameters.get('identity_preserve'):
snapmirror_create.add_new_child('identity-preserve', str(self.parameters['identity_preserve']))
try:
self.server.invoke_successfully(snapmirror_create, enable_tunneling=True)
self.snapmirror_initialize()
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating SnapMirror %s' % to_native(error),
exception=traceback.format_exc())
def set_source_cluster_connection(self):
"""
Setup ontap ZAPI server connection for source hostname
:return: None
"""
if self.parameters.get('source_username'):
self.module.params['username'] = self.parameters['source_username']
if self.parameters.get('source_password'):
self.module.params['password'] = self.parameters['source_password']
self.module.params['hostname'] = self.parameters['source_hostname']
self.source_server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def delete_snapmirror(self, is_hci, relationship_type):
"""
Delete a SnapMirror relationship
#1. Quiesce the SnapMirror relationship at destination
#2. Break the SnapMirror relationship at the destination
#3. Release the SnapMirror at source
#4. Delete SnapMirror at destination
"""
if not is_hci:
if not self.parameters.get('source_hostname'):
self.module.fail_json(msg='Missing parameters for delete: Please specify the '
'source cluster hostname to release the SnapMirror relation')
# Quiesce at destination
self.snapmirror_quiesce()
# Break at destination
if relationship_type not in ['load_sharing', 'vault']:
self.snapmirror_break()
# if source is ONTAP, release the destination at source cluster
if not is_hci:
self.set_source_cluster_connection()
if self.get_destination():
# Release at source
self.snapmirror_release()
# Delete at destination
self.snapmirror_delete()
def snapmirror_quiesce(self):
"""
Quiesce SnapMirror relationship - disable all future transfers to this destination
"""
options = {'destination-location': self.parameters['destination_path']}
snapmirror_quiesce = netapp_utils.zapi.NaElement.create_node_with_children(
'snapmirror-quiesce', **options)
try:
self.server.invoke_successfully(snapmirror_quiesce,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error Quiescing SnapMirror : %s'
% (to_native(error)),
exception=traceback.format_exc())
def snapmirror_delete(self):
"""
Delete SnapMirror relationship at destination cluster
"""
options = {'destination-location': self.parameters['destination_path']}
snapmirror_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'snapmirror-destroy', **options)
try:
self.server.invoke_successfully(snapmirror_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting SnapMirror : %s'
% (to_native(error)),
exception=traceback.format_exc())
def snapmirror_break(self, destination=None):
"""
Break SnapMirror relationship at destination cluster
"""
if destination is None:
destination = self.parameters['destination_path']
options = {'destination-location': destination}
snapmirror_break = netapp_utils.zapi.NaElement.create_node_with_children(
'snapmirror-break', **options)
try:
self.server.invoke_successfully(snapmirror_break,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error breaking SnapMirror relationship : %s'
% (to_native(error)),
exception=traceback.format_exc())
def snapmirror_release(self):
"""
Release SnapMirror relationship from source cluster
"""
options = {'destination-location': self.parameters['destination_path']}
snapmirror_release = netapp_utils.zapi.NaElement.create_node_with_children(
'snapmirror-release', **options)
try:
self.source_server.invoke_successfully(snapmirror_release,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error releasing SnapMirror relationship : %s'
% (to_native(error)),
exception=traceback.format_exc())
def snapmirror_abort(self):
"""
Abort a SnapMirror relationship in progress
"""
options = {'destination-location': self.parameters['destination_path']}
snapmirror_abort = netapp_utils.zapi.NaElement.create_node_with_children(
'snapmirror-abort', **options)
try:
self.server.invoke_successfully(snapmirror_abort,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error aborting SnapMirror relationship : %s'
% (to_native(error)),
exception=traceback.format_exc())
def snapmirror_initialize(self):
"""
Initialize SnapMirror based on relationship type
"""
current = self.snapmirror_get()
if current['mirror_state'] != 'snapmirrored':
initialize_zapi = 'snapmirror-initialize'
if self.parameters.get('relationship_type') and self.parameters['relationship_type'] == 'load_sharing':
initialize_zapi = 'snapmirror-initialize-ls-set'
options = {'source-location': self.parameters['source_path']}
else:
options = {'destination-location': self.parameters['destination_path']}
snapmirror_init = netapp_utils.zapi.NaElement.create_node_with_children(
initialize_zapi, **options)
try:
self.server.invoke_successfully(snapmirror_init,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error initializing SnapMirror : %s'
% (to_native(error)),
exception=traceback.format_exc())
def snapmirror_modify(self, modify):
"""
Modify SnapMirror schedule or policy
"""
options = {'destination-location': self.parameters['destination_path']}
snapmirror_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'snapmirror-modify', **options)
if modify.get('schedule') is not None:
snapmirror_modify.add_new_child('schedule', modify.get('schedule'))
if modify.get('policy'):
snapmirror_modify.add_new_child('policy', modify.get('policy'))
if modify.get('max_transfer_rate'):
snapmirror_modify.add_new_child('max-transfer-rate', str(modify.get('max_transfer_rate')))
try:
self.server.invoke_successfully(snapmirror_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying SnapMirror schedule or policy : %s'
% (to_native(error)),
exception=traceback.format_exc())
def snapmirror_update(self):
"""
Update data in destination endpoint
"""
options = {'destination-location': self.parameters['destination_path']}
snapmirror_update = netapp_utils.zapi.NaElement.create_node_with_children(
'snapmirror-update', **options)
try:
result = self.server.invoke_successfully(snapmirror_update,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error updating SnapMirror : %s'
% (to_native(error)),
exception=traceback.format_exc())
def check_parameters(self):
"""
Validate parameters and fail if one or more required params are missing
Update source and destination path from vserver and volume parameters
"""
if self.parameters['state'] == 'present'\
and (self.parameters.get('source_path') or self.parameters.get('destination_path')):
if not self.parameters.get('destination_path') or not self.parameters.get('source_path'):
self.module.fail_json(msg='Missing parameters: Source path or Destination path')
elif self.parameters.get('source_volume'):
if not self.parameters.get('source_vserver') or not self.parameters.get('destination_vserver'):
self.module.fail_json(msg='Missing parameters: source vserver or destination vserver or both')
self.parameters['source_path'] = self.parameters['source_vserver'] + ":" + self.parameters['source_volume']
self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":" +\
self.parameters['destination_volume']
elif self.parameters.get('source_vserver'):
self.parameters['source_path'] = self.parameters['source_vserver'] + ":"
self.parameters['destination_path'] = self.parameters['destination_vserver'] + ":"
def get_destination(self):
result = None
release_get = netapp_utils.zapi.NaElement('snapmirror-get-destination-iter')
query = netapp_utils.zapi.NaElement('query')
snapmirror_dest_info = netapp_utils.zapi.NaElement('snapmirror-destination-info')
snapmirror_dest_info.add_new_child('destination-location', self.parameters['destination_path'])
query.add_child_elem(snapmirror_dest_info)
release_get.add_child_elem(query)
try:
result = self.source_server.invoke_successfully(release_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching snapmirror destinations info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) > 0:
return True
return None
@staticmethod
def element_source_path_format_matches(value):
return re.match(pattern=r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\/lun\/[0-9]+",
string=value)
def check_elementsw_parameters(self, kind='source'):
"""
Validate all ElementSW cluster parameters required for managing the SnapMirror relationship
Validate if both source and destination paths are present
Validate if source_path follows the required format
Validate SVIP
Validate if ElementSW volume exists
:return: None
"""
path = None
if kind == 'destination':
path = self.parameters.get('destination_path')
elif kind == 'source':
path = self.parameters.get('source_path')
if path is None:
self.module.fail_json(msg="Error: Missing required parameter %s_path for "
"connection_type %s" % (kind, self.parameters['connection_type']))
else:
if NetAppONTAPSnapmirror.element_source_path_format_matches(path) is None:
self.module.fail_json(msg="Error: invalid %s_path %s. "
"If the path is a ElementSW cluster, the value should be of the format"
" <Element_SVIP>:/lun/<Element_VOLUME_ID>" % (kind, path))
# validate source_path
elementsw_helper, elem = self.set_element_connection(kind)
self.validate_elementsw_svip(path, elem)
self.check_if_elementsw_volume_exists(path, elementsw_helper)
def validate_elementsw_svip(self, path, elem):
"""
Validate ElementSW cluster SVIP
:return: None
"""
result = None
try:
result = elem.get_cluster_info()
except solidfire.common.ApiServerError as err:
self.module.fail_json(msg="Error fetching SVIP", exception=to_native(err))
if result and result.cluster_info.svip:
cluster_svip = result.cluster_info.svip
svip = path.split(':')[0] # split IP address from source_path
if svip != cluster_svip:
self.module.fail_json(msg="Error: Invalid SVIP")
def check_if_elementsw_volume_exists(self, path, elementsw_helper):
"""
Check if remote ElementSW volume exists
:return: None
"""
volume_id, vol_id = None, path.split('/')[-1]
try:
volume_id = elementsw_helper.volume_id_exists(int(vol_id))
except solidfire.common.ApiServerError as err:
self.module.fail_json(msg="Error fetching Volume details", exception=to_native(err))
if volume_id is None:
self.module.fail_json(msg="Error: Source volume does not exist in the ElementSW cluster")
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def apply(self):
"""
Apply action to SnapMirror
"""
self.asup_log_for_cserver("na_ontap_snapmirror")
# source is ElementSW
if self.parameters['state'] == 'present' and self.parameters.get('connection_type') == 'elementsw_ontap':
self.check_elementsw_parameters()
elif self.parameters.get('connection_type') == 'ontap_elementsw':
self.check_elementsw_parameters('destination')
else:
self.check_parameters()
if self.parameters['state'] == 'present' and self.parameters.get('connection_type') == 'ontap_elementsw':
current_elementsw_ontap = self.snapmirror_get(self.parameters['source_path'])
if current_elementsw_ontap is None:
self.module.fail_json(msg='Error: creating an ONTAP to ElementSW snapmirror relationship requires an '
'established SnapMirror relation from ElementSW to ONTAP cluster')
current = self.snapmirror_get()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
element_snapmirror = False
if cd_action == 'create':
self.snapmirror_create()
elif cd_action == 'delete':
if current['status'] == 'transferring':
self.snapmirror_abort()
else:
if self.parameters.get('connection_type') == 'elementsw_ontap':
element_snapmirror = True
self.delete_snapmirror(element_snapmirror, current['relationship'])
else:
if modify:
self.snapmirror_modify(modify)
# check for initialize
if current and current['mirror_state'] != 'snapmirrored':
self.snapmirror_initialize()
# set changed explicitly for initialize
self.na_helper.changed = True
# Update when create is called again, or modify is being called
if self.parameters['state'] == 'present':
self.snapmirror_update()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPSnapmirror()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,326 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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': 'community'}
DOCUMENTATION = '''
module: na_ontap_snapshot
short_description: NetApp ONTAP manage Snapshots
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Modify/Delete ONTAP snapshots
options:
state:
description:
- If you want to create/modify a snapshot, or delete it.
choices: ['present', 'absent']
default: present
snapshot:
description:
Name of the snapshot to be managed.
The maximum string length is 256 characters.
required: true
from_name:
description:
- Name of the existing snapshot to be renamed to.
version_added: '2.8'
volume:
description:
- Name of the volume on which the snapshot is to be created.
required: true
async_bool:
description:
- If true, the snapshot is to be created asynchronously.
type: bool
comment:
description:
A human readable comment attached with the snapshot.
The size of the comment can be at most 255 characters.
snapmirror_label:
description:
A human readable SnapMirror Label attached with the snapshot.
Size of the label can be at most 31 characters.
ignore_owners:
description:
- if this field is true, snapshot will be deleted
even if some other processes are accessing it.
type: bool
snapshot_instance_uuid:
description:
- The 128 bit unique snapshot identifier expressed in the form of UUID.
vserver:
description:
- The Vserver name
required: true
'''
EXAMPLES = """
- name: create SnapShot
tags:
- create
na_ontap_snapshot:
state: present
snapshot: "{{ snapshot name }}"
volume: "{{ vol name }}"
comment: "i am a comment"
vserver: "{{ vserver name }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
- name: delete SnapShot
tags:
- delete
na_ontap_snapshot:
state: absent
snapshot: "{{ snapshot name }}"
volume: "{{ vol name }}"
vserver: "{{ vserver name }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
- name: modify SnapShot
tags:
- modify
na_ontap_snapshot:
state: present
snapshot: "{{ snapshot name }}"
comment: "New comments are great"
volume: "{{ vol name }}"
vserver: "{{ vserver name }}"
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapSnapshot(object):
"""
Creates, modifies, and deletes a Snapshot
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
from_name=dict(required=False, type='str'),
snapshot=dict(required=True, type="str"),
volume=dict(required=True, type="str"),
async_bool=dict(required=False, type="bool", default=False),
comment=dict(required=False, type="str"),
snapmirror_label=dict(required=False, type="str"),
ignore_owners=dict(required=False, type="bool", default=False),
snapshot_instance_uuid=dict(required=False, type="str"),
vserver=dict(required=True, type="str"),
))
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_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(
module=self.module, vserver=self.parameters['vserver'])
return
def get_snapshot(self, snapshot_name=None):
"""
Checks to see if a snapshot exists or not
:return: Return True if a snapshot exists, False if it doesn't
"""
if snapshot_name is None:
snapshot_name = self.parameters['snapshot']
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-get-iter")
desired_attr = netapp_utils.zapi.NaElement("desired-attributes")
snapshot_info = netapp_utils.zapi.NaElement('snapshot-info')
comment = netapp_utils.zapi.NaElement('comment')
snapmirror_label = netapp_utils.zapi.NaElement('snapmirror-label')
# add more desired attributes that are allowed to be modified
snapshot_info.add_child_elem(comment)
snapshot_info.add_child_elem(snapmirror_label)
desired_attr.add_child_elem(snapshot_info)
snapshot_obj.add_child_elem(desired_attr)
# compose query
query = netapp_utils.zapi.NaElement("query")
snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
snapshot_info_obj.add_new_child("name", snapshot_name)
snapshot_info_obj.add_new_child("volume", self.parameters['volume'])
snapshot_info_obj.add_new_child("vserver", self.parameters['vserver'])
query.add_child_elem(snapshot_info_obj)
snapshot_obj.add_child_elem(query)
result = self.server.invoke_successfully(snapshot_obj, True)
return_value = None
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
attributes_list = result.get_child_by_name('attributes-list')
snap_info = attributes_list.get_child_by_name('snapshot-info')
return_value = {'comment': snap_info.get_child_content('comment')}
if snap_info.get_child_by_name('snapmirror-label'):
return_value['snapmirror_label'] = snap_info.get_child_content('snapmirror-label')
else:
return_value['snapmirror_label'] = None
return return_value
def create_snapshot(self):
"""
Creates a new snapshot
"""
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-create")
# set up required variables to create a snapshot
snapshot_obj.add_new_child("snapshot", self.parameters['snapshot'])
snapshot_obj.add_new_child("volume", self.parameters['volume'])
# Set up optional variables to create a snapshot
if self.parameters.get('async_bool'):
snapshot_obj.add_new_child("async", str(self.parameters['async_bool']))
if self.parameters.get('comment'):
snapshot_obj.add_new_child("comment", self.parameters['comment'])
if self.parameters.get('snapmirror_label'):
snapshot_obj.add_new_child(
"snapmirror-label", self.parameters['snapmirror_label'])
try:
self.server.invoke_successfully(snapshot_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating snapshot %s: %s' %
(self.parameters['snapshot'], to_native(error)),
exception=traceback.format_exc())
def delete_snapshot(self):
"""
Deletes an existing snapshot
"""
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-delete")
# Set up required variables to delete a snapshot
snapshot_obj.add_new_child("snapshot", self.parameters['snapshot'])
snapshot_obj.add_new_child("volume", self.parameters['volume'])
# set up optional variables to delete a snapshot
if self.parameters.get('ignore_owners'):
snapshot_obj.add_new_child("ignore-owners", str(self.parameters['ignore_owners']))
if self.parameters.get('snapshot_instance_uuid'):
snapshot_obj.add_new_child("snapshot-instance-uuid", self.parameters['snapshot_instance_uuid'])
try:
self.server.invoke_successfully(snapshot_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting snapshot %s: %s' %
(self.parameters['snapshot'], to_native(error)),
exception=traceback.format_exc())
def modify_snapshot(self):
"""
Modify an existing snapshot
:return:
"""
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-modify-iter")
# Create query object, this is the existing object
query = netapp_utils.zapi.NaElement("query")
snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
snapshot_info_obj.add_new_child("name", self.parameters['snapshot'])
snapshot_info_obj.add_new_child("vserver", self.parameters['vserver'])
query.add_child_elem(snapshot_info_obj)
snapshot_obj.add_child_elem(query)
# this is what we want to modify in the snapshot object
attributes = netapp_utils.zapi.NaElement("attributes")
snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-info")
snapshot_info_obj.add_new_child("name", self.parameters['snapshot'])
if self.parameters.get('comment'):
snapshot_info_obj.add_new_child("comment", self.parameters['comment'])
if self.parameters.get('snapmirror_label'):
snapshot_info_obj.add_new_child("snapmirror-label", self.parameters['snapmirror_label'])
attributes.add_child_elem(snapshot_info_obj)
snapshot_obj.add_child_elem(attributes)
try:
self.server.invoke_successfully(snapshot_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying snapshot %s: %s' %
(self.parameters['snapshot'], to_native(error)),
exception=traceback.format_exc())
def rename_snapshot(self):
"""
Rename the snapshot
"""
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-rename")
# set up required variables to rename a snapshot
snapshot_obj.add_new_child("current-name", self.parameters['from_name'])
snapshot_obj.add_new_child("new-name", self.parameters['snapshot'])
snapshot_obj.add_new_child("volume", self.parameters['volume'])
try:
self.server.invoke_successfully(snapshot_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error renaming snapshot %s to %s: %s' %
(self.parameters['from_name'], self.parameters['snapshot'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Check to see which play we should run
"""
current = self.get_snapshot()
netapp_utils.ems_log_event("na_ontap_snapshot", self.server)
rename, cd_action = None, None
modify = {}
if self.parameters.get('from_name'):
current_old_name = self.get_snapshot(self.parameters['from_name'])
rename = self.na_helper.is_rename_action(current_old_name, current)
modify = self.na_helper.get_modified_attributes(current_old_name, self.parameters)
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None:
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_snapshot()
if cd_action == 'create':
self.create_snapshot()
elif cd_action == 'delete':
self.delete_snapshot()
elif modify:
self.modify_snapshot()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Creates, modifies, and deletes a Snapshot
"""
obj = NetAppOntapSnapshot()
obj.apply()
if __name__ == '__main__':
main()

@ -1,453 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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': 'community'}
DOCUMENTATION = '''
module: na_ontap_snapshot_policy
short_description: NetApp ONTAP manage Snapshot Policy
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Modify/Delete ONTAP snapshot policies
options:
state:
description:
- If you want to create, modify or delete a snapshot policy.
choices: ['present', 'absent']
default: present
name:
description:
Name of the snapshot policy to be managed.
The maximum string length is 256 characters.
required: true
enabled:
description:
- Status of the snapshot policy indicating whether the policy will be enabled or disabled.
type: bool
comment:
description:
A human readable comment attached with the snapshot.
The size of the comment can be at most 255 characters.
count:
description:
Retention count for the snapshots created by the schedule.
type: list
schedule:
description:
- Schedule to be added inside the policy.
type: list
snapmirror_label:
description:
- SnapMirror label assigned to each schedule inside the policy. Use an empty
string ('') for no label.
type: list
required: false
version_added: '2.9'
vserver:
description:
- The name of the vserver to use. In a multi-tenanted environment, assigning a
Snapshot Policy to a vserver will restrict its use to that vserver.
required: false
version_added: '2.9'
'''
EXAMPLES = """
- name: Create Snapshot policy
na_ontap_snapshot_policy:
state: present
name: ansible2
schedule: hourly
count: 150
enabled: True
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
https: False
- name: Create Snapshot policy with multiple schedules
na_ontap_snapshot_policy:
state: present
name: ansible2
schedule: ['hourly', 'daily', 'weekly', 'monthly', '5min']
count: [1, 2, 3, 4, 5]
enabled: True
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
https: False
- name: Create Snapshot policy owned by a vserver
na_ontap_snapshot_policy:
state: present
name: ansible3
vserver: ansible
schedule: ['hourly', 'daily', 'weekly', 'monthly', '5min']
count: [1, 2, 3, 4, 5]
snapmirror_label: ['hourly', 'daily', 'weekly', 'monthly', '']
enabled: True
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
https: False
- name: Modify Snapshot policy with multiple schedules
na_ontap_snapshot_policy:
state: present
name: ansible2
schedule: ['daily', 'weekly']
count: [20, 30]
snapmirror_label: ['daily', 'weekly']
enabled: True
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
https: False
- name: Delete Snapshot policy
na_ontap_snapshot_policy:
state: absent
name: ansible2
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
https: False
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapSnapshotPolicy(object):
"""
Creates and deletes a Snapshot Policy
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
name=dict(required=True, type="str"),
enabled=dict(required=False, type="bool"),
# count is a list of integers
count=dict(required=False, type="list", elements="int"),
comment=dict(required=False, type="str"),
schedule=dict(required=False, type="list", elements="str"),
snapmirror_label=dict(required=False, type="list", elements="str"),
vserver=dict(required=False, type="str")
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['enabled', 'count', 'schedule']),
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
if 'vserver' in self.parameters:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
return
def get_snapshot_policy(self):
"""
Checks to see if a snapshot policy exists or not
:return: Return policy details if a snapshot policy exists, None if it doesn't
"""
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-get-iter")
# compose query
query = netapp_utils.zapi.NaElement("query")
snapshot_info_obj = netapp_utils.zapi.NaElement("snapshot-policy-info")
snapshot_info_obj.add_new_child("policy", self.parameters['name'])
if 'vserver' in self.parameters:
snapshot_info_obj.add_new_child("vserver-name", self.parameters['vserver'])
query.add_child_elem(snapshot_info_obj)
snapshot_obj.add_child_elem(query)
try:
result = self.server.invoke_successfully(snapshot_obj, True)
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) == 1:
snapshot_policy = result.get_child_by_name('attributes-list').get_child_by_name('snapshot-policy-info')
current = {}
current['name'] = snapshot_policy.get_child_content('policy')
current['vserver'] = snapshot_policy.get_child_content('vserver-name')
current['enabled'] = False if snapshot_policy.get_child_content('enabled').lower() == 'false' else True
current['comment'] = snapshot_policy.get_child_content('comment') or ''
current['schedule'], current['count'], current['snapmirror_label'] = [], [], []
if snapshot_policy.get_child_by_name('snapshot-policy-schedules'):
for schedule in snapshot_policy['snapshot-policy-schedules'].get_children():
current['schedule'].append(schedule.get_child_content('schedule'))
current['count'].append(int(schedule.get_child_content('count')))
snapmirror_label = schedule.get_child_content('snapmirror-label')
if snapmirror_label is None or snapmirror_label == '-':
snapmirror_label = ''
current['snapmirror_label'].append(snapmirror_label)
return current
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg=to_native(error), exception=traceback.format_exc())
return None
def validate_parameters(self):
"""
Validate if each schedule has a count associated
:return: None
"""
if 'count' not in self.parameters or 'schedule' not in self.parameters or \
len(self.parameters['count']) > 5 or len(self.parameters['schedule']) > 5 or \
len(self.parameters['count']) < 1 or len(self.parameters['schedule']) < 1 or \
len(self.parameters['count']) != len(self.parameters['schedule']):
self.module.fail_json(msg="Error: A Snapshot policy must have at least 1 "
"schedule and can have up to a maximum of 5 schedules, with a count "
"representing the maximum number of Snapshot copies for each schedule")
if 'snapmirror_label' in self.parameters:
if len(self.parameters['snapmirror_label']) != len(self.parameters['schedule']):
self.module.fail_json(msg="Error: Each Snapshot Policy schedule must have an "
"accompanying SnapMirror Label")
def modify_snapshot_policy(self, current):
"""
Modifies an existing snapshot policy
"""
# Set up required variables to modify snapshot policy
options = {'policy': self.parameters['name']}
modify = False
# Set up optional variables to modify snapshot policy
if 'enabled' in self.parameters and self.parameters['enabled'] != current['enabled']:
options['enabled'] = str(self.parameters['enabled'])
modify = True
if 'comment' in self.parameters and self.parameters['comment'] != current['comment']:
options['comment'] = self.parameters['comment']
modify = True
if modify:
snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children('snapshot-policy-modify', **options)
try:
self.server.invoke_successfully(snapshot_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying snapshot policy %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_snapshot_policy_schedules(self, current):
"""
Modify existing schedules in snapshot policy
:return: None
"""
self.validate_parameters()
delete_schedules, modify_schedules, add_schedules = [], [], []
if 'snapmirror_label' in self.parameters:
snapmirror_labels = self.parameters['snapmirror_label']
else:
# User hasn't supplied any snapmirror labels.
snapmirror_labels = [None] * len(self.parameters['schedule'])
# Identify schedules for deletion
for schedule in current['schedule']:
schedule = schedule.strip()
if schedule not in [item.strip() for item in self.parameters['schedule']]:
options = {'policy': current['name'],
'schedule': schedule}
delete_schedules.append(options)
# Identify schedules to be modified or added
for schedule, count, snapmirror_label in zip(self.parameters['schedule'], self.parameters['count'], snapmirror_labels):
schedule = schedule.strip()
if snapmirror_label is not None:
snapmirror_label = snapmirror_label.strip()
options = {'policy': current['name'],
'schedule': schedule}
if schedule in current['schedule']:
# Schedule exists. Only modify if it has changed.
modify = False
schedule_index = current['schedule'].index(schedule)
if count != current['count'][schedule_index]:
options['new-count'] = str(count)
modify = True
if snapmirror_label is not None:
if snapmirror_label != current['snapmirror_label'][schedule_index]:
options['new-snapmirror-label'] = snapmirror_label
modify = True
if modify:
modify_schedules.append(options)
else:
# New schedule
options['count'] = str(count)
if snapmirror_label is not None and snapmirror_label != '':
options['snapmirror-label'] = snapmirror_label
add_schedules.append(options)
# Delete N-1 schedules no longer required. Must leave 1 schedule in policy
# at any one time. Delete last one afterwards.
while len(delete_schedules) > 1:
options = delete_schedules.pop()
self.modify_snapshot_policy_schedule(options, 'snapshot-policy-remove-schedule')
# Modify schedules.
while len(modify_schedules) > 0:
options = modify_schedules.pop()
self.modify_snapshot_policy_schedule(options, 'snapshot-policy-modify-schedule')
# Add N-1 new schedules. Add last one after last schedule has been deleted.
while len(add_schedules) > 1:
options = add_schedules.pop()
self.modify_snapshot_policy_schedule(options, 'snapshot-policy-add-schedule')
# Delete last schedule no longer required.
while len(delete_schedules) > 0:
options = delete_schedules.pop()
self.modify_snapshot_policy_schedule(options, 'snapshot-policy-remove-schedule')
# Add last new schedule.
while len(add_schedules) > 0:
options = add_schedules.pop()
self.modify_snapshot_policy_schedule(options, 'snapshot-policy-add-schedule')
def modify_snapshot_policy_schedule(self, options, zapi):
"""
Add, modify or remove a schedule to/from a snapshot policy
"""
snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children(zapi, **options)
try:
self.server.invoke_successfully(snapshot_obj, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying snapshot policy schedule %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def create_snapshot_policy(self):
"""
Creates a new snapshot policy
"""
# set up required variables to create a snapshot policy
self.validate_parameters()
options = {'policy': self.parameters['name'],
'enabled': str(self.parameters['enabled']),
}
if 'snapmirror_label' in self.parameters:
snapmirror_labels = self.parameters['snapmirror_label']
else:
# User hasn't supplied any snapmirror labels.
snapmirror_labels = [None] * len(self.parameters['schedule'])
# zapi attribute for first schedule is schedule1, second is schedule2 and so on
positions = [str(i) for i in range(1, len(self.parameters['schedule']) + 1)]
for schedule, count, snapmirror_label, position in zip(self.parameters['schedule'], self.parameters['count'], snapmirror_labels, positions):
schedule = schedule.strip()
options['count' + position] = str(count)
options['schedule' + position] = schedule
if snapmirror_label is not None:
snapmirror_label = snapmirror_label.strip()
if snapmirror_label != '':
options['snapmirror-label' + position] = snapmirror_label
snapshot_obj = netapp_utils.zapi.NaElement.create_node_with_children('snapshot-policy-create', **options)
# Set up optional variables to create a snapshot policy
if self.parameters.get('comment'):
snapshot_obj.add_new_child("comment", self.parameters['comment'])
try:
self.server.invoke_successfully(snapshot_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating snapshot policy %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_snapshot_policy(self):
"""
Deletes an existing snapshot policy
"""
snapshot_obj = netapp_utils.zapi.NaElement("snapshot-policy-delete")
# Set up required variables to delete a snapshot policy
snapshot_obj.add_new_child("policy", self.parameters['name'])
try:
self.server.invoke_successfully(snapshot_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting snapshot policy %s: %s' %
(self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def apply(self):
"""
Check to see which play we should run
"""
self.asup_log_for_cserver("na_ontap_snapshot_policy")
current = self.get_snapshot_policy()
modify = None
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is None and self.parameters['state'] == 'present':
# Don't sort schedule/count/snapmirror_label lists as it can
# mess up the intended parameter order.
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_snapshot_policy()
elif cd_action == 'delete':
self.delete_snapshot_policy()
if modify:
self.modify_snapshot_policy(current)
self.modify_snapshot_policy_schedules(current)
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Creates and deletes a Snapshot Policy
"""
obj = NetAppOntapSnapshotPolicy()
obj.apply()
if __name__ == '__main__':
main()

@ -1,152 +0,0 @@
#!/usr/bin/python
"""
create SNMP module to add/delete/modify SNMP user
"""
# (c) 2018-2019, 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 = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- "Create/Delete SNMP community"
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_snmp
options:
access_control:
description:
- "Access control for the community. The only supported value is 'ro' (read-only)"
required: true
community_name:
description:
- "The name of the SNMP community to manage."
required: true
state:
choices: ['present', 'absent']
description:
- "Whether the specified SNMP community should exist or not."
default: 'present'
short_description: NetApp ONTAP SNMP community
version_added: "2.6"
'''
EXAMPLES = """
- name: Create SNMP community
na_ontap_snmp:
state: present
community_name: communityName
access_control: 'ro'
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete SNMP community
na_ontap_snmp:
state: absent
community_name: communityName
access_control: 'ro'
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
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPSnmp(object):
'''Class with SNMP methods, doesn't support check mode'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
community_name=dict(required=True, type='str'),
access_control=dict(required=True, type='str'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=False
)
parameters = self.module.params
# set up state variables
self.state = parameters['state']
self.community_name = parameters['community_name']
self.access_control = parameters['access_control']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def invoke_snmp_community(self, zapi):
"""
Invoke zapi - add/delete take the same NaElement structure
@return: SUCCESS / FAILURE with an error_message
"""
snmp_community = netapp_utils.zapi.NaElement.create_node_with_children(
zapi, **{'community': self.community_name,
'access-control': self.access_control})
try:
self.server.invoke_successfully(snmp_community, enable_tunneling=True)
except netapp_utils.zapi.NaApiError: # return False for duplicate entry
return False
return True
def add_snmp_community(self):
"""
Adds a SNMP community
"""
return self.invoke_snmp_community('snmp-community-add')
def delete_snmp_community(self):
"""
Delete a SNMP community
"""
return self.invoke_snmp_community('snmp-community-delete')
def apply(self):
"""
Apply action to SNMP community
This module is not idempotent:
Add doesn't fail the playbook if user is trying
to add an already existing snmp community
"""
changed = False
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_snmp", cserver)
if self.state == 'present': # add
if self.add_snmp_community():
changed = True
elif self.state == 'absent': # delete
if self.delete_snmp_community():
changed = True
self.module.exit_json(changed=changed)
def main():
'''Execute action'''
community_obj = NetAppONTAPSnmp()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,301 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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': 'community'}
DOCUMENTATION = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Update ONTAP software
- Requires an https connection and is not supported over http
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_software_update
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified ONTAP package should update or not.
default: present
nodes:
description:
- List of nodes to be updated, the nodes have to be a part of a HA Pair.
aliases:
- node
package_version:
required: true
description:
- Specifies the package version to update software.
package_url:
required: true
description:
- Specifies the package URL to download the package.
ignore_validation_warning:
description:
- Allows the update to continue if warnings are encountered during the validation phase.
default: False
type: bool
short_description: NetApp ONTAP Update Software
version_added: "2.7"
'''
EXAMPLES = """
- name: ONTAP software update
na_ontap_software_update:
state: present
nodes: vsim1
package_url: "{{ url }}"
package_version: "{{ version_name }}"
ignore_validation_warning: True
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
import time
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPSoftwareUpdate(object):
"""
Class with ONTAP software update methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
nodes=dict(required=False, type='list', aliases=["node"]),
package_version=dict(required=True, type='str'),
package_url=dict(required=True, type='str'),
ignore_validation_warning=dict(required=False, type='bool', default=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def cluster_image_get_iter(self):
"""
Compose NaElement object to query current version
:return: NaElement object for cluster-image-get-iter with query
"""
cluster_image_get = netapp_utils.zapi.NaElement('cluster-image-get-iter')
query = netapp_utils.zapi.NaElement('query')
cluster_image_info = netapp_utils.zapi.NaElement('cluster-image-info')
query.add_child_elem(cluster_image_info)
cluster_image_get.add_child_elem(query)
return cluster_image_get
def cluster_image_get(self):
"""
Get current cluster image info
:return: True if query successful, else return None
"""
cluster_image_get_iter = self.cluster_image_get_iter()
try:
result = self.server.invoke_successfully(cluster_image_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching cluster image details: %s: %s'
% (self.parameters['package_version'], to_native(error)),
exception=traceback.format_exc())
# return cluster image details
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) > 0:
return True
return None
def cluster_image_get_for_node(self, node_name):
"""
Get current cluster image info for given node
"""
cluster_image_get = netapp_utils.zapi.NaElement('cluster-image-get')
cluster_image_get.add_new_child('node-id', node_name)
try:
self.server.invoke_successfully(cluster_image_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching cluster image details for %s: %s'
% (node_name, to_native(error)),
exception=traceback.format_exc())
def cluster_image_update_progress_get(self):
"""
Get current cluster image update progress info
:return: Dictionary of cluster image update progress if query successful, else return None
"""
cluster_update_progress_get = netapp_utils.zapi.NaElement('cluster-image-update-progress-info')
cluster_update_progress_info = dict()
try:
result = self.server.invoke_successfully(cluster_update_progress_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
# return empty dict on error to satisfy package delete upon image update
if to_native(error.code) == 'Unexpected error' and self.parameters.get('https') is True:
return cluster_update_progress_info
else:
self.module.fail_json(msg='Error fetching cluster image update progress details: %s'
% (to_native(error)),
exception=traceback.format_exc())
# return cluster image update progress details
if result.get_child_by_name('attributes').get_child_by_name('ndu-progress-info'):
update_progress_info = result.get_child_by_name('attributes').get_child_by_name('ndu-progress-info')
cluster_update_progress_info['overall_status'] = update_progress_info.get_child_content('overall-status')
cluster_update_progress_info['completed_node_count'] = update_progress_info.\
get_child_content('completed-node-count')
return cluster_update_progress_info
def cluster_image_update(self):
"""
Update current cluster image
"""
cluster_update_info = netapp_utils.zapi.NaElement('cluster-image-update')
cluster_update_info.add_new_child('package-version', self.parameters['package_version'])
cluster_update_info.add_new_child('ignore-validation-warning',
str(self.parameters['ignore_validation_warning']))
if self.parameters.get('nodes'):
cluster_nodes = netapp_utils.zapi.NaElement('nodes')
for node in self.parameters['nodes']:
cluster_nodes.add_new_child('node-name', node)
cluster_update_info.add_child_elem(cluster_nodes)
try:
self.server.invoke_successfully(cluster_update_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error updating cluster image for %s: %s'
% (self.parameters['package_version'], to_native(error)),
exception=traceback.format_exc())
def cluster_image_package_download(self):
"""
Get current cluster image package download
:return: True if package already exists, else return False
"""
cluster_image_package_download_info = netapp_utils.zapi.NaElement('cluster-image-package-download')
cluster_image_package_download_info.add_new_child('package-url', self.parameters['package_url'])
try:
self.server.invoke_successfully(cluster_image_package_download_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
# Error 18408 denotes Package image with the same name already exists
if to_native(error.code) == "18408":
return True
else:
self.module.fail_json(msg='Error downloading cluster image package for %s: %s'
% (self.parameters['package_url'], to_native(error)),
exception=traceback.format_exc())
return False
def cluster_image_package_delete(self):
"""
Delete current cluster image package
"""
cluster_image_package_delete_info = netapp_utils.zapi.NaElement('cluster-image-package-delete')
cluster_image_package_delete_info.add_new_child('package-version', self.parameters['package_version'])
try:
self.server.invoke_successfully(cluster_image_package_delete_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting cluster image package for %s: %s'
% (self.parameters['package_version'], to_native(error)),
exception=traceback.format_exc())
def cluster_image_package_download_progress(self):
"""
Get current cluster image package download progress
:return: Dictionary of cluster image download progress if query successful, else return None
"""
cluster_image_package_download_progress_info = netapp_utils.zapi.\
NaElement('cluster-image-get-download-progress')
try:
result = self.server.invoke_successfully(
cluster_image_package_download_progress_info, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching cluster image package download progress for %s: %s'
% (self.parameters['package_url'], to_native(error)),
exception=traceback.format_exc())
# return cluster image download progress details
cluster_download_progress_info = dict()
if result.get_child_by_name('progress-status'):
cluster_download_progress_info['progress_status'] = result.get_child_content('progress-status')
cluster_download_progress_info['progress_details'] = result.get_child_content('progress-details')
cluster_download_progress_info['failure_reason'] = result.get_child_content('failure-reason')
return cluster_download_progress_info
return None
def autosupport_log(self):
"""
Autosupport log for software_update
:return:
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_software_update", cserver)
def apply(self):
"""
Apply action to update ONTAP software
"""
if self.parameters.get('https') is not True:
self.module.fail_json(msg='https parameter must be True')
changed = False
self.autosupport_log()
current = self.cluster_image_get()
if self.parameters.get('nodes'):
for node in self.parameters['nodes']:
self.cluster_image_get_for_node(node)
if self.parameters.get('state') == 'present' and current:
package_exists = self.cluster_image_package_download()
if package_exists is False:
cluster_download_progress = self.cluster_image_package_download_progress()
while cluster_download_progress.get('progress_status') == 'async_pkg_get_phase_running':
time.sleep(5)
cluster_download_progress = self.cluster_image_package_download_progress()
if cluster_download_progress.get('progress_status') == 'async_pkg_get_phase_complete':
self.cluster_image_update()
changed = True
else:
self.module.fail_json(msg='Error downloading package: %s'
% (cluster_download_progress['failure_reason']))
else:
self.cluster_image_update()
changed = True
# delete package once update is completed
cluster_update_progress = self.cluster_image_update_progress_get()
while not cluster_update_progress or cluster_update_progress.get('overall_status') == 'in_progress':
time.sleep(25)
cluster_update_progress = self.cluster_image_update_progress_get()
if cluster_update_progress.get('overall_status') == 'completed':
self.cluster_image_package_delete()
self.module.exit_json(changed=changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPSoftwareUpdate()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,444 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_svm
short_description: NetApp ONTAP SVM
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create, modify or delete SVM on NetApp ONTAP
options:
state:
description:
- Whether the specified SVM should exist or not.
choices: ['present', 'absent']
default: 'present'
name:
description:
- The name of the SVM to manage.
required: true
from_name:
description:
- Name of the SVM to be renamed
version_added: '2.7'
root_volume:
description:
- Root volume of the SVM.
- Cannot be modified after creation.
root_volume_aggregate:
description:
- The aggregate on which the root volume will be created.
- Cannot be modified after creation.
root_volume_security_style:
description:
- Security Style of the root volume.
- When specified as part of the vserver-create,
this field represents the security style for the Vserver root volume.
- When specified as part of vserver-get-iter call,
this will return the list of matching Vservers.
- The 'unified' security style, which applies only to Infinite Volumes,
cannot be applied to a Vserver's root volume.
- Cannot be modified after creation.
choices: ['unix', 'ntfs', 'mixed', 'unified']
allowed_protocols:
description:
- Allowed Protocols.
- When specified as part of a vserver-create,
this field represent the list of protocols allowed on the Vserver.
- When part of vserver-get-iter call,
this will return the list of Vservers
which have any of the protocols specified
as part of the allowed-protocols.
- When part of vserver-modify,
this field should include the existing list
along with new protocol list to be added to prevent data disruptions.
- Possible values
- nfs NFS protocol,
- cifs CIFS protocol,
- fcp FCP protocol,
- iscsi iSCSI protocol,
- ndmp NDMP protocol,
- http HTTP protocol,
- nvme NVMe protocol
aggr_list:
description:
- List of aggregates assigned for volume operations.
- These aggregates could be shared for use with other Vservers.
- When specified as part of a vserver-create,
this field represents the list of aggregates
that are assigned to the Vserver for volume operations.
- When part of vserver-get-iter call,
this will return the list of Vservers
which have any of the aggregates specified as part of the aggr list.
ipspace:
description:
- IPSpace name
- Cannot be modified after creation.
version_added: '2.7'
snapshot_policy:
description:
- Default snapshot policy setting for all volumes of the Vserver.
This policy will be assigned to all volumes created in this
Vserver unless the volume create request explicitly provides a
snapshot policy or volume is modified later with a specific
snapshot policy. A volume-level snapshot policy always overrides
the default Vserver-wide snapshot policy.
version_added: '2.7'
language:
description:
- Language to use for the SVM
- Default to C.UTF-8
- Possible values Language
- c POSIX
- ar Arabic
- cs Czech
- da Danish
- de German
- en English
- en_us English (US)
- es Spanish
- fi Finnish
- fr French
- he Hebrew
- hr Croatian
- hu Hungarian
- it Italian
- ja Japanese euc-j
- ja_v1 Japanese euc-j
- ja_jp.pck Japanese PCK (sjis)
- ja_jp.932 Japanese cp932
- ja_jp.pck_v2 Japanese PCK (sjis)
- ko Korean
- no Norwegian
- nl Dutch
- pl Polish
- pt Portuguese
- ro Romanian
- ru Russian
- sk Slovak
- sl Slovenian
- sv Swedish
- tr Turkish
- zh Simplified Chinese
- zh.gbk Simplified Chinese (GBK)
- zh_tw Traditional Chinese euc-tw
- zh_tw.big5 Traditional Chinese Big 5
version_added: '2.7'
subtype:
description:
- The subtype for vserver to be created.
- Cannot be modified after creation.
choices: ['default', 'dp_destination', 'sync_source', 'sync_destination']
version_added: '2.7'
comment:
description:
- When specified as part of a vserver-create, this field represents the comment associated with the Vserver.
- When part of vserver-get-iter call, this will return the list of matching Vservers.
version_added: '2.8'
'''
EXAMPLES = """
- name: Create SVM
na_ontap_svm:
state: present
name: ansibleVServer
root_volume: vol1
root_volume_aggregate: aggr1
root_volume_security_style: mixed
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapSVM(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=[
'present', 'absent'], default='present'),
name=dict(required=True, type='str'),
from_name=dict(required=False, type='str'),
root_volume=dict(type='str'),
root_volume_aggregate=dict(type='str'),
root_volume_security_style=dict(type='str', choices=['unix',
'ntfs',
'mixed',
'unified'
]),
allowed_protocols=dict(type='list'),
aggr_list=dict(type='list'),
ipspace=dict(type='str', required=False),
snapshot_policy=dict(type='str', required=False),
language=dict(type='str', required=False),
subtype=dict(choices=['default', 'dp_destination', 'sync_source', 'sync_destination']),
comment=dict(type="str", required=False)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(
msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_vserver(self, vserver_name=None):
"""
Checks if vserver exists.
:return:
vserver object if vserver found
None if vserver is not found
:rtype: object/None
"""
if vserver_name is None:
vserver_name = self.parameters['name']
vserver_info = netapp_utils.zapi.NaElement('vserver-get-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'vserver-info', **{'vserver-name': vserver_name})
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
vserver_info.add_child_elem(query)
result = self.server.invoke_successfully(vserver_info,
enable_tunneling=False)
vserver_details = None
if (result.get_child_by_name('num-records') and
int(result.get_child_content('num-records')) >= 1):
attributes_list = result.get_child_by_name('attributes-list')
vserver_info = attributes_list.get_child_by_name('vserver-info')
aggr_list = list()
''' vserver aggr-list can be empty by default'''
get_list = vserver_info.get_child_by_name('aggr-list')
if get_list is not None:
aggregates = get_list.get_children()
for aggr in aggregates:
aggr_list.append(aggr.get_content())
protocols = list()
'''allowed-protocols is not empty for data SVM, but is for node SVM'''
allowed_protocols = vserver_info.get_child_by_name('allowed-protocols')
if allowed_protocols is not None:
get_protocols = allowed_protocols.get_children()
for protocol in get_protocols:
protocols.append(protocol.get_content())
vserver_details = {'name': vserver_info.get_child_content('vserver-name'),
'root_volume': vserver_info.get_child_content('root-volume'),
'root_volume_aggregate': vserver_info.get_child_content('root-volume-aggregate'),
'root_volume_security_style': vserver_info.get_child_content('root-volume-security-style'),
'subtype': vserver_info.get_child_content('vserver-subtype'),
'aggr_list': aggr_list,
'language': vserver_info.get_child_content('language'),
'snapshot_policy': vserver_info.get_child_content('snapshot-policy'),
'allowed_protocols': protocols,
'ipspace': vserver_info.get_child_content('ipspace'),
'comment': vserver_info.get_child_content('comment')}
return vserver_details
def create_vserver(self):
options = {'vserver-name': self.parameters['name']}
self.add_parameter_to_dict(options, 'root_volume', 'root-volume')
self.add_parameter_to_dict(options, 'root_volume_aggregate', 'root-volume-aggregate')
self.add_parameter_to_dict(options, 'root_volume_security_style', 'root-volume-security-style')
self.add_parameter_to_dict(options, 'language', 'language')
self.add_parameter_to_dict(options, 'ipspace', 'ipspace')
self.add_parameter_to_dict(options, 'snapshot_policy', 'snapshot-policy')
self.add_parameter_to_dict(options, 'subtype', 'vserver-subtype')
self.add_parameter_to_dict(options, 'comment', 'comment')
vserver_create = netapp_utils.zapi.NaElement.create_node_with_children('vserver-create', **options)
try:
self.server.invoke_successfully(vserver_create,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error provisioning SVM %s: %s'
% (self.parameters['name'], to_native(e)),
exception=traceback.format_exc())
# add allowed-protocols, aggr-list after creation,
# since vserver-create doesn't allow these attributes during creation
options = dict()
for key in ('allowed_protocols', 'aggr_list'):
if self.parameters.get(key):
options[key] = self.parameters[key]
if options:
self.modify_vserver(options)
def delete_vserver(self):
vserver_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'vserver-destroy', **{'vserver-name': self.parameters['name']})
try:
self.server.invoke_successfully(vserver_delete,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error deleting SVM %s: %s'
% (self.parameters['name'], to_native(e)),
exception=traceback.format_exc())
def rename_vserver(self):
vserver_rename = netapp_utils.zapi.NaElement.create_node_with_children(
'vserver-rename', **{'vserver-name': self.parameters['from_name'],
'new-name': self.parameters['name']})
try:
self.server.invoke_successfully(vserver_rename,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error renaming SVM %s: %s'
% (self.parameters['from_name'], to_native(e)),
exception=traceback.format_exc())
def modify_vserver(self, modify):
'''
Modify vserver.
:param modify: list of modify attributes
'''
vserver_modify = netapp_utils.zapi.NaElement('vserver-modify')
vserver_modify.add_new_child('vserver-name', self.parameters['name'])
for attribute in modify:
if attribute == 'language':
vserver_modify.add_new_child('language', self.parameters['language'])
if attribute == 'snapshot_policy':
vserver_modify.add_new_child('snapshot_policy', self.parameters['snapshot_policy'])
if attribute == 'comment':
vserver_modify.add_new_child('comment', self.parameters['comment'])
if attribute == 'allowed_protocols':
allowed_protocols = netapp_utils.zapi.NaElement('allowed-protocols')
for protocol in self.parameters['allowed_protocols']:
allowed_protocols.add_new_child('protocol', protocol)
vserver_modify.add_child_elem(allowed_protocols)
if attribute == 'aggr_list':
aggregates = netapp_utils.zapi.NaElement('aggr-list')
for aggr in self.parameters['aggr_list']:
aggregates.add_new_child('aggr-name', aggr)
vserver_modify.add_child_elem(aggregates)
try:
self.server.invoke_successfully(vserver_modify,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error modifying SVM %s: %s'
% (self.parameters['name'], to_native(e)),
exception=traceback.format_exc())
def add_parameter_to_dict(self, adict, name, key=None, tostr=False):
'''
add defined parameter (not None) to adict using key.
:param adict: a dictionary.
:param name: name in self.parameters.
:param key: key in adict.
:param tostr: boolean.
'''
if key is None:
key = name
if self.parameters.get(name) is not None:
if tostr:
adict[key] = str(self.parameters.get(name))
else:
adict[key] = self.parameters.get(name)
def apply(self):
'''Call create/modify/delete operations.'''
self.asup_log_for_cserver("na_ontap_svm")
current = self.get_vserver()
cd_action, rename = None, None
if self.parameters.get('from_name'):
rename = self.na_helper.is_rename_action(self.get_vserver(self.parameters['from_name']), current)
else:
cd_action = self.na_helper.get_cd_action(current, self.parameters)
modify = self.na_helper.get_modified_attributes(current, self.parameters)
for attribute in modify:
if attribute in ['root_volume', 'root_volume_aggregate', 'root_volume_security_style', 'subtype', 'ipspace']:
self.module.fail_json(msg='Error modifying SVM %s: can not modify %s.' % (self.parameters['name'], attribute))
if attribute == 'language':
# Ontap documentation uses C.UTF-8, but actually stores as c.utf_8.
if self.parameters['language'].lower() == 'c.utf-8':
self.parameters['language'] = 'c.utf_8'
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if rename:
self.rename_vserver()
# If rename is True, cd_action is None, but modify could be true or false.
if cd_action == 'create':
self.create_vserver()
elif cd_action == 'delete':
self.delete_vserver()
elif modify:
self.modify_vserver(modify)
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
'''Apply vserver operations from playbook'''
v = NetAppOntapSVM()
v.apply()
if __name__ == '__main__':
main()

@ -1,156 +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': 'community'}
DOCUMENTATION = '''
short_description: NetApp ONTAP Modify SVM Options
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Modify ONTAP SVM Options
- Only Options that appear on "vserver options show" can be set
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_svm_options
version_added: "2.7"
options:
name:
description:
- Name of the option.
value:
description:
- Value of the option.
- Value must be in quote
vserver:
description:
- The name of the vserver to which this option belongs to.
required: True
'''
EXAMPLES = """
- name: Set SVM Options
na_ontap_svm_options:
vserver: "{{ netapp_vserver_name }}"
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
name: snmp.enable
value: 'on'
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPSvnOptions(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
name=dict(required=False, type="str", default=None),
value=dict(required=False, type='str', default=None),
vserver=dict(required=True, type='str')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
return
def set_options(self):
"""
Set a specific option
:return: None
"""
option_obj = netapp_utils.zapi.NaElement("options-set")
option_obj.add_new_child('name', self.parameters['name'])
option_obj.add_new_child('value', self.parameters['value'])
try:
result = self.server.invoke_successfully(option_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error setting options: %s" % to_native(error), exception=traceback.format_exc())
def list_options(self):
"""
List all Options on the Vserver
:return: None
"""
option_obj = netapp_utils.zapi.NaElement("options-list-info")
try:
result = self.server.invoke_successfully(option_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error getting options: %s" % to_native(error), exception=traceback.format_exc())
def is_option_set(self):
"""
Checks to see if an option is set or not
:return: If option is set return True, else return False
"""
option_obj = netapp_utils.zapi.NaElement("options-get-iter")
options_info = netapp_utils.zapi.NaElement("option-info")
if self.parameters.get('name') is not None:
options_info.add_new_child("name", self.parameters['name'])
if self.parameters.get('value') is not None:
options_info.add_new_child("value", self.parameters['value'])
if "vserver" in self.parameters.keys():
if self.parameters['vserver'] is not None:
options_info.add_new_child("vserver", self.parameters['vserver'])
query = netapp_utils.zapi.NaElement("query")
query.add_child_elem(options_info)
option_obj.add_child_elem(query)
try:
result = self.server.invoke_successfully(option_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error finding option: %s" % to_native(error), exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
return True
return False
def apply(self):
changed = False
netapp_utils.ems_log_event("na_ontap_svm_options", self.server)
is_set = self.is_option_set()
if not is_set:
self.set_options()
changed = True
self.module.exit_json(changed=changed)
def main():
"""
Execute action from playbook
:return: none
"""
cg_obj = NetAppONTAPSvnOptions()
cg_obj.apply()
if __name__ == '__main__':
main()

@ -1,224 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_ucadapter
short_description: NetApp ONTAP UC adapter configuration
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- modify the UC adapter mode and type taking pending type and mode into account.
options:
state:
description:
- Whether the specified adapter should exist.
required: false
choices: ['present']
default: 'present'
adapter_name:
description:
- Specifies the adapter name.
required: true
node_name:
description:
- Specifies the adapter home node.
required: true
mode:
description:
- Specifies the mode of the adapter.
type:
description:
- Specifies the fc4 type of the adapter.
'''
EXAMPLES = '''
- name: Modify adapter
na_ontap_adapter:
state: present
adapter_name: data2
node_name: laurentn-vsim1
mode: fc
type: target
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
'''
RETURN = '''
'''
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapadapter(object):
''' object to describe adapter info '''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present'], default='present'),
adapter_name=dict(required=True, type='str'),
node_name=dict(required=True, type='str'),
mode=dict(required=False, type='str'),
type=dict(required=False, type='str'),
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_adapter(self):
"""
Return details about the adapter
:param:
name : Name of the name of the adapter
:return: Details about the adapter. None if not found.
:rtype: dict
"""
adapter_info = netapp_utils.zapi.NaElement('ucm-adapter-get')
adapter_info.add_new_child('adapter-name', self.parameters['adapter_name'])
adapter_info.add_new_child('node-name', self.parameters['node_name'])
try:
result = self.server.invoke_successfully(adapter_info, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching ucadapter details: %s: %s'
% (self.parameters['node_name'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('attributes'):
adapter_attributes = result.get_child_by_name('attributes').\
get_child_by_name('uc-adapter-info')
return_value = {
'mode': adapter_attributes.get_child_content('mode'),
'pending-mode': adapter_attributes.get_child_content('pending-mode'),
'type': adapter_attributes.get_child_content('fc4-type'),
'pending-type': adapter_attributes.get_child_content('pending-fc4-type'),
'status': adapter_attributes.get_child_content('status'),
}
return return_value
return None
def modify_adapter(self):
"""
Modify the adapter.
"""
params = {'adapter-name': self.parameters['adapter_name'],
'node-name': self.parameters['node_name']}
if self.parameters['type'] is not None:
params['fc4-type'] = self.parameters['type']
if self.parameters['mode'] is not None:
params['mode'] = self.parameters['mode']
adapter_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'ucm-adapter-modify', ** params)
try:
self.server.invoke_successfully(adapter_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error modifying adapter %s: %s' % (self.parameters['adapter_name'], to_native(e)),
exception=traceback.format_exc())
def online_or_offline_adapter(self, status):
"""
Bring a Fibre Channel target adapter offline/online.
"""
if status == 'down':
adapter = netapp_utils.zapi.NaElement('fcp-adapter-config-down')
elif status == 'up':
adapter = netapp_utils.zapi.NaElement('fcp-adapter-config-up')
adapter.add_new_child('fcp-adapter', self.parameters['adapter_name'])
adapter.add_new_child('node', self.parameters['node_name'])
try:
self.server.invoke_successfully(adapter,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error trying to %s fc-adapter %s: %s' % (status, self.parameters['adapter_name'], to_native(e)),
exception=traceback.format_exc())
def autosupport_log(self):
"""
Autosupport log for ucadater
:return:
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_ucadapter", cserver)
def apply(self):
''' calling all adapter features '''
changed = False
adapter_detail = self.get_adapter()
def need_to_change(expected, pending, current):
if expected is None:
return False
elif pending is not None:
return pending != expected
elif current is not None:
return current != expected
return False
if adapter_detail:
changed = need_to_change(self.parameters.get('type'), adapter_detail['pending-type'],
adapter_detail['type']) or need_to_change(self.parameters.get('mode'),
adapter_detail['pending-mode'],
adapter_detail['mode'])
if changed:
if self.module.check_mode:
pass
else:
self.online_or_offline_adapter('down')
self.modify_adapter()
self.online_or_offline_adapter('up')
self.module.exit_json(changed=changed)
def main():
adapter = NetAppOntapadapter()
adapter.apply()
if __name__ == '__main__':
main()

@ -1,348 +0,0 @@
#!/usr/bin/python
"""
create Autosupport module to enable, disable or modify
"""
# (c) 2019, 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': 'community'}
DOCUMENTATION = """
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- "Create/Delete Unix user group"
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_unix_group
options:
state:
description:
- Whether the specified group should exist or not.
choices: ['present', 'absent']
default: 'present'
name:
description:
- Specifies UNIX group's name, unique for each group.
- Non-modifiable.
required: true
id:
description:
- Specifies an identification number for the UNIX group.
- Group ID is unique for each UNIX group.
- Required for create, modifiable.
vserver:
description:
- Specifies the Vserver for the UNIX group.
- Non-modifiable.
required: true
skip_name_validation:
description:
- Specifies if group name validation is skipped.
type: bool
users:
description:
- Specifies the users associated with this group. Should be comma separated.
- It represents the expected state of a list of users at any time.
- Add a user into group if it is specified in expected state but not in current state.
- Delete a user from group if it is specified in current state but not in expected state.
- To delete all current users, use '' as value.
type: list
version_added: "2.9"
short_description: NetApp ONTAP UNIX Group
version_added: "2.8"
"""
EXAMPLES = """
- name: Create UNIX group
na_ontap_unix_group:
state: present
name: SampleGroup
vserver: ansibleVServer
id: 2
users: user1,user2
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete all users in UNIX group
na_ontap_unix_group:
state: present
name: SampleGroup
vserver: ansibleVServer
users: ''
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete UNIX group
na_ontap_unix_group:
state: absent
name: SampleGroup
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapUnixGroup(object):
"""
Common operations to manage UNIX groups
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
id=dict(required=False, type='int'),
skip_name_validation=dict(required=False, type='bool'),
vserver=dict(required=True, type='str'),
users=dict(required=False, type='list')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
self.set_playbook_zapi_key_map()
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def set_playbook_zapi_key_map(self):
self.na_helper.zapi_string_keys = {
'name': 'group-name'
}
self.na_helper.zapi_int_keys = {
'id': 'group-id'
}
self.na_helper.zapi_bool_keys = {
'skip_name_validation': 'skip-name-validation'
}
def get_unix_group(self):
"""
Checks if the UNIX group exists.
:return:
dict() if group found
None if group is not found
"""
get_unix_group = netapp_utils.zapi.NaElement('name-mapping-unix-group-get-iter')
attributes = {
'query': {
'unix-group-info': {
'group-name': self.parameters['name'],
'vserver': self.parameters['vserver'],
}
}
}
get_unix_group.translate_struct(attributes)
try:
result = self.server.invoke_successfully(get_unix_group, enable_tunneling=True)
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
group_info = result['attributes-list']['unix-group-info']
group_details = dict()
else:
return None
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting UNIX group %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
for item_key, zapi_key in self.na_helper.zapi_string_keys.items():
group_details[item_key] = group_info[zapi_key]
for item_key, zapi_key in self.na_helper.zapi_int_keys.items():
group_details[item_key] = self.na_helper.get_value_for_int(from_zapi=True,
value=group_info[zapi_key])
if group_info.get_child_by_name('users') is not None:
group_details['users'] = [user.get_child_content('user-name')
for user in group_info.get_child_by_name('users').get_children()]
else:
group_details['users'] = None
return group_details
def create_unix_group(self):
"""
Creates an UNIX group in the specified Vserver
:return: None
"""
if self.parameters.get('id') is None:
self.module.fail_json(msg='Error: Missing a required parameter for create: (id)')
group_create = netapp_utils.zapi.NaElement('name-mapping-unix-group-create')
group_details = {}
for item in self.parameters:
if item in self.na_helper.zapi_string_keys:
zapi_key = self.na_helper.zapi_string_keys.get(item)
group_details[zapi_key] = self.parameters[item]
elif item in self.na_helper.zapi_bool_keys:
zapi_key = self.na_helper.zapi_bool_keys.get(item)
group_details[zapi_key] = self.na_helper.get_value_for_bool(from_zapi=False,
value=self.parameters[item])
elif item in self.na_helper.zapi_int_keys:
zapi_key = self.na_helper.zapi_int_keys.get(item)
group_details[zapi_key] = self.na_helper.get_value_for_int(from_zapi=True,
value=self.parameters[item])
group_create.translate_struct(group_details)
try:
self.server.invoke_successfully(group_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating UNIX group %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
if self.parameters.get('users') is not None:
self.modify_users_in_group()
def delete_unix_group(self):
"""
Deletes an UNIX group from a vserver
:return: None
"""
group_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'name-mapping-unix-group-destroy', **{'group-name': self.parameters['name']})
try:
self.server.invoke_successfully(group_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing UNIX group %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_unix_group(self, params):
"""
Modify an UNIX group from a vserver
:param params: modify parameters
:return: None
"""
# modify users requires separate zapi.
if 'users' in params:
self.modify_users_in_group()
if len(params) == 1:
return
group_modify = netapp_utils.zapi.NaElement('name-mapping-unix-group-modify')
group_details = {'group-name': self.parameters['name']}
for key in params:
if key in self.na_helper.zapi_int_keys:
zapi_key = self.na_helper.zapi_int_keys.get(key)
group_details[zapi_key] = self.na_helper.get_value_for_int(from_zapi=True,
value=params[key])
group_modify.translate_struct(group_details)
try:
self.server.invoke_successfully(group_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying UNIX group %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_users_in_group(self):
"""
Add/delete one or many users in a UNIX group
:return: None
"""
current_users = self.get_unix_group().get('users')
expect_users = self.parameters.get('users')
if current_users is None:
current_users = []
if expect_users[0] == '' and len(expect_users) == 1:
expect_users = []
users_to_remove = list(set(current_users) - set(expect_users))
users_to_add = list(set(expect_users) - set(current_users))
if len(users_to_add) > 0:
for user in users_to_add:
add_user = netapp_utils.zapi.NaElement('name-mapping-unix-group-add-user')
group_details = {'group-name': self.parameters['name'], 'user-name': user}
add_user.translate_struct(group_details)
try:
self.server.invoke_successfully(add_user, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(
msg='Error adding user %s to UNIX group %s: %s' % (user, self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
if len(users_to_remove) > 0:
for user in users_to_remove:
delete_user = netapp_utils.zapi.NaElement('name-mapping-unix-group-delete-user')
group_details = {'group-name': self.parameters['name'], 'user-name': user}
delete_user.translate_struct(group_details)
try:
self.server.invoke_successfully(delete_user, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(
msg='Error deleting user %s from UNIX group %s: %s' % (user, self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
"""
Autosupport log for unix_group
:return: None
"""
netapp_utils.ems_log_event("na_ontap_unix_group", self.server)
def apply(self):
"""
Invoke appropriate action based on playbook parameters
:return: None
"""
self.autosupport_log()
current = self.get_unix_group()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.parameters['state'] == 'present' and cd_action is None:
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_unix_group()
elif cd_action == 'delete':
self.delete_unix_group()
else:
self.modify_unix_group(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
obj = NetAppOntapUnixGroup()
obj.apply()
if __name__ == '__main__':
main()

@ -1,253 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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': 'community'}
DOCUMENTATION = '''
module: na_ontap_unix_user
short_description: NetApp ONTAP UNIX users
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create, delete or modify UNIX users local to ONTAP.
options:
state:
description:
- Whether the specified user should exist or not.
choices: ['present', 'absent']
default: 'present'
name:
description:
- Specifies user's UNIX account name.
- Non-modifiable.
required: true
group_id:
description:
- Specifies the primary group identification number for the UNIX user
- Required for create, modifiable.
vserver:
description:
- Specifies the Vserver for the UNIX user.
- Non-modifiable.
required: true
id:
description:
- Specifies an identification number for the UNIX user.
- Required for create, modifiable.
full_name:
description:
- Specifies the full name of the UNIX user
- Optional for create, modifiable.
'''
EXAMPLES = """
- name: Create UNIX User
na_ontap_unix_user:
state: present
name: SampleUser
vserver: ansibleVServer
group_id: 1
id: 2
full_name: Test User
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete UNIX User
na_ontap_unix_user:
state: absent
name: SampleUser
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapUnixUser(object):
"""
Common operations to manage users and roles.
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
group_id=dict(required=False, type='int'),
id=dict(required=False, type='int'),
full_name=dict(required=False, type='str'),
vserver=dict(required=True, type='str'),
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_unix_user(self):
"""
Checks if the UNIX user exists.
:return:
dict() if user found
None if user is not found
"""
get_unix_user = netapp_utils.zapi.NaElement('name-mapping-unix-user-get-iter')
attributes = {
'query': {
'unix-user-info': {
'user-name': self.parameters['name'],
'vserver': self.parameters['vserver'],
}
}
}
get_unix_user.translate_struct(attributes)
try:
result = self.server.invoke_successfully(get_unix_user, enable_tunneling=True)
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
user_info = result['attributes-list']['unix-user-info']
return {'group_id': int(user_info['group-id']),
'id': int(user_info['user-id']),
'full_name': user_info['full-name']}
return None
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting UNIX user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def create_unix_user(self):
"""
Creates an UNIX user in the specified Vserver
:return: None
"""
if self.parameters.get('group_id') is None or self.parameters.get('id') is None:
self.module.fail_json(msg='Error: Missing one or more required parameters for create: (group_id, id)')
user_create = netapp_utils.zapi.NaElement.create_node_with_children(
'name-mapping-unix-user-create', **{'user-name': self.parameters['name'],
'group-id': str(self.parameters['group_id']),
'user-id': str(self.parameters['id'])})
if self.parameters.get('full_name') is not None:
user_create.add_new_child('full-name', self.parameters['full_name'])
try:
self.server.invoke_successfully(user_create, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating UNIX user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_unix_user(self):
"""
Deletes an UNIX user from a vserver
:return: None
"""
user_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'name-mapping-unix-user-destroy', **{'user-name': self.parameters['name']})
try:
self.server.invoke_successfully(user_delete, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing UNIX user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_unix_user(self, params):
user_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'name-mapping-unix-user-modify', **{'user-name': self.parameters['name']})
for key in params:
if key == 'group_id':
user_modify.add_new_child('group-id', str(params['group_id']))
if key == 'id':
user_modify.add_new_child('user-id', str(params['id']))
if key == 'full_name':
user_modify.add_new_child('full-name', params['full_name'])
try:
self.server.invoke_successfully(user_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying UNIX user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def autosupport_log(self):
"""
Autosupport log for unix_user
:return: None
"""
netapp_utils.ems_log_event("na_ontap_unix_user", self.server)
def apply(self):
"""
Invoke appropriate action based on playbook parameters
:return: None
"""
self.autosupport_log()
current = self.get_unix_user()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.parameters['state'] == 'present' and cd_action is None:
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_unix_user()
elif cd_action == 'delete':
self.delete_unix_user()
else:
self.modify_unix_user(modify)
self.module.exit_json(changed=self.na_helper.changed)
def main():
obj = NetAppOntapUnixUser()
obj.apply()
if __name__ == '__main__':
main()

@ -1,389 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_user
short_description: NetApp ONTAP user configuration and management
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or destroy users.
options:
state:
description:
- Whether the specified user should exist or not.
choices: ['present', 'absent']
default: 'present'
name:
description:
- The name of the user to manage.
required: true
applications:
description:
- List of application to grant access to.
required: true
type: list
choices: ['console', 'http','ontapi','rsh','snmp','service-processor','sp','ssh','telnet']
aliases:
- application
authentication_method:
description:
- Authentication method for the application.
- Not all authentication methods are valid for an application.
- Valid authentication methods for each application are as denoted in I(authentication_choices_description).
- Password for console application
- Password, domain, nsswitch, cert for http application.
- Password, domain, nsswitch, cert for ontapi application.
- Community for snmp application (when creating SNMPv1 and SNMPv2 users).
- The usm and community for snmp application (when creating SNMPv3 users).
- Password for sp application.
- Password for rsh application.
- Password for telnet application.
- Password, publickey, domain, nsswitch for ssh application.
required: true
choices: ['community', 'password', 'publickey', 'domain', 'nsswitch', 'usm', 'cert']
set_password:
description:
- Password for the user account.
- It is ignored for creating snmp users, but is required for creating non-snmp users.
- For an existing user, this value will be used as the new password.
role_name:
description:
- The name of the role. Required when C(state=present)
lock_user:
description:
- Whether the specified user account is locked.
type: bool
vserver:
description:
- The name of the vserver to use.
required: true
'''
EXAMPLES = """
- name: Create User
na_ontap_user:
state: present
name: SampleUser
applications: ssh,console
authentication_method: password
set_password: apn1242183u1298u41
lock_user: True
role_name: vsadmin
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Delete User
na_ontap_user:
state: absent
name: SampleUser
applications: ssh
authentication_method: password
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapUser(object):
"""
Common operations to manage users and roles.
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
applications=dict(required=True, type='list', aliases=['application'],
choices=['console', 'http', 'ontapi', 'rsh', 'snmp',
'sp', 'service-processor', 'ssh', 'telnet'],),
authentication_method=dict(required=True, type='str',
choices=['community', 'password', 'publickey', 'domain', 'nsswitch', 'usm', 'cert']),
set_password=dict(required=False, type='str', no_log=True),
role_name=dict(required=False, type='str'),
lock_user=dict(required=False, type='bool'),
vserver=dict(required=True, type='str'),
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['role_name'])
],
supports_check_mode=True
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_user(self, application=None):
"""
Checks if the user exists.
:param: application: application to grant access to
:return:
Dictionary if user found
None if user is not found
"""
security_login_get_iter = netapp_utils.zapi.NaElement('security-login-get-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-account-info', **{'vserver': self.parameters['vserver'],
'user-name': self.parameters['name'],
'authentication-method': self.parameters['authentication_method']})
if application is not None:
query_details.add_new_child('application', application)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
security_login_get_iter.add_child_elem(query)
try:
result = self.server.invoke_successfully(security_login_get_iter,
enable_tunneling=False)
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) >= 1:
interface_attributes = result.get_child_by_name('attributes-list').\
get_child_by_name('security-login-account-info')
return_value = {
'lock_user': interface_attributes.get_child_content('is-locked'),
'role_name': interface_attributes.get_child_content('role-name')
}
return return_value
return None
except netapp_utils.zapi.NaApiError as error:
# Error 16034 denotes a user not being found.
if to_native(error.code) == "16034":
return None
# Error 16043 denotes the user existing, but the application missing
elif to_native(error.code) == "16043":
return None
else:
self.module.fail_json(msg='Error getting user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def create_user(self, application):
"""
creates the user for the given application and authentication_method
:param: application: application to grant access to
"""
user_create = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-create', **{'vserver': self.parameters['vserver'],
'user-name': self.parameters['name'],
'application': application,
'authentication-method': self.parameters['authentication_method'],
'role-name': self.parameters.get('role_name')})
if self.parameters.get('set_password') is not None:
user_create.add_new_child('password', self.parameters.get('set_password'))
try:
self.server.invoke_successfully(user_create,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def lock_given_user(self):
"""
locks the user
:return:
True if user locked
False if lock user is not performed
:rtype: bool
"""
user_lock = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-lock', **{'vserver': self.parameters['vserver'],
'user-name': self.parameters['name']})
try:
self.server.invoke_successfully(user_lock,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error locking user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def unlock_given_user(self):
"""
unlocks the user
:return:
True if user unlocked
False if unlock user is not performed
:rtype: bool
"""
user_unlock = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-unlock', **{'vserver': self.parameters['vserver'],
'user-name': self.parameters['name']})
try:
self.server.invoke_successfully(user_unlock,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
if to_native(error.code) == '13114':
return False
else:
self.module.fail_json(msg='Error unlocking user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
return True
def delete_user(self, application):
"""
deletes the user for the given application and authentication_method
:param: application: application to grant access to
"""
user_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-delete', **{'vserver': self.parameters['vserver'],
'user-name': self.parameters['name'],
'application': application,
'authentication-method': self.parameters['authentication_method']})
try:
self.server.invoke_successfully(user_delete,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def change_password(self):
"""
Changes the password
:return:
True if password updated
False if password is not updated
:rtype: bool
"""
# self.server.set_vserver(self.parameters['vserver'])
modify_password = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-modify-password', **{
'new-password': str(self.parameters.get('set_password')),
'user-name': self.parameters['name']})
try:
self.server.invoke_successfully(modify_password,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if to_native(error.code) == '13114':
return False
# if the user give the same password, instead of returning an error, return ok
if to_native(error.code) == '13214' and \
(error.message.startswith('New password must be different than last 6 passwords.')
or error.message.startswith('New password must be different than the old password.')):
return False
self.module.fail_json(msg='Error setting password for user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
self.server.set_vserver(None)
return True
def modify_user(self, application):
"""
Modify user
"""
user_modify = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-modify', **{'vserver': self.parameters['vserver'],
'user-name': self.parameters['name'],
'application': application,
'authentication-method': self.parameters['authentication_method'],
'role-name': self.parameters.get('role_name')})
try:
self.server.invoke_successfully(user_modify,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying user %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
create_delete_decision = {}
modify_decision = {}
netapp_utils.ems_log_event("na_ontap_user", self.server)
for application in self.parameters['applications']:
current = self.get_user(application)
if current is not None:
current['lock_user'] = self.na_helper.get_value_for_bool(True, current['lock_user'])
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action is not None:
create_delete_decision[application] = cd_action
else:
modify_decision[application] = self.na_helper.get_modified_attributes(current, self.parameters)
if not create_delete_decision and self.parameters.get('state') == 'present':
if self.parameters.get('set_password') is not None:
self.na_helper.changed = True
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
for application in create_delete_decision:
if create_delete_decision[application] == 'create':
self.create_user(application)
elif create_delete_decision[application] == 'delete':
self.delete_user(application)
lock_user = False
for application in modify_decision:
if 'role_name' in modify_decision[application]:
self.modify_user(application)
if 'lock_user' in modify_decision[application]:
lock_user = True
if lock_user:
if self.parameters.get('lock_user'):
self.lock_given_user()
else:
self.unlock_given_user()
if not create_delete_decision and self.parameters.get('set_password') is not None:
# if change password return false nothing has changed so we need to set changed to False
self.na_helper.changed = self.change_password()
self.module.exit_json(changed=self.na_helper.changed)
def main():
obj = NetAppOntapUser()
obj.apply()
if __name__ == '__main__':
main()

@ -1,268 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_user_role
short_description: NetApp ONTAP user role configuration and management
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create or destroy user roles
options:
state:
description:
- Whether the specified user should exist or not.
choices: ['present', 'absent']
default: present
name:
description:
- The name of the role to manage.
required: true
command_directory_name:
description:
- The command or command directory to which the role has an access.
required: true
access_level:
description:
- The name of the role to manage.
choices: ['none', 'readonly', 'all']
default: all
query:
description:
- A query for the role. The query must apply to the specified command or directory name.
- Use double quotes "" for modifying a existing query to none.
version_added: '2.8'
vserver:
description:
- The name of the vserver to use.
required: true
'''
EXAMPLES = """
- name: Create User Role
na_ontap_user_role:
state: present
name: ansibleRole
command_directory_name: volume
access_level: none
query: show
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Modify User Role
na_ontap_user_role:
state: present
name: ansibleRole
command_directory_name: volume
access_level: none
query: ""
vserver: ansibleVServer
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.netapp_module import NetAppModule
import ansible.module_utils.netapp as netapp_utils
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapUserRole(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present', 'absent'], default='present'),
name=dict(required=True, type='str'),
command_directory_name=dict(required=True, type='str'),
access_level=dict(required=False, type='str', default='all',
choices=['none', 'readonly', 'all']),
vserver=dict(required=True, type='str'),
query=dict(required=False, type='str')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_role(self):
"""
Checks if the role exists for specific command-directory-name.
:return:
True if role found
False if role is not found
:rtype: bool
"""
options = {'vserver': self.parameters['vserver'],
'role-name': self.parameters['name'],
'command-directory-name': self.parameters['command_directory_name']}
security_login_role_get_iter = netapp_utils.zapi.NaElement(
'security-login-role-get-iter')
query_details = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-role-info', **options)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(query_details)
security_login_role_get_iter.add_child_elem(query)
try:
result = self.server.invoke_successfully(
security_login_role_get_iter, enable_tunneling=False)
except netapp_utils.zapi.NaApiError as e:
# Error 16031 denotes a role not being found.
if to_native(e.code) == "16031":
return None
# Error 16039 denotes command directory not found.
elif to_native(e.code) == "16039":
return None
else:
self.module.fail_json(msg='Error getting role %s: %s' % (self.name, to_native(e)),
exception=traceback.format_exc())
if (result.get_child_by_name('num-records') and
int(result.get_child_content('num-records')) >= 1):
role_info = result.get_child_by_name('attributes-list').get_child_by_name('security-login-role-info')
result = {
'name': role_info['role-name'],
'access_level': role_info['access-level'],
'command_directory_name': role_info['command-directory-name'],
'query': role_info['role-query']
}
return result
return None
def create_role(self):
options = {'vserver': self.parameters['vserver'],
'role-name': self.parameters['name'],
'command-directory-name': self.parameters['command_directory_name'],
'access-level': self.parameters['access_level']}
if self.parameters.get('query'):
options['role-query'] = self.parameters['query']
role_create = netapp_utils.zapi.NaElement.create_node_with_children('security-login-role-create', **options)
try:
self.server.invoke_successfully(role_create,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating role %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def delete_role(self):
role_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'security-login-role-delete', **{'vserver': self.parameters['vserver'],
'role-name': self.parameters['name'],
'command-directory-name':
self.parameters['command_directory_name']})
try:
self.server.invoke_successfully(role_delete,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error removing role %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def modify_role(self, modify):
options = {'vserver': self.parameters['vserver'],
'role-name': self.parameters['name'],
'command-directory-name': self.parameters['command_directory_name']}
if 'access_level' in modify.keys():
options['access-level'] = self.parameters['access_level']
if 'query' in modify.keys():
options['role-query'] = self.parameters['query']
role_modify = netapp_utils.zapi.NaElement.create_node_with_children('security-login-role-modify', **options)
try:
self.server.invoke_successfully(role_modify,
enable_tunneling=False)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying role %s: %s' % (self.parameters['name'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
self.asup_log_for_cserver('na_ontap_user_role')
current = self.get_role()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
# if desired state specify empty quote query and current query is None, set desired query to None.
# otherwise na_helper.get_modified_attributes will detect a change.
if self.parameters.get('query') == '' and current is not None:
if current['query'] is None:
self.parameters['query'] = None
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_role()
elif cd_action == 'delete':
self.delete_role()
elif modify:
self.modify_role(modify)
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
netapp_utils.ems_log_event(event_name, self.server)
def main():
obj = NetAppOntapUserRole()
obj.apply()
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

@ -1,361 +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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
module: na_ontap_volume_autosize
short_description: NetApp ONTAP manage volume autosize
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Modify Volume AutoSize
options:
volume:
description:
- The name of the flexible volume for which we want to set autosize.
type: str
required: true
mode:
description:
- Specify the flexible volume's autosize mode of operation.
type: str
choices: ['grow', 'grow_shrink', 'off']
vserver:
description:
- Name of the vserver to use.
required: true
type: str
grow_threshold_percent:
description:
- Specifies the percentage of the flexible volume's capacity at which autogrow is initiated.
- The default grow threshold varies from 85% to 98%, depending on the volume size.
- It is an error for the grow threshold to be less than or equal to the shrink threshold.
- Range between 0 and 100
type: int
increment_size:
description:
- Specify the flexible volume's increment size using the following format < number > [k|m|g|t]
- The amount is the absolute size to set.
- The trailing 'k', 'm', 'g', and 't' indicates the desired units, namely 'kilobytes', 'megabytes', 'gigabytes', and 'terabytes' (respectively).
type: str
maximum_size:
description:
- Specify the flexible volume's maximum allowed size using the following format < number > [k|m|g|t]
- The amount is the absolute size to set.
- The trailing 'k', 'm', 'g', and 't' indicates the desired units, namely 'kilobytes', 'megabytes', 'gigabytes', and 'terabytes' (respectively).
- The default value is 20% greater than the volume size at the time autosize was enabled.
- It is an error for the maximum volume size to be less than the current volume size.
- It is also an error for the maximum size to be less than or equal to the minimum size.
type: str
minimum_size:
description:
- Specify the flexible volume's minimum allowed size using the following format < number > [k|m|g|t] The amount is the absolute size to set.
- The trailing 'k', 'm', 'g', and 't' indicates the desired units, namely 'kilobytes', 'megabytes', 'gigabytes', and 'terabytes' (respectively).
- The default value is the size of the volume at the time the 'grow_shrink' mode was enabled.
- It is an error for the minimum size to be greater than or equal to the maximum size.
type: str
reset:
description:
- "Sets the values of maximum_size, increment_size, minimum_size, grow_threshold_percent, shrink_threshold_percent and mode to their defaults"
type: bool
shrink_threshold_percent:
description:
- Specifies the percentage of the flexible volume's capacity at which autoshrink is initiated.
- The default shrink threshold is 50%. It is an error for the shrink threshold to be greater than or equal to the grow threshold.
- Range between 0 and 100
type: int
'''
EXAMPLES = """
- name: Modify volume autosize
na_ontap_volume_autosize:
hostname: 10.193.79.189
username: admin
password: netapp1!
volume: ansibleVolumesize12
mode: grow
grow_threshold_percent: 99
increment_size: 50m
maximum_size: 10g
minimum_size: 21m
shrink_threshold_percent: 40
vserver: ansible_vserver
- name: Reset volume autosize
na_ontap_volume_autosize:
hostname: 10.193.79.189
username: admin
password: netapp1!
volume: ansibleVolumesize12
reset: true
vserver: ansible_vserver
"""
RETURN = """
"""
import sys
import copy
import traceback
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.netapp import OntapRestAPI
from ansible.module_utils._text import to_native
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapVolumeAutosize(object):
def __init__(self):
self.use_rest = False
# Volume_autosize returns KB and not B like Volume so values are shifted down 1
self._size_unit_map = dict(
k=1,
m=1024,
g=1024 ** 2,
t=1024 ** 3,
)
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
volume=dict(required=True, type="str"),
mode=dict(required=False, choices=['grow', 'grow_shrink', 'off']),
vserver=dict(required=True, type='str'),
grow_threshold_percent=dict(required=False, type='int'),
increment_size=dict(required=False, type='str'),
maximum_size=dict(required=False, type='str'),
minimum_size=dict(required=False, type='str'),
reset=dict(required=False, type='bool'),
shrink_threshold_percent=dict(required=False, type='int')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True,
mutually_exclusive=[
['reset', 'maximum_size'],
['reset', 'increment_size'],
['reset', 'minimum_size'],
['reset', 'grow_threshold_percent'],
['reset', 'shrink_threshold_percent'],
['reset', 'mode']
]
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
# API should be used for ONTAP 9.6 or higher, ZAPI for lower version
self.restApi = OntapRestAPI(self.module)
if self.restApi.is_rest():
self.use_rest = True
# increment size and reset are not supported with rest api
if self.parameters.get('increment_size'):
self.module.fail_json(msg="Rest API does not support increment size, please switch to ZAPI")
if self.parameters.get('reset'):
self.module.fail_json(msg="Rest API does not support reset, please switch to ZAPI")
else:
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_volume_autosize(self, uuid=None):
"""
Get volume_autosize information from the ONTAP system
:return:
"""
if self.use_rest:
params = {'fields': 'autosize'}
api = 'storage/volumes/' + uuid
message, error = self.restApi.get(api, params)
if error is not None:
self.module.fail_json(msg="%s" % error)
return self._create_get_volume_return(message['autosize'])
else:
volume_autosize_info = netapp_utils.zapi.NaElement('volume-autosize-get')
volume_autosize_info.add_new_child('volume', self.parameters['volume'])
try:
result = self.server.invoke_successfully(volume_autosize_info, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching volume autosize infor for %s : %s' % (self.parameters['volume'],
to_native(error)),
exception=traceback.format_exc())
return self._create_get_volume_return(result)
def _create_get_volume_return(self, results):
"""
Create a return value from volume-autosize-get info file
:param results:
:return:
"""
return_value = {}
if self.use_rest:
if 'mode' in results:
return_value['mode'] = results['mode']
if 'grow_threshold' in results:
return_value['grow_threshold_percent'] = results['grow_threshold']
if 'maximum' in results:
return_value['maximum_size'] = results['maximum']
if 'minimum' in results:
return_value['minimum_size'] = results['minimum']
if 'shrink_threshold' in results:
return_value['shrink_threshold_percent'] = results['shrink_threshold']
else:
if results.get_child_by_name('mode'):
return_value['mode'] = results.get_child_content('mode')
if results.get_child_by_name('grow-threshold-percent'):
return_value['grow_threshold_percent'] = int(results.get_child_content('grow-threshold-percent'))
if results.get_child_by_name('increment-size'):
return_value['increment_size'] = results.get_child_content('increment-size')
if results.get_child_by_name('maximum-size'):
return_value['maximum_size'] = results.get_child_content('maximum-size')
if results.get_child_by_name('minimum-size'):
return_value['minimum_size'] = results.get_child_content('minimum-size')
if results.get_child_by_name('shrink-threshold-percent'):
return_value['shrink_threshold_percent'] = int(results.get_child_content('shrink-threshold-percent'))
if return_value == {}:
return_value = None
return return_value
def modify_volume_autosize(self, uuid=None):
"""
Modify a Volumes autosize
:return:
"""
if self.use_rest:
params = {}
data = {}
autosize = {}
if self.parameters.get('mode'):
autosize['mode'] = self.parameters['mode']
if self.parameters.get('grow_threshold_percent'):
autosize['grow_threshold'] = self.parameters['grow_threshold_percent']
if self.parameters.get('maximum_size'):
autosize['maximum'] = self.parameters['maximum_size']
if self.parameters.get('minimum_size'):
autosize['minimum'] = self.parameters['minimum_size']
if self.parameters.get('shrink_threshold_percent'):
autosize['shrink_threshold'] = self.parameters['shrink_threshold_percent']
data['autosize'] = autosize
api = "storage/volumes/" + uuid
message, error = self.restApi.patch(api, data, params)
if error is not None:
self.module.fail_json(msg="%s" % error)
else:
volume_autosize_info = netapp_utils.zapi.NaElement('volume-autosize-set')
volume_autosize_info.add_new_child('volume', self.parameters['volume'])
if self.parameters.get('mode'):
volume_autosize_info.add_new_child('mode', self.parameters['mode'])
if self.parameters.get('grow_threshold_percent'):
volume_autosize_info.add_new_child('grow-threshold-percent', str(self.parameters['grow_threshold_percent']))
if self.parameters.get('increment_size'):
volume_autosize_info.add_new_child('increment-size', self.parameters['increment_size'])
if self.parameters.get('reset') is not None:
volume_autosize_info.add_new_child('reset', str(self.parameters['reset']))
if self.parameters.get('maximum_size'):
volume_autosize_info.add_new_child('maximum-size', self.parameters['maximum_size'])
if self.parameters.get('minimum_size'):
volume_autosize_info.add_new_child('minimum-size', self.parameters['minimum_size'])
if self.parameters.get('shrink_threshold_percent'):
volume_autosize_info.add_new_child('shrink-threshold-percent', str(self.parameters['shrink_threshold_percent']))
try:
self.server.invoke_successfully(volume_autosize_info, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error modify volume autosize for %s: %s" % (self.parameters["volume"], to_native(error)),
exception=traceback.format_exc())
def modify_to_kb(self, converted_parameters):
"""
Save a converted parameter
:param converted_parameters: Dic of all parameters
:return:
"""
for attr in ['maximum_size', 'minimum_size', 'increment_size']:
if converted_parameters.get(attr):
if self.use_rest:
converted_parameters[attr] = self.convert_to_byte(attr, converted_parameters)
else:
converted_parameters[attr] = str(self.convert_to_kb(attr, converted_parameters))
return converted_parameters
def convert_to_kb(self, variable, converted_parameters):
"""
Convert a number 10m in to its correct KB size
:param variable: the Parameter we are going to covert
:param converted_parameters: Dic of all parameters
:return:
"""
if converted_parameters.get(variable)[-1] not in ['k', 'm', 'g', 't']:
self.module.fail_json(msg="%s must end with a k, m, g or t" % variable)
return self._size_unit_map[converted_parameters.get(variable)[-1]] * int(converted_parameters.get(variable)[:-1])
def convert_to_byte(self, variable, converted_parameters):
if converted_parameters.get(variable)[-1] not in ['k', 'm', 'g', 't']:
self.module.fail_json(msg="%s must end with a k, m, g or t" % variable)
return (self._size_unit_map[converted_parameters.get(variable)[-1]] * int(converted_parameters.get(variable)[:-1])) * 1024
def get_volume_uuid(self):
"""
Get a volume's UUID
:return: uuid of the volume
"""
params = {'fields': '*',
'name': self.parameters['volume'],
'svm.name': self.parameters['vserver']}
api = "storage/volumes"
message, error = self.restApi.get(api, params)
if error is not None:
self.module.fail_json(msg="%s" % error)
return message['records'][0]['uuid']
def apply(self):
# TODO Logging for rest
uuid = None
if not self.use_rest:
netapp_utils.ems_log_event("na_ontap_volume_autosize", self.server)
if self.use_rest:
# we only have the volume name, we need to the uuid for the volume
uuid = self.get_volume_uuid()
current = self.get_volume_autosize(uuid=uuid)
converted_parameters = copy.deepcopy(self.parameters)
converted_parameters = self.modify_to_kb(converted_parameters)
self.na_helper.get_modified_attributes(current, converted_parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
self.modify_volume_autosize(uuid=uuid)
if self.parameters.get('reset') is True:
self.modify_volume_autosize(uuid=uuid)
self.na_helper.changed = True
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Apply volume autosize operations from playbook
:return:
"""
obj = NetAppOntapVolumeAutosize()
obj.apply()
if __name__ == '__main__':
main()

@ -1,228 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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': 'community'}
DOCUMENTATION = '''
module: na_ontap_volume_clone
short_description: NetApp ONTAP manage volume clones.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.6'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create NetApp ONTAP volume clones.
- A FlexClone License is required to use this module
options:
state:
description:
- Whether volume clone should be created.
choices: ['present']
default: 'present'
parent_volume:
description:
- The parent volume of the volume clone being created.
required: true
type: str
name:
description:
- The name of the volume clone being created.
required: true
type: str
aliases:
- volume
vserver:
description:
- Vserver in which the volume clone should be created.
required: true
type: str
parent_snapshot:
description:
- Parent snapshot in which volume clone is created off.
type: str
parent_vserver:
description:
- Vserver of parent volume in which clone is created off.
type: str
qos_policy_group_name:
description:
- The qos-policy-group-name which should be set for volume clone.
type: str
space_reserve:
description:
- The space_reserve setting which should be used for the volume clone.
choices: ['volume', 'none']
volume_type:
description:
- The volume-type setting which should be used for the volume clone.
choices: ['rw', 'dp']
junction_path:
version_added: '2.8'
description:
- Junction path of the volume.
type: str
uid:
version_added: '2.9'
description:
- The UNIX user ID for the clone volume.
type: int
gid:
version_added: '2.9'
description:
- The UNIX group ID for the clone volume.
type: int
'''
EXAMPLES = """
- name: create volume clone
na_ontap_volume_clone:
state: present
username: "{{ netapp username }}"
password: "{{ netapp password }}"
hostname: "{{ netapp hostname }}"
vserver: vs_hack
parent_volume: normal_volume
name: clone_volume_7
space_reserve: none
parent_snapshot: backup1
junction_path: /clone_volume_7
uid: 1
gid: 1
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
from ansible.module_utils._text import to_native
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPVolumeClone(object):
"""
Creates a volume clone
"""
def __init__(self):
"""
Initialize the NetAppOntapVolumeClone class
"""
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, choices=['present'], default='present'),
parent_volume=dict(required=True, type='str'),
name=dict(required=True, type='str', aliases=["volume"]),
vserver=dict(required=True, type='str'),
parent_snapshot=dict(required=False, type='str', default=None),
parent_vserver=dict(required=False, type='str', default=None),
qos_policy_group_name=dict(required=False, type='str', default=None),
space_reserve=dict(required=False, choices=['volume', 'none'], default=None),
volume_type=dict(required=False, choices=['rw', 'dp']),
junction_path=dict(required=False, type='str', default=None),
uid=dict(required=False, type='int'),
gid=dict(required=False, type='int')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True,
required_together=[
['uid', 'gid']
]
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
return
def create_volume_clone(self):
"""
Creates a new volume clone
"""
clone_obj = netapp_utils.zapi.NaElement('volume-clone-create')
clone_obj.add_new_child("parent-volume", self.parameters['parent_volume'])
clone_obj.add_new_child("volume", self.parameters['volume'])
if self.parameters.get('qos_policy_group_name'):
clone_obj.add_new_child("qos-policy-group-name", self.parameters['qos_policy_group_name'])
if self.parameters.get('space_reserve'):
clone_obj.add_new_child("space-reserve", self.parameters['space_reserve'])
if self.parameters.get('parent_snapshot'):
clone_obj.add_new_child("parent-snapshot", self.parameters['parent_snapshot'])
if self.parameters.get('parent_vserver'):
clone_obj.add_new_child("parent-vserver", self.parameters['parent_vserver'])
if self.parameters.get('volume_type'):
clone_obj.add_new_child("volume-type", self.parameters['volume_type'])
if self.parameters.get('junction_path'):
clone_obj.add_new_child("junction-path", self.parameters['junction_path'])
if self.parameters.get('uid'):
clone_obj.add_new_child("uid", str(self.parameters['uid']))
clone_obj.add_new_child("gid", str(self.parameters['gid']))
try:
self.server.invoke_successfully(clone_obj, True)
except netapp_utils.zapi.NaApiError as exc:
self.module.fail_json(msg='Error creating volume clone: %s: %s' %
(self.parameters['volume'], to_native(exc)), exception=traceback.format_exc())
def get_volume_clone(self):
clone_obj = netapp_utils.zapi.NaElement('volume-clone-get')
clone_obj.add_new_child("volume", self.parameters['volume'])
try:
results = self.server.invoke_successfully(clone_obj, True)
if results.get_child_by_name('attributes'):
attributes = results.get_child_by_name('attributes')
info = attributes.get_child_by_name('volume-clone-info')
parent_volume = info.get_child_content('parent-volume')
# checking if clone volume name already used to create by same parent volume
if parent_volume == self.parameters['parent_volume']:
return results
except netapp_utils.zapi.NaApiError as error:
# Error 15661 denotes an volume clone not being found.
if to_native(error.code) == "15661":
pass
else:
self.module.fail_json(msg='Error fetching volume clone information %s: %s' %
(self.parameters['volume'], to_native(error)), exception=traceback.format_exc())
return None
def apply(self):
"""
Run Module based on play book
"""
netapp_utils.ems_log_event("na_ontap_volume_clone", self.server)
current = self.get_volume_clone()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_volume_clone()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Creates the NetApp Ontap Volume Clone object and runs the correct play task
"""
obj = NetAppONTAPVolumeClone()
obj.apply()
if __name__ == '__main__':
main()

@ -1,178 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_vscan
short_description: NetApp ONTAP Vscan enable/disable.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
notes:
- on demand task, on_access_policy and scanner_pools must be set up before running this module
description:
- Enable and Disable Vscan
options:
enable:
description:
- Whether to enable to disable a Vscan
type: bool
default: True
vserver:
description:
- the name of the data vserver to use.
required: true
type: str
'''
EXAMPLES = """
- name: Enable Vscan
na_ontap_vscan:
enable: True
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: trident_svm
- name: Disable Vscan
na_ontap_vscan:
enable: False
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: trident_svm
"""
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 import OntapRestAPI
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapVscan(object):
def __init__(self):
self.use_rest = False
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
enable=dict(type='bool', default=True),
vserver=dict(required=True, type='str'),
))
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)
# API should be used for ONTAP 9.6 or higher, Zapi for lower version
self.restApi = OntapRestAPI(self.module)
if self.restApi.is_rest():
self.use_rest = True
else:
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_vscan(self):
if self.use_rest:
params = {'fields': 'svm,enabled',
"svm.name": self.parameters['vserver']}
api = "protocols/vscan"
message, error = self.restApi.get(api, params)
if error:
self.module.fail_json(msg=error)
return message['records'][0]
else:
vscan_status_iter = netapp_utils.zapi.NaElement('vscan-status-get-iter')
vscan_status_info = netapp_utils.zapi.NaElement('vscan-status-info')
vscan_status_info.add_new_child('vserver', self.parameters['vserver'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(vscan_status_info)
vscan_status_iter.add_child_elem(query)
try:
result = self.server.invoke_successfully(vscan_status_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error getting Vscan info for Vserver %s: %s' %
(self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
return result.get_child_by_name('attributes-list').get_child_by_name('vscan-status-info')
def enable_vscan(self, uuid=None):
if self.use_rest:
params = {"svm.name": self.parameters['vserver']}
data = {"enabled": self.parameters['enable']}
api = "protocols/vscan/" + uuid
message, error = self.restApi.patch(api, data, params)
if error is not None:
self.module.fail_json(msg=error)
# self.module.fail_json(msg=repr(self.restApi.errors), log=repr(self.restApi.debug_logs))
else:
vscan_status_obj = netapp_utils.zapi.NaElement("vscan-status-modify")
vscan_status_obj.add_new_child('is-vscan-enabled', str(self.parameters['enable']))
try:
self.server.invoke_successfully(vscan_status_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg="Error Enable/Disabling Vscan: %s" % to_native(error), exception=traceback.format_exc())
def asup_log(self):
if self.use_rest:
# TODO: logging for Rest
return
else:
# Either we are using ZAPI, or REST failed when it should not
try:
netapp_utils.ems_log_event("na_ontap_vscan", self.server)
except Exception:
# TODO: we may fail to connect to REST or ZAPI, the line below shows REST issues only
# self.module.fail_json(msg=repr(self.restApi.errors), log=repr(self.restApi.debug_logs))
pass
def apply(self):
changed = False
self.asup_log()
current = self.get_vscan()
if self.use_rest:
if current['enabled'] != self.parameters['enable']:
if not self.module.check_mode:
self.enable_vscan(current['svm']['uuid'])
changed = True
else:
if current.get_child_content('is-vscan-enabled') != str(self.parameters['enable']).lower():
if not self.module.check_mode:
self.enable_vscan()
changed = True
self.module.exit_json(changed=changed)
def main():
"""
Execute action from playbook
"""
command = NetAppOntapVscan()
command.apply()
if __name__ == '__main__':
main()

@ -1,366 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_vscan_on_access_policy
short_description: NetApp ONTAP Vscan on access policy configuration.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Configure on access policy for Vscan (virus scan)
options:
state:
description:
- Whether a Vscan on Access policy is present or not
choices: ['present', 'absent']
default: present
vserver:
description:
- the name of the data vserver to use.
required: true
policy_name:
description:
- The name of the policy
required: true
file_ext_to_exclude:
description:
- File extensions for which On-Access scanning must not be performed.
file_ext_to_include:
description:
- File extensions for which On-Access scanning is considered. The default value is '*', which means that all files are considered for scanning except
- those which are excluded from scanning.
filters:
description:
- A list of filters which can be used to define the scope of the On-Access policy more precisely. The filters can be added in any order. Possible values
- scan_ro_volume Enable scans for read-only volume,
- scan_execute_access Scan only files opened with execute-access (CIFS only)
is_scan_mandatory:
description:
- Specifies whether access to a file is allowed if there are no external virus-scanning servers available for virus scanning. It is true if not provided at
the time of creating a policy.
type: bool
max_file_size:
description:
- Max file-size (in bytes) allowed for scanning. The default value of 2147483648 (2GB) is taken if not provided at the time of creating a policy.
paths_to_exclude:
description:
- File paths for which On-Access scanning must not be performed.
scan_files_with_no_ext:
description:
- Specifies whether files without any extension are considered for scanning or not.
default: True
'''
EXAMPLES = """
- name: Create Vscan On Access Policy
na_ontap_vscan_on_access_policy:
state: present
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: carchi-vsim2
policy_name: carchi_policy
file_ext_to_exclude: ['exe', 'yml']
- name: modify Vscan on Access Policy
na_ontap_vscan_on_access_policy:
state: present
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: carchi-vsim2
policy_name: carchi_policy
file_ext_to_exclude: ['exe', 'yml', 'py']
- name: Delete On Access Policy
na_ontap_vscan_on_access_policy:
state: absent
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: carchi-vsim2
policy_name: carchi_policy
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapVscanOnAccessPolicy(object):
"""
Create/Modify/Delete a Vscan OnAccess policy
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
policy_name=dict(required=True, type='str'),
file_ext_to_exclude=dict(required=False, type="list"),
file_ext_to_include=dict(required=False, type="list"),
filters=dict(required=False, type="list"),
is_scan_mandatory=dict(required=False, type='bool', default=False),
max_file_size=dict(required=False, type="int"),
paths_to_exclude=dict(required=False, type="list"),
scan_files_with_no_ext=dict(required=False, type=bool, default=True)
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
self.state = parameters['state']
self.vserver = parameters['vserver']
self.policy_name = parameters['policy_name']
self.file_ext_to_exclude = parameters['file_ext_to_exclude']
self.file_ext_to_include = parameters['file_ext_to_include']
self.filters = parameters['filters']
self.is_scan_mandatory = parameters['is_scan_mandatory']
self.max_file_size = parameters['max_file_size']
self.paths_to_exclude = parameters['paths_to_exclude']
self.scan_files_with_no_ext = parameters['scan_files_with_no_ext']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def exists_access_policy(self, policy_obj=None):
"""
Check if a Vscan Access policy exists
:return: True if Exist, False if it does not
"""
if policy_obj is None:
policy_obj = self.return_on_access_policy()
if policy_obj:
return True
else:
return False
def return_on_access_policy(self):
"""
Return a Vscan on Access Policy
:return: None if there is no access policy, return the policy if there is
"""
access_policy_obj = netapp_utils.zapi.NaElement('vscan-on-access-policy-get-iter')
access_policy_info = netapp_utils.zapi.NaElement('vscan-on-access-policy-info')
access_policy_info.add_new_child('policy-name', self.policy_name)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(access_policy_info)
access_policy_obj.add_child_elem(query)
try:
result = self.server.invoke_successfully(access_policy_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error searching Vscan on Access Policy %s: %s' %
(self.policy_name, to_native(error)), exception=traceback.format_exc())
if result.get_child_by_name('num-records'):
if int(result.get_child_content('num-records')) == 1:
return result
elif int(result.get_child_content('num-records')) > 1:
self.module.fail_json(msg='Multiple Vscan on Access Policy matching %s:' % self.policy_name)
return None
def create_on_access_policy(self):
"""
Create a Vscan on Access policy
:return: none
"""
access_policy_obj = netapp_utils.zapi.NaElement('vscan-on-access-policy-create')
access_policy_obj.add_new_child('policy-name', self.policy_name)
access_policy_obj.add_new_child('protocol', 'cifs')
access_policy_obj = self._fill_in_access_policy(access_policy_obj)
try:
self.server.invoke_successfully(access_policy_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating Vscan on Access Policy %s: %s' %
(self.policy_name, to_native(error)), exception=traceback.format_exc())
def delete_on_access_policy(self):
"""
Delete a Vscan On Access Policy
:return:
"""
access_policy_obj = netapp_utils.zapi.NaElement('vscan-on-access-policy-delete')
access_policy_obj.add_new_child('policy-name', self.policy_name)
try:
self.server.invoke_successfully(access_policy_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error Deleting Vscan on Access Policy %s: %s' %
(self.policy_name, to_native(error)), exception=traceback.format_exc())
def modify_on_access_policy(self):
"""
Modify a Vscan On Access policy
:return: nothing
"""
access_policy_obj = netapp_utils.zapi.NaElement('vscan-on-access-policy-modify')
access_policy_obj.add_new_child('policy-name', self.policy_name)
access_policy_obj = self._fill_in_access_policy(access_policy_obj)
try:
self.server.invoke_successfully(access_policy_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error Modifying Vscan on Access Policy %s: %s' %
(self.policy_name, to_native(error)), exception=traceback.format_exc())
def _fill_in_access_policy(self, access_policy_obj):
if self.is_scan_mandatory is not None:
access_policy_obj.add_new_child('is-scan-mandatory', str(self.is_scan_mandatory).lower())
if self.max_file_size:
access_policy_obj.add_new_child('max-file-size', str(self.max_file_size))
if self.scan_files_with_no_ext is not None:
access_policy_obj.add_new_child('scan-files-with-no-ext', str(self.scan_files_with_no_ext))
if self.file_ext_to_exclude:
ext_obj = netapp_utils.zapi.NaElement('file-ext-to-exclude')
access_policy_obj.add_child_elem(ext_obj)
for extension in self.file_ext_to_exclude:
ext_obj.add_new_child('file-extension', extension)
if self.file_ext_to_include:
ext_obj = netapp_utils.zapi.NaElement('file-ext-to-include')
access_policy_obj.add_child_elem(ext_obj)
for extension in self.file_ext_to_include:
ext_obj.add_new_child('file-extension', extension)
if self.filters:
ui_filter_obj = netapp_utils.zapi.NaElement('filters')
access_policy_obj.add_child_elem(ui_filter_obj)
for filter in self.filters:
ui_filter_obj.add_new_child('vscan-on-access-policy-ui-filter', filter)
if self.paths_to_exclude:
path_obj = netapp_utils.zapi.NaElement('paths-to-exclude')
access_policy_obj.add_child_elem(path_obj)
for path in self.paths_to_exclude:
path_obj.add_new_child('file-path', path)
return access_policy_obj
def has_policy_changed(self):
results = self.return_on_access_policy()
if results is None:
return False
try:
policy_obj = results.get_child_by_name('attributes-list').get_child_by_name('vscan-on-access-policy-info')
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error Accessing on access policy %s: %s' %
(self.policy_name, to_native(error)), exception=traceback.format_exc())
if self.is_scan_mandatory is not None:
if str(self.is_scan_mandatory).lower() != policy_obj.get_child_content('is-scan-mandatory'):
return True
if self.max_file_size:
if self.max_file_size != int(policy_obj.get_child_content('max-file-size')):
return True
if self.scan_files_with_no_ext is not None:
if str(self.scan_files_with_no_ext).lower() != policy_obj.get_child_content('scan-files-with-no-ext'):
return True
if self.file_ext_to_exclude:
# if no file-ext-to-exclude are given at creation, XML will not have a file-ext-to-exclude
if policy_obj.get_child_by_name('file-ext-to-exclude') is None:
return True
current_to_exclude = []
for each in policy_obj.get_child_by_name('file-ext-to-exclude').get_children():
current_to_exclude.append(each.get_content())
k = self._diff(self.file_ext_to_exclude, current_to_exclude)
# If the diff returns something the lists don't match and the policy has changed
if k:
return True
if self.file_ext_to_include:
# if no file-ext-to-include are given at creation, XML will not have a file-ext-to-include
if policy_obj.get_child_by_name('file-ext-to-include') is None:
return True
current_to_include = []
for each in policy_obj.get_child_by_name('file-ext-to-include').get_children():
current_to_include.append(each.get_content())
k = self._diff(self.file_ext_to_include, current_to_include)
# If the diff returns something the lists don't match and the policy has changed
if k:
return True
if self.filters:
if policy_obj.get_child_by_name('filters') is None:
return True
current_filters = []
for each in policy_obj.get_child_by_name('filters').get_children():
current_filters.append(each.get_content())
k = self._diff(self.filters, current_filters)
# If the diff returns something the lists don't match and the policy has changed
if k:
return True
if self.paths_to_exclude:
if policy_obj.get_child_by_name('paths-to-exclude') is None:
return True
current_paths_to_exlude = []
for each in policy_obj.get_child_by_name('paths-to-exclude').get_children():
current_paths_to_exlude.append(each.get_content())
k = self._diff(self.paths_to_exclude, current_paths_to_exlude)
# If the diff returns something the lists don't match and the policy has changed
if k:
return True
return False
def _diff(self, li1, li2):
"""
:param li1: list 1
:param li2: list 2
:return: a list contain items that are not on both lists
"""
li_dif = [i for i in li1 + li2 if i not in li1 or i not in li2]
return li_dif
def apply(self):
netapp_utils.ems_log_event("na_ontap_vscan_on_access_policy", self.server)
changed = False
policy_obj = self.return_on_access_policy()
if self.state == 'present':
if not self.exists_access_policy(policy_obj):
if not self.module.check_mode:
self.create_on_access_policy()
changed = True
else:
# Check if anything has changed first.
if self.has_policy_changed():
if not self.module.check_mode:
self.modify_on_access_policy()
changed = True
if self.state == 'absent':
if self.exists_access_policy(policy_obj):
if not self.module.check_mode:
self.delete_on_access_policy()
changed = True
self.module.exit_json(changed=changed)
def main():
"""
Execute action from playbook
"""
command = NetAppOntapVscanOnAccessPolicy()
command.apply()
if __name__ == '__main__':
main()

@ -1,313 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_vscan_on_demand_task
short_description: NetApp ONTAP Vscan on demand task configuration.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Configure on demand task for Vscan
options:
state:
description:
- Whether a Vscan on demand task is present or not
choices: ['present', 'absent']
default: present
vserver:
description:
- the name of the data vserver to use.
required: true
cross_junction:
description:
- Specifies whether the On-Demand task is allowed to cross volume junctions
type: bool
default: False
directory_recursion:
description:
- Specifies whether the On-Demand task is allowed to recursively scan through sub-directories.
type: bool
default: False
file_ext_to_exclude:
description:
- File-Extensions for which scanning must not be performed.
- File whose extension matches with both inclusion and exclusion list is not considered for scanning.
type: list
file_ext_to_include:
description:
- File extensions for which scanning is considered.
- The default value is '*', which means that all files are considered for scanning except those which are excluded from scanning.
- File whose extension matches with both inclusion and exclusion list is not considered for scanning.
type: list
max_file_size:
description:
- Max file-size (in bytes) allowed for scanning. The default value of 10737418240 (10GB) is taken if not provided at the time of creating a task.
paths_to_exclude:
description:
- File-paths for which scanning must not be performed.
type: list
report_directory:
description:
- Path from the vserver root where task report is created. The path must be a directory and provided in unix-format from the root of the Vserver.
- Example /vol1/on-demand-reports.
report_log_level:
description:
- Log level for the On-Demand report.
choices: ['verbose', 'info', 'error']
default: error
request_timeout:
description:
- Total request-service time-limit in seconds. If the virus-scanner does not respond within the provided time, scan will be timed out.
scan_files_with_no_ext:
description:
- Specifies whether files without any extension are considered for scanning or not.
type: bool
default: True
scan_paths:
description:
- List of paths that need to be scanned. The path must be provided in unix-format and from the root of the Vserver.
- Example /vol1/large_files.
type: list
scan_priority:
description:
- Priority of the On-Demand scan requests generated by this task.
choices: ['low', 'normal']
default: low
schedule:
description:
- Schedule of the task. The task will be run as per the schedule.
- For running the task immediately, vscan-on-demand-task-run api must be used after creating a task.
task_name:
description:
- Name of the task.
required: True
'''
EXAMPLES = """
- name: Create Vscan On Demand Task
na_ontap_vscan_on_demand_task:
state: present
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: carchi-vsim2
task_name: carchiOnDemand
scan_paths: /
report_directory: /
file_ext_to_exclude: ['py', 'yml']
max_file_size: 10737418241
paths_to_exclude: ['/tmp', '/var']
report_log_level: info
request_timeout: 60
- name: Delete Vscan On Demand Task
na_ontap_vscan_on_demand_task:
state: absent
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: carchi-vsim2
task_name: carchiOnDemand
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapVscanOnDemandTask(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
cross_junction=dict(required=False, type='bool', default=False),
directory_recursion=dict(required=False, type='bool', default=False),
file_ext_to_exclude=dict(required=False, type="list"),
file_ext_to_include=dict(required=False, type="list"),
max_file_size=dict(required=False, type="str"),
paths_to_exclude=dict(required=False, type="list"),
report_directory=dict(required=False, type='str'),
report_log_level=dict(required=False, choices=['verbose', 'info', 'error'], default='error'),
request_timeout=dict(required=False, type='str'),
scan_files_with_no_ext=dict(required=False, type='bool', default=True),
scan_paths=dict(required=False, type="list"),
scan_priority=dict(required=False, choices=['low', 'normal'], default='low'),
schedule=dict(required=False, type="str"),
task_name=dict(required=True, type="str")
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True,
required_if=[
["state", "present", ["report_directory", "scan_paths"]]
]
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def get_demand_task(self):
"""
Get a demand task
:return: A vscan-on-demand-task-info or None
"""
demand_task_iter = netapp_utils.zapi.NaElement("vscan-on-demand-task-get-iter")
demand_task_info = netapp_utils.zapi.NaElement("vscan-on-demand-task-info")
demand_task_info.add_new_child('task-name', self.parameters['task_name'])
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(demand_task_info)
demand_task_iter.add_child_elem(query)
try:
result = self.server.invoke_successfully(demand_task_iter, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error searching for Vscan on demand task %s: %s' %
(self.parameters['task_name'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
return result.get_child_by_name('attributes-list').get_child_by_name('vscan-on-demand-task-info')
return None
def create_demand_task(self):
"""
Create a Demand Task
:return: None
"""
demand_task_obj = netapp_utils.zapi.NaElement("vscan-on-demand-task-create")
# Required items first
demand_task_obj.add_new_child('report-directory', self.parameters['report_directory'])
demand_task_obj.add_new_child('task-name', self.parameters['task_name'])
scan_paths = netapp_utils.zapi.NaElement("scan-paths")
for scan_path in self.parameters['scan_paths']:
scan_paths.add_new_child('string', scan_path)
demand_task_obj.add_child_elem(scan_paths)
# Optional items next
if self.parameters.get('cross_junction'):
demand_task_obj.add_new_child('cross-junction', str(self.parameters['cross_junction']).lower())
if self.parameters.get('directory_recursion'):
demand_task_obj.add_new_child('directory-recursion', str(self.parameters['directory_recursion']).lower())
if self.parameters.get('file_ext_to_exclude'):
ext_to_exclude_obj = netapp_utils.zapi.NaElement('file-ext-to-exclude')
for exclude_file in self.parameters['file_ext_to_exclude']:
ext_to_exclude_obj.add_new_child('file-extension', exclude_file)
demand_task_obj.add_child_elem(ext_to_exclude_obj)
if self.parameters.get('file_ext_to_include'):
ext_to_include_obj = netapp_utils.zapi.NaElement('file-ext-to-include')
for include_file in self.parameters['file_ext_to_exclude']:
ext_to_include_obj.add_child_elem('file-extension', include_file)
demand_task_obj.add_child_elem(ext_to_include_obj)
if self.parameters.get('max_file_size'):
demand_task_obj.add_new_child('max-file-size', self.parameters['max_file_size'])
if self.parameters.get('paths_to_exclude'):
exclude_paths = netapp_utils.zapi.NaElement('paths-to-exclude')
for path in self.parameters['paths_to_exclude']:
exclude_paths.add_new_child('string', path)
demand_task_obj.add_child_elem(exclude_paths)
if self.parameters.get('report_log_level'):
demand_task_obj.add_new_child('report-log-level', self.parameters['report_log_level'])
if self.parameters.get('request_timeout'):
demand_task_obj.add_new_child('request-timeout', self.parameters['request_timeout'])
if self.parameters.get('scan_files_with_no_ext'):
demand_task_obj.add_new_child('scan-files-with-no-ext', str(self.parameters['scan_files_with_no_ext']).lower())
if self.parameters.get('scan_priority'):
demand_task_obj.add_new_child('scan-priority', self.parameters['scan_priority'].lower())
if self.parameters.get('schedule'):
demand_task_obj.add_new_child('schedule', self.parameters['schedule'])
try:
result = self.server.invoke_successfully(demand_task_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating on demand task %s: %s' %
(self.parameters['task_name'], to_native(error)),
exception=traceback.format_exc())
def delete_demand_task(self):
"""
Delete a Demand Task"
:return:
"""
demand_task_obj = netapp_utils.zapi.NaElement('vscan-on-demand-task-delete')
demand_task_obj.add_new_child('task-name', self.parameters['task_name'])
try:
self.server.invoke_successfully(demand_task_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting on demand task, %s: %s' %
(self.parameters['task_name'], to_native(error)),
exception=traceback.format_exc())
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def apply(self):
self.asup_log_for_cserver("na_ontap_vscan_on_demand_task")
current = self.get_demand_task()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if cd_action == 'create':
self.create_demand_task()
elif cd_action == 'delete':
self.delete_demand_task()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""
Execute action from playbook
"""
command = NetAppOntapVscanOnDemandTask()
command.apply()
if __name__ == '__main__':
main()

@ -1,240 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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': 'community'}
DOCUMENTATION = '''
module: na_ontap_vscan_scanner_pool
short_description: NetApp ONTAP Vscan Scanner Pools Configuration.
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.8'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Configure a Vscan Scanner Pool
options:
state:
description:
- Whether a Vscan Scanner pool is present or not
choices: ['present', 'absent']
default: present
vserver:
description:
- the name of the data vserver to use.
required: true
hostnames:
description:
- List of hostnames of Vscan servers which are allowed to connect to Data ONTAP
privileged_users:
description:
- List of privileged usernames. Username must be in the form "domain-name\\user-name"
scanner_pool:
description:
- the name of the virus scanner pool
required: true
scanner_policy:
description:
- The name of the Virus scanner Policy
choices: ['primary', 'secondary', 'idle']
'''
EXAMPLES = """
- name: Create and enable Scanner pool
na_ontap_vscan_scanner_pool:
state: present
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: carchi-vsim2
hostnames: ['name', 'name2']
privileged_users: ['sim.rtp.openeng.netapp.com\\admin', 'sim.rtp.openeng.netapp.com\\carchi']
scanner_pool: Scanner1
scanner_policy: primary
- name: Delete a scanner pool
na_ontap_vscan_scanner_pool:
state: absent
username: '{{ netapp_username }}'
password: '{{ netapp_password }}'
hostname: '{{ netapp_hostname }}'
vserver: carchi-vsim2
scanner_pool: Scanner1
"""
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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppOntapVscanScannerPool(object):
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
hostnames=dict(required=False, type='list'),
privileged_users=dict(required=False, type='list'),
scanner_pool=dict(required=True, type='str'),
scanner_policy=dict(required=False, choices=['primary', 'secondary', 'idle'])
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
supports_check_mode=True
)
parameters = self.module.params
self.hostnames = parameters['hostnames']
self.vserver = parameters['vserver']
self.privileged_users = parameters['privileged_users']
self.scanner_pool = parameters['scanner_pool']
self.state = parameters['state']
self.scanner_policy = parameters['scanner_policy']
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.vserver)
def create_scanner_pool(self):
"""
Create a Vscan Scanner Pool
:return: nothing
"""
scanner_pool_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-create')
if self.hostnames:
string_obj = netapp_utils.zapi.NaElement('hostnames')
scanner_pool_obj.add_child_elem(string_obj)
for hostname in self.hostnames:
string_obj.add_new_child('string', hostname)
if self.privileged_users:
users_obj = netapp_utils.zapi.NaElement('privileged-users')
scanner_pool_obj.add_child_elem(users_obj)
for user in self.privileged_users:
users_obj.add_new_child('privileged-user', user)
scanner_pool_obj.add_new_child('scanner-pool', self.scanner_pool)
try:
self.server.invoke_successfully(scanner_pool_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating Vscan Scanner Pool %s: %s' %
(self.scanner_pool, to_native(error)),
exception=traceback.format_exc())
def apply_policy(self):
"""
Apply a Scanner policy to a Scanner pool
:return: nothing
"""
apply_policy_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-apply-policy')
apply_policy_obj.add_new_child('scanner-policy', self.scanner_policy)
apply_policy_obj.add_new_child('scanner-pool', self.scanner_pool)
try:
self.server.invoke_successfully(apply_policy_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error applying policy %s to pool %s: %s' %
(self.scanner_policy, self.scanner_pool, to_native(error)),
exception=traceback.format_exc())
def get_scanner_pool(self):
"""
Check to see if a scanner pool exist or not
:return: True if it exist, False if it does not
"""
scanner_pool_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-get-iter')
scanner_pool_info = netapp_utils.zapi.NaElement('scan-scanner-pool-info')
scanner_pool_info.add_new_child('scanner-pool', self.scanner_pool)
scanner_pool_info.add_new_child('vserver', self.vserver)
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(scanner_pool_info)
scanner_pool_obj.add_child_elem(query)
try:
result = self.server.invoke_successfully(scanner_pool_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error searching for Vscan Scanner Pool %s: %s' %
(self.scanner_pool, to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) >= 1:
if result.get_child_by_name('attributes-list').get_child_by_name('vscan-scanner-pool-info').get_child_content(
'scanner-pool') == self.scanner_pool:
return result.get_child_by_name('attributes-list').get_child_by_name('vscan-scanner-pool-info')
return False
return False
def delete_scanner_pool(self):
"""
Delete a Scanner pool
:return: nothing
"""
scanner_pool_obj = netapp_utils.zapi.NaElement('vscan-scanner-pool-delete')
scanner_pool_obj.add_new_child('scanner-pool', self.scanner_pool)
try:
self.server.invoke_successfully(scanner_pool_obj, True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting Vscan Scanner Pool %s: %s' %
(self.scanner_pool, to_native(error)),
exception=traceback.format_exc())
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def apply(self):
self.asup_log_for_cserver("na_ontap_vscan_scanner_pool")
changed = False
scanner_pool_obj = self.get_scanner_pool()
if self.state == 'present':
if not scanner_pool_obj:
self.create_scanner_pool()
if self.scanner_policy:
self.apply_policy()
changed = True
# apply Scanner policy
if scanner_pool_obj:
if self.scanner_policy:
if scanner_pool_obj.get_child_content('scanner-policy') != self.scanner_policy:
self.apply_policy()
changed = True
if self.state == 'absent':
if scanner_pool_obj:
self.delete_scanner_pool()
changed = True
self.module.exit_json(changed=changed)
def main():
"""
Execute action from playbook
"""
command = NetAppOntapVscanScannerPool()
command.apply()
if __name__ == '__main__':
main()

@ -1,282 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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_ontap_vserver_cifs_security
short_description: NetApp ONTAP vserver CIFS security modification
extends_documentation_fragment:
- netapp.na_ontap
version_added: '2.9'
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- modify vserver CIFS security.
options:
vserver:
description:
- name of the vserver.
required: true
type: str
kerberos_clock_skew:
description:
- The clock skew in minutes is the tolerance for accepting tickets with time stamps that do not exactly match the host's system clock.
type: int
kerberos_ticket_age:
description:
- Determine the maximum amount of time in hours that a user's ticket may be used for the purpose of Kerberos authentication.
type: int
kerberos_renew_age:
description:
- Determine the maximum amount of time in days for which a ticket can be renewed.
type: int
kerberos_kdc_timeout:
description:
- Determine the timeout value in seconds for KDC connections.
type: int
is_signing_required:
description:
- Determine whether signing is required for incoming CIFS traffic.
type: bool
is_password_complexity_required:
description:
- Determine whether password complexity is required for local users.
type: bool
is_aes_encryption_enabled:
description:
- Determine whether AES-128 and AES-256 encryption mechanisms are enabled for Kerberos-related CIFS communication.
type: bool
is_smb_encryption_required:
description:
- Determine whether SMB encryption is required for incoming CIFS traffic.
type: bool
lm_compatibility_level:
description:
- Determine the LM compatibility level.
choices: ['lm_ntlm_ntlmv2_krb', 'ntlm_ntlmv2_krb', 'ntlmv2_krb', 'krb']
type: str
referral_enabled_for_ad_ldap:
description:
- Determine whether LDAP referral chasing is enabled or not for AD LDAP connections.
type: bool
session_security_for_ad_ldap:
description:
- Determine the level of security required for LDAP communications.
choices: ['none', 'sign', 'seal']
type: str
smb1_enabled_for_dc_connections:
description:
- Determine if SMB version 1 is used for connections to domain controllers.
choices: ['false', 'true', 'system_default']
type: str
smb2_enabled_for_dc_connections:
description:
- Determine if SMB version 2 is used for connections to domain controllers.
choices: ['false', 'true', 'system_default']
type: str
use_start_tls_for_ad_ldap:
description:
- Determine whether to use start_tls for AD LDAP connections.
type: bool
'''
EXAMPLES = '''
- name: modify cifs security
na_ontap_vserver_cifs_security:
vserver: ansible
hostname: "{{ hostname }}"
kerberos_clock_skew: 5
kerberos_ticket_age: 5
kerberos_renew_age: 10
kerberos_kdc_timeout: 5
is_signing_required: true
is_password_complexity_required: true
is_aes_encryption_enabled: true
is_smb_encryption_required: true
lm_compatibility_level: krb
smb1_enabled_for_dc_connections: true
smb2_enabled_for_dc_connections: true
use_start_tls_for_ad_ldap: true
username: username
password: password
- name: modify cifs security
na_ontap_vserver_cifs_security:
vserver: ansible
hostname: "{{ hostname }}"
referral_enabled_for_ad_ldap: true
username: username
password: password
- name: modify cifs security
na_ontap_vserver_cifs_security:
vserver: ansible
hostname: "{{ hostname }}"
session_security_for_ad_ldap: true
username: username
password: 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_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPCifsSecurity(object):
'''
modify vserver cifs security
'''
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
vserver=dict(required=True, type='str'),
kerberos_clock_skew=dict(required=False, type='int'),
kerberos_ticket_age=dict(required=False, type='int'),
kerberos_renew_age=dict(required=False, type='int'),
kerberos_kdc_timeout=dict(required=False, type='int'),
is_signing_required=dict(required=False, type='bool'),
is_password_complexity_required=dict(required=False, type='bool'),
is_aes_encryption_enabled=dict(required=False, type='bool'),
is_smb_encryption_required=dict(required=False, type='bool'),
lm_compatibility_level=dict(required=False, choices=['lm_ntlm_ntlmv2_krb', 'ntlm_ntlmv2_krb', 'ntlmv2_krb', 'krb']),
referral_enabled_for_ad_ldap=dict(required=False, type='bool'),
session_security_for_ad_ldap=dict(required=False, choices=['none', 'sign', 'seal']),
smb1_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']),
smb2_enabled_for_dc_connections=dict(required=False, choices=['false', 'true', 'system_default']),
use_start_tls_for_ad_ldap=dict(required=False, type='bool')
))
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_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=self.parameters['vserver'])
def cifs_security_get_iter(self):
"""
get current vserver cifs security.
:return: a dict of vserver cifs security
"""
cifs_security_get = netapp_utils.zapi.NaElement('cifs-security-get-iter')
query = netapp_utils.zapi.NaElement('query')
cifs_security = netapp_utils.zapi.NaElement('cifs-security')
cifs_security.add_new_child('vserver', self.parameters['vserver'])
query.add_child_elem(cifs_security)
cifs_security_get.add_child_elem(query)
cifs_security_details = dict()
try:
result = self.server.invoke_successfully(cifs_security_get, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching cifs security from %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
if result.get_child_by_name('num-records') and int(result.get_child_content('num-records')) > 0:
cifs_security_info = result.get_child_by_name('attributes-list').get_child_by_name('cifs-security')
cifs_security_details['kerberos_clock_skew'] = cifs_security_info.get_child_content('kerberos-clock-skew')
cifs_security_details['kerberos_ticket_age'] = cifs_security_info.get_child_content('kerberos-ticket-age')
cifs_security_details['kerberos_renew_age'] = cifs_security_info.get_child_content('kerberos-renew-age')
cifs_security_details['kerberos_kdc_timeout'] = cifs_security_info.get_child_content('kerberos-kdc-timeout')
cifs_security_details['is_signing_required'] = bool(cifs_security_info.get_child_content('is-signing-required'))
cifs_security_details['is_password_complexity_required'] = bool(cifs_security_info.get_child_content('is-password-complexity-required'))
cifs_security_details['is_aes_encryption_enabled'] = bool(cifs_security_info.get_child_content('is-aes-encryption-enabled'))
cifs_security_details['is_smb_encryption_required'] = bool(cifs_security_info.get_child_content('is-smb-encryption-required'))
cifs_security_details['lm_compatibility_level'] = cifs_security_info.get_child_content('lm-compatibility-level')
cifs_security_details['referral_enabled_for_ad_ldap'] = bool(cifs_security_info.get_child_content('referral-enabled-for-ad-ldap'))
cifs_security_details['session_security_for_ad_ldap'] = cifs_security_info.get_child_content('session-security-for-ad-ldap')
cifs_security_details['smb1_enabled_for_dc_connections'] = cifs_security_info.get_child_content('smb1-enabled-for-dc-connections')
cifs_security_details['smb2_enabled_for_dc_connections'] = cifs_security_info.get_child_content('smb2-enabled-for-dc-connections')
cifs_security_details['use_start_tls_for_ad_ldap'] = bool(cifs_security_info.get_child_content('use-start-tls-for-ad-ldap'))
return cifs_security_details
return None
def cifs_security_modify(self, modify):
"""
:param modify: A list of attributes to modify
:return: None
"""
cifs_security_modify = netapp_utils.zapi.NaElement('cifs-security-modify')
for attribute in modify:
cifs_security_modify.add_new_child(self.attribute_to_name(attribute), str(self.parameters[attribute]))
try:
self.server.invoke_successfully(cifs_security_modify, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as e:
self.module.fail_json(msg='Error modifying cifs security on %s: %s'
% (self.parameters['vserver'], to_native(e)),
exception=traceback.format_exc())
@staticmethod
def attribute_to_name(attribute):
return str.replace(attribute, '_', '-')
def apply(self):
"""Call modify operations."""
self.asup_log_for_cserver("na_ontap_vserver_cifs_security")
current = self.cifs_security_get_iter()
modify = self.na_helper.get_modified_attributes(current, self.parameters)
if self.na_helper.changed:
if self.module.check_mode:
pass
else:
if modify:
self.cifs_security_modify(modify)
self.module.exit_json(changed=self.na_helper.changed)
def asup_log_for_cserver(self, event_name):
"""
Fetch admin vserver for the given cluster
Create and Autosupport log event with the given module name
:param event_name: Name of the event log
:return: None
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event(event_name, cserver)
def main():
obj = NetAppONTAPCifsSecurity()
obj.apply()
if __name__ == '__main__':
main()

@ -1,276 +0,0 @@
#!/usr/bin/python
# (c) 2018-2019, 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 = '''
author: NetApp Ansible Team (@carchi8py) <ng-ansibleteam@netapp.com>
description:
- Create/Delete vserver peer
extends_documentation_fragment:
- netapp.na_ontap
module: na_ontap_vserver_peer
options:
state:
choices: ['present', 'absent']
description:
- Whether the specified vserver peer should exist or not.
default: present
vserver:
description:
- Specifies name of the source Vserver in the relationship.
applications:
choices: ['snapmirror', 'file_copy', 'lun_copy', 'flexcache']
description:
- List of applications which can make use of the peering relationship.
- FlexCache supported from ONTAP 9.5 onwards.
peer_vserver:
description:
- Specifies name of the peer Vserver in the relationship.
peer_cluster:
description:
- Specifies name of the peer Cluster.
- Required for creating the vserver peer relationship with a remote cluster
dest_hostname:
description:
- Destination hostname or IP address.
- Required for creating the vserver peer relationship with a remote cluster
dest_username:
description:
- Destination username.
- Optional if this is same as source username.
dest_password:
description:
- Destination password.
- Optional if this is same as source password.
short_description: NetApp ONTAP Vserver peering
version_added: "2.7"
'''
EXAMPLES = """
- name: Source vserver peer create
na_ontap_vserver_peer:
state: present
peer_vserver: ansible2
peer_cluster: ansibleCluster
vserver: ansible
applications: snapmirror
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
dest_hostname: "{{ netapp_dest_hostname }}"
- name: vserver peer delete
na_ontap_vserver_peer:
state: absent
peer_vserver: ansible2
vserver: ansible
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
import ansible.module_utils.netapp as netapp_utils
from ansible.module_utils.netapp_module import NetAppModule
HAS_NETAPP_LIB = netapp_utils.has_netapp_lib()
class NetAppONTAPVserverPeer(object):
"""
Class with vserver peer methods
"""
def __init__(self):
self.argument_spec = netapp_utils.na_ontap_host_argument_spec()
self.argument_spec.update(dict(
state=dict(required=False, type='str', choices=['present', 'absent'], default='present'),
vserver=dict(required=True, type='str'),
peer_vserver=dict(required=True, type='str'),
peer_cluster=dict(required=False, type='str'),
applications=dict(required=False, type='list', choices=['snapmirror', 'file_copy', 'lun_copy', 'flexcache']),
dest_hostname=dict(required=False, 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
)
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
if HAS_NETAPP_LIB is False:
self.module.fail_json(msg="the python NetApp-Lib module is required")
else:
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
if self.parameters.get('dest_hostname'):
self.module.params['hostname'] = self.parameters['dest_hostname']
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_server = netapp_utils.setup_na_ontap_zapi(module=self.module)
# reset to source host connection for asup logs
self.module.params['hostname'] = self.parameters['hostname']
def vserver_peer_get_iter(self):
"""
Compose NaElement object to query current vserver using peer-vserver and vserver parameters
:return: NaElement object for vserver-get-iter with query
"""
vserver_peer_get = netapp_utils.zapi.NaElement('vserver-peer-get-iter')
query = netapp_utils.zapi.NaElement('query')
vserver_peer_info = netapp_utils.zapi.NaElement('vserver-peer-info')
vserver_peer_info.add_new_child('peer-vserver', self.parameters['peer_vserver'])
vserver_peer_info.add_new_child('vserver', self.parameters['vserver'])
query.add_child_elem(vserver_peer_info)
vserver_peer_get.add_child_elem(query)
return vserver_peer_get
def vserver_peer_get(self):
"""
Get current vserver peer info
:return: Dictionary of current vserver peer details if query successful, else return None
"""
vserver_peer_get_iter = self.vserver_peer_get_iter()
vserver_info = dict()
try:
result = self.server.invoke_successfully(vserver_peer_get_iter, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching vserver peer %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
# return vserver peer details
if result.get_child_by_name('num-records') and \
int(result.get_child_content('num-records')) > 0:
vserver_peer_info = result.get_child_by_name('attributes-list').get_child_by_name('vserver-peer-info')
vserver_info['peer_vserver'] = vserver_peer_info.get_child_content('peer-vserver')
vserver_info['vserver'] = vserver_peer_info.get_child_content('vserver')
vserver_info['peer_state'] = vserver_peer_info.get_child_content('peer-state')
return vserver_info
return None
def vserver_peer_delete(self):
"""
Delete a vserver peer
"""
vserver_peer_delete = netapp_utils.zapi.NaElement.create_node_with_children(
'vserver-peer-delete', **{'peer-vserver': self.parameters['peer_vserver'],
'vserver': self.parameters['vserver']})
try:
self.server.invoke_successfully(vserver_peer_delete,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error deleting vserver peer %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
def get_peer_cluster_name(self):
"""
Get local cluster name
:return: cluster name
"""
cluster_info = netapp_utils.zapi.NaElement('cluster-identity-get')
try:
result = self.server.invoke_successfully(cluster_info, enable_tunneling=True)
return result.get_child_by_name('attributes').get_child_by_name(
'cluster-identity-info').get_child_content('cluster-name')
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error fetching peer cluster name for peer vserver %s: %s'
% (self.parameters['peer_vserver'], to_native(error)),
exception=traceback.format_exc())
def vserver_peer_create(self):
"""
Create a vserver peer
"""
if self.parameters.get('applications') is None:
self.module.fail_json(msg='applications parameter is missing')
if self.parameters.get('peer_cluster') is not None and self.parameters.get('dest_hostname') is None:
self.module.fail_json(msg='dest_hostname is required for peering a vserver in remote cluster')
if self.parameters.get('peer_cluster') is None:
self.parameters['peer_cluster'] = self.get_peer_cluster_name()
vserver_peer_create = netapp_utils.zapi.NaElement.create_node_with_children(
'vserver-peer-create', **{'peer-vserver': self.parameters['peer_vserver'],
'vserver': self.parameters['vserver'],
'peer-cluster': self.parameters['peer_cluster']})
applications = netapp_utils.zapi.NaElement('applications')
for application in self.parameters['applications']:
applications.add_new_child('vserver-peer-application', application)
vserver_peer_create.add_child_elem(applications)
try:
self.server.invoke_successfully(vserver_peer_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error creating vserver peer %s: %s'
% (self.parameters['vserver'], to_native(error)),
exception=traceback.format_exc())
def is_remote_peer(self):
if self.parameters.get('dest_hostname') is None or \
(self.parameters['dest_hostname'] == self.parameters['hostname']):
return False
return True
def vserver_peer_accept(self):
"""
Accept a vserver peer at destination
"""
# peer-vserver -> remote (source vserver is provided)
# vserver -> local (destination vserver is provided)
vserver_peer_accept = netapp_utils.zapi.NaElement.create_node_with_children(
'vserver-peer-accept', **{'peer-vserver': self.parameters['vserver'],
'vserver': self.parameters['peer_vserver']})
try:
self.dest_server.invoke_successfully(vserver_peer_accept, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error accepting vserver peer %s: %s'
% (self.parameters['peer_vserver'], to_native(error)),
exception=traceback.format_exc())
def apply(self):
"""
Apply action to create/delete or accept vserver peer
"""
results = netapp_utils.get_cserver(self.server)
cserver = netapp_utils.setup_na_ontap_zapi(module=self.module, vserver=results)
netapp_utils.ems_log_event("na_ontap_vserver_peer", cserver)
current = self.vserver_peer_get()
cd_action = self.na_helper.get_cd_action(current, self.parameters)
if cd_action == 'create':
self.vserver_peer_create()
# accept only if the peer relationship is on a remote cluster
if self.is_remote_peer():
self.vserver_peer_accept()
elif cd_action == 'delete':
self.vserver_peer_delete()
self.module.exit_json(changed=self.na_helper.changed)
def main():
"""Execute action"""
community_obj = NetAppONTAPVserverPeer()
community_obj.apply()
if __name__ == '__main__':
main()

@ -1,226 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2018, Sumit Kumar <sumit4@netapp.com>, chris Archibald <carchi@netapp.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class ModuleDocFragment(object):
DOCUMENTATION = r'''
options:
- See respective platform section for more details
requirements:
- See respective platform section for more details
notes:
- Ansible modules are available for the following NetApp Storage Platforms: E-Series, ONTAP, SolidFire
'''
# Documentation fragment for Cloud Volume Services on Azure NetApp (azure_rm_netapp)
AZURE_RM_NETAPP = r'''
options:
resource_group:
description:
- Name of the resource group.
required: true
type: str
requirements:
- python >= 2.7
- azure >= 2.0.0
- Python netapp-mgmt. Install using 'pip install netapp-mgmt'
- Python netapp-mgmt-netapp. Install using 'pip install netapp-mgmt-netapp'
- For authentication with Azure NetApp log in before you run your tasks or playbook with C(az login).
notes:
- The modules prefixed with azure_rm_netapp are built to support the Cloud Volume Services for Azure NetApp Files.
seealso:
- name: Sign in with Azure CLI
link: https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli?view=azure-cli-latest
description: How to authenticate using the C(az login) command.
'''
# Documentation fragment for ONTAP (na_ontap)
NA_ONTAP = r'''
options:
hostname:
description:
- The hostname or IP address of the ONTAP instance.
type: str
required: true
username:
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://mysupport.netapp.com/NOW/download/software/nmsdk/9.4/).
type: str
required: true
aliases: [ user ]
password:
description:
- Password for the specified user.
type: str
required: true
aliases: [ pass ]
https:
description:
- Enable and disable https
type: bool
default: no
validate_certs:
description:
- If set to C(no), the SSL certificates will not be validated.
- This should only set to C(False) used on personally controlled sites using self-signed certificates.
type: bool
default: yes
http_port:
description:
- Override the default port (80 or 443) with this port
type: int
ontapi:
description:
- The ontap api version to use
type: int
use_rest:
description:
- REST API if supported by the target system for all the resources and attributes the module requires. Otherwise will revert to ZAPI.
- Always -- will always use the REST API
- Never -- will always use the ZAPI
- Auto -- will try to use the REST Api
default: Auto
choices: ['Never', 'Always', 'Auto']
type: str
requirements:
- A physical or virtual clustered Data ONTAP system. The modules support Data ONTAP 9.1 and onward
- Ansible 2.6
- Python2 netapp-lib (2017.10.30) or later. Install using 'pip install netapp-lib'
- Python3 netapp-lib (2018.11.13) or later. Install using 'pip install netapp-lib'
- To enable http on the cluster you must run the following commands 'set -privilege advanced;' 'system services web modify -http-enabled true;'
notes:
- The modules prefixed with na\\_ontap are built to support the ONTAP storage platform.
'''
# Documentation fragment for ONTAP (na_cdot)
ONTAP = r'''
options:
hostname:
required: true
description:
- The hostname or IP address of the ONTAP instance.
username:
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://mysupport.netapp.com/NOW/download/software/nmsdk/9.4/).
aliases: ['user']
password:
required: true
description:
- Password for the specified user.
aliases: ['pass']
requirements:
- A physical or virtual clustered Data ONTAP system. The modules were developed with Clustered Data ONTAP 8.3
- Ansible 2.2
- netapp-lib (2015.9.25). Install using 'pip install netapp-lib'
notes:
- The modules prefixed with na\\_cdot are built to support the ONTAP storage platform.
'''
# Documentation fragment for SolidFire
SOLIDFIRE = r'''
options:
hostname:
required: true
description:
- The hostname or IP address of the SolidFire cluster.
username:
required: true
description:
- Please ensure that the user has the adequate permissions. For more information, please read the official documentation
U(https://mysupport.netapp.com/documentation/docweb/index.html?productID=62636&language=en-US).
aliases: ['user']
password:
required: true
description:
- Password for the specified user.
aliases: ['pass']
requirements:
- The modules were developed with SolidFire 10.1
- solidfire-sdk-python (1.1.0.92) or greater. Install using 'pip install solidfire-sdk-python'
notes:
- The modules prefixed with na\\_elementsw are built to support the SolidFire storage platform.
'''
# Documentation fragment for E-Series
ESERIES = r'''
options:
api_username:
required: true
type: str
description:
- The username to authenticate with the SANtricity Web Services Proxy or Embedded Web Services API.
api_password:
required: true
type: str
description:
- The password to authenticate with the SANtricity Web Services Proxy or Embedded Web Services API.
api_url:
required: true
type: str
description:
- The url to the SANtricity Web Services Proxy or Embedded Web Services API.
Example https://prod-1.wahoo.acme.com/devmgr/v2
validate_certs:
required: false
default: true
description:
- Should https certificates be validated?
type: bool
ssid:
required: false
type: str
default: 1
description:
- The ID of the array to manage. This value must be unique for each array.
notes:
- The E-Series Ansible modules require either an instance of the Web Services Proxy (WSP), to be available to manage
the storage-system, or an E-Series storage-system that supports the Embedded Web Services API.
- Embedded Web Services is currently available on the E2800, E5700, EF570, and newer hardware models.
- M(netapp_e_storage_system) may be utilized for configuring the systems managed by a WSP instance.
'''
# Documentation fragment for AWSCVS
AWSCVS = """
options:
api_key:
required: true
type: str
description:
- The access key to authenticate with the AWSCVS Web Services Proxy or Embedded Web Services API.
secret_key:
required: true
type: str
description:
- The secret_key to authenticate with the AWSCVS Web Services Proxy or Embedded Web Services API.
api_url:
required: true
type: str
description:
- The url to the AWSCVS Web Services Proxy or Embedded Web Services API.
validate_certs:
required: false
default: true
description:
- Should https certificates be validated?
type: bool
notes:
- The modules prefixed with aws\\_cvs\\_netapp are built to Manage AWS Cloud Volume Service .
"""

@ -86,12 +86,6 @@ lib/ansible/module_utils/gcp_utils.py future-import-boilerplate
lib/ansible/module_utils/gcp_utils.py metaclass-boilerplate
lib/ansible/module_utils/json_utils.py future-import-boilerplate
lib/ansible/module_utils/json_utils.py metaclass-boilerplate
lib/ansible/module_utils/netapp.py future-import-boilerplate
lib/ansible/module_utils/netapp.py metaclass-boilerplate
lib/ansible/module_utils/netapp_elementsw_module.py future-import-boilerplate
lib/ansible/module_utils/netapp_elementsw_module.py metaclass-boilerplate
lib/ansible/module_utils/netapp_module.py future-import-boilerplate
lib/ansible/module_utils/netapp_module.py metaclass-boilerplate
lib/ansible/module_utils/network/asa/asa.py future-import-boilerplate
lib/ansible/module_utils/network/asa/asa.py metaclass-boilerplate
lib/ansible/module_utils/network/checkpoint/checkpoint.py metaclass-boilerplate
@ -3324,160 +3318,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_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
lib/ansible/modules/storage/netapp/na_ontap_autosupport.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_autosupport.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_autosupport.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain_ports.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain_ports.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain_ports.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_broadcast_domain_ports.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_cg_snapshot.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_cg_snapshot.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_cg_snapshot.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_cifs.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_cifs.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_cifs_acl.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_cifs_server.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_cifs_server.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_cluster.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_cluster.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_cluster_ha.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_cluster_peer.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_cluster_peer.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_command.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_command.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_disks.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_dns.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_dns.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_dns.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_export_policy.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_export_policy_rule.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_export_policy_rule.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_fcp.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_fcp.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_firewall_policy.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_firewall_policy.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_firewall_policy.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_firmware_upgrade.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/netapp/na_ontap_flexcache.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_flexcache.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_igroup.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_igroup.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_igroup.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_igroup_initiator.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_igroup_initiator.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_igroup_initiator.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_info.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_info.py validate-modules:parameter-state-invalid-choice
lib/ansible/modules/storage/netapp/na_ontap_interface.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_interface.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_interface.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_ipspace.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_ipspace.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_iscsi.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_iscsi.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_job_schedule.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_job_schedule.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_job_schedule.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_kerberos_realm.py validate-modules:invalid-ansiblemodule-schema
lib/ansible/modules/storage/netapp/na_ontap_ldap_client.py validate-modules:invalid-ansiblemodule-schema
lib/ansible/modules/storage/netapp/na_ontap_ldap_client.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_license.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_license.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_license.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_lun.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_lun.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_lun_copy.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_lun_copy.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_lun_map.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_lun_map.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_motd.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_motd.py validate-modules:invalid-argument-name
lib/ansible/modules/storage/netapp/na_ontap_motd.py validate-modules:nonexistent-parameter-documented
lib/ansible/modules/storage/netapp/na_ontap_ndmp.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_net_ifgrp.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_net_ifgrp.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_net_ifgrp.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_net_port.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_net_port.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_net_port.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_net_routes.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_net_routes.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_net_subnet.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_net_subnet.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/netapp/na_ontap_net_subnet.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_net_vlan.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_net_vlan.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_nfs.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_nfs.py validate-modules:parameter-invalid
lib/ansible/modules/storage/netapp/na_ontap_nfs.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_node.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_ntp.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_ntp.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_nvme.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_nvme_namespace.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/netapp/na_ontap_nvme_namespace.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_nvme_subsystem.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_nvme_subsystem.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_ports.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_portset.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_portset.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_portset.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_qos_policy_group.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/netapp/na_ontap_qos_policy_group.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_qtree.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_qtree.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/netapp/na_ontap_security_key_manager.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_security_key_manager.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_service_processor_network.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_service_processor_network.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_snapmirror.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_snapshot.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_snapshot.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_snapshot_policy.py validate-modules:doc-elements-mismatch
lib/ansible/modules/storage/netapp/na_ontap_snapshot_policy.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_snapshot_policy.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_snmp.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_software_update.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_software_update.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_svm.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_svm.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_svm.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_svm_options.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_ucadapter.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_ucadapter.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_unix_group.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_unix_group.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_unix_group.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_unix_user.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_unix_user.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_user.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_user.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_user.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_user_role.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_user_role.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_volume.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_volume.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_volume.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_volume_clone.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_vscan_on_access_policy.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_vscan_on_access_policy.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_vscan_on_access_policy.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_vscan_on_demand_task.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_vscan_on_demand_task.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_vscan_on_demand_task.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_vscan_scanner_pool.py validate-modules:doc-missing-type
lib/ansible/modules/storage/netapp/na_ontap_vscan_scanner_pool.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_vscan_scanner_pool.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/netapp/na_ontap_vserver_peer.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/netapp/na_ontap_vserver_peer.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/netapp/na_ontap_vserver_peer.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/purestorage/purefa_alert.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_arrayname.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_banner.py validate-modules:doc-required-mismatch
@ -3655,8 +3495,6 @@ lib/ansible/plugins/doc_fragments/junos.py future-import-boilerplate
lib/ansible/plugins/doc_fragments/junos.py metaclass-boilerplate
lib/ansible/plugins/doc_fragments/meraki.py future-import-boilerplate
lib/ansible/plugins/doc_fragments/meraki.py metaclass-boilerplate
lib/ansible/plugins/doc_fragments/netapp.py future-import-boilerplate
lib/ansible/plugins/doc_fragments/netapp.py metaclass-boilerplate
lib/ansible/plugins/doc_fragments/nxos.py future-import-boilerplate
lib/ansible/plugins/doc_fragments/nxos.py metaclass-boilerplate
lib/ansible/plugins/doc_fragments/openstack.py future-import-boilerplate
@ -3948,104 +3786,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_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
test/units/modules/storage/netapp/test_na_ontap_autosupport.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_broadcast_domain.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_broadcast_domain.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cifs.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cifs.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cifs_server.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cifs_server.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cluster.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cluster.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cluster_peer.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_cluster_peer.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_command.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_command.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_export_policy_rule.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_export_policy_rule.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_firewall_policy.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_firewall_policy.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_flexcache.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_flexcache.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_igroup.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_igroup.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_igroup_initiator.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_igroup_initiator.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_info.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_info.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_interface.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_interface.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_ipspace.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_ipspace.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_job_schedule.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_job_schedule.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_lun_copy.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_lun_copy.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_lun_map.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_lun_map.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_motd.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_motd.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_ifgrp.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_ifgrp.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_port.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_port.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_routes.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_routes.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_subnet.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_net_subnet.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nfs.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nfs.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nvme.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nvme.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nvme_namespace.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nvme_namespace.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nvme_subsystem.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_nvme_subsystem.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_portset.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_portset.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_qos_policy_group.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_qos_policy_group.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_quotas.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_quotas.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_security_key_manager.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_security_key_manager.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_service_processor_network.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_service_processor_network.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_snapmirror.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_snapmirror.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_snapshot.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_snapshot.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_snapshot_policy.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_snapshot_policy.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_software_update.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_software_update.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_svm.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_svm.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_ucadapter.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_ucadapter.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_unix_group.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_unix_group.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_unix_user.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_unix_user.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_user.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_user.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_user_role.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_user_role.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_volume.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_volume.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_volume_clone.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_volume_clone.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan_on_access_policy.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan_on_access_policy.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan_on_demand_task.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan_on_demand_task.py metaclass-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan_scanner_pool.py future-import-boilerplate
test/units/modules/storage/netapp/test_na_ontap_vscan_scanner_pool.py metaclass-boilerplate
test/units/modules/system/test_iptables.py future-import-boilerplate
test/units/modules/system/test_iptables.py metaclass-boilerplate
test/units/modules/system/test_known_hosts.py future-import-boilerplate

@ -1,163 +0,0 @@
# (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
import pytest
from ansible.module_utils.ansible_release import __version__ as ansible_version
import ansible.module_utils.netapp as netapp_utils
try:
from unittest.mock import patch, mock_open
except ImportError:
from mock import patch, mock_open
from ansible.module_utils.six.moves.urllib.error import URLError
from ansible.module_utils.netapp import NetAppESeriesModule, create_multipart_formdata
from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
class StubNetAppESeriesModule(NetAppESeriesModule):
def __init__(self):
super(StubNetAppESeriesModule, self).__init__(ansible_options={})
class NetappTest(ModuleTestCase):
REQUIRED_PARAMS = {"api_username": "rw",
"api_password": "password",
"api_url": "http://localhost",
"ssid": "1"}
REQ_FUNC = "ansible.module_utils.netapp.request"
def _set_args(self, args=None):
module_args = self.REQUIRED_PARAMS.copy()
if args is not None:
module_args.update(args)
set_module_args(module_args)
def test_is_embedded_embedded_pass(self):
"""Verify is_embedded successfully returns True when an embedded web service's rest api is inquired."""
self._set_args()
with patch(self.REQ_FUNC, side_effect=[(200, {"version": "03.10.9000.0009"}),
(200, {"runningAsProxy": False})]):
base = StubNetAppESeriesModule()
self.assertTrue(base.is_embedded())
with patch(self.REQ_FUNC, side_effect=[(200, {"version": "03.10.9000.0009"}),
(200, {"runningAsProxy": True})]):
base = StubNetAppESeriesModule()
self.assertFalse(base.is_embedded())
def test_is_embedded_fail(self):
"""Verify exception is thrown when a web service's rest api fails to return about information."""
self._set_args()
with patch(self.REQ_FUNC, side_effect=[(200, {"version": "03.10.9000.0009"}), Exception()]):
with self.assertRaisesRegexp(AnsibleFailJson, r"Failed to retrieve the webservices about information!"):
base = StubNetAppESeriesModule()
base.is_embedded()
with patch(self.REQ_FUNC, side_effect=[(200, {"version": "03.10.9000.0009"}), URLError(""), Exception()]):
with self.assertRaisesRegexp(AnsibleFailJson, r"Failed to retrieve the webservices about information!"):
base = StubNetAppESeriesModule()
base.is_embedded()
def test_check_web_services_version_fail(self):
"""Verify that an unacceptable rest api version fails."""
minimum_required = "02.10.9000.0010"
test_set = ["02.10.9000.0009", "02.09.9000.0010", "01.10.9000.0010"]
self._set_args()
base = StubNetAppESeriesModule()
base.web_services_version = minimum_required
base.is_embedded = lambda: True
for current_version in test_set:
with patch(self.REQ_FUNC, return_value=(200, {"version": current_version})):
with self.assertRaisesRegexp(AnsibleFailJson, r"version does not meet minimum version required."):
base._check_web_services_version()
def test_check_web_services_version_pass(self):
"""Verify that an unacceptable rest api version fails."""
minimum_required = "02.10.9000.0010"
test_set = ["02.10.9000.0009", "02.09.9000.0010", "01.10.9000.0010"]
self._set_args()
base = StubNetAppESeriesModule()
base.web_services_version = minimum_required
base.is_embedded = lambda: True
for current_version in test_set:
with patch(self.REQ_FUNC, return_value=(200, {"version": current_version})):
with self.assertRaisesRegexp(AnsibleFailJson, r"version does not meet minimum version required."):
base._check_web_services_version()
def test_check_check_web_services_version_fail(self):
"""Verify exception is thrown when api url is invalid."""
invalid_url_forms = ["localhost:8080/devmgr/v2",
"http:///devmgr/v2"]
invalid_url_protocols = ["ssh://localhost:8080/devmgr/v2"]
for url in invalid_url_forms:
self._set_args({"api_url": url})
with patch(self.REQ_FUNC, return_value=(200, {"runningAsProxy": True})):
with self.assertRaisesRegexp(AnsibleFailJson, r"Failed to provide valid API URL."):
base = StubNetAppESeriesModule()
base._check_web_services_version()
for url in invalid_url_protocols:
self._set_args({"api_url": url})
with patch(self.REQ_FUNC, return_value=(200, {"runningAsProxy": True})):
with self.assertRaisesRegexp(AnsibleFailJson, r"Protocol must be http or https."):
base = StubNetAppESeriesModule()
base._check_web_services_version()
class MockONTAPConnection(object):
''' mock a server connection to ONTAP host '''
def __init__(self, kind=None, parm1=None):
''' save arguments '''
self.type = kind
self.parm1 = parm1
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'vserver':
xml = self.build_vserver_info(self.parm1)
self.xml_out = xml
return xml
@staticmethod
def build_vserver_info(vserver):
''' build xml data for vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = netapp_utils.zapi.NaElement('attributes-list')
attributes.add_node_with_children('vserver-info',
**{'vserver-name': vserver})
xml.add_child_elem(attributes)
return xml
@pytest.mark.skipif(not netapp_utils.has_netapp_lib(), reason="skipping as missing required netapp_lib")
def test_ems_log_event_version():
''' validate Ansible version is correctly read '''
source = 'unittest'
server = MockONTAPConnection()
netapp_utils.ems_log_event(source, server)
xml = server.xml_in
version = xml.get_child_content('app-version')
assert version == ansible_version
print("Ansible version: %s" % ansible_version)
@pytest.mark.skipif(not netapp_utils.has_netapp_lib(), reason="skipping as missing required netapp_lib")
def test_get_cserver():
''' validate cluster vserser name is correctly retrieved '''
svm_name = 'svm1'
server = MockONTAPConnection('vserver', svm_name)
cserver = netapp_utils.get_cserver(server)
assert cserver == svm_name

@ -1,217 +0,0 @@
# (c) 2018-2019, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
""" unit tests for Ansible module: na_ontap_aggregate """
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_aggregate \
import NetAppOntapAggregate as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, parm1=None, parm2=None):
''' save arguments '''
self.type = kind
self.parm1 = parm1
self.parm2 = parm2
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'aggregate':
xml = self.build_aggregate_info(self.parm1, self.parm2)
elif self.type == 'aggregate_fail':
raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test")
self.xml_out = xml
return xml
@staticmethod
def build_aggregate_info(vserver, aggregate):
''' build xml data for aggregatte and vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
data = {'num-records': 2,
'attributes-list':
{'aggr-attributes':
{'aggregate-name': aggregate,
'aggr-raid-attributes':
{'state': 'offline'
}
},
},
'vserver-info':
{'vserver-name': vserver
}
}
xml.translate_struct(data)
print(xml.to_string())
return xml
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)
self.server = MockONTAPConnection('aggregate', '12', 'name')
# whether to use a mock or a simulator
self.onbox = False
def set_default_args(self):
if self.onbox:
hostname = '10.193.74.78'
username = 'admin'
password = 'netapp1!'
name = 'name'
else:
hostname = 'hostname'
username = 'username'
password = 'password'
name = 'name'
return dict({
'hostname': hostname,
'username': username,
'password': password,
'name': name
})
def call_command(self, module_args):
''' utility function to call apply '''
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
my_obj.asup_log_for_cserver = Mock(return_value=None)
if not self.onbox:
# mock the connection
my_obj.server = MockONTAPConnection('aggregate', '12', 'test_name')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
return exc.value.args[0]['changed']
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'])
def test_is_mirrored(self):
module_args = {
'disk_count': '2',
'is_mirrored': 'true',
}
changed = self.call_command(module_args)
assert not changed
def test_disks_list(self):
module_args = {
'disk_count': '2',
'disks': ['1', '2'],
}
changed = self.call_command(module_args)
assert not changed
def test_mirror_disks(self):
module_args = {
'disk_count': '2',
'disks': ['1', '2'],
'mirror_disks': ['3', '4']
}
changed = self.call_command(module_args)
assert not changed
def test_spare_pool(self):
module_args = {
'disk_count': '2',
'spare_pool': 'Pool1'
}
changed = self.call_command(module_args)
assert not changed
def test_rename(self):
module_args = {
'from_name': 'test_name2'
}
changed = self.call_command(module_args)
assert not changed
def test_if_all_methods_catch_exception(self):
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'service_state': 'online'})
module_args.update({'unmount_volumes': 'True'})
module_args.update({'from_name': 'test_name2'})
set_module_args(module_args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('aggregate_fail')
with pytest.raises(AnsibleFailJson) as exc:
my_obj.aggr_get_iter(module_args.get('name'))
assert '' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.aggregate_online()
assert 'Error changing the state of aggregate' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.aggregate_offline()
assert 'Error changing the state of aggregate' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.create_aggr()
assert 'Error provisioning aggregate' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.delete_aggr()
assert 'Error removing aggregate' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.rename_aggregate()
assert 'Error renaming aggregate' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.asup_log_for_cserver = Mock(return_value=None)
my_obj.apply()
assert 'Error renaming: aggregate test_name2 does not exist' in exc.value.args[0]['msg']

@ -1,244 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_autosupport \
import NetAppONTAPasup as asup_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.kind = kind
self.params = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.kind == 'asup':
xml = self.build_asup_config_info(self.params)
self.xml_out = xml
return xml
@staticmethod
def build_asup_config_info(asup_data):
''' build xml data for asup-config '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {'attributes': {'autosupport-config-info': {
'node-name': asup_data['node_name'],
'is-enabled': asup_data['is_enabled'],
'is-support-enabled': asup_data['support'],
'proxy-url': asup_data['proxy_url'],
'post-url': asup_data['post_url'],
'transport': asup_data['transport'],
'is-node-in-subject': 'false',
'from': 'test',
'mail-hosts': [{'string': '1.2.3.4'}, {'string': '4.5.6.8'}],
'noteto': [{'mail-address': 'abc@test.com'},
{'mail-address': 'def@test.com'}],
}}}
xml.translate_struct(attributes)
return xml
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)
self.server = MockONTAPConnection()
self.mock_asup = {
'node_name': 'test-vsim1',
'transport': 'https',
'support': 'false',
'post_url': 'testbed.netapp.com/asupprod/post/1.0/postAsup',
'proxy_url': 'something.com',
}
def mock_args(self):
return {
'node_name': self.mock_asup['node_name'],
'transport': self.mock_asup['transport'],
'support': self.mock_asup['support'],
'post_url': self.mock_asup['post_url'],
'proxy_url': self.mock_asup['proxy_url'],
'hostname': 'host',
'username': 'admin',
'password': 'password',
}
def get_asup_mock_object(self, kind=None, enabled='false'):
"""
Helper method to return an na_ontap_volume object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_volume object
"""
asup_obj = asup_module()
asup_obj.autosupport_log = Mock(return_value=None)
if kind is None:
asup_obj.server = MockONTAPConnection()
else:
data = self.mock_asup
data['is_enabled'] = enabled
asup_obj.server = MockONTAPConnection(kind='asup', data=data)
return asup_obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
asup_module()
print('Info: %s' % exc.value.args[0]['msg'])
def test_enable_asup(self):
''' a more interesting test '''
data = self.mock_args()
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_asup_mock_object('asup').apply()
assert exc.value.args[0]['changed']
def test_disable_asup(self):
''' a more interesting test '''
# enable asup
data = self.mock_args()
data['state'] = 'absent'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_asup_mock_object(kind='asup', enabled='true').apply()
assert exc.value.args[0]['changed']
def test_result_from_get(self):
''' Check boolean and service_state conversion from get '''
data = self.mock_args()
set_module_args(data)
obj = self.get_asup_mock_object(kind='asup', enabled='true')
# constructed based on valued passed in self.mock_asup and build_asup_config_info()
expected_dict = {
'node_name': 'test-vsim1',
'service_state': 'started',
'support': False,
'hostname_in_subject': False,
'transport': self.mock_asup['transport'],
'post_url': self.mock_asup['post_url'],
'proxy_url': self.mock_asup['proxy_url'],
'from_address': 'test',
'mail_hosts': ['1.2.3.4', '4.5.6.8'],
'partner_addresses': [],
'to_addresses': [],
'noteto': ['abc@test.com', 'def@test.com']
}
result = obj.get_autosupport_config()
assert result == expected_dict
def test_modify_config(self):
''' Check boolean and service_state conversion from get '''
data = self.mock_args()
data['transport'] = 'http'
data['post_url'] = 'somethingelse.com'
data['proxy_url'] = 'somethingelse.com'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_asup_mock_object('asup').apply()
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_autosupport.NetAppONTAPasup.get_autosupport_config')
def test_get_called(self, get_asup):
data = self.mock_args()
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_asup_mock_object('asup').apply()
get_asup.assert_called_with()
@patch('ansible.modules.storage.netapp.na_ontap_autosupport.NetAppONTAPasup.modify_autosupport_config')
def test_modify_called(self, modify_asup):
data = self.mock_args()
data['transport'] = 'http'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_asup_mock_object('asup').apply()
modify_asup.assert_called_with({'transport': 'http', 'service_state': 'started'})
@patch('ansible.modules.storage.netapp.na_ontap_autosupport.NetAppONTAPasup.modify_autosupport_config')
@patch('ansible.modules.storage.netapp.na_ontap_autosupport.NetAppONTAPasup.get_autosupport_config')
def test_modify_not_called(self, get_asup, modify_asup):
data = self.mock_args()
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_asup_mock_object('asup').apply()
get_asup.assert_called_with()
modify_asup.assert_not_called()
def test_modify_packet(self):
'''check XML construction for nested attributes like mail-hosts, noteto, partner-address, and to'''
data = self.mock_args()
set_module_args(data)
obj = self.get_asup_mock_object(kind='asup', enabled='true')
modify_dict = {
'noteto': ['one@test.com'],
'partner_addresses': ['firstpartner@test.com'],
'mail_hosts': ['1.1.1.1'],
'to_addresses': ['first@test.com']
}
obj.modify_autosupport_config(modify_dict)
xml = obj.server.xml_in
for key in ['noteto', 'to', 'partner-address']:
assert xml[key] is not None
assert xml[key]['mail-address'] is not None
assert xml['noteto']['mail-address'] == modify_dict['noteto'][0]
assert xml['to']['mail-address'] == modify_dict['to_addresses'][0]
assert xml['partner-address']['mail-address'] == modify_dict['partner_addresses'][0]
assert xml['mail-hosts'] is not None
assert xml['mail-hosts']['string'] is not None
assert xml['mail-hosts']['string'] == modify_dict['mail_hosts'][0]

@ -1,308 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_broadcast_domain \
import NetAppOntapBroadcastDomain as broadcast_domain_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.type = kind
self.params = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'broadcast_domain':
xml = self.build_broadcast_domain_info(self.params)
self.xml_out = xml
return xml
@staticmethod
def build_broadcast_domain_info(broadcast_domain_details):
''' build xml data for broadcast_domain info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'net-port-broadcast-domain-info': {
'broadcast-domain': broadcast_domain_details['name'],
'ipspace': broadcast_domain_details['ipspace'],
'mtu': broadcast_domain_details['mtu'],
'ports': {
'port-info': {
'port': 'test_port_1'
}
}
}
}
}
xml.translate_struct(attributes)
return xml
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)
self.server = MockONTAPConnection()
self.mock_broadcast_domain = {
'name': 'test_broadcast_domain',
'mtu': '1000',
'ipspace': 'Default',
'ports': 'test_port_1'
}
def mock_args(self):
return {
'name': self.mock_broadcast_domain['name'],
'ipspace': self.mock_broadcast_domain['ipspace'],
'mtu': self.mock_broadcast_domain['mtu'],
'ports': self.mock_broadcast_domain['ports'],
'hostname': 'test',
'username': 'test_user',
'password': 'test_pass!'
}
def get_broadcast_domain_mock_object(self, kind=None, data=None):
"""
Helper method to return an na_ontap_volume object
:param kind: passes this param to MockONTAPConnection()
:param data: passes this param to MockONTAPConnection()
:return: na_ontap_volume object
"""
broadcast_domain_obj = broadcast_domain_module()
broadcast_domain_obj.asup_log_for_cserver = Mock(return_value=None)
broadcast_domain_obj.cluster = Mock()
broadcast_domain_obj.cluster.invoke_successfully = Mock()
if kind is None:
broadcast_domain_obj.server = MockONTAPConnection()
else:
if data is None:
broadcast_domain_obj.server = MockONTAPConnection(kind='broadcast_domain', data=self.mock_broadcast_domain)
else:
broadcast_domain_obj.server = MockONTAPConnection(kind='broadcast_domain', data=data)
return broadcast_domain_obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
broadcast_domain_module()
print('Info: %s' % exc.value.args[0]['msg'])
def test_get_nonexistent_net_route(self):
''' Test if get_broadcast_domain returns None for non-existent broadcast_domain '''
set_module_args(self.mock_args())
result = self.get_broadcast_domain_mock_object().get_broadcast_domain()
assert result is None
def test_create_error_missing_broadcast_domain(self):
''' Test if create throws an error if broadcast_domain is not specified'''
data = self.mock_args()
del data['name']
set_module_args(data)
with pytest.raises(AnsibleFailJson) as exc:
self.get_broadcast_domain_mock_object('broadcast_domain').create_broadcast_domain()
msg = 'missing required arguments: name'
assert exc.value.args[0]['msg'] == msg
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.create_broadcast_domain')
def test_successful_create(self, create_broadcast_domain):
''' Test successful create '''
data = self.mock_args()
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_broadcast_domain_mock_object().apply()
assert exc.value.args[0]['changed']
create_broadcast_domain.assert_called_with()
def test_create_idempotency(self):
''' Test create idempotency '''
set_module_args(self.mock_args())
obj = self.get_broadcast_domain_mock_object('broadcast_domain')
with pytest.raises(AnsibleExitJson) as exc:
obj.apply()
assert not exc.value.args[0]['changed']
def test_modify_mtu(self):
''' Test successful modify mtu '''
data = self.mock_args()
data['mtu'] = '1200'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_broadcast_domain_mock_object('broadcast_domain').apply()
assert exc.value.args[0]['changed']
def test_modify_ipspace_idempotency(self):
''' Test modify ipsapce idempotency'''
data = self.mock_args()
data['ipspace'] = 'Cluster'
set_module_args(data)
with pytest.raises(AnsibleFailJson) as exc:
self.get_broadcast_domain_mock_object('broadcast_domain').apply()
msg = 'A domain ipspace can not be modified after the domain has been created.'
assert exc.value.args[0]['msg'] == msg
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.add_broadcast_domain_ports')
def test_add_ports(self, add_broadcast_domain_ports):
''' Test successful modify ports '''
data = self.mock_args()
data['ports'] = 'test_port_1,test_port_2'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_broadcast_domain_mock_object('broadcast_domain').apply()
assert exc.value.args[0]['changed']
add_broadcast_domain_ports.assert_called_with(['test_port_2'])
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.delete_broadcast_domain_ports')
def test_delete_ports(self, delete_broadcast_domain_ports):
''' Test successful modify ports '''
data = self.mock_args()
data['ports'] = ''
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_broadcast_domain_mock_object('broadcast_domain').apply()
assert exc.value.args[0]['changed']
delete_broadcast_domain_ports.assert_called_with(['test_port_1'])
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.modify_broadcast_domain')
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.split_broadcast_domain')
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.get_broadcast_domain')
def test_split_broadcast_domain(self, get_broadcast_domain, split_broadcast_domain, modify_broadcast_domain):
''' Test successful split broadcast domain '''
data = self.mock_args()
data['from_name'] = 'test_broadcast_domain'
data['name'] = 'test_broadcast_domain_2'
data['ports'] = 'test_port_2'
set_module_args(data)
current = {
'name': 'test_broadcast_domain',
'mtu': '1000',
'ipspace': 'Default',
'ports': ['test_port_1,test_port2']
}
get_broadcast_domain.side_effect = [
None,
current,
current
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_broadcast_domain_mock_object().apply()
assert exc.value.args[0]['changed']
modify_broadcast_domain.assert_not_called()
split_broadcast_domain.assert_called_with()
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.delete_broadcast_domain')
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.modify_broadcast_domain')
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.get_broadcast_domain')
def test_split_broadcast_domain_modify_delete(self, get_broadcast_domain, modify_broadcast_domain, delete_broadcast_domain):
''' Test successful split broadcast domain '''
data = self.mock_args()
data['from_name'] = 'test_broadcast_domain'
data['name'] = 'test_broadcast_domain_2'
data['ports'] = 'test_port_1,test_port_2'
data['mtu'] = '1200'
set_module_args(data)
current = {
'name': 'test_broadcast_domain',
'mtu': '1000',
'ipspace': 'Default',
'ports': ['test_port_1,test_port2']
}
get_broadcast_domain.side_effect = [
None,
current,
current
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_broadcast_domain_mock_object().apply()
assert exc.value.args[0]['changed']
delete_broadcast_domain.assert_called_with('test_broadcast_domain')
modify_broadcast_domain.assert_called_with()
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.get_broadcast_domain')
def test_split_broadcast_domain_not_exist(self, get_broadcast_domain):
''' Test successful split broadcast domain '''
data = self.mock_args()
data['from_name'] = 'test_broadcast_domain'
data['name'] = 'test_broadcast_domain_2'
data['ports'] = 'test_port_2'
set_module_args(data)
get_broadcast_domain.side_effect = [
None,
None,
]
with pytest.raises(AnsibleFailJson) as exc:
self.get_broadcast_domain_mock_object().apply()
msg = 'A domain can not be split if it does not exist.'
assert exc.value.args[0]['msg'], msg
@patch('ansible.modules.storage.netapp.na_ontap_broadcast_domain.NetAppOntapBroadcastDomain.split_broadcast_domain')
def test_split_broadcast_domain_idempotency(self, split_broadcast_domain):
''' Test successful split broadcast domain '''
data = self.mock_args()
data['from_name'] = 'test_broadcast_domain'
data['ports'] = 'test_port_1'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_broadcast_domain_mock_object('broadcast_domain').apply()
assert exc.value.args[0]['changed'] is False
split_broadcast_domain.assert_not_called()

@ -1,227 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit tests ONTAP Ansible module: na_ontap_cifs '''
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
from ansible.modules.storage.netapp.na_ontap_cifs \
import NetAppONTAPCifsShare as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None):
''' save arguments '''
self.type = kind
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'cifs':
xml = self.build_cifs_info()
elif self.type == 'cifs_fail':
raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test")
self.xml_out = xml
return xml
@staticmethod
def build_cifs_info():
''' build xml data for cifs-info '''
xml = netapp_utils.zapi.NaElement('xml')
data = {'num-records': 1, 'attributes-list': {'cifs-share': {
'share-name': 'test',
'path': '/test',
'vscan-fileop-profile': 'standard',
'share-properties': [{'cifs-share-properties': 'browsable'},
{'cifs-share-properties': 'oplocks'}],
'symlink-properties': [{'cifs-share-symlink-properties': 'enable'},
{'cifs-share-symlink-properties': 'read_only'}],
}}}
xml.translate_struct(data)
print(xml.to_string())
return xml
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)
self.server = MockONTAPConnection()
self.onbox = False
def set_default_args(self):
if self.onbox:
hostname = '10.193.77.37'
username = 'admin'
password = 'netapp1!'
share_name = 'test'
path = '/test'
share_properties = 'browsable,oplocks'
symlink_properties = 'disable'
vscan_fileop_profile = 'standard'
vserver = 'abc'
else:
hostname = '10.193.77.37'
username = 'admin'
password = 'netapp1!'
share_name = 'test'
path = '/test'
share_properties = 'show_previous_versions'
symlink_properties = 'disable'
vscan_fileop_profile = 'no_scan'
vserver = 'abc'
return dict({
'hostname': hostname,
'username': username,
'password': password,
'share_name': share_name,
'path': path,
'share_properties': share_properties,
'symlink_properties': symlink_properties,
'vscan_fileop_profile': vscan_fileop_profile,
'vserver': vserver
})
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'])
def test_ensure_cifs_get_called(self):
''' fetching details of cifs '''
set_module_args(self.set_default_args())
my_obj = my_module()
my_obj.server = self.server
cifs_get = my_obj.get_cifs_share()
print('Info: test_cifs_share_get: %s' % repr(cifs_get))
assert not bool(cifs_get)
def test_ensure_apply_for_cifs_called(self):
''' creating cifs share and checking idempotency '''
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
if not self.onbox:
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
if not self.onbox:
my_obj.server = MockONTAPConnection('cifs')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_cifs.NetAppONTAPCifsShare.create_cifs_share')
def test_cifs_create_called(self, create_cifs_share):
''' creating cifs'''
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection()
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_apply: %s' % repr(exc.value))
create_cifs_share.assert_called_with()
@patch('ansible.modules.storage.netapp.na_ontap_cifs.NetAppONTAPCifsShare.delete_cifs_share')
def test_cifs_delete_called(self, delete_cifs_share):
''' deleting cifs'''
module_args = {}
module_args.update(self.set_default_args())
module_args['state'] = 'absent'
set_module_args(module_args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('cifs')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_apply: %s' % repr(exc.value))
delete_cifs_share.assert_called_with()
@patch('ansible.modules.storage.netapp.na_ontap_cifs.NetAppONTAPCifsShare.modify_cifs_share')
def test_cifs_modify_called(self, modify_cifs_share):
''' modifying cifs'''
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('cifs')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_apply: %s' % repr(exc.value))
modify_cifs_share.assert_called_with()
def test_if_all_methods_catch_exception(self):
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('cifs_fail')
with pytest.raises(AnsibleFailJson) as exc:
my_obj.create_cifs_share()
assert 'Error creating cifs-share' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.delete_cifs_share()
assert 'Error deleting cifs-share' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.modify_cifs_share()
assert 'Error modifying cifs-share' in exc.value.args[0]['msg']

@ -1,221 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit tests ONTAP Ansible module: na_ontap_cifs_server '''
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
from ansible.modules.storage.netapp.na_ontap_cifs_server \
import NetAppOntapcifsServer as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, parm1=None, parm2=None):
''' save arguments '''
self.type = kind
self.parm1 = parm1
self.parm2 = parm2
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'cifs_server':
xml = self.build_vserver_info(self.parm1, self.parm2)
self.xml_out = xml
return xml
@staticmethod
def build_vserver_info(cifs_server, admin_status):
''' build xml data for cifs-server-info '''
xml = netapp_utils.zapi.NaElement('xml')
data = {'num-records': 1,
'attributes-list': {'cifs-server-config': {'cifs-server': cifs_server,
'administrative-status': admin_status}}}
xml.translate_struct(data)
print(xml.to_string())
return xml
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)
self.server = MockONTAPConnection()
self.use_vsim = False
def set_default_args(self):
if self.use_vsim:
hostname = '10.193.77.154'
username = 'admin'
password = 'netapp1!'
cifs_server = 'test'
vserver = 'ansible_test'
else:
hostname = 'hostname'
username = 'username'
password = 'password'
cifs_server = 'name'
vserver = 'vserver'
return dict({
'hostname': hostname,
'username': username,
'password': password,
'cifs_server_name': cifs_server,
'vserver': vserver
})
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'])
def test_ensure_cifs_server_get_called(self):
''' a more interesting test '''
set_module_args(self.set_default_args())
my_obj = my_module()
my_obj.server = self.server
cifs_server = my_obj.get_cifs_server()
print('Info: test_cifs_server_get: %s' % repr(cifs_server))
assert cifs_server is None
def test_ensure_cifs_server_apply_for_create_called(self):
''' creating cifs server and checking idempotency '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'cifs_server_name': 'create'})
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_server_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cifs_server', 'create', 'up')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_server_apply_for_create: %s' % repr(exc.value))
assert not exc.value.args[0]['changed']
def test_ensure_cifs_server_apply_for_delete_called(self):
''' deleting cifs server and checking idempotency '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'cifs_server_name': 'delete'})
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cifs_server', 'delete', 'up')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_server_apply: %s' % repr(exc.value))
assert not exc.value.args[0]['changed']
module_args.update({'state': 'absent'})
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cifs_server', 'delete', 'up')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cifs_server_delete: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
def test_ensure_start_cifs_server_called(self):
''' starting cifs server and checking idempotency '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'cifs_server_name': 'delete'})
module_args.update({'service_state': 'started'})
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cifs_server', 'test', 'up')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_ensure_start_cifs_server: %s' % repr(exc.value))
assert not exc.value.args[0]['changed']
module_args.update({'service_state': 'stopped'})
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cifs_server', 'test', 'up')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_ensure_start_cifs_server: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
def test_ensure_stop_cifs_server_called(self):
''' stopping cifs server and checking idempotency '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'cifs_server_name': 'delete'})
module_args.update({'service_state': 'stopped'})
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cifs_server', 'test', 'down')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_ensure_stop_cifs_server: %s' % repr(exc.value))
assert not exc.value.args[0]['changed']
module_args.update({'service_state': 'started'})
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cifs_server', 'test', 'down')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_ensure_stop_cifs_server: %s' % repr(exc.value))
assert exc.value.args[0]['changed']

@ -1,198 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit tests ONTAP Ansible module: na_ontap_cluster '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_cluster \
import NetAppONTAPCluster as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None):
''' save arguments '''
self.type = kind
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'cluster':
xml = self.build_cluster_info()
elif self.type == 'cluster_fail':
raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test")
self.xml_out = xml
return xml
def autosupport_log(self):
''' mock autosupport log'''
return None
@staticmethod
def build_cluster_info():
''' build xml data for cluster-info '''
xml = netapp_utils.zapi.NaElement('xml')
data = {'license-v2-status': {'package': 'cifs', 'method': 'site'}}
xml.translate_struct(data)
print(xml.to_string())
return xml
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)
self.server = MockONTAPConnection()
self.use_vsim = False
def set_default_args(self):
if self.use_vsim:
hostname = '10.193.77.37'
username = 'admin'
password = 'netapp1!'
license_package = 'CIFS'
node_serial_number = '123'
license_code = 'AAA'
cluster_name = 'abc'
else:
hostname = '10.193.77.37'
username = 'admin'
password = 'netapp1!'
license_package = 'CIFS'
node_serial_number = '123'
cluster_ip_address = '0.0.0.0'
license_code = 'AAA'
cluster_name = 'abc'
return dict({
'hostname': hostname,
'username': username,
'password': password,
'license_package': license_package,
'node_serial_number': node_serial_number,
'license_code': license_code,
'cluster_name': cluster_name,
'cluster_ip_address': cluster_ip_address
})
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'])
def test_ensure_license_get_called(self):
''' fetching details of license '''
set_module_args(self.set_default_args())
my_obj = my_module()
my_obj.server = self.server
license_get = my_obj.get_licensing_status()
print('Info: test_license_get: %s' % repr(license_get))
assert not bool(license_get)
def test_ensure_apply_for_cluster_called(self):
''' creating license and checking idempotency '''
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
my_obj.autosupport_log = Mock(return_value=None)
if not self.use_vsim:
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cluster_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cluster')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cluster_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_cluster.NetAppONTAPCluster.create_cluster')
def test_cluster_create_called(self, cluster_create):
''' creating cluster'''
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
my_obj.autosupport_log = Mock(return_value=None)
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cluster')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_cluster_apply: %s' % repr(exc.value))
cluster_create.assert_called_with()
def test_if_all_methods_catch_exception(self):
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
my_obj = my_module()
if not self.use_vsim:
my_obj.server = MockONTAPConnection('cluster_fail')
with pytest.raises(AnsibleFailJson) as exc:
my_obj.get_licensing_status()
assert 'Error checking license status' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.create_cluster()
assert 'Error creating cluster' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.cluster_join()
assert 'Error adding node to cluster' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.license_v2_add()
assert 'Error adding license' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.license_v2_delete()
assert 'Error deleting license' in exc.value.args[0]['msg']

@ -1,211 +0,0 @@
''' unit tests ONTAP Ansible module: na_ontap_cluster_peer '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_cluster_peer \
import NetAppONTAPClusterPeer as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, parm1=None):
''' save arguments '''
self.type = kind
self.data = parm1
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'cluster_peer':
xml = self.build_cluster_peer_info(self.data)
self.xml_out = xml
return xml
@staticmethod
def build_cluster_peer_info(parm1):
''' build xml data for vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'cluster-peer-info': {
'cluster-name': parm1['dest_cluster_name'],
'peer-addresses': parm1['dest_intercluster_lifs']
}
}
}
xml.translate_struct(attributes)
return xml
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)
self.server = MockONTAPConnection()
self.mock_cluster_peer = {
'source_intercluster_lifs': '1.2.3.4,1.2.3.5',
'dest_intercluster_lifs': '1.2.3.6,1.2.3.7',
'passphrase': 'netapp123',
'dest_hostname': '10.20.30.40',
'dest_cluster_name': 'cluster2',
'hostname': 'hostname',
'username': 'username',
'password': 'password',
}
def mock_args(self):
return {
'source_intercluster_lifs': self.mock_cluster_peer['source_intercluster_lifs'],
'dest_intercluster_lifs': self.mock_cluster_peer['dest_intercluster_lifs'],
'passphrase': self.mock_cluster_peer['passphrase'],
'dest_hostname': self.mock_cluster_peer['dest_hostname'],
'dest_cluster_name': 'cluster2',
'hostname': 'hostname',
'username': 'username',
'password': 'password',
}
def get_cluster_peer_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_cluster_peer object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_cluster_peer object
"""
cluster_peer_obj = my_module()
cluster_peer_obj.asup_log_for_cserver = Mock(return_value=None)
cluster_peer_obj.cluster = Mock()
cluster_peer_obj.cluster.invoke_successfully = Mock()
if kind is None:
cluster_peer_obj.server = MockONTAPConnection()
cluster_peer_obj.dest_server = MockONTAPConnection()
else:
cluster_peer_obj.server = MockONTAPConnection(kind=kind, parm1=self.mock_cluster_peer)
cluster_peer_obj.dest_server = MockONTAPConnection(kind=kind, parm1=self.mock_cluster_peer)
return cluster_peer_obj
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.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_successful_create(self, cluster_peer_get):
''' Test successful create '''
set_module_args(self.mock_args())
cluster_peer_get.side_effect = [
None,
None
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object().apply()
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_create_idempotency(self, cluster_peer_get):
''' Test create idempotency '''
set_module_args(self.mock_args())
current1 = {
'cluster_name': 'cluster1',
'peer-addresses': '1.2.3.6,1.2.3.7'
}
current2 = {
'cluster_name': 'cluster2',
'peer-addresses': '1.2.3.4,1.2.3.5'
}
cluster_peer_get.side_effect = [
current1,
current2
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object('cluster_peer').apply()
assert not exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_successful_delete(self, cluster_peer_get):
''' Test delete existing interface '''
data = self.mock_args()
data['state'] = 'absent'
data['source_cluster_name'] = 'cluster1'
set_module_args(data)
current1 = {
'cluster_name': 'cluster1',
'peer-addresses': '1.2.3.6,1.2.3.7'
}
current2 = {
'cluster_name': 'cluster2',
'peer-addresses': '1.2.3.4,1.2.3.5'
}
cluster_peer_get.side_effect = [
current1,
current2
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object('cluster_peer').apply()
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_cluster_peer.NetAppONTAPClusterPeer.cluster_peer_get')
def test_delete_idempotency(self, cluster_peer_get):
''' Test delete idempotency '''
data = self.mock_args()
data['state'] = 'absent'
data['source_cluster_name'] = 'cluster2'
set_module_args(data)
cluster_peer_get.side_effect = [
None,
None
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_cluster_peer_mock_object().apply()
assert not exc.value.args[0]['changed']

@ -1,169 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test for ONTAP Command Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_command \
import NetAppONTAPCommand as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, parm1=None):
''' save arguments '''
self.type = kind
self.parm1 = parm1
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
# print(xml.to_string())
if self.type == 'version':
priv = xml.get_child_content('priv')
xml = self.build_version(priv)
self.xml_out = xml
return xml
@staticmethod
def build_version(priv):
''' build xml data for version '''
prefix = 'NetApp Release'
if priv == 'advanced':
prefix = '\n' + prefix
xml = netapp_utils.zapi.NaElement('results')
xml.add_new_child('cli-output', prefix)
# print(xml.to_string())
return xml
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)
self.server = MockONTAPConnection(kind='version')
# whether to use a mock or a simulator
self.use_vsim = False
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'])
@staticmethod
def set_default_args(vsim=False):
''' populate hostname/username/password '''
if vsim:
hostname = '10.10.10.10'
username = 'admin'
password = 'admin'
else:
hostname = 'hostname'
username = 'username'
password = 'password'
return dict({
'hostname': hostname,
'username': username,
'password': password,
'https': True,
'validate_certs': False
})
def call_command(self, module_args, vsim=False):
''' utility function to call apply '''
module_args.update(self.set_default_args(vsim=vsim))
set_module_args(module_args)
my_obj = my_module()
my_obj.asup_log_for_cserver = Mock(return_value=None)
if not vsim:
# mock the connection
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
msg = exc.value.args[0]['msg']
return msg
def test_default_priv(self):
''' make sure privilege is not required '''
module_args = {
'command': 'version',
}
msg = self.call_command(module_args, vsim=self.use_vsim)
needle = b'<cli-output>NetApp Release'
assert needle in msg
print('Version (raw): %s' % msg)
def test_admin_priv(self):
''' make sure admin is accepted '''
module_args = {
'command': 'version',
'privilege': 'admin',
}
msg = self.call_command(module_args, vsim=self.use_vsim)
needle = b'<cli-output>NetApp Release'
assert needle in msg
print('Version (raw): %s' % msg)
def test_advanced_priv(self):
''' make sure advanced is not required '''
module_args = {
'command': 'version',
'privilege': 'advanced',
}
msg = self.call_command(module_args, vsim=self.use_vsim)
# Interestingly, the ZAPI returns a slightly different response
needle = b'<cli-output>\nNetApp Release'
assert needle in msg
print('Version (raw): %s' % msg)

@ -1,276 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit tests for Ansible module: na_ontap_dns'''
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, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_dns \
import NetAppOntapDns as dns_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
HAS_NETAPP_ZAPI_MSG = "pip install netapp_lib is required"
# REST API canned responses when mocking send_request
SRR = {
# common responses
'is_rest': (200, None),
'is_zapi': (400, "Unreachable"),
'empty_good': ({}, None),
'end_of_sequence': (None, "Unexpected call to send_request"),
'generic_error': (None, "Expected error"),
'dns_record': ({"records": [{"domains": ['0.0.0.0'],
"servers": ['0.0.0.0'],
"svm": {"name": "svm1", "uuid": "02c9e252-41be-11e9-81d5-00a0986138f7"}}]}, None)}
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.kind = kind
self.params = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
request = xml.to_string().decode('utf-8')
if request.startswith("<ems-autosupport-log>"):
xml = None # or something that may the logger happy, and you don't need @patch anymore
# or
# xml = build_ems_log_response()
elif request == "<net-dns-get/>":
if self.kind == 'create':
raise netapp_utils.zapi.NaApiError(code="15661")
else:
xml = self.build_dns_status_info()
elif request.startswith("<net-dns-create>"):
xml = self.build_dns_status_info()
if self.kind == 'enable':
xml = self.build_dns_status_info()
self.xml_out = xml
return xml
@staticmethod
def build_dns_status_info():
xml = netapp_utils.zapi.NaElement('xml')
nameservers = [{'ip-address': '0.0.0.0'}]
domains = [{'string': '0.0.0.0'}]
attributes = {'num-records': 1,
'attributes': {'net-dns-info': {'name-servers': nameservers,
'domains': domains,
'skip-config-validation': 'false'}}}
xml.translate_struct(attributes)
return xml
class TestMyModule(unittest.TestCase):
''' Unit tests for na_ontap_job_schedule '''
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 mock_args(self):
return {
'state': 'present',
'vserver': 'vserver',
'nameservers': ['0.0.0.0'],
'domains': ['0.0.0.0'],
'hostname': 'test',
'username': 'test_user',
'password': 'test_pass!'
}
def get_dns_mock_object(self, type='zapi', kind=None, status=None):
dns_obj = dns_module()
if type == 'zapi':
if kind is None:
dns_obj.server = MockONTAPConnection()
else:
dns_obj.server = MockONTAPConnection(kind=kind, data=status)
return dns_obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
dns_module()
print('Info: %s' % exc.value.args[0]['msg'])
def test_idempotent_modify_dns(self):
data = self.mock_args()
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object('zapi', 'enable', 'false').apply()
assert not exc.value.args[0]['changed']
def test_successfully_modify_dns(self):
data = self.mock_args()
data['domains'] = ['1.1.1.1']
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object('zapi', 'enable', 'false').apply()
assert exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.ems_log_event')
def test_idempotent_create_dns(self, mock_ems_log_event):
data = self.mock_args()
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object('zapi').apply()
assert not exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.ems_log_event')
def test_successfully_create_dns(self, mock_ems_log_event):
data = self.mock_args()
print("create dns")
data['domains'] = ['1.1.1.1']
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object('zapi', 'create').apply()
assert exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_error(self, mock_request):
data = self.mock_args()
set_module_args(data)
mock_request.side_effect = [
SRR['is_rest'],
SRR['generic_error'],
SRR['end_of_sequence']
]
with pytest.raises(AnsibleFailJson) as exc:
self.get_dns_mock_object(type='rest').apply()
assert exc.value.args[0]['msg'] == SRR['generic_error'][1]
@patch('ansible.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_successfully_create(self, mock_request):
data = self.mock_args()
set_module_args(data)
mock_request.side_effect = [
SRR['is_rest'],
SRR['empty_good'], # get
SRR['empty_good'], # post
SRR['end_of_sequence']
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object(type='rest').apply()
assert exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_idempotent_create_dns(self, mock_request):
data = self.mock_args()
set_module_args(data)
mock_request.side_effect = [
SRR['is_rest'],
SRR['dns_record'], # get
SRR['end_of_sequence']
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object(type='rest').apply()
assert not exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_successfully_destroy(self, mock_request):
data = self.mock_args()
data['state'] = 'absent'
set_module_args(data)
mock_request.side_effect = [
SRR['is_rest'],
SRR['dns_record'], # get
SRR['empty_good'], # delete
SRR['end_of_sequence']
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object(type='rest').apply()
assert exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_idempotently_destroy(self, mock_request):
data = self.mock_args()
data['state'] = 'absent'
set_module_args(data)
mock_request.side_effect = [
SRR['is_rest'],
SRR['empty_good'], # get
SRR['end_of_sequence']
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object(type='rest').apply()
assert not exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_successfully_modify(self, mock_request):
data = self.mock_args()
data['state'] = 'present'
set_module_args(data)
mock_request.side_effect = [
SRR['is_rest'],
SRR['empty_good'], # get
SRR['empty_good'], # patch
SRR['end_of_sequence']
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object(type='rest').apply()
assert exc.value.args[0]['changed']
@patch('ansible.module_utils.netapp.OntapRestAPI.send_request')
def test_rest_idempotently_modify(self, mock_request):
data = self.mock_args()
data['state'] = 'present'
set_module_args(data)
mock_request.side_effect = [
SRR['is_rest'],
SRR['dns_record'], # get
SRR['end_of_sequence']
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_dns_mock_object(type='rest').apply()
assert not exc.value.args[0]['changed']

@ -1,264 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_export_policy_rule \
import NetAppontapExportRule as policy_rule # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.kind = kind
self.data = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.kind == 'rule':
xml = self.build_policy_rule(self.data)
if self.kind == 'rules':
xml = self.build_policy_rule(self.data, multiple=True)
if self.kind == 'policy':
xml = self.build_policy()
self.xml_out = xml
return xml
@staticmethod
def build_policy_rule(policy, multiple=False):
''' build xml data for vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {'attributes-list': {
'export-rule-info': {
'policy-name': policy['name'],
'client-match': policy['client_match'],
'ro-rule': {
'security-flavor': 'any'
},
'rw-rule': {
'security-flavor': 'any'
},
'protocol': {
'access-protocol': policy['protocol']
},
'super-user-security': {
'security-flavor': 'any'
},
'is-allow-set-uid-enabled': 'false',
'rule-index': policy['rule_index']
}
}, 'num-records': 2 if multiple is True else 1}
xml.translate_struct(attributes)
return xml
@staticmethod
def build_policy():
''' build xml data for export-policy-get-iter '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
}
xml.translate_struct(attributes)
return xml
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)
self.server = MockONTAPConnection()
self.mock_rule = {
'name': 'test',
'protocol': 'nfs',
'client_match': '1.1.1.0',
'rule_index': 10
}
def mock_rule_args(self):
return {
'name': self.mock_rule['name'],
'client_match': self.mock_rule['client_match'],
'vserver': 'test',
'protocol': self.mock_rule['protocol'],
'rule_index': self.mock_rule['rule_index'],
'ro_rule': 'any',
'rw_rule': 'any',
'hostname': 'test',
'username': 'test_user',
'password': 'test_pass!'
}
def get_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_firewall_policy object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_firewall_policy object
"""
obj = policy_rule()
obj.autosupport_log = Mock(return_value=None)
if kind is None:
obj.server = MockONTAPConnection()
else:
obj.server = MockONTAPConnection(kind=kind, data=self.mock_rule_args())
return obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
policy_rule()
print('Info: %s' % exc.value.args[0]['msg'])
def test_get_nonexistent_rule(self):
''' Test if get_export_policy_rule returns None for non-existent policy '''
set_module_args(self.mock_rule_args())
result = self.get_mock_object().get_export_policy_rule()
assert result is None
def test_get_nonexistent_policy(self):
''' Test if get_export_policy returns None for non-existent policy '''
set_module_args(self.mock_rule_args())
result = self.get_mock_object().get_export_policy()
assert result is None
def test_get_existing_rule(self):
''' Test if get_export_policy_rule returns rule details for existing policy '''
data = self.mock_rule_args()
set_module_args(data)
result = self.get_mock_object('rule').get_export_policy_rule()
assert result['name'] == data['name']
assert result['client_match'] == data['client_match']
assert result['ro_rule'] == ['any'] # from build_rule()
def test_get_existing_policy(self):
''' Test if get_export_policy returns policy details for existing policy '''
data = self.mock_rule_args()
set_module_args(data)
result = self.get_mock_object('policy').get_export_policy()
assert result is not None
def test_create_missing_param_error(self):
''' Test validation error from create '''
data = self.mock_rule_args()
del data['ro_rule']
set_module_args(data)
with pytest.raises(AnsibleFailJson) as exc:
self.get_mock_object().apply()
msg = 'Error: Missing required param for creating export policy rule ro_rule'
assert exc.value.args[0]['msg'] == msg
def test_successful_create(self):
''' Test successful create '''
set_module_args(self.mock_rule_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object().apply()
assert exc.value.args[0]['changed']
def test_create_idempotency(self):
''' Test create idempotency '''
set_module_args(self.mock_rule_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('rule').apply()
assert not exc.value.args[0]['changed']
def test_successful_delete_without_rule_index(self):
''' Test delete existing job '''
data = self.mock_rule_args()
data['state'] = 'absent'
del data['rule_index']
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('rule').apply()
assert exc.value.args[0]['changed']
def test_delete_idempotency(self):
''' Test delete idempotency '''
data = self.mock_rule_args()
data['state'] = 'absent'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object().apply()
assert not exc.value.args[0]['changed']
def test_successful_modify(self):
''' Test successful modify protocol '''
data = self.mock_rule_args()
data['protocol'] = ['cifs']
data['allow_suid'] = 'true'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('rule').apply()
assert exc.value.args[0]['changed']
def test_error_on_ambiguous_delete(self):
''' Test error if multiple entries match for a delete '''
data = self.mock_rule_args()
data['state'] = 'absent'
set_module_args(data)
with pytest.raises(AnsibleFailJson) as exc:
self.get_mock_object('rules').apply()
msg = "Multiple export policy rules exist.Please specify a rule_index to delete"
assert exc.value.args[0]['msg'] == msg
def test_helper_query_parameters(self):
''' Test helper method set_query_parameters() '''
data = self.mock_rule_args()
set_module_args(data)
result = self.get_mock_object('rule').set_query_parameters()
print(str(result))
assert 'query' in result
assert 'export-rule-info' in result['query']
assert result['query']['export-rule-info']['rule-index'] == data['rule_index']

@ -1,286 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_firewall_policy \
import NetAppONTAPFirewallPolicy as fp_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.kind = kind
self.data = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.kind == 'policy':
xml = self.build_policy_info(self.data)
if self.kind == 'config':
xml = self.build_firewall_config_info(self.data)
self.xml_out = xml
return xml
@staticmethod
def build_policy_info(data):
''' build xml data for net-firewall-policy-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'net-firewall-policy-info': {
'policy': data['policy'],
'service': data['service'],
'allow-list': [
{'ip-and-mask': '1.2.3.0/24'}
]
}
}
}
xml.translate_struct(attributes)
return xml
@staticmethod
def build_firewall_config_info(data):
''' build xml data for net-firewall-config-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'attributes': {
'net-firewall-config-info': {
'is-enabled': 'true',
'is-logging': 'false'
}
}
}
xml.translate_struct(attributes)
return xml
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)
self.mock_policy = {
'policy': 'test',
'service': 'http',
'vserver': 'my_vserver',
'allow_list': '1.2.3.0/24'
}
self.mock_config = {
'node': 'test',
'enable': 'enable',
'logging': 'enable'
}
def mock_policy_args(self):
return {
'policy': self.mock_policy['policy'],
'service': self.mock_policy['service'],
'vserver': self.mock_policy['vserver'],
'allow_list': [self.mock_policy['allow_list']],
'hostname': 'test',
'username': 'test_user',
'password': 'test_pass!'
}
def mock_config_args(self):
return {
'node': self.mock_config['node'],
'enable': self.mock_config['enable'],
'logging': self.mock_config['logging'],
'hostname': 'test',
'username': 'test_user',
'password': 'test_pass!'
}
def get_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_firewall_policy object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_firewall_policy object
"""
obj = fp_module()
obj.autosupport_log = Mock(return_value=None)
if kind is None:
obj.server = MockONTAPConnection()
else:
mock_data = self.mock_config if kind == 'config' else self.mock_policy
obj.server = MockONTAPConnection(kind=kind, data=mock_data)
return obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
fp_module()
print('Info: %s' % exc.value.args[0]['msg'])
def test_helper_firewall_policy_attributes(self):
''' helper returns dictionary with vserver, service and policy details '''
data = self.mock_policy
set_module_args(self.mock_policy_args())
result = self.get_mock_object('policy').firewall_policy_attributes()
del data['allow_list']
assert data == result
def test_helper_validate_ip_addresses_positive(self):
''' test if helper validates if IP is a network address '''
data = self.mock_policy_args()
data['allow_list'] = ['1.2.0.0/16', '1.2.3.0/24']
set_module_args(data)
result = self.get_mock_object().validate_ip_addresses()
assert result is None
def test_helper_validate_ip_addresses_negative(self):
''' test if helper validates if IP is a network address '''
data = self.mock_policy_args()
data['allow_list'] = ['1.2.0.10/16', '1.2.3.0/24']
set_module_args(data)
with pytest.raises(AnsibleFailJson) as exc:
self.get_mock_object().validate_ip_addresses()
msg = 'Error: Invalid IP address value for allow_list parameter.' \
'Please specify a network address without host bits set: ' \
'1.2.0.10/16 has host bits set'
assert exc.value.args[0]['msg'] == msg
def test_get_nonexistent_policy(self):
''' Test if get_firewall_policy returns None for non-existent policy '''
set_module_args(self.mock_policy_args())
result = self.get_mock_object().get_firewall_policy()
assert result is None
def test_get_existing_policy(self):
''' Test if get_firewall_policy returns policy details for existing policy '''
data = self.mock_policy_args()
set_module_args(data)
result = self.get_mock_object('policy').get_firewall_policy()
assert result['service'] == data['service']
assert result['allow_list'] == ['1.2.3.0/24'] # from build_policy_info()
def test_successful_create(self):
''' Test successful create '''
set_module_args(self.mock_policy_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object().apply()
assert exc.value.args[0]['changed']
def test_create_idempotency(self):
''' Test create idempotency '''
set_module_args(self.mock_policy_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('policy').apply()
assert not exc.value.args[0]['changed']
def test_successful_delete(self):
''' Test delete existing job '''
data = self.mock_policy_args()
data['state'] = 'absent'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('policy').apply()
assert exc.value.args[0]['changed']
def test_delete_idempotency(self):
''' Test delete idempotency '''
data = self.mock_policy_args()
data['state'] = 'absent'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object().apply()
assert not exc.value.args[0]['changed']
def test_successful_modify(self):
''' Test successful modify allow_list '''
data = self.mock_policy_args()
data['allow_list'] = ['1.2.0.0/16']
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('policy').apply()
assert exc.value.args[0]['changed']
def test_successful_modify_multiple_ips(self):
''' Test successful modify allow_list '''
data = self.mock_policy_args()
data['allow_list'] = ['1.2.0.0/16', '1.0.0.0/8']
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('policy').apply()
assert exc.value.args[0]['changed']
def test_get_nonexistent_config(self):
''' Test if get_firewall_config returns None for non-existent node '''
set_module_args(self.mock_config_args())
result = self.get_mock_object().get_firewall_config_for_node()
assert result is None
def test_get_existing_config(self):
''' Test if get_firewall_config returns policy details for existing node '''
data = self.mock_config_args()
set_module_args(data)
result = self.get_mock_object('config').get_firewall_config_for_node()
assert result['enable'] == 'enable' # from build_config_info()
assert result['logging'] == 'disable' # from build_config_info()
def test_successful_modify_config(self):
''' Test successful modify allow_list '''
data = self.mock_config_args()
data['enable'] = 'disable'
data['logging'] = 'enable'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_mock_object('config').apply()
assert exc.value.args[0]['changed']

@ -1,291 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit tests ONTAP Ansible module: na_ontap_firmware_upgrade '''
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, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_firmware_upgrade\
import NetAppONTAPFirmwareUpgrade as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, parm1=None, parm2=None, parm3=None):
''' save arguments '''
self.type = kind
self.parm1 = parm1
self.parm2 = parm2
# self.parm3 = parm3
self.xml_in = None
self.xml_out = None
self.firmware_type = 'None'
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'firmware_upgrade':
xml = self.build_firmware_upgrade_info(self.parm1, self.parm2)
if self.type == 'acp':
xml = self.build_acp_firmware_info(self.firmware_type)
self.xml_out = xml
return xml
def autosupport_log(self):
''' mock autosupport log'''
return None
@staticmethod
def build_firmware_upgrade_info(version, node):
''' build xml data for service-processor firmware info '''
xml = netapp_utils.zapi.NaElement('xml')
data = {
'num-records': 1,
'attributes-list': {'service-processor-info': {'firmware-version': '3.4'}}
}
xml.translate_struct(data)
print(xml.to_string())
return xml
@staticmethod
def build_acp_firmware_info(firmware_type):
''' build xml data for acp firmware info '''
xml = netapp_utils.zapi.NaElement('xml')
data = {
# 'num-records': 1,
'attributes-list': {'storage-shelf-acp-module': {'state': 'firmware_update_required'}}
}
xml.translate_struct(data)
print(xml.to_string())
return xml
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)
self.server = MockONTAPConnection()
self.use_vsim = False
def set_default_args(self):
if self.use_vsim:
hostname = '10.10.10.10'
username = 'admin'
password = 'admin'
node = 'vsim1'
clear_logs = True
package = 'test1.zip'
install_baseline_image = False
update_type = 'serial_full'
else:
hostname = 'hostname'
username = 'username'
password = 'password'
node = 'abc'
package = 'test1.zip'
clear_logs = True
install_baseline_image = False
update_type = 'serial_full'
return dict({
'hostname': hostname,
'username': username,
'password': password,
'node': node,
'package': package,
'clear_logs': clear_logs,
'install_baseline_image': install_baseline_image,
'update_type': update_type,
'https': 'true'
})
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'])
def test_missing_parameters(self):
''' fail if firmware_type is missing '''
module_args = {}
module_args.update(self.set_default_args())
set_module_args(module_args)
with pytest.raises(AnsibleFailJson) as exc:
set_module_args(module_args)
my_module()
msg = 'missing required arguments: firmware_type'
print('Info: %s' % exc.value.args[0]['msg'])
assert exc.value.args[0]['msg'] == msg
def test_invalid_firmware_type_parameters(self):
''' fail if firmware_type is missing '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'firmware_type': 'service_test'})
set_module_args(module_args)
with pytest.raises(AnsibleFailJson) as exc:
set_module_args(module_args)
my_module()
msg = 'value of firmware_type must be one of: service-processor, shelf, acp, disk, got: %s' % module_args['firmware_type']
print('Info: %s' % exc.value.args[0]['msg'])
assert exc.value.args[0]['msg'] == msg
def test_ensure_sp_firmware_get_called(self):
''' a more interesting test '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'firmware_type': 'service-processor'})
set_module_args(module_args)
my_obj = my_module()
my_obj.server = self.server
firmware_image_get = my_obj.firmware_image_get('node')
print('Info: test_firmware_upgrade_get: %s' % repr(firmware_image_get))
assert firmware_image_get is None
def test_ensure_firmware_get_with_package_baseline_called(self):
''' a more interesting test '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'firmware_type': 'service-processor'})
module_args.update({'package': 'test1.zip'})
module_args.update({'install_baseline_image': True})
with pytest.raises(AnsibleFailJson) as exc:
set_module_args(module_args)
my_module()
msg = 'Do not specify both package and install_baseline_image: true'
print('info: ' + exc.value.args[0]['msg'])
assert exc.value.args[0]['msg'] == msg
def test_ensure_acp_firmware_required_get_called(self):
''' a test tp verify acp firmware upgrade is required or not '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'firmware_type': 'acp'})
set_module_args(module_args)
my_obj = my_module()
# my_obj.server = self.server
my_obj.server = MockONTAPConnection(kind='acp')
acp_firmware_required_get = my_obj.acp_firmware_required_get()
print('Info: test_acp_firmware_upgrade_required_get: %s' % repr(acp_firmware_required_get))
assert acp_firmware_required_get is True
@patch('ansible.modules.storage.netapp.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.sp_firmware_image_update')
@patch('ansible.modules.storage.netapp.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.sp_firmware_image_update_progress_get')
def test_ensure_apply_for_firmware_upgrade_called(self, get_mock, update_mock):
''' updgrading firmware and checking idempotency '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'package': 'test1.zip'})
module_args.update({'firmware_type': 'service-processor'})
set_module_args(module_args)
my_obj = my_module()
my_obj.autosupport_log = Mock(return_value=None)
if not self.use_vsim:
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value))
assert not exc.value.args[0]['changed']
if not self.use_vsim:
my_obj.server = MockONTAPConnection('firmware_upgrade', '3.5', 'true')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
update_mock.assert_called_with()
def test_shelf_firmware_upgrade(self):
''' Test shelf firmware upgrade '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'firmware_type': 'shelf'})
set_module_args(module_args)
my_obj = my_module()
my_obj.autosupport_log = Mock(return_value=None)
if not self.use_vsim:
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.acp_firmware_upgrade')
@patch('ansible.modules.storage.netapp.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.acp_firmware_required_get')
def test_acp_firmware_upgrade(self, get_mock, update_mock):
''' Test ACP firmware upgrade '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'firmware_type': 'acp'})
set_module_args(module_args)
my_obj = my_module()
my_obj.autosupport_log = Mock(return_value=None)
if not self.use_vsim:
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_firmware_upgrade.NetAppONTAPFirmwareUpgrade.disk_firmware_upgrade')
def test_disk_firmware_upgrade(self, get_mock):
''' Test disk firmware upgrade '''
module_args = {}
module_args.update(self.set_default_args())
module_args.update({'firmware_type': 'disk'})
set_module_args(module_args)
my_obj = my_module()
my_obj.autosupport_log = Mock(return_value=None)
if not self.use_vsim:
my_obj.server = self.server
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Info: test_firmware_upgrade_apply: %s' % repr(exc.value))
assert exc.value.args[0]['changed']

@ -1,529 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test for ONTAP FlexCache Ansible module '''
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
from ansible.modules.storage.netapp.na_ontap_flexcache \
import NetAppONTAPFlexCache as my_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, parm1=None, api_error=None, job_error=None):
''' save arguments '''
self.type = kind
self.parm1 = parm1
self.api_error = api_error
self.job_error = job_error
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
tag = xml.get_name()
if tag == 'flexcache-get-iter' and self.type == 'vserver':
xml = self.build_flexcache_info(self.parm1)
elif tag == 'flexcache-create-async':
xml = self.build_flexcache_create_destroy_rsp()
elif tag == 'flexcache-destroy-async':
if self.api_error:
code, message = self.api_error.split(':', 2)
raise netapp_utils.zapi.NaApiError(code, message)
xml = self.build_flexcache_create_destroy_rsp()
elif tag == 'job-get':
xml = self.build_job_info(self.job_error)
self.xml_out = xml
return xml
@staticmethod
def build_flexcache_info(vserver):
''' build xml data for vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = netapp_utils.zapi.NaElement('attributes-list')
count = 2 if vserver == 'repeats' else 1
for dummy in range(count):
attributes.add_node_with_children('flexcache-info', **{
'vserver': vserver,
'origin-vserver': 'ovserver',
'origin-volume': 'ovolume',
'origin-cluster': 'ocluster',
'volume': 'volume',
})
xml.add_child_elem(attributes)
xml.add_new_child('num-records', str(count))
return xml
@staticmethod
def build_flexcache_create_destroy_rsp():
''' build xml data for a create or destroy response '''
xml = netapp_utils.zapi.NaElement('xml')
xml.add_new_child('result-status', 'in_progress')
xml.add_new_child('result-jobid', '1234')
return xml
@staticmethod
def build_job_info(error):
''' build xml data for a job '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = netapp_utils.zapi.NaElement('attributes')
if error is None:
state = 'success'
elif error == 'time_out':
state = 'running'
else:
state = 'failure'
attributes.add_node_with_children('job-info', **{
'job-state': state,
'job-progress': 'dummy',
'job-completion': error,
})
xml.add_child_elem(attributes)
xml.add_new_child('result-status', 'in_progress')
xml.add_new_child('result-jobid', '1234')
return xml
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)
# make sure to change this to False before submitting
self.onbox = False
self.dummy_args = dict()
for arg in ('hostname', 'username', 'password'):
self.dummy_args[arg] = arg
if self.onbox:
self.args = {
'hostname': '10.193.78.219',
'username': 'admin',
'password': 'netapp1!',
}
else:
self.args = self.dummy_args
self.server = MockONTAPConnection()
def create_flexcache(self, vserver, volume, junction_path):
''' create flexcache '''
if not self.onbox:
return
args = {
'state': 'present',
'volume': volume,
'size': '90', # 80MB minimum
'size_unit': 'mb', # 80MB minimum
'vserver': vserver,
'aggr_list': 'aggr1',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
'junction_path': junction_path,
}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
try:
my_obj.apply()
except AnsibleExitJson as exc:
print('Create util: ' + repr(exc))
except AnsibleFailJson as exc:
print('Create util: ' + repr(exc))
def delete_flexcache(self, vserver, volume):
''' delete flexcache '''
if not self.onbox:
return
args = {'volume': volume, 'vserver': vserver, 'state': 'absent', 'force_offline': 'true'}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
try:
my_obj.apply()
except AnsibleExitJson as exc:
print('Delete util: ' + repr(exc))
except AnsibleFailJson as exc:
print('Delete util: ' + repr(exc))
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'])
def test_missing_parameters(self):
''' fail if origin volume and origin verser are missing '''
args = {
'vserver': 'vserver',
'volume': 'volume'
}
args.update(self.dummy_args)
set_module_args(args)
my_obj = my_module()
my_obj.server = self.server
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
# Hint: start with get methods, as they are called first
my_obj.apply()
msg = 'Missing parameters: origin_volume, origin_vserver'
assert exc.value.args[0]['msg'] == msg
def test_missing_parameter(self):
''' fail if origin verser parameter is missing '''
args = {
'vserver': 'vserver',
'origin_volume': 'origin_volume',
'volume': 'volume'
}
args.update(self.dummy_args)
set_module_args(args)
my_obj = my_module()
my_obj.server = self.server
with pytest.raises(AnsibleFailJson) as exc:
my_obj.apply()
msg = 'Missing parameter: origin_vserver'
assert exc.value.args[0]['msg'] == msg
def test_get_flexcache(self):
''' get flexcache info '''
args = {
'vserver': 'ansibleSVM',
'origin_volume': 'origin_volume',
'volume': 'volume'
}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver')
info = my_obj.flexcache_get()
print('info: ' + repr(info))
def test_get_flexcache_double(self):
''' get flexcache info returns 2 entries! '''
args = {
'vserver': 'ansibleSVM',
'origin_volume': 'origin_volume',
'volume': 'volume'
}
args.update(self.dummy_args)
set_module_args(args)
my_obj = my_module()
my_obj.server = MockONTAPConnection('vserver', 'repeats')
with pytest.raises(AnsibleFailJson) as exc:
my_obj.flexcache_get()
msg = 'Error fetching FlexCache info: Multiple records found for %s:' % args['volume']
assert exc.value.args[0]['msg'] == msg
def test_create_flexcache(self):
''' create flexcache '''
args = {
'volume': 'volume',
'size': '90', # 80MB minimum
'size_unit': 'mb', # 80MB minimum
'vserver': 'ansibleSVM',
'aggr_list': 'aggr1',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
}
self.delete_flexcache(args['vserver'], args['volume'])
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection()
with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create:
# with patch('__main__.my_module.flexcache_create', wraps=my_obj.flexcache_create) as mock_create:
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
assert exc.value.args[0]['changed']
mock_create.assert_called_with()
def test_create_flexcache_idempotent(self):
''' create flexcache - already exists '''
args = {
'volume': 'volume',
'vserver': 'ansibleSVM',
'aggr_list': 'aggr1',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
assert exc.value.args[0]['changed'] is False
def test_create_flexcache_autoprovision(self):
''' create flexcache with autoprovision'''
args = {
'volume': 'volume',
'size': '90', # 80MB minimum
'size_unit': 'mb', # 80MB minimum
'vserver': 'ansibleSVM',
'auto_provision_as': 'flexgroup',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
}
self.delete_flexcache(args['vserver'], args['volume'])
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection()
with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create:
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
assert exc.value.args[0]['changed']
mock_create.assert_called_with()
def test_create_flexcache_autoprovision_idempotent(self):
''' create flexcache with autoprovision - already exists '''
args = {
'volume': 'volume',
'vserver': 'ansibleSVM',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
'auto_provision_as': 'flexgroup',
}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
assert exc.value.args[0]['changed'] is False
def test_create_flexcache_multiplier(self):
''' create flexcache with aggregate multiplier'''
args = {
'volume': 'volume',
'size': '90', # 80MB minimum
'size_unit': 'mb', # 80MB minimum
'vserver': 'ansibleSVM',
'aggr_list': 'aggr1',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
'aggr_list_multiplier': '2',
}
self.delete_flexcache(args['vserver'], args['volume'])
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection()
with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create:
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
assert exc.value.args[0]['changed']
mock_create.assert_called_with()
def test_create_flexcache_multiplier_idempotent(self):
''' create flexcache with aggregate multiplier - already exists '''
args = {
'volume': 'volume',
'vserver': 'ansibleSVM',
'aggr_list': 'aggr1',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
'aggr_list_multiplier': '2',
}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver')
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
assert exc.value.args[0]['changed'] is False
def test_delete_flexcache_exists_no_force(self):
''' delete flexcache '''
args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'state': 'absent'}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
error = '13001:Volume volume in Vserver ansibleSVM must be offline to be deleted. ' \
'Use "volume offline -vserver ansibleSVM -volume volume" command to offline ' \
'the volume'
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver', 'flex', api_error=error)
with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete:
with pytest.raises(AnsibleFailJson) as exc:
my_obj.apply()
print('Delete: ' + repr(exc.value))
msg = 'Error deleting FlexCache : NetApp API failed. Reason - %s' % error
assert exc.value.args[0]['msg'] == msg
mock_delete.assert_called_with()
def test_delete_flexcache_exists_with_force(self):
''' delete flexcache '''
args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'state': 'absent', 'force_offline': 'true'}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver', 'flex')
with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete:
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Delete: ' + repr(exc.value))
assert exc.value.args[0]['changed']
mock_delete.assert_called_with()
def test_delete_flexcache_exists_junctionpath_no_force(self):
''' delete flexcache '''
args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'junction_path': 'jpath', 'state': 'absent', 'force_offline': 'true'}
self.create_flexcache(args['vserver'], args['volume'], args['junction_path'])
args.update(self.args)
set_module_args(args)
my_obj = my_module()
error = '160:Volume volume on Vserver ansibleSVM must be unmounted before being taken offline or restricted.'
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver', 'flex', api_error=error)
with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete:
with pytest.raises(AnsibleFailJson) as exc:
my_obj.apply()
print('Delete: ' + repr(exc.value))
msg = 'Error deleting FlexCache : NetApp API failed. Reason - %s' % error
assert exc.value.args[0]['msg'] == msg
mock_delete.assert_called_with()
def test_delete_flexcache_exists_junctionpath_with_force(self):
''' delete flexcache '''
args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'junction_path': 'jpath', 'state': 'absent', 'force_offline': 'true', 'force_unmount': 'true'}
self.create_flexcache(args['vserver'], args['volume'], args['junction_path'])
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection('vserver', 'flex')
with patch.object(my_module, 'flexcache_delete', wraps=my_obj.flexcache_delete) as mock_delete:
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Delete: ' + repr(exc.value))
assert exc.value.args[0]['changed']
mock_delete.assert_called_with()
def test_delete_flexcache_not_exist(self):
''' delete flexcache '''
args = {'volume': 'volume', 'vserver': 'ansibleSVM', 'state': 'absent'}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection()
with pytest.raises(AnsibleExitJson) as exc:
my_obj.apply()
print('Delete: ' + repr(exc.value))
assert exc.value.args[0]['changed'] is False
def test_create_flexcache_size_error(self):
''' create flexcache '''
args = {
'volume': 'volume_err',
'size': '50', # 80MB minimum
'size_unit': 'mb', # 80MB minimum
'vserver': 'ansibleSVM',
'aggr_list': 'aggr1',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
error = 'Size "50MB" ("52428800B") is too small. Minimum size is "80MB" ("83886080B"). '
if not self.onbox:
my_obj.server = MockONTAPConnection(job_error=error)
with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create:
with pytest.raises(AnsibleFailJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
msg = 'Error when creating flexcache: %s' % error
assert exc.value.args[0]['msg'] == msg
mock_create.assert_called_with()
@patch('time.sleep')
def test_create_flexcache_time_out(self, mock_sleep):
''' create flexcache '''
args = {
'volume': 'volume_err',
'size': '50', # 80MB minimum
'size_unit': 'mb', # 80MB minimum
'vserver': 'ansibleSVM',
'aggr_list': 'aggr1',
'origin_volume': 'fc_vol_origin',
'origin_vserver': 'ansibleSVM',
'time_out': '2'
}
args.update(self.args)
set_module_args(args)
my_obj = my_module()
if not self.onbox:
my_obj.server = MockONTAPConnection(job_error='time_out')
with patch.object(my_module, 'flexcache_create', wraps=my_obj.flexcache_create) as mock_create:
with pytest.raises(AnsibleFailJson) as exc:
my_obj.apply()
print('Create: ' + repr(exc.value))
msg = 'Error when creating flexcache: job completion exceeded expected timer of: %s seconds' \
% args['time_out']
assert exc.value.args[0]['msg'] == msg
mock_create.assert_called_with()

@ -1,259 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_igroup \
import NetAppOntapIgroup as igroup # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.kind = kind
self.data = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.kind == 'igroup':
xml = self.build_igroup()
if self.kind == 'igroup_no_initiators':
xml = self.build_igroup_no_initiators()
self.xml_out = xml
return xml
@staticmethod
def build_igroup():
''' build xml data for initiator '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'initiator-group-info': {
'initiators': [
{
'initiator-info': {
'initiator-name': 'init1'
}},
{
'initiator-info': {
'initiator-name': 'init2'
}}
]
}
}
}
xml.translate_struct(attributes)
return xml
@staticmethod
def build_igroup_no_initiators():
''' build xml data for igroup with no initiators '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'initiator-group-info': {
'vserver': 'test'
}
}
}
xml.translate_struct(attributes)
return xml
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)
self.server = MockONTAPConnection()
def mock_args(self):
return {
'vserver': 'vserver',
'name': 'test',
'initiators': 'init1',
'ostype': 'linux',
'initiator_group_type': 'fcp',
'bind_portset': 'true',
'hostname': 'hostname',
'username': 'username',
'password': 'password'
}
def get_igroup_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_igroup object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_igroup object
"""
obj = igroup()
obj.autosupport_log = Mock(return_value=None)
if kind is None:
obj.server = MockONTAPConnection()
else:
obj.server = MockONTAPConnection(kind=kind)
return obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
igroup()
def test_get_nonexistent_igroup(self):
''' Test if get_igroup returns None for non-existent igroup '''
data = self.mock_args()
set_module_args(data)
result = self.get_igroup_mock_object().get_igroup('dummy')
assert result is None
def test_get_existing_igroup_with_initiators(self):
''' Test if get_igroup returns list of existing initiators '''
data = self.mock_args()
set_module_args(data)
result = self.get_igroup_mock_object('igroup').get_igroup(data['name'])
assert data['initiators'] in result['initiators']
assert result['initiators'] == ['init1', 'init2']
def test_get_existing_igroup_without_initiators(self):
''' Test if get_igroup returns empty list() '''
data = self.mock_args()
set_module_args(data)
result = self.get_igroup_mock_object('igroup_no_initiators').get_igroup(data['name'])
assert result['initiators'] == []
@patch('ansible.modules.storage.netapp.na_ontap_igroup.NetAppOntapIgroup.add_initiators')
@patch('ansible.modules.storage.netapp.na_ontap_igroup.NetAppOntapIgroup.remove_initiators')
def test_modify_initiator_calls_add_and_remove(self, remove, add):
'''Test remove_initiator() is called followed by add_initiator() on modify operation'''
data = self.mock_args()
data['initiators'] = 'replacewithme'
set_module_args(data)
obj = self.get_igroup_mock_object('igroup')
with pytest.raises(AnsibleExitJson) as exc:
current = obj.get_igroup(data['name'])
obj.apply()
remove.assert_called_with(current['initiators'])
add.assert_called_with()
@patch('ansible.modules.storage.netapp.na_ontap_igroup.NetAppOntapIgroup.modify_initiator')
def test_modify_called_from_add(self, modify):
'''Test remove_initiator() and add_initiator() calls modify'''
data = self.mock_args()
data['initiators'] = 'replacewithme'
add, remove = 'igroup-add', 'igroup-remove'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_igroup_mock_object('igroup_no_initiators').apply()
modify.assert_called_with('replacewithme', add)
assert modify.call_count == 1 # remove nothing, add 1 new
@patch('ansible.modules.storage.netapp.na_ontap_igroup.NetAppOntapIgroup.modify_initiator')
def test_modify_called_from_remove(self, modify):
'''Test remove_initiator() and add_initiator() calls modify'''
data = self.mock_args()
data['initiators'] = ''
remove = 'igroup-remove'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_igroup_mock_object('igroup').apply()
modify.assert_called_with('init2', remove)
assert modify.call_count == 2 # remove existing 2, add nothing
@patch('ansible.modules.storage.netapp.na_ontap_igroup.NetAppOntapIgroup.add_initiators')
def test_successful_create(self, add):
''' Test successful create '''
set_module_args(self.mock_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_igroup_mock_object().apply()
assert exc.value.args[0]['changed']
add.assert_called_with()
def test_successful_delete(self):
''' Test successful delete '''
data = self.mock_args()
data['state'] = 'absent'
set_module_args(self.mock_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_igroup_mock_object('igroup').apply()
assert exc.value.args[0]['changed']
def test_successful_modify(self):
''' Test successful modify '''
data = self.mock_args()
data['initiators'] = 'new'
set_module_args(self.mock_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_igroup_mock_object('igroup').apply()
assert exc.value.args[0]['changed']
@patch('ansible.modules.storage.netapp.na_ontap_igroup.NetAppOntapIgroup.get_igroup')
def test_successful_rename(self, get_vserver):
'''Test successful rename'''
data = self.mock_args()
data['from_name'] = 'test'
data['name'] = 'test_new'
set_module_args(data)
current = {
'initiators': ['init1', 'init2']
}
get_vserver.side_effect = [
None,
current
]
with pytest.raises(AnsibleExitJson) as exc:
self.get_igroup_mock_object().apply()
assert exc.value.args[0]['changed']

@ -1,217 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_igroup_initiator \
import NetAppOntapIgroupInitiator as initiator # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.kind = kind
self.data = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.kind == 'initiator':
xml = self.build_igroup_initiator()
elif self.kind == 'initiator_fail':
raise netapp_utils.zapi.NaApiError(code='TEST', message="This exception is from the unit test")
self.xml_out = xml
return xml
@staticmethod
def build_igroup_initiator():
''' build xml data for initiator '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'initiator-group-info': {
'initiators': [
{'initiator-info': {
'initiator-name': 'init1'
}},
{'initiator-info': {
'initiator-name': 'init2'
}}
]
}
}
}
xml.translate_struct(attributes)
return xml
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)
self.server = MockONTAPConnection()
def mock_args(self):
return {
'vserver': 'vserver',
'name': 'init1',
'initiator_group': 'test',
'hostname': 'hostname',
'username': 'username',
'password': 'password'
}
def get_initiator_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_initiator object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_initiator object
"""
obj = initiator()
obj.autosupport_log = Mock(return_value=None)
if kind is None:
obj.server = MockONTAPConnection()
else:
obj.server = MockONTAPConnection(kind=kind)
return obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
initiator()
def test_get_nonexistent_initiator(self):
''' Test if get_initiators returns None for non-existent initiator '''
data = self.mock_args()
data['name'] = 'idontexist'
set_module_args(data)
result = self.get_initiator_mock_object('initiator').get_initiators()
assert data['name'] not in result
def test_get_nonexistent_igroup(self):
''' Test if get_initiators returns None for non-existent igroup '''
data = self.mock_args()
data['name'] = 'idontexist'
set_module_args(data)
result = self.get_initiator_mock_object().get_initiators()
assert result == []
def test_get_existing_initiator(self):
''' Test if get_initiator returns None for existing initiator '''
data = self.mock_args()
set_module_args(data)
result = self.get_initiator_mock_object(kind='initiator').get_initiators()
assert data['name'] in result
assert result == ['init1', 'init2'] # from build_igroup_initiators()
def test_successful_add(self):
''' Test successful add'''
data = self.mock_args()
data['name'] = 'iamnew'
set_module_args(data)
obj = self.get_initiator_mock_object('initiator')
with pytest.raises(AnsibleExitJson) as exc:
current = obj.get_initiators()
obj.apply()
assert data['name'] not in current
assert exc.value.args[0]['changed']
def test_successful_add_idempotency(self):
''' Test successful add idempotency '''
data = self.mock_args()
set_module_args(data)
obj = self.get_initiator_mock_object('initiator')
with pytest.raises(AnsibleExitJson) as exc:
current_list = obj.get_initiators()
obj.apply()
assert data['name'] in current_list
assert not exc.value.args[0]['changed']
def test_successful_remove(self):
''' Test successful remove '''
data = self.mock_args()
data['state'] = 'absent'
set_module_args(data)
obj = self.get_initiator_mock_object('initiator')
with pytest.raises(AnsibleExitJson) as exc:
current_list = obj.get_initiators()
obj.apply()
assert data['name'] in current_list
assert exc.value.args[0]['changed']
def test_successful_remove_idempotency(self):
''' Test successful remove idempotency'''
data = self.mock_args()
data['state'] = 'absent'
data['name'] = 'alreadyremoved'
set_module_args(data)
obj = self.get_initiator_mock_object('initiator')
with pytest.raises(AnsibleExitJson) as exc:
current_list = obj.get_initiators()
obj.apply()
assert data['name'] not in current_list
assert not exc.value.args[0]['changed']
def test_if_all_methods_catch_exception(self):
data = self.mock_args()
set_module_args(data)
my_obj = self.get_initiator_mock_object('initiator_fail')
with pytest.raises(AnsibleFailJson) as exc:
my_obj.get_initiators()
assert 'Error fetching igroup info ' in exc.value.args[0]['msg']
with pytest.raises(AnsibleFailJson) as exc:
my_obj.modify_initiator(data['name'], 'igroup-add')
assert 'Error modifying igroup initiator ' in exc.value.args[0]['msg']

@ -1,313 +0,0 @@
# (c) 2018-2019, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit tests for ONTAP Ansible module na_ontap_info '''
from __future__ import print_function
import json
import pytest
import sys
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
from ansible.modules.storage.netapp.na_ontap_info import main as info_main
from ansible.modules.storage.netapp.na_ontap_info import __finditem as info_finditem
from ansible.modules.storage.netapp.na_ontap_info \
import NetAppONTAPGatherInfo as info_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None):
''' save arguments '''
self.type = kind
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'vserver':
xml = self.build_vserver_info()
elif self.type == 'net_port':
xml = self.build_net_port_info()
elif self.type == 'zapi_error':
error = netapp_utils.zapi.NaApiError('test', 'error')
raise error
self.xml_out = xml
return xml
@staticmethod
def build_vserver_info():
''' build xml data for vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = netapp_utils.zapi.NaElement('attributes-list')
attributes.add_node_with_children('vserver-info',
**{'vserver-name': 'test_vserver'})
xml.add_child_elem(attributes)
return xml
@staticmethod
def build_net_port_info():
''' build xml data for net-port-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes_list = netapp_utils.zapi.NaElement('attributes-list')
num_net_port_info = 2
for i in range(num_net_port_info):
net_port_info = netapp_utils.zapi.NaElement('net-port-info')
net_port_info.add_new_child('node', 'node_' + str(i))
net_port_info.add_new_child('port', 'port_' + str(i))
net_port_info.add_new_child('broadcast_domain', 'test_domain_' + str(i))
net_port_info.add_new_child('ipspace', 'ipspace' + str(i))
attributes_list.add_child_elem(net_port_info)
xml.add_child_elem(attributes_list)
return xml
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)
self.server = MockONTAPConnection()
def mock_args(self):
return {
'hostname': 'hostname',
'username': 'username',
'password': 'password',
}
def get_info_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_info object
"""
module = basic.AnsibleModule(
argument_spec=netapp_utils.na_ontap_host_argument_spec(),
supports_check_mode=True
)
obj = info_module(module)
obj.netapp_info = dict()
if kind is None:
obj.server = MockONTAPConnection()
else:
obj.server = MockONTAPConnection(kind)
return obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
self.get_info_mock_object()
print('Info: %s' % exc.value.args[0]['msg'])
@patch('ansible.module_utils.netapp.ems_log_event')
def test_ensure_command_called(self, mock_ems_log):
''' calling get_all will raise a KeyError exception '''
set_module_args(self.mock_args())
my_obj = self.get_info_mock_object('vserver')
with pytest.raises(KeyError) as exc:
my_obj.get_all(['net_interface_info'])
if sys.version_info >= (2, 7):
msg = 'net-interface-info'
assert exc.value.args[0] == msg
@patch('ansible.module_utils.netapp.ems_log_event')
def test_get_generic_get_iter(self, mock_ems_log):
'''calling get_generic_get_iter will return expected dict'''
set_module_args(self.mock_args())
obj = self.get_info_mock_object('net_port')
result = obj.get_generic_get_iter(
'net-port-get-iter',
attribute='net-port-info',
field=('node', 'port'),
query={'max-records': '1024'}
)
assert result.get('node_0:port_0')
assert result.get('node_1:port_1')
@patch('ansible.modules.storage.netapp.na_ontap_info.NetAppONTAPGatherInfo.get_all')
def test_main(self, get_all):
'''test main method.'''
set_module_args(self.mock_args())
get_all.side_effect = [
{'test_get_all':
{'vserver_login_banner_info': 'test_vserver_login_banner_info', 'vserver_info': 'test_vserver_info'}}
]
with pytest.raises(AnsibleExitJson) as exc:
info_main()
assert exc.value.args[0]['state'] == 'info'
@patch('ansible.modules.storage.netapp.na_ontap_info.NetAppONTAPGatherInfo.get_generic_get_iter')
def test_get_ifgrp_info(self, get_generic_get_iter):
'''test get_ifgrp_info with empty ifgrp_info'''
set_module_args(self.mock_args())
get_generic_get_iter.side_effect = [
{}
]
obj = self.get_info_mock_object()
obj.netapp_info['net_port_info'] = {}
result = obj.get_ifgrp_info()
assert result == {}
def test_ontapi_error(self):
'''test ontapi will raise zapi error'''
set_module_args(self.mock_args())
obj = self.get_info_mock_object('zapi_error')
with pytest.raises(AnsibleFailJson) as exc:
obj.ontapi()
assert exc.value.args[0]['msg'] == 'Error calling API system-get-ontapi-version: NetApp API failed. Reason - test:error'
def test_call_api_error(self):
'''test call_api will raise zapi error'''
set_module_args(self.mock_args())
obj = self.get_info_mock_object('zapi_error')
with pytest.raises(AnsibleFailJson) as exc:
obj.call_api('nvme-get-iter')
assert exc.value.args[0]['msg'] == 'Error calling API nvme-get-iter: NetApp API failed. Reason - test:error'
def test_find_item(self):
'''test __find_item return expected key value'''
obj = {"A": 1, "B": {"C": {"D": 2}}}
key = "D"
result = info_finditem(obj, key)
assert result == 2
def test_subset_return_all_complete(self):
''' Check all returns all of the entries if version is high enough '''
version = '140' # change this if new ZAPIs are supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
subset = obj.get_subset(['all'], version)
assert set(obj.info_subsets.keys()) == subset
def test_subset_return_all_partial(self):
''' Check all returns a subset of the entries if version is low enough '''
version = '120' # low enough so that some ZAPIs are not supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
subset = obj.get_subset(['all'], version)
all_keys = obj.info_subsets.keys()
assert set(all_keys) > subset
supported_keys = filter(lambda key: obj.info_subsets[key]['min_version'] <= version, all_keys)
assert set(supported_keys) == subset
def test_subset_return_one(self):
''' Check single entry returns one '''
version = '120' # low enough so that some ZAPIs are not supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
subset = obj.get_subset(['net_interface_info'], version)
assert len(subset) == 1
def test_subset_return_multiple(self):
''' Check that more than one entry returns the same number '''
version = '120' # low enough so that some ZAPIs are not supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
subset_entries = ['net_interface_info', 'net_port_info']
subset = obj.get_subset(subset_entries, version)
assert len(subset) == len(subset_entries)
def test_subset_return_bad(self):
''' Check that a bad subset entry will error out '''
version = '120' # low enough so that some ZAPIs are not supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
with pytest.raises(AnsibleFailJson) as exc:
subset = obj.get_subset(['net_interface_info', 'my_invalid_subset'], version)
print('Info: %s' % exc.value.args[0]['msg'])
assert exc.value.args[0]['msg'] == 'Bad subset: my_invalid_subset'
def test_subset_return_unsupported(self):
''' Check that a new subset entry will error out on an older system '''
version = '120' # low enough so that some ZAPIs are not supported
key = 'nvme_info' # only supported starting at 140
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
with pytest.raises(AnsibleFailJson) as exc:
subset = obj.get_subset(['net_interface_info', key], version)
print('Info: %s' % exc.value.args[0]['msg'])
msg = 'Remote system at version %s does not support %s' % (version, key)
assert exc.value.args[0]['msg'] == msg
def test_subset_return_none(self):
''' Check usable subset can be empty '''
version = '!' # lower then 0, so that no ZAPI is supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
subset = obj.get_subset(['all'], version)
assert len(subset) == 0
def test_subset_return_all_expect_one(self):
''' Check !x returns all of the entries except x if version is high enough '''
version = '140' # change this if new ZAPIs are supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
subset = obj.get_subset(['!net_interface_info'], version)
assert len(obj.info_subsets.keys()) == len(subset) + 1
subset.add('net_interface_info')
assert set(obj.info_subsets.keys()) == subset
def test_subset_return_all_expect_three(self):
''' Check !x,!y,!z returns all of the entries except x, y, z if version is high enough '''
version = '140' # change this if new ZAPIs are supported
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
subset = obj.get_subset(['!net_interface_info', '!nvme_info', '!ontap_version'], version)
assert len(obj.info_subsets.keys()) == len(subset) + 3
subset.update(['net_interface_info', 'nvme_info', 'ontap_version'])
assert set(obj.info_subsets.keys()) == subset
def test_subset_return_none_with_exclusion(self):
''' Check usable subset can be empty with !x '''
version = '!' # lower then 0, so that no ZAPI is supported
key = 'net_interface_info'
set_module_args(self.mock_args())
obj = self.get_info_mock_object('vserver')
with pytest.raises(AnsibleFailJson) as exc:
subset = obj.get_subset(['!' + key], version)
print('Info: %s' % exc.value.args[0]['msg'])
msg = 'Remote system at version %s does not support %s' % (version, key)
assert exc.value.args[0]['msg'] == msg

@ -1,272 +0,0 @@
# (c) 2018, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
''' unit test template for ONTAP Ansible module '''
from __future__ import print_function
import json
import pytest
from units.compat import unittest
from units.compat.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
import ansible.module_utils.netapp as netapp_utils
from ansible.modules.storage.netapp.na_ontap_interface \
import NetAppOntapInterface as interface_module # module under test
if not netapp_utils.has_netapp_lib():
pytestmark = pytest.mark.skip('skipping as missing required netapp_lib')
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 MockONTAPConnection(object):
''' mock server connection to ONTAP host '''
def __init__(self, kind=None, data=None):
''' save arguments '''
self.type = kind
self.params = data
self.xml_in = None
self.xml_out = None
def invoke_successfully(self, xml, enable_tunneling): # pylint: disable=unused-argument
''' mock invoke_successfully returning xml data '''
self.xml_in = xml
if self.type == 'interface':
xml = self.build_interface_info(self.params)
self.xml_out = xml
return xml
@staticmethod
def build_interface_info(data):
''' build xml data for vserser-info '''
xml = netapp_utils.zapi.NaElement('xml')
attributes = {
'num-records': 1,
'attributes-list': {
'net-interface-info': {
'interface-name': data['name'],
'administrative-status': data['administrative-status'],
'failover-policy': data['failover-policy'],
'firewall-policy': data['firewall-policy'],
'is-auto-revert': data['is-auto-revert'],
'home-node': data['home_node'],
'home-port': data['home_port'],
'address': data['address'],
'netmask': data['netmask'],
'role': data['role'],
'protocols': data['protocols'] if data.get('protocols') else None,
'dns-domain-name': data['dns_domain_name'],
'listen-for-dns_query': data['listen_for_dns_query'],
'is-dns-update-enabled': data['is_dns_update_enabled']
}
}
}
xml.translate_struct(attributes)
return xml
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)
self.mock_interface = {
'name': 'test_lif',
'administrative-status': 'up',
'failover-policy': 'up',
'firewall-policy': 'up',
'is-auto-revert': 'true',
'home_node': 'node',
'role': 'test',
'home_port': 'e0c',
'address': '2.2.2.2',
'netmask': '1.1.1.1',
'dns_domain_name': 'test.com',
'listen_for_dns_query': True,
'is_dns_update_enabled': True
}
def mock_args(self):
return {
'vserver': 'vserver',
'interface_name': self.mock_interface['name'],
'home_node': self.mock_interface['home_node'],
'role': self.mock_interface['role'],
'home_port': self.mock_interface['home_port'],
'address': self.mock_interface['address'],
'netmask': self.mock_interface['netmask'],
'hostname': 'hostname',
'username': 'username',
'password': 'password',
}
def get_interface_mock_object(self, kind=None):
"""
Helper method to return an na_ontap_interface object
:param kind: passes this param to MockONTAPConnection()
:return: na_ontap_interface object
"""
interface_obj = interface_module()
interface_obj.autosupport_log = Mock(return_value=None)
if kind is None:
interface_obj.server = MockONTAPConnection()
else:
interface_obj.server = MockONTAPConnection(kind=kind, data=self.mock_interface)
return interface_obj
def test_module_fail_when_required_args_missing(self):
''' required arguments are reported as errors '''
with pytest.raises(AnsibleFailJson) as exc:
set_module_args({})
interface_module()
print('Info: %s' % exc.value.args[0]['msg'])
def test_create_error_missing_param(self):
''' Test if create throws an error if required param 'role' is not specified'''
data = self.mock_args()
del data['role']
set_module_args(data)
with pytest.raises(AnsibleFailJson) as exc:
self.get_interface_mock_object('interface').create_interface()
msg = 'Error: Missing one or more required parameters for creating interface: ' \
'home_port, netmask, role, home_node, address'
expected = sorted(','.split(msg))
received = sorted(','.split(exc.value.args[0]['msg']))
assert expected == received
def test_get_nonexistent_interface(self):
''' Test if get_interface returns None for non-existent interface '''
set_module_args(self.mock_args())
result = self.get_interface_mock_object().get_interface()
assert result is None
def test_get_existing_interface(self):
''' Test if get_interface returns None for existing interface '''
set_module_args(self.mock_args())
result = self.get_interface_mock_object(kind='interface').get_interface()
assert result['interface_name'] == self.mock_interface['name']
def test_successful_create(self):
''' Test successful create '''
set_module_args(self.mock_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_interface_mock_object().apply()
assert exc.value.args[0]['changed']
def test_successful_create_for_NVMe(self):
''' Test successful create for NVMe protocol'''
data = self.mock_args()
data['protocols'] = 'fc-nvme'
del data['address']
del data['netmask']
del data['home_port']
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_interface_mock_object().apply()
assert exc.value.args[0]['changed']
def test_create_idempotency_for_NVMe(self):
''' Test create idempotency for NVMe protocol '''
data = self.mock_args()
data['protocols'] = 'fc-nvme'
del data['address']
del data['netmask']
del data['home_port']
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_interface_mock_object('interface').apply()
assert not exc.value.args[0]['changed']
def test_create_error_for_NVMe(self):
''' Test if create throws an error if required param 'protocols' uses NVMe'''
data = self.mock_args()
data['protocols'] = 'fc-nvme'
set_module_args(data)
with pytest.raises(AnsibleFailJson) as exc:
self.get_interface_mock_object('interface').create_interface()
msg = 'Error: Following parameters for creating interface are not supported for data-protocol fc-nvme: ' \
'netmask, firewall_policy, address'
expected = sorted(','.split(msg))
received = sorted(','.split(exc.value.args[0]['msg']))
assert expected == received
def test_create_idempotency(self):
''' Test create idempotency '''
set_module_args(self.mock_args())
with pytest.raises(AnsibleExitJson) as exc:
self.get_interface_mock_object('interface').apply()
assert not exc.value.args[0]['changed']
def test_successful_delete(self):
''' Test delete existing interface '''
data = self.mock_args()
data['state'] = 'absent'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_interface_mock_object('interface').apply()
assert exc.value.args[0]['changed']
def test_delete_idempotency(self):
''' Test delete idempotency '''
data = self.mock_args()
data['state'] = 'absent'
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_interface_mock_object().apply()
assert not exc.value.args[0]['changed']
def test_successful_modify(self):
''' Test successful modify interface_minutes '''
data = self.mock_args()
data['home_port'] = 'new_port'
data['dns_domain_name'] = 'test2.com'
data['listen_for_dns_query'] = False
data['is_dns_update_enabled'] = False
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
interface_obj = self.get_interface_mock_object('interface')
interface_obj.apply()
assert exc.value.args[0]['changed']
def test_modify_idempotency(self):
''' Test modify idempotency '''
data = self.mock_args()
set_module_args(data)
with pytest.raises(AnsibleExitJson) as exc:
self.get_interface_mock_object('interface').apply()
assert not exc.value.args[0]['changed']

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save