Skip to content

Commit 07976f4

Browse files
authored
Merge branch 'develop' into child_fabric_module
2 parents ae49831 + c419ae2 commit 07976f4

File tree

26 files changed

+1388
-223
lines changed

26 files changed

+1388
-223
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,6 @@ venv.bak/
7777

7878
# mypy
7979
.mypy_cache/
80+
81+
# Ignore Integration Tests Files Directories
82+
tests/integration/targets/ndfc_interface/files

docs/cisco.dcnm.dcnm_interface_module.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,7 @@ Parameters
610610
</td>
611611
<td>
612612
<div>Interface mode</div>
613+
<div>When ethernet interface is a PortChannel or vPC member, mode is ignored. The only properties that can be managed for PortChannel or vPC member interfaces are &#x27;admin_state&#x27;, &#x27;description&#x27; and &#x27;cmds&#x27;. All other properties are ignored.</div>
613614
</td>
614615
</tr>
615616
<tr>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[defaults]
2+
# This ansible.cfg file is only used for testing purposes in this directory.
3+
roles_path = <path to targets>/collections/ansible_collections/cisco/dcnm/tests/integration/targets
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
all:
2+
vars:
3+
ansible_user: "admin"
4+
ansible_password: "password"
5+
ansible_python_interpreter: python
6+
ansible_httpapi_validate_certs: False
7+
ansible_httpapi_use_ssl: True
8+
children:
9+
ndfc:
10+
vars:
11+
ansible_connection: ansible.netcommon.httpapi
12+
ansible_network_os: cisco.dcnm.dcnm
13+
hosts:
14+
nac-ndfc1:
15+
ansible_host: 10.0.55.128
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
# This playbook can be used to execute integration tests for
3+
# the role located in:
4+
#
5+
# tests/integration/targets/dcnm_interface
6+
#
7+
# Modify the vars section with details for your testing setup.
8+
#
9+
- hosts: ndfc
10+
gather_facts: no
11+
connection: ansible.netcommon.httpapi
12+
13+
vars:
14+
# Uncomment testcase to run a specific test
15+
testcase: ndfc_pc_members
16+
ansible_it_fabric: test_fabric
17+
ansible_switch1: 192.168.1.13
18+
ansible_switch2: 192.168.1.14
19+
ansible_eth_intf2: Ethernet1/2
20+
ansible_eth_intf3: Ethernet1/3
21+
ansible_eth_intf4: Ethernet1/4
22+
ansible_eth_intf5: Ethernet1/5
23+
ansible_eth_intf6: Ethernet1/6
24+
ansible_eth_intf7: Ethernet1/7
25+
ansible_eth_intf8: Ethernet1/8
26+
ansible_eth_intf9: Ethernet1/9
27+
ansible_eth_intf10: Ethernet1/10
28+
ansible_eth_intf11: Ethernet1/11
29+
ansible_eth_intf12: Ethernet1/12
30+
ansible_eth_intf13: Ethernet1/13
31+
ansible_eth_intf14: Ethernet1/14
32+
ansible_eth_intf15: Ethernet1/15
33+
ansible_eth_intf16: Ethernet1/16
34+
ansible_eth_intf17: Ethernet1/17
35+
ansible_eth_intf18: Ethernet1/18
36+
ansible_eth_intf19: Ethernet1/19
37+
ansible_eth_intf20: Ethernet1/20
38+
ansible_eth_intf21: Ethernet1/21
39+
port_channel_1: port-channel801
40+
port_channel_2: port-channel802
41+
port_channel_3: port-channel803
42+
43+
roles:
44+
- ndfc_interface

plugins/__init__.py

