Repository navigation
test(global-router): add a multi-cluster Global Discovery deploy test - #18161
atchernych wants to merge 2 commits into
Conversation
Run the most basic multi-cluster Global Discovery case on two vClusters after every merge. The hub runs the Global Discovery Service in registrations mode. The member runs a mocker DGD and its operator-managed DynamoPoolRelay, which registers the pool with the hub across the cluster boundary. The test checks that GET /pools lists the pool with its cluster ID, model, region and frontend URL, that heartbeats keep it listed, and that it leaves about one lease after the relay is deleted. post-merge-ci.yml builds the service image, runs the test in a host namespace of its own, and deletes both vClusters. The jobs are non-blocking while the multi-cluster pieces land. The service's Dockerfile takes DOCKER_PROXY, as the operator's does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
TEMPORARY: pr.yaml gets copies of the three post-merge jobs, so the test runs on its own pull request. Remove them before merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Anna Tchernych <atchernych@nvidia.com>
| name: Global Discovery Service | ||
| needs: [changed-files] | ||
| if: | | ||
| needs.changed-files.outputs.run_deploy_tests == 'true' && | ||
| needs.changed-files.outputs.deploy == 'true' | ||
| runs-on: dynamo-cpu-4gi-amd64 | ||
| continue-on-error: true | ||
| outputs: | ||
| image_tag: ${{ steps.build.outputs.image_tag }} | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| - name: Initialize Dynamo Builder | ||
| uses: $/.github/actions/init-dynamo-builder | ||
| with: | ||
| builder_name: ${{ needs.changed-files.outputs.builder_name }} | ||
| flavor: general | ||
| arch: linux/amd64 | ||
| namespace: ${{ vars.BUILDKIT_NAMESPACE }} | ||
| - name: Docker Login | ||
| uses: $/.github/actions/docker-login | ||
| with: | ||
| aws_default_region: ${{ vars.AWS_DEFAULT_REGION }} | ||
| ecr_registry: ${{ vars.ECR_REGISTRY }} | ||
| azure_acr_hostname: ${{ secrets.AZURE_ACR_HOSTNAME }} | ||
| azure_acr_user: ${{ secrets.AZURE_ACR_USER }} | ||
| azure_acr_password: ${{ secrets.AZURE_ACR_PASSWORD }} | ||
| - name: Build and push the Global Discovery Service image | ||
| id: build | ||
| shell: bash | ||
| env: | ||
| IMAGE: ${{ secrets.AZURE_ACR_HOSTNAME }}/${{ vars.ACR_REPOSITORY }} | ||
| IMAGE_TAG: ${{ github.sha }}-global-discovery-service | ||
| DOCKER_PROXY: ${{ vars.ECR_REGISTRY }}/dockerhub/ | ||
| run: | | ||
| set -euo pipefail | ||
| docker buildx build --push \ | ||
| --platform linux/amd64 \ | ||
| --build-arg "DOCKER_PROXY=${DOCKER_PROXY}" \ | ||
| -t "${IMAGE}:${IMAGE_TAG}" \ | ||
| -f container/Dockerfile.global-discovery-service . | ||
| # Only the tag crosses jobs: an output holding a secret is dropped. | ||
| echo "image_tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| deploy-test-global-discovery: |
❌ Dynamo PR CI failed — run 41948641293 (attempt 1) on
|
| Other | Jobs |
|---|---|
| Global Discovery Deploy Test | ❌ 1 |
Failure details
1 job failed: the Global Discovery Deploy Test could not create its second (member) vCluster because vCluster refuses to create more than one virtual cluster in the same host namespace.
❌ Global Discovery Deploy Test: vcluster create fatal: already a virtual cluster in namespace
Job: Global Discovery Deploy Test · Failed step: Create the member vCluster · Logs: gh run view --job 126994131613 -R ai-dynamo/dynamo --log-failed
Checking for existing vCluster ci-41948641293-gd-member in gh-id-41948641293-global-discovery-dt
No existing vCluster named ci-41948641293-gd-member found.
Using host Kubernetes v1.36.4; pinning vCluster guest to v1.36.4
+ vcluster create ci-41948641293-gd-member --namespace gh-id-41948641293-global-discovery-dt --connect=false --upgrade --set controlPlane.distro.k8s.enabled=true --set controlPlane.distro.k8s.version=v1.36.4
01:50:03 fatal there is already a virtual cluster in namespace gh-id-41948641293-global-discovery-dt; creating multiple virtual clusters inside the same namespace is not supported
+ exit 1
##[error]Process completed with exit code 1.
The hub vCluster ci-41948641293-gd-hub was created and the Dynamo platform installed in it earlier in the job; creating the member vCluster ci-41948641293-gd-member in the same host namespace gh-id-41948641293-global-discovery-dt then fails with vCluster's one-cluster-per-namespace restriction. The later diagnostics (127.0.0.1:8443 connection refused, missing Grove webhook Service) are follow-on noise from this failure.
For agents
{"pr": 18161, "run_id": 41948641293, "run_attempt": 1, "head_sha": "9cdec8f76b434b695b641c1ea6368789e3a68ed8",
"failures": [{"job": "Global Discovery Deploy Test", "job_id": 126994131613, "failed_step": "Create the member vCluster", "signature": "vcluster create: there is already a virtual cluster in namespace gh-id-41948641293-global-discovery-dt; creating multiple virtual clusters inside the same namespace is not supported", "tests": [], "log_cmd": "gh run view --job 126994131613 -R ai-dynamo/dynamo --log-failed"}]}Posted automatically by Devin for run 41948641293. Updated on every full-CI run of this PR.
Overview
Adds the most basic end-to-end test of multi-cluster Global Discovery, run after every merge. Two vClusters stand in for two clusters: the hub runs the Global Discovery Service, and the member runs a pool whose PoolRelay registers with it across the cluster boundary. The test checks that the hub lists the member's pool and drops it once the relay is gone.
The job is non-blocking while the multi-cluster pieces land, and the test may fail until they do.
Key Changes
tests/deploy/global_discovery/test_global_discovery.py:GET /poolson the hub must list the pool under the member's cluster ID, with the model, region and frontend URL the relay registered, and say the list is complete. Heartbeats must keep it listed for three leases, and it must leave about one lease after the relay is deleted.tests/deploy/global_discovery/cluster.py: kubectl bound to one cluster's kubeconfig, retried withvcluster_utils.retry_vcluster_api, and HTTP through a port-forward that reopens. Thedgd_utilshelpers serve only the cluster thatKUBECONFIGselects..github/workflows/post-merge-ci.yml:global-discovery-service-buildbuildscontainer/Dockerfile.global-discovery-service, which now takesDOCKER_PROXYas the operator's Dockerfile does.deploy-test-global-discoverycreates the hub and member vClusters, each with the operator, in a host namespace of their own, and runs the test.setup-dynamo-operatorandconnect-vclusteralways tunnel to127.0.0.1:8443and reuse a tunnel they find there, so the job closes the hub's tunnel before it creates the member and connects to the hub again on 8444.deploy-cleanup-global-discoverydeletes both vClusters and the namespace.continue-on-error,deploy-status-checkdoes not wait for them, and the Slack notice leaves out jobs namedGlobal Discovery..github/workflows/pr.yamlcarries copies of the three jobs, so the test runs on this PR. They come out before merge.Product gaps
The test works around these. Each needs its own fix.
DynamoPoolRelayhas no fields for--global-discovery-url,--routing-pool,--routing-pool-regionor--routing-pool-frontends(feat(pool-relay): register the routing pool with Global Discovery #18077), and the controller sets none of them. The test sets the matchingDYN_RELAY_*variables in the relay's pod template.lib/llm/src/pool_relay/routing_pool.rsrecords why it registers nothing, for example while the DGD's serving topology has no model, only inrouting_pool_unregistered_reason. That field is served by the relay's request-planehealthendpoint, not the system port's/health, so a pool that never registers leaves no trace in the relay's log.envFrom. Unless a frontend or worker container also setsDYN_DISCOVERY_BACKENDandDYN_KUBE_DISCOVERY_MODE, the relay is withdrawn asUnsupportedDiscovery. The examples loadhf-token-secretwithenvFrom(for exampleexamples/backends/vllm/deploy/agg.yaml), so a relay for one of them is refused. The test passes the token through asecretKeyRef.Validation
pr.yamljobs on this PR are its first run.pre-commitpasses. pytest collects the test, and the marker report finds no missing category for it. The DGD andDynamoPoolRelaymanifests validate against the operator's CRD schemas, and the kubectl retry and port-forward paths pass against a stubkubectl.Issues and Ongoing Work
pr.yamljobs.Pull Request Checklist
/pr-checklistskill.🤖 Generated with Claude Code