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:
# 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:
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):