Skip to content

Commit f9385f1

Browse files
ironcommitmatthewgrossman
authored andcommitted
feat(jobs): add auth-aware E2E tests and job diagnostics infrastructure (#398)
Add a new E2E test suite (`test_jobs_auth.py`) that validates workspace isolation and principal propagation under an auth-enabled platform config. Introduce a reusable `diagnostics.py` module in the jobs controller layer to collect and log structured job/step/task state on errors, and wire it into the reconciler and scheduler for automatic debug-level diagnostics when steps transition to ERROR or encounter unexpected exceptions. Refactor `e2e/conftest.py` to support multiple running-services instances keyed by config hash, enabling per-test-module platform configs (e.g., `local-subprocess.yaml` with auth enabled) to coexist in a single session. Add a `local-subprocess.yaml` E2E config and extend `nmp_testing` utilities with `grant_workspace_role`, `unique_email`, and `TEST_ADMIN_EMAIL` helpers needed by the auth test scenarios. Signed-off-by: Ryan S <267728323+ironcommit@users.noreply.github.com> Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
1 parent ed04d87 commit f9385f1

59 files changed

Lines changed: 9947 additions & 326 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

commit.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
feat(jobs): add auth-aware E2E tests and job diagnostics infrastructure
2+
3+
Add a new E2E test suite (`test_jobs_auth.py`) that validates workspace
4+
isolation and principal propagation under an auth-enabled platform config.
5+
Introduce a reusable `diagnostics.py` module in the jobs controller layer
6+
to collect and log structured job/step/task state on errors, and wire it
7+
into the reconciler and scheduler for automatic debug-level diagnostics
8+
when steps transition to ERROR or encounter unexpected exceptions.
9+
10+
Refactor `e2e/conftest.py` to support multiple running-services instances
11+
keyed by config hash, enabling per-test-module platform configs (e.g.,
12+
`local-subprocess.yaml` with auth enabled) to coexist in a single session.
13+
Add a `local-subprocess.yaml` E2E config and extend `nmp_testing` utilities
14+
with `grant_workspace_role`, `unique_email`, and `TEST_ADMIN_EMAIL` helpers
15+
needed by the auth test scenarios.

container.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Just talking out loud here, so try to wrap my own head around this. Conceptually the jobs service is a "backend" (which is a type of execution), and a executor (an configured instance of a backend). "Profile" is a higher level concept that does fancy selection, and arguably it's just a useless layer of abstraction that confuses everyone. The idea was that some jobs need cpu, and some jobs need gpu, so why not create an abstraction layer that does the hard work of choosing for you. Or you can just use an executor as part of the job. Profiles introduces a category of compute: cpu, gpu, gpu_distributed, which are really just shortcuts for the backend you're looking for. So for customization you would select gpu_distributed, for eval: gpu. So I think the idea is that the job compiler chooses the category, and the platform config does the mapping to the executor. And you can pass a profile in with the job spec, which will tell the compiler which profile to use. So you end up having to map profiles to executors anyways. It makes me think that the services should be responsible for doing this mapping, not jobs. Ex: customizer configures the mapping. But having a central concept of "profile" means that every service does things the exact same way, which is nice. It just makes the responsibility unclear. In the future we might want to allow a plugin to define their own "provider", and allow other services to use it. Different providers having different configuration requirements, that map down to the execution back end job format. At this level, we are talking about job specs, and each service compiles their own spec down to the provider spec, which then selects the executor, and the job is submitted to the executor. And as part of the platform config, we have defaults for some of these things, which might include containers. For example, customizer chooses the image to use, depending on the type of customization requested. But for customizer, we could imagine a subprocess executor, with the customizer task image, and the behavior would be to call docker run .... So this is an appropriate translation for the subprocess executor. So the job is running as a subprocess, but we require a container, so we use docker (or podman), depending on how we the subprocess executor is configured to run containers. If the container is absent, then we just call the entrypoint in the workspace configured by the executor. The goal should be that we don't care what executor it's running on, the job spec is the same. But services will want some flexibility here to choose the right executor. For example, imagine a plugin has some dependency on a specific version of python, so we might want to defined an executor executes commands in the context of a specific venv when using the subprocess executor. And we can also provide a container for that plugin, which would execute the same command in a container. The platform shouldn't need to know anything about the venv, or the container, as this would be specific to the plugin. The plugin just selects the "provider", and the platform maps that to the correct executor. Now a plugin could define it's own provider, which will select the correct venv (either using subprocess or in a container). What this suggests to me is that the plugin needs more control over how jobs are mapped to executors, and which executors are configured beyond the rigid "provider" categories. For example, a plugin could define a venv for subprocess exec (typically for local dev), a container for production workloads, and a slurm script for a batch workflow specific to slurm backends.

docs/set-up/config-reference.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,8 @@ jobs:
422422
schedule_interval_seconds: 5
423423
# Register the subprocess/default execution profile. When unset, defaults to true for docker/none runtimes and false for kubernetes.
424424
enable_subprocess_executor:
425+
# Include raw job log lines in controller diagnostics snapshots. Disabled by default because job logs may contain secrets or PII. Enable only for local debugging or test environments. | default: False
426+
include_job_logs_in_diagnostics: false
425427
```
426428

427429
### `models`

e2e/configs/local-subprocess.yaml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Local E2E config for hosts without Docker.
2+
#
3+
# This keeps the explicit subprocess/default jobs profile required by
4+
# translate_cpu_container_steps_to_subprocess(), while avoiding the default
5+
# docker job backends that are derived from platform.runtime: "docker".
6+
7+
platform:
8+
runtime: "none"
9+
base_url: "http://0.0.0.0:8080"
10+
11+
service: {}
12+
13+
auth:
14+
enabled: false
15+
allow_unsigned_jwt: true
16+
policy_decision_point_provider: embedded
17+
policy_decision_point_base_url: "http://localhost:8080"
18+
policy_data_refresh_interval: 2
19+
bundle_cache_seconds: 15
20+
admin_email: "admin@example.com"
21+
22+
entities: {}
23+
24+
jobs:
25+
# Local E2E-only debugging aid. This may leak secrets or PII from job output,
26+
# so it must remain disabled in non-test configs.
27+
include_job_logs_in_diagnostics: true
28+
executors:
29+
- provider: subprocess
30+
profile: default
31+
backend: subprocess
32+
config:
33+
working_directory: .tmp/e2e/subprocess-jobs
34+
cleanup_completed_jobs_immediately: false
35+
ttl_seconds_before_active: 60
36+
ttl_seconds_active: 3600
37+
ttl_seconds_after_finished: 300
38+
executor_defaults:
39+
subprocess:
40+
working_directory: .tmp/e2e/subprocess-jobs
41+
cleanup_completed_jobs_immediately: false
42+
ttl_seconds_before_active: 60
43+
ttl_seconds_active: 3600
44+
ttl_seconds_after_finished: 300
45+
46+
evaluator:
47+
recreate_existing_system_entities: true
48+
49+
safe_synthesizer: {}
50+
51+
models:
52+
controller:
53+
interval_seconds: 5
54+
model_deployment_garbage_collection_ttl_seconds: 30
55+
56+
inference_gateway: {}
57+
58+
secrets:
59+
allow_key_creation: true
60+
61+
files:
62+
default_storage_config:
63+
type: local
64+
path: .tmp/e2e/files
65+
66+
studio:
67+
static_files_path: web/packages/studio/dist
68+
sandbox_enabled: true

0 commit comments

Comments
 (0)