]> git.puffer.fish Git - matthieu/frr.git/commitdiff
tools: always append "exit" in frr-reload.py
authorIgor Ryzhov <iryzhov@nfware.com>
Sun, 28 Nov 2021 21:36:35 +0000 (00:36 +0300)
committerChristian Breunig <christian@breunig.cc>
Wed, 7 Feb 2024 19:39:42 +0000 (20:39 +0100)
When reloading the following config:
```
router ospf6
 area 0 range 2001:db8::/32 advertise
exit
!
interface eth0
 ipv6 ospf6 area 0
exit
```
frr-reload.py doesn't execute "exit" commands. Because of that, it tries
to execute "interface eth0" inside the "router ospf6" context and fails.

To always execute all commands from the correct context, frr-reload.py
should properly exit from every entered node.

Fixes #10132.

Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
(cherry picked from commit 792ada473893b2d16e0eccde974b71ddc65d15eb)

tools/frr-reload.py

index b7b300adb3c22eba7c1038bc5e3701872a173e3c..c07469b96f5af89f23f4a803d309bfd656760483 100755 (executable)
@@ -219,6 +219,53 @@ def get_normalized_mac_ip_line(line):
     return line
 
 
+# This dictionary contains a tree of all commands that we know start a
+# new multi-line context. All other commands are treated either as
+# commands inside a multi-line context or as single-line contexts. This
+# dictionary should be updated whenever a new node is added to FRR.
+ctx_keywords = {
+    "router bgp ": {
+        "address-family ": {
+            "vni ": {},
+        },
+        "vnc defaults": {},
+        "vnc nve-group ": {},
+        "vnc l2-group ": {},
+        "vrf-policy ": {},
+        "bmp targets ": {},
+        "segment-routing srv6": {},
+    },
+    "router rip": {},
+    "router ripng": {},
+    "router isis ": {},
+    "router openfabric ": {},
+    "router ospf": {},
+    "router ospf6": {},
+    "router eigrp ": {},
+    "router babel": {},
+    "mpls ldp": {"address-family ": {"interface ": {}}},
+    "l2vpn ": {"member pseudowire ": {}},
+    "key chain ": {"key ": {}},
+    "vrf ": {},
+    "interface ": {"link-params": {}},
+    "pseudowire ": {},
+    "segment-routing": {
+        "traffic-eng": {
+            "segment-list ": {},
+            "policy ": {"candidate-path ": {}},
+            "pcep": {"pcc": {}, "pce ": {}, "pce-config ": {}},
+        },
+        "srv6": {"locators": {"locator ": {}}},
+    },
+    "nexthop-group ": {},
+    "route-map ": {},
+    "pbr-map ": {},
+    "rpki": {},
+    "bfd": {"peer ": {}, "profile ": {}},
+    "line vty": {},
+}
+
+
 class Config(object):
     """
     A frr configuration is stored in a Config object. A Config object
@@ -490,54 +537,7 @@ class Config(object):
         key of the context. So "router bgp 10" is the key for the non-address
         family part of bgp, "router bgp 10, address-family ipv6 unicast" is
         the key for the subcontext and so on.
-
-        This dictionary contains a tree of all commands that we know start a
-        new multi-line context. All other commands are treated either as
-        commands inside a multi-line context or as single-line contexts. This
-        dictionary should be updated whenever a new node is added to FRR.
         """
