diff --git a/plugins/module_utils/network/nxos/config/vrf_address_family/vrf_address_family.py b/plugins/module_utils/network/nxos/config/vrf_address_family/vrf_address_family.py index 11d273d6a..82accdcdf 100644 --- a/plugins/module_utils/network/nxos/config/vrf_address_family/vrf_address_family.py +++ b/plugins/module_utils/network/nxos/config/vrf_address_family/vrf_address_family.py @@ -83,7 +83,7 @@ def generate_commands(self): # if state is deleted, empty out wantd and set haved to wantd if self.state == "deleted": - haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + haved = self.filter_dict(haved, wantd) wantd = {} # remove superfluous config for overridden and deleted @@ -166,6 +166,17 @@ def _convert_to_dict(self, vrf_af_item: list, parser_item: str) -> dict: result[key] = item return result + def filter_dict(self, haved, wantd): + if isinstance(haved, dict) and isinstance(wantd, dict): + # Only keep keys that are in `wantd` and filter recursively + return {k: self.filter_dict(haved[k], wantd[k]) for k in haved if k in wantd} + elif isinstance(haved, list) and isinstance(wantd, list): + # Filter list elements if they are dictionaries, otherwise return only items that match + return [self.filter_dict(h_item, wantd[0]) for h_item in haved if isinstance(h_item, dict)] if wantd and isinstance(wantd[0], dict) else [item for item in haved if item in wantd] + else: + # For non-dict and non-list values, just return the value if it matches + return haved if haved == wantd else None + def _vrf_address_family_list_to_dict(self, vrf_af_list: list) -> dict: """Convert a list of vrf_address_family dictionaries to a dictionary. diff --git a/tests/unit/modules/network/nxos/test_nxos_vrf_address_family.py b/tests/unit/modules/network/nxos/test_nxos_vrf_address_family.py index ed1506ca7..25ab431c4 100644 --- a/tests/unit/modules/network/nxos/test_nxos_vrf_address_family.py +++ b/tests/unit/modules/network/nxos/test_nxos_vrf_address_family.py @@ -201,6 +201,7 @@ def test_vrf_af_merged(self): ], }, ], + state="merged", ), ) commands = [ @@ -220,3 +221,395 @@ def test_vrf_af_merged(self): ] result = self.execute_module(changed=True) self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_merged_idempotent(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target import 64512:200 + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + address-family ipv6 unicast + route-target import 554832:500 + maximum routes 1000 + route-target import 65512:200 + import map 22 + import vrf default map 44 advertise-vpn + import vrf advertise-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + "import": [ + { + "map": "22", + }, + { + "vrf": { + "advertise_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "advertise_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="merged", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_overridden(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target import 64512:200 + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + address-family ipv6 unicast + route-target import 554832:500 + maximum routes 1000 + route-target import 65512:200 + import map 22 + import vrf default map 44 advertise-vpn + import vrf advertise-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "export": [ + { + "map": "22", + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + }, + ], + }, + { + "name": "VRF2", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + "import": [ + { + "map": "22", + }, + { + "vrf": { + "advertise_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "advertise_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="overridden", + ), + ) + commands = [ + 'vrf context VRF1', + 'address-family ipv4 unicast', + 'no maximum routes 500 60 reinstall 80', + 'no route-target import 64512:200', + 'no export vrf default map 44 allow-vpn', + 'no export vrf allow-vpn', + 'address-family ipv6 unicast', + 'no route-target import 554832:500', + 'no import map 22', + 'no import vrf default map 44 advertise-vpn', + 'no import vrf advertise-vpn', + 'vrf context VRF2', + 'address-family ipv4 unicast', + 'maximum routes 500 60 reinstall 80', + 'route-target export 65512:200', + 'export map 22', + 'export vrf default map 44 allow-vpn', + 'export vrf allow-vpn', + 'address-family ipv6 unicast', + 'maximum routes 1000', + 'route-target import 65512:200', + 'import map 22', + 'import vrf default map 44 advertise-vpn', + 'import vrf advertise-vpn' + ] + result = self.execute_module(changed=True) + self.assertEqual(sorted(result["commands"]), sorted(commands)) + + def test_vrf_af_overridden_idemp(self): + """Test merged.""" + self.execute_show_command.return_value = dedent( + """\ + vrf context VRF1 + address-family ipv4 unicast + route-target export 65512:200 + export map 22 + address-family ipv6 unicast + maximum routes 1000 + route-target import 65512:200 + vrf context VRF2 + address-family ipv4 unicast + maximum routes 500 60 reinstall 80 + route-target export 65512:200 + export map 22 + export vrf default map 44 allow-vpn + export vrf allow-vpn + address-family ipv6 unicast + maximum routes 1000 + route-target import 65512:200 + import map 22 + import vrf default map 44 advertise-vpn + import vrf advertise-vpn + """, + ) + + set_module_args( + dict( + config=[ + { + "name": "VRF1", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "export": [ + { + "map": "22", + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + }, + ], + }, + { + "name": "VRF2", + "address_families": [ + { + "afi": "ipv4", + "safi": "unicast", + "route_target": [ + { + "export": "65512:200", + }, + ], + "maximum": { + "max_routes": 500, + "max_route_options": { + "threshold": { + "threshold_value": 60, + "reinstall_threshold": 80, + }, + }, + }, + "export": [ + { + "map": "22", + }, + { + "vrf": { + "allow_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "allow_vpn": True, + }, + }, + ], + }, + { + "afi": "ipv6", + "safi": "unicast", + "maximum": { + "max_routes": 1000, + }, + "route_target": [ + { + "import": "65512:200", + }, + ], + "import": [ + { + "map": "22", + }, + { + "vrf": { + "advertise_vpn": True, + "map_import": "44", + }, + }, + { + "vrf": { + "advertise_vpn": True, + }, + }, + ], + }, + ], + }, + ], + state="overridden", + ), + ) + commands = [] + result = self.execute_module(changed=False) + self.assertEqual(sorted(result["commands"]), sorted(commands))