Skip to content

Commit 990582c

Browse files
author
Mallik M J
committed
Fix for issue 229 to accept individual vlans in accepted-vlans parameter
1 parent e2c0b91 commit 990582c

File tree

4 files changed

+243
-13
lines changed

4 files changed

+243
-13
lines changed

plugins/modules/dcnm_interface.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4052,6 +4052,7 @@ def dcnm_intf_get_diff_overridden(self, cfg):
40524052
== "DCNM_INTF_MATCH"
40534053
):
40544054
continue
4055+
40554056
if uelem is not None:
40564057
# Before defaulting ethernet interfaces, check if they are
40574058
# member of any port-channel. If so, do not default that
@@ -4850,6 +4851,66 @@ def dcnm_intf_send_message_to_dcnm(self):
48504851
else:
48514852
self.result["changed"] = False
48524853

4854+
def dcnm_intf_get_xlated_object(self, cfg, key):
4855+
4856+
"""
4857+
Routine to translate individual vlans like 45, 55 to 44-44 and 55-55 format
4858+
4859+
Parameters:
4860+
cfg (dict): Config element that includes the object idebtified by key to be translated
4861+
key (str): key identifying the object to be translated
4862+
4863+
Returns:
4864+
translated object
4865+
"""
4866+
4867+
citems = cfg["profile"][key].split(",")
4868+
4869+
for index in range(len(citems)):
4870+
if (
4871+
(citems[index].lower() == "none")
4872+
or (citems[index].lower() == "all")
4873+
or ("-" in citems[index])
4874+
):
4875+
continue
4876+
4877+
# Playbook config includes individual vlans in allowed_vlans object. Convert the elem to
4878+
# appropriate format i.e. vlaues in the form of 4, 7 to 4-4 and 7-7
4879+
citems[index] = citems[index].strip() + "-" + citems[index].strip()
4880+
return citems
4881+
4882+
def dcnm_intf_translate_allowed_vlans(self, cfg):
4883+
4884+
"""
4885+
Routine to translate xxx_allowed_vlans object in the config. 'xxx_allowed_vlans' object will
4886+
allow only 'none', 'all', or 'vlan-ranges like 1-5' values. It does not allow individual
4887+
vlans to be included. To enable user to include individual vlans in the playbook config, this
4888+
routine tranlates the individual vlans like 3, 5 etc to 3-3 and 5-5 format.
4889+
4890+
Parameters:
4891+
cfg (dict): Config element that needs to be translated
4892+
4893+
Returns:
4894+
None
4895+
"""
4896+
4897+
if cfg.get("profile", None) is None:
4898+
return
4899+
4900+
if cfg["profile"].get("allowed_vlans", None) is not None:
4901+
xlated_obj = self.dcnm_intf_get_xlated_object(cfg, "allowed_vlans")
4902+
cfg["profile"]["allowed_vlans"] = ",".join(xlated_obj)
4903+
if cfg["profile"].get("peer1_allowed_vlans", None) is not None:
4904+
xlated_obj = self.dcnm_intf_get_xlated_object(
4905+
cfg, "peer1_allowed_vlans"
4906+
)
4907+
cfg["profile"]["peer1_allowed_vlans"] = ",".join(xlated_obj)
4908+
if cfg["profile"].get("peer2_allowed_vlans", None) is not None:
4909+
xlated_obj = self.dcnm_intf_get_xlated_object(
4910+
cfg, "peer2_allowed_vlans"
4911+
)
4912+
cfg["profile"]["peer2_allowed_vlans"] = ",".join(xlated_obj)
4913+
48534914
def dcnm_intf_update_inventory_data(self):
48544915

