Skip to content
This repository was archived by the owner on Apr 20, 2021. It is now read-only.

Commit 84b3b21

Browse files
committed
ios l3 interface update
Signed-off-by: Sumit Jaiswal <[email protected]>
1 parent f5e423d commit 84b3b21

File tree

12 files changed

+330
-87
lines changed

12 files changed

+330
-87
lines changed

library/ios_l3_interface.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,6 @@
9393
description:
9494
- Configures the IPv6 address for Interface.
9595
type: str
96-
autoconfig:
97-
description:
98-
- Obtain address using autoconfiguration.
99-
type: bool
100-
dhcp:
101-
description:
102-
- Obtain a ipv6 address using dhcp.
103-
type: bool
10496
state:
10597
choices:
10698
- merged
@@ -138,7 +130,7 @@
138130
# encapsulation dot1Q 20
139131
140132
- name: Merge provided configuration with device configuration
141-
ios_interfaces:
133+
ios_l3_interfaces:
142134
config:
143135
- name: GigabitEthernet0/1
144136
ipv4:
@@ -201,7 +193,7 @@
201193
# ip address 192.168.0.2 255.255.255.0
202194
203195
- name: Replaces device configuration of listed interfaces with provided configuration
204-
ios_interfaces:
196+
ios_l3_interfaces:
205197
config:
206198
- name: GigabitEthernet0/2
207199
ipv4:
@@ -263,14 +255,14 @@
263255
# ip address 192.168.0.2 255.255.255.0
264256
265257
- name: Override device configuration of all interfaces with provided configuration
266-
ios_interfaces:
258+
ios_l3_interfaces:
267259
config:
268260
- name: GigabitEthernet0/2
269261
ipv4:
270262
- address: 192.168.0.1/24
271263
- name: GigabitEthernet0/3.100
272264
ipv6:
273-
- autoconfig: True
265+
- address: autoconfig
274266
operation: overridden
275267
276268
# After state:
@@ -319,7 +311,7 @@
319311
# ip address 192.168.0.2 255.255.255.0
320312
321313
- name: Delete attributes of given interfaces (Note: This won't delete the interface itself)
322-
ios_interfaces:
314+
ios_l3_interfaces:
323315
config:
324316
- name: GigabitEthernet0/2
325317
- name: GigabitEthernet0/3

module_utils/ios/config/l3_interfaces/l3_interfaces.py

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from ansible.module_utils.ios.argspec.l3_interfaces.l3_interfaces import L3_InterfacesArgs
1818
from ansible.module_utils.ios.config.base import ConfigBase
1919
from ansible.module_utils.ios.facts.facts import Facts
20-
20+
import q
2121

2222
class L3_Interfaces(ConfigBase, L3_InterfacesArgs):
2323
"""
@@ -132,6 +132,8 @@ def _state_replaced(**kwargs):
132132
commands.extend(L3_Interfaces.clear_interface(**kwargs))
133133
kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module}
134134
commands.extend(L3_Interfaces.set_interface(**kwargs))
135+
# Remove the duplicate interface call
136+
commands = L3_Interfaces._remove_duplicate_interface(commands)
135137

136138
return commands
137139

@@ -162,6 +164,8 @@ def _state_overridden(**kwargs):
162164
commands.extend(L3_Interfaces.clear_interface(**kwargs))
163165
kwargs = {'want': interface, 'have': each, 'commands': commands, 'module': module}
164166
commands.extend(L3_Interfaces.set_interface(**kwargs))
167+
# Remove the duplicate interface call
168+
commands = L3_Interfaces._remove_duplicate_interface(commands)
165169

166170
return commands
167171

@@ -246,16 +250,38 @@ def validate_ipv6(value, module):
246250
if not 0 <= int(address[1]) <= 128:
247251
module.fail_json(msg='invalid value for mask: {}, mask should be in range 0-128'.format(address[1]))
248252

253+
@staticmethod
254+
def validate_n_expand_ipv4(module, want):
255+
# Check if input IPV4 is valid IP and expand IPV4 with its subnet mask
256+
ip_addr_want = want.get('address')
257+
L3_Interfaces.validate_ipv4(ip_addr_want, module)
258+
ip = ip_addr_want.split('/')
259+
if len(ip) == 2:
260+
ip_addr_want = '{0} {1}'.format(ip[0], to_netmask(ip[1]))
261+
262+
return ip_addr_want
263+
264+
@staticmethod
265+
def _remove_duplicate_interface(commands):
266+
# Remove duplicate interface from commands
267+
set_cmd = []
268+
for each in commands:
269+
if 'interface' in each:
270+
interface = each
271+
if interface not in set_cmd:
272+
set_cmd.append(each)
273+
else:
274+
set_cmd.append(each)
275+
276+
return set_cmd
277+
249278
@staticmethod
250279
def set_interface(**kwargs):
251280
# Set the interface config based on the want and have config
252281
commands = []
253282
want = kwargs['want']
254283
have = kwargs['have']
255284
module = kwargs['module']
256-
clear_cmds = []
257-
if kwargs.get('commands'):
258-
clear_cmds = kwargs['commands']
259285
interface = 'interface ' + want['name']
260286

261287
# To handle Sub-Interface if encapsulation is not already configured
@@ -264,67 +290,59 @@ def set_interface(**kwargs):
264290
module.fail_json(msg='IP routing on a LAN Sub-Interface is only allowed if Encapsulation'
265291
' is configured over respective Sub-Interface'.format(want['name']))
266292
# To handle L3 IPV4 configuration
267-
ipv4 = want.get('ipv4')
268-
if ipv4:
269-
for each_ip in ipv4:
270-
if each_ip.get('address') == 'dhcp':
271-
if each_ip.get('dhcp_client') and (each_ip.get('dhcp_client') != have.get('dhcp_client')
272-
or 'no ip address' in clear_cmds):
273-
if each_ip.get('dhcp_hostname') and each_ip.get('dhcp_hostname') == have.get('dhcp_hostname'):
274-
cmd = 'ip address dhcp client-id GigabitEthernet0/{} hostname {}'.\
275-
format(each_ip.get('dhcp_client'), each_ip.get('dhcp_hostname'))
276-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
277-
else:
278-
cmd = 'ip address dhcp client-id GigabitEthernet0/{}'.format(each_ip.get('dhcp_client'))
279-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
280-
if each_ip.get('dhcp_hostname') and (each_ip.get('dhcp_hostname') != have.get('dhcp_hostname')
281-
or 'no ip address' in clear_cmds):
282-
cmd = 'ip address dhcp hostname {}'.format(each_ip.get('dhcp_hostname'))
283-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
284-
else:
285-
ip_addr = each_ip.get('address')
286-
L3_Interfaces.validate_ipv4(ip_addr, module)
287-
ip = ip_addr.split('/')
288-
if len(ip) == 2:
289-
ip_addr = '{0} {1}'.format(ip[0], to_netmask(ip[1]))
290-
if each_ip.get('secondary'):
291-
if ip_addr != have.get('secondary_ipv4') or 'no ip address' in clear_cmds:
292-
cmd = 'ip address {} secondary'.format(ip_addr)
293-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
294-
elif have.get('ipv4') != ip_addr or 'no ip address' in clear_cmds:
295-
cmd = 'ip address {}'.format(ip_addr)
296-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
293+
if want.get("ipv4"):
294+
for each in want.get("ipv4"):
295+
if each.get('address') != 'dhcp':
296+
ip_addr_want = L3_Interfaces.validate_n_expand_ipv4(module, each)
297+
each['address'] = ip_addr_want
298+
299+
want_ipv4 = set(tuple({k:v for k,v in iteritems(address) if v is not None}.items()) for address in want.get("ipv4") or [])
300+
have_ipv4 = set(tuple(address.items()) for address in have.get("ipv4") or [])
301+
diff = want_ipv4 - have_ipv4
302+
for address in diff:
303+
address = dict(address)
304+
if address.get('address') != 'dhcp':
305+
cmd = "ip address {}".format(address["address"])
306+
if address.get("secondary"):
307+
cmd += " secondary"
308+
elif address.get('address') == 'dhcp':
309+
if address.get('dhcp_client') and address.get('dhcp_hostname'):
310+
cmd = "ip address dhcp client-id GigabitEthernet 0/{} hostname {}".format\
311+
(address.get('dhcp_client'),address.get('dhcp_hostname'))
312+
elif address.get('dhcp_client') and not address.get('dhcp_hostname'):
313+
cmd = "ip address dhcp client-id GigabitEthernet 0/{}".format(address.get('dhcp_client'))
314+
elif not address.get('dhcp_client') and address.get('dhcp_hostname'):
315+
cmd = "ip address dhcp hostname {}".format(address.get('dhcp_client'))
316+
317+
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
297318

298319
# To handle L3 IPV6 configuration
299-
ipv6 = want.get('ipv6')
300-
if ipv6:
301-
for each_ip in ipv6:
302-
if each_ip.get('dhcp') and (have('dhcp') or 'no ipv6 address' in clear_cmds):
303-
cmd = 'ipv6 address dhcp'
304-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
305-
elif each_ip.get('autoconfig') and (have('autoconfig') or 'no ipv6 address' in clear_cmds):
306-
cmd = 'ipv6 address autoconfig'
307-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
308-
else:
309-
ipv6_addr = each_ip.get('address')
310-
L3_Interfaces.validate_ipv6(ipv6_addr, module)
311-
if have.get('ipv6') != ipv6_addr.upper() or 'no ipv6 address' in clear_cmds:
312-
cmd = 'ipv6 address {}'.format(ipv6_addr)
313-
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
320+
want_ipv6 = set(tuple({k:v for k,v in iteritems(address) if v is not None}.items()) for address in want.get("ipv6") or [])
321+
have_ipv6 = set(tuple(address.items()) for address in have.get("ipv6") or [])
322+
diff = want_ipv6 - have_ipv6
323+
for address in diff:
324+
address = dict(address)
325+
L3_Interfaces.validate_ipv6(address.get('address'), module)
326+
cmd = "ipv6 address {}".format(address.get('address'))
327+
L3_Interfaces._add_command_to_interface(interface, cmd, commands)
314328

315329
return commands
316330

317331
@staticmethod
318332
def clear_interface(**kwargs):
319333
# Delete the interface config based on the want and have config
334+
count = 0
320335
commands = []
321336
want = kwargs['want']
322337
have = kwargs['have']
323338
interface = 'interface ' + want['name']
324339

325-
if have.get('secondary') and not want.get('secondary'):
326-
cmd = 'ip address {} secondary'.format(have.get('secondary_ipv4'))
327-
L3_Interfaces._remove_command_from_interface(interface, cmd, commands)
340+
if have.get('ipv4') and want.get('ipv4'):
341+
for each in have.get('ipv4'):
342+
if each.get('secondary') and not (want.get('ipv4')[count].get('secondary')):
343+
cmd = 'ipv4 address {} secondary'.format(each.get('address'))
344+
L3_Interfaces._remove_command_from_interface(interface, cmd, commands)
345+
count += 1
328346
if have.get('ipv4') and not want.get('ipv4'):
329347
L3_Interfaces._remove_command_from_interface(interface, 'ip address', commands)
330348
if have.get('ipv6') and not want.get('ipv6'):

module_utils/ios/facts/l3_interfaces/l3_interfaces.py

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def render_config(self, spec, conf):
5454
:rtype: dictionary
5555
:returns: The generated config
5656
"""
57+
import q
5758
config = deepcopy(spec)
5859
match = re.search(r'^(\S+)', conf)
5960
intf = match.group(1)
@@ -63,32 +64,40 @@ def render_config(self, spec, conf):
6364
# populate the facts from the configuration
6465
config['name'] = normalize_interface(intf)
6566

66-
# Get the configured IPV4 details
67-
ipv4 = re.findall(r"ip address (\S+.*)", conf)
68-
for each in ipv4:
69-
if 'secondary' in each:
70-
config['secondary'] = True
71-
config['secondary_ipv4'] = each.split(' secondary')[0]
67+
ipv4 = []
68+
ipv4_all = re.findall(r"ip address (\S+.*)", conf)
69+
for each in ipv4_all:
70+
each_ipv4 = dict()
71+
if 'secondary' not in each and 'dhcp' not in each:
72+
each_ipv4['address'] = each
73+
elif 'secondary' in each:
74+
each_ipv4['secondary'] = True
75+
each_ipv4['address'] = each.split(' secondary')[0]
7276
elif 'dhcp' in each:
73-
config["ipv4"] = 'dhcp'
77+
each_ipv4['address'] = 'dhcp'
7478
if 'hostname' in each and 'client-id' in each:
75-
config['dhcp_client'] = each.split(' hostname ')[0].split('/')[-1]
76-
config["dhcp_hostname"] = each.split(' hostname ')[-1]
77-
if 'hostname' in each and not config["dhcp_hostname"]:
78-
config["dhcp_hostname"] = each.split(' hostname ')[-1]
79-
if 'client-id' in each and not config['dhcp_client']:
80-
config['dhcp_client'] = each.split('/')[-1]
81-
else:
82-
config["ipv4"] = each
79+
each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1]
80+
each_ipv4['dhcp_client'] = each.split(' hostname ')[0].split('/')[-1]
81+
if 'client-id' in each and not each_ipv4['dhcp_client']:
82+
each_ipv4['dhcp_client'] = each.split('/')[-1]
83+
if 'hostname' in each and not each_ipv4["dhcp_hostname"]:
84+
each_ipv4["dhcp_hostname"] = each.split(' hostname ')[-1]
85+
86+
ipv4.append(each_ipv4)
87+
config['ipv4'] = ipv4
8388

