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:
GnomeZworc 2026-10-04 20:43:53 +02:00
commit b4660c95d3
Signed by: nicolas.boufideline
GPG key ID: 4406BBBF8845D632
25 changed files with 760 additions and 27 deletions

151
scripts/lab/node.sh Normal file
View file

@ -0,0 +1,151 @@
set -u
API=http://127.0.0.1:8080
IMAGE_URL=https://cloud.debian.org/images/cloud/bookworm/latest
IMAGE_FILE=debian-12-genericcloud-amd64.qcow2
TWO_IMAGE=/var/tmp/deb-two.qcow2
VM_KEY=/root/.ssh/lab-vm
VOLUMES=/var/lib/two/volumes
check () {
local desc="${1}" out
shift
if out=$("$@" 2>&1); then
echo "RÉUSSI: ${desc}"
else
echo "ÉCHOUÉ: ${desc}${out:+ — $(printf '%s' "${out}" | tail -n 1)}"
fi
}
info () { echo "INFO: ${1}"; }
api_post () {
curl -fsS -o /dev/null -X POST -H 'Content-Type: application/json' -d "${2}" "${API}/${1}"
}
api_delete () {
curl -fsS -o /dev/null -X DELETE "${API}/${1}"
}
api_state () {
curl -fsS "${API}/${1}" 2>/dev/null | jq -r .state
}
wait_state () {
local path="${1}" want="${2}" i s
for i in $(seq 1 90); do
s=$(api_state "${path}")
[ "${s}" = "${want}" ] && return 0
[ "${want}" = deleted ] && [ -z "${s}" ] && return 0
[ "${s}" = error ] && { echo "${path}: error" >&2; return 1; }
sleep 2
done
echo "${path}: ${s:-absent}, ${want} attendu" >&2
return 1
}
vpc_create () {
api_state "vpcs/${1}" | grep -qx running && return 0
api_post vpcs "{\"name\":\"${1}\",\"cidr\":\"${2}\"}" && wait_state "vpcs/${1}" running
}
subnet_create () {
local name="${1}" vpc="${2}" vni="${3}" gw="${4}" cidr="${5}" extra="${6:-}"
api_state "subnets/${name}" | grep -qx running && return 0
api_post subnets "{\"name\":\"${name}\",\"vpc\":\"${vpc}\",\"mode\":\"vxlan\",\"vxlan_id\":${vni},\"iface_type\":\"vms\",\"interface_ip\":\"${gw}\",\"cidr\":\"${cidr}\"${extra}}" \
&& wait_state "subnets/${name}" running
}
two_image () {
[ -f "${TWO_IMAGE}" ] && return 0
local work
work=$(mktemp -d)
curl -fsSL -o "${work}/${IMAGE_FILE}" "${IMAGE_URL}/${IMAGE_FILE}" || return 1
curl -fsSL "${IMAGE_URL}/SHA512SUMS" | grep " ${IMAGE_FILE}\$" > "${work}/sums" || return 1
(cd "${work}" && sha512sum -c sums >/dev/null) || { echo "image: somme SHA-512 incorrecte" >&2; return 1; }
command -v qemu-nbd >/dev/null || DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends qemu-utils >/dev/null
modprobe nbd max_part=8
qemu-nbd -c /dev/nbd0 "${work}/${IMAGE_FILE}" || return 1
for i in $(seq 1 20); do [ -b /dev/nbd0p1 ] && break; sleep 0.5; done
mkdir -p /mnt/two-image
mount /dev/nbd0p1 /mnt/two-image || { qemu-nbd -d /dev/nbd0 >/dev/null; return 1; }
printf '%s\n' 'datasource_list: [ NoCloud ]' 'datasource:' ' NoCloud:' " seedfrom: 'http://169.254.169.254:80/'" ' timeout: 5' ' max_wait: 10' \
> /mnt/two-image/etc/cloud/cloud.cfg.d/99_metadata.cfg
umount /mnt/two-image
qemu-nbd -d /dev/nbd0 >/dev/null
mv "${work}/${IMAGE_FILE}" "${TWO_IMAGE}"
rm -rf "${work}"
}
vm_key () {
[ -f "${VM_KEY}" ] || ssh-keygen -q -t ed25519 -N '' -C lab-vm -f "${VM_KEY}"
cat "${VM_KEY}.pub"
}
vm_create () {
local name="${1}" subnet="${2}" ip="${3}" key="${4:-}" meta="{}"
[ -n "${key}" ] && meta="{\"sshkey\":\"${key}\"}"
mkdir -p "${VOLUMES}"
cp "${TWO_IMAGE}" "${VOLUMES}/${name}.qcow2" || return 1
api_post vms "{\"name\":\"${name}\",\"memory\":1024,\"cpus\":1,\"metadata\":${meta},\"interfaces\":[{\"subnet\":\"${subnet}\",\"ip\":\"${ip}\",\"primary\":true}],\"storage\":[{\"path\":\"${VOLUMES}/${name}.qcow2\",\"dev\":\"vda\"}]}" \
&& wait_state "vms/${name}" running
}
vm_delete () {
api_delete "vms/${1}" && wait_state "vms/${1}" deleted
}
vm_ssh () {
local vpc="${1}" ip="${2}"
shift 2
ip netns exec "${vpc}" ssh -i "${VM_KEY}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
-o LogLevel=ERROR -o ConnectTimeout=3 -o BatchMode=yes -n "syonad@${ip}" "$@"
}
vm_wait () {
local i
for i in $(seq 1 40); do
vm_ssh "${1}" "${2}" true 2>/dev/null && return 0
sleep 5
done
echo "${2}: SSH injoignable" >&2
return 1
}
vm_fails () {
local out
out=$(vm_ssh "${1}" "${2}" "${3} >/dev/null 2>&1; echo rc=\$?") || { echo "SSH vers ${2} en échec"; return 1; }
case "${out}" in
rc=0) echo "la commande a réussi dans ${2}"; return 1 ;;
rc=*) return 0 ;;
*) echo "sortie inattendue : ${out}"; return 1 ;;
esac
}
vm_grep () {
vm_ssh "${1}" "${2}" "${3} 2>&1" | grep -q -- "${4}"
}
has_addr () {
vm_ssh "${1}" "${2}" 'ip -4 -o addr show dev ens3' | grep -q " ${2}/"
}
route_via () {
vm_ssh "${1}" "${2}" "ip -4 route show ${3}" | grep -q "via ${4} "
}
dhcp_hosts () {
python3 - "/run/two/dhcp/${1}.sock" <<'PY'
import json, socket, sys
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect(sys.argv[1])
s.sendall(b'{"verb":"get-state"}\n')
buf = b""
while not buf.endswith(b"\n"):
c = s.recv(65536)
if not c:
break
buf += c
state = json.loads(buf).get("state") or {}
print(" ".join(sorted(h["mac"].lower() for h in state.get("hosts") or [])))
PY
}

67
scripts/lab/scenario.sh Executable file
View file

@ -0,0 +1,67 @@
#!/usr/bin/env bash
LAB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LAB_HOST="${LAB_HOST:-${LAB_DIR}/../lab-host.sh}"
SCENARIO_DIR="${SCENARIO_DIR:-${LAB_DIR}/scenarios}"
SCENARIO_LOG=""
usage () {
cat >&2 <<USAGE
usage: ${0##*/} <scénario>... | all
scénarios : $(cd "${SCENARIO_DIR}" && ls -- *.sh | sed 's/\.sh$//' | tr '\n' ' ')
Chaque scénario s'exécute depuis le Mac sur le lab en cours (lab-host.sh up, push, ./lab up).
Une ligne RÉUSSI ou ÉCHOUÉ par vérification ; code de sortie 1 si une vérification échoue ou
si aucune n'a été faite.
USAGE
exit 2
}
on () {
local node="${1}" assignment
shift
{
for assignment in "$@"; do
printf 'export %q=%q\n' "${assignment%%=*}" "${assignment#*=}"
done
cat "${LAB_DIR}/node.sh"
cat
} | "${LAB_HOST}" ssh "./lab ssh ${node} 'sudo bash -s'" 2>&1 | tee -a "${SCENARIO_LOG}"
}
run_scenario () {
local file="${1}" name passed failed
name="$(basename "${file}" .sh)"
SCENARIO_LOG="$(mktemp)"
echo "=== ${name}"
( . "${file}" )
passed=$(grep -c '^RÉUSSI: ' "${SCENARIO_LOG}")
failed=$(grep -c '^ÉCHOUÉ: ' "${SCENARIO_LOG}")
rm -f "${SCENARIO_LOG}"
echo "=== ${name} : ${passed} réussi(s), ${failed} échoué(s)"
[[ "${failed}" -eq 0 && "${passed}" -gt 0 ]]
}
main () {
local -a files=()
local arg file rc=0
[[ $# -gt 0 ]] || usage
for arg in "$@"; do
if [[ "${arg}" == all ]]; then
files+=("${SCENARIO_DIR}"/*.sh)
continue
fi
file=$(ls "${SCENARIO_DIR}/${arg}"*.sh 2>/dev/null | head -n 1)
[[ -n "${file}" ]] || { echo "scénario inconnu : ${arg}" >&2; usage; }
files+=("${file}")
done
for file in "${files[@]}"; do
run_scenario "${file}" || rc=1
done
return "${rc}"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" || -z "${BASH_SOURCE[0]}" ]]; then
main "$@"
fi

140
scripts/lab/scenario_test.sh Executable file
View file

@ -0,0 +1,140 @@
#!/usr/bin/env bash
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASS=0
FAIL=0
fail () { echo "FAIL: ${CURRENT}: ${1}"; FAIL=$(( FAIL + 1 )); CASE_OK=0; }
setup () {
CURRENT="${1}"
CASE_OK=1
WORK=$(mktemp -d "${TMPDIR:-/tmp}/scenario.XXXXXX")
mkdir -p "${WORK}/scenarios"
cat > "${WORK}/lab-host" <<'FAKE'
#!/usr/bin/env bash
N=$(ls "${FAKE_DIR}" | grep -c '^payload\.')
cat > "${FAKE_DIR}/payload.$(( N + 1 ))"
printf '%s\n' "$*" >> "${FAKE_DIR}/calls.log"
if ! bash -n "${FAKE_DIR}/payload.$(( N + 1 ))" 2>"${FAKE_DIR}/syntax.$(( N + 1 ))"; then
echo "ÉCHOUÉ: syntaxe du bloc $(( N + 1 ))"
exit 0
fi
[ -n "${FAKE_OUT:-}" ] && printf '%b\n' "${FAKE_OUT}"
exit 0
FAKE
chmod +x "${WORK}/lab-host"
: > "${WORK}/calls.log"
}
run () {
env LAB_HOST="${WORK}/lab-host" FAKE_DIR="${WORK}" SCENARIO_DIR="${SCENARIO_DIR:-${WORK}/scenarios}" \
FAKE_OUT="${FAKE_OUT:-}" bash "${DIR}/scenario.sh" "$@" > "${WORK}/out.log" 2>&1
}
teardown () {
[[ "${CASE_OK}" -eq 1 ]] && PASS=$(( PASS + 1 )) && echo "ok: ${CURRENT}"
[[ "${CASE_OK}" -eq 1 ]] || { echo " sortie :"; sed 's/^/ | /' "${WORK}/out.log"; }
rm -rf "${WORK}"
}
node () {
bash -c ". '${DIR}/node.sh'; ${1}" 2>&1
}
test_on_sends_assignments_library_and_block_to_the_node () {
setup "on : variables, bibliothèque et bloc envoyés au bon nœud"
printf '%s\n' "on hv2 KEY='ssh-ed25519 AAAA x' NAME=vm <<'NODE'" 'echo "${NAME}"' 'NODE' > "${WORK}/scenarios/t1.sh"
FAKE_OUT='RÉUSSI: un' run t1 || fail "code de sortie $?"
[[ $(cat "${WORK}/calls.log") == "ssh ./lab ssh hv2 'sudo bash -s'" ]] || fail "appel : $(cat "${WORK}/calls.log")"
grep -q '^check () {' "${WORK}/payload.1" || fail "node.sh absent du bloc"
[[ $(tail -n 1 "${WORK}/payload.1") == 'echo "${NAME}"' ]] || fail "bloc du scénario absent en fin d'envoi"
[[ $(bash -c "$(grep '^export ' "${WORK}/payload.1"); printf '%s|%s' \"\${KEY}\" \"\${NAME}\"") == "ssh-ed25519 AAAA x|vm" ]] \
|| fail "variables mal transmises : $(grep '^export ' "${WORK}/payload.1")"
teardown
}
test_scenario_result_counts_and_exit_codes () {
local CASE
for CASE in ok fail none; do
setup "résultat d'un scénario (${CASE})"
printf '%s\n' "on hv1 <<'NODE'" 'true' 'NODE' > "${WORK}/scenarios/t2.sh"
case "${CASE}" in
ok) FAKE_OUT='RÉUSSI: a\nRÉUSSI: b' run t2; [[ $? -eq 0 ]] || fail "code non nul"
grep -q '=== t2 : 2 réussi(s), 0 échoué(s)' "${WORK}/out.log" || fail "bilan absent" ;;
fail) FAKE_OUT='RÉUSSI: a\nÉCHOUÉ: b — détail' run t2; [[ $? -eq 1 ]] || fail "code différent de 1"
grep -q '=== t2 : 1 réussi(s), 1 échoué(s)' "${WORK}/out.log" || fail "bilan absent" ;;
none) FAKE_OUT='INFO: rien' run t2; [[ $? -eq 1 ]] || fail "un scénario sans vérification a réussi" ;;
esac
teardown
done
}
test_unknown_scenario_and_no_argument () {
setup "scénario inconnu ou absent : usage, code 2"
run nope; [[ $? -eq 2 ]] || fail "scénario inconnu : code $?"
run; [[ $? -eq 2 ]] || fail "sans argument : code $?"
[[ -s "${WORK}/calls.log" ]] && fail "un nœud a été appelé"
teardown
}
test_all_runs_every_scenario_and_fails_if_one_fails () {
setup "all : chaque scénario, échec si l'un échoue"
printf '%s\n' "on hv1 <<'NODE'" 'true' 'NODE' > "${WORK}/scenarios/a.sh"
printf '%s\n' "on hv1 <<'NODE'" 'true' 'NODE' > "${WORK}/scenarios/b.sh"
FAKE_OUT='ÉCHOUÉ: x' run all; [[ $? -eq 1 ]] || fail "code différent de 1"
[[ $(grep -c '^=== [ab] :' "${WORK}/out.log") -eq 2 ]] || fail "les deux scénarios n'ont pas tourné"
teardown
}
test_every_shipped_block_is_valid_bash () {
local f
setup "chaque bloc envoyé par les scénarios livrés est du bash valide"
SCENARIO_DIR="${DIR}/scenarios" FAKE_OUT='RÉUSSI: x' run all
for f in "${WORK}"/syntax.*; do
[[ -s "${f}" ]] && fail "${f##*/} : $(cat "${f}")"
done
[[ $(ls "${WORK}" | grep -c '^payload\.') -ge 10 ]] || fail "trop peu de blocs envoyés : $(ls "${WORK}" | grep -c '^payload\.')"
grep -q 'ÉCHOUÉ: syntaxe' "${WORK}/out.log" && fail "bloc invalide"
teardown
}
test_check_reports_success_and_failure_with_reason () {
setup "check : RÉUSSI, ou ÉCHOUÉ avec la dernière ligne de sortie"
[[ $(node 'check "a" true') == "RÉUSSI: a" ]] || fail "succès : $(node 'check "a" true')"
[[ $(node 'check "b" sh -c "echo un; echo deux >&2; exit 3"') == "ÉCHOUÉ: b — deux" ]] || fail "échec : $(node 'check "b" sh -c "echo un; echo deux >&2; exit 3"')"
[[ $(node 'check "c" false') == "ÉCHOUÉ: c" ]] || fail "échec muet : $(node 'check "c" false')"
teardown
}
test_vm_fails_only_when_ssh_worked_and_command_failed () {
setup "vm_fails : réussit seulement si SSH marche et la commande échoue"
[[ $(node 'vm_ssh () { echo rc=1; }; vm_fails v 1.2.3.4 x && echo OUI') == "OUI" ]] || fail "commande échouée non reconnue"
[[ $(node 'vm_ssh () { echo rc=0; }; vm_fails v 1.2.3.4 x || echo NON') == *NON ]] || fail "commande réussie prise pour un échec"
[[ $(node 'vm_ssh () { return 255; }; vm_fails v 1.2.3.4 x || echo NON') == *NON ]] || fail "SSH en panne pris pour une isolation"
[[ $(node 'vm_ssh () { echo bizarre; }; vm_fails v 1.2.3.4 x || echo NON') == *NON ]] || fail "sortie inattendue acceptée"
teardown
}
test_route_via_matches_the_next_hop_exactly () {
setup "route_via : le next-hop exact, pas un préfixe"
[[ $(node 'vm_ssh () { echo "default via 10.220.1.1 dev ens3 proto dhcp"; }; route_via v ip default 10.220.1.1 && echo OUI') == OUI ]] || fail "next-hop exact refusé"
[[ $(node 'vm_ssh () { echo "default via 10.220.1.10 dev ens3"; }; route_via v ip default 10.220.1.1 || echo NON') == NON ]] || fail "10.220.1.10 pris pour 10.220.1.1"
teardown
}
test_wait_state () {
setup "wait_state : état atteint, erreur, suppression"
[[ $(node 'api_state () { echo running; }; wait_state vms/x running && echo OUI') == OUI ]] || fail "running"
local start=${SECONDS}
[[ $(node 'api_state () { echo error; }; wait_state vms/x running 2>&1 || echo NON') == *NON ]] || fail "error accepté"
(( SECONDS - start < 5 )) || fail "une ressource en error n'arrête pas l'attente immédiatement ($(( SECONDS - start )) s)"
[[ $(node 'api_state () { echo; }; wait_state vms/x deleted && echo OUI') == OUI ]] || fail "absence = supprimé"
teardown
}
for T in $(declare -F | awk '{print $3}' | grep '^test_'); do
"${T}"
done
echo "${PASS} réussi(s), ${FAIL} échec(s)"
[[ "${FAIL}" -eq 0 ]]

View file

@ -0,0 +1,34 @@
on hv1 <<'NODE'
two_image || { echo "ÉCHOUÉ: image compatible two"; exit 0; }
KEY=$(vm_key)
check "VPC vp-s1" vpc_create vp-s1 10.210.0.0/16
check "subnet sn-s1a" subnet_create sn-s1a vp-s1 2101 10.210.1.1 10.210.1.0/24
check "subnet sn-s1b" subnet_create sn-s1b vp-s1 2102 10.210.2.1 10.210.2.0/24
check "backend two : socket de contrôle de sn-s1a" test -S /run/two/dhcp/vp-s1_br-s1a.sock
check "backend two : socket de contrôle de sn-s1b" test -S /run/two/dhcp/vp-s1_br-s1b.sock
check "VM s1-a1, démarrée seule" vm_create s1-a1 sn-s1a 10.210.1.10 "${KEY}"
check "VM s1-a1 joignable" vm_wait vp-s1 10.210.1.10
check "VM s1-b1, démarrée seule" vm_create s1-b1 sn-s1b 10.210.2.10 "${KEY}"
check "VM s1-b1 joignable" vm_wait vp-s1 10.210.2.10
vm_create s1-a2 sn-s1a 10.210.1.11 "${KEY}" > /tmp/s1-a2.log 2>&1 & A=$!
vm_create s1-b2 sn-s1b 10.210.2.11 "${KEY}" > /tmp/s1-b2.log 2>&1 & B=$!
wait "${A}"; RA=$?
wait "${B}"; RB=$?
check "VM s1-a2, démarrée en même temps que s1-b2" test "${RA}" -eq 0
check "VM s1-b2, démarrée en même temps que s1-a2" test "${RB}" -eq 0
for vm in 10.210.1.10:10.210.1.1 10.210.1.11:10.210.1.1 10.210.2.10:10.210.2.1 10.210.2.11:10.210.2.1; do
ip=${vm%%:*}; gw=${vm#*:}
check "${ip} joignable" vm_wait vp-s1 "${ip}"
check "${ip} : adresse de son propre subnet" has_addr vp-s1 "${ip}"
check "${ip} : bail DHCP tenu par systemd-networkd" vm_ssh vp-s1 "${ip}" 'ls /run/systemd/netif/leases/ | grep -q .'
check "${ip} : route par défaut via ${gw}" route_via vp-s1 "${ip}" default "${gw}"
check "${ip} : route vers la VPC via ${gw}" route_via vp-s1 "${ip}" 10.210.0.0/16 "${gw}"
check "${ip} : route /32 vers les métadonnées via ${gw}" route_via vp-s1 "${ip}" 169.254.169.254 "${gw}"
done
mac () { vm_ssh vp-s1 "${1}" 'cat /sys/class/net/ens3/address'; }
A_MACS=$(printf '%s\n' "$(mac 10.210.1.10)" "$(mac 10.210.1.11)" | sort | tr '\n' ' ' | sed 's/ $//')
B_MACS=$(printf '%s\n' "$(mac 10.210.2.10)" "$(mac 10.210.2.11)" | sort | tr '\n' ' ' | sed 's/ $//')
info "MAC de sn-s1a : ${A_MACS} ; de sn-s1b : ${B_MACS}"
check "probe sn-s1a : uniquement les MAC de sn-s1a" test "$(dhcp_hosts vp-s1_br-s1a)" = "${A_MACS}"
check "probe sn-s1b : uniquement les MAC de sn-s1b" test "$(dhcp_hosts vp-s1_br-s1b)" = "${B_MACS}"
NODE

View file

@ -0,0 +1,26 @@
on hv1 <<'NODE'
two_image || { echo "ÉCHOUÉ: image compatible two"; exit 0; }
KEY=$(vm_key)
check "VPC vp-s2" vpc_create vp-s2 10.220.0.0/16
check "subnet sn-s2a, default_route absent" subnet_create sn-s2a vp-s2 2201 10.220.1.1 10.220.1.0/24
check "subnet sn-s2b, default_route et gateway 10.220.2.254" subnet_create sn-s2b vp-s2 2202 10.220.2.1 10.220.2.0/24 ',"default_route":true,"gateway":"10.220.2.254"'
check "VM s2-a" vm_create s2-a sn-s2a 10.220.1.10 "${KEY}"
check "VM s2-b" vm_create s2-b sn-s2b 10.220.2.10 "${KEY}"
check "s2-a joignable" vm_wait vp-s2 10.220.1.10
check "s2-b joignable" vm_wait vp-s2 10.220.2.10
check "sn-s2a : route par défaut via interface_ip 10.220.1.1" route_via vp-s2 10.220.1.10 default 10.220.1.1
check "sn-s2a : route vers la VPC via interface_ip" route_via vp-s2 10.220.1.10 10.220.0.0/16 10.220.1.1
check "sn-s2a : route /32 vers les métadonnées via interface_ip" route_via vp-s2 10.220.1.10 169.254.169.254 10.220.1.1
check "sn-s2b : route par défaut via la gateway 10.220.2.254" route_via vp-s2 10.220.2.10 default 10.220.2.254
check "sn-s2b : route vers la VPC via interface_ip, pas via la gateway" route_via vp-s2 10.220.2.10 10.220.0.0/16 10.220.2.1
check "sn-s2b : route /32 vers les métadonnées via interface_ip" route_via vp-s2 10.220.2.10 169.254.169.254 10.220.2.1
check "suppression de s2-a" vm_delete s2-a
check "suppression de sn-s2a" api_delete subnets/sn-s2a
check "sn-s2a supprimé" wait_state subnets/sn-s2a deleted
check "sn-s2a recréé" subnet_create sn-s2a vp-s2 2201 10.220.1.1 10.220.1.0/24
check "VM s2-a2 sur le subnet recréé" vm_create s2-a2 sn-s2a 10.220.1.11 "${KEY}"
check "s2-a2 joignable" vm_wait vp-s2 10.220.1.11
check "après recréation : route par défaut via interface_ip 10.220.1.1" route_via vp-s2 10.220.1.11 default 10.220.1.1
check "après recréation : route vers la VPC via interface_ip" route_via vp-s2 10.220.1.11 10.220.0.0/16 10.220.1.1
check "après recréation : route /32 vers les métadonnées" route_via vp-s2 10.220.1.11 169.254.169.254 10.220.1.1
NODE

View file

@ -0,0 +1,16 @@
on hv1 <<'NODE'
two_image || { echo "ÉCHOUÉ: image compatible two"; exit 0; }
KEY=$(vm_key)
check "VPC vp-s3a" vpc_create vp-s3a 10.230.0.0/16
check "VPC vp-s3b" vpc_create vp-s3b 10.231.0.0/16
check "subnet sn-s3a" subnet_create sn-s3a vp-s3a 2301 10.230.1.1 10.230.1.0/24
check "subnet sn-s3b" subnet_create sn-s3b vp-s3b 2302 10.231.1.1 10.231.1.0/24
check "VM s3-x dans vp-s3a" vm_create s3-x sn-s3a 10.230.1.10 "${KEY}"
check "VM s3-y dans vp-s3b" vm_create s3-y sn-s3b 10.231.1.10 "${KEY}"
check "s3-x joignable depuis sa VPC" vm_wait vp-s3a 10.230.1.10
check "s3-y joignable depuis sa VPC (témoin : la cible est vivante)" vm_wait vp-s3b 10.231.1.10
check "s3-x joint sa passerelle (témoin)" vm_ssh vp-s3a 10.230.1.10 'ping -c 2 -W 2 10.230.1.1'
check "s3-x ne joint pas s3-y en ICMP" vm_fails vp-s3a 10.230.1.10 'ping -c 2 -W 2 10.231.1.10'
check "s3-x n'ouvre pas de connexion TCP vers s3-y:22" vm_fails vp-s3a 10.230.1.10 'timeout 5 bash -c "</dev/tcp/10.231.1.10/22"'
check "s3-y ne joint pas s3-x en ICMP" vm_fails vp-s3b 10.231.1.10 'ping -c 2 -W 2 10.230.1.10'
NODE

View file

@ -0,0 +1,25 @@
on hv1 <<'NODE'
two_image || { echo "ÉCHOUÉ: image compatible two (hv1)"; exit 0; }
KEY=$(vm_key)
check "hv1 : VPC vp-s4" vpc_create vp-s4 10.240.0.0/16
check "hv1 : subnet sn-s4" subnet_create sn-s4 vp-s4 2401 10.240.1.1 10.240.1.0/24
check "hv1 : vxlan-2401 porte l'adresse VTEP locale 192.168.14.11" sh -c "ip -d link show vxlan-2401 | grep -q 'local 192.168.14.11 '"
check "hv1 : VM s4-p" vm_create s4-p sn-s4 10.240.1.10 "${KEY}"
NODE
on hv2 <<'NODE'
two_image || { echo "ÉCHOUÉ: image compatible two (hv2)"; exit 0; }
check "hv2 : VPC vp-s4" vpc_create vp-s4 10.240.0.0/16
check "hv2 : subnet sn-s4" subnet_create sn-s4 vp-s4 2401 10.240.1.1 10.240.1.0/24
check "hv2 : vxlan-2401 porte l'adresse VTEP locale 192.168.14.12" sh -c "ip -d link show vxlan-2401 | grep -q 'local 192.168.14.12 '"
check "hv2 : VM s4-q" vm_create s4-q sn-s4 10.240.1.11
NODE
on hv1 <<'NODE'
check "s4-p joignable" vm_wait vp-s4 10.240.1.10
for i in $(seq 1 30); do vtysh -c "show evpn vni 2401" | grep -q 192.168.14.12 && break; sleep 2; done
check "hv1 : VTEP distant 192.168.14.12 appris par EVPN pour la VNI 2401" sh -c "vtysh -c 'show evpn vni 2401' | grep -q '192.168.14.12'"
check "hv1 : entrée d'inondation vers 192.168.14.12" sh -c "bridge fdb show dev vxlan-2401 | grep -q '00:00:00:00:00:00 dst 192.168.14.12'"
for i in $(seq 1 24); do vm_ssh vp-s4 10.240.1.10 'ping -c 1 -W 2 10.240.1.11' >/dev/null 2>&1 && break; sleep 5; done
check "s4-p (hv1) joint s4-q (hv2)" vm_ssh vp-s4 10.240.1.10 'ping -c 3 -W 2 10.240.1.11'
check "trame pleine taille : 1472 octets en -M do" vm_ssh vp-s4 10.240.1.10 'ping -M do -s 1472 -c 2 -W 2 10.240.1.11'
check "1473 octets refusés localement (MTU 1500)" vm_grep vp-s4 10.240.1.10 'ping -M do -s 1473 -c 1 -W 2 10.240.1.11' 'message too long'
NODE

View file

@ -0,0 +1,23 @@
on hv1 <<'NODE'
two_image || { echo "ÉCHOUÉ: image compatible two (hv1)"; exit 0; }
KEY=$(vm_key)
check "hv1 : VPC vp-s5a" vpc_create vp-s5a 10.245.0.0/16
check "hv1 : subnet sn-s5a (VNI 2501)" subnet_create sn-s5a vp-s5a 2501 10.245.1.1 10.245.1.0/24
check "hv1 : VM s5-r dans vp-s5a" vm_create s5-r sn-s5a 10.245.1.10 "${KEY}"
NODE
on hv2 <<'NODE'
two_image || { echo "ÉCHOUÉ: image compatible two (hv2)"; exit 0; }
check "hv2 : VPC vp-s5a" vpc_create vp-s5a 10.245.0.0/16
check "hv2 : subnet sn-s5a (VNI 2501)" subnet_create sn-s5a vp-s5a 2501 10.245.1.1 10.245.1.0/24
check "hv2 : VM s5-t dans vp-s5a (témoin)" vm_create s5-t sn-s5a 10.245.1.12
check "hv2 : VPC vp-s5b, même plage" vpc_create vp-s5b 10.245.0.0/16
check "hv2 : subnet sn-s5b (VNI 2502), même plage" subnet_create sn-s5b vp-s5b 2502 10.245.1.1 10.245.1.0/24
check "hv2 : VM s5-s dans vp-s5b" vm_create s5-s sn-s5b 10.245.1.11
NODE
on hv1 <<'NODE'
check "s5-r joignable" vm_wait vp-s5a 10.245.1.10
for i in $(seq 1 24); do vm_ssh vp-s5a 10.245.1.10 'ping -c 1 -W 2 10.245.1.12' >/dev/null 2>&1 && break; sleep 5; done
check "s5-r joint s5-t, même VPC sur hv2 (témoin)" vm_ssh vp-s5a 10.245.1.10 'ping -c 3 -W 2 10.245.1.12'
check "s5-r ne joint pas s5-s, autre VPC sur hv2, même plage" vm_fails vp-s5a 10.245.1.10 'ping -c 3 -W 2 10.245.1.11'
check "aucune entrée ARP résolue pour 10.245.1.11 dans s5-r" vm_fails vp-s5a 10.245.1.10 'ip neigh show 10.245.1.11 | grep -q lladdr'
NODE

View file

@ -0,0 +1,36 @@
on hv1 <<'NODE'
check "prérequis : VM s4-p de S4 en marche" test "$(api_state vms/s4-p)" = running
check "session EVPN de hv1 établie avant l'arrêt" sh -c "vtysh -c 'show bgp neighbors 10.255.255.1' | grep -q 'BGP state = Established'"
check "prérequis : s4-p joint s4-q" vm_ssh vp-s4 10.240.1.10 'ping -c 2 -W 2 10.240.1.11'
vm_ssh vp-s4 10.240.1.10 'rm -f /tmp/s6.log; nohup ping -D -i 0.2 10.240.1.11 > /tmp/s6.log 2>&1 &'
sleep 5
NODE
on rr1 <<'NODE'
date +%s > /tmp/s6.stop
check "arrêt de FRR sur le route reflector" systemctl stop frr
NODE
sleep 30
on hv1 <<'NODE'
recent () { vm_ssh vp-s4 10.240.1.10 "now=\$(date +%s); awk -F'[][]' -v now=\$now '/bytes from/ && \$2 > now - ${1}' /tmp/s6.log | wc -l"; }
check "session EVPN de hv1 tombée" sh -c "! vtysh -c 'show bgp neighbors 10.255.255.1' | grep -q 'BGP state = Established'"
info "30 s après l'arrêt du route reflector : $(recent 10) réponses dans les 10 dernières secondes (50 si le trafic passe intégralement)"
info "VTEP distant encore connu : $(vtysh -c 'show evpn vni 2401' | grep -c 192.168.14.12) ; entrée d'inondation : $(bridge fdb show dev vxlan-2401 | grep -c '00:00:00:00:00:00 dst')"
sleep 60
info "90 s après l'arrêt : $(recent 10) réponses dans les 10 dernières secondes"
NODE
on rr1 <<'NODE'
check "redémarrage de FRR sur le route reflector" systemctl start frr
date +%s > /tmp/s6.start
NODE
on hv1 <<'NODE'
recent () { vm_ssh vp-s4 10.240.1.10 "now=\$(date +%s); awk -F'[][]' -v now=\$now '/bytes from/ && \$2 > now - ${1}' /tmp/s6.log | wc -l"; }
start=$(date +%s)
for i in $(seq 1 90); do
vtysh -c "show evpn vni 2401" | grep -q 192.168.14.12 && [ "$(recent 3)" -gt 0 ] && break
sleep 2
done
info "retour du VTEP distant et du trafic $(( $(date +%s) - start )) s après le redémarrage de FRR"
check "après le retour du route reflector : VTEP distant réappris" sh -c "vtysh -c 'show evpn vni 2401' | grep -q 192.168.14.12"
check "après le retour du route reflector : le trafic passe" test "$(recent 5)" -gt 0
vm_ssh vp-s4 10.240.1.10 'pkill -x ping' || true
NODE