diff --git a/lib/ansible/module_utils/net_tools/nios/api.py b/lib/ansible/module_utils/net_tools/nios/api.py index 64392942da3..072b7944706 100644 --- a/lib/ansible/module_utils/net_tools/nios/api.py +++ b/lib/ansible/module_utils/net_tools/nios/api.py @@ -55,6 +55,8 @@ NIOS_SRV_RECORD = 'record:srv' NIOS_NAPTR_RECORD = 'record:naptr' NIOS_TXT_RECORD = 'record:txt' NIOS_NSGROUP = 'nsgroup' +NIOS_IPV4_FIXED_ADDRESS = 'fixedaddress' +NIOS_IPV6_FIXED_ADDRESS = 'ipv6fixedaddress' NIOS_PROVIDER_SPEC = { 'host': dict(), @@ -368,6 +370,8 @@ class WapiModule(WapiBase): test_obj_filter = dict([('name', name)]) else: test_obj_filter = dict([('name', name), ('view', obj_filter['view'])]) + elif (ib_obj_type == NIOS_IPV4_FIXED_ADDRESS or ib_obj_type == NIOS_IPV6_FIXED_ADDRESS and 'mac' in obj_filter): + test_obj_filter = dict([['mac', obj_filter['mac']]]) # check if test_obj_filter is empty copy passed obj_filter else: test_obj_filter = obj_filter diff --git a/lib/ansible/modules/net_tools/nios/nios_fixed_address.py b/lib/ansible/modules/net_tools/nios/nios_fixed_address.py new file mode 100644 index 00000000000..c936d440ad9 --- /dev/null +++ b/lib/ansible/modules/net_tools/nios/nios_fixed_address.py @@ -0,0 +1,262 @@ +#!/usr/bin/python +# Copyright (c) 2018 Red Hat, Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'certified'} + + +DOCUMENTATION = ''' +--- +module: nios_fixed_address +version_added: "2.8" +author: "Sumit Jaiswal (@sjaiswal)" +short_description: Configure Infoblox NIOS DHCP Fixed Address +description: + - A fixed address is a specific IP address that a DHCP server + always assigns when a lease request comes from a particular + MAC address of the clien. + - Supports both IPV4 and IPV6 internet protocols +requirements: + - infoblox-client +extends_documentation_fragment: nios +options: + name: + description: + - Specifies the hostname with which fixed DHCP ip-address is stored + for respective mac. + required: false + ipaddr: + description: + - IPV4/V6 address of the fixed address. + required: true + mac: + description: + - The MAC address of the interface. + required: true + network: + description: + - Specifies the network range in which ipaddr exists. + required: true + aliases: + - network + network_view: + description: + - Configures the name of the network view to associate with this + configured instance. + required: false + default: default + options: + description: + - Configures the set of DHCP options to be included as part of + the configured network instance. This argument accepts a list + of values (see suboptions). When configuring suboptions at + least one of C(name) or C(num) must be specified. + suboptions: + name: + description: + - The name of the DHCP option to configure + num: + description: + - The number of the DHCP option to configure + value: + description: + - The value of the DHCP option specified by C(name) + required: true + use_option: + description: + - Only applies to a subset of options (see NIOS API documentation) + type: bool + default: 'yes' + vendor_class: + description: + - The name of the space this DHCP option is associated to + default: DHCP + extattrs: + description: + - Allows for the configuration of Extensible Attributes on the + instance of the object. This argument accepts a set of key / value + pairs for configuration. + comment: + description: + - Configures a text string comment to be associated with the instance + of this object. The provided text string will be configured on the + object instance. + state: + description: + - Configures the intended state of the instance of the object on + the NIOS server. When this value is set to C(present), the object + is configured on the device and when this value is set to C(absent) + the value is removed (if necessary) from the device. + default: present + choices: + - present + - absent +''' + +EXAMPLES = ''' +- name: configure ipv4 dhcp fixed address + nios_fixed_address: + name: ipv4_fixed + ipaddr: 192.168.10.1 + mac: 08:6d:41:e8:fd:e8 + network: 192.168.10.0/24 + network_view: default + comment: this is a test comment + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + connection: local +- name: configure a ipv6 dhcp fixed address + nios_fixed_address: + name: ipv6_fixed + ipaddr: fe80::1/10 + mac: 08:6d:41:e8:fd:e8 + network: fe80::/64 + network_view: default + comment: this is a test comment + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + connection: local +- name: set dhcp options for a ipv4 fixed address + nios_fixed_address: + name: ipv4_fixed + ipaddr: 192.168.10.1 + mac: 08:6d:41:e8:fd:e8 + network: 192.168.10.0/24 + network_view: default + comment: this is a test comment + options: + - name: domain-name + value: ansible.com + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + connection: local +- name: remove a ipv4 dhcp fixed address + nios_fixed_address: + name: ipv4_fixed + ipaddr: 192.168.10.1 + mac: 08:6d:41:e8:fd:e8 + network: 192.168.10.0/24 + network_view: default + state: absent + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + connection: local +''' + +RETURN = ''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.net_tools.nios.api import WapiModule +from ansible.module_utils.network.common.utils import validate_ip_address, validate_ip_v6_address +from ansible.module_utils.net_tools.nios.api import NIOS_IPV4_FIXED_ADDRESS, NIOS_IPV6_FIXED_ADDRESS + + +def options(module): + ''' Transforms the module argument into a valid WAPI struct + This function will transform the options argument into a structure that + is a valid WAPI structure in the format of: + { + name: , + num: , + value: , + use_option: , + vendor_class: + } + It will remove any options that are set to None since WAPI will error on + that condition. It will also verify that either `name` or `num` is + set in the structure but does not validate the values are equal. + The remainder of the value validation is performed by WAPI + ''' + options = list() + for item in module.params['options']: + opt = dict([(k, v) for k, v in iteritems(item) if v is not None]) + if 'name' not in opt and 'num' not in opt: + module.fail_json(msg='one of `name` or `num` is required for option value') + options.append(opt) + return options + + +def validate_ip_addr_type(ip, arg_spec, module): + '''This function will check if the argument ip is type v4/v6 and return appropriate infoblox network type + ''' + check_ip = ip.split('/') + + if validate_ip_address(check_ip[0]) and 'ipaddr' in arg_spec: + arg_spec['ipv4addr'] = arg_spec.pop('ipaddr') + module.params['ipv4addr'] = module.params.pop('ipaddr') + return NIOS_IPV4_FIXED_ADDRESS, arg_spec, module + elif validate_ip_v6_address(check_ip[0]) and 'ipaddr' in arg_spec: + arg_spec['ipv6addr'] = arg_spec.pop('ipaddr') + module.params['ipv6addr'] = module.params.pop('ipaddr') + return NIOS_IPV6_FIXED_ADDRESS, arg_spec, module + + +def main(): + ''' Main entry point for module execution + ''' + option_spec = dict( + # one of name or num is required; enforced by the function options() + name=dict(), + num=dict(type='int'), + + value=dict(required=True), + + use_option=dict(type='bool', default=True), + vendor_class=dict(default='DHCP') + ) + + ib_spec = dict( + name=dict(required=True), + ipaddr=dict(required=True, aliases=['ipaddr'], ib_req=True), + mac=dict(required=True, aliases=['mac'], ib_req=True), + network=dict(required=True, aliases=['network'], ib_req=True), + network_view=dict(default='default', aliases=['network_view']), + + options=dict(type='list', elements='dict', options=option_spec, transform=options), + + extattrs=dict(type='dict'), + comment=dict() + ) + + argument_spec = dict( + provider=dict(required=True), + state=dict(default='present', choices=['present', 'absent']) + ) + + argument_spec.update(ib_spec) + argument_spec.update(WapiModule.provider_spec) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + # to get the argument ipaddr + obj_filter = dict([(k, module.params[k]) for k, v in iteritems(ib_spec) if v.get('ib_req')]) + # to modify argument based on ipaddr type i.e. IPV4/IPV6 + fixed_address_ip_type, ib_spec, module = validate_ip_addr_type(obj_filter['ipaddr'], ib_spec, module) + + wapi = WapiModule(module) + + result = wapi.run(fixed_address_ip_type, ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/net_tools/nios/test_nios_fixed_address.py b/test/units/modules/net_tools/nios/test_nios_fixed_address.py new file mode 100644 index 00000000000..9b21c2d3202 --- /dev/null +++ b/test/units/modules/net_tools/nios/test_nios_fixed_address.py @@ -0,0 +1,201 @@ +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible.module_utils.net_tools.nios import api +from ansible.modules.net_tools.nios import nios_fixed_address +from units.compat.mock import patch, MagicMock, Mock +from .test_nios_module import TestNiosModule, load_fixture + + +class TestNiosFixedAddressModule(TestNiosModule): + + module = nios_fixed_address + + def setUp(self): + super(TestNiosFixedAddressModule, self).setUp() + self.module = MagicMock(name='ansible.modules.net_tools.nios.nios_fixed_address.WapiModule') + self.module.check_mode = False + self.module.params = {'provider': None} + self.mock_wapi = patch('ansible.modules.net_tools.nios.nios_fixed_address.WapiModule') + self.exec_command = self.mock_wapi.start() + self.mock_wapi_run = patch('ansible.modules.net_tools.nios.nios_fixed_address.WapiModule.run') + self.mock_wapi_run.start() + self.load_config = self.mock_wapi_run.start() + + def tearDown(self): + super(TestNiosFixedAddressModule, self).tearDown() + self.mock_wapi.stop() + self.mock_wapi_run.stop() + + def load_fixtures(self, commands=None): + self.exec_command.return_value = (0, load_fixture('nios_result.txt').strip(), None) + self.load_config.return_value = dict(diff=None, session='session') + + def _get_wapi(self, test_object): + wapi = api.WapiModule(self.module) + wapi.get_object = Mock(name='get_object', return_value=test_object) + wapi.create_object = Mock(name='create_object') + wapi.update_object = Mock(name='update_object') + wapi.delete_object = Mock(name='delete_object') + return wapi + + def test_nios_fixed_address_ipv4_create(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'test_fa', 'ipaddr': '192.168.10.1', 'mac': '08:6d:41:e8:fd:e8', + 'network': '192.168.10.0/24', 'network_view': 'default', 'comment': None, 'extattrs': None} + + test_object = None + test_spec = { + "name": {}, + "ipaddr": {"ib_req": True}, + "mac": {"ib_req": True}, + "network": {"ib_req": True}, + "network_view": {}, + "comment": {}, + "extattrs": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.create_object.assert_called_once_with('testobject', {'name': 'test_fa', 'ipaddr': '192.168.10.1', 'mac': '08:6d:41:e8:fd:e8', + 'network': '192.168.10.0/24', 'network_view': 'default'}) + + def test_nios_fixed_address_ipv4_dhcp_update(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'test_fa', 'ipaddr': '192.168.10.1', 'mac': '08:6d:41:e8:fd:e8', + 'network': '192.168.10.0/24', 'network_view': 'default', 'comment': 'updated comment', 'extattrs': None} + + test_object = [ + { + "comment": "test comment", + "name": "test_fa", + "_ref": "network/ZG5zLm5ldHdvcmtfdmlldyQw:default/true", + "ipaddr": "192.168.10.1", + "mac": "08:6d:41:e8:fd:e8", + "network": "192.168.10.0/24", + "network_view": "default", + "extattrs": {'options': {'name': 'test', 'value': 'ansible.com'}} + } + ] + + test_spec = { + "name": {}, + "ipaddr": {"ib_req": True}, + "mac": {"ib_req": True}, + "network": {"ib_req": True}, + "network_view": {}, + "comment": {}, + "extattrs": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + + def test_nios_fixed_address_ipv4_remove(self): + self.module.params = {'provider': None, 'state': 'absent', 'name': 'test_fa', 'ipaddr': '192.168.10.1', 'mac': '08:6d:41:e8:fd:e8', + 'network': '192.168.10.0/24', 'network_view': 'default', 'comment': None, 'extattrs': None} + + ref = "fixedaddress/ZG5zLm5ldHdvcmtfdmlldyQw:ansible/false" + + test_object = [{ + "comment": "test comment", + "name": "test_fa", + "_ref": ref, + "ipaddr": "192.168.10.1", + "mac": "08:6d:41:e8:fd:e8", + "network": "192.168.10.0/24", + "network_view": "default", + "extattrs": {'Site': {'value': 'test'}} + }] + + test_spec = { + "name": {}, + "ipaddr": {"ib_req": True}, + "mac": {"ib_req": True}, + "network": {"ib_req": True}, + "network_view": {}, + "comment": {}, + "extattrs": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.delete_object.assert_called_once_with(ref) + + def test_nios_fixed_address_ipv6_create(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'test_fa', 'ipaddr': 'fe80::1/10', 'mac': '08:6d:41:e8:fd:e8', + 'network': 'fe80::/64', 'network_view': 'default', 'comment': None, 'extattrs': None} + + test_object = None + + test_spec = { + "name": {}, + "ipaddr": {"ib_req": True}, + "mac": {"ib_req": True}, + "network": {"ib_req": True}, + "network_view": {}, + "comment": {}, + "extattrs": {} + } + + wapi = self._get_wapi(test_object) + print("WAPI: ", wapi) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.create_object.assert_called_once_with('testobject', {'name': 'test_fa', 'ipaddr': 'fe80::1/10', 'mac': '08:6d:41:e8:fd:e8', + 'network': 'fe80::/64', 'network_view': 'default'}) + + def test_nios_fixed_address_ipv6_remove(self): + self.module.params = {'provider': None, 'state': 'absent', 'name': 'test_fa', 'ipaddr': 'fe80::1/10', 'mac': '08:6d:41:e8:fd:e8', + 'network': 'fe80::/64', 'network_view': 'default', 'comment': None, 'extattrs': None} + + ref = "ipv6fixedaddress/ZG5zLm5ldHdvcmtfdmlldyQw:ansible/false" + + test_object = [{ + "comment": "test comment", + "name": "test_fa", + "_ref": ref, + "ipaddr": "fe80::1/10", + "mac": "08:6d:41:e8:fd:e8", + "network": "fe80::/64", + "network_view": "default", + "extattrs": {'Site': {'value': 'test'}} + }] + + test_spec = { + "name": {}, + "ipaddr": {"ib_req": True}, + "mac": {"ib_req": True}, + "network": {"ib_req": True}, + "network_view": {}, + "comment": {}, + "extattrs": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.delete_object.assert_called_once_with(ref)