diff options
Diffstat (limited to 'ansible_collections/dellemc/unity/plugins/modules/interface.py')
-rw-r--r-- | ansible_collections/dellemc/unity/plugins/modules/interface.py | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/ansible_collections/dellemc/unity/plugins/modules/interface.py b/ansible_collections/dellemc/unity/plugins/modules/interface.py new file mode 100644 index 00000000..47707baa --- /dev/null +++ b/ansible_collections/dellemc/unity/plugins/modules/interface.py @@ -0,0 +1,521 @@ +#!/usr/bin/python +# Copyright: (c) 2022, Dell Technologies + +# Apache License version 2.0 (see MODULE-LICENSE or http://www.apache.org/licenses/LICENSE-2.0.txt) + +"""Ansible module for managing Interfaces on Unity""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r''' +module: interface +version_added: '1.4.0' +short_description: Manage Interfaces on Unity storage system +description: +- Managing the Interfaces on the Unity storage system includes adding Interfaces to NAS Server, getting + details of interface and deleting configured interfaces. + +extends_documentation_fragment: + - dellemc.unity.unity + +author: +- Meenakshi Dembi (@dembim) <ansible.team@dell.com> + +options: + nas_server_name: + description: + - Name of the NAS server for which interface will be configured. + type: str + nas_server_id: + description: + - ID of the NAS server for which interface will be configured. + type: str + ethernet_port_name: + description: + - Name of the ethernet port. + type: str + ethernet_port_id: + description: + - ID of the ethernet port. + type: str + role: + description: + - Indicates whether interface is configured as production or backup. + choices: [PRODUCTION, BACKUP] + type: str + interface_ip: + description: + - IP of network interface. + required: true + type: str + netmask: + description: + - Netmask of network interface. + type: str + prefix_length: + description: + - Prefix length is mutually exclusive with I(netmask). + type: int + gateway: + description: + - Gateway of network interface. + type: str + vlan_id: + description: + - Vlan id of the interface. + type: int + state: + description: + - Define whether the interface should exist or not. + choices: [present, absent] + required: true + type: str +notes: +- The I(check_mode) is supported. +- Modify operation for interface is not supported. +''' + +EXAMPLES = r''' + + - name: Add Interface as Backup to NAS Server + dellemc.unity.interface: + unispherehost: "{{unispherehost}}" + username: "{{username}}" + password: "{{password}}" + validate_certs: "{{validate_certs}}" + nas_server_name: "dummy_nas" + ethernet_port_name: "SP A 4-Port Card Ethernet Port 0" + role: "BACKUP" + interface_ip: "xx.xx.xx.xx" + netmask: "xx.xx.xx.xx" + gateway: "xx.xx.xx.xx" + vlan_id: 324 + state: "present" + + - name: Add Interface as Production to NAS Server + dellemc.unity.interface: + unispherehost: "{{unispherehost}}" + username: "{{username}}" + password: "{{password}}" + validate_certs: "{{validate_certs}}" + nas_server_name: "dummy_nas" + ethernet_port_name: "SP A 4-Port Card Ethernet Port 0" + role: "PRODUCTION" + interface_ip: "xx.xx.xx.xx" + netmask: "xx.xx.xx.xx" + gateway: "xx.xx.xx.xx" + vlan_id: 324 + state: "present" + + - name: Get interface details + dellemc.unity.interface: + unispherehost: "{{unispherehost}}" + username: "{{username}}" + password: "{{password}}" + validate_certs: "{{validate_certs}}" + nas_server_name: "dummy_nas" + interface_ip: "xx.xx.xx.xx" + state: "present" + + - name: Delete Interface + dellemc.unity.interface: + unispherehost: "{{unispherehost}}" + username: "{{username}}" + password: "{{password}}" + validate_certs: "{{validate_certs}}" + nas_server_name: "dummy_nas" + interface_ip: "xx.xx.xx.xx" + state: "absent" +''' + +RETURN = r''' +changed: + description: Whether or not the resource has changed. + returned: always + type: bool + sample: true +interface_details: + description: Details of the interface. + returned: When interface is configured for NAS Server. + type: dict + contains: + existed: + description: Indicates if interface exists. + type: bool + gateway: + description: Gateway of network interface. + type: str + id: + description: Unique identifier interface. + type: str + ip_address: + description: IP address of interface. + type: str + ip_port: + description: Port on which network interface is configured. + type: dict + contains: + id: + description: ID of ip_port. + type: str + ip_protocol_version: + description: IP protocol version. + type: str + is_disabled: + description: Indicates whether interface is disabled. + type: bool + is_preferred: + description: Indicates whether interface is preferred. + type: bool + mac_address: + description: Mac address of ip_port. + type: bool + name: + description: System configured name of interface. + type: bool + nas_server: + description: Details of NAS server where interface is configured. + type: dict + contains: + id: + description: ID of NAS Server. + type: str + sample: { + "existed": true, + "gateway": "xx.xx.xx.xx", + "hash": 8785300560421, + "health": { + "UnityHealth": { + "hash": 8785300565468 + } + }, + "id": "if_69", + "ip_address": "10.10.10.10", + "ip_port": { + "UnityIpPort": { + "hash": 8785300565300, + "id": "spb_ocp_0_eth0" + } + }, + "ip_protocol_version": "IpProtocolVersionEnum.IPv4", + "is_disabled": false, + "is_preferred": true, + "mac_address": "0C:48:C6:9F:57:BF", + "name": "36_APM00213404194", + "nas_server": { + "UnityNasServer": { + "hash": 8785300565417, + "id": "nas_10" + } + }, + "netmask": "10.10.10.10", + "replication_policy": null, + "role": "FileInterfaceRoleEnum.PRODUCTION", + "source_parameters": null, + "v6_prefix_length": null, + "vlan_id": 324 + } +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.dellemc.unity.plugins.module_utils.storage.dell \ + import utils +import ipaddress +from ipaddress import ip_network + +LOG = utils.get_logger('interface') + +application_type = "Ansible/1.5.0" + + +class Interface(object): + """Class with Interface operations""" + + def __init__(self): + """Define all parameters required by this module""" + self.module_params = utils.get_unity_management_host_parameters() + self.module_params.update(get_interface_parameters()) + + mutually_exclusive = [['nas_server_name', 'nas_server_id'], ['ethernet_port_id', 'ethernet_port_name'], ['netmask', 'prefix_length']] + required_one_of = [['nas_server_name', 'nas_server_id']] + + # initialize the Ansible module + self.module = AnsibleModule( + argument_spec=self.module_params, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + required_one_of=required_one_of + ) + utils.ensure_required_libs(self.module) + + self.unity_conn = utils.get_unity_unisphere_connection( + self.module.params, application_type) + LOG.info('Check Mode Flag %s', self.module.check_mode) + + def get_interface_details(self, nas_server_obj): + """Get interface details. + :param: nas_server_obj: NAS server object. + :return: Returns interface details configured on NAS server. + """ + + try: + nas_server_obj_properties = nas_server_obj._get_properties() + if nas_server_obj_properties['file_interface']: + for item in nas_server_obj_properties['file_interface']['UnityFileInterfaceList']: + interface_id = self.unity_conn.get_file_interface(_id=item['UnityFileInterface']['id']) + if interface_id.ip_address == self.module.params['interface_ip']: + return interface_id + return None + except Exception as e: + error_msg = "Getting Interface details failed" \ + " with error %s" % (str(e)) + LOG.error(error_msg) + self.module.fail_json(msg=error_msg) + + def get_nas_server_obj(self, nas_server_name, nas_server_id): + """Get NAS server ID. + :param: nas_server_name: The name of NAS server + :param: nas_server_id: ID of NAS server + :return: Return NAS server object if exists + """ + + LOG.info("Getting NAS server object") + try: + if nas_server_name: + obj_nas = self.unity_conn.get_nas_server(name=nas_server_name) + return obj_nas + elif nas_server_id: + obj_nas = self.unity_conn.get_nas_server(_id=nas_server_id) + if obj_nas._get_properties()['existed']: + return obj_nas + else: + msg = "NAS server with id %s does not exist" % (nas_server_id) + LOG.error(msg) + self.module.fail_json(msg=msg) + except Exception as e: + msg = "Failed to get details of NAS server with error: %s" % (str(e)) + LOG.error(msg) + self.module.fail_json(msg=msg) + + def add_interface(self, nas_server_obj, ethernet_port_id=None, ethernet_port_name=None, role=None, interface_ip=None, + netmask=None, prefix_length=None, gateway=None, vlan_id=None): + """Adding interface to NAS server. + :param: nas_server_obj: The NAS server object. + :param: ethernet_port_id: ID of ethernet port. + :param: ethernet_port_name: Name of ethernet port. + :param: role: Role of the interface. + :param: interface_ip: IP of interface. + :param: netmask: Netmask for interface. + :param: prefix_length: Prefix length. + :param: gateway: Gateway for interface. + :param: vlan_id: vlan_id for interface. + :return: Return True if interface is configured successfully. + """ + + LOG.info("Adding interface to NAS Server") + try: + nas_server_obj_properties = nas_server_obj._get_properties() + if nas_server_obj_properties['file_interface']: + for item in nas_server_obj_properties['file_interface']['UnityFileInterfaceList']: + interface_id = self.unity_conn.get_file_interface(_id=item['UnityFileInterface']['id']) + if interface_id._get_properties()['ip_address'] == self.module.params['interface_ip']: + return False + if role: + role_value = get_role_enum(role) + if ethernet_port_name: + ethernet_port_info = self.unity_conn.get_ethernet_port(name=ethernet_port_name) + ethernet_port_id = ethernet_port_info.id + if not self.module.check_mode: + utils.UnityFileInterface.create(cli=self.unity_conn._cli, nas_server=nas_server_obj.get_id(), ip_port=ethernet_port_id, + role=role_value, ip=interface_ip, netmask=netmask, v6_prefix_length=prefix_length, + gateway=gateway, vlan_id=vlan_id) + return True + except Exception as e: + msg = "Failed to add interface to NAS Server with error: %s" % (str(e)) + LOG.error(msg) + self.module.fail_json(msg=msg) + + def is_modification_required(self, interface_details): + """Check if modification is required in existing interface/s configured for NAS Server + :param: interface_details: Existing interface details + :return: True if modification is required + """ + key_list = ['vlan_id', 'gateway', 'netmask'] + for item in key_list: + if self.module.params[item] and self.module.params[item] != interface_details[item]: + return True + return False + + def delete_interface(self, interface_obj): + """Delete NFS server. + :param: interface_obj: Interface object. + :return: Return True if interface is deleted. + """ + + LOG.info("Deleting interface") + try: + if not self.module.check_mode: + interface_obj.delete() + return True + except Exception as e: + msg = "Failed to delete interface with error: %s" % (str(e)) + LOG.error(msg) + self.module.fail_json(msg=msg) + + def validate_input_params(self): + """Validates input parameters""" + param_list = ["nas_server_id", "nas_server_name", "ethernet_port_name", "ethernet_port_id", "role", "interface_ip", + "netmask", "gateway"] + + for param in param_list: + msg = "Please provide valid value for: %s" % param + if self.module.params[param] is not None and len(self.module.params[param].strip()) == 0: + errmsg = msg.format(param) + self.module.fail_json(msg=errmsg) + + if self.module.params['vlan_id'] is not None: + if self.module.params['vlan_id'] <= 3 or self.module.params['vlan_id'] >= 4094: + self.module.fail_json(msg='vlan_id should be in the range of 3 to 4094') + + if self.module.params['interface_ip'] and \ + not is_valid_ip(self.module.params['interface_ip']): + self.module.fail_json(msg='The value for interface ip is invalid') + + if self.module.params['gateway'] and \ + not is_valid_ip(self.module.params['gateway']): + self.module.fail_json(msg='The value for gateway is invalid') + + if self.module.params['netmask'] and not \ + utils.is_valid_netmask(self.module.params['netmask']): + self.module.fail_json(msg='Invalid IPV4 address specified for netmask') + + if self.module.params['interface_ip'] and (get_ip_version(self.module.params['interface_ip']) == 6): + self.module.fail_json(msg='IPv6 format is not supported') + + def validate_create_params(self): + """Validates input parameters for adding interface""" + if self.module.params['role'] is None: + self.module.fail_json(msg='Role is a mandatory parameter for adding interface to NAS Server.') + if self.module.params['ethernet_port_name'] is None and self.module.params['ethernet_port_id'] is None: + self.module.fail_json(msg='ethernet_port_name/ethernet_port_id is mandatory parameter for adding interface to NAS Server.') + + def perform_module_operation(self): + """ + Perform different actions on Interface module based on parameters + passed in the playbook + """ + nas_server_id = self.module.params['nas_server_id'] + nas_server_name = self.module.params['nas_server_name'] + ethernet_port_name = self.module.params['ethernet_port_name'] + ethernet_port_id = self.module.params['ethernet_port_id'] + role = self.module.params['role'] + interface_ip = self.module.params['interface_ip'] + netmask = self.module.params['netmask'] + prefix_length = self.module.params['prefix_length'] + gateway = self.module.params['gateway'] + vlan_id = self.module.params['vlan_id'] + state = self.module.params['state'] + + # result is a dictionary that contains changed status and Interface details + result = dict( + changed=False, + interface_details={} + ) + modify_flag = False + + self.validate_input_params() + + interface_details = None + + nas_server_obj = self.get_nas_server_obj(nas_server_name, nas_server_id) + + interface_obj = self.get_interface_details(nas_server_obj) + + if interface_obj and state == 'present': + interface_details = interface_obj._get_properties() + modify_flag = self.is_modification_required(interface_details) + if modify_flag: + self.module.fail_json(msg="Modification of Interfaces for NAS server is not supported through Ansible module") + + if not interface_obj and state == 'present': + self.validate_create_params() + + result['changed'] = self.add_interface(nas_server_obj, ethernet_port_id, ethernet_port_name, role, + interface_ip, netmask, prefix_length, gateway, vlan_id) + + if interface_obj and state == 'absent': + result['changed'] = self.delete_interface(interface_obj) + + if result['changed']: + nas_server_obj = self.get_nas_server_obj(nas_server_name, nas_server_id) + interface_obj = self.get_interface_details(nas_server_obj) + if interface_obj: + interface_details = interface_obj._get_properties() + + result['interface_details'] = interface_details + + self.module.exit_json(**result) + + +def get_interface_parameters(): + """This method provide parameters required for the ansible + Interface module on Unity""" + return dict( + nas_server_id=dict(type='str'), + nas_server_name=dict(type='str'), + ethernet_port_name=dict(type='str'), + ethernet_port_id=dict(type='str'), + role=dict(type='str', choices=['PRODUCTION', 'BACKUP']), + interface_ip=dict(required=True, type='str'), + netmask=dict(type='str'), + prefix_length=dict(type='int'), + gateway=dict(type='str'), + vlan_id=dict(type='int'), + state=dict(required=True, type='str', choices=['present', 'absent']) + ) + + +def get_role_enum(role): + """Getting correct enum values for role + :param: role: Indicates role of interface. + :return: enum value for role. + """ + if utils.FileInterfaceRoleEnum[role]: + role = utils.FileInterfaceRoleEnum[role] + return role + + +def is_valid_ip(address): + """Validating IP address format + :param: address: IP address to be validated for format. + """ + try: + ipaddress.ip_address(address) + return True + except ValueError: + return False + + +def get_ip_version(val): + """Returns IP address version + :param: val: IP address to be validated for version. + """ + try: + val = u'{0}'.format(val) + ip = ip_network(val, strict=False) + return ip.version + except ValueError: + return 0 + + +def main(): + """Create Unity Interface object and perform action on it + based on user input from playbook""" + obj = Interface() + obj.perform_module_operation() + + +if __name__ == '__main__': + main() |