f-50: lab: scénarios E5, configuration de l'agent par hyperviseur, release 0.2.0rc003 #50
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
0659dc69a5
commit
5728fdd33c
25 changed files with 760 additions and 27 deletions
|
|
@ -32,8 +32,8 @@ func (e exitErr) Error() string { return "exit status " + strconv.Itoa(int(e)) }
|
|||
func (e exitErr) ExitCode() int { return int(e) }
|
||||
|
||||
type fakeRunner struct {
|
||||
mu sync.Mutex
|
||||
calls [][]string
|
||||
mu sync.Mutex
|
||||
calls [][]string
|
||||
fail string
|
||||
ssh map[string][]error
|
||||
always map[string]error
|
||||
|
|
|
|||
|
|
@ -76,7 +76,11 @@ func Prepare(ctx context.Context, p *topology.Plan, o Options) ([]render.Node, e
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes, err := render.Render(p, render.Options{RunDir: o.RunDir, AuthorizedKeys: []string{key}, FRR: frr})
|
||||
agent, err := ReadAgent(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes, err := render.Render(p, render.Options{RunDir: o.RunDir, AuthorizedKeys: []string{key}, FRR: frr, Agent: agent})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -89,18 +93,26 @@ func Prepare(ctx context.Context, p *topology.Plan, o Options) ([]render.Node, e
|
|||
}
|
||||
|
||||
func ReadFRR(p *topology.Plan) (map[string]string, error) {
|
||||
configs := map[string]string{}
|
||||
return readNodeFiles(p, func(n topology.NodePlan) string { return n.FRR })
|
||||
}
|
||||
|
||||
func ReadAgent(p *topology.Plan) (map[string]string, error) {
|
||||
return readNodeFiles(p, func(n topology.NodePlan) string { return n.Agent })
|
||||
}
|
||||
|
||||
func readNodeFiles(p *topology.Plan, path func(topology.NodePlan) string) (map[string]string, error) {
|
||||
files := map[string]string{}
|
||||
for _, n := range p.Nodes {
|
||||
if n.FRR == "" {
|
||||
if path(n) == "" {
|
||||
continue
|
||||
}
|
||||
data, err := os.ReadFile(n.FRR)
|
||||
data, err := os.ReadFile(path(n))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("node %s: %w", n.Name, err)
|
||||
}
|
||||
configs[n.Name] = string(data)
|
||||
files[n.Name] = string(data)
|
||||
}
|
||||
return configs, nil
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func EnsureKey(ctx context.Context, r Runner, dir string) (string, error) {
|
||||
|
|
|
|||
|
|
@ -385,3 +385,23 @@ func TestPrepare_PutsTheFRRConfigIntoTheSeed(t *testing.T) {
|
|||
}
|
||||
t.Errorf("hv1 user-data has no /etc/lab/frr.conf:\n%s", data)
|
||||
}
|
||||
|
||||
func TestReadAgent_ReadsOnlyTheNodesThatDeclareOne(t *testing.T) {
|
||||
conf := filepath.Join(t.TempDir(), "two.yml")
|
||||
if err := os.WriteFile(conf, []byte("dhcp:\n backend: two\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m := newMirror(t)
|
||||
p := labPlan(t, m)
|
||||
p.Nodes[2].Agent = conf
|
||||
|
||||
got, err := ReadAgent(p)
|
||||
|
||||
if err != nil || !reflect.DeepEqual(got, map[string]string{"hv1": "dhcp:\n backend: two\n"}) {
|
||||
t.Errorf("ReadAgent = %q, %v", got, err)
|
||||
}
|
||||
p.Nodes[3].Agent = filepath.Join(t.TempDir(), "absent.yml")
|
||||
if _, err := ReadAgent(p); err == nil || !strings.Contains(err.Error(), "node hv2: ") {
|
||||
t.Errorf("error = %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ type Options struct {
|
|||
RunDir string
|
||||
AuthorizedKeys []string
|
||||
FRR map[string]string
|
||||
Agent map[string]string
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
|
|
@ -50,6 +51,9 @@ func Render(p *topology.Plan, o Options) ([]Node, error) {
|
|||
if _, ok := o.FRR[n.Name]; n.FRR != "" && !ok {
|
||||
return nil, fmt.Errorf("node %s: frr configuration %s was not read", n.Name, n.FRR)
|
||||
}
|
||||
if _, ok := o.Agent[n.Name]; n.Agent != "" && !ok {
|
||||
return nil, fmt.Errorf("node %s: agent configuration %s was not read", n.Name, n.Agent)
|
||||
}
|
||||
}
|
||||
var nodes []Node
|
||||
for index, n := range p.Nodes {
|
||||
|
|
|
|||
|
|
@ -262,3 +262,37 @@ func TestRoles_SwitchLoopbackIsCreatedByTheSwitchScript(t *testing.T) {
|
|||
t.Errorf("lab-switch:\n%s\ndoes not end with:\n%s", script, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoles_HypervisorAgentConfigIsWrittenBeforeTheDeployment(t *testing.T) {
|
||||
doc := strings.Replace(withRoles, "frr: hv1.conf, release: 0.2.0rc002 }", "frr: hv1.conf, release: 0.2.0rc002, agent: two.yml }", 1)
|
||||
nodes, err := Render(plan(t, doc), Options{RunDir: "/srv/lab", AuthorizedKeys: []string{labKey}, FRR: frrConfigs, Agent: map[string]string{"hv1": "dhcp:\n backend: two\n"}})
|
||||
if err != nil {
|
||||
t.Fatalf("Render: %v", err)
|
||||
}
|
||||
var hv1, hv2 Node
|
||||
for _, n := range nodes {
|
||||
switch n.Name {
|
||||
case "hv1":
|
||||
hv1 = n
|
||||
case "hv2":
|
||||
hv2 = n
|
||||
}
|
||||
}
|
||||
f := fileAt(t, user(t, hv1), "/etc/two/agent.yml")
|
||||
if f.Content != "dhcp:\n backend: two\n" || f.Permissions != "0640" {
|
||||
t.Errorf("agent.yml (%s) = %q", f.Permissions, f.Content)
|
||||
}
|
||||
for _, w := range user(t, hv2).WriteFiles {
|
||||
if w.Path == "/etc/two/agent.yml" {
|
||||
t.Error("hv2 receives an agent.yml it does not declare")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoles_RefusesAnUnreadAgentConfig(t *testing.T) {
|
||||
doc := strings.Replace(withRoles, "frr: hv1.conf, release: 0.2.0rc002 }", "frr: hv1.conf, release: 0.2.0rc002, agent: two.yml }", 1)
|
||||
_, err := Render(plan(t, doc), Options{RunDir: "/srv/lab", AuthorizedKeys: []string{labKey}, FRR: frrConfigs})
|
||||
if err == nil || err.Error() != "node hv1: agent configuration two.yml was not read" {
|
||||
t.Errorf("error = %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ const (
|
|||
DeployScript = "/usr/local/sbin/lab-deploy"
|
||||
TwoScriptsDir = "/opt/two/scripts"
|
||||
TwoGitServer = "https://git.g3e.fr/"
|
||||
AgentConfig = "/etc/two/agent.yml"
|
||||
)
|
||||
|
||||
//go:embed frrouting.gpg
|
||||
|
|
@ -114,6 +115,9 @@ func userData(p *topology.Plan, n topology.NodePlan, o Options) ([]byte, error)
|
|||
}
|
||||
steps = append(steps, "systemctl daemon-reload", "systemctl enable --now lab-node.service")
|
||||
}
|
||||
if n.Agent != "" {
|
||||
cfg.WriteFiles = append(cfg.WriteFiles, writeFile{Path: AgentConfig, Permissions: "0640", Content: o.Agent[n.Name]})
|
||||
}
|
||||
if n.Role == topology.RoleHypervisor {
|
||||
cfg.WriteFiles = append(cfg.WriteFiles,
|
||||
writeFile{Path: TwoScriptsDir + "/deploy.sh", Permissions: "0755", Encoding: "b64", Content: base64.StdEncoding.EncodeToString(scripts.Deploy)},
|
||||
|
|
|
|||
|
|
@ -22,15 +22,15 @@ func (p *Plan) Write(w io.Writer) error {
|
|||
|
||||
var extras []NodePlan
|
||||
for _, n := range p.Nodes {
|
||||
if len(n.Secondary) > 0 || n.Loopback.IsValid() || n.FRR != "" || n.Release != "" {
|
||||
if len(n.Secondary) > 0 || n.Loopback.IsValid() || n.FRR != "" || n.Release != "" || n.Agent != "" {
|
||||
extras = append(extras, n)
|
||||
}
|
||||
}
|
||||
if len(extras) > 0 {
|
||||
fmt.Fprintf(tw, "\nroles\n")
|
||||
fmt.Fprintf(tw, " name\tloopback\tsecondary\tfrr\trelease\n")
|
||||
fmt.Fprintf(tw, " name\tloopback\tsecondary\tfrr\trelease\tagent\n")
|
||||
for _, n := range extras {
|
||||
fmt.Fprintf(tw, " %s\t%s\t%s\t%s\t%s\n", n.Name, orDash(loopback(n)), orDash(secondary(n)), orDash(filepath.Base(n.FRR)), orDash(n.Release))
|
||||
fmt.Fprintf(tw, " %s\t%s\t%s\t%s\t%s\t%s\n", n.Name, orDash(loopback(n)), orDash(secondary(n)), orDash(filepath.Base(n.FRR)), orDash(n.Release), orDash(filepath.Base(n.Agent)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ type NodePlan struct {
|
|||
Loopback netip.Prefix
|
||||
FRR string
|
||||
Release string
|
||||
Agent string
|
||||
}
|
||||
|
||||
type Cable struct {
|
||||
|
|
@ -78,6 +79,7 @@ func Compute(t *Topology) (*Plan, error) {
|
|||
SSHPort: SSHBasePort + i,
|
||||
FRR: n.FRR,
|
||||
Release: n.Release,
|
||||
Agent: n.Agent,
|
||||
}
|
||||
for segment, raws := range n.Secondary {
|
||||
for _, raw := range raws {
|
||||
|
|
|
|||
|
|
@ -315,9 +315,9 @@ nodes
|
|||
hv2 hypervisor deb 4 16384 MiB 127.0.0.1:2203
|
||||
|
||||
roles
|
||||
name loopback secondary frr release
|
||||
hv1 - - - 0.2.0rc002
|
||||
hv2 - - - 0.2.0rc002
|
||||
name loopback secondary frr release agent
|
||||
hv1 - - - 0.2.0rc002 -
|
||||
hv2 - - - 0.2.0rc002 -
|
||||
|
||||
segment underlay: 10.250.0.0/24, mtu 9000, switch sw1, bridge br-underlay, gateway 10.250.0.1
|
||||
node interface address mac udp switch port mac udp
|
||||
|
|
|
|||
|
|
@ -142,10 +142,10 @@ func TestWrite_ShowsTheRoles(t *testing.T) {
|
|||
}
|
||||
want := `
|
||||
roles
|
||||
name loopback secondary frr release
|
||||
sw1 - underlay 169.254.0.1/28 sw1.conf -
|
||||
rr1 lo1 10.255.255.1/32 underlay 169.254.0.3/28 rr1.conf -
|
||||
hv1 - - hv1.conf 0.2.0rc002
|
||||
name loopback secondary frr release agent
|
||||
sw1 - underlay 169.254.0.1/28 sw1.conf - -
|
||||
rr1 lo1 10.255.255.1/32 underlay 169.254.0.3/28 rr1.conf - -
|
||||
hv1 - - hv1.conf 0.2.0rc002 -
|
||||
`
|
||||
if !bytes.Contains(buf.Bytes(), []byte(want)) {
|
||||
t.Errorf("plan:\n%s\ndoes not contain:\n%s", buf.String(), want)
|
||||
|
|
@ -214,3 +214,52 @@ func TestCompute_CarriesTheRelease(t *testing.T) {
|
|||
t.Errorf("rr1 release = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate_AgentOnlyForHypervisors(t *testing.T) {
|
||||
doc := header + `
|
||||
segments:
|
||||
red: { switch: sw, cidr: 10.1.0.0/24 }
|
||||
nodes:
|
||||
sw: { role: switch, image: deb, cpus: 1, memory: 512 }
|
||||
rr: { role: rr, image: deb, cpus: 1, memory: 512, segments: [red], agent: agent.yml }
|
||||
hv: { role: hypervisor, image: deb, cpus: 1, memory: 512, segments: [red], release: 0.2.0rc003, agent: agent.yml }
|
||||
`
|
||||
msg := validationError(t, doc)
|
||||
requireContains(t, msg, "node rr: agent is only for hypervisors")
|
||||
if bytes.Contains([]byte(msg), []byte("node hv: agent")) {
|
||||
t.Errorf("a hypervisor agent was refused:\n%s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoad_ResolvesTheAgentPathAgainstTheTopologyFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
path := filepath.Join(dir, "lab.yml")
|
||||
doc := header + `
|
||||
segments:
|
||||
red: { switch: sw, cidr: 10.1.0.0/24 }
|
||||
nodes:
|
||||
sw: { role: switch, image: deb, cpus: 1, memory: 512 }
|
||||
hv1: { role: hypervisor, image: deb, cpus: 1, memory: 512, segments: [red], release: 0.2.0rc003, agent: agent/two.yml }
|
||||
hv2: { role: hypervisor, image: deb, cpus: 1, memory: 512, segments: [red], release: 0.2.0rc003, agent: /etc/lab/agent.yml }
|
||||
`
|
||||
if err := os.WriteFile(path, []byte(doc), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
topo, err := Load(path)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
want := map[string]string{"sw": "", "hv1": filepath.Join(dir, "agent", "two.yml"), "hv2": "/etc/lab/agent.yml"}
|
||||
for _, n := range topo.Nodes {
|
||||
if n.Agent != want[n.Name] {
|
||||
t.Errorf("%s agent = %q, want %q", n.Name, n.Agent, want[n.Name])
|
||||
}
|
||||
}
|
||||
p, err := Compute(topo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := nodeOf(t, p, "hv1").Agent; got != filepath.Join(dir, "agent", "two.yml") {
|
||||
t.Errorf("plan hv1 agent = %q", got)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ type Node struct {
|
|||
Loopback string
|
||||
FRR string
|
||||
Release string
|
||||
Agent string
|
||||
}
|
||||
|
||||
type fileImage struct {
|
||||
|
|
@ -74,6 +75,7 @@ type fileNode struct {
|
|||
Loopback string `yaml:"loopback"`
|
||||
FRR string `yaml:"frr"`
|
||||
Release string `yaml:"release"`
|
||||
Agent string `yaml:"agent"`
|
||||
}
|
||||
|
||||
type file struct {
|
||||
|
|
@ -96,6 +98,9 @@ func Load(path string) (*Topology, error) {
|
|||
if n.FRR != "" && !filepath.IsAbs(n.FRR) {
|
||||
t.Nodes[i].FRR = filepath.Join(filepath.Dir(path), n.FRR)
|
||||
}
|
||||
if n.Agent != "" && !filepath.IsAbs(n.Agent) {
|
||||
t.Nodes[i].Agent = filepath.Join(filepath.Dir(path), n.Agent)
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
|
@ -144,6 +149,7 @@ func Parse(data []byte) (*Topology, error) {
|
|||
Loopback: n.Loopback,
|
||||
FRR: n.FRR,
|
||||
Release: n.Release,
|
||||
Agent: n.Agent,
|
||||
})
|
||||
}
|
||||
return t, nil
|
||||
|
|
|
|||
|
|
@ -215,6 +215,9 @@ func validateExtras(n Node, segments map[string]Segment, add func(string, ...any
|
|||
case n.Role != RoleHypervisor && n.Release != "":
|
||||
add("node %s: release is only for hypervisors", n.Name)
|
||||
}
|
||||
if n.Role != RoleHypervisor && n.Agent != "" {
|
||||
add("node %s: agent is only for hypervisors", n.Name)
|
||||
}
|
||||
if n.Loopback != "" {
|
||||
prefix, err := netip.ParsePrefix(n.Loopback)
|
||||
switch {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue