Skip to content

Commit 865f974

Browse files
authored
Add support for model serving endpoints in deployment bind/unbind comands (#2634)
## Changes <!-- Brief summary of your changes that is easy to understand --> 1. Changed FindResourceByConfigKey to return model serving endpoint resources ## Why <!-- Why are these changes needed? Provide the context that the reviewer might be missing. For example, were there any decisions behind the change that are not reflected in the code itself? --> This PR adds support for model serving endpoints in deployment operations, enabling users to: - Bind quality monitors using `databricks bundle deployment bind <myendpoint_key> <myendpoint_name>` - Unbind monitors using `databricks bundle deployment unbind <myendpoint_key>` Where: - `myendpoint_key` is a resource key defined in the bundle's .yml file - `myendpoint_name` references an existing model serving endpoint by name These capabilities allow for more flexible resource management of model serving endpoints within bundles. ## Tests <!-- How have you tested the changes? --> 1. Added a new acceptance test 2. Modified an existing unit test to check that model serving endpoint is bindable <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent 9e43332 commit 865f974

File tree

8 files changed

+162
-4
lines changed

8 files changed

+162
-4
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
### CLI
1010

1111
### Bundles
12+
* Added support for model serving endpoints in deployment bind/unbind commands ([#2634](https://github.com/databricks/cli/pull/2634))
1213

1314
### API Changes
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
bundle:
2+
name: bind-model-serving-endpoint-test-$UNIQUE_NAME
3+
4+
resources:
5+
model_serving_endpoints:
6+
endpoint1:
7+
name: $ENDPOINT_NAME
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
bundle:
2+
name: bind-model-serving-endpoint-test-[UNIQUE_NAME]
3+
4+
resources:
5+
model_serving_endpoints:
6+
endpoint1:
7+
name: test-endpoint-[UUID]
8+
9+
>>> [CLI] serving-endpoints create test-endpoint-[UUID]
10+
{
11+
"name": "test-endpoint-[UUID]",
12+
"permission_level": "CAN_MANAGE",
13+
"route_optimized": false,
14+
"state": {
15+
"config_update": "NOT_UPDATING",
16+
"ready": "NOT_READY"
17+
}
18+
}
19+
20+
>>> [CLI] serving-endpoints get test-endpoint-[UUID]
21+
{
22+
"name": "test-endpoint-[UUID]",
23+
"permission_level": "CAN_MANAGE",
24+
"route_optimized": false,
25+
"state": {
26+
"config_update": "NOT_UPDATING",
27+
"ready": "NOT_READY"
28+
}
29+
}
30+
31+
>>> [CLI] bundle deployment bind endpoint1 test-endpoint-[UUID]
32+
Updating deployment state...
33+
Successfully bound model_serving_endpoint with an id 'test-endpoint-[UUID]'. Run 'bundle deploy' to deploy changes to your workspace
34+
35+
>>> [CLI] bundle deploy
36+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/bind-model-serving-endpoint-test-[UNIQUE_NAME]/default/files...
37+
Deploying resources...
38+
Updating deployment state...
39+
Deployment complete!
40+
41+
>>> [CLI] serving-endpoints get test-endpoint-[UUID]
42+
{
43+
"name": "test-endpoint-[UUID]",
44+
"permission_level": "CAN_MANAGE",
45+
"route_optimized": false,
46+
"state": {
47+
"config_update": "NOT_UPDATING",
48+
"ready": "NOT_READY"
49+
}
50+
}
51+
52+
>>> [CLI] bundle deployment unbind endpoint1
53+
Updating deployment state...
54+
55+
>>> [CLI] bundle destroy --auto-approve
56+
All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/bind-model-serving-endpoint-test-[UNIQUE_NAME]/default
57+
58+
Deleting files...
59+
Destroy complete!
60+
61+
>>> [CLI] serving-endpoints get test-endpoint-[UUID]
62+
{
63+
"name": "test-endpoint-[UUID]",
64+
"permission_level": "CAN_MANAGE",
65+
"route_optimized": false,
66+
"state": {
67+
"config_update": "NOT_UPDATING",
68+
"ready": "NOT_READY"
69+
}
70+
}
71+
72+
>>> [CLI] serving-endpoints delete test-endpoint-[UUID]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
ENDPOINT_NAME="test-endpoint-$(uuid)"
2+
if [ -z "$CLOUD_ENV" ]; then
3+
ENDPOINT_NAME="test-endpoint-6260d50f-e8ff-4905-8f28-812345678903"
4+
fi
5+
export ENDPOINT_NAME
6+
envsubst < databricks.yml.tmpl > databricks.yml
7+
cat databricks.yml
8+
9+
# Create a pre-defined serving endpoint:
10+
trace $CLI serving-endpoints create "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}'
11+
12+
cleanup() {
13+
trace $CLI serving-endpoints delete "${ENDPOINT_NAME}"
14+
}
15+
trap cleanup EXIT
16+
17+
trace $CLI serving-endpoints get "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}'
18+
19+
trace $CLI bundle deployment bind endpoint1 "${ENDPOINT_NAME}"
20+
21+
trace $CLI bundle deploy
22+
23+
trace $CLI serving-endpoints get "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}'
24+
25+
trace $CLI bundle deployment unbind endpoint1
26+
27+
trace $CLI bundle destroy --auto-approve
28+
29+
# Read the pre-defined serving-endpoint again (expecting it still exists and is not deleted):
30+
trace $CLI serving-endpoints get "${ENDPOINT_NAME}" | jq '{name, permission_level, route_optimized, state}'
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Local = true
2+
Cloud = true
3+
4+
Ignore = [
5+
"databricks.yml",
6+
]
7+
8+
[[Server]]
9+
Pattern = "POST /api/2.0/serving-endpoints"
10+
Response.Body = '''
11+
{
12+
"name": "test-endpoint-6260d50f-e8ff-4905-8f28-812345678903"
13+
}
14+
'''
15+
16+
[[Server]]
17+
Pattern = "GET /api/2.0/serving-endpoints/"
18+
19+
[[Server]]
20+
Pattern = "GET /api/2.0/serving-endpoints/{endpoint_name}"
21+
Response.Body = '''
22+
{
23+
"name": "test-endpoint-6260d50f-e8ff-4905-8f28-812345678903",
24+
"permission_level": "CAN_MANAGE",
25+
"route_optimized": false,
26+
"state": {
27+
"config_update": "NOT_UPDATING",
28+
"ready": "NOT_READY"
29+
}
30+
}
31+
'''
32+
33+
[[Server]]
34+
Pattern = "DELETE /api/2.0/serving-endpoints/{endpoint_name}"

bundle/config/resources.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ func (r *Resources) FindResourceByConfigKey(key string) (ConfigResource, error)
157157
}
158158
}
159159

160+
for k := range r.ModelServingEndpoints {
161+
if k == key {
162+
found = append(found, r.ModelServingEndpoints[k])
163+
}
164+
}
165+
160166
if len(found) == 0 {
161167
return nil, fmt.Errorf("no such resource: %s", key)
162168
}

bundle/config/resources/model_serving_endpoint.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ func (s ModelServingEndpoint) MarshalJSON() ([]byte, error) {
3535
return marshal.Marshal(s)
3636
}
3737

38-
func (s *ModelServingEndpoint) Exists(ctx context.Context, w *databricks.WorkspaceClient, id string) (bool, error) {
38+
func (s *ModelServingEndpoint) Exists(ctx context.Context, w *databricks.WorkspaceClient, name string) (bool, error) {
3939
_, err := w.ServingEndpoints.Get(ctx, serving.GetServingEndpointRequest{
40-
Name: id,
40+
Name: name,
4141
})
4242
if err != nil {
43-
log.Debugf(ctx, "serving endpoint %s does not exist", id)
43+
log.Debugf(ctx, "serving endpoint %s does not exist", name)
4444
return false, err
4545
}
4646
return true, nil

bundle/config/resources_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"strings"
88
"testing"
99

10+
"github.com/databricks/databricks-sdk-go/service/serving"
11+
1012
"github.com/databricks/cli/bundle/config/resources"
1113
"github.com/databricks/databricks-sdk-go/experimental/mocks"
1214
"github.com/databricks/databricks-sdk-go/service/apps"
@@ -164,8 +166,13 @@ func TestResourcesBindSupport(t *testing.T) {
164166
CreateMonitor: &catalog.CreateMonitor{},
165167
},
166168
},
169+
ModelServingEndpoints: map[string]*resources.ModelServingEndpoint{
170+
"my_model_serving_endpoint": {
171+
CreateServingEndpoint: &serving.CreateServingEndpoint{},
172+
},
173+
},
167174
}
168-
unbindableResources := map[string]bool{"model": true, "model_serving_endpoint": true}
175+
unbindableResources := map[string]bool{"model": true}
169176

170177
ctx := context.Background()
171178
m := mocks.NewMockWorkspaceClient(t)
@@ -179,6 +186,7 @@ func TestResourcesBindSupport(t *testing.T) {
179186
m.GetMockVolumesAPI().EXPECT().Read(mock.Anything, mock.Anything).Return(nil, nil)
180187
m.GetMockAppsAPI().EXPECT().GetByName(mock.Anything, mock.Anything).Return(nil, nil)
181188
m.GetMockQualityMonitorsAPI().EXPECT().Get(mock.Anything, mock.Anything).Return(nil, nil)
189+
m.GetMockServingEndpointsAPI().EXPECT().Get(mock.Anything, mock.Anything).Return(nil, nil)
182190

183191
allResources := supportedResources.AllResources()
184192
for _, group := range allResources {

0 commit comments

Comments
 (0)