Capstone
Canary Rollout and Validation Gates
What you'll learn
- Selecting a canary from a runtime-detected subset, not a static inventory tag
- Applying the exact Git-evidenced configuration from Lesson 3, not a hardcoded string
- Both real outcomes: a canary that passes and expands, and one that fails and rolls back
- What this lesson does not re-teach from Production Automation
Listen to a guided explanation of this lesson.
Spoken narration isn't supported in this browser.
Connecting the earlier stages to rollout
Not re-teaching Production Automation
select_canary(), run_stage(), evaluate_gate(), and rollback_stage() use the exact mechanics Production Automation already taught and verified -- nr.filter(), napalm_configure()/napalm_get(), a threshold gate, napalm_rollback. Nothing about how those individual pieces work is re-explained here. This lesson's job is connecting them to what Lessons 1-3 actually produced.
- Lesson 1's non-compliant devices + Lesson 3's approved config
- select_canary()one host from the detected subset, not a static role tag
- run_stage(canary, approved_config)apply, then validate against the Git-evidenced line
- evaluate_gate()
- PASS -> expand to the rest of the detected subsetsame approved_config
- FAIL -> abort -> rollback_stage(canary)
The code
from nornir_napalm.plugins.tasks import napalm_rollback
def select_canary(nr, affected_hostnames):
affected_sorted = sorted(affected_hostnames)
canary_host = affected_sorted[0]
rest_hosts = affected_sorted[1:]
canary_nr = nr.filter(filter_func=lambda h: h.name == canary_host)
rest_nr = nr.filter(filter_func=lambda h: h.name in rest_hosts)
return canary_nr, rest_nr
def _apply_and_validate(task, configuration):
task.run(task=napalm_configure, configuration=configuration)
task.run(task=napalm_get, getters=["get_config"])
def run_stage(nr_subset, configuration):
agg = nr_subset.run(task=_apply_and_validate, configuration=configuration)
stage_result = {}
for hostname, multi_result in agg.items():
if multi_result.failed:
stage_result[hostname] = {"applied": False, "validated": False}
continue
applied = not multi_result[1].failed
running_config = multi_result[2].result["get_config"]["running"]
validated = configuration.strip() in running_config
stage_result[hostname] = {"applied": applied, "validated": validated}
return stage_result
def evaluate_gate(stage_result, threshold=1.0):
total = len(stage_result)
passed = sum(1 for r in stage_result.values() if r["applied"] and r["validated"])
pass_rate = passed / total if total else 0.0
return "PASS" if pass_rate >= threshold else "FAIL"
def rollback_stage(nr_subset):
return nr_subset.run(task=napalm_rollback)Real, verified code. run_stage() validates against the "configuration" parameter it was actually given -- Lesson 3's Git-evidenced artifact, passed in directly, not a separately hardcoded expectation.
Scenario A — real, verified: canary passes, rollout expands
canary: {'RTR1': {'applied': True, 'validated': True}}
decision: PASS
expanded: {
'RTR2': {'applied': True, 'validated': True},
'SW-EOS1': {'applied': True, 'validated': True}
}
rolled_back: FalseReal, verified. RTR1 (canary) both applied and validated correctly; the gate decided PASS; the same approved configuration expanded to RTR2 and SW-EOS1 -- both applying and validating successfully.
Scenario B — real, verified: canary fails, aborts, rolls back
Not just the happy path
A separate fixture scenario represents the canary's commit succeeding but its post-change validation failing. No fixtures for the rollout stage exist for RTR2/SW-EOS1 in this scenario (they still have detection-stage fixtures, since detection touches the whole fleet) -- so if the code mistakenly tried to expand after a failed gate, it would fail loudly with a real error rather than silently succeeding.
canary: {'RTR1': {'applied': True, 'validated': False}}
decision: FAIL
expanded: {}
rolled_back: TrueReal, verified. RTR1's configuration was applied (committed) but validation found the required line still missing -- a genuine post-change verification failure, not a connection error. The gate correctly decided FAIL, expansion never ran, and rollback genuinely succeeded.
Key takeaways
- select_canary() operates on Lesson 1's runtime-detected affected devices, not a static inventory role -- the one genuinely new wiring in this stage.
- run_stage() validates against the exact configuration parameter it receives -- Lesson 3's Git-evidenced artifact, not a separately maintained constant.
- Scenario A, real and verified: canary applies and validates, gate decides PASS, the same approved configuration expands to the rest of the affected devices.
- Scenario B, real and verified: canary applies but fails validation, gate decides FAIL, expansion never runs, rollback genuinely succeeds -- with fixture absence proving the rest of the fleet was truly untouched.
- None of napalm_configure(), napalm_rollback(), or nr.filter()'s own mechanics are re-explained here -- all assumed from Production Automation.
Done with this lesson?
Next lesson
Capstone: Source of Truth to Safe Network Change