]> git.puffer.fish Git - matthieu/frr.git/commitdiff
tools: significantly simplify frr-reload context processing
authorIgor Ryzhov <iryzhov@nfware.com>
Mon, 9 Aug 2021 20:38:21 +0000 (23:38 +0300)
committerIgor Ryzhov <iryzhov@nfware.com>
Mon, 23 Aug 2021 19:08:20 +0000 (22:08 +0300)
Currently, in frr-reload we:
- store a list of single-line context keywords which needs to be
  frequently updated,
- have a separate "if" clause for every node and subnode we have in FRR.

Instead, we can store the tree of all known FRR nodes. This tree needs
to be updated whenever we add a new node, which is not frequent. And,
most importantly, it allows us to write node-agnostic code and save more
than 250 LOC.

Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
tools/frr-reload.py

index a326ecc0f9a2c0ebc0d6457673f89b85530a50e0..7c6a83a51de4d85487864a0cbbcc141021e535d1 100755 (executable)
@@ -513,9 +513,6 @@ class Config(object):
         Parse the configuration and create contexts for each appropriate block
         """
 
-        current_context_lines = []
-        ctx_keys = []
-
         """
         The end of a context is flagged via the 'end' keyword:
 
@@ -574,43 +571,80 @@ end
         # 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 ": {},
+                "vrf-policy ": {},
+                "bmp ": {},
+                "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 = []
-        main_ctx_key = []
-        new_ctx = True
-
-        # the keywords that we know are single line contexts. bgp in this case
-        # is not the main router bgp block, but enabling multi-instance
-        oneline_ctx_keywords = (
-            "access-list ",
-            "agentx",
-            "allow-external-route-update",
-            "bgp ",
-            "debug ",
-            "domainname ",
-            "dump ",
-            "enable ",
-            "evpn mh",
-            "frr ",
-            "fpm ",
-            "hostname ",
-            "ip ",
-            "ipv6 ",
-            "log ",
-            "mac access-list ",
-            "mpls lsp",
-            "mpls label",
-            "no ",
-            "password ",
-            "pbr ",
-            "ptm-enable",
-            "router-id ",
-            "service ",
-            "table ",
-            "username ",
-            "vni ",
-            "vrrp autoconfigure",
-            "zebra "
-        )
+        # stack of context keywords
+        cur_ctx_keywords = [ctx_keywords]
+        # list of stored commands
+        cur_ctx_lines = []
 
         for line in self.lines:
 
@@ -620,357 +654,77 @@ end
             if line.startswith("!") or line.startswith("#"):
                 continue
 
-            if (
-                len(ctx_keys) == 2
-                and ctx_keys[0].startswith("bfd")
-                and ctx_keys[1].startswith("profile ")
-                and line == "end"
-            ):
-                log.debug("LINE %-50s: popping from sub context, %-50s", line, ctx_keys)
-
-                if main_ctx_key:
-                    self.save_contexts(ctx_keys, current_context_lines)
-                    ctx_keys = copy.deepcopy(main_ctx_key)
-                    current_context_lines = []
+            if line.startswith("exit"):
+                # ignore on top level
+                if len(ctx_keys) == 0:
                     continue
 
-            # one line contexts
-            # there is one exception though: ldpd accepts a 'router-id' clause
-            # as part of its 'mpls ldp' config context. If we are processing
-            # ldp configuration and encounter a router-id we should NOT switch
-            # to a new context
-            if (
-                new_ctx is True
-                and any(line.startswith(keyword) for keyword in oneline_ctx_keywords)
-                and not (
-                    ctx_keys
-                    and ctx_keys[0].startswith("mpls ldp")
-                    and line.startswith("router-id ")
-                )
-            ):
-                self.save_contexts(ctx_keys, current_context_lines)
-
-                # Start a new context
-                main_ctx_key = []
-                ctx_keys = [
-                    line,
-                ]
-                current_context_lines = []
-
-                log.debug("LINE %-50s: entering new context, %-50s", line, ctx_keys)
-                self.save_contexts(ctx_keys, current_context_lines)
-                new_ctx = True
-
-            elif line == "end":
-                self.save_contexts(ctx_keys, current_context_lines)
-                log.debug("LINE %-50s: exiting old context, %-50s", line, ctx_keys)
-
-                # Start a new context
-                new_ctx = True
-                main_ctx_key = []
-                ctx_keys = []
-                current_context_lines = []
-
-            elif line == "exit" and ctx_keys[0].startswith("rpki"):
-                self.save_contexts(ctx_keys, current_context_lines)
-                log.debug("LINE %-50s: exiting old context, %-50s", line, ctx_keys)
-
-                # Start a new context
-                new_ctx = True
-                main_ctx_key = []
-                ctx_keys = []
-                current_context_lines = []
-
-            elif line == "exit-vrf":
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines.append(line)
-                log.debug(
-                    "LINE %-50s: append to current_context_lines, %-50s", line, ctx_keys
-                )
+                # save current context
+                self.save_contexts(ctx_keys, cur_ctx_lines)
 
