f-50: lab: déploiement de two sur les hyperviseurs et provisionnement en un script #50
Signed-off-by: GnomeZworc <nicolas.boufidjeline@g3e.fr>
This commit is contained in:
parent
b348652995
commit
4410f30afa
16 changed files with 360 additions and 64 deletions
|
|
@ -22,8 +22,8 @@ segments:
|
|||
nodes:
|
||||
sw1: { role: switch, image: deb, cpus: 2, memory: 1024 }
|
||||
rr1: { role: rr, image: deb, cpus: 1, memory: 1024, segments: [underlay] }
|
||||
hv1: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay] }
|
||||
hv2: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay] }
|
||||
hv1: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay], release: 0.2.0rc002 }
|
||||
hv2: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay], release: 0.2.0rc002 }
|
||||
`
|
||||
|
||||
const twoSegments = `name: two-seg
|
||||
|
|
@ -36,7 +36,7 @@ segments:
|
|||
blue: { switch: sw, cidr: 10.2.0.0/24, mtu: 1500 }
|
||||
nodes:
|
||||
sw: { role: switch, image: deb, cpus: 1, memory: 512 }
|
||||
hv: { role: hypervisor, image: deb, cpus: 1, memory: 512, segments: [blue, red] }
|
||||
hv: { role: hypervisor, image: deb, cpus: 1, memory: 512, segments: [blue, red], release: 0.2.0rc002 }
|
||||
`
|
||||
|
||||
func plan(t *testing.T, doc string) *topology.Plan {
|
||||
|
|
@ -317,7 +317,7 @@ func TestNetworkConfig_DefaultRouteOnlyOnFirstSegment(t *testing.T) {
|
|||
|
||||
func TestUserData_Hypervisor(t *testing.T) {
|
||||
cfg := user(t, nodeNamed(t, renderAll(t, twoHypervisors), "hv1"))
|
||||
if cfg.Hostname != "hv1" || !cfg.DisableRoot || len(cfg.WriteFiles) != 0 || len(cfg.Runcmd) != 0 || len(cfg.Packages) != 0 {
|
||||
if cfg.Hostname != "hv1" || !cfg.DisableRoot || len(cfg.Packages) != 0 || !reflect.DeepEqual(cfg.Runcmd, [][]string{{"/usr/local/sbin/lab-provision"}}) {
|
||||
t.Errorf("hv1 user-data = %+v", cfg)
|
||||
}
|
||||
if !reflect.DeepEqual(cfg.SSHAuthorizedKeys, []string{labKey}) {
|
||||
|
|
@ -376,9 +376,12 @@ nft -f /etc/lab-switch.nft
|
|||
t.Errorf("unit misses %q", want)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(cfg.Runcmd, [][]string{{"systemctl", "daemon-reload"}, {"systemctl", "enable", "--now", "lab-switch.service"}}) {
|
||||
if !reflect.DeepEqual(cfg.Runcmd, [][]string{{"/usr/local/sbin/lab-provision"}}) {
|
||||
t.Errorf("runcmd = %v", cfg.Runcmd)
|
||||
}
|
||||
if got := fileAt(t, cfg, "/usr/local/sbin/lab-provision").Content; got != "#!/bin/sh\nset -eu\nsystemctl daemon-reload\nsystemctl enable --now lab-switch.service\n" {
|
||||
t.Errorf("lab-provision:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserData_SwitchWithTwoSegments(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package render
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -19,8 +21,8 @@ segments:
|
|||
nodes:
|
||||
sw1: { role: switch, image: deb, cpus: 2, memory: 1024, secondary: { underlay: [169.254.0.1/28] }, frr: sw1.conf }
|
||||
rr1: { role: rr, image: deb, cpus: 1, memory: 1024, segments: [underlay], secondary: { underlay: [169.254.0.3/28] }, loopback: 10.255.255.1/32, frr: rr1.conf }
|
||||
hv1: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay], frr: hv1.conf }
|
||||
hv2: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay] }
|
||||
hv1: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay], frr: hv1.conf, release: 0.2.0rc002 }
|
||||
hv2: { role: hypervisor, image: deb, cpus: 4, memory: 16384, segments: [underlay], release: 0.2.0rc002 }
|
||||
`
|
||||
|
||||
var frrConfigs = map[string]string{
|
||||
|
|
@ -95,9 +97,9 @@ func TestRoles_LoopbackOnADummyInterfaceReplayedAtBoot(t *testing.T) {
|
|||
if !strings.Contains(unit, "ExecStart=/usr/local/sbin/lab-node\n") || !strings.Contains(unit, "WantedBy=multi-user.target\n") {
|
||||
t.Errorf("lab-node.service:\n%s", unit)
|
||||
}
|
||||
wantCmds := []string{"systemctl daemon-reload", "systemctl enable --now lab-node.service", "/usr/local/sbin/lab-frr"}
|
||||
if got := runcmd(t, rr1); !reflect.DeepEqual(got, wantCmds) {
|
||||
t.Errorf("runcmd = %q, want %q", got, wantCmds)
|
||||
want = "#!/bin/sh\nset -eu\nsystemctl daemon-reload\nsystemctl enable --now lab-node.service\n/usr/local/sbin/lab-frr\n"
|
||||
if got := fileAt(t, cfg, "/usr/local/sbin/lab-provision").Content; got != want {
|
||||
t.Errorf("lab-provision:\n%s\nwant:\n%s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,22 +143,96 @@ func TestRoles_FRRKeyIsThePinnedRepositoryKey(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRoles_FRRInstallRunsLast(t *testing.T) {
|
||||
func TestRoles_ProvisioningIsOneScriptThatStopsAtTheFirstFailure(t *testing.T) {
|
||||
nodes := renderRoles(t)
|
||||
for name, want := range map[string][]string{
|
||||
"sw1": {"systemctl daemon-reload", "systemctl enable --now lab-switch.service", "/usr/local/sbin/lab-frr"},
|
||||
"hv1": {"/usr/local/sbin/lab-frr"},
|
||||
for name, steps := range map[string]string{
|
||||
"sw1": "systemctl daemon-reload\nsystemctl enable --now lab-switch.service\n/usr/local/sbin/lab-frr\n",
|
||||
"hv1": "/usr/local/sbin/lab-deploy\n/usr/local/sbin/lab-frr\n",
|
||||
"hv2": "/usr/local/sbin/lab-deploy\n",
|
||||
} {
|
||||
if got := runcmd(t, nodeNamed(t, nodes, name)); !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("%s runcmd = %q, want %q", name, got, want)
|
||||
cfg := user(t, nodeNamed(t, nodes, name))
|
||||
if !reflect.DeepEqual(cfg.Runcmd, [][]string{{"/usr/local/sbin/lab-provision"}}) {
|
||||
t.Errorf("%s runcmd = %q", name, cfg.Runcmd)
|
||||
}
|
||||
f := fileAt(t, cfg, "/usr/local/sbin/lab-provision")
|
||||
if f.Content != "#!/bin/sh\nset -eu\n"+steps || f.Permissions != "0755" {
|
||||
t.Errorf("%s lab-provision (%s):\n%s", name, f.Permissions, f.Content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoles_NodeWithoutRoleFieldsGetsNothingExtra(t *testing.T) {
|
||||
cfg := user(t, nodeNamed(t, renderRoles(t), "hv2"))
|
||||
if len(cfg.WriteFiles) != 0 || len(cfg.Runcmd) != 0 {
|
||||
t.Errorf("hv2 write_files %d, runcmd %q", len(cfg.WriteFiles), cfg.Runcmd)
|
||||
func TestRoles_HypervisorDeploysTheReleaseThroughTheRepositoryScripts(t *testing.T) {
|
||||
cfg := user(t, nodeNamed(t, renderRoles(t), "hv1"))
|
||||
|
||||
f := fileAt(t, cfg, "/usr/local/sbin/lab-deploy")
|
||||
want := `#!/bin/sh
|
||||
set -eu
|
||||
n=0
|
||||
until curl -fsS -o /dev/null https://git.g3e.fr/; do
|
||||
n=$((n + 1))
|
||||
[ "$n" -lt 30 ] || exit 1
|
||||
sleep 10
|
||||
done
|
||||
cd /opt/two/scripts
|
||||
bash ./deploy.sh --noup_script -i -u underlay -t 0.2.0rc002
|
||||
`
|
||||
if f.Content != want || f.Permissions != "0755" {
|
||||
t.Errorf("lab-deploy (%s):\n%s\nwant:\n%s", f.Permissions, f.Content, want)
|
||||
}
|
||||
|
||||
for path, source := range map[string]string{
|
||||
"/opt/two/scripts/deploy.sh": "../../../scripts/deploy.sh",
|
||||
"/opt/two/scripts/bootstrap_kvm.sh": "../../../scripts/bootstrap_kvm.sh",
|
||||
} {
|
||||
f := fileAt(t, cfg, path)
|
||||
got, err := base64.StdEncoding.DecodeString(f.Content)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
repo, err := os.ReadFile(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f.Encoding != "b64" || f.Permissions != "0755" || !bytes.Equal(got, repo) {
|
||||
t.Errorf("%s: encoding %q, permissions %q, identical to %s: %v", path, f.Encoding, f.Permissions, source, bytes.Equal(got, repo))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoles_UplinkIsTheInterfaceCarryingTheDefaultRoute(t *testing.T) {
|
||||
hv := nodeNamed(t, renderAll(t, twoSegments), "hv")
|
||||
var withDefault []string
|
||||
for name, e := range network(t, hv).Ethernets {
|
||||
for _, r := range e.Routes {
|
||||
if r.To == "0.0.0.0/0" {
|
||||
withDefault = append(withDefault, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(withDefault, []string{"red"}) {
|
||||
t.Fatalf("default route on %v, want [red]", withDefault)
|
||||
}
|
||||
if got := fileAt(t, user(t, hv), "/usr/local/sbin/lab-deploy").Content; !strings.HasSuffix(got, "bash ./deploy.sh --noup_script -i -u red -t 0.2.0rc002\n") {
|
||||
t.Errorf("lab-deploy:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoles_OnlyHypervisorsDeployTwo(t *testing.T) {
|
||||
nodes := renderRoles(t)
|
||||
for _, name := range []string{"sw1", "rr1"} {
|
||||
for _, f := range user(t, nodeNamed(t, nodes, name)).WriteFiles {
|
||||
if strings.HasPrefix(f.Path, "/opt/two/") || f.Path == "/usr/local/sbin/lab-deploy" {
|
||||
t.Errorf("%s receives %s", name, f.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoles_NodeWithoutRoleFieldsGetsNoFRR(t *testing.T) {
|
||||
for _, f := range user(t, nodeNamed(t, renderRoles(t), "hv2")).WriteFiles {
|
||||
if strings.Contains(f.Path, "frr") {
|
||||
t.Errorf("hv2 receives %s", f.Path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"go.yaml.in/yaml/v3"
|
||||
|
||||
"git.g3e.fr/syonad/two/internal/lab/topology"
|
||||
"git.g3e.fr/syonad/two/scripts"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -25,6 +26,11 @@ const (
|
|||
FRRSuite = "frr-stable"
|
||||
FRRRepo = "https://deb.frrouting.org/frr"
|
||||
FRRPackages = "frr frr-pythontools"
|
||||
|
||||
ProvisionScript = "/usr/local/sbin/lab-provision"
|
||||
DeployScript = "/usr/local/sbin/lab-deploy"
|
||||
TwoScriptsDir = "/opt/two/scripts"
|
||||
TwoGitServer = "https://git.g3e.fr/"
|
||||
)
|
||||
|
||||
//go:embed frrouting.gpg
|
||||
|
|
@ -91,6 +97,7 @@ func userData(p *topology.Plan, n topology.NodePlan, o Options) ([]byte, error)
|
|||
DisableRoot: true,
|
||||
SSHAuthorizedKeys: o.AuthorizedKeys,
|
||||
}
|
||||
var steps []string
|
||||
switch {
|
||||
case n.Role == topology.RoleSwitch:
|
||||
cfg.Packages = []string{"nftables"}
|
||||
|
|
@ -99,19 +106,21 @@ func userData(p *topology.Plan, n topology.NodePlan, o Options) ([]byte, error)
|
|||
{Path: SwitchNFT, Permissions: "0644", Content: switchNFT(p, n.Name)},
|
||||
{Path: SwitchUnit, Permissions: "0644", Content: unit("Lab switch: bridges, gateways and NAT", SwitchScript)},
|
||||
}
|
||||
cfg.Runcmd = [][]string{
|
||||
{"systemctl", "daemon-reload"},
|
||||
{"systemctl", "enable", "--now", "lab-switch.service"},
|
||||
}
|
||||
steps = append(steps, "systemctl daemon-reload", "systemctl enable --now lab-switch.service")
|
||||
case n.Loopback.IsValid():
|
||||
cfg.WriteFiles = []writeFile{
|
||||
{Path: NodeScript, Permissions: "0755", Content: "#!/bin/sh\nset -eu\n" + loopbackLines(n)},
|
||||
{Path: NodeUnit, Permissions: "0644", Content: unit("Lab node: loopback", NodeScript)},
|
||||
}
|
||||
cfg.Runcmd = [][]string{
|
||||
{"systemctl", "daemon-reload"},
|
||||
{"systemctl", "enable", "--now", "lab-node.service"},
|
||||
}
|
||||
steps = append(steps, "systemctl daemon-reload", "systemctl enable --now lab-node.service")
|
||||
}
|
||||
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)},
|
||||
writeFile{Path: TwoScriptsDir + "/bootstrap_kvm.sh", Permissions: "0755", Encoding: "b64", Content: base64.StdEncoding.EncodeToString(scripts.BootstrapKVM)},
|
||||
writeFile{Path: DeployScript, Permissions: "0755", Content: deployScript(p, n)},
|
||||
)
|
||||
steps = append(steps, DeployScript)
|
||||
}
|
||||
if n.FRR != "" {
|
||||
cfg.WriteFiles = append(cfg.WriteFiles,
|
||||
|
|
@ -119,7 +128,13 @@ func userData(p *topology.Plan, n topology.NodePlan, o Options) ([]byte, error)
|
|||
writeFile{Path: FRRConfig, Permissions: "0640", Content: o.FRR[n.Name]},
|
||||
writeFile{Path: FRRScript, Permissions: "0755", Content: frrScript(n)},
|
||||
)
|
||||
cfg.Runcmd = append(cfg.Runcmd, []string{FRRScript})
|
||||
steps = append(steps, FRRScript)
|
||||
}
|
||||
if len(steps) > 0 {
|
||||
cfg.WriteFiles = append(cfg.WriteFiles,
|
||||
writeFile{Path: ProvisionScript, Permissions: "0755", Content: "#!/bin/sh\nset -eu\n" + strings.Join(steps, "\n") + "\n"},
|
||||
)
|
||||
cfg.Runcmd = [][]string{{ProvisionScript}}
|
||||
}
|
||||
body, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
|
|
@ -128,6 +143,18 @@ func userData(p *topology.Plan, n topology.NodePlan, o Options) ([]byte, error)
|
|||
return append([]byte("#cloud-config\n"), body...), nil
|
||||
}
|
||||
|
||||
func retry(command string) string {
|
||||
return "n=0\nuntil " + command + "; do\n n=$((n + 1))\n [ \"$n\" -lt 30 ] || exit 1\n sleep 10\ndone\n"
|
||||
}
|
||||
|
||||
func deployScript(p *topology.Plan, n topology.NodePlan) string {
|
||||
uplink := nodeCables(p, n.Name)[0].NodeInterface
|
||||
return "#!/bin/sh\nset -eu\n" +
|
||||
retry("curl -fsS -o /dev/null "+TwoGitServer) +
|
||||
"cd " + TwoScriptsDir + "\n" +
|
||||
fmt.Sprintf("bash ./deploy.sh --noup_script -i -u %s -t %s\n", uplink, n.Release)
|
||||
}
|
||||
|
||||
func frrDaemons(n topology.NodePlan) []string {
|
||||
if n.Role == topology.RoleHypervisor {
|
||||
return []string{"bgpd"}
|
||||
|
|
@ -139,7 +166,7 @@ func frrScript(n topology.NodePlan) string {
|
|||
var b strings.Builder
|
||||
b.WriteString("#!/bin/sh\nset -eu\nexport DEBIAN_FRONTEND=noninteractive\n. /etc/os-release\n")
|
||||
fmt.Fprintf(&b, "echo \"deb [signed-by=%s] %s ${VERSION_CODENAME} %s\" > /etc/apt/sources.list.d/frr.list\n", FRRKey, FRRRepo, FRRSuite)
|
||||
b.WriteString("n=0\nuntil apt-get update -qq --error-on=any; do\n n=$((n + 1))\n [ \"$n\" -lt 30 ] || exit 1\n sleep 10\ndone\n")
|
||||
b.WriteString(retry("apt-get update -qq --error-on=any"))
|
||||
fmt.Fprintf(&b, "apt-get install -y -qq --no-install-recommends %s\n", FRRPackages)
|
||||
for _, d := range frrDaemons(n) {
|
||||
fmt.Fprintf(&b, "sed -i 's/^%s=no/%s=yes/' /etc/frr/daemons\n", d, d)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue