Address order: sequential, reverse or random, per subnet and pool
- Which never-used pool address a new client gets: sequential (the lowest, as before), reverse or random, in the settings, a subnet or a single pool (FIRST-LAST:ORDER). When no never-used address is left, the one free the longest, whatever the order; returning clients keep theirs. - A subnet's pools are used in the order listed, or one at random weighted by its never-used addresses. Pools now keep the order they are given in (no more sorting by address). - CLI --address-order and --pool-order; --add-pool of an existing range changes its order in place. Web UI: per-pool order and a move-up button, pool and address order on the subnet and settings pages. - Kea's allocator: random is imported. Schema migration 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
29fc991ce1
commit
8ee430276c
29 files changed
+653
-79
No files matched your search
@@ -249,10 +249,11 @@ func settingsCmd() *cobra.Command {
|
||||
auth = "no (such requests get no answer)"
|
||||
}
|
||||
fmt.Fprintf(w, "Authoritative:\t%s\n", auth)
|
||||
fmt.Fprintf(w, "Address order:\t%s (subnets and pools can set their own)\n", s.EffectiveAddressOrder())
|
||||
})
|
||||
},
|
||||
})
|
||||
var leaseTime, ping, pingTimeout, hold, authoritative string
|
||||
var leaseTime, ping, pingTimeout, hold, authoritative, addrOrder string
|
||||
var opts, unsetOpts []string
|
||||
set := &cobra.Command{
|
||||
Use: "set",
|
||||
@@ -316,6 +317,9 @@ reservation's own option of the same name wins.
|
||||
not := !*a
|
||||
in.NotAuthoritative = ¬
|
||||
}
|
||||
if fl.Changed("address-order") {
|
||||
in.AddressOrder = &addrOrder
|
||||
}
|
||||
if len(opts) > 0 || len(unsetOpts) > 0 {
|
||||
var cur model.Settings
|
||||
if err := c.Get(ctx(), "/settings", nil, &cur); err != nil {
|
||||
@@ -344,6 +348,7 @@ reservation's own option of the same name wins.
|
||||
f.StringVar(&pingTimeout, "ping-timeout", "", "how long to wait for a ping's answer (500ms)")
|
||||
f.StringVar(&hold, "decline-hold", "", "how long an address in use by someone else is held back (24h)")
|
||||
f.StringVar(&authoritative, "authoritative", "", "refuse requests for addresses that don't belong on the interface: on or off")
|
||||
f.StringVar(&addrOrder, "address-order", "", "which never-used pool address a new client gets: sequential (the lowest), reverse (the highest) or random")
|
||||
f.StringArrayVar(&opts, "option", nil, "set an option NAME=VALUE for every subnet")
|
||||
f.StringArrayVar(&unsetOpts, "unset-option", nil, "remove an option")
|
||||
cmd.AddCommand(set)
|
||||
|
||||
+60
-9
@@ -17,6 +17,20 @@ func subnetPath(ref string) string {
|
||||
return "/subnets/" + url.PathEscape(strings.Replace(ref, "/", "_", 1))
|
||||
}
|
||||
|
||||
// orderText says how new addresses are chosen: "pools listed, addresses
|
||||
// the settings' order".
|
||||
func orderText(s *model.Subnet) string {
|
||||
pools := "pools in the order listed"
|
||||
if s.PoolOrder == model.PoolsRandom {
|
||||
pools = "a pool at random"
|
||||
}
|
||||
addrs := "the settings' order"
|
||||
if s.AddressOrder != "" {
|
||||
addrs = s.AddressOrder
|
||||
}
|
||||
return pools + ", addresses " + addrs + " (unless a pool has its own)"
|
||||
}
|
||||
|
||||
func poolsText(ps []model.Pool) string {
|
||||
var out []string
|
||||
for _, p := range ps {
|
||||
@@ -78,6 +92,7 @@ func subnetCmd() *cobra.Command {
|
||||
fmt.Fprintf(w, "Interface:\t%s\n", s.Interface)
|
||||
fmt.Fprintf(w, "Prefix:\t%s (mask %s)\n", s.Prefix, model.Mask(s.Prefix))
|
||||
fmt.Fprintf(w, "Pools:\t%s\n", poolsText(s.Pools))
|
||||
fmt.Fprintf(w, "Order:\t%s\n", orderText(s.Subnet))
|
||||
fmt.Fprintf(w, "Leased:\t%s, %d declined\n", usageText(s.Usage), s.Usage.Declined)
|
||||
fmt.Fprintf(w, "Reservations:\t%d (%d with a lease now)\n", s.Usage.Reservations, s.Usage.ReservationsLeased)
|
||||
lt := "the default"
|
||||
@@ -96,7 +111,7 @@ func subnetCmd() *cobra.Command {
|
||||
},
|
||||
})
|
||||
|
||||
var name, iface, leaseTime, comment, prefix string
|
||||
var name, iface, leaseTime, comment, prefix, poolOrder, addrOrder string
|
||||
var pools, opts, addPools, removePools, unsetOpts []string
|
||||
add := &cobra.Command{
|
||||
Use: "add PREFIX --interface IFACE",
|
||||
@@ -108,7 +123,11 @@ get its addresses.
|
||||
--pool 10.0.10.100-10.0.10.199 \
|
||||
--option routers=10.0.10.1 --option domain-name-servers=10.0.10.2
|
||||
|
||||
Pools are FIRST-LAST or a prefix; give --pool once per pool. Options are
|
||||
Pools are FIRST-LAST or a prefix; give --pool once per pool. A new client
|
||||
gets a never-used address: from the pools in the order given (or a pool at
|
||||
random: --pool-order random), the lowest first (or --address-order reverse
|
||||
or random). A pool can have its own order: --pool 10.0.10.200-10.0.10.250:random.
|
||||
Options are
|
||||
NAME=VALUE ('steward option list' has the names); a number works for any
|
||||
other option, with its type for ones Steward doesn't know: 224:string=hi.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
@@ -134,6 +153,12 @@ other option, with its type for ones Steward doesn't know: 224:string=hi.`,
|
||||
}
|
||||
in.LeaseTime = &n
|
||||
}
|
||||
if poolOrder != "" {
|
||||
in.PoolOrder = &poolOrder
|
||||
}
|
||||
if addrOrder != "" {
|
||||
in.AddressOrder = &addrOrder
|
||||
}
|
||||
if len(opts) > 0 {
|
||||
o, err := optionsArg(opts)
|
||||
if err != nil {
|
||||
@@ -151,7 +176,9 @@ other option, with its type for ones Steward doesn't know: 224:string=hi.`,
|
||||
f := add.Flags()
|
||||
f.StringVar(&iface, "interface", "", "the network interface it is served on (required)")
|
||||
f.StringVar(&name, "name", "", "a name for it")
|
||||
f.StringArrayVar(&pools, "pool", nil, "a pool: FIRST-LAST or a prefix (repeat for several)")
|
||||
f.StringArrayVar(&pools, "pool", nil, "a pool: FIRST-LAST or a prefix, with :ORDER for its own address order (repeat for several)")
|
||||
f.StringVar(&poolOrder, "pool-order", "", "which pool a new address comes from: listed (in the order given, the default) or random")
|
||||
f.StringVar(&addrOrder, "address-order", "", "which address of a pool: sequential, reverse or random (default: the settings')")
|
||||
f.StringVar(&leaseTime, "lease-time", "", "lease time (12h, 1d; default: the settings')")
|
||||
f.StringArrayVar(&opts, "option", nil, "an option NAME=VALUE (repeat for several)")
|
||||
f.StringVar(&comment, "comment", "", "a comment")
|
||||
@@ -161,11 +188,13 @@ other option, with its type for ones Steward doesn't know: 224:string=hi.`,
|
||||
update := &cobra.Command{
|
||||
Use: "update SUBNET",
|
||||
Short: "Change a subnet",
|
||||
Long: `Changes a subnet. Only what is given changes. --pool replaces all pools;
|
||||
--add-pool and --remove-pool change one. --option sets an option (keeping
|
||||
the others), --unset-option removes one.
|
||||
Long: `Changes a subnet. Only what is given changes. --pool replaces all pools,
|
||||
in the order given; --add-pool adds one at the end, or changes the order
|
||||
of a pool it already has; --remove-pool removes one. --option sets an
|
||||
option (keeping the others), --unset-option removes one.
|
||||
|
||||
steward subnet update office --add-pool 10.0.10.200-10.0.10.220
|
||||
steward subnet update office --address-order reverse --add-pool 10.0.10.200-10.0.10.220:random
|
||||
steward subnet update office --option ntp-servers=10.0.10.2 --unset-option time-offset`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
@@ -191,6 +220,12 @@ the others), --unset-option removes one.
|
||||
if fl.Changed("comment") {
|
||||
in.Comment = &comment
|
||||
}
|
||||
if fl.Changed("pool-order") {
|
||||
in.PoolOrder = &poolOrder
|
||||
}
|
||||
if fl.Changed("address-order") {
|
||||
in.AddressOrder = &addrOrder
|
||||
}
|
||||
if fl.Changed("lease-time") {
|
||||
n := 0
|
||||
if leaseTime != "default" {
|
||||
@@ -216,7 +251,7 @@ the others), --unset-option removes one.
|
||||
}
|
||||
found := false
|
||||
for i, p := range list {
|
||||
if pp, _ := model.ParsePool(p); pp == rp {
|
||||
if pp, _ := model.ParsePool(p); pp.SameRange(rp) {
|
||||
list = append(list[:i], list[i+1:]...)
|
||||
found = true
|
||||
break
|
||||
@@ -226,7 +261,21 @@ the others), --unset-option removes one.
|
||||
return fmt.Errorf("the subnet has no pool %s (it has %s)", rp, poolsText(cur.Pools))
|
||||
}
|
||||
}
|
||||
list = append(list, addPools...)
|
||||
// A pool it has already changes in place (its order).
|
||||
adding:
|
||||
for _, a := range addPools {
|
||||
ap, err := model.ParsePool(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, p := range list {
|
||||
if pp, _ := model.ParsePool(p); pp.SameRange(ap) {
|
||||
list[i] = a
|
||||
continue adding
|
||||
}
|
||||
}
|
||||
list = append(list, a)
|
||||
}
|
||||
in.Pools = &list
|
||||
}
|
||||
if len(opts) > 0 || len(unsetOpts) > 0 {
|
||||
@@ -252,7 +301,9 @@ the others), --unset-option removes one.
|
||||
f.StringVar(&iface, "interface", "", "a new interface")
|
||||
f.StringVar(&prefix, "prefix", "", "a new prefix")
|
||||
f.StringArrayVar(&pools, "pool", nil, "replace the pools (repeat for several)")
|
||||
f.StringArrayVar(&addPools, "add-pool", nil, "add a pool")
|
||||
f.StringArrayVar(&addPools, "add-pool", nil, "add a pool (FIRST-LAST[:ORDER]), or set the order of one it has")
|
||||
f.StringVar(&poolOrder, "pool-order", "", "which pool a new address comes from: listed or random")
|
||||
f.StringVar(&addrOrder, "address-order", "", "which address of a pool: sequential, reverse, random or default (the settings')")
|
||||
f.StringArrayVar(&removePools, "remove-pool", nil, "remove a pool")
|
||||
f.StringVar(&leaseTime, "lease-time", "", "lease time (12h, 1d, or \"default\")")
|
||||
f.StringArrayVar(&opts, "option", nil, "set an option NAME=VALUE")
|
||||
|
||||
Reference in new issue
Block a user