diff --git a/deploy/stacks/self-managed/.gitignore b/deploy/stacks/self-managed/.gitignore new file mode 100644 index 000000000..89f9ac04a --- /dev/null +++ b/deploy/stacks/self-managed/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/tests/bdd/features/single-cluster-helmfile.feature b/tests/bdd/features/single-cluster-helmfile.feature index 7efeccd26..a85fdbe7c 100644 --- a/tests/bdd/features/single-cluster-helmfile.feature +++ b/tests/bdd/features/single-cluster-helmfile.feature @@ -20,10 +20,11 @@ Feature: Install a local single-cluster NVCF stack with Helmfile # ingress.gatewayApi.*). The Background only overlays the operator-specific # values that vary per NGC org and pull-secret name. And I update yaml file "deploy/stacks/self-managed/environments/local-bdd.yaml" with keys: - | global.imagePullSecrets[0].name | nvcr-pull-secret | - | global.helm.sources.repository | ${SAMPLE_NGC_ORG}/${SAMPLE_NGC_TEAM} | - | global.image.repository | ${SAMPLE_NGC_ORG}/${SAMPLE_NGC_TEAM} | - | observability.profile | disabled | + | global.imagePullSecrets[0].name | nvcr-pull-secret | + | global.helm.sources.repository | ${SAMPLE_NGC_ORG}/${SAMPLE_NGC_TEAM} | + | global.image.repository | ${SAMPLE_NGC_ORG}/${SAMPLE_NGC_TEAM} | + | api.env.NVCF_SIDECARS_LLM_ROUTER_CLIENT_IMAGE | nvcr.io/${SAMPLE_NGC_ORG}/${SAMPLE_NGC_TEAM}/stargate-client:0.2.0 | + | observability.profile | disabled | And I copy the file "tests/bdd/fixtures/nvcf-compute-plane-local-bdd.yaml" to "deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml" And I update yaml file "deploy/stacks/nvcf-compute-plane/environments/local-bdd.yaml" with keys: | global.imagePullSecrets[0].name | nvcr-pull-secret | @@ -180,6 +181,14 @@ Feature: Install a local single-cluster NVCF stack with Helmfile Then the command exit code should be 0 And the command output should contain "bdd-echo" + # Remove the deployment: the local sizing cannot hold every + # scenario's deployment at once. + When I run command: + """ + ${NVCF_CLI} --config ${REPO_ROOT}/tests/bdd/fixtures/nvcf-cli-local.yaml function delete --deployment-only + """ + Then the command exit code should be 0 + @function-lifecycle @grpc Scenario: Operator creates, deploys, and invokes the gRPC Load Tester Supreme sample function When I run command: @@ -206,3 +215,62 @@ Feature: Install a local single-cluster NVCF stack with Helmfile """ Then the command exit code should be 0 And the command output should contain "bdd-grpc-echo" + + # Free the GPU node for the LLM scenario. + When I run command: + """ + ${NVCF_CLI} --config ${REPO_ROOT}/tests/bdd/fixtures/nvcf-cli-local.yaml function delete --deployment-only + """ + Then the command exit code should be 0 + + # Proves the serve path the @llm-gateway scenario only installs: + # LLM-type functions route through llm-api-gateway and + # llm-request-router. The CLI maps invocation.localhost to the + # llm.localhost gateway host and rewrites the body model to + # /. Depends on the earlier install and + # registration scenarios; not a standalone tag target. + @llm-function-type + Scenario: Operator creates, deploys, and invokes an LLM-type OpenAI-compatible sample function + When I run command: + """ + ${NVCF_CLI} --config ${REPO_ROOT}/tests/bdd/fixtures/nvcf-cli-local.yaml function create --name bdd-openai-compatible-sample --image nvcr.io/${SAMPLE_NGC_ORG}/${SAMPLE_NGC_TEAM}/nvcf-openai-compatible-sample:local --function-type LLM --inference-url /v1/chat/completions --inference-port 8000 --health-uri /health --health-port 8000 --health-timeout PT30S --llm-model 'name=openai-compatible-sample,uris=/v1/chat/completions|/v1/embeddings,routingMethod=round_robin' + """ + Then the command exit code should be 0 + + When I run command: + """ + ${NVCF_CLI} --config ${REPO_ROOT}/tests/bdd/fixtures/nvcf-cli-local.yaml function deploy create --gpu H100 --instance-type NCP.GPU.H100_8x --backend ncp-local --regions us-west-1 --min-instances 1 --max-instances 1 --timeout 900 + """ + Then the command exit code should be 0 + + When I run command: + """ + ${NVCF_CLI} --config ${REPO_ROOT}/tests/bdd/fixtures/nvcf-cli-local.yaml api-key generate --description bdd-openai-compatible-sample --for function --scopes invoke_function,list_functions,queue_details,list_functions_details + """ + Then the command exit code should be 0 + + # The gateway answers synchronously (no queue polling). The + # sample always returns its fixed load-testing message. + When I run command: + """ + ${NVCF_CLI} --config ${REPO_ROOT}/tests/bdd/fixtures/nvcf-cli-local.yaml function invoke --inference-url /v1/chat/completions --model-name openai-compatible-sample --request-body '{"messages":[{"role":"user","content":"bdd-llm-echo"}]}' --timeout 120 + """ + Then the command exit code should be 0 + And the command output should contain "chat.completion" + And the command output should contain "fixed 128-byte response" + + # curl reports only the status code so the assertion cannot + # match response-body noise. + When I run command: + """ + curl -s -o /dev/null -w "%{http_code}" -X POST http://llm.localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"unauthenticated/check","messages":[]}' + """ + Then the command exit code should be 0 + And the command output should contain "401" + + # Leave the GPU capacity free, same as the echo scenarios. + When I run command: + """ + ${NVCF_CLI} --config ${REPO_ROOT}/tests/bdd/fixtures/nvcf-cli-local.yaml function delete --deployment-only + """ + Then the command exit code should be 0 diff --git a/tests/bdd/fixtures/self-managed-local-bdd.yaml b/tests/bdd/fixtures/self-managed-local-bdd.yaml index a746c6ed6..b4ed793ca 100644 --- a/tests/bdd/fixtures/self-managed-local-bdd.yaml +++ b/tests/bdd/fixtures/self-managed-local-bdd.yaml @@ -50,9 +50,13 @@ openbao: # Pylon exits at startup when the API advertises its default HTTPS dummy OTLP # endpoint because the current binary has no OpenTelemetry TLS provider. Keep # worker telemetry on HTTP in this fixture until Pylon supports HTTPS OTLP. +# +# The LLM router client sidecar image defaults to "dummy" in the +# self-managed API profile; the Background overlays the placeholders. api: env: MANAGEMENT_OTLP_TRACING_ENDPOINT: http://dummy:8282 + NVCF_SIDECARS_LLM_ROUTER_CLIENT_IMAGE: nvcr.io/REPLACE_WITH_SAMPLE_NGC_ORG/REPLACE_WITH_SAMPLE_NGC_TEAM/stargate-client:0.2.0 # LLM gateway talks to nvcf-api over plaintext gRPC in local dev # (api.nvcf.svc.cluster.local:9090 has no TLS). diff --git a/tests/bdd/godog_test.go b/tests/bdd/godog_test.go index 91a8571f2..7b31c14cc 100644 --- a/tests/bdd/godog_test.go +++ b/tests/bdd/godog_test.go @@ -411,6 +411,18 @@ func TestSingleClusterHelmfileFeatureFileWiresToSteps(t *testing.T) { ExitCode: 0, Stdout: "Function invocation completed!\n\nResponse:\n{\"message\":\"bdd-grpc-echo\"}\n", }, + "/usr/bin/nvcf-cli --config /repo-root-placeholder/tests/bdd/fixtures/nvcf-cli-local.yaml function invoke" + + " --inference-url /v1/chat/completions --model-name openai-compatible-sample" + + " --request-body '{\"messages\":[{\"role\":\"user\",\"content\":\"bdd-llm-echo\"}]}' --timeout 120": { + ExitCode: 0, + Stdout: "Function invocation completed!\n\nResponse:\n" + + `{"object":"chat.completion","choices":[{"message":{"content":"This is a fixed 128-byte response from an NVCF-hosted OpenAI-compatible sample, used for load testing and throughput benchmarks."}}]}` + + "\n", + }, + `curl -s -o /dev/null -w "%{http_code}" -X POST http://llm.localhost:8080/v1/chat/completions -H "Content-Type: application/json" -d '{"model":"unauthenticated/check","messages":[]}'`: { + ExitCode: 0, + Stdout: "401", + }, // Conflict precheck: feature asserts the conflicting // multi-cluster control-plane is absent. "k3d cluster get ncp-local-cp": {ExitCode: 1}, @@ -462,6 +474,22 @@ func TestSingleClusterHelmfileFeatureFileWiresToSteps(t *testing.T) { if !commandRanThatContains(suite.Runner.(*fakeRunner).runs, "api-key generate --description bdd-grpc-load-tester-supreme --for function") { t.Fatal("gRPC sample function API key was not generated for the function service") } + if !commandRanThatContainsAll(suite.Runner.(*fakeRunner).runs, + "function create --name bdd-openai-compatible-sample", + "nvcf-openai-compatible-sample:local", + "--function-type LLM", + "--llm-model") { + t.Fatal("LLM sample function was not created with the LLM function type and model config") + } + if !commandRanThatContains(suite.Runner.(*fakeRunner).runs, "function invoke --inference-url /v1/chat/completions --model-name openai-compatible-sample") { + t.Fatal("LLM function invoke CLI command was never invoked") + } + if !commandRanThatContains(suite.Runner.(*fakeRunner).runs, "function delete --deployment-only") { + t.Fatal("function deployment cleanup was never invoked") + } + if !commandRanThatContains(suite.Runner.(*fakeRunner).runs, "http://llm.localhost:8080/v1/chat/completions") { + t.Fatal("unauthenticated LLM gateway check was never invoked") + } } // TestObservabilityControlFeatureFileWiresToSteps runs the live-install