235 lines
9.3 KiB
Python
235 lines
9.3 KiB
Python
# 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'
|
|
|
|
@staticmethod
|
|
def check_keys(current, desired):
|
|
''' TODO: raise an error if keys do not match
|
|
with the exception of:
|
|
new_name, state in desired
|
|
'''
|
|
pass
|
|
|
|
def get_modified_attributes(self, current, desired, 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:
|
|
value.sort()
|
|
desired[key].sort()
|
|
if cmp(value, desired[key]) != 0:
|
|
if not get_list_diff:
|
|
modified[key] = desired[key]
|
|
else:
|
|
modified[key] = [item for item in desired[key] if item not in value]
|
|
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
|