Whitespace-only changes.
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
from __future__ import absolute_import, division, print_function
2+
3+
4+
__metaclass__ = type
5+
6+
from ansible.utils.display import Display
7+
from ansible.plugins.action import ActionBase
8+
9+
display = Display()
10+
11+
12+
class ActionModule(ActionBase):
13+
14+
def run(self, tmp=None, task_vars=None):
15+
results = super(ActionModule, self).run(tmp, task_vars)
16+
results['failed'] = False
17+
18+
ndfc_data = self._task.args['ndfc_data']
19+
test_data = self._task.args['test_data']
20+
21+
# import epdb ; epdb.st()
22+
23+
expected_state = {}
24+
expected_state['pc_trunk_description'] = test_data['pc_trunk_desc']
25+
expected_state['pc_trunk_member_description'] = test_data['eth_trunk_desc']
26+
expected_state['pc_access_description'] = test_data['pc_access_desc']
27+
expected_state['pc_access_member_description'] = test_data['eth_access_desc']
28+
expected_state['pc_l3_description'] = test_data['pc_l3_desc']
29+
expected_state['pc_l3_member_description'] = test_data['eth_l3_desc']
30+
# --
31+
expected_state['pc_trunk_host_policy'] = 'int_port_channel_trunk_host'
32+
expected_state['pc_trunk_member_policy'] = 'int_port_channel_trunk_member_11_1'
33+
# --
34+
expected_state['pc_access_host_policy'] = 'int_port_channel_access_host'
35+
expected_state['pc_access_member_policy'] = 'int_port_channel_access_member_11_1'
36+
# --
37+
expected_state['pc_l3_policy'] = 'int_l3_port_channel'
38+
expected_state['pc_l3_member_policy'] = 'int_l3_port_channel_member'
39+
40+
interface_list = [test_data['pc1'], test_data['eth_intf8'], test_data['eth_intf9'],
41+
test_data['pc2'], test_data['eth_intf10'], test_data['eth_intf11'],
42+
test_data['pc3'], test_data['eth_intf12'], test_data['eth_intf13']]
43+
44+
if len(ndfc_data['response']) == 0:
45+
results['failed'] = True
46+
results['msg'] = 'No response data found'
47+
return results
48+
49+
# ReWrite List Data to Dict keyed by interface name
50+
ndfc_data_dict = {}
51+
for interface in ndfc_data['response']:
52+
int = interface['interfaces'][0]['ifName']
53+
ndfc_data_dict[int] = interface['interfaces'][0]
54+
ndfc_data_dict[int]['policy'] = interface['policy']
55+
56+
for interface in interface_list:
57+
if interface not in ndfc_data_dict.keys():
58+
results['failed'] = True
59+
results['msg'] = f'Interface {interface} not found in response data'
60+
return results
61+
62+
# Use a regex to match string 'Eth' in interface variable
63+
if interface == test_data['pc1']:
64+
if ndfc_data_dict[interface]['policy'] != expected_state['pc_trunk_host_policy']:
65+
results['failed'] = True
66+
results['msg'] = f'Interface {interface} policy is not {expected_state["pc_trunk_host_policy"]}'
67+
return results
68+
if ndfc_data_dict[interface]['nvPairs']['DESC'] != expected_state['pc_trunk_description']:
69+
results['failed'] = True
70+
results['msg'] = f'Interface {interface} description is not {expected_state["pc_trunk_description"]}'
71+
return results
72+
if interface == test_data['eth_intf8'] or interface == test_data['eth_intf9']:
73+
if ndfc_data_dict[interface]['policy'] != expected_state['pc_trunk_member_policy']:
74+
results['failed'] = True
75+
results['msg'] = f'Interface {interface} policy is not {expected_state["pc_trunk_member_policy"]}'
76+
return results
77+
if ndfc_data_dict[interface]['nvPairs']['DESC'] != expected_state['pc_trunk_member_description']:
78+
results['failed'] = True
79+
results['msg'] = f'Interface {interface} description is not {expected_state["pc_trunk_member_description"]}'
80+
return results
81+
82+
if interface == test_data['pc2']:
83+
if ndfc_data_dict[interface]['policy'] != expected_state['pc_access_host_policy']:
84+
results['failed'] = True
85+
results['msg'] = f'Interface {interface} policy is {expected_state["pc_access_host_policy"]}'
86+
return results
87+
if ndfc_data_dict[interface]['nvPairs']['DESC'] != expected_state['pc_access_description']:
88+
results['failed'] = True
89+
results['msg'] = f'Interface {interface} description is not {expected_state["pc_access_description"]}'
90+
return results
91+
if interface == test_data['eth_intf10'] or interface == test_data['eth_intf11']:
92+
if ndfc_data_dict[interface]['policy'] != expected_state['pc_access_member_policy']:
93+
results['failed'] = True
94+
results['msg'] = f'Interface {interface} policy is not {expected_state["pc_access_member_policy"]}'
95+
return results
96+
if ndfc_data_dict[interface]['nvPairs']['DESC'] != expected_state['pc_access_member_description']:
97+
results['failed'] = True
98+
results['msg'] = f'Interface {interface} description is not {expected_state["pc_access_member_description"]}'
99+
return results
100+
101+
if interface == test_data['pc3']:
102+
if ndfc_data_dict[interface]['policy'] != expected_state['pc_l3_policy']:
103+
results['failed'] = True
104+
results['msg'] = f'Interface {interface} policy is not {expected_state["pc_l3_policy"]}'
105+
return results
106+
if ndfc_data_dict[interface]['nvPairs']['DESC'] != expected_state['pc_l3_description']:
107+
results['failed'] = True
108+
results['msg'] = f'Interface {interface} description is not {expected_state["pc_l3_description"]}'
109+
return results
110+
if interface == test_data['eth_intf12'] or interface == test_data['eth_intf13']:
111+
if ndfc_data_dict[interface]['policy'] != expected_state['pc_l3_member_policy']:
112+
results['failed'] = True
113+
results['msg'] = f'Interface {interface} policy is not {expected_state["pc_l3_member_policy"]}'
114+
return results
115+
if ndfc_data_dict[interface]['nvPairs']['DESC'] != expected_state['pc_l3_member_description']:
116+
results['failed'] = True
117+
results['msg'] = f'Interface {interface} description is not {expected_state["pc_l3_member_description"]}'
118+
return results
119+
120+
return results

plugins/module_utils/fabric/fabric_details_v2.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,32 @@ def is_read_only(self):
361361
self.log.debug(msg)
362362
return None
363363

364+
@property
365+
def per_vrf_loopback_auto_provision(self):
366+
"""
367+
### Summary
368+
The ``nvPairs.PER_VRF_LOOPBACK_AUTO_PROVISION`` value of the fabric
369+
specified with filter.
370+
371+
### Raises
372+
None
373+
374+
### Type
375+
boolean
376+
377+
### Returns
378+
- True
379+
- False
380+
- None
381+
"""
382+
try:
383+
return self._get_nv_pair("PER_VRF_LOOPBACK_AUTO_PROVISION")
384+
except ValueError as error:
385+
msg = "Failed to retrieve per_vrf_loopback_auto_provision: "
386+
msg += f"Error detail: {error}"
387+
self.log.debug(msg)
388+
return None
389+
364390
@property
365391
def replication_mode(self):
366392
"""

plugins/module_utils/network/dcnm/dcnm.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,3 +820,42 @@ def dcnm_post_request(path, hdrs, verify_flag, upload_files):
820820
json_resp["REQUEST_PATH"] = path
821821
json_resp.pop("message")
822822
return json_resp
823+
824+
825+
def find_dict_in_list_by_key_value(search: list, key: str, value: str):
826+
"""
827+
# Summary
828+
829+
Find a dictionary in a list of dictionaries.
830+
831+
832+
## Raises
833+
834+
None
835+
836+
## Parameters
837+
838+
- search: A list of dict
839+
- key: The key to lookup in each dict
840+
- value: The desired matching value for key
841+
842+
## Returns
843+
844+
Either the first matching dict or None
845+
846+
## Usage
847+
848+
```python
849+
content = [{"foo": "bar"}, {"foo": "baz"}]
850+
851+
match = find_dict_in_list_by_key_value(search=content, key="foo", value="baz")
852+
print(f"{match}")
853+
# -> {"foo": "baz"}
854+
855+
match = find_dict_in_list_by_key_value(search=content, key="foo", value="bingo")
856+
print(f"{match}")
857+
# -> None
858+
```
859+
"""
860+
match = (d for d in search if d[key] == value)
861+
return next(match, None)

0 commit comments

Comments
 (0)