Kea: reservations in memory when there is no hosts database
Found against Kea 3.0.5 in the lab: reservation-add answers "Host database not available" on a server that keeps its configuration in a file. Then reservations go into the running configuration (operation-target memory) and config-write saves them; with a hosts database nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
482a9d5987
commit
dbb0d93dc7
2 files changed
+37
-9
No files matched your search
@@ -2,8 +2,10 @@ package main
|
||||
|
||||
// Kea: the control API (Kea 3.0's own HTTP control socket, or the Control
|
||||
// Agent before it) with the host_cmds hook. Subnet ids come from
|
||||
// config-get; reservations carry user-context {"rollcall": <machine>};
|
||||
// config-write keeps reservations made in memory (no hosts database).
|
||||
// config-get; reservations carry user-context {"rollcall": <machine>}.
|
||||
// A server with a hosts database keeps them there; one without answers
|
||||
// "Host database not available", and then they go into its running
|
||||
// configuration (operation-target memory), which config-write saves.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -22,6 +24,7 @@ type keaProvider struct {
|
||||
hc *http.Client
|
||||
persist bool
|
||||
service *bool // send "service": ["dhcp4"] (a Control Agent wants it; Kea 3.0's own socket refuses it)
|
||||
memory bool // no hosts database: reservations go into the running configuration
|
||||
}
|
||||
|
||||
func (k *keaProvider) Name() string { return "kea" }
|
||||
@@ -193,7 +196,21 @@ func (k *keaProvider) Put(ctx context.Context, r dhcpRes) error {
|
||||
if r.Hostname != "" {
|
||||
res["hostname"] = r.Hostname
|
||||
}
|
||||
_, err = k.ok(ctx, "reservation-add", map[string]any{"reservation": res})
|
||||
return k.change(ctx, "reservation-add", map[string]any{"reservation": res})
|
||||
}
|
||||
|
||||
// change runs reservation-add/-del against the hosts database, or the
|
||||
// configuration in memory when the server has no hosts database.
|
||||
func (k *keaProvider) change(ctx context.Context, command string, args map[string]any) error {
|
||||
if k.memory {
|
||||
args["operation-target"] = "memory"
|
||||
}
|
||||
_, err := k.ok(ctx, command, args)
|
||||
if err != nil && !k.memory && strings.Contains(err.Error(), "database not available") {
|
||||
k.memory = true
|
||||
args["operation-target"] = "memory"
|
||||
_, err = k.ok(ctx, command, args)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -202,12 +219,11 @@ func (k *keaProvider) Delete(ctx context.Context, r dhcpRes) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = k.ok(ctx, "reservation-del", map[string]any{"subnet-id": id, "identifier-type": "hw-address", "identifier": r.MAC})
|
||||
return err
|
||||
return k.change(ctx, "reservation-del", map[string]any{"subnet-id": id, "identifier-type": "hw-address", "identifier": r.MAC})
|
||||
}
|
||||
|
||||
func (k *keaProvider) Done(ctx context.Context) error {
|
||||
if !k.persist {
|
||||
if !k.persist || !k.memory { // a hosts database keeps them by itself
|
||||
return nil
|
||||
}
|
||||
if _, err := k.ok(ctx, "config-write", map[string]any{}); err != nil {
|
||||
|
||||
@@ -43,6 +43,7 @@ type fakeKea struct {
|
||||
mu sync.Mutex
|
||||
agent bool
|
||||
noHooks bool
|
||||
noHostDB bool // like Kea with a configuration file only: add/del need operation-target memory
|
||||
hosts []obj
|
||||
commands []string
|
||||
written int
|
||||
@@ -89,6 +90,13 @@ func (f *fakeKea) handler() http.Handler {
|
||||
return
|
||||
}
|
||||
answer(obj{"result": 0, "arguments": obj{"hosts": f.hosts}})
|
||||
case "reservation-add", "reservation-del":
|
||||
if f.noHostDB && args["operation-target"] != "memory" {
|
||||
answer(obj{"result": 1, "text": "Host database not available, cannot add host."})
|
||||
return
|
||||
}
|
||||
}
|
||||
switch req.Command {
|
||||
case "reservation-add":
|
||||
res := args["reservation"].(map[string]any)
|
||||
for _, h := range f.hosts {
|
||||
@@ -122,7 +130,7 @@ func keaFor(t *testing.T, f *fakeKea) (*keaProvider, func()) {
|
||||
|
||||
func TestKeaReservations(t *testing.T) {
|
||||
for _, agent := range []bool{false, true} {
|
||||
f := &fakeKea{agent: agent, hosts: []obj{
|
||||
f := &fakeKea{agent: agent, noHostDB: agent, hosts: []obj{
|
||||
{"hw-address": "52:54:00:aa:aa:aa", "ip-address": "10.214.0.9", "hostname": "printer"}, // someone else's
|
||||
{"hw-address": "52:54:00:00:00:09", "ip-address": "10.214.0.99", "user-context": obj{"rollcall": "m-old"}},
|
||||
}}
|
||||
@@ -146,13 +154,17 @@ func TestKeaReservations(t *testing.T) {
|
||||
added = h
|
||||
}
|
||||
}
|
||||
if dig(added, "user-context", "rollcall") != "m-1" || added["hostname"] != "fresh01" || added["subnet-id"] != float64(7) || f.written != 1 {
|
||||
wantWrites := 0 // a hosts database keeps reservations by itself
|
||||
if f.noHostDB {
|
||||
wantWrites = 1 // in memory: config-write saves them
|
||||
}
|
||||
if dig(added, "user-context", "rollcall") != "m-1" || added["hostname"] != "fresh01" || added["subnet-id"] != float64(7) || f.written != wantWrites {
|
||||
t.Errorf("agent=%v added %v written %d", agent, added, f.written)
|
||||
}
|
||||
// a second pass changes nothing
|
||||
rep = dhcpResReport{}
|
||||
a.dhcpResWith(context.Background(), want, []string{"10.214.0.0/24"}, &rep)
|
||||
if len(rep.Put)+len(rep.Deleted) != 0 || f.written != 1 {
|
||||
if len(rep.Put)+len(rep.Deleted) != 0 || f.written != wantWrites {
|
||||
t.Errorf("agent=%v second pass %+v", agent, rep)
|
||||
}
|
||||
done()
|
||||
|
||||
Reference in new issue
Block a user