2 Commits

Author SHA1 Message Date
mustafa.ulukaya d92a7e9660 feat(vip): adopt a discovered keepalived instance into a managed VIP
GET /api/vip/discoveries lists what the agents found; POST /api/vip/adopt turns
one vrrp_instance into a managed VIP using the values from the node's own file
instead of retyping them. The VIP is created PENDING like any other, so nothing
reaches the node until it is applied from Apply Management.

Adoption replaces the operator's file with our render, so the gate is the
feature. Blockers fall into three kinds and only two are resolvable:

  - a LOSS ("our renderer cannot reproduce this, so adopting would delete it")
    can be accepted explicitly - that is an informed choice about a notify hook
    or an LVS section;
  - an UNKNOWN prefix length can be supplied, because picking a netmask for a
    live VIP would change its routing;
  - anything else is an IMPOSSIBILITY, not a loss: an absent virtual_router_id,
    a fractional advert_int, an unsupported auth_type. No flag waves those
    through.

That rule now lives in one place, remaining_blockers(), so the endpoint and the
UI cannot drift apart - and it is unit-testable, which matters because getting
it wrong destroys a working config.

Adoption keeps the VRRP identity it found: unlike create_vip, which allocates
the next free VRID, a VRID already used in the pool is a hard 409. Silently
renumbering would put the adopted node in a different VRRP domain from the
peers that still run the original config.

The member row records the reporting node's own role, priority and interface,
and carries the one-shot takeover hash. The response returns the instance's
unicast peers, because those nodes hold their own keepalived.conf and have to
be adopted or added as members before the render describes a complete group.
2026-08-11 01:35:59 +03:00
mustafa.ulukaya 8ac567dfe0 feat(vip): parse an existing keepalived.conf so a VIP can be adopted
Groundwork for adopting a hand-maintained keepalived setup into HA/VIP
management. The page is empty today because the flow is one-way: VIPs are
declared in OpenManager and pushed to the node, and nothing reads what is
already there.

The heartbeat cannot drive adoption. It carries two keepalived facts -
keepalive_state (MASTER/BACKUP, best-effort from logs) and keepalive_ip (the
first address grepped out of virtual_ipaddress) - while render_keepalived_conf
needs eleven: virtual_router_id, auth_pass, interface, priority, prefix_length,
advert_int, unicast peers, track_haproxy, role, address and name. Guessing the
rest is not a cosmetic risk: a wrong VRID puts the nodes in separate VRRP
domains and a wrong auth_pass makes them reject each other, and either way both
nodes claim the VIP. So the config itself has to be read.

Extracting the fields is the easy half. Adoption REPLACES the operator's file
with our render, so anything their file contains that the renderer cannot
reproduce would be destroyed on takeover - a notify_master failover hook, an LVS
virtual_server section, a sync group, a second address in one instance, a custom
track_script. The parser therefore also returns everything it could not model,
and build_adoption_candidate turns each entry into a blocker with the file's own
line number. Values that are unknowable rather than unreproducible block too: a
missing virtual_router_id, and a missing prefix length, because our renderer
always writes an explicit prefix and picking one would silently change a live
VIP's netmask. keepalived's own documented defaults (state BACKUP, priority 100,
advert_int 1) are applied but reported in `defaulted`, so the UI can say which
values were assumed rather than read.

Handles the layout variation real files have: nested braces, `#` and `!`
comments, blocks opened and closed on one line, quoted script paths containing
spaces, and several vrrp_instance blocks in one file.

A parse result carries auth_pass in cleartext, since that is the only way to
re-render an identical config, so it must never be logged - noted on every
function that returns one.

Tests pin each blocker and the layout variants, and include the invariant that
keeps the parser honest: a config the renderer itself produced must parse back
with zero blockers, so adding a directive to render_keepalived_conf without
teaching the parser fails the suite instead of making OpenManager's own output
look unadoptable. Verified by mutation - eight deliberate weakenings of the
safety checks are each caught by at least one test.

No endpoint, no schema change and no agent change yet; nothing calls this.
2026-08-11 01:18:49 +03:00