-
Notifications
You must be signed in to change notification settings - Fork 184
Enable reading deployment state from DMS #5355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
shreyas-goenka
wants to merge
29
commits into
main
Choose a base branch
from
shreyas-goenka/cli-state-read-from-dms
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
93117a0
bundle/statemgmt: add StateReader abstraction for file and DMS state
shreyas-goenka b06e4d1
cmd/bundle: read state from DMS when record_deployment_history is set
shreyas-goenka a358c74
bundle/statemgmt: preserve deployment lineage when reading state from…
shreyas-goenka 52e9a93
libs/testserver: minimal DMS resource recording for tests
shreyas-goenka 52cb43d
acceptance/bundle/dms: state-read reads resource state from DMS
shreyas-goenka 4cb6109
acceptance/bundle/dms: plan shows create/update/delete from DMS state
shreyas-goenka b174c65
acceptance/bundle/dms/plan: fix CI (yamlfmt comment, jq-free id capture)
shreyas-goenka a71d17b
bundle/statemgmt: simplify statereader tests
shreyas-goenka 94ce558
acceptance/bundle/dms: fix plan test on CI; log full plan JSON
shreyas-goenka fbcde65
acceptance/bundle/dms/plan: create pipelines via the pipelines command
shreyas-goenka 05ffbf7
bundle/statemgmt: trim redundant statereader tests
shreyas-goenka 322c498
bundle/statemgmt: clarify TestDMSStateReader comment
shreyas-goenka 4c1a319
bundle/statemgmt: clarify DMS read keeps resources.json for backward …
shreyas-goenka 7c92471
libs/testserver: alias bundle SDK as sdkbundle
shreyas-goenka 4e585e7
acceptance/bundle/dms: combine state-read and plan into one read test
shreyas-goenka 6c1f1b8
bundle/statemgmt: use record-deployment-history terminology, not mana…
shreyas-goenka ea94cde
bundle/statemgmt: document statereader identity-vs-resources model an…
shreyas-goenka 0af053f
bundle/statemgmt: keep resources.json resources when DMS has none yet
shreyas-goenka 121192b
bundle/statemgmt: read from DMS only after a version completed succes…
shreyas-goenka dc7d772
libs/testserver: track DMS versions; acceptance records a completed v…
shreyas-goenka fc879a6
bundle/statemgmt: make reader-selection test names state the direct f…
shreyas-goenka 8e3a46d
bundle/statemgmt: clarify dmsStateReader does not fall back to local …
shreyas-goenka eafc9e5
bundle/statemgmt: note resources.json fallback only on opting out of DMS
shreyas-goenka b2fa5b3
bundle/statemgmt: note where the lineage is minted (first write-mode …
shreyas-goenka 434628c
bundle/direct/dstate: fold DMS state read into StateDB.Open
shreyas-goenka 6206d44
bundle/direct/dstate: page through DMS list APIs instead of fetching all
shreyas-goenka 8110fbf
acceptance/bundle/dms: fix Windows CI with MSYS_NO_PATHCONV on api calls
shreyas-goenka 28cf10f
bundle/direct/dstate: pin DMS overlay semantics and early-exit scan
shreyas-goenka f50dfc0
bundle/direct/dstate: table-drive the DMS open and version-gate tests
shreyas-goenka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # pipelines.delete_me is recorded in DMS (see script) but intentionally absent | ||
| # here, so the plan deletes it. Pipelines are used (not jobs) because their ids | ||
| # are strings: the `databricks api` command rounds large int64 job ids in its | ||
| # output, which would break the id round-trip this test relies on. | ||
| bundle: | ||
| name: dms-read | ||
|
|
||
| experimental: | ||
| record_deployment_history: true | ||
|
|
||
| resources: | ||
| pipelines: | ||
| # Not recorded in DMS -> plan should create it. | ||
| create_me: | ||
| name: create-me | ||
| # Recorded in DMS with a different name -> plan should update it. | ||
| update_me: | ||
| name: update-me-new |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
|
|
||
| === bundle summary reflects resources read from DMS (update_me resolves its id; create_me is not deployed) | ||
| >>> [CLI] bundle summary | ||
| Name: dms-read | ||
| Target: default | ||
| Workspace: | ||
| User: [USERNAME] | ||
| Path: /Workspace/Users/[USERNAME]/.bundle/dms-read/default | ||
| Resources: | ||
| Pipelines: | ||
| create_me: | ||
| Name: create-me | ||
| URL: (not deployed) | ||
| delete_me: | ||
| Name: | ||
| URL: [DATABRICKS_URL]/pipelines/[UUID]?w=[NUMID] | ||
| update_me: | ||
| Name: update-me-new | ||
| URL: [DATABRICKS_URL]/pipelines/[UUID]?w=[NUMID] | ||
|
|
||
| === bundle plan derives actions from DMS state: create create_me, update update_me, delete delete_me | ||
| >>> [CLI] bundle plan | ||
| create pipelines.create_me | ||
| delete pipelines.delete_me | ||
| update pipelines.update_me | ||
|
|
||
| Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged | ||
|
|
||
| === full plan as JSON | ||
| >>> [CLI] bundle plan -o json | ||
| { | ||
| "plan_version": 2, | ||
| "cli_version": "[DEV_VERSION]", | ||
| "lineage": "synthetic-dep", | ||
| "serial": 1, | ||
| "plan": { | ||
| "resources.pipelines.create_me": { | ||
| "action": "create", | ||
| "new_state": { | ||
| "value": { | ||
| "channel": "CURRENT", | ||
| "deployment": { | ||
| "kind": "BUNDLE", | ||
| "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-read/default/state/metadata.json" | ||
| }, | ||
| "edition": "ADVANCED", | ||
| "name": "create-me" | ||
| } | ||
| } | ||
| }, | ||
| "resources.pipelines.delete_me": { | ||
| "action": "delete", | ||
| "remote_state": { | ||
| "creator_user_name": "[USERNAME]", | ||
| "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", | ||
| "id": "[UUID]", | ||
| "last_modified": [UNIX_TIME_MILLIS][0], | ||
| "name": "delete-me", | ||
| "pipeline_id": "[UUID]", | ||
| "run_as_user_name": "[USERNAME]", | ||
| "state": "IDLE", | ||
| "storage": "dbfs:/pipelines/[UUID]" | ||
| } | ||
| }, | ||
| "resources.pipelines.update_me": { | ||
| "action": "update", | ||
| "new_state": { | ||
| "value": { | ||
| "channel": "CURRENT", | ||
| "deployment": { | ||
| "kind": "BUNDLE", | ||
| "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-read/default/state/metadata.json" | ||
| }, | ||
| "edition": "ADVANCED", | ||
| "name": "update-me-new" | ||
| } | ||
| }, | ||
| "remote_state": { | ||
| "creator_user_name": "[USERNAME]", | ||
| "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", | ||
| "id": "[UUID]", | ||
| "last_modified": [UNIX_TIME_MILLIS][1], | ||
| "name": "update-me-old", | ||
| "pipeline_id": "[UUID]", | ||
| "run_as_user_name": "[USERNAME]", | ||
| "state": "IDLE", | ||
| "storage": "dbfs:/pipelines/[UUID]" | ||
| }, | ||
| "changes": { | ||
| "channel": { | ||
| "action": "update", | ||
| "new": "CURRENT" | ||
| }, | ||
| "deployment": { | ||
| "action": "update", | ||
| "new": { | ||
| "kind": "BUNDLE", | ||
| "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-read/default/state/metadata.json" | ||
| } | ||
| }, | ||
| "edition": { | ||
| "action": "update", | ||
| "new": "ADVANCED" | ||
| }, | ||
| "name": { | ||
| "action": "update", | ||
| "old": "update-me-old", | ||
| "new": "update-me-new", | ||
| "remote": "update-me-old" | ||
| }, | ||
| "storage": { | ||
| "action": "skip", | ||
| "reason": "backend_default", | ||
| "remote": "dbfs:/pipelines/[UUID]" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # Read deployment state from DMS: build synthetic DMS state for a deployment | ||
| # whose resources, compared with databricks.yml, exercise each plan action | ||
| # - create_me: in config, not in DMS -> create | ||
| # - update_me: in both, names differ -> update | ||
| # - delete_me: in DMS, not in config -> delete | ||
| # then verify both `bundle summary` and `bundle plan` read it from DMS. | ||
|
|
||
| update_id=$($CLI pipelines create -o json --json '{"name":"update-me-old"}' | jq -r .pipeline_id) | ||
| delete_id=$($CLI pipelines create -o json --json '{"name":"delete-me"}' | jq -r .pipeline_id) | ||
|
|
||
| # DMS owns the state only once a version has completed successfully, so record one | ||
| # for the deployment before seeding its resources. | ||
| MSYS_NO_PATHCONV=1 $CLI api post "/api/2.0/bundle/deployments/synthetic-dep/versions?version_id=1" --json '{"version_type":"VERSION_TYPE_DEPLOY"}' > /dev/null | ||
| MSYS_NO_PATHCONV=1 $CLI api post "/api/2.0/bundle/deployments/synthetic-dep/versions/1/complete" --json '{"completion_reason":"VERSION_COMPLETE_SUCCESS"}' > /dev/null | ||
|
|
||
| MSYS_NO_PATHCONV=1 $CLI api post "/api/2.0/bundle/deployments/synthetic-dep/versions/1/operations" --json "{\"resource_key\":\"pipelines.update_me\",\"resource_id\":\"$update_id\",\"action_type\":\"OPERATION_ACTION_TYPE_CREATE\",\"status\":\"OPERATION_STATUS_SUCCEEDED\",\"state\":{\"name\":\"update-me-old\"}}" > /dev/null | ||
| MSYS_NO_PATHCONV=1 $CLI api post "/api/2.0/bundle/deployments/synthetic-dep/versions/1/operations" --json "{\"resource_key\":\"pipelines.delete_me\",\"resource_id\":\"$delete_id\",\"action_type\":\"OPERATION_ACTION_TYPE_CREATE\",\"status\":\"OPERATION_STATUS_SUCCEEDED\",\"state\":{\"name\":\"delete-me\"}}" > /dev/null | ||
|
|
||
| MSYS_NO_PATHCONV=1 $CLI api post "/api/2.0/workspace-files/import-file/Workspace/Users/$CURRENT_USER_NAME/.bundle/dms-read/default/state/resources.json?overwrite=true" --json '{"state_version":2,"lineage":"synthetic-dep","serial":1,"state":{}}' > /dev/null | ||
|
|
||
| title "bundle summary reflects resources read from DMS (update_me resolves its id; create_me is not deployed)" | ||
| trace $CLI bundle summary | ||
|
|
||
| title "bundle plan derives actions from DMS state: create create_me, update update_me, delete delete_me" | ||
| trace $CLI bundle plan | ||
|
|
||
| title "full plan as JSON" | ||
| trace $CLI bundle plan -o json |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # DMS state reading is a direct-engine feature; pin the engine for stable output. | ||
| EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package dstate | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
|
|
||
| "github.com/databricks/databricks-sdk-go/apierr" | ||
| sdkbundle "github.com/databricks/databricks-sdk-go/service/bundle" | ||
| ) | ||
|
|
||
| // overlayDMSState replaces the file-derived resource state with the state | ||
| // recorded in the deployment metadata service, when DMS owns this deployment. | ||
| // Once DMS is authoritative its resource set is trusted even when empty (a | ||
| // successful deploy with no resources); the file's resources are only used when | ||
| // DMS has no successful version, or when the user opts out of recording | ||
| // deployment history. The caller holds db.mu and has already populated db.Data | ||
| // from the file. | ||
| func (db *DeploymentState) overlayDMSState(ctx context.Context, client sdkbundle.BundleInterface) error { | ||
| authoritative, err := deploymentHasSuccessfulVersion(ctx, client, db.Data.Lineage) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if !authoritative { | ||
| // DMS has no completed version for this lineage: a prior direct deployment | ||
| // that has not yet successfully recorded to DMS. Keep the file state. | ||
| return nil | ||
| } | ||
|
|
||
| resources, err := fetchDeploymentResources(ctx, client, db.Data.Lineage) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| db.Data.State = resources | ||
| db.stateIDs = make(map[string]string, len(resources)) | ||
| for key, entry := range resources { | ||
| db.stateIDs[key] = entry.ID | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // deploymentHasSuccessfulVersion reports whether DMS holds a successfully | ||
| // completed version for the deployment. It is the signal that DMS owns the | ||
| // state: if the deployment was never recorded to DMS, or its initial DMS deploy | ||
| // did not complete successfully, DMS state is absent or partial and Open keeps | ||
| // the local file's resources instead. | ||
| func deploymentHasSuccessfulVersion(ctx context.Context, client sdkbundle.BundleInterface, deploymentID string) (bool, error) { | ||
| // Versions are listed newest-first and fetched page by page, and we stop at | ||
| // the first successful one, so a deployment with a long version history does | ||
| // not require reading the whole list (typically just the first page). | ||
| it := client.ListVersions(ctx, sdkbundle.ListVersionsRequest{ | ||
| Parent: "deployments/" + deploymentID, | ||
| }) | ||
| for it.HasNext(ctx) { | ||
| v, err := it.Next(ctx) | ||
| if err != nil { | ||
| // A deployment that was never recorded to DMS is not an error here: it just | ||
| // means DMS is not (yet) the source of truth. | ||
| if errors.Is(err, apierr.ErrNotFound) { | ||
| return false, nil | ||
| } | ||
| return false, fmt.Errorf("listing versions from deployment metadata service: %w", err) | ||
| } | ||
| if v.Status == sdkbundle.VersionStatusVersionStatusCompleted && | ||
| v.CompletionReason == sdkbundle.VersionCompleteVersionCompleteSuccess { | ||
| return true, nil | ||
| } | ||
| } | ||
| return false, nil | ||
| } | ||
|
|
||
| // fetchDeploymentResources lists every resource recorded for the deployment in | ||
| // DMS and maps them into state entries keyed by the fully-qualified resource key. | ||
| func fetchDeploymentResources(ctx context.Context, client sdkbundle.BundleInterface, deploymentID string) (map[string]ResourceEntry, error) { | ||
| it := client.ListResources(ctx, sdkbundle.ListResourcesRequest{ | ||
| Parent: "deployments/" + deploymentID, | ||
| }) | ||
|
|
||
| out := make(map[string]ResourceEntry) | ||
| for it.HasNext(ctx) { | ||
| res, err := it.Next(ctx) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("listing resources from deployment metadata service: %w", err) | ||
| } | ||
|
|
||
| // DMS reports resource keys without the "resources." prefix (e.g. | ||
| // "jobs.foo"), but the state DB keys are fully qualified | ||
| // ("resources.jobs.foo"), so prepend it here. | ||
| key := "resources." + res.ResourceKey | ||
|
|
||
| var state json.RawMessage | ||
| if res.State != nil { | ||
| state = *res.State | ||
| } | ||
|
|
||
| out[key] = ResourceEntry{ | ||
| ID: res.ResourceId, | ||
| State: state, | ||
| } | ||
| } | ||
| return out, nil | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can add a serial based comparision and override here as a followup.