-                # Start a new context
-                new_ctx = True
-                main_ctx_key = []
-                ctx_keys = []
-                current_context_lines = []
+                # exit current context
+                log.debug("LINE %-50s: exit context %-50s", line, ctx_keys)
 
-            elif (
-                line == "exit"
-                and len(ctx_keys) > 1
-                and ctx_keys[0].startswith("segment-routing")
-            ):
-                self.save_contexts(ctx_keys, current_context_lines)
-
-                # Start a new context
-                ctx_keys = ctx_keys[:-1]
-                current_context_lines = []
-                log.debug(
-                    "LINE %-50s: popping segment routing sub-context to ctx%-50s",
-                    line,
-                    ctx_keys,
-                )
-
-            elif line in ["exit-address-family", "exit", "exit-vnc"]:
-                # if this exit is for address-family ipv4 unicast, ignore the pop
-                if main_ctx_key:
-                    self.save_contexts(ctx_keys, current_context_lines)
-
-                    # Start a new context
-                    ctx_keys = copy.deepcopy(main_ctx_key)
-                    current_context_lines = []
-                    log.debug(
-                        "LINE %-50s: popping from subcontext to ctx%-50s",
-                        line,
-                        ctx_keys,
-                    )
+                ctx_keys.pop()
+                cur_ctx_keywords.pop()
+                cur_ctx_lines = []
 
-            elif line in ["exit-vni", "exit-ldp-if"]:
-                if sub_main_ctx_key:
-                    self.save_contexts(ctx_keys, current_context_lines)
-
-                    # Start a new context
-                    ctx_keys = copy.deepcopy(sub_main_ctx_key)
-                    current_context_lines = []
-                    log.debug(
-                        "LINE %-50s: popping from sub-subcontext to ctx%-50s",
-                        line,
-                        ctx_keys,
-                    )
+                continue
 
-            elif new_ctx is True:
-                if not main_ctx_key:
-                    ctx_keys = [
-                        line,
-                    ]
-                else:
-                    ctx_keys = copy.deepcopy(main_ctx_key)
-                    main_ctx_key = []
+            if line.startswith("end"):
+                # exit all contexts
+                while len(ctx_keys) > 0:
+                    # save current context
+                    self.save_contexts(ctx_keys, cur_ctx_lines)
 
-                current_context_lines = []
-                new_ctx = False
-                log.debug("LINE %-50s: entering new context, %-50s", line, ctx_keys)
+                    # exit current context
+                    log.debug("LINE %-50s: exit context %-50s", line, ctx_keys)
 
-            elif (
-                line.startswith("address-family ")
-                or line.startswith("vnc defaults")
-                or line.startswith("vnc l2-group")
-                or line.startswith("vnc nve-group")
-                or line.startswith("peer")
-                or line.startswith("key ")
-                or line.startswith("member pseudowire")
-            ):
-                main_ctx_key = []
+                    ctx_keys.pop()
+                    cur_ctx_keywords.pop()
+                    cur_ctx_lines = []
 
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug("LINE %-50s: entering sub-context, append to ctx_keys", line)
+                continue
 
-                if line == "address-family ipv6" and not ctx_keys[0].startswith(
-                    "mpls ldp"
-                ):
-                    ctx_keys.append("address-family ipv6 unicast")
-                elif line == "address-family ipv4" and not ctx_keys[0].startswith(
-                    "mpls ldp"
-                ):
-                    ctx_keys.append("address-family ipv4 unicast")
-                elif line == "address-family evpn":
-                    ctx_keys.append("address-family l2vpn evpn")
-                else:
+            new_ctx = False
+
+            # check if the line is a context-entering keyword
+            for k, v in cur_ctx_keywords[-1].items():
+                if line.startswith(k):
+                    # candidate-path is a special case. It may be a node and
+                    # may be a single-line command. The distinguisher is the
+                    # word "dynamic" or "explicit" at the middle of the line.
+                    # It was perhaps not the best choice by the pathd authors
+                    # but we have what we have.
+                    if k == "candidate-path " and "explicit" in line:
+                        # this is a single-line command
+                        break
+
+                    # save current context
+                    self.save_contexts(ctx_keys, cur_ctx_lines)
+
+                    # enter new context
+                    new_ctx = True
                     ctx_keys.append(line)
+                    cur_ctx_keywords.append(v)
+                    cur_ctx_lines = []
 
-            elif (
-                line.startswith("vni ")
-                and len(ctx_keys) == 2
-                and ctx_keys[0].startswith("router bgp")
-                and ctx_keys[1] == "address-family l2vpn evpn"
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                sub_main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering sub-sub-context, append to ctx_keys", line
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("interface ")
-                and len(ctx_keys) == 2
-                and ctx_keys[0].startswith("mpls ldp")
-                and ctx_keys[1].startswith("address-family")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                sub_main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering sub-sub-context, append to ctx_keys", line
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("traffic-eng")
-                and len(ctx_keys) == 1
-                and ctx_keys[0].startswith("segment-routing")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                log.debug(
-                    "LINE %-50s: entering segment routing sub-context, append to ctx_keys",
-                    line,
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("segment-list ")
-                and len(ctx_keys) == 2
-                and ctx_keys[0].startswith("segment-routing")
-                and ctx_keys[1].startswith("traffic-eng")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                log.debug(
-                    "LINE %-50s: entering segment routing sub-context, append to ctx_keys",
-                    line,
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("policy ")
-                and len(ctx_keys) == 2
-                and ctx_keys[0].startswith("segment-routing")
-                and ctx_keys[1].startswith("traffic-eng")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                log.debug(
-                    "LINE %-50s: entering segment routing sub-context, append to ctx_keys",
-                    line,
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("candidate-path ")
-                and line.endswith(" dynamic")
-                and len(ctx_keys) == 3
-                and ctx_keys[0].startswith("segment-routing")
-                and ctx_keys[1].startswith("traffic-eng")
-                and ctx_keys[2].startswith("policy")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering candidate-path sub-context, append to ctx_keys",
-                    line,
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("pcep")
-                and len(ctx_keys) == 2
-                and ctx_keys[0].startswith("segment-routing")
-                and ctx_keys[1].startswith("traffic-eng")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering pcep sub-context, append to ctx_keys", line
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("pce-config ")
-                and len(ctx_keys) == 3
-                and ctx_keys[0].startswith("segment-routing")
-                and ctx_keys[1].startswith("traffic-eng")
-                and ctx_keys[2].startswith("pcep")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering pce-config sub-context, append to ctx_keys",
-                    line,
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("pce ")
-                and len(ctx_keys) == 3
-                and ctx_keys[0].startswith("segment-routing")
-                and ctx_keys[1].startswith("traffic-eng")
-                and ctx_keys[2].startswith("pcep")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering pce sub-context, append to ctx_keys", line
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("pcc")
-                and len(ctx_keys) == 3
-                and ctx_keys[0].startswith("segment-routing")
-                and ctx_keys[1].startswith("traffic-eng")
-                and ctx_keys[2].startswith("pcep")
-            ):
-
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering pcc sub-context, append to ctx_keys", line
-                )
-                ctx_keys.append(line)
-
-            elif (
-                line.startswith("profile ")
-                and len(ctx_keys) == 1
-                and ctx_keys[0].startswith("bfd")
-            ):
+                    log.debug("LINE %-50s: enter context %-50s", line, ctx_keys)
+                    break
 
-                # Save old context first
-                self.save_contexts(ctx_keys, current_context_lines)
-                current_context_lines = []
-                main_ctx_key = copy.deepcopy(ctx_keys)
-                log.debug(
-                    "LINE %-50s: entering BFD profile sub-context, append to ctx_keys",
-                    line,
-                )
-                ctx_keys.append(line)
+            if new_ctx:
+                continue
 
+            if len(ctx_keys) == 0:
+                log.debug("LINE %-50s: single-line context", line)
+                self.save_contexts([line], [])
             else:
-                # Continuing in an existing context, add non-commented lines to it
-                current_context_lines.append(line)
-                log.debug(
-                    "LINE %-50s: append to current_context_lines, %-50s", line, ctx_keys
-                )
+                log.debug("LINE %-50s: add to current context %-50s", line, ctx_keys)
+                cur_ctx_lines.append(line)
 
         # Save the context of the last one
-        self.save_contexts(ctx_keys, current_context_lines)
+        if len(ctx_keys) > 0:
+            self.save_contexts(ctx_keys, cur_ctx_lines)
 
 
 def lines_to_config(ctx_keys, line, delete):