8489
# Get the configured IPV6 details
85-
ipv6 = re.findall(r"ipv6 address (\S+)", conf)
86-
for each in ipv6:
87-
config["ipv6"] = each
88-
if 'autoconfig' in config["ipv6"]:
89-
config['autoconfig'] = True
90-
elif 'dhcp' in config['ipv6']:
91-
config['dhcp'] = True
90+
ipv6 = []
91+
ipv6_all = re.findall(r"ipv6 address (\S+)", conf)
92+
for each in ipv6_all:
93+
each_ipv6 = dict()
94+
if 'autoconfig' in each:
95+
each_ipv6['autoconfig'] = True
96+
if 'dhcp' in each:
97+
each_ipv6['dhcp'] = True
98+
each_ipv6['address'] = each.lower()
99+
ipv6.append(each_ipv6)
100+
config['ipv6'] = ipv6
92101

93102
encapsulation = re.search(r"encapsulation (\S+)", conf)
94103
if encapsulation:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
testcase: "*"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dependencies:
2+
- prepare_ios_tests
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
- name: collect all cli test cases
3+
find:
4+
paths: "{{ role_path }}/tests/cli"
5+
patterns: "{{ testcase }}.yaml"
6+
register: test_cases
7+
delegate_to: localhost
8+
9+
- name: set test_items
10+
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
11+
12+
- name: run test cases (connection=network_cli)
13+
include: "{{ test_case_to_run }}"
14+
with_items: "{{ test_items }}"
15+
loop_control:
16+
loop_var: test_case_to_run
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
- { include: cli.yaml, tags: ['cli'] }
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
- debug:
3+
msg: "START ios_l3_interfaces Deleted integration tests on connection={{ ansible_connection }}"
4+
5+
- include_tasks: reset_config.yaml
6+
7+
- name: Delete attributes of given interfaces (Note: This won't delete the interface itself)
8+
ios_l3_interfaces:
9+
config:
10+
- name: GigabitEthernet0/1
11+
- name: GigabitEthernet0/2
12+
- name: GigabitEthernet0/3
13+
- name: GigabitEthernet0/3.100
14+
state: deleted
15+
16+
- ios_facts:
17+
gather_subset: net_configuration_interfaces
18+
19+
- set_fact:
20+
expected_output:
21+
- name: GigabitEthernet0/1
22+
- name: GigabitEthernet0/2
23+
- name: GigabitEthernet0/3
24+
- name: GigabitEthernet0/3.100
25+
- assert:
26+
that:
27+
- "ansible_facts.net_configuration.l3_interfaces == expected_output"
28+
29+
- include_tasks: reset_config.yaml

0 commit comments

Comments
 (0)