Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions acceptance/bundle/dms/existing-state/databricks.yml.tmpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
bundle:
name: dms-existing-state-$UNIQUE_NAME

experimental:
deployment_history: false

resources:
jobs:
one:
name: one
two:
name: two
tasks:
- task_key: run_one
run_job_task:
job_id: ${resources.jobs.one.id}
153 changes: 97 additions & 56 deletions acceptance/bundle/dms/existing-state/output.txt
Original file line number Diff line number Diff line change
@@ -1,78 +1,119 @@

=== Deploy without recording: ordinary direct-engine state tracking one job, unknown to DMS
>>> [CLI] bundle deploy
=== Config disables recording even when the environment enables it
>>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/files...
Created jobs.one
Created jobs.two
Files: 2 uploaded, 0 deleted
Resources: 1 created, 0 changed, 0 deleted, 0 unchanged

>>> [CLI] bundle plan -o json
{
"plan_version": [PLAN_VERSION],
"cli_version": "[CLI_VERSION]",
"lineage": "[UUID]",
"serial": 1
}
Resources: 2 created, 0 changed, 0 deleted, 0 unchanged

>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged
>>> cp .databricks/bundle/default/resources.json .databricks/resources.before-dms.json

>>> print_requests.py --dms //api/2.0/bundle --oneline

>>> jq {state_version, features} .databricks/bundle/default/resources.json
>>> jq {state_version, serial, features} .databricks/bundle/default/resources.json
{
"state_version": 3,
"serial": 1,
"features": null
}

=== Enabling recording is refused because the existing deployment is not recorded
>>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle deploy
Error: deployment history setting (true) does not match the existing state (false)

Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over


>>> print_requests.py --dms //api/2.0/bundle --oneline

=== Wiping the local cache still refuses after pulling the non-DMS state from the workspace
>>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle deploy
Error: deployment history setting (true) does not match the existing state (false)

Update experimental.deployment_history to match the existing deployment, or run "databricks bundle destroy" to start over


