File: //lib/python3.9/site-packages/ansible_collections/netapp/ontap/plugins/modules/na_ontap_cluster.py
# (c) 2017-2022, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
na_ontap_cluster
'''
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: na_ontap_cluster
short_description: NetApp ONTAP cluster - create a cluster and add/remove nodes.
extends_documentation_fragment:
- netapp.ontap.netapp.na_ontap
version_added: 2.6.0
author: NetApp Ansible Team (@carchi8py) <[email protected]>
description:
- Create ONTAP cluster.
- Add or remove cluster nodes using cluster_ip_address.
- Adding a node requires ONTAP 9.3 or better.
- Removing a node requires ONTAP 9.4 or better.
options:
state:
description:
- Whether the specified cluster should exist (deleting a cluster is not supported).
- Whether the node identified by its cluster_ip_address should be in the cluster or not.
choices: ['present', 'absent']
type: str
default: present
cluster_name:
description:
- The name of the cluster to manage.
type: str
cluster_ip_address:
description:
- intra cluster IP address of the node to be added or removed.
type: str
single_node_cluster:
description:
- Whether the cluster is a single node cluster. Ignored for 9.3 or older versions.
- If present, it was observed that 'Cluster' interfaces were deleted, whatever the value with ZAPI.
version_added: 19.11.0
type: bool
cluster_location:
description:
- Cluster location, only relevant if performing a modify action.
version_added: 19.11.0
type: str
cluster_contact:
description:
- Cluster contact, only relevant if performing a modify action.
version_added: 19.11.0
type: str
node_name:
description:
- Name of the node to be added or removed from the cluster.
- Be aware that when adding a node, '-' are converted to '_' by the ONTAP backend.
- When creating a cluster, C(node_name) is ignored.
- When adding a node using C(cluster_ip_address), C(node_name) is optional.
- When used to remove a node, C(cluster_ip_address) and C(node_name) are mutually exclusive.
version_added: 20.9.0
type: str
time_out:
description:
- time to wait for cluster creation in seconds.
- Error out if task is not completed in defined time.
- if 0, the request is asynchronous.
- default is set to 3 minutes.
default: 180
type: int
version_added: 21.1.0
force:
description:
- forcibly remove a node that is down and cannot be brought online to remove its shared resources.
default: false
type: bool
version_added: 21.13.0
timezone:
description: timezone for the cluster. Only supported by REST.
type: dict
version_added: 21.24.0
suboptions:
name:
type: str
description:
- The timezone name must be
- A geographic region, usually expressed as area/location
- Greenwich Mean Time (GMT) or the difference in hours from GMT
- A valid alias; that is, a term defined by the standard to refer to a geographic region or GMT
- A system-specific or other term not associated with a geographic region or GMT
- "full list of supported alias can be found here: https://library.netapp.com/ecmdocs/ECMP1155590/html/GUID-D3B8A525-67A2-4BEE-99DB-EF52D6744B5F.html"
- Only supported by REST
notes:
- supports REST and ZAPI
'''
EXAMPLES = """
- name: Create cluster
netapp.ontap.na_ontap_cluster:
state: present
cluster_name: new_cluster
time_out: 0
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Add node to cluster (Join cluster)
netapp.ontap.na_ontap_cluster:
state: present
cluster_ip_address: 10.10.10.10
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Add node to cluster (Join cluster)
netapp.ontap.na_ontap_cluster:
state: present
cluster_ip_address: 10.10.10.10
node_name: my_preferred_node_name
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Create a 2 node cluster in one call
netapp.ontap.na_ontap_cluster:
state: present
cluster_name: new_cluster
cluster_ip_address: 10.10.10.10
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Remove node from cluster
netapp.ontap.na_ontap_cluster:
state: absent
cluster_ip_address: 10.10.10.10
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: Remove node from cluster
netapp.ontap.na_ontap_cluster:
state: absent
node_name: node002
hostname: "{{ netapp_hostname }}"
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
- name: modify cluster
netapp.ontap.na_ontap_cluster:
state: present
cluster_contact: testing
cluster_location: testing
cluster_name: "{{ netapp_cluster}}"
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_collections.netapp.ontap.plugins.module_utils.netapp as netapp_utils
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
from ansible_collections.netapp.ontap.plugins.module_utils.netapp import OntapRestAPI
from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
class NetAppONTAPCluster:
"""
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'),
cluster_name=dict(required=False, type='str'),
cluster_ip_address=dict(required=False, type='str'),
cluster_location=dict(required=False, type='str'),
cluster_contact=dict(required=False, type='str'),
force=dict(required=False, type='bool', default=False),
single_node_cluster=dict(required=False, type='bool'),
node_name=dict(required=False, type='str'),
time_out=dict(required=False, type='int', default=180),
timezone=dict(required=False, type='dict', options=dict(
name=dict(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.warnings = []
# cached, so that we don't call the REST API more than once
self.node_records = None
if self.parameters['state'] == 'absent' and self.parameters.get('node_name') is not None and self.parameters.get('cluster_ip_address') is not None:
msg = 'when state is "absent", parameters are mutually exclusive: cluster_ip_address|node_name'
self.module.fail_json(msg=msg)
if self.parameters.get('node_name') is not None and '-' in self.parameters.get('node_name'):
self.warnings.append('ONTAP ZAPI converts "-" to "_", node_name: %s may be changed or not matched' % self.parameters.get('node_name'))
self.rest_api = OntapRestAPI(self.module)
self.use_rest = self.rest_api.is_rest()
if self.use_rest and self.parameters['state'] == 'absent' and not self.rest_api.meets_rest_minimum_version(True, 9, 7, 0):
self.module.warn('switching back to ZAPI as DELETE is not supported on 9.6')
self.use_rest = False
if not self.use_rest:
if self.na_helper.safe_get(self.parameters, ['timezone', 'name']):
self.module.fail_json(msg='Timezone is only supported with REST')
if not netapp_utils.has_netapp_lib():
self.module.fail_json(msg="the python NetApp-Lib module is required")
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
def get_cluster_identity_rest(self):
''' get cluster information, but the cluster may not exist yet
return:
None if the cluster cannot be reached
a dictionary of attributes
'''
record, error = rest_generic.get_one_record(self.rest_api, 'cluster', fields='contact,location,name,timezone')
if error:
if 'are available in precluster.' in error:
# assuming precluster state
return None
self.module.fail_json(msg='Error fetching cluster identity info: %s' % to_native(error),
exception=traceback.format_exc())
if record:
return {
'cluster_contact': record.get('contact'),
'cluster_location': record.get('location'),
'cluster_name': record.get('name'),
'timezone': self.na_helper.safe_get(record, ['timezone'])
}
return None
def get_cluster_identity(self, ignore_error=True):
''' get cluster information, but the cluster may not exist yet
return:
None if the cluster cannot be reached
a dictionary of attributes
'''
if self.use_rest:
return self.get_cluster_identity_rest()
zapi = netapp_utils.zapi.NaElement('cluster-identity-get')
try:
result = self.server.invoke_successfully(zapi, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if ignore_error:
return None
self.module.fail_json(msg='Error fetching cluster identity info: %s' % to_native(error),
exception=traceback.format_exc())
cluster_identity = {}
if result.get_child_by_name('attributes'):
identity_info = result.get_child_by_name('attributes').get_child_by_name('cluster-identity-info')
if identity_info:
cluster_identity['cluster_contact'] = identity_info.get_child_content('cluster-contact')
cluster_identity['cluster_location'] = identity_info.get_child_content('cluster-location')
cluster_identity['cluster_name'] = identity_info.get_child_content('cluster-name')
return cluster_identity
return None
def get_cluster_nodes_rest(self):
''' get cluster node names, but the cluster may not exist yet
return:
None if the cluster cannot be reached
a list of nodes
'''
if self.node_records is None:
records, error = rest_generic.get_0_or_more_records(self.rest_api, 'cluster/nodes', fields='name,uuid,cluster_interfaces')
if error:
self.module.fail_json(msg='Error fetching cluster node info: %s' % to_native(error),
exception=traceback.format_exc())
self.node_records = records or []
return self.node_records
def get_cluster_node_names_rest(self):
''' get cluster node names, but the cluster may not exist yet
return:
None if the cluster cannot be reached
a list of nodes
'''
records = self.get_cluster_nodes_rest()
return [record['name'] for record in records]
def get_cluster_nodes(self, ignore_error=True):
''' get cluster node names, but the cluster may not exist yet
return:
None if the cluster cannot be reached
a list of nodes
'''
if self.use_rest:
return self.get_cluster_node_names_rest()
zapi = netapp_utils.zapi.NaElement('cluster-node-get-iter')
try:
result = self.server.invoke_successfully(zapi, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if ignore_error:
return None
self.module.fail_json(msg='Error fetching cluster node info: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('attributes-list'):
cluster_nodes = []
for node_info in result.get_child_by_name('attributes-list').get_children():
node_name = node_info.get_child_content('node-name')
if node_name is not None:
cluster_nodes.append(node_name)
return cluster_nodes
return None
def get_cluster_ip_addresses_rest(self, cluster_ip_address):
''' get list of IP addresses for this cluster
return:
a list of dictionaries
'''
if_infos = []
records = self.get_cluster_nodes_rest()
for record in records:
for interface in record.get('cluster_interfaces', []):
ip_address = self.na_helper.safe_get(interface, ['ip', 'address'])
if cluster_ip_address is None or ip_address == cluster_ip_address:
if_info = {
'address': ip_address,
'home_node': record['name'],
}
if_infos.append(if_info)
return if_infos
def get_cluster_ip_addresses(self, cluster_ip_address, ignore_error=True):
''' get list of IP addresses for this cluster
return:
a list of dictionaries
'''
if_infos = []
zapi = netapp_utils.zapi.NaElement('net-interface-get-iter')
if cluster_ip_address is not None:
query = netapp_utils.zapi.NaElement('query')
net_info = netapp_utils.zapi.NaElement('net-interface-info')
net_info.add_new_child('address', cluster_ip_address)
query.add_child_elem(net_info)
zapi.add_child_elem(query)
try:
result = self.server.invoke_successfully(zapi, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if ignore_error:
return if_infos
self.module.fail_json(msg='Error getting IP addresses: %s' % to_native(error),
exception=traceback.format_exc())
if result.get_child_by_name('attributes-list'):
for net_info in result.get_child_by_name('attributes-list').get_children():
if net_info:
if_info = {'address': net_info.get_child_content('address')}
if_info['home_node'] = net_info.get_child_content('home-node')
if_infos.append(if_info)
return if_infos
def get_cluster_ip_address(self, cluster_ip_address, ignore_error=True):
''' get node information if it is discoverable
return:
None if the cluster cannot be reached
a dictionary of attributes
'''
if cluster_ip_address is None:
return None
if self.use_rest:
nodes = self.get_cluster_ip_addresses_rest(cluster_ip_address)
else:
nodes = self.get_cluster_ip_addresses(cluster_ip_address, ignore_error=ignore_error)
return nodes if len(nodes) > 0 else None
def create_cluster_body(self, modify=None, nodes=None):
body = {}
params = modify if modify is not None else self.parameters
for (param_key, rest_key) in {
'cluster_contact': 'contact',
'cluster_location': 'location',
'cluster_name': 'name',
'single_node_cluster': 'single_node_cluster',
'timezone': 'timezone'
}.items():
if param_key in params:
body[rest_key] = params[param_key]
if nodes:
body['nodes'] = nodes
return body
def create_node_body(self):
node = {}
for (param_key, rest_key) in {
'cluster_ip_address': 'cluster_interface.ip.address',
'cluster_location': 'location',
'node_name': 'name'
}.items():
if param_key in self.parameters:
node[rest_key] = self.parameters[param_key]
return node
def create_nodes(self):
node = self.create_node_body()
return [node] if node else None
def create_cluster_rest(self, older_api=False):
"""
Create a cluster
"""
query = None
body = self.create_cluster_body(nodes=self.create_nodes())
if 'single_node_cluster' in body:
query = {'single_node_cluster': body.pop('single_node_cluster')}
dummy, error = rest_generic.post_async(self.rest_api, 'cluster', body, query, job_timeout=120)
if error:
self.module.fail_json(msg='Error creating cluster %s: %s'
% (self.parameters['cluster_name'], to_native(error)),
exception=traceback.format_exc())
def create_cluster(self, older_api=False):
"""
Create a cluster
"""
if self.use_rest:
return self.create_cluster_rest()
# Note: cannot use node_name here:
# 13001:The "-node-names" parameter must be used with either the "-node-uuids" or the "-cluster-ips" parameters.
options = {'cluster-name': self.parameters['cluster_name']}
if not older_api and self.parameters.get('single_node_cluster') is not None:
options['single-node-cluster'] = str(self.parameters['single_node_cluster']).lower()
cluster_create = netapp_utils.zapi.NaElement.create_node_with_children(
'cluster-create', **options)
try:
self.server.invoke_successfully(cluster_create,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if error.message == "Extra input: single-node-cluster" and not older_api:
return self.create_cluster(older_api=True)
# Error 36503 denotes node already being used.
if to_native(error.code) == "36503":
return False
self.module.fail_json(msg='Error creating cluster %s: %s'
% (self.parameters['cluster_name'], to_native(error)),
exception=traceback.format_exc())
return True
def add_node_rest(self):
"""
Add a node to an existing cluster
"""
body = self.create_node_body()
dummy, error = rest_generic.post_async(self.rest_api, 'cluster/nodes', body, job_timeout=120)
if error:
self.module.fail_json(msg='Error adding node with ip %s: %s'
% (self.parameters.get('cluster_ip_address'), to_native(error)),
exception=traceback.format_exc())
def add_node(self, older_api=False):
"""
Add a node to an existing cluster
9.2 and 9.3 do not support cluster-ips so fallback to node-ip
"""
if self.use_rest:
return self.add_node_rest()
if self.parameters.get('cluster_ip_address') is None:
return False
cluster_add_node = netapp_utils.zapi.NaElement('cluster-add-node')
if older_api:
cluster_add_node.add_new_child('node-ip', self.parameters.get('cluster_ip_address'))
else:
cluster_ips = netapp_utils.zapi.NaElement.create_node_with_children('cluster-ips', **{'ip-address': self.parameters.get('cluster_ip_address')})
cluster_add_node.add_child_elem(cluster_ips)
if self.parameters.get('node_name') is not None:
node_names = netapp_utils.zapi.NaElement.create_node_with_children('node-names', **{'string': self.parameters.get('node_name')})
cluster_add_node.add_child_elem(node_names)
try:
self.server.invoke_successfully(cluster_add_node, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if error.message == "Extra input: cluster-ips" and not older_api:
return self.add_node(older_api=True)
# skip if error says no failed operations to retry.
if to_native(error) == "NetApp API failed. Reason - 13001:There are no failed \"cluster create\" or \"cluster add-node\" operations to retry.":
return False
self.module.fail_json(msg='Error adding node with ip %s: %s'
% (self.parameters.get('cluster_ip_address'), to_native(error)),
exception=traceback.format_exc())
return True
def get_uuid_from_ip(self, ip_address):
for node in self.get_cluster_nodes_rest():
if ip_address in (interface['ip']['address'] for interface in node['cluster_interfaces']):
return node['uuid']
return None
def get_uuid_from_name(self, node_name):
for node in self.get_cluster_nodes_rest():
if node_name == node['name']:
return node['uuid']
return None
def get_uuid(self):
if self.parameters.get('cluster_ip_address') is not None:
from_node = self.parameters['cluster_ip_address']
uuid = self.get_uuid_from_ip(from_node)
elif self.parameters.get('node_name') is not None:
from_node = self.parameters['node_name']
uuid = self.get_uuid_from_name(from_node)
else:
# Unexpected, for delete one of cluster_ip_address, node_name is required.
uuid = None
if uuid is None:
self.module.fail_json(msg='Internal error, cannot find UUID in %s: for %s or %s'
% (self.get_cluster_nodes_rest(), self.parameters['cluster_ip_address'], self.parameters.get('node_name') is not None),
exception=traceback.format_exc())
return uuid, from_node
def remove_node_rest(self):
"""
Remove a node from an existing cluster
"""
uuid, from_node = self.get_uuid()
query = {'force': True} if self.parameters.get('force') else None
dummy, error = rest_generic.delete_async(self.rest_api, 'cluster/nodes', uuid, query, job_timeout=120)
if error:
self.module.fail_json(msg='Error removing node with %s: %s'
% (from_node, to_native(error)), exception=traceback.format_exc())
def remove_node(self):
"""
Remove a node from an existing cluster
"""
if self.use_rest:
return self.remove_node_rest()
cluster_remove_node = netapp_utils.zapi.NaElement('cluster-remove-node')
from_node = ''
# cluster-ip and node-name are mutually exclusive:
# 13115:Element "cluster-ip" within "cluster-remove-node" has been excluded by another element.
if self.parameters.get('cluster_ip_address') is not None:
cluster_remove_node.add_new_child('cluster-ip', self.parameters.get('cluster_ip_address'))
from_node = 'IP: %s' % self.parameters.get('cluster_ip_address')
elif self.parameters.get('node_name') is not None:
cluster_remove_node.add_new_child('node', self.parameters.get('node_name'))
from_node = 'name: %s' % self.parameters.get('node_name')
if self.parameters.get('force'):
cluster_remove_node.add_new_child('force', 'true')
try:
self.server.invoke_successfully(cluster_remove_node, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if error.message == "Unable to find API: cluster-remove-node":
msg = 'Error: ZAPI is not available. Removing a node requires ONTAP 9.4 or newer.'
self.module.fail_json(msg=msg)
self.module.fail_json(msg='Error removing node with %s: %s'
% (from_node, to_native(error)), exception=traceback.format_exc())
def modify_cluster_identity_rest(self, modify):
"""
Modifies the cluster identity
"""
body = self.create_cluster_body(modify)
dummy, error = rest_generic.patch_async(self.rest_api, 'cluster', None, body)
if error:
self.module.fail_json(msg='Error modifying cluster idetity details %s: %s'
% (self.parameters['cluster_name'], to_native(error)),
exception=traceback.format_exc())
def modify_cluster_identity(self, modify):
"""
Modifies the cluster identity
"""
if self.use_rest:
return self.modify_cluster_identity_rest(modify)
cluster_modify = netapp_utils.zapi.NaElement('cluster-identity-modify')
if modify.get('cluster_name') is not None:
cluster_modify.add_new_child("cluster-name", modify.get('cluster_name'))
if modify.get('cluster_location') is not None:
cluster_modify.add_new_child("cluster-location", modify.get('cluster_location'))
if modify.get('cluster_contact') is not None:
cluster_modify.add_new_child("cluster-contact", modify.get('cluster_contact'))
try:
self.server.invoke_successfully(cluster_modify,
enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
self.module.fail_json(msg='Error modifying cluster idetity details %s: %s'
% (self.parameters['cluster_name'], to_native(error)),
exception=traceback.format_exc())
def cluster_create_wait(self):
"""
Wait whilst cluster creation completes
"""
if self.use_rest:
# wait is part of post_async for REST
return
cluster_wait = netapp_utils.zapi.NaElement('cluster-create-join-progress-get')
is_complete = False
status = ''
retries = self.parameters['time_out']
errors = []
while not is_complete and status not in ('failed', 'success') and retries > 0:
retries = retries - 10
time.sleep(10)
try:
result = self.server.invoke_successfully(cluster_wait, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
# collecting errors, and retrying
errors.append(repr(error))
continue
clus_progress = result.get_child_by_name('attributes')
result = clus_progress.get_child_by_name('cluster-create-join-progress-info')
is_complete = self.na_helper.get_value_for_bool(from_zapi=True,
value=result.get_child_content('is-complete'))
status = result.get_child_content('status')
if self.parameters['time_out'] == 0:
is_complete = True
if not is_complete and status != 'success':
current_status_message = result.get_child_content('current-status-message')
errors.append('Failed to confirm cluster creation %s: %s' % (self.parameters.get('cluster_name'), current_status_message))
if retries <= 0:
errors.append("Timeout after %s seconds" % self.parameters['time_out'])
self.module.fail_json(msg='Error creating cluster %s: %s'
% (self.parameters['cluster_name'], str(errors)))
return is_complete
def node_add_wait(self):
"""
Wait whilst node is being added to the existing cluster
"""
if self.use_rest:
# wait is part of post_async for REST
return
cluster_node_status = netapp_utils.zapi.NaElement('cluster-add-node-status-get-iter')
node_status_info = netapp_utils.zapi.NaElement('cluster-create-add-node-status-info')
node_status_info.add_new_child('cluster-ip', self.parameters.get('cluster_ip_address'))
query = netapp_utils.zapi.NaElement('query')
query.add_child_elem(node_status_info)
cluster_node_status.add_child_elem(query)
is_complete = None
failure_msg = None
retries = self.parameters['time_out']
errors = []
while is_complete != 'success' and is_complete != 'failure' and retries > 0:
retries = retries - 10
time.sleep(10)
try:
result = self.server.invoke_successfully(cluster_node_status, enable_tunneling=True)
except netapp_utils.zapi.NaApiError as error:
if error.message == "Unable to find API: cluster-add-node-status-get-iter":
# This API is not supported for 9.3 or earlier releases, just wait a bit
time.sleep(60)
return
# collecting errors, and retrying
errors.append(repr(error))
continue
attributes_list = result.get_child_by_name('attributes-list')
join_progress = attributes_list.get_child_by_name('cluster-create-add-node-status-info')
is_complete = join_progress.get_child_content('status')
failure_msg = join_progress.get_child_content('failure-msg')
if self.parameters['time_out'] == 0:
is_complete = 'success'
if is_complete != 'success':
if 'Node is already in a cluster' in failure_msg:
return
elif retries <= 0:
errors.append("Timeout after %s seconds" % self.parameters['time_out'])
if failure_msg:
errors.append(failure_msg)
self.module.fail_json(msg='Error adding node with ip address %s: %s'
% (self.parameters['cluster_ip_address'], str(errors)))
def node_remove_wait(self):
''' wait for node name or clister IP address to disappear '''
if self.use_rest:
# wait is part of delete_async for REST
return
node_name = self.parameters.get('node_name')
node_ip = self.parameters.get('cluster_ip_address')
retries = self.parameters['time_out']
while retries > 0:
retries = retries - 10
if node_name is not None and node_name not in self.get_cluster_nodes():
return
if node_ip is not None and self.get_cluster_ip_address(node_ip) is None:
return
time.sleep(10)
self.module.fail_json(msg='Timeout waiting for node to be removed from cluster.')
def get_cluster_action(self, cluster_identity):
cluster_action = None
if self.parameters.get('cluster_name') is not None:
cluster_action = self.na_helper.get_cd_action(cluster_identity, self.parameters)
if cluster_action == 'delete':
# delete only applies to node
cluster_action = None
self.na_helper.changed = False
return cluster_action
def get_node_action(self):
node_action = None
if self.parameters.get('cluster_ip_address') is not None:
existing_interfaces = self.get_cluster_ip_address(self.parameters.get('cluster_ip_address'))
if self.parameters.get('state') == 'present':
node_action = 'add_node' if existing_interfaces is None else None
else:
node_action = 'remove_node' if existing_interfaces is not None else None
if self.parameters.get('node_name') is not None and self.parameters['state'] == 'absent':
nodes = self.get_cluster_nodes()
if self.parameters.get('node_name') in nodes:
node_action = 'remove_node'
if node_action is not None:
self.na_helper.changed = True
return node_action
def apply(self):
"""
Apply action to cluster
"""
cluster_identity = self.get_cluster_identity(ignore_error=True)
cluster_action = self.get_cluster_action(cluster_identity)
node_action = self.get_node_action()
modify = self.na_helper.get_modified_attributes(cluster_identity, self.parameters)
if not self.module.check_mode:
if cluster_action == 'create' and self.create_cluster():
self.cluster_create_wait()
if node_action == 'add_node':
if self.add_node():
self.node_add_wait()
elif node_action == 'remove_node':
self.remove_node()
self.node_remove_wait()
if modify:
self.modify_cluster_identity(modify)
results = {'changed': self.na_helper.changed}
if self.warnings:
results['warnings'] = self.warnings
if netapp_utils.has_feature(self.module, 'show_modified'):
results['modify'] = modify
self.module.exit_json(**results)
def main():
"""
Create object and call apply
"""
cluster_obj = NetAppONTAPCluster()
cluster_obj.apply()
if __name__ == '__main__':
main()