48554916
"""
@@ -4958,6 +5019,26 @@ def dcnm_translate_playbook_info(self, config, ip_sn, hn_sn):
49585019
cfg["switch"].remove(sw_elem)
49595020
index = index + 1
49605021

5022+
# 'allowed-vlans' in the case of trunk interfaces accepts 'all', 'none' and 'vlan-ranges' which
5023+
# will be of the form 20-30 etc. There is not way to include individual vlans which are not contiguous.
5024+
# To include individual vlans like 3,6,20 etc. user must input them in the form 3-3, 6-6, 20-20 which is
5025+
# not very intuitive. To handle this scenario, we allow playbooks to include individual vlans and translate
5026+
# them here appropriately.
5027+
5028+
if cfg.get("profile", None) is not None:
5029+
if (
5030+
(
5031+
cfg["profile"].get("peer1_allowed_vlans", None)
5032+
is not None
5033+
)
5034+
or (
5035+
cfg["profile"].get("peer2_allowed_vlans", None)
5036+
is not None
5037+
)
5038+
or (cfg["profile"].get("allowed_vlans", None) is not None)
5039+
):
5040+
self.dcnm_intf_translate_allowed_vlans(cfg)
5041+
49615042

49625043
def main():
49635044

tests/integration/targets/dcnm_interface/tests/dcnm/dcnm_pc_merge.yaml

Lines changed: 75 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
- name: Put the fabric to default state
99
cisco.dcnm.dcnm_interface:
1010
check_deploy: True
11-
fabric: "{{ ansible_it_fabric }}"
11+
fabric: "{{ ansible_it_fabric }}"
1212
state: overridden # only choose form [merged, replaced, deleted, overridden, query]
13-
register: result
13+
register: result
1414

1515
- assert:
1616
that:
17-
- 'item["RETURN_CODE"] == 200'
17+
- 'item["RETURN_CODE"] == 200'
1818
loop: '{{ result.response }}'
1919

2020
- block:
@@ -37,13 +37,13 @@
3737
profile:
3838
admin_state: true # choose from [true, false]
3939
mode: trunk # choose from [trunk, access, l3, monitor]
40-
members: # member interfaces
40+
members: # member interfaces
4141
- "{{ ansible_eth_intf13 }}"
4242
pc_mode: 'on' # choose from ['on', 'active', 'passive']
4343
bpdu_guard: true # choose from [true, false, no]
4444
port_type_fast: true # choose from [true, false]
4545
mtu: jumbo # choose from [default, jumbo]
46-
allowed_vlans: none # choose from [none, all, vlan range]
46+
allowed_vlans: none # choose from [none, all, vlan range]
4747
cmds: # Freeform config
4848
- no shutdown
4949
description: "port channel acting as trunk"
@@ -56,13 +56,13 @@
5656
profile:
5757
admin_state: true # choose from [true, false]
5858
mode: access # choose from [trunk, access, l3, monitor]
59-
members: # member interfaces
59+
members: # member interfaces
6060
- "{{ ansible_eth_intf14 }}"
6161
pc_mode: 'on' # choose from ['on', 'active', 'passive']
6262
bpdu_guard: true # choose from [true, false, no]
6363
port_type_fast: true # choose from [true, false]
6464
mtu: default # choose from [default, jumbo]
65-
access_vlan: 301 #
65+
access_vlan: 301 #
6666
cmds: # Freeform config
6767
- no shutdown
6868
description: "port channel acting as access"
@@ -75,11 +75,11 @@
7575
profile:
7676
admin_state: true # choose from [true, false]
7777
mode: l3 # choose from [trunk, access, l3, monitor]
78-
members: # member interfaces
78+
members: # member interfaces
7979
- "{{ ansible_eth_intf15 }}"
8080
pc_mode: 'on' # choose from ['on', 'active', 'passive']
8181
mtu: 9216 # choose between [min=576, max=9216]
82-
int_vrf: "" # interface VRF
82+
int_vrf: "" # interface VRF
8383
ipv4_addr: 192.168.20.1 # ipv4 address for the interface
8484
ipv4_mask_len: 24 # choose between [min:1, max:31]
8585
route_tag: "" # Route Tag
@@ -107,7 +107,7 @@
107107

108108
- assert:
109109
that:
110-
- 'item["RETURN_CODE"] == 200'
110+
- 'item["RETURN_CODE"] == 200'
111111
loop: '{{ result.response }}'
112112

113113
- name: Create port channel interfaces - Idempotence
@@ -125,7 +125,69 @@
125125

126126
- assert:
127127
that:
128-
- 'item["RETURN_CODE"] == 200'
128+
- 'item["RETURN_CODE"] == 200'
129+
loop: '{{ result.response }}'
130+
131+
##############################################
132+
## MERGE ##
133+
##############################################
134+
135+
- name: Create port channel interfaces with vlan ranges
136+
cisco.dcnm.dcnm_interface: &pc_merge2
137+
check_deploy: True
138+
fabric: "{{ ansible_it_fabric }}"
139+
state: merged # only choose form [merged, replaced, deleted, overridden, query]
140+
config:
141+
- name: po400 # should be of the form po<port-id>
142+
type: pc # choose from this list [pc, vpc, sub_int, lo, eth, svi]
143+
switch:
144+
- "{{ ansible_switch1 }}" # provide the switch information where the config is to be deployed
145+
deploy: true # choose from [true, false]
146+
profile:
147+
admin_state: true # choose from [true, false]
148+
mode: trunk # choose from [trunk, access, l3, monitor]
149+
members: # member interfaces
150+
- "{{ ansible_eth_intf21 }}"
151+
pc_mode: 'on' # choose from ['on', 'active', 'passive']
152+
bpdu_guard: true # choose from [true, false, no]
153+
port_type_fast: true # choose from [true, false]
154+
mtu: jumbo # choose from [default, jumbo]
155+
allowed_vlans: 10,20,30-40 # choose from [none, all, vlan range]
156+
cmds: # Freeform config
157+
- no shutdown
158+
description: "port channel acting as trunk"
159+
register: result
160+
161+
- assert:
162+
that:
163+
- 'result.changed == true'
164+
- '(result["diff"][0]["merged"] | length) == 1'
165+
- '(result["diff"][0]["deleted"] | length) == 0'
166+
- '(result["diff"][0]["replaced"] | length) == 0'
167+
- '(result["diff"][0]["overridden"] | length) == 0'
168+
- '(result["diff"][0]["deploy"] | length) == 1'
169+
170+
- assert:
171+
that:
172+
- 'item["RETURN_CODE"] == 200'
173+
loop: '{{ result.response }}'
174+
175+
- name: Create port channel interfaces with vlan ranges - Idempotence
176+
cisco.dcnm.dcnm_interface: *pc_merge2
177+
register: result
178+
179+
- assert:
180+
that:
181+
- 'result.changed == false'
182+
- '(result["diff"][0]["merged"] | length) == 0'
183+
- '(result["diff"][0]["deleted"] | length) == 0'
184+
- '(result["diff"][0]["replaced"] | length) == 0'
185+
- '(result["diff"][0]["overridden"] | length) == 0'
186+
- '(result["diff"][0]["deploy"] | length) == 0'
187+
188+
- assert:
189+
that:
190+
- 'item["RETURN_CODE"] == 200'
129191
loop: '{{ result.response }}'
130192

131193
##############################################
@@ -137,13 +199,13 @@
137199
- name: Put fabric to default state
138200
cisco.dcnm.dcnm_interface:
139201
check_deploy: True
140-
fabric: "{{ ansible_it_fabric }}"
202+
fabric: "{{ ansible_it_fabric }}"
141203
state: overridden # only choose form [merged, replaced, deleted, overridden, query]
142204
register: result
143205
when: IT_CONTEXT is not defined
144206

145207
- assert:
146208
that:
147-
- 'item["RETURN_CODE"] == 200'
209+
- 'item["RETURN_CODE"] == 200'
148210
loop: '{{ result.response }}'
149211
when: IT_CONTEXT is not defined

tests/unit/modules/dcnm/fixtures/dcnm_intf_pc_configs.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,36 @@
297297
"deploy": "True"
298298
}],
299299

300+
"pc_merged_vlan_range_config" : [
301+
{
302+
"switch": [
303+
"192.168.1.108"
304+
],
305+
"profile": {
306+
"description": "port channel acting as trunk",
307+
"bpdu_guard": "True",
308+
"sno": "SAL1819SAN8",
309+
"mtu": "jumbo",
310+
"pc_mode": "on",
311+
"mode": "trunk",
312+
"members": [
313+
"e1/9"
314+
],
315+
"port_type_fast": "True",
316+
"policy": "int_port_channel_trunk_host_11_1",
317+
"admin_state": "True",
318+
"allowed_vlans": "20,30,40,50-60,70,90-100",
319+
"cmds": [
320+
"no shutdown"
321+
],
322+
"ifname": "Port-channel300",
323+
"fabric": "test_fabric"
324+
},
325+
"type": "pc",
326+
"name": "po300",
327+
"deploy": "True"
328+
}],
329+
300330
"pc_deleted_config_deploy" : [
301331
{
302332
"switch": [

tests/unit/modules/dcnm/test_dcnm_intf.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,24 @@ def load_pc_fixtures(self):
964964
self.playbook_mock_succ_resp,
965965
self.playbook_mock_succ_resp,
966966
]
967+
968+
if "_pc_merged_vlan_range_new" in self._testMethodName:
969+
# No I/F exists case
970+
playbook_pc_intf1 = []
971+
playbook_have_all_data = self.have_all_payloads_data.get(
972+
"payloads"
973+
)
974+
975+
self.run_dcnm_send.side_effect = [
976+
self.mock_monitor_false_resp,
977+
self.playbook_mock_vpc_resp,
978+
playbook_pc_intf1,
979+
playbook_have_all_data,
980+
playbook_have_all_data,
981+
self.playbook_mock_succ_resp,
982+
self.playbook_mock_succ_resp,
983+
]
984+
967985
if "_pc_merged_policy_change" in self._testMethodName:
968986
playbook_pc_intf1 = self.payloads_data.get(
969987
"pc_merged_trunk_payloads"
@@ -2369,6 +2387,45 @@ def test_dcnm_intf_pc_merged_new(self):
23692387
True,
23702388
)
23712389

2390+
def test_dcnm_intf_pc_merged_vlan_range_new(self):
2391+
2392+
# load the json from playbooks
2393+
self.config_data = loadPlaybookData("dcnm_intf_pc_configs")
2394+
self.payloads_data = loadPlaybookData("dcnm_intf_pc_payloads")
2395+
self.have_all_payloads_data = loadPlaybookData(
2396+
"dcnm_intf_have_all_payloads"
2397+
)
2398+
2399+
# load required config data
2400+
self.playbook_config = self.config_data.get("pc_merged_vlan_range_config")
2401+
self.playbook_mock_succ_resp = self.config_data.get("mock_succ_resp")
2402+
self.mock_ip_sn = self.config_data.get("mock_ip_sn")
2403+
self.mock_fab_inv = self.config_data.get("mock_fab_inv_data")
2404+
self.mock_monitor_true_resp = self.config_data.get("mock_monitor_true_resp")
2405+
self.mock_monitor_false_resp = self.config_data.get("mock_monitor_false_resp")
2406+
self.playbook_mock_vpc_resp = self.config_data.get("mock_vpc_resp")
2407+
2408+
set_module_args(
2409+
dict(
2410+
state="merged",
2411+
fabric="test_fabric",
2412+
config=self.playbook_config,
2413+
)
2414+
)
2415+
result = self.execute_module(changed=True, failed=False)
2416+
self.assertEqual(len(result["diff"][0]["merged"]), 1)
2417+
for d in result["diff"][0]["merged"]:
2418+
for intf in d["interfaces"]:
2419+
self.assertEqual(
2420+
(
2421+
intf["ifName"]
2422+
in [
2423+
"Port-channel300"
2424+
]
2425+
),
2426+
True,
2427+
)
2428+
23722429
def test_dcnm_intf_pc_merged_idempotent(self):
23732430

23742431
# load the json from playbooks

0 commit comments

Comments
 (0)