Core Question
Core Question
If several routers share one virtual IP, how does the code decide which one gets to be master?
Outcome
Outcome
By the end of this session, the learner should be able to:
- explain why priority 255 is not just "high priority" but a different category of claim
- name the exact tuple
elect()compares candidates on, in order - state RFC 5798's tiebreak rule and why it is guaranteed to produce exactly one winner
- explain the difference between "the best router" and "the router that currently masters," and why
should_preempt()exists to bridge that gap
Read Order
Read Order
- Read the module docstring at the top of the file
- Read
PRIORITY_OWNER - Read
_ip_key() - Read
VrrpRouter - Read
elect() - Read
should_preempt() - Run
examples/ha/session_01_walkthrough.py
Read It Like Code
Read It Like Code
VrrpRouter(
name,
priority,
primary_ip,
)Fields That Matter
Fields That Matter
| Field | Why it matters |
|---|---|
priority | The primary ranking key. 1-254 is configured; 255 is reserved and means something categorically different. |
primary_ip | The tiebreak key. Every router in a group has a distinct one, so it always resolves a tie. |
name | Identity only — never part of the comparison. |
Decision Flow
Decision Flow
elect(routers): compare every router on (priority, ip_key(primary_ip)) return the max should_preempt(current_master, candidate, preempt_enabled): preempt_enabled is False -> False (never take over) candidate.priority not > current -> False (not actually better) otherwise -> True (better candidate, preemption allowed)
Reading Lens
Reading Lens
The important move in this session is to stop reading elect() as VRRP-specific and start asking:
- what is the comparison key, and is it a single value or a tuple?
- what happens when two candidates tie on the first element of that tuple?
- is the max being taken over all candidates at once, or incrementally as they're heard from?
This is the third time this course elects-by-comparison: OSPF's dr_election.py ranks candidates on (priority, router_id), RIP's route.py better() ranks on a single metric, and VRRP here ranks on (priority, primary_ip). Same shape — rank every candidate on a comparison key and take the max — a different, protocol-specific rulebook filling in what the key actually is. Once you've read one, the other two are the same function with different tuples.
Toy Model Boundary
Toy Model Boundary
Real VRRP maps the elected master onto a virtual MAC address and answers ARP for the virtual IP; a backup that becomes master has to send a gratuitous ARP so the LAN's forwarding tables catch up. None of that exists here — this module stops at "who wins the comparison," not "how the network finds out." elect() also assumes it is handed the full candidate set at once; the real protocol assembles that set advertisement by advertisement, which is Session 02's job.
Code Landmarks
Code Landmarks
PRIORITY_OWNER's docstring
"It is not 'high priority,' it is authoritative." The address owner isn't merely favored by the comparison — RFC 5798 reserves 255 so that a router configured with the virtual IP as its own real address always wins, by construction. elect() doesn't special-case this; the ordinary max() on the tuple handles it because nothing else can reach 255.
_ip_key()
Turns "192.0.2.10" into (192, 0, 2, 10) — a tuple of ints, not a string. Comparing IP strings lexically would rank "192.0.2.9" above "192.0.2.10"; comparing the parsed tuple ranks them numerically, the way the RFC intends.
elect()'s one line
max(routers, key=lambda router: (router.priority, _ip_key(router.primary_ip))). Reading this line is the whole session: Python's tuple comparison already does the "compare priority first, break ties by IP" work — the function doesn't need an if for the tiebreak at all.
should_preempt()'s docstring
Both sentences on the tradeoff (preemption on: fast, predictable failback; preemption off: less churn on flaky links) are equally valid engineering choices in the source — this module resists picking a side, and the walkthrough exercises both.
Failure Questions
Failure Questions
Use the source file to answer these:
- Two routers are configured with the same priority. What is the exact second element of the comparison tuple that decides between them, and why does
_ip_key()exist instead of comparingprimary_ipstrings directly? - A router configured with
priority=255and a router configured withpriority=254are both candidates. Can thepriority=254router ever winelect()against it? Why not, structurally? should_preempt()is called with acandidatewhose priority is lower thancurrent_master's, andpreempt_enabled=True. What does it return, and which line decides that regardless of the flag?- If
preempt_enabled=False, doesshould_preempt()ever look at the two routers' priorities at all? Which line makes that comparison unreachable? elect()takes atuple[VrrpRouter, ...]and returns a singleVrrpRouter. What would happen if it were called with an empty tuple — is that guarded anywhere in this file?
Walkthrough
Walkthrough
Run this:
PYTHONPATH=src python3 examples/ha/session_01_walkthrough.py
The walkthrough elects among an address owner and two configured priorities, breaks a tie by primary IP in both tuple orders, and flips should_preempt()'s answer by toggling preempt_enabled and by lowering the candidate's priority.
Done When
Done When
The learner can say all of the following without looking at notes:
- "Priority 255 isn't the top of the range — it's a reserved category for the address owner, and nothing else can reach it."
- "elect() is one max() call over a (priority, ip) tuple; the tiebreak is built into how tuples compare, not a separate if."
- "should_preempt() answers a policy question, not a correctness question — the RFC doesn't mandate which way you set the flag."
References
References
- RFC 5798 Section 1.2 (VRRP overview, priority and preemption)
- RFC 5798 Section 4.1 (Master election, address owner priority 255)
Continue