=== A leftover local WAL with the wrong lineage is still refused
>>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle deploy
Error: reading state from [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: WAL recovery failed: WAL lineage ("stale") does not match state lineage ("[UUID]")

=== Normal state selection prefers local serial 2 over remote serial 1
>>> update_file.py .databricks/bundle/default/resources.json "serial": 1 "serial": 2

=== Destroy clears the deployment. With nothing recorded remotely the bundle is treated as new, so recording can be enabled once the stale WAL is gone
>>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.jobs.one
>>> jq -c {serial} .databricks/bundle/default/resources.json
{"serial":2}

All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default

Destroy: 1 deleted

>>> DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true [CLI] bundle deploy
=== Config enables recording despite the disabled environment: register version 3, then deploy version 4
>>> [CLI] bundle deploy
Enabling deployment history...
Deployment history enabled: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=3
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/files...
Created jobs.one
Files: 2 uploaded, 0 deleted
Resources: 1 created, 0 changed, 0 deleted, 0 unchanged

>>> print_requests.py --dms //api/2.0/bundle --oneline
{"method": "POST", "path": "/api/2.0/bundle/deployments", "body": {"display_name": "dms-existing-state-[UNIQUE_NAME]", "initial_parent_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state", "target_name": "default", "workspace_info": {"file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/files", "root_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default"}}}
{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions", "q": {"version_id": "1"}, "body": {"cli_version": "[CLI_VERSION]", "operations": [{"action_type": "OPERATION_ACTION_TYPE_CREATE", "resource_key": "jobs.one"}], "version_type": "VERSION_TYPE_DEPLOY"}}
{"method": "PATCH", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/1/operations/jobs.one", "q": {"update_mask": "state,error_message,resource_id,status"}, "body": {"error_message": "", "resource_id": "[ONE_ID]", "sequence_id": 0, "state": "{\"state\":{\"deployment\":{\"deployment_id\":\"[DEPLOYMENT_ID]\",\"kind\":\"BUNDLE\",\"metadata_file_path\":\"/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/metadata.json\",\"version_id\":\"1\"},\"edit_mode\":\"UI_LOCKED\",\"format\":\"MULTI_TASK\",\"max_concurrent_runs\":1,\"name\":\"one\",\"queue\":{\"enabled\":true}}}", "status": "OPERATION_STATUS_SUCCEEDED"}}
{"method": "POST", "path": "/api/2.0/bundle/deployments/[DEPLOYMENT_ID]/versions/1/complete", "body": {"completion_reason": "VERSION_COMPLETE_SUCCESS"}}
Current Deployment Version: [DATABRICKS_URL]/deployments/[DEPLOYMENT_ID]?version=4
Updated jobs.one
Updated jobs.two
Files: 1 uploaded, 0 deleted
Resources: 0 created, 2 changed, 0 deleted, 0 unchanged

>>> find.py resources[.]json[.]wal --expect 0

>>> jq {state_version, features} .databricks/bundle/default/resources.json
>>> jq {serial, features, state} .databricks/bundle/default/resources.json
{
"state_version": 3,
"serial": 4,
"features": {
"deployment_history": {}
},
"state": {}
}

=== Inspect the remote backup's original pre-migration resource IDs, state, and dependencies
>>> MSYS_NO_PATHCONV=1 [CLI] workspace export /Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/resources.json.backup --format AUTO --file .databricks/resources.backup.first.json

>>> cat .databricks/resources.backup.first.json
{
"state_version": 3,
"cli_version": "[CLI_VERSION]",
"lineage": "[UUID]",
"serial": 1,
"state": {
"resources.jobs.one": {
"__id__": "[ONE_ID]",
"state": {
"deployment": {
"kind": "BUNDLE",
"metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/metadata.json"
},
"edit_mode": "UI_LOCKED",
"format": "MULTI_TASK",
"max_concurrent_runs": 1,
"name": "one",
"queue": {
"enabled": true
}
}
},
"resources.jobs.two": {
"__id__": "[TWO_ID]",
"state": {
"deployment": {
"kind": "BUNDLE",
"metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dms-existing-state-[UNIQUE_NAME]/default/state/metadata.json"
},
"edit_mode": "UI_LOCKED",
"format": "MULTI_TASK",
"max_concurrent_runs": 1,
"name": "two",
"queue": {
"enabled": true
},
"tasks": [
{
"run_job_task": {
"job_id": [ONE_ID]
},
"task_key": "run_one"
}
]
},
"depends_on": [
{
"node": "resources.jobs.one",
"label": "${resources.jobs.one.id}"
}
]
}
}
}
=== The backup equals the remote pre-migration snapshot byte for byte
>>> cmp .databricks/resources.before-dms.json .databricks/resources.backup.first.json

=== Read back the completed migration and deploy versions with their recorded resource state
>>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-versions deployments/[DEPLOYMENT_ID] -o json
[{"version_id":"3","previous_version_id":null,"version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"},{"version_id":"4","previous_version_id":"3","version_type":"VERSION_TYPE_DEPLOY","status":"VERSION_STATUS_COMPLETED","completion_reason":"VERSION_COMPLETE_SUCCESS"}]

>>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/3 -o json
[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","action_type":"OPERATION_ACTION_TYPE_INITIAL_REGISTER","status":"OPERATION_STATUS_SUCCEEDED"}]

>>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-operations deployments/[DEPLOYMENT_ID]/versions/4 -o json
[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","action_type":"OPERATION_ACTION_TYPE_UPDATE","status":"OPERATION_STATUS_SUCCEEDED"}]

>>> MSYS_NO_PATHCONV=1 [CLI] bundle-deployments list-resources deployments/[DEPLOYMENT_ID] -o json
[{"resource_key":"jobs.one","resource_id":"[ONE_ID]","last_action_type":"OPERATION_ACTION_TYPE_UPDATE","last_version_id":"4","state":{"name":"updated","tasks":[],"depends_on":[]}},{"resource_key":"jobs.two","resource_id":"[TWO_ID]","last_action_type":"OPERATION_ACTION_TYPE_UPDATE","last_version_id":"4","state":{"name":"two","tasks":[{"task_key":"run_one","job_id":[ONE_ID]}],"depends_on":[{"node":"resources.jobs.one","label":"${resources.jobs.one.id}"}]}}]
67 changes: 35 additions & 32 deletions acceptance/bundle/dms/existing-state/script
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
envsubst < databricks.yml.tmpl > databricks.yml

title "Deploy without recording: ordinary direct-engine state tracking one job, unknown to DMS"
trace $CLI bundle deploy
dms_plan
trace print_requests.py --dms //api/2.0/bundle --oneline
trace jq '{state_version, features}' .databricks/bundle/default/resources.json

title "Enabling recording is refused because the existing deployment is not recorded"
musterr trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle deploy
trace print_requests.py --dms //api/2.0/bundle --oneline

title "Wiping the local cache still refuses after pulling the non-DMS state from the workspace"
rm -rf .databricks
musterr trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle deploy

title "A leftover local WAL with the wrong lineage is still refused"
mkdir -p .databricks/bundle/default
printf '{"lineage":"stale","serial":99,"state_version":2,"features":{"deployment_history":{}}}\n' > .databricks/bundle/default/resources.json.wal
musterr trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle deploy

title "Destroy clears the deployment. With nothing recorded remotely the bundle is treated as new, so recording can be enabled once the stale WAL is gone"
# Clear the stale WAL first: it was refused, not deleted, and destroy runs without recording, so it
# would otherwise try to replay it and fail on the lineage mismatch.
rm -f .databricks/bundle/default/resources.json.wal
# Keep the mismatched setting here to verify that destroy ignores configuration and follows state.
trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle destroy --auto-approve
trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle deploy
trace print_requests.py --dms //api/2.0/bundle --oneline
title "Config disables recording even when the environment enables it"
trace DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true $CLI bundle deploy 2>&1 | contains.py '!Current Deployment Version:' '!Enabling deployment history'
replace_ids.py
trace cp .databricks/bundle/default/resources.json .databricks/resources.before-dms.json
trace jq '{state_version, serial, features}' .databricks/bundle/default/resources.json

title "Normal state selection prefers local serial 2 over remote serial 1"
trace update_file.py .databricks/bundle/default/resources.json '"serial": 1' '"serial": 2'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we have this serial update here? what its purpose, does migration below not work without it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it absolutely does. This just simulates that if local state is ahead, the local state is used and thus the version created us at version 3.

I can see this test being a bit hard to read, let me simplify this just to the core assertions.

trace jq -c '{serial}' .databricks/bundle/default/resources.json

export DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=false
update_file.py databricks.yml 'deployment_history: false' 'deployment_history: true'
update_file.py databricks.yml 'name: one' 'name: updated'

title "Config enables recording despite the disabled environment: register version 3, then deploy version 4"
trace $CLI bundle deploy 2>&1 | contains.py 'Enabling deployment history...' 'Deployment history enabled: ' 'Current Deployment Version: ' '?version=3' '?version=4'
trace find.py 'resources[.]json[.]wal' --expect 0
trace jq '{state_version, features}' .databricks/bundle/default/resources.json
DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY=true replace_ids.py
deployment_id=$(MSYS_NO_PATHCONV=1 $CLI workspace get-status "/Workspace/Users/${CURRENT_USER_NAME}/.bundle/dms-existing-state-${UNIQUE_NAME}/default/state/resources.deployment.json" | python3 -c 'import sys, json; print(json.load(sys.stdin)["object_id"])')
trace jq '{serial, features, state}' .databricks/bundle/default/resources.json

title "Inspect the remote backup's original pre-migration resource IDs, state, and dependencies"
trace MSYS_NO_PATHCONV=1 $CLI workspace export "/Workspace/Users/${CURRENT_USER_NAME}/.bundle/dms-existing-state-${UNIQUE_NAME}/default/state/resources.json.backup" --format AUTO --file .databricks/resources.backup.first.json
trace cat .databricks/resources.backup.first.json

title "The backup equals the remote pre-migration snapshot byte for byte"
trace cmp .databricks/resources.before-dms.json .databricks/resources.backup.first.json

title "Read back the completed migration and deploy versions with their recorded resource state"
deployment_id=$(MSYS_NO_PATHCONV=1 $CLI workspace get-status "/Workspace/Users/${CURRENT_USER_NAME}/.bundle/dms-existing-state-${UNIQUE_NAME}/default/state/resources.deployment.json" -o json | python3 -c 'import sys, json; print(json.load(sys.stdin)["object_id"])')
add_repl "$deployment_id" DEPLOYMENT_ID
trace MSYS_NO_PATHCONV=1 $CLI bundle-deployments list-versions "deployments/${deployment_id}" -o json | jq -c 'sort_by(.version_id | tonumber) | map({version_id, previous_version_id, version_type, status, completion_reason})'
trace MSYS_NO_PATHCONV=1 $CLI bundle-deployments list-operations "deployments/${deployment_id}/versions/3" -o json | jq -c 'sort_by(.resource_key) | map({resource_key, resource_id, action_type, status})'
trace MSYS_NO_PATHCONV=1 $CLI bundle-deployments list-operations "deployments/${deployment_id}/versions/4" -o json | jq -c 'sort_by(.resource_key) | map({resource_key, resource_id, action_type, status})'
trace MSYS_NO_PATHCONV=1 $CLI bundle-deployments list-resources "deployments/${deployment_id}" -o json | jq -c '
sort_by(.resource_key) | map({resource_key, resource_id, last_action_type, last_version_id,
state: (.state | fromjson | {name: .state.name,
tasks: [.state.tasks[]? | {task_key, job_id: .run_job_task.job_id}],
depends_on: (.depends_on // [])})})'

# The recorded requests are asserted above via print_requests.py; drop whatever the run added after
# that (telemetry, and the state probes of the non-recording variant) so nothing is left to compare.
rm -f "$OUT_REQUESTS"
18 changes: 16 additions & 2 deletions acceptance/bundle/dms/existing-state/test.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# This test deploys without recording first, then turns it on per-command, so it opts out of
# the parent's DMS=true and drives DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY inline instead.
# This test checks config precedence with conflicting environment settings,
# so it drives DATABRICKS_BUNDLE_DEPLOYMENT_HISTORY instead of the parent's matrix.
EnvMatrix.DMS = [""]

# acceptance/bundle/test.toml removes Current Deployment Version for shared DMS/non-DMS goldens.
# Inherited Repls append and cannot be disabled here.
# Mask at Order -1 before the parent's default 0, then restore at +1.
# This lets these tests assert both migration and deployment links.
[[Repls]]
Old = '(?m)^Current Deployment Version: '
New = '__CURRENT_DEPLOYMENT_VERSION__: '
Order = -1

[[Repls]]
Old = '(?m)^__CURRENT_DEPLOYMENT_VERSION__: '
New = 'Current Deployment Version: '
Order = 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
bundle:
name: dms-migration-before-deploy-$UNIQUE_NAME

experimental:
deployment_history: false

sync:
exclude:
- LOG.*

resources:
jobs:
job:
name: migration-before-deploy
schemas:
schema:
name: dms_migration_before_deploy_$UNIQUE_NAME
catalog_name: main
2 changes: 2 additions & 0 deletions acceptance/bundle/dms/migration-before-deploy/out.test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Cloud = false
EnvMatrix.DMS = ["true"]
Loading
Loading