#!/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) 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.7.1" 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 and \ (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()