mirror of https://github.com/ansible/ansible.git
Migrated to netapp.ontap
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 .
|
||||
"""
|
||||
@ -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…
Reference in New Issue