Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
# (c) 2018-2021, NetApp, Inc
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
'''
na_ontap_net_ifgrp
'''
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: na_ontap_net_ifgrp
short_description: NetApp Ontap modify network interface group
extends_documentation_fragment:
- netapp.ontap.netapp.na_ontap
version_added: 2.6.0
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']
type: str
default: present
distribution_function:
description:
- Specifies the traffic distribution function for the ifgrp.
choices: ['mac', 'ip', 'sequential', 'port']
type: str
name:
description:
- Specifies the interface group name.
- Not supported with REST, use C(ports) or C(from_lag_ports).
- Required with ZAPI.
type: str
mode:
description:
- Specifies the link policy for the ifgrp.
type: str
node:
description:
- Specifies the name of node.
required: true
type: str
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.
- With REST, ports in this list are used to find the current LAG port.
- If LAG is not found or only partial port matches, then C(from_lag_port) are used to get the current LAG.
- With REST, when C(state=absent) is set, all of the ports in ifgrp should be provided to delete it.
- Example C(ports=['e0c','e0a']) will delete ifgrp that has ports C(['e0c','e0a']).
version_added: 2.8.0
type: list
elements: str
from_lag_ports:
description:
- Only supported with REST and is ignored with ZAPI.
- Specify all the ports to find current LAG port.
- Ignored if LAG found with exact match of C(ports).
- Example if current LAG has ports C(['e0c','e0d']) and C(ports=['e0c','e0d']), then from_lag_ports will be ignored.
- If LAG not found with C(ports), then ports in this list are used to find the current LAG.
- Ports in this list are used only for finding current LAG, provide exact match of all the ports in the current LAG.
- Ignored when C(state=absent).
version_added: 2.14.0
type: list
elements: str
broadcast_domain:
description:
- Specify the broadcast_domain name.
- Only supported with REST and is ignored with ZAPI.
- Required with ONTAP 9.6 and 9.7, but optional with 9.8 or later.
type: str
version_added: 21.14.0
ipspace:
description:
- Specify the ipspace for the broadcast domain.
- Only supported with REST and is ignored with ZAPI.
- Required with ONTAP 9.6 and 9.7, but optional with 9.8 or later.
type: str
version_added: 21.14.0
"""
EXAMPLES = """
- name: create ifgrp
netapp.ontap.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
netapp.ontap.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
netapp.ontap.na_ontap_net_ifgrp:
state: absent
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
name: a0c
node: "{{ Vsim node name }}"
- name: create ifgrp - REST
netapp.ontap.na_ontap_net_ifgrp:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
distribution_function: ip
ports: [e0a,e0b]
mode: multimode
node: "{{ Vsim node name }}"
broadcast_domain: Default
ipspace: Default
- name: Remove e0a and add port e0d to above created lag REST
netapp.ontap.na_ontap_net_ifgrp:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
from_lag_ports: [a0a,e0b]
ports: [e0b,e0d]
node: "{{ Vsim node name }}"
- name: Add e0a to lag that has port e0b e0d REST
netapp.ontap.na_ontap_net_ifgrp:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
distribution_function: ip
ports: [e0b,e0d,e0a]
mode: multimode
node: "{{ Vsim node name }}"
- name: Modify broadcast_domain and ipspace REST
netapp.ontap.na_ontap_net_ifgrp:
state: present
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
broadcast_domain: test
ipspace: test
ports: [e0b,e0d,e0a]
node: "{{ Vsim node name }}"
- name: Delete LAG with exact match of ports
netapp.ontap.na_ontap_net_ifgrp:
state: absent
username: "{{ netapp_username }}"
password: "{{ netapp_password }}"
hostname: "{{ netapp_hostname }}"
ports: [e0b,e0d,e0a]
node: "{{ Vsim node name }}"
"""
RETURN = """
"""
import traceback
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.netapp.ontap.plugins.module_utils.netapp_module import NetAppModule
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 import OntapRestAPI
from ansible_collections.netapp.ontap.plugins.module_utils import rest_generic
class NetAppOntapIfGrp:
"""
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, type='str', choices=['present', 'absent'], default='present'),
distribution_function=dict(required=False, type='str', choices=['mac', 'ip', 'sequential', 'port']),
name=dict(required=False, type='str'),
mode=dict(required=False, type='str'),
node=dict(required=True, type='str'),
ports=dict(required=False, type='list', elements='str', aliases=["port"]),
from_lag_ports=dict(required=False, type='list', elements='str'),
broadcast_domain=dict(required=False, type='str'),
ipspace=dict(required=False, type='str')
))
self.module = AnsibleModule(
argument_spec=self.argument_spec,
required_if=[
('state', 'present', ['distribution_function', 'mode'])
],
required_together=[['broadcast_domain', 'ipspace']],
supports_check_mode=True
)
self.current_records = []
# set up variables
self.na_helper = NetAppModule()
self.parameters = self.na_helper.set_parameters(self.module.params)
# Set up Rest API
self.rest_api = OntapRestAPI(self.module)
# if rest and use_rest: auto and name is present, revert to zapi
# if rest and use_rest: always and name is present, throw error.
unsupported_rest_properties = ['name']
self.use_rest = self.rest_api.is_rest_supported_properties(self.parameters, unsupported_rest_properties)
if self.use_rest:
# if rest and ports is not present, throw error as ports is a required field with REST
if 'ports' not in self.parameters:
error_msg = "Error: ports is a required field with REST"
self.module.fail_json(msg=error_msg)
required_options = ['broadcast_domain', 'ipspace']
min_ontap_98 = self.rest_api.meets_rest_minimum_version(self.use_rest, 9, 8, 0)
if not min_ontap_98 and not any(x in self.parameters for x in required_options):
error_msg = "'%s' are mandatory fields with ONTAP 9.6 and 9.7" % ', '.join(required_options)
self.module.fail_json(msg=error_msg)
else:
if not netapp_utils.has_netapp_lib():
self.module.fail_json(msg=netapp_utils.netapp_lib_is_required())
if 'name' not in self.parameters:
self.module.fail_json("Error: name is a required field with ZAPI.")
if 'broadcast_domain' in self.parameters or 'ipspace' in self.parameters or 'from_lag_ports' in self.parameters:
msg = 'Using ZAPI and ignoring options - broadcast_domain, ipspace and from_lag_ports'
self.module.warn(msg)
self.parameters.pop('broadcast_domain', None)
self.parameters.pop('ipspace', None)
self.parameters.pop('from_lag_ports', None)
self.server = netapp_utils.setup_na_ontap_zapi(module=self.module)
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_rest(self, ports, allow_partial_match):
api = 'network/ethernet/ports'
query = {
'type': 'lag',
'node.name': self.parameters['node'],
}
fields = 'name,node,uuid,broadcast_domain,lag'
error = None
if not self.current_records:
self.current_records, error = rest_generic.get_0_or_more_records(self.rest_api, api, query, fields)
if error:
self.module.fail_json(msg=error)
if self.current_records:
current_ifgrp = self.get_if_grp_current(self.current_records, ports)
if current_ifgrp:
exact_match = self.check_exact_match(ports, current_ifgrp['ports'])
if exact_match or allow_partial_match:
return current_ifgrp, exact_match
return None, None
def check_exact_match(self, desired_ports, current_ifgrp):
matched = set(desired_ports) == set(current_ifgrp)
if not matched:
self.rest_api.log_debug(0, "found LAG with partial match of ports: %s but current is %s" % (desired_ports, current_ifgrp))
return matched
def get_if_grp_current(self, records, ports):
desired_ifgrp_in_current = []
for record in records:
if 'member_ports' in record['lag']:
current_port_list = [port['name'] for port in record['lag']['member_ports']]
for current_port in current_port_list:
if current_port in ports:
desired_ifgrp_in_current.append(self.get_if_grp_detail(record, current_port_list))
break
# if ports are in different LAGs and state is absent, return None
if len(desired_ifgrp_in_current) > 1 and self.parameters['state'] == 'present':
error_msg = "'%s' are in different LAGs" % ', '.join(ports)
self.module.fail_json(msg=error_msg)
elif len(desired_ifgrp_in_current) == 1:
return desired_ifgrp_in_current[0]
return None
def get_if_grp_detail(self, record, current_port_list):
current = {
'node': record['node']['name'],
'uuid': record['uuid'],
'ports': current_port_list
}
if record.get('broadcast_domain'):
current['broadcast_domain'] = record['broadcast_domain']['name']
current['ipspace'] = record['broadcast_domain']['ipspace']['name']
return current
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
"""
if self.use_rest:
return self.create_if_grp_rest()
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())
if self.parameters.get('ports') is not None:
for port in self.parameters.get('ports'):
self.add_port_to_if_grp(port)
def create_if_grp_rest(self):
api = 'network/ethernet/ports'
body = {
'type': 'lag',
'node': {'name': self.parameters['node']},
'lag': {
"mode": self.parameters['mode'],
"distribution_policy": self.parameters['distribution_function']
}
}
if self.parameters.get('ports') is not None:
body['lag']['member_ports'] = self.build_member_ports()
if 'broadcast_domain' in self.parameters:
body['broadcast_domain'] = {'name': self.parameters['broadcast_domain']}
body['broadcast_domain']['ipspace'] = {'name': self.parameters['ipspace']}
dummy, error = rest_generic.post_async(self.rest_api, api, body)
if error:
self.module.fail_json(msg=error)
def delete_if_grp(self, uuid=None):
"""
Deletes a ifgrp
"""
if self.use_rest:
api = 'network/ethernet/ports'
dummy, error = rest_generic.delete_async(self.rest_api, api, uuid)
if error:
self.module.fail_json(msg=error)
else:
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 modify_ports_rest(self, modify, uuid):
api = 'network/ethernet/ports'
body = {}
if 'ports' in modify:
member_ports = self.build_member_ports()
body['lag'] = {'member_ports': member_ports}
if 'broadcast_domain' in modify or 'ipspace' in modify:
broadcast_domain = modify['broadcast_domain'] if 'broadcast_domain' in modify else self.parameters['broadcast_domain']
ipspace = modify['ipspace'] if 'ipspace' in modify else self.parameters['ipspace']
body['broadcast_domain'] = {'name': broadcast_domain}
body['broadcast_domain']['ipspace'] = {'name': ipspace}
dummy, error = rest_generic.patch_async(self.rest_api, api, uuid, body)
if error:
self.module.fail_json(msg=error)
def build_member_ports(self):
member_ports = []
for port in self.parameters['ports']:
port_detail = {'name': port, 'node': {'name': self.parameters['node']}}
member_ports.append(port_detail)
return member_ports
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 apply(self):
# for a LAG, rename is equivalent to adding/removing ports from an existing LAG.
current, exact_match, modify, rename = None, True, None, None
if not self.use_rest:
netapp_utils.ems_log_event_cserver("na_ontap_net_ifgrp", self.server, self.module)
current = self.get_if_grp()
elif self.use_rest:
current, exact_match = self.get_if_grp_rest(self.parameters.get('ports'), allow_partial_match=True)
cd_action = self.na_helper.get_cd_action(current if exact_match else None, self.parameters)
if cd_action == 'create' and self.use_rest:
# if we could not find a lag, or only a lag with a partial match, do a new query using from_lag_ports.
if self.parameters.get('from_lag_ports') is not None:
from_ifgrp, dummy = self.get_if_grp_rest(self.parameters['from_lag_ports'], allow_partial_match=False)
if not from_ifgrp:
error_msg = "Error: cannot find LAG matching from_lag_ports: '%s'." % self.parameters['from_lag_ports']
self.module.fail_json(msg=error_msg)
rename = True
current = from_ifgrp
# if we have a partial match with an existing LAG, we will update the ports.
elif not exact_match and current:
rename = True
if rename:
cd_action = None
if cd_action is None and self.parameters['state'] == 'present':
# with rest, current will have the port details
current_ports = self.get_if_grp_ports() if not self.use_rest else current
modify = self.na_helper.get_modified_attributes(current_ports, self.parameters)
if self.na_helper.changed and not self.module.check_mode:
uuid = current['uuid'] if current and self.use_rest else None
if cd_action == 'create':
self.create_if_grp()
elif cd_action == 'delete':
self.delete_if_grp(uuid)
elif modify:
if self.use_rest:
self.modify_ports_rest(modify, uuid)
else:
self.modify_ports(current_ports['ports'])
self.module.exit_json(changed=self.na_helper.changed, modify=modify)
def main():
"""
Creates the NetApp Ontap Net Route object and runs the correct play task
"""
obj = NetAppOntapIfGrp()
obj.apply()
if __name__ == '__main__':
main()