f-50: lab: topologie et calcul du plan #50
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
81eb0ebe3b
commit
4d0fc7e18d
11 changed files with 1310 additions and 4 deletions
170
internal/lab/topology/validate.go
Normal file
170
internal/lab/topology/validate.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package topology
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
MinMTU = 1280
|
||||
MaxMTU = 9000
|
||||
MinMemory = 256
|
||||
MaxPrefix = 30
|
||||
MinPrefix = 8
|
||||
)
|
||||
|
||||
var (
|
||||
namePattern = regexp.MustCompile(`^[a-z][a-z0-9-]{0,14}$`)
|
||||
segmentPattern = regexp.MustCompile(`^[a-z][a-z0-9]{0,11}$`)
|
||||
)
|
||||
|
||||
func (t *Topology) Validate() error {
|
||||
var errs []error
|
||||
add := func(format string, args ...any) {
|
||||
errs = append(errs, fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
if !namePattern.MatchString(t.Name) {
|
||||
add("name %q: must match %s", t.Name, namePattern)
|
||||
}
|
||||
|
||||
images := map[string]bool{}
|
||||
for _, i := range t.Images {
|
||||
images[i.Name] = true
|
||||
if !strings.HasPrefix(i.URL, "https://") {
|
||||
add("image %s: url must be an https:// URL", i.Name)
|
||||
}
|
||||
if !strings.HasPrefix(i.Sums, "https://") {
|
||||
add("image %s: sums must be an https:// URL", i.Name)
|
||||
}
|
||||
}
|
||||
|
||||
nodes := map[string]Node{}
|
||||
for _, n := range t.Nodes {
|
||||
nodes[n.Name] = n
|
||||
}
|
||||
if len(t.Nodes) == 0 {
|
||||
add("nodes: at least one node is required")
|
||||
}
|
||||
if len(t.Segments) > maxSegments {
|
||||
add("segments: %d declared, at most %d", len(t.Segments), maxSegments)
|
||||
}
|
||||
if len(t.Nodes) > maxNodes {
|
||||
add("nodes: %d declared, at most %d", len(t.Nodes), maxNodes)
|
||||
}
|
||||
|
||||
segments := map[string]Segment{}
|
||||
for _, s := range t.Segments {
|
||||
segments[s.Name] = s
|
||||
if !segmentPattern.MatchString(s.Name) {
|
||||
add("segment %s: name must match %s", s.Name, segmentPattern)
|
||||
}
|
||||
sw, ok := nodes[s.Switch]
|
||||
switch {
|
||||
case s.Switch == "":
|
||||
add("segment %s: switch is required", s.Name)
|
||||
case !ok:
|
||||
add("segment %s: switch %s is not a declared node", s.Name, s.Switch)
|
||||
case sw.Role != RoleSwitch:
|
||||
add("segment %s: %s is a %s, not a switch", s.Name, s.Switch, sw.Role)
|
||||
}
|
||||
if s.MTU < MinMTU || s.MTU > MaxMTU {
|
||||
add("segment %s: mtu %d out of range [%d, %d]", s.Name, s.MTU, MinMTU, MaxMTU)
|
||||
}
|
||||
prefix, err := netip.ParsePrefix(s.CIDR)
|
||||
switch {
|
||||
case err != nil:
|
||||
add("segment %s: cidr %q: %v", s.Name, s.CIDR, err)
|
||||
case !prefix.Addr().Is4():
|
||||
add("segment %s: cidr %s is not IPv4", s.Name, s.CIDR)
|
||||
case prefix.Masked() != prefix:
|
||||
add("segment %s: cidr %s has host bits set, network is %s", s.Name, s.CIDR, prefix.Masked())
|
||||
case prefix.Bits() < MinPrefix || prefix.Bits() > MaxPrefix:
|
||||
add("segment %s: cidr %s prefix length out of range [/%d, /%d]", s.Name, s.CIDR, MinPrefix, MaxPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
attached := map[string]int{}
|
||||
hosting := map[string]int{}
|
||||
for _, s := range t.Segments {
|
||||
hosting[s.Switch]++
|
||||
}
|
||||
for _, n := range t.Nodes {
|
||||
if !namePattern.MatchString(n.Name) {
|
||||
add("node %s: name must match %s", n.Name, namePattern)
|
||||
}
|
||||
switch n.Role {
|
||||
case RoleSwitch, RoleRR, RoleHypervisor:
|
||||
default:
|
||||
add("node %s: role %q must be one of %s, %s, %s", n.Name, n.Role, RoleSwitch, RoleRR, RoleHypervisor)
|
||||
}
|
||||
if !images[n.Image] {
|
||||
add("node %s: image %q is not declared", n.Name, n.Image)
|
||||
}
|
||||
if n.CPUs < 1 {
|
||||
add("node %s: cpus must be at least 1", n.Name)
|
||||
}
|
||||
if n.Memory < MinMemory {
|
||||
add("node %s: memory must be at least %d MiB", n.Name, MinMemory)
|
||||
}
|
||||
if n.Role == RoleSwitch {
|
||||
if len(n.Segments) > 0 || len(n.Addresses) > 0 {
|
||||
add("node %s: a switch carries its segments through segments.<name>.switch, not through segments or addresses", n.Name)
|
||||
}
|
||||
if hosting[n.Name] == 0 {
|
||||
add("node %s: switch carries no segment", n.Name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(n.Segments) == 0 {
|
||||
add("node %s: must be attached to at least one segment", n.Name)
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, name := range n.Segments {
|
||||
if seen[name] {
|
||||
add("node %s: segment %s listed twice", n.Name, name)
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
if _, ok := segments[name]; !ok {
|
||||
add("node %s: segment %s is not declared", n.Name, name)
|
||||
continue
|
||||
}
|
||||
attached[name]++
|
||||
}
|
||||
for _, name := range sortedKeys(n.Addresses) {
|
||||
address := n.Addresses[name]
|
||||
if !seen[name] {
|
||||
add("node %s: address given for segment %s it is not attached to", n.Name, name)
|
||||
continue
|
||||
}
|
||||
if _, err := netip.ParseAddr(address); err != nil {
|
||||
add("node %s: address %q on %s: %v", n.Name, address, name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range t.Segments {
|
||||
if attached[s.Name] == 0 {
|
||||
add("segment %s: no node is attached", s.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sortedKeys(m map[string]string) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue