lib.sh 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. #!/usr/bin/env bash
  2. KUBECONFIG="kind.yaml"
  3. KIND_CLUSTER="kind-cluster-kilo"
  4. KIND_BINARY="${KIND_BINARY:-kind}"
  5. KUBECTL_BINARY="${KUBECTL_BINARY:-kubectl}"
  6. KGCTL_BINARY="${KGCTL_BINARY:-kgctl}"
  7. KILO_IMAGE="${KILO_IMAGE:-squat/kilo}"
  8. retry() {
  9. local COUNT="${1:-10}"
  10. local SLEEP="${2:-5}"
  11. local ERROR=$3
  12. [ -n "$ERROR" ] && ERROR="$ERROR "
  13. shift 3
  14. for c in $(seq 1 "$COUNT"); do
  15. if "$@"; then
  16. return 0
  17. else
  18. printf "%s(attempt %d/%d)\n" "$ERROR" "$c" "$COUNT" | color "$YELLOW" 1>&2
  19. if [ "$c" != "$COUNT" ]; then
  20. printf "retrying in %d seconds...\n" "$SLEEP" | color "$YELLOW" 1>&2
  21. sleep "$SLEEP"
  22. fi
  23. fi
  24. done
  25. return 1
  26. }
  27. _not() {
  28. if "$@"; then
  29. return 1
  30. fi
  31. return 0
  32. }
  33. # _kubectl is a helper that calls kubectl with the --kubeconfig flag.
  34. _kubectl() {
  35. $KUBECTL_BINARY --kubeconfig="$KUBECONFIG" "$@"
  36. }
  37. # _kgctl is a helper that calls kgctl with the --kubeconfig flag.
  38. _kgctl() {
  39. $KGCTL_BINARY --kubeconfig="$KUBECONFIG" "$@"
  40. }
  41. # _kind is a helper that calls kind with the --kubeconfig flag.
  42. _kind() {
  43. $KIND_BINARY --kubeconfig="$KUBECONFIG" "$@"
  44. }
  45. # shellcheck disable=SC2120
  46. build_kind_config() {
  47. local WORKER_COUNT="${1:-0}"
  48. export API_SERVER_PORT="${2:-6443}"
  49. export POD_SUBNET="${3:-10.42.0.0/16}"
  50. export SERVICE_SUBNET="${4:-10.43.0.0/16}"
  51. export WORKERS=""
  52. local i=0
  53. while [ "$i" -lt "$WORKER_COUNT" ]; do
  54. WORKERS="$(printf "%s\n- role: worker" "$WORKERS")"
  55. ((i++))
  56. done
  57. envsubst < ./kind-config.yaml
  58. unset API_SERVER_PORT POD_SUBNET SERVICE_SUBNET WORKERS
  59. }
  60. create_interface() {
  61. docker run -d --name="$1" --rm --network=host --cap-add=NET_ADMIN --device=/dev/net/tun -v /var/run/wireguard:/var/run/wireguard -e WG_LOG_LEVEL=debug masipcat/wireguard-go:0.0.20230223 wireguard-go --foreground "$1"
  62. }
  63. delete_interface() {
  64. docker rm --force "$1"
  65. }
  66. create_peer() {
  67. cat <<EOF | _kubectl apply -f -
  68. apiVersion: kilo.squat.ai/v1alpha1
  69. kind: Peer
  70. metadata:
  71. name: $1
  72. spec:
  73. allowedIPs:
  74. - $2
  75. persistentKeepalive: $3
  76. publicKey: $4
  77. EOF
  78. }
  79. delete_peer() {
  80. _kubectl delete peer "$1"
  81. }
  82. is_ready() {
  83. for pod in $(_kubectl -n "$1" get pods -o name -l "$2"); do
  84. if ! _kubectl -n "$1" get "$pod" | tail -n 1 | grep -q Running; then
  85. return 1;
  86. fi
  87. done
  88. return 0
  89. }
  90. # Returns non zero if one pod of the given name in the given namespace is not ready.
  91. block_until_ready_by_name() {
  92. block_until_ready "$1" "app.kubernetes.io/name=$2"
  93. }
  94. # Blocks until all pods of a deployment are ready.
  95. block_until_ready() {
  96. retry 30 5 "some $2 pods are not ready yet" is_ready "$1" "$2"
  97. }
  98. # create_cluster launches a kind cluster and deploys Kilo, Adjacency, and a helper with curl.
  99. create_cluster() {
  100. # shellcheck disable=SC2119
  101. local CONFIG="${1:-$(build_kind_config)}"
  102. _kind delete clusters $KIND_CLUSTER > /dev/null
  103. # Create the kind cluster.
  104. _kind create cluster --name $KIND_CLUSTER --config <(echo "$CONFIG")
  105. # Load the Kilo image into kind.
  106. docker tag "$KILO_IMAGE" squat/kilo:test
  107. # This command does not accept the --kubeconfig flag, so call the command directly.
  108. $KIND_BINARY load docker-image squat/kilo:test --name $KIND_CLUSTER
  109. # Create the kubeconfig secret.
  110. _kubectl create secret generic kubeconfig --from-file=kubeconfig="$KUBECONFIG" -n kube-system
  111. # Apply Kilo the the cluster.
  112. _kubectl apply -f ../manifests/crds.yaml
  113. _kubectl apply -f kilo-kind-userspace.yaml
  114. if ! block_until_ready_by_name kube-system kilo-userspace; then return 1; fi
  115. _kubectl wait nodes --all --for=condition=Ready
  116. # Wait for CoreDNS.
  117. block_until_ready kube_system k8s-app=kube-dns
  118. # Ensure the curl helper is not scheduled on a control-plane node.
  119. _kubectl apply -f helper-curl.yaml
  120. block_until_ready_by_name default curl || return 1
  121. _kubectl taint node $KIND_CLUSTER-control-plane node-role.kubernetes.io/control-plane:NoSchedule-
  122. _kubectl apply -f https://raw.githubusercontent.com/kilo-io/adjacency/main/example.yaml
  123. block_until_ready_by_name default adjacency
  124. }
  125. delete_cluster () {
  126. _kind delete clusters $KIND_CLUSTER
  127. }
  128. curl_pod() {
  129. _kubectl get pods -l app.kubernetes.io/name=curl -o name | xargs -I{} "$KUBECTL_BINARY" --kubeconfig="$KUBECONFIG" exec {} -- /usr/bin/curl "$@"
  130. }
  131. check_ping() {
  132. local LOCAL
  133. while [ $# -gt 0 ]; do
  134. case $1 in
  135. --local)
  136. LOCAL=true
  137. ;;
  138. esac
  139. shift
  140. done
  141. for ip in $(_kubectl get pods -l app.kubernetes.io/name=adjacency -o jsonpath='{.items[*].status.podIP}'); do
  142. if [ -n "$LOCAL" ]; then
  143. ping=$(curl -m 1 -s http://"$ip":8080/ping)
  144. else
  145. ping=$(curl_pod -m 1 -s http://"$ip":8080/ping)
  146. fi
  147. if [ "$ping" = "pong" ]; then
  148. echo "successfully pinged $ip"
  149. else
  150. printf 'failed to ping %s; expected "pong" but got "%s"\n' "$ip" "$ping"
  151. return 1
  152. fi
  153. done
  154. return 0
  155. }
  156. check_adjacent() {
  157. curl_pod adjacency:8080/?format=fancy
  158. [ "$(curl_pod -m 1 -s adjacency:8080/?format=json | jq '.[].latencies[].ok' | grep -c true)" -eq $(($1*$1)) ]
  159. }
  160. check_peer() {
  161. local INTERFACE=$1
  162. local PEER=$2
  163. local ALLOWED_IP=$3
  164. local GRANULARITY=$4
  165. create_interface "$INTERFACE"
  166. docker run --rm leonnicolas/wg-tools wg genkey > "$INTERFACE"
  167. assert "create_peer $PEER $ALLOWED_IP 10 $(docker run --rm --entrypoint=/bin/sh -v "$PWD/$INTERFACE":/key leonnicolas/wg-tools -c 'cat /key | wg pubkey')" "should be able to create Peer"
  168. assert "_kgctl showconf peer $PEER --mesh-granularity=$GRANULARITY > $PEER.ini" "should be able to get Peer configuration"
  169. assert "docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/usr/bin/wg -v /var/run/wireguard:/var/run/wireguard -v $PWD/$PEER.ini:/peer.ini leonnicolas/wg-tools setconf $INTERFACE /peer.ini" "should be able to apply configuration from kgctl"
  170. docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/usr/bin/wg -v /var/run/wireguard:/var/run/wireguard -v "$PWD/$INTERFACE":/key leonnicolas/wg-tools set "$INTERFACE" private-key /key
  171. docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/sbin/ip leonnicolas/wg-tools address add "$ALLOWED_IP" dev "$INTERFACE"
  172. docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/sbin/ip leonnicolas/wg-tools link set "$INTERFACE" up
  173. docker run --rm --network=host --cap-add=NET_ADMIN --entrypoint=/sbin/ip leonnicolas/wg-tools route add 10.42/16 dev "$INTERFACE"
  174. assert "retry 10 5 '' check_ping --local" "should be able to ping Pods from host"
  175. assert_equals "$(_kgctl showconf peer "$PEER")" "$(_kgctl showconf peer "$PEER" --mesh-granularity="$GRANULARITY")" "kgctl should be able to auto detect the mesh granularity"
  176. rm "$INTERFACE" "$PEER".ini
  177. delete_peer "$PEER"
  178. delete_interface "$INTERFACE"
  179. }