Skip to content

IOS-XR: exit/endif after elseif/else in route-policy patches #495

@kzaikov

Description

@kzaikov

Description

When generating patches for IOS-XR route-policy blocks that contain if/elseif/else structures, AsrFormatter.block_exit() adds spurious exit statements after elseif and else blocks, producing invalid patches.

Root Cause

In annet/vendors/tabparser.py, AsrFormatter.block_exit() only handles if...then (adds endif) and route-policy (adds end-policy). Lines starting with elseif or else fall through to super().block_exit() which
yields exit:


  def block_exit(self, context: Optional[FormatterContext]) -> str:
      current = context and context.row or ""
      if current.startswith(("prefix-set", "as-path-set", "community-set")):
          yield from block_wrapper("end-set")
      elif current.startswith("if") and current.endswith("then"):
          yield from block_wrapper("endif")
      elif current.startswith("route-policy"):
          yield from block_wrapper("end-policy")
      else:
          yield from super().block_exit(context)  # yields "exit"

Actual Behavior

Patch output includes spurious exit and endif between branches:

route-policy TEST-IN
   if destination in IPV4-DEFAULT or destination in IPV6-DEFAULT then
     drop
     endif           <-- wrong
   elseif destination in TEST-DENY then
     drop
     exit            <-- wrong
   elseif community in CUSTOMERS then
     set local-preference 110
     done
     exit            <-- wrong
   else
     set local-preference 110
     exit            <-- wrong
   end-policy

Expected Behavior

elseif and else are branches of the parent if block. They should not produce any exit command — the endif from the if block covers the entire conditional:

route-policy TEST-IN
  if destination in IPV4-DEFAULT or destination in IPV6-DEFAULT then
    drop
  elseif destination in TEST-DENY then
    drop
  elseif community in CUSTOMERS then
    set local-preference 110
    done
  else
    set local-preference 110
  endif
end-policy

Note: annet diff produces correct output. Only annet patch is affected.

Suggested Fix

def _patched_block_exit(self, context):
    from annet.vendors.tabparser import block_wrapper
    current = context and context.row or ""
    # if/elseif are part of a single conditional structure in route-policies.
    # Only "else" (the last branch) should close the structure with "endif".

    # if/elseif/else are branches of a single conditional in route-policies.
    # We defer "endif" until the conditional structure is fully closed.
    if current.startswith("if") and current.endswith("then"):
        self._pending_endif = True
        return
    if current.startswith("elseif"):
        # still inside the if-block, keep deferring
        return
    if current.startswith("else"):
        # last branch — emit endif now
        self._pending_endif = False
        yield from block_wrapper("endif")
        return

    # If we were inside an if-block with no else (e.g. just if or if/elseif),
    # emit the deferred endif before the parent block's exit.
    if getattr(self, "_pending_endif", False):
        self._pending_endif = False
        yield from block_wrapper("endif")
    
    yield from _original_block_exit(self, context)

Environment

  • Platform: IOS-XR (NCS57 series)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions