Skip to content

Latest commit

 

History

History
218 lines (188 loc) · 9.34 KB

File metadata and controls

218 lines (188 loc) · 9.34 KB

Future Config

The Future Config feature, introduced in version 2.2.0, attempts to predict the state of the running configuration after a change is applied.

This feature is useful in scenarios where you need to determine the anticipated configuration state following a change, such as:

  • Verifying that a configuration change was successfully applied to a device
    • For example, checking if the post-change configuration matches the predicted future configuration
  • Generating a future-state configuration that can be analyzed by tools like Batfish to assess the potential impact of a change
  • Building rollback configurations: once the future configuration state is known, a rollback configuration can be generated by simply creating the remediation in reverse (rollback = future.config_to_get_to(running)).
    • When building rollbacks for a series of configuration changes, you can use the future configuration from each change as input for the subsequent change. For example, use the future configuration after Change 1 as the input for determining the future configuration after Change 2, and so on.

merge() versus future()

HConfig.merge() and HConfig.future() both combine configuration data, but they serve different purposes:

  • merge() expects non-overlapping sections. If the same parent/child path already exists, Hier Config raises hier_config.exceptions.DuplicateChildError to highlight the conflict. This behavior is helpful when you are stitching together independent remediation fragments (AAA, logging, SNMP, and so on) and need deterministic, fail-fast validation.
  • future() mirrors how a device processes configuration input. Shared sections are merged recursively, later commands overwrite earlier leaf nodes, and the result reflects the post-change running configuration. Use future() when you want to preview layered changes or intentionally override existing settings.

If you receive a DuplicateChildError while calling merge(), consider whether your use case aligns better with future().

post_change_1_config = running_config.future(change_1_config)
change_1_rollback_config = post_change_1_config.config_to_get_to(running_config)
post_change_2_config = post_change_1_config.future(change_2_config)
change_2_rollback_config = post_change_2_config.config_to_get_to(post_change_1_config)

Known Limitations

The future() algorithm is a best-effort simulation. The following cases are not yet handled:

  1. Negating a numbered ACL when removing a single entry. Removing one line from a numbered ACL (e.g. no access-list 10 deny 10.0.0.0 0.255.255.255) requires the OS to track sequence numbers, which hier_config does not currently model. Workaround: Use extended named ACLs with sequence numbers so that hier_config can treat each entry independently via the ACL sequence-number stripping logic.

  2. Sectional exiting. The future() algorithm does not emit sectional-exit tokens (e.g. exit-peer-policy) when reconstructing the predicted configuration. The output is structurally correct but may be missing closure tokens if you render it verbatim.

  3. Negate-with rules. When a command has a custom negation string defined by the driver (e.g. NegationDefaultWithRule), the future() algorithm may not apply that string when processing removal of that command.

  4. Idempotent command avoid list. Commands matched by IdempotentCommandsAvoidRule should be excluded from idempotency comparisons, but future() does not currently consult this list.

  5. ACL idempotency check. The extended ACL idempotency logic (sequence-number based matching) that drives config_to_get_to() is not replicated in future().

  6. And likely others. Complex platform-specific interactions (e.g. VRF-aware BGP sections, route-policy inline templates on IOS XR) may produce imperfect results.

Tip: If you discover a gap, please open an issue and include a minimal reproducible example (running config + change config + expected future config). Contributions that extend _future() to cover new cases are welcome.

Structural Idempotency Matching

Starting in version 3.4.0, Hier Config derives a structural "idempotency key" from each command’s lineage when evaluating IdempotentCommandsRule. This prevents unrelated lines that happen to share a prefix from being treated as duplicates during future() predictions. For example, distinct BGP neighbor descriptions such as neighbor 2.2.2.2 description neighbor2 and neighbor 3.3.3.3 description neighbor3 now remain in the predicted future configuration because their identities differ within the neighbor hierarchy.

Tip: When creating driver rules, ensure your MatchRule definitions capture the structural parts of the command that should be unique (for instance, via regex capture groups or specific startswith clauses). This allows the idempotency engine to distinguish between commands that only vary by attributes such as IP address or description text.

>>> from hier_config import get_hconfig, Platform
>>> from hier_config.utils import read_text_from_file
>>>

>>> running_config_text = read_text_from_file("./tests/fixtures/running_config.conf")
>>> generated_config_text = read_text_from_file("./tests/fixtures/remediation_config_without_tags.conf")
>>>
>>> running_config = get_hconfig(Platform.CISCO_IOS, running_config_text)
>>> remediation_config = get_hconfig(Platform.CISCO_IOS, remediation_config_text)
>>>
>>> print("Running Config")
Running Config
>>> for line in running_config.all_children():
...     print(line.cisco_style_text())
...
hostname aggr-example.rtr
ip access-list extended TEST
  10 permit ip 10.0.0.0 0.0.0.7 any
vlan 2
  name switch_mgmt_10.0.2.0/24
vlan 3
  name switch_mgmt_10.0.4.0/24
interface Vlan2
  descripton switch_10.0.2.0/24
  ip address 10.0.2.1 255.255.255.0
  shutdown
interface Vlan3
  mtu 9000
  description switch_mgmt_10.0.4.0/24
  ip address 10.0.4.1 255.255.0.0
  ip access-group TEST in
  no shutdown
>>>
>>> print("Remediation Config")
Remediation Config
>>> for line in remediation_config.all_children():
...     print(line.cisco_style_text())
...
vlan 3
  name switch_mgmt_10.0.3.0/24
vlan 4
  name switch_mgmt_10.0.4.0/24
interface Vlan2
  mtu 9000
  ip access-group TEST in
  no shutdown
interface Vlan3
  description switch_mgmt_10.0.3.0/24
  ip address 10.0.3.1 255.255.0.0
interface Vlan4
  mtu 9000
  description switch_mgmt_10.0.4.0/24
  ip address 10.0.4.1 255.255.0.0
  ip access-group TEST in
  no shutdown
>>>
>>> print("Future Config")
Future Config
>>> for line in running_config.future(remediation_config).all_children():
...     print(line.cisco_style_text())
...
vlan 3
  name switch_mgmt_10.0.3.0/24
vlan 4
  name switch_mgmt_10.0.4.0/24
interface Vlan2
  mtu 9000
  ip access-group TEST in
  descripton switch_10.0.2.0/24
  ip address 10.0.2.1 255.255.255.0
interface Vlan3
  description switch_mgmt_10.0.3.0/24
  ip address 10.0.3.1 255.255.0.0
  mtu 9000
  ip access-group TEST in
  no shutdown
interface Vlan4
  mtu 9000
  description switch_mgmt_10.0.4.0/24
  ip address 10.0.4.1 255.255.0.0
  ip access-group TEST in
  no shutdown
hostname aggr-example.rtr
ip access-list extended TEST
  10 permit ip 10.0.0.0 0.0.0.7 any
vlan 2
  name switch_mgmt_10.0.2.0/24
>>>

Unified Diff

HConfig.unified_diff() provides output similar to difflib.unified_diff() but with added awareness of out-of-order lines and parent-child relationships in the hier_config tree model.

This is useful when comparing configurations from two network devices (such as redundant pairs) or when validating differences between running and intended configurations.

Note: The unified diff algorithm shares the same limitations as future() regarding duplicate child entries (e.g., multiple endif statements in an IOS XR route-policy) and command ordering in sections where sequence matters (such as ACLs). For accurate ACL ordering, use sequence numbers.

>>> from hier_config import get_hconfig, Platform
>>> from pprint import pprint
>>>
>>> running_config_text = read_text_from_file("./tests/fixtures/running_config.conf")
>>> generated_config_text = read_text_from_file("./tests/fixtures/generated_config.conf")
>>>
>>> running_config = get_hconfig(Platform.CISCO_IOS, running_config_text)
>>> generated_config = get_hconfig(Platform.CISCO_IOS, generated_config_text)
>>>
>>> pprint(list(running_config.unified_diff(generated_config)))
['vlan 3',
 '  - name switch_mgmt_10.0.4.0/24',
 '  + name switch_mgmt_10.0.3.0/24',
 'interface Vlan2',
 '  - shutdown',
 '  + mtu 9000',
 '  + ip access-group TEST in',
 '  + no shutdown',
 'interface Vlan3',
 '  - description switch_mgmt_10.0.4.0/24',
 '  - ip address 10.0.4.1 255.255.0.0',
 '  + description switch_mgmt_10.0.3.0/24',
 '  + ip address 10.0.3.1 255.255.0.0',
 '+ vlan 4',
 '  + name switch_mgmt_10.0.4.0/24',
 '+ interface Vlan4',
 '  + mtu 9000',
 '  + description switch_mgmt_10.0.4.0/24',
 '  + ip address 10.0.4.1 255.255.0.0',
 '  + ip access-group TEST in',
 '  + no shutdown']
>>>

Lines prefixed with + are present in generated_config but not in running_config; lines prefixed with - are present in running_config but not in generated_config. Parent lines without a prefix are shown as context only.