diff --git a/demos/counter/README.md b/demos/counter/README.md index a6784fe5c..b10c28741 100644 --- a/demos/counter/README.md +++ b/demos/counter/README.md @@ -35,6 +35,22 @@ This command will: - Create the `WorkerPool` and `ActorTemplate`. - Wait until the template is ready. +#### Outbound connectivity probe + +By default the counter fetches `https://www.google.com/generate_204` every 10 +seconds and logs the result, showing that the actor's egress path keeps working +across suspends and resumes. Override or disable the target at deploy time: + +```bash +# Probe a custom URL +OUTBOUND_PROBE_URL=http://example.com/health ./hack/install-ate.sh --deploy-demo-counter + +# Disable the probe (e.g. air-gapped clusters) +OUTBOUND_PROBE_URL="" ./hack/install-ate.sh --deploy-demo-counter +``` + +The same variable works for the micro-VM variant (`./hack/run-microvm-demo.sh`). + ### 2. Create a Counter Actor Actors live in an **atespace**, which must exist before you create actors in it. Create one (e.g., `demo`), then create the counter actor with a chosen ID (e.g., `my-counter-1`): diff --git a/demos/counter/counter-microvm.yaml.tmpl b/demos/counter/counter-microvm.yaml.tmpl index e626c94ef..4a8a1566b 100644 --- a/demos/counter/counter-microvm.yaml.tmpl +++ b/demos/counter/counter-microvm.yaml.tmpl @@ -117,6 +117,9 @@ spec: containers: - name: counter image: ko://github.com/agent-substrate/substrate/demos/counter + command: + - /ko-app/counter +${OUTBOUND_PROBE_URL_ARG} readyz: httpGet: path: /readyz diff --git a/demos/counter/counter.go b/demos/counter/counter.go index 107bef68d..5bd0e5e6d 100644 --- a/demos/counter/counter.go +++ b/demos/counter/counter.go @@ -64,6 +64,7 @@ func main() { fileCounterDirectory := pflag.String("file-counter-directory", "/home/counter", "Directory for file counter") secondFileCounterDirectory := pflag.String("second-file-counter-directory", "", "Directory for a second file counter; empty disables it. Used to exercise an Actor with more than one durable volume") validateExistingFilePath := pflag.String("validate-existing-file-path", "", "Path to existing file to validate reading") + outboundProbeURL := pflag.String("outbound-probe-url", "", "URL fetched on every count tick to demonstrate outbound connectivity (e.g. https://www.google.com/generate_204); empty disables it") pflag.Parse() ctx := context.Background() @@ -138,13 +139,36 @@ func main() { slog.InfoContext(ctx, "Count", slog.Int("count", count), slog.String("fshash", hashRandomFile())) count++ + probeClient := &http.Client{Transport: &http.Transport{DisableKeepAlives: true}} for range time.Tick(10 * time.Second) { - // TODO: Test outbound connectivity by pinging google.com + logOutboundProbe(ctx, probeClient, *outboundProbeURL) slog.InfoContext(ctx, "Count", slog.Int("count", count), slog.String("fshash", hashRandomFile())) count++ } } +// A reachable server counts as connectivity, whatever its status code. +func logOutboundProbe(ctx context.Context, client *http.Client, url string) { + if url == "" { + return + } + probeCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + start := time.Now() + req, err := http.NewRequestWithContext(probeCtx, http.MethodGet, url, nil) + if err != nil { + slog.ErrorContext(ctx, "Outbound probe failed", slog.String("url", url), slog.Any("err", err)) + return + } + resp, err := client.Do(req) + if err != nil { + slog.ErrorContext(ctx, "Outbound probe failed", slog.String("url", url), slog.Any("err", err)) + return + } + resp.Body.Close() + slog.InfoContext(ctx, "Outbound probe succeeded", slog.String("url", url), slog.Int("status", resp.StatusCode), slog.Duration("elapsed", time.Since(start))) +} + func writeRandomFile() error { rf, err := os.Create("/random-content-file") if err != nil { diff --git a/demos/counter/counter.yaml.tmpl b/demos/counter/counter.yaml.tmpl index fa8801842..dcfe7a3de 100644 --- a/demos/counter/counter.yaml.tmpl +++ b/demos/counter/counter.yaml.tmpl @@ -45,6 +45,7 @@ spec: command: - /ko-app/counter ${VALIDATE_EXISTING_FILE_PATH_ARG} +${OUTBOUND_PROBE_URL_ARG} readyz: httpGet: path: /readyz diff --git a/hack/install-demo-counter.sh b/hack/install-demo-counter.sh index c2248271e..6cbead9da 100644 --- a/hack/install-demo-counter.sh +++ b/hack/install-demo-counter.sh @@ -48,10 +48,18 @@ demo-counter_deploy() { ext_vol_spec_cmd=("-e" "s|\${EXTERNAL_VOLUMES}| - name: external-data\n externalVolumeTemplate:\n capacity: 1Gi\n storageClassName: standard|g") fi + # On by default; OUTBOUND_PROBE_URL="" disables (e.g. air-gapped clusters). + local probe_url="${OUTBOUND_PROBE_URL-https://www.google.com/generate_204}" + local probe_cmd=("-e" "/\${OUTBOUND_PROBE_URL_ARG}/d") + if [[ -n "${probe_url}" ]]; then + probe_cmd=("-e" "s|\${OUTBOUND_PROBE_URL_ARG}| - --outbound-probe-url=${probe_url}|g") + fi + sed -e "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" \ "${validate_cmd[@]}" \ "${ext_vol_mount_cmd[@]}" \ "${ext_vol_spec_cmd[@]}" \ + "${probe_cmd[@]}" \ demos/counter/counter.yaml.tmpl \ | run_ko apply -f - @@ -71,6 +79,7 @@ demo-counter_delete() { delete_demo_actors ate-demo-counter counter sed -e "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" \ -e "/\${VALIDATE_EXISTING_FILE_PATH_ARG}/d" \ + -e "/\${OUTBOUND_PROBE_URL_ARG}/d" \ -e "/\${EXTERNAL_VOLUME_MOUNTS}/d" \ -e "/\${EXTERNAL_VOLUMES}/d" \ demos/counter/counter.yaml.tmpl \ diff --git a/hack/run-microvm-demo.sh b/hack/run-microvm-demo.sh index 628877f4e..d3f309a8e 100755 --- a/hack/run-microvm-demo.sh +++ b/hack/run-microvm-demo.sh @@ -31,6 +31,9 @@ # OUT asset dir (default: $PWD/bin/microvm-assets/$ARCH, gitignored). # ATE_INSTALL_KIND "true" for the kind path (stage assets to rustfs + install-ate-kind.sh); # default false uploads assets to GCS + uses install-ate.sh. +# OUTBOUND_PROBE_URL +# URL the counter probes every tick (default: google generate_204); +# set to "" to disable, e.g. on air-gapped clusters. set -o errexit -o nounset -o pipefail @@ -149,8 +152,15 @@ log "Applying the counter-microvm demo manifest..." # (cloud-hypervisor/kernel/rootfs/config, plus virtiofsd on amd64 where upstream # publishes a prebuilt) keep their committed, reproducible per-arch shas. VIRTIOFSD_SHA256="$(sha256sum "${OUT}/virtiofsd" | awk '{print $1}')" +# On by default; OUTBOUND_PROBE_URL="" disables (e.g. air-gapped clusters). +probe_url="${OUTBOUND_PROBE_URL-https://www.google.com/generate_204}" +probe_cmd=("-e" "/\${OUTBOUND_PROBE_URL_ARG}/d") +if [[ -n "${probe_url}" ]]; then + probe_cmd=("-e" "s|\${OUTBOUND_PROBE_URL_ARG}| - --outbound-probe-url=${probe_url}|g") +fi sed -e "s|\${BUCKET_NAME}|${BUCKET_NAME}|g" \ -e "s|\${VIRTIOFSD_SHA256}|${VIRTIOFSD_SHA256}|g" \ + "${probe_cmd[@]}" \ demos/counter/counter-microvm.yaml.tmpl \ | ./hack/run-tool.sh ko apply -f - ${KUBECTL_CONTEXT:+-- --context="${KUBECTL_CONTEXT}"}