Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0cc9bcf
Fix model serving telemetry_config never converging and silently drop…
radakam Jul 30, 2026
2ff0482
Shorten comments
radakam Jul 30, 2026
64f5d2e
Apply telemetry_config instead of suppressing it
radakam Aug 3, 2026
f4eda68
Tighten comments and test step titles
radakam Aug 3, 2026
ebdd8d1
Drop telemetry_config from the invariant configs
radakam Aug 4, 2026
fcc8cec
Skip a remote-only telemetry_config instead of removing it
radakam Aug 4, 2026
f29ba54
Restore blank line dropped from the invariant test.toml
radakam Aug 4, 2026
271f236
Stop Git Bash rewriting the api get path on Windows
radakam Aug 4, 2026
2c0fcf3
Cover removing telemetry_config and document the behavior change
radakam Aug 4, 2026
e586f1b
Reject telemetry_config on an endpoint that serves nothing
radakam Aug 4, 2026
585ea0a
Correct telemetry_config against the verified serving API
radakam Aug 5, 2026
b4d708b
Shorten the comments added by this change
radakam Aug 5, 2026
ce2a3b9
Cover the telemetry wait and reject a telemetry_config naming no tables
radakam Aug 6, 2026
e11ddb7
Shorten the remaining comments added by this change
radakam Aug 6, 2026
2fbbfa1
Wait unconditionally before patching telemetry_config
radakam Aug 6, 2026
fce68de
Narrow this change to the telemetry_config drift fix
radakam Aug 6, 2026
e50622b
Reject a telemetry_config that names no profile
radakam Aug 6, 2026
b08f4a8
Move the telemetry_config validation rule out of this change
radakam Aug 6, 2026
e2f8ede
Show only what each telemetry_config test is about
radakam Aug 6, 2026
3a61bb7
Trim the comments that restate their code
radakam Aug 6, 2026
4c79f79
Drop leftover noise from the telemetry_config drift tests
radakam Aug 7, 2026
14fbfee
Restore telemetry fake fidelity dropped in the last cleanup
radakam Aug 7, 2026
37ba094
Tighten telemetry_config comments added by this change
radakam Aug 7, 2026
0d9ea65
Cover telemetry_config with an invariant test instead of a manual one
radakam Aug 7, 2026
7f95348
Drop Local from the telemetry test snapshots
radakam Aug 7, 2026
1cb9377
Drop the Local knob left behind by a concurrent merge
radakam Aug 7, 2026
200afa8
Cover removing telemetry_config
radakam Aug 7, 2026
6484f94
Shorten the comments and changelog added by this change
radakam Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
direct: Fixed model serving `telemetry_config` drift and applied planned telemetry updates. Unsupported endpoint types now fail when telemetry is applied; create may still succeed because it drops the field ([#6106](https://github.com/databricks/cli/pull/6106)).
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
bundle:
name: test-bundle-$UNIQUE_NAME

resources:
model_serving_endpoints:
foo:
name: test-endpoint-$UNIQUE_NAME
config:
served_entities:
- name: prod
entity_name: main.default.test_model
entity_version: "1"
workload_size: Small
scale_to_zero_enabled: true
telemetry_config:
table_names:
logs_table: main.default.test_logs
traces_table: main.default.test_traces
inference_table_config:
sampling_fraction: 0.5
1 change: 1 addition & 0 deletions acceptance/bundle/invariant/continue_293/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions acceptance/bundle/invariant/migrate/test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ EnvMatrixExclude.no_pydabs_1000_tasks = ["INPUT_CONFIG=job_pydabs_1000_tasks.yml
# migrate deploys via Terraform first, and the TF provider rejects an uppercase
# volume schema_name ("inconsistent final plan"). Covered by no_drift on direct.
EnvMatrixExclude.no_volume_uppercase = ["INPUT_CONFIG=volume_uppercase_name.yml.tmpl"]

# Terraform types sampling_fraction as an integer and truncates 0.5; covered by no_drift.
EnvMatrixExclude.no_model_serving_endpoint_telemetry = ["INPUT_CONFIG=model_serving_endpoint_telemetry.yml.tmpl"]
1 change: 1 addition & 0 deletions acceptance/bundle/invariant/no_drift/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions acceptance/bundle/invariant/test.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ EnvMatrix.INPUT_CONFIG = [
"model.yml.tmpl",
"model_with_permissions.yml.tmpl",
"model_serving_endpoint.yml.tmpl",
"model_serving_endpoint_telemetry.yml.tmpl",
"pipeline.yml.tmpl",
"pipeline_allow_duplicate_names.yml.tmpl",
"pipeline_apply_policy_default_values.yml.tmpl",
Expand Down Expand Up @@ -116,6 +117,9 @@ no_external_volume_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=volume_externa
# it here to keep the cloud invariant runs from timing out. Still exercised locally.
no_vector_search_index_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=vector_search_index.yml.tmpl"]

# Telemetry requires a real model, which cloud invariant tests do not provision.
no_model_serving_endpoint_telemetry_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=model_serving_endpoint_telemetry.yml.tmpl"]

# Fake SQL endpoint for local tests
[[Server]]
Pattern = "POST /api/2.0/sql/statements/"
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
Badness = "After deleting and recreating a model serving endpoint remotely with the same name but a different endpoint_id, bundle plan/deploy ends up with a permanent update on permissions because the V1 permissions API does not delete ACLs immediately when the parent is gone."

Local = true
Cloud = true
RequiresUnityCatalog = true
RecordRequests = false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
bundle:
name: test-bundle

resources:
model_serving_endpoints:
endpoint1:
name: test-endpoint
config:
served_entities:
- name: prod
entity_name: main.default.test_model
entity_version: "1"
workload_size: Small
scale_to_zero_enabled: true

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

=== Deploy without telemetry_config
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

=== Configure telemetry out of band
=== Remote-only telemetry_config is not drift
>>> [CLI] bundle plan --output json
{
"telemetry_config": {
"action": "skip",
"reason": "backend_default",
"remote": {
"telemetry_profile_id": "[UUID]"
}
}
}

=== Deploy does not remove it
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

>>> [CLI] api get /api/2.0/serving-endpoints/test-endpoint
{
"telemetry_profile_id": "[UUID]"
}

=== Only the out-of-band telemetry PATCH was sent
>>> print_requests.py --method PATCH --oneline //serving-endpoints
{"method": "PATCH", "path": "/api/2.0/serving-endpoints/test-endpoint/telemetry-config", "body": {"telemetry_config": {"table_names": {"logs_table": "main.default.other_logs"}}}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
echo "*" > .gitignore

title "Deploy without telemetry_config"
trace $CLI bundle deploy

title "Configure telemetry out of band"
MSYS_NO_PATHCONV=1 $CLI api patch "/api/2.0/serving-endpoints/test-endpoint/telemetry-config" --json '{"telemetry_config": {"table_names": {"logs_table": "main.default.other_logs"}}}' > /dev/null

title "Remote-only telemetry_config is not drift"
trace $CLI bundle plan --output json | jq '.plan[].changes | with_entries(select(.key | startswith("telemetry_config")))'

title "Deploy does not remove it"
trace $CLI bundle deploy
MSYS_NO_PATHCONV=1 trace $CLI api get "/api/2.0/serving-endpoints/test-endpoint" | jq .telemetry_config

title "Only the out-of-band telemetry PATCH was sent"
trace print_requests.py --method PATCH --oneline //serving-endpoints
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RecordRequests = true

# Telemetry classification is direct-engine-specific.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
bundle:
name: test-bundle

resources:
model_serving_endpoints:
endpoint1:
name: test-endpoint
config:
served_entities:
- name: prod
entity_name: main.default.test_model
entity_version: "1"
workload_size: Small
scale_to_zero_enabled: true
telemetry_config: # TELEMETRY
table_names: # TELEMETRY
logs_table: main.default.test_logs # TELEMETRY
inference_table_config: # TELEMETRY
sampling_fraction: 0.5 # TELEMETRY

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

=== Initial deployment
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

=== Update config and telemetry_config
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

=== Plan converges
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged

=== Request order: config PUT, poll, telemetry PATCH
>>> print_requests.py --get --oneline //serving-endpoints
1 {"method": "POST", "path": "/api/2.0/serving-endpoints", "body": {"config": {"served_entities": [{"entity_name": "main.default.test_model", "entity_version": "1", "name": "prod", "scale_to_zero_enabled": true, "workload_size": "Small"}]}, "name": "test-endpoint", "telemetry_config": {"inference_table_config": {"sampling_fraction": 0.5}, "table_names": {"logs_table": "main.default.test_logs"}}}}
2 {"method": "GET", "path": "/api/2.0/serving-endpoints/test-endpoint"}
1 {"method": "PUT", "path": "/api/2.0/serving-endpoints/test-endpoint/config", "body": {"served_entities": [{"entity_name": "main.default.test_model", "entity_version": "1", "name": "prod", "scale_to_zero_enabled": true, "workload_size": "Medium"}]}}
2 {"method": "GET", "path": "/api/2.0/serving-endpoints/test-endpoint"}
1 {"method": "PATCH", "path": "/api/2.0/serving-endpoints/test-endpoint/telemetry-config", "body": {"telemetry_config": {"inference_table_config": {"sampling_fraction": 0.9}, "table_names": {"logs_table": "main.default.test_logs"}}}}
2 {"method": "GET", "path": "/api/2.0/serving-endpoints/test-endpoint"}

=== Remove telemetry_config
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

>>> [CLI] api get /api/2.0/serving-endpoints/test-endpoint
null

=== Plan converges after removal
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged

=== Removal sends an empty telemetry PATCH
>>> print_requests.py --method PATCH --oneline //serving-endpoints
{"method": "PATCH", "path": "/api/2.0/serving-endpoints/test-endpoint/telemetry-config", "body": {}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
echo "*" > .gitignore

title "Initial deployment"
trace $CLI bundle deploy

title "Update config and telemetry_config"
update_file.py databricks.yml "workload_size: Small" "workload_size: Medium"
update_file.py databricks.yml "sampling_fraction: 0.5" "sampling_fraction: 0.9"
trace $CLI bundle deploy

title "Plan converges"
trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged"

title "Request order: config PUT, poll, telemetry PATCH"
trace print_requests.py --get --oneline //serving-endpoints | uniq -c | sed 's/^ *//'

title "Remove telemetry_config"
grep -v TELEMETRY databricks.yml > updated.yml && mv updated.yml databricks.yml
trace $CLI bundle deploy
MSYS_NO_PATHCONV=1 trace $CLI api get "/api/2.0/serving-endpoints/test-endpoint" | jq .telemetry_config

title "Plan converges after removal"
trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged"

title "Removal sends an empty telemetry PATCH"
trace print_requests.py --method PATCH --oneline //serving-endpoints
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RecordRequests = true

# Telemetry updates are direct-engine-specific.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
27 changes: 27 additions & 0 deletions bundle/direct/dresources/model_serving_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ var (
pathAiGateway = structpath.MustParsePath("ai_gateway")
pathConfig = structpath.MustParsePath("config")
pathEmailNotifications = structpath.MustParsePath("email_notifications")
pathTelemetryConfig = structpath.MustParsePath("telemetry_config")
)

type ResourceModelServingEndpoint struct {
Expand Down Expand Up @@ -246,6 +247,19 @@ func (r *ResourceModelServingEndpoint) updateNotifications(ctx context.Context,
return nil
}

// updateTelemetryConfig removes telemetry when telemetryConfig is nil.
func (r *ResourceModelServingEndpoint) updateTelemetryConfig(ctx context.Context, id string, telemetryConfig *serving.TelemetryConfig) error {
req := serving.PatchTelemetryConfigRequest{
Name: id,
TelemetryConfig: telemetryConfig,
}
_, err := r.client.ServingEndpoints.PatchTelemetryConfig(ctx, req)
if err != nil {
return fmt.Errorf("failed to update telemetry config: %w", err)
}
return nil
}

func diffTags(currentTags, desiredTags []serving.EndpointTag) (addTags []serving.EndpointTag, deleteTags []string) {
addTags = make([]serving.EndpointTag, 0)

Expand Down Expand Up @@ -343,6 +357,19 @@ func (r *ResourceModelServingEndpoint) DoUpdate(ctx context.Context, id string,
}
}

if entry.Changes.HasChange(pathTelemetryConfig) {
// The telemetry API rejects endpoints with an update in progress.
_, err = r.waitForEndpointReady(ctx, id)
if err != nil {
return nil, err
}

err = r.updateTelemetryConfig(ctx, id, config.TelemetryConfig)
if err != nil {
return nil, err
}
}

return nil, nil
}

Expand Down
6 changes: 6 additions & 0 deletions bundle/direct/dresources/resources.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ resources:
# Accepted on write but not returned by GET.
- field: config.served_entities[*].burst_scaling_enabled
reason: input_only
# table_names creates a profile and is not returned; inference_table_config round-trips.
- field: telemetry_config.table_names
reason: input_only
# Write-only secrets: the backend stores them and returns the reference field, not the plaintext.
- field: config.served_entities[*].external_model.ai21labs_config.ai21labs_api_key_plaintext
reason: input_only
Expand Down Expand Up @@ -316,6 +319,9 @@ resources:
- field: rate_limits
reason: not_implemented
backend_defaults:
# Remote-only telemetry is reported as a change at this parent path.
- field: telemetry_config

# https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/serving/resource_model_serving.go#L383
# common.CustomizeSchemaPath(m, "config", "served_entities", "name").SetComputed()
- field: config.served_entities[*].name
Expand Down
6 changes: 5 additions & 1 deletion libs/testserver/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,7 @@ func AddDefaultHandlers(server *Server) {

// Serving Endpoints:
server.Handle("GET", "/api/2.0/serving-endpoints/{name}", func(req Request) any {
return MapGet(req.Workspace, req.Workspace.ServingEndpoints, req.Vars["name"])
return req.Workspace.ServingEndpointGet(req.Vars["name"])
})

server.Handle("POST", "/api/2.0/serving-endpoints", func(req Request) any {
Expand All @@ -984,6 +984,10 @@ func AddDefaultHandlers(server *Server) {
return req.Workspace.ServingEndpointPatchTags(req, req.Vars["name"])
})

server.Handle("PATCH", "/api/2.0/serving-endpoints/{name}/telemetry-config", func(req Request) any {
return req.Workspace.ServingEndpointPatchTelemetryConfig(req, req.Vars["name"])
})

// Vector Search Endpoints:

server.Handle("POST", "/api/2.0/vector-search/endpoints", func(req Request) any {
Expand Down
Loading
Loading