-        ctx_keywords = {
-            "router bgp ": {
-                "address-family ": {
-                    "vni ": {},
-                },
-                "vnc defaults": {},
-                "vnc nve-group ": {},
-                "vnc l2-group ": {},
-                "vrf-policy ": {},
-                "bmp targets ": {},
-                "segment-routing srv6": {},
-            },
-            "router rip": {},
-            "router ripng": {},
-            "router isis ": {},
-            "router openfabric ": {},
-            "router ospf": {},
-            "router ospf6": {},
-            "router eigrp ": {},
-            "router babel": {},
-            "mpls ldp": {"address-family ": {"interface ": {}}},
-            "l2vpn ": {"member pseudowire ": {}},
-            "key chain ": {"key ": {}},
-            "vrf ": {},
-            "interface ": {"link-params": {}},
-            "pseudowire ": {},
-            "segment-routing": {
-                "traffic-eng": {
-                    "segment-list ": {},
-                    "policy ": {"candidate-path ": {}},
-                    "pcep": {"pcc": {}, "pce ": {}, "pce-config ": {}},
-                },
-                "srv6": {"locators": {"locator ": {}}},
-            },
-            "nexthop-group ": {},
-            "route-map ": {},
-            "pbr-map ": {},
-            "rpki": {},
-            "bfd": {"peer ": {}, "profile ": {}},
-            "line vty": {},
-        }
-
         # stack of context keys
         ctx_keys = []
         # stack of context keywords
@@ -632,6 +632,20 @@ def lines_to_config(ctx_keys, line, delete):
     """
     cmd = []
 
+    # If there's no `line` and `ctx_keys` length is 1, then it may be a single-line command.
+    # In this case, we should treat it as a single command in an empty context.
+    if len(ctx_keys) == 1 and not line:
+        single = True
+
+        for k, v in ctx_keywords.items():
+            if ctx_keys[0].startswith(k):
+                single = False
+                break
+
+        if single:
+            line = ctx_keys[0]
+            ctx_keys = []
+
     if line:
         for (i, ctx_key) in enumerate(ctx_keys):
             cmd.append(" " * i + ctx_key)
@@ -652,6 +666,9 @@ def lines_to_config(ctx_keys, line, delete):
         else:
             cmd.append(indent + line)
 
+        for i in reversed(range(len(ctx_keys))):
+            cmd.append(" " * i + "exit")
+
     # If line is None then we are typically deleting an entire
     # context ('no router ospf' for example)
     else:
@@ -666,6 +683,10 @@ def lines_to_config(ctx_keys, line, delete):
                 cmd.append("%sno %s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1]))
         else:
             cmd.append("%s%s" % (" " * (len(ctx_keys) - 1), ctx_keys[-1]))
+            cmd.append("%sexit" % (" " * (len(ctx_keys) - 1)))
+
+        for i in reversed(range(len(ctx_keys) - 1)):
+            cmd.append(" " * i + "exit")
 
     return cmd
 
@@ -715,38 +736,6 @@ def line_exist(lines, target_ctx_keys, target_line, exact_match=True):
     return False
 
 
-def check_for_exit_vrf(lines_to_add, lines_to_del):
-
-    # exit-vrf is a bit tricky.  If the new config is missing it but we
-    # have configs under a vrf, we need to add it at the end to do the
-    # right context changes.  If exit-vrf exists in both the running and
-    # new config, we cannot delete it or it will break context changes.
-    add_exit_vrf = False
-    index = 0
-
-    for (ctx_keys, line) in lines_to_add:
-        if add_exit_vrf == True:
-            if ctx_keys[0] != prior_ctx_key:
-                insert_key = ((prior_ctx_key),)
-                lines_to_add.insert(index, ((insert_key, "exit-vrf")))
-                add_exit_vrf = False
-
-        if ctx_keys[0].startswith("vrf") and line:
-            if line != "exit-vrf":
-                add_exit_vrf = True
-                prior_ctx_key = ctx_keys[0]
-            else:
-                add_exit_vrf = False
-        index += 1
-
-    for (ctx_keys, line) in lines_to_del:
-        if line == "exit-vrf":
-            if line_exist(lines_to_add, ctx_keys, line):
-                lines_to_del.remove((ctx_keys, line))
-
-    return (lines_to_add, lines_to_del)
-
-
 def bgp_delete_inst_move_line(lines_to_del):
     # Deletion of bgp default inst followed by
     # bgp vrf inst leads to issue of default
@@ -1779,7 +1768,6 @@ def compare_context_objects(newconf, running):
     if len(candidates_to_add) > 0:
         lines_to_add.extend(candidates_to_add)
 
-    (lines_to_add, lines_to_del) = check_for_exit_vrf(lines_to_add, lines_to_del)
     (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(
         lines_to_add, lines_to_del
     )