]> git.proxmox.com Git - mirror_frr.git/blobdiff - tools/frr-reload.py
Merge pull request #8870 from anlancs/master-fix-reload-service
[mirror_frr.git] / tools / frr-reload.py
index a617c0a9c5e1168ff4a1c263b8f94c2c0a2d853c..a326ecc0f9a2c0ebc0d6457673f89b85530a50e0 100755 (executable)
@@ -423,17 +423,6 @@ class Config(object):
                     re_lege.group(2),
                     re_lege.group(4),
                 )
-            re_lege = re.search(r"(.*)ge\s+(\d+)\s+le\s+(\d+)(.*)", legestr)
-
-            if re_lege and (
-                (re_key_rt.group(1) == "ip" and re_lege.group(3) == "32")
-                or (re_key_rt.group(1) == "ipv6" and re_lege.group(3) == "128")
-            ):
-                legestr = "%sge %s%s" % (
-                    re_lege.group(1),
-                    re_lege.group(2),
-                    re_lege.group(4),
-                )
 
             key[0] = "%s prefix-list%s%s %s%s" % (
                 re_key_rt.group(1),
@@ -600,24 +589,27 @@ end
             "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 ",
-            "zebra ",
+            "vni ",
             "vrrp autoconfigure",
-            "evpn mh",
+            "zebra "
         )
 
         for line in self.lines:
@@ -1106,6 +1098,175 @@ def check_for_exit_vrf(lines_to_add, lines_to_del):
     return (lines_to_add, lines_to_del)
 
 
+"""
+This method handles deletion of bgp peer group config.
+The objective is to delete config lines related to peers
+associated with the peer-group and move the peer-group
+config line to the end of the lines_to_del list.
+"""
+
+
+def delete_move_lines(lines_to_add, lines_to_del):
+
+    del_dict = dict()
+    # Stores the lines to move to the end of the pending list.
+    lines_to_del_to_del = []
+    # Stores the lines to move to end of the pending list.
+    lines_to_del_to_app = []
+    found_pg_del_cmd = False
+
+    """
+    When "neighbor <pg_name> peer-group" under a bgp instance is removed,
+    it also deletes the associated peer config. Any config line below no form of
+    peer-group related to a peer are errored out as the peer no longer exists.
+    To cleanup peer-group and associated peer(s) configs:
+    - Remove all the peers config lines from the pending list (lines_to_del list).
+    - Move peer-group deletion line to the end of the pending list, to allow
+    removal of any of the peer-group specific configs.
+
+    Create a dictionary of config context (i.e. router bgp vrf x).
+    Under each context node, create a dictionary of a peer-group name.
+    Append a peer associated to the peer-group into a list under a peer-group node.
+    Remove all of the peer associated config lines from the pending list.
+    Append peer-group deletion line to end of the pending list.
+
+    Example:
+      neighbor underlay peer-group
+      neighbor underlay remote-as external
+      neighbor underlay advertisement-interval 0
+      neighbor underlay timers 3 9
+      neighbor underlay timers connect 10
+      neighbor swp1 interface peer-group underlay
+      neighbor swp1 advertisement-interval 0
+      neighbor swp1 timers 3 9
+      neighbor swp1 timers connect 10
+      neighbor swp2 interface peer-group underlay
+      neighbor swp2 advertisement-interval 0
+      neighbor swp2 timers 3 9
+      neighbor swp2 timers connect 10
+      neighbor swp3 interface peer-group underlay
+      neighbor uplink1 interface remote-as internal
+      neighbor uplink1 advertisement-interval 0
+      neighbor uplink1 timers 3 9
+      neighbor uplink1 timers connect 10
+
+    New order:
+      "router bgp 200  no bgp bestpath as-path multipath-relax"
+      "router bgp 200  no neighbor underlay advertisement-interval 0"
+      "router bgp 200  no neighbor underlay timers 3 9"
+      "router bgp 200  no neighbor underlay timers connect 10"
+      "router bgp 200  no neighbor uplink1 advertisement-interval 0"
+      "router bgp 200  no neighbor uplink1 timers 3 9"
+      "router bgp 200  no neighbor uplink1 timers connect 10"
+      "router bgp 200  no neighbor underlay remote-as external"
+      "router bgp 200  no neighbor uplink1 interface remote-as internal"
+      "router bgp 200  no neighbor underlay peer-group"
+
+    """
+
+    for (ctx_keys, line) in lines_to_del:
+        if (
+            ctx_keys[0].startswith("router bgp")
+            and line
+            and line.startswith("neighbor ")
+        ):
+            """
+            When 'neighbor <peer> remote-as <>' is removed it deletes the peer,
+            there might be a peer associated config which also needs to be removed
+            prior to peer.
+            Append the 'neighbor <peer> remote-as <>' to the lines_to_del.
+            Example:
+
+             neighbor uplink1 interface remote-as internal
+             neighbor uplink1 advertisement-interval 0
+             neighbor uplink1 timers 3 9
+             neighbor uplink1 timers connect 10
+
+             Move to end:
+             neighbor uplink1 advertisement-interval 0
+             neighbor uplink1 timers 3 9
+             neighbor uplink1 timers connect 10
+             ...
+
+             neighbor uplink1 interface remote-as internal
+
+            """
+            # 'no neighbor peer [interface] remote-as <>'
+            nb_remoteas = "neighbor (\S+) .*remote-as (\S+)"
+            re_nb_remoteas = re.search(nb_remoteas, line)
+            if re_nb_remoteas:
+                lines_to_del_to_app.append((ctx_keys, line))
+
+            """
+            {'router bgp 65001': {'PG': [], 'PG1': []},
+            'router bgp 65001 vrf vrf1': {'PG': [], 'PG1': []}}
+            """
+            if ctx_keys[0] not in del_dict:
+                del_dict[ctx_keys[0]] = dict()
+            # find 'no neighbor <pg_name> peer-group'
+            re_pg = re.match("neighbor (\S+) peer-group$", line)
+            if re_pg and re_pg.group(1) not in del_dict[ctx_keys[0]]:
+                del_dict[ctx_keys[0]][re_pg.group(1)] = list()
+
+    for (ctx_keys, line) in lines_to_del_to_app:
+        lines_to_del.remove((ctx_keys, line))
+        lines_to_del.append((ctx_keys, line))
+
+    if found_pg_del_cmd == False:
+        return (lines_to_add, lines_to_del)
+
+    """
+    {'router bgp 65001': {'PG': ['10.1.1.2'], 'PG1': ['10.1.1.21']},
+     'router bgp 65001 vrf vrf1': {'PG': ['10.1.1.2'], 'PG1': ['10.1.1.21']}}
+    """
+    for (ctx_keys, line) in lines_to_del:
+        if (
+            ctx_keys[0].startswith("router bgp")
+            and line
+            and line.startswith("neighbor ")
+        ):
+            if ctx_keys[0] in del_dict:
+                for pg_key in del_dict[ctx_keys[0]]:
+                    # 'neighbor <peer> [interface] peer-group <pg_name>'
+                    nb_pg = "neighbor (\S+) .*peer-group %s$" % pg_key
+                    re_nbr_pg = re.search(nb_pg, line)
+                    if (
+                        re_nbr_pg
+                        and re_nbr_pg.group(1) not in del_dict[ctx_keys[0]][pg_key]
+                    ):
+                        del_dict[ctx_keys[0]][pg_key].append(re_nbr_pg.group(1))
+
+    lines_to_del_to_app = []
+    for (ctx_keys, line) in lines_to_del:
+        if (
+            ctx_keys[0].startswith("router bgp")
+            and line
+            and line.startswith("neighbor ")
+        ):
+            if ctx_keys[0] in del_dict:
+                for pg in del_dict[ctx_keys[0]]:
+                    for nbr in del_dict[ctx_keys[0]][pg]:
+                        nb_exp = "neighbor %s .*" % nbr
+                        re_nb = re.search(nb_exp, line)
+                        # add peer configs to delete list.
+                        if re_nb and line not in lines_to_del_to_del:
+                            lines_to_del_to_del.append((ctx_keys, line))
+
+                    pg_exp = "neighbor %s peer-group$" % pg
+                    re_pg = re.match(pg_exp, line)
+                    if re_pg:
+                        lines_to_del_to_app.append((ctx_keys, line))
+
+    for (ctx_keys, line) in lines_to_del_to_del:
+        lines_to_del.remove((ctx_keys, line))
+
+    for (ctx_keys, line) in lines_to_del_to_app:
+        lines_to_del.remove((ctx_keys, line))
+        lines_to_del.append((ctx_keys, line))
+
+    return (lines_to_add, lines_to_del)
+
+
 def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
 
     # Quite possibly the most confusing (while accurate) variable names in history
@@ -1230,6 +1391,53 @@ def ignore_delete_re_add_lines(lines_to_add, lines_to_del):
                                 if found_add_bfd_nbr:
                                     lines_to_del_to_del.append((ctx_keys, line))
 
+                """
+                Neighbor changes of route-maps need to be accounted for in that we
+                do not want to do a `no route-map...` `route-map ....` when changing
+                a route-map.  This is bad mojo as that we will send/receive
+                data we don't want.
+                Additionally we need to ensure that if we have different afi/safi
+                variants that they actually match and if we are going from a very
+                old style command such that the neighbor command is under the
+                `router bgp ..` node that we need to handle that appropriately
+                """
+                re_nbr_rm = re.search("neighbor(.*)route-map(.*)(in|out)$", line)
+                if re_nbr_rm:
+                    adjust_for_bgp_node = 0
+                    neighbor_name = re_nbr_rm.group(1)
+                    rm_name_del = re_nbr_rm.group(2)
+                    dir = re_nbr_rm.group(3)
+                    search = "neighbor%sroute-map(.*)%s" % (neighbor_name, dir)
+                    save_line = "EMPTY"
+                    for (ctx_keys_al, add_line) in lines_to_add:
+                        if ctx_keys_al[0].startswith("router bgp"):
+                            if add_line:
+                                rm_match = re.search(search, add_line)
+                            if rm_match:
+                                rm_name_add = rm_match.group(1)
+                                if rm_name_add == rm_name_del:
+                                    continue
+                                if len(ctx_keys_al) == 1:
+                                    save_line = line
+                                    adjust_for_bgp_node = 1
+                                else:
+                                    if (
+                                        len(ctx_keys) > 1
+                                        and len(ctx_keys_al) > 1
+                                        and ctx_keys[1] == ctx_keys_al[1]
+                                    ):
+                                        lines_to_del_to_del.append((ctx_keys_al, line))
+
+                    if adjust_for_bgp_node == 1:
+                        for (ctx_keys_dl, dl_line) in lines_to_del:
+                            if (
+                                ctx_keys_dl[0].startswith("router bgp")
+                                and len(ctx_keys_dl) > 1
+                                and ctx_keys_dl[1] == "address-family ipv4 unicast"
+                            ):
+                                if save_line == dl_line:
+                                    lines_to_del_to_del.append((ctx_keys_dl, save_line))
+
                 """
                 We changed how we display the neighbor interface command. Older
                 versions of frr would display the following:
@@ -1735,6 +1943,7 @@ def compare_context_objects(newconf, running):
     (lines_to_add, lines_to_del) = ignore_delete_re_add_lines(
         lines_to_add, lines_to_del
     )
+    (lines_to_add, lines_to_del) = delete_move_lines(lines_to_add, lines_to_del)
     (lines_to_add, lines_to_del) = ignore_unconfigurable_lines(
         lines_to_add, lines_to_del
     )
@@ -1804,6 +2013,11 @@ if __name__ == "__main__":
     parser.add_argument(
         "--daemon", help="daemon for which want to replace the config", default=""
     )
+    parser.add_argument(
+        "--test-reset",
+        action="store_true",
+        help="Used by topotest to not delete debug or log file commands",
+    )
 
     args = parser.parse_args()
 
@@ -1917,7 +2131,7 @@ if __name__ == "__main__":
                     service_integrated_vtysh_config = False
                     break
 
-    if not service_integrated_vtysh_config and not args.daemon:
+    if not args.test and not service_integrated_vtysh_config and not args.daemon:
         log.error(
             "'service integrated-vtysh-config' is not configured, this is required for 'service frr reload'"
         )
@@ -1945,35 +2159,56 @@ if __name__ == "__main__":
             running.load_from_show_running(args.daemon)
 
         (lines_to_add, lines_to_del) = compare_context_objects(newconf, running)
-        lines_to_configure = []
 
         if lines_to_del:
-            print("\nLines To Delete")
-            print("===============")
+            if not args.test_reset:
+                print("\nLines To Delete")
+                print("===============")
 
             for (ctx_keys, line) in lines_to_del:
 
                 if line == "!":
                     continue
 
-                cmd = "\n".join(lines_to_config(ctx_keys, line, True))
-                lines_to_configure.append(cmd)
+                nolines = lines_to_config(ctx_keys, line, True)
+
+                if args.test_reset:
+                    # For topotests the original code stripped the lines, and ommitted blank lines
+                    # after, do that here
+                    nolines = [x.strip() for x in nolines]
+                    # For topotests leave these lines in (don't delete them)
+                    # [chopps: why is "log file" more special than other "log" commands?]
+                    nolines = [x for x in nolines if "debug" not in x and "log file" not in x]
+                    if not nolines:
+                        continue
+
+                cmd = "\n".join(nolines)
                 print(cmd)
 
         if lines_to_add:
-            print("\nLines To Add")
-            print("============")
+            if not args.test_reset:
+                print("\nLines To Add")
+                print("============")
 
             for (ctx_keys, line) in lines_to_add:
 
                 if line == "!":
                     continue
 
-                cmd = "\n".join(lines_to_config(ctx_keys, line, False))
-                lines_to_configure.append(cmd)
+                lines = lines_to_config(ctx_keys, line, False)
+
+                if args.test_reset:
+                    # For topotests the original code stripped the lines, and ommitted blank lines
+                    # after, do that here
+                    lines = [x.strip() for x in lines if x.strip()]
+                    if not lines:
+                        continue
+
+                cmd = "\n".join(lines)
                 print(cmd)
 
     elif args.reload:
+        lines_to_configure = []
 
         # We will not be able to do anything, go ahead and exit(1)
         if not vtysh.is_config_available():