@fabricorg/databricks-testkit 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +313 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +313 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1722,7 +1722,7 @@ function createSqlDeltaFoundationPack() {
|
|
|
1722
1722
|
],
|
|
1723
1723
|
requiredChecks: ["sql"],
|
|
1724
1724
|
requirements: [workspaceRequirement, authRequirement, warehouseRequirement],
|
|
1725
|
-
docsUrl: `${DOCS}#
|
|
1725
|
+
docsUrl: `${DOCS}#sql-and-delta-foundation`,
|
|
1726
1726
|
certificationScopes: [AZURE_SCOPE]
|
|
1727
1727
|
});
|
|
1728
1728
|
}
|
|
@@ -1747,7 +1747,7 @@ function createLakeflowFoundationPack() {
|
|
|
1747
1747
|
],
|
|
1748
1748
|
requiredChecks: ["pipeline"],
|
|
1749
1749
|
requirements: [workspaceRequirement, authRequirement],
|
|
1750
|
-
docsUrl: `${DOCS}#
|
|
1750
|
+
docsUrl: `${DOCS}#lakeflow-ingestion-foundation`,
|
|
1751
1751
|
certificationScopes: [AZURE_SCOPE]
|
|
1752
1752
|
});
|
|
1753
1753
|
}
|
|
@@ -1775,7 +1775,7 @@ function createJobsCodeFoundationPack() {
|
|
|
1775
1775
|
],
|
|
1776
1776
|
requiredChecks: ["jobs"],
|
|
1777
1777
|
requirements: [workspaceRequirement, authRequirement],
|
|
1778
|
-
docsUrl: `${DOCS}#
|
|
1778
|
+
docsUrl: `${DOCS}#jobs-and-code-foundation`,
|
|
1779
1779
|
certificationScopes: [AZURE_SCOPE]
|
|
1780
1780
|
});
|
|
1781
1781
|
}
|
|
@@ -1801,7 +1801,7 @@ function createUnityStorageFoundationPack() {
|
|
|
1801
1801
|
],
|
|
1802
1802
|
requiredChecks: ["uc-grants"],
|
|
1803
1803
|
requirements: [workspaceRequirement, authRequirement, warehouseRequirement],
|
|
1804
|
-
docsUrl: `${DOCS}#
|
|
1804
|
+
docsUrl: `${DOCS}#unity-catalog-and-storage-foundation`,
|
|
1805
1805
|
certificationScopes: [AZURE_SCOPE]
|
|
1806
1806
|
});
|
|
1807
1807
|
}
|
|
@@ -1827,7 +1827,7 @@ function createAppsOperationalFoundationPack() {
|
|
|
1827
1827
|
],
|
|
1828
1828
|
requiredChecks: ["app"],
|
|
1829
1829
|
requirements: [workspaceRequirement, authRequirement],
|
|
1830
|
-
docsUrl: `${DOCS}#
|
|
1830
|
+
docsUrl: `${DOCS}#apps-and-operational-data-foundation`,
|
|
1831
1831
|
certificationScopes: [AZURE_SCOPE]
|
|
1832
1832
|
});
|
|
1833
1833
|
}
|
|
@@ -1858,7 +1858,8 @@ var DatabricksJobsAdapter = class {
|
|
|
1858
1858
|
async get(runId) {
|
|
1859
1859
|
const raw = await this.client.get("/api/2.1/jobs/runs/get", {
|
|
1860
1860
|
run_id: runId,
|
|
1861
|
-
include_history: true
|
|
1861
|
+
include_history: true,
|
|
1862
|
+
include_resolved_values: true
|
|
1862
1863
|
});
|
|
1863
1864
|
return normalizeJobRun(runId, raw);
|
|
1864
1865
|
}
|
|
@@ -1872,15 +1873,46 @@ var DatabricksJobsAdapter = class {
|
|
|
1872
1873
|
async cancel(runId) {
|
|
1873
1874
|
await this.client.post("/api/2.1/jobs/runs/cancel", { run_id: runId });
|
|
1874
1875
|
}
|
|
1876
|
+
async waitUntilActive(runId, options = {}) {
|
|
1877
|
+
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
1878
|
+
const pollIntervalMs = options.pollIntervalMs ?? 2e3;
|
|
1879
|
+
const deadline = Date.now() + timeoutMs;
|
|
1880
|
+
while (Date.now() < deadline) {
|
|
1881
|
+
const run = await this.get(runId);
|
|
1882
|
+
if (run.lifeCycleState === "RUNNING") return run;
|
|
1883
|
+
if (isTerminal(run.lifeCycleState)) {
|
|
1884
|
+
throw new Error(
|
|
1885
|
+
`Databricks run ${runId} reached ${run.lifeCycleState}/${run.resultState} before it could be canceled`
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
await delay2(pollIntervalMs);
|
|
1889
|
+
}
|
|
1890
|
+
throw new Error(`Databricks run ${runId} did not become active within ${timeoutMs}ms`);
|
|
1891
|
+
}
|
|
1875
1892
|
async repair(runId, options = {}) {
|
|
1876
1893
|
const response = await this.client.post("/api/2.1/jobs/runs/repair", {
|
|
1877
1894
|
run_id: runId,
|
|
1878
1895
|
...options.latestRepairId ? { latest_repair_id: options.latestRepairId } : {},
|
|
1896
|
+
...options.rerunDependentTasks ? { rerun_dependent_tasks: true } : {},
|
|
1879
1897
|
...options.rerunTasks?.length ? { rerun_tasks: options.rerunTasks } : { rerun_all_failed_tasks: true }
|
|
1880
1898
|
});
|
|
1881
1899
|
if (!response.repair_id) throw new Error("Databricks Jobs repair returned no repair_id");
|
|
1882
1900
|
return response.repair_id;
|
|
1883
1901
|
}
|
|
1902
|
+
async waitForRepair(runId, repairId, options = {}) {
|
|
1903
|
+
const timeoutMs = options.timeoutMs ?? 9e5;
|
|
1904
|
+
const pollIntervalMs = options.pollIntervalMs ?? 5e3;
|
|
1905
|
+
const deadline = Date.now() + timeoutMs;
|
|
1906
|
+
while (Date.now() < deadline) {
|
|
1907
|
+
const run = await this.get(runId);
|
|
1908
|
+
const repair = run.repairs.find((candidate) => candidate.repairId === repairId);
|
|
1909
|
+
if (repair && isTerminal(repair.lifeCycleState)) return run;
|
|
1910
|
+
await delay2(pollIntervalMs);
|
|
1911
|
+
}
|
|
1912
|
+
throw new Error(
|
|
1913
|
+
`Databricks repair ${repairId} for run ${runId} timed out after ${timeoutMs}ms`
|
|
1914
|
+
);
|
|
1915
|
+
}
|
|
1884
1916
|
};
|
|
1885
1917
|
function validateJobGraph(tasks) {
|
|
1886
1918
|
if (tasks.length === 0) throw new Error("Jobs orchestration requires at least one task");
|
|
@@ -1932,10 +1964,14 @@ function jobsOrchestrationCheck() {
|
|
|
1932
1964
|
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
1933
1965
|
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
1934
1966
|
});
|
|
1967
|
+
let repairId;
|
|
1935
1968
|
if (spec.repairFailedTasks && run.resultState !== "SUCCESS") {
|
|
1936
1969
|
const failed = run.tasks.filter((task) => task.resultState === "FAILED").map((task) => task.taskKey);
|
|
1937
|
-
await adapter.repair(runId, {
|
|
1938
|
-
|
|
1970
|
+
repairId = await adapter.repair(runId, {
|
|
1971
|
+
rerunTasks: failed.length > 0 ? failed : void 0,
|
|
1972
|
+
rerunDependentTasks: true
|
|
1973
|
+
});
|
|
1974
|
+
run = await adapter.waitForRepair(runId, repairId, {
|
|
1939
1975
|
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
1940
1976
|
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
1941
1977
|
});
|
|
@@ -1953,10 +1989,32 @@ function jobsOrchestrationCheck() {
|
|
|
1953
1989
|
throw new Error(`Task '${taskKey}' ended in ${task.resultState}, expected ${expected}`);
|
|
1954
1990
|
}
|
|
1955
1991
|
}
|
|
1992
|
+
let cancellation;
|
|
1993
|
+
if (spec.cancellation) {
|
|
1994
|
+
const cancellationRunId = await adapter.submit(spec.cancellation.request);
|
|
1995
|
+
await adapter.waitUntilActive(cancellationRunId, {
|
|
1996
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
1997
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
1998
|
+
});
|
|
1999
|
+
await adapter.cancel(cancellationRunId);
|
|
2000
|
+
const canceled = await adapter.wait(cancellationRunId, {
|
|
2001
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
2002
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 1e4)
|
|
2003
|
+
});
|
|
2004
|
+
const expectedCancellation = spec.cancellation.expectedResult ?? "CANCELED";
|
|
2005
|
+
if (canceled.resultState !== expectedCancellation) {
|
|
2006
|
+
throw new Error(
|
|
2007
|
+
`Cancellation run ${cancellationRunId} ended in ${canceled.resultState}, expected ${expectedCancellation}`
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
cancellation = { runId: cancellationRunId, resultState: canceled.resultState };
|
|
2011
|
+
}
|
|
1956
2012
|
return {
|
|
1957
2013
|
runId,
|
|
2014
|
+
...repairId ? { repairId } : {},
|
|
1958
2015
|
resultState: run.resultState,
|
|
1959
|
-
tasks: run.tasks
|
|
2016
|
+
tasks: run.tasks,
|
|
2017
|
+
...cancellation ? { cancellation } : {}
|
|
1960
2018
|
};
|
|
1961
2019
|
}
|
|
1962
2020
|
};
|
|
@@ -1976,11 +2034,24 @@ function parseJobOrchestrationLiveSpec(value) {
|
|
|
1976
2034
|
throw new Error("Jobs orchestration spec requires request.tasks");
|
|
1977
2035
|
}
|
|
1978
2036
|
validateJobGraph(spec.request.tasks);
|
|
2037
|
+
if (spec.cancellation) {
|
|
2038
|
+
if (typeof spec.cancellation !== "object" || !spec.cancellation.request || !Array.isArray(spec.cancellation.request.tasks)) {
|
|
2039
|
+
throw new Error("Jobs orchestration cancellation requires request.tasks");
|
|
2040
|
+
}
|
|
2041
|
+
validateJobGraph(spec.cancellation.request.tasks);
|
|
2042
|
+
}
|
|
1979
2043
|
return spec;
|
|
1980
2044
|
}
|
|
1981
2045
|
function normalizeJobRun(fallbackRunId, raw) {
|
|
1982
2046
|
const state = objectOf(raw.state);
|
|
1983
|
-
const
|
|
2047
|
+
const repairEntries = Array.isArray(raw.repair_history) ? raw.repair_history.filter(
|
|
2048
|
+
(repair) => Boolean(repair && typeof repair === "object")
|
|
2049
|
+
) : [];
|
|
2050
|
+
const taskRunRanks = /* @__PURE__ */ new Map();
|
|
2051
|
+
for (const [rank, repair] of repairEntries.entries()) {
|
|
2052
|
+
for (const runId of numberArray(repair.task_run_ids)) taskRunRanks.set(runId, rank);
|
|
2053
|
+
}
|
|
2054
|
+
const taskAttempts = Array.isArray(raw.tasks) ? raw.tasks.filter(
|
|
1984
2055
|
(task) => Boolean(task && typeof task === "object")
|
|
1985
2056
|
).map((task) => {
|
|
1986
2057
|
const taskState = objectOf(task.state);
|
|
@@ -1989,20 +2060,236 @@ function normalizeJobRun(fallbackRunId, raw) {
|
|
|
1989
2060
|
...Number.isFinite(Number(task.run_id)) ? { runId: Number(task.run_id) } : {},
|
|
1990
2061
|
lifeCycleState: String(taskState.life_cycle_state ?? "UNKNOWN"),
|
|
1991
2062
|
resultState: String(taskState.result_state ?? "UNKNOWN"),
|
|
1992
|
-
...typeof taskState.state_message === "string" ? { stateMessage: taskState.state_message } : {}
|
|
2063
|
+
...typeof taskState.state_message === "string" ? { stateMessage: taskState.state_message } : {},
|
|
2064
|
+
...Number.isFinite(Number(task.attempt_number)) ? { attemptNumber: Number(task.attempt_number) } : {}
|
|
1993
2065
|
};
|
|
1994
2066
|
}) : [];
|
|
2067
|
+
const latestTasks = /* @__PURE__ */ new Map();
|
|
2068
|
+
for (const task of taskAttempts) {
|
|
2069
|
+
const current = latestTasks.get(task.taskKey);
|
|
2070
|
+
const currentAttempt = current?.attemptNumber ?? -1;
|
|
2071
|
+
const candidateAttempt = task.attemptNumber ?? -1;
|
|
2072
|
+
const currentRank = current?.runId ? taskRunRanks.get(current.runId) ?? -1 : -1;
|
|
2073
|
+
const candidateRank = task.runId ? taskRunRanks.get(task.runId) ?? -1 : -1;
|
|
2074
|
+
if (!current || candidateAttempt > currentAttempt || candidateAttempt === currentAttempt && candidateRank >= currentRank) {
|
|
2075
|
+
latestTasks.set(task.taskKey, task);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
const tasks = [...latestTasks.values()];
|
|
2079
|
+
const repairs = repairEntries.map((repair) => {
|
|
2080
|
+
const repairState = objectOf(repair.state);
|
|
2081
|
+
return {
|
|
2082
|
+
repairId: Number(repair.id ?? repair.repair_id ?? 0),
|
|
2083
|
+
type: String(repair.type ?? "UNKNOWN"),
|
|
2084
|
+
lifeCycleState: String(repairState.life_cycle_state ?? "UNKNOWN"),
|
|
2085
|
+
resultState: String(repairState.result_state ?? "UNKNOWN"),
|
|
2086
|
+
taskRunIds: numberArray(repair.task_run_ids)
|
|
2087
|
+
};
|
|
2088
|
+
});
|
|
1995
2089
|
return {
|
|
1996
2090
|
runId: Number(raw.run_id ?? fallbackRunId),
|
|
1997
2091
|
lifeCycleState: String(state.life_cycle_state ?? "UNKNOWN"),
|
|
1998
2092
|
resultState: String(state.result_state ?? "UNKNOWN"),
|
|
1999
2093
|
tasks,
|
|
2094
|
+
repairs,
|
|
2000
2095
|
raw
|
|
2001
2096
|
};
|
|
2002
2097
|
}
|
|
2098
|
+
function isTerminal(state) {
|
|
2099
|
+
return ["TERMINATED", "SKIPPED", "INTERNAL_ERROR", "BLOCKED"].includes(state);
|
|
2100
|
+
}
|
|
2101
|
+
function delay2(ms) {
|
|
2102
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2103
|
+
}
|
|
2003
2104
|
function objectOf(value) {
|
|
2004
2105
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2005
2106
|
}
|
|
2107
|
+
function numberArray(value) {
|
|
2108
|
+
return Array.isArray(value) ? value.map(Number).filter((candidate) => Number.isFinite(candidate)) : [];
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/jobs-certification-check.ts
|
|
2112
|
+
var NOTEBOOKS = {
|
|
2113
|
+
seed: `# Databricks notebook source
|
|
2114
|
+
dbutils.jobs.taskValues.set(key="branch", value="run")
|
|
2115
|
+
dbutils.jobs.taskValues.set(key="items", value=["alpha", "beta"])
|
|
2116
|
+
dbutils.notebook.exit("seeded")
|
|
2117
|
+
`,
|
|
2118
|
+
process: `# Databricks notebook source
|
|
2119
|
+
dbutils.widgets.text("item", "")
|
|
2120
|
+
item = dbutils.widgets.get("item")
|
|
2121
|
+
assert item, "item parameter is required"
|
|
2122
|
+
dbutils.notebook.exit(item)
|
|
2123
|
+
`,
|
|
2124
|
+
retry: `# Databricks notebook source
|
|
2125
|
+
dbutils.widgets.text("execution_count", "0")
|
|
2126
|
+
execution_count = int(dbutils.widgets.get("execution_count"))
|
|
2127
|
+
if execution_count <= 1:
|
|
2128
|
+
raise RuntimeError("intentional retryable failure")
|
|
2129
|
+
dbutils.notebook.exit("retried")
|
|
2130
|
+
`,
|
|
2131
|
+
repair: `# Databricks notebook source
|
|
2132
|
+
dbutils.widgets.text("repair_count", "0")
|
|
2133
|
+
repair_count = int(dbutils.widgets.get("repair_count"))
|
|
2134
|
+
if repair_count == 0:
|
|
2135
|
+
raise RuntimeError("intentional first-run failure")
|
|
2136
|
+
dbutils.notebook.exit("repaired")
|
|
2137
|
+
`,
|
|
2138
|
+
sleep: `# Databricks notebook source
|
|
2139
|
+
import time
|
|
2140
|
+
time.sleep(300)
|
|
2141
|
+
`
|
|
2142
|
+
};
|
|
2143
|
+
function jobsCertificationCheck() {
|
|
2144
|
+
return {
|
|
2145
|
+
id: "jobs-certification",
|
|
2146
|
+
description: "Disposable Lakeflow Jobs branch, loop, failure, repair, cancellation, and cleanup certification",
|
|
2147
|
+
configured: (env) => env.DBX_TEST_JOBS_CERTIFY === "1",
|
|
2148
|
+
async run({ env, client }) {
|
|
2149
|
+
const root = `${env.DBX_TEST_JOBS_FIXTURE_ROOT ?? "/Workspace/Shared/fabric-experiments-target-pack"}/${crypto.randomUUID()}`;
|
|
2150
|
+
const paths = Object.fromEntries(
|
|
2151
|
+
Object.keys(NOTEBOOKS).map((name) => [name, `${root}/${name}`])
|
|
2152
|
+
);
|
|
2153
|
+
try {
|
|
2154
|
+
await importNotebooks(client, paths);
|
|
2155
|
+
const adapter = new DatabricksJobsAdapter(client);
|
|
2156
|
+
const runId = await adapter.submit(certificationRequest(paths));
|
|
2157
|
+
const initial = await adapter.wait(runId, waitOptions(env));
|
|
2158
|
+
if (initial.resultState !== "FAILED") {
|
|
2159
|
+
throw new Error(
|
|
2160
|
+
`Certification run ${runId} expected FAILED before repair, got ${initial.resultState}`
|
|
2161
|
+
);
|
|
2162
|
+
}
|
|
2163
|
+
const retried = assertTask(initial.tasks, "retry", "SUCCESS");
|
|
2164
|
+
if ((retried.attemptNumber ?? 0) < 1) {
|
|
2165
|
+
throw new Error("Certification retry task did not record a second attempt");
|
|
2166
|
+
}
|
|
2167
|
+
assertTask(initial.tasks, "seed", "SUCCESS");
|
|
2168
|
+
assertTask(initial.tasks, "branch", "SUCCESS");
|
|
2169
|
+
assertTask(initial.tasks, "foreach", "SUCCESS");
|
|
2170
|
+
assertTask(initial.tasks, "repair", "FAILED");
|
|
2171
|
+
const repairId = await adapter.repair(runId, {
|
|
2172
|
+
rerunTasks: ["repair"],
|
|
2173
|
+
rerunDependentTasks: true
|
|
2174
|
+
});
|
|
2175
|
+
const repaired = await adapter.waitForRepair(runId, repairId, waitOptions(env));
|
|
2176
|
+
if (repaired.resultState !== "SUCCESS") {
|
|
2177
|
+
throw new Error(`Certification repair ${repairId} ended in ${repaired.resultState}`);
|
|
2178
|
+
}
|
|
2179
|
+
assertTask(repaired.tasks, "repair", "SUCCESS");
|
|
2180
|
+
const cancellationRunId = await adapter.submit(cancellationRequest(paths));
|
|
2181
|
+
await adapter.waitUntilActive(cancellationRunId, waitOptions(env));
|
|
2182
|
+
await adapter.cancel(cancellationRunId);
|
|
2183
|
+
const canceled = await adapter.wait(cancellationRunId, waitOptions(env));
|
|
2184
|
+
if (canceled.resultState !== "CANCELED") {
|
|
2185
|
+
throw new Error(`Certification cancellation ended in ${canceled.resultState}`);
|
|
2186
|
+
}
|
|
2187
|
+
return {
|
|
2188
|
+
runId,
|
|
2189
|
+
repairId,
|
|
2190
|
+
cancellationRunId,
|
|
2191
|
+
taskValues: true,
|
|
2192
|
+
retry: true,
|
|
2193
|
+
condition: true,
|
|
2194
|
+
forEach: true,
|
|
2195
|
+
repair: true,
|
|
2196
|
+
cancellation: true,
|
|
2197
|
+
fixtureRoot: root
|
|
2198
|
+
};
|
|
2199
|
+
} finally {
|
|
2200
|
+
await client.post("/api/2.0/workspace/delete", { path: root, recursive: true });
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
async function importNotebooks(client, paths) {
|
|
2206
|
+
await client.post("/api/2.0/workspace/mkdirs", {
|
|
2207
|
+
path: paths.seed.slice(0, paths.seed.lastIndexOf("/"))
|
|
2208
|
+
});
|
|
2209
|
+
for (const [name, source] of Object.entries(NOTEBOOKS)) {
|
|
2210
|
+
await client.post("/api/2.0/workspace/import", {
|
|
2211
|
+
path: paths[name],
|
|
2212
|
+
format: "SOURCE",
|
|
2213
|
+
language: "PYTHON",
|
|
2214
|
+
overwrite: true,
|
|
2215
|
+
content: Buffer.from(source).toString("base64")
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
function certificationRequest(paths) {
|
|
2220
|
+
return {
|
|
2221
|
+
run_name: "fabric-experiments-jobs-certification",
|
|
2222
|
+
tasks: [
|
|
2223
|
+
{
|
|
2224
|
+
task_key: "retry",
|
|
2225
|
+
notebook_task: {
|
|
2226
|
+
notebook_path: paths.retry,
|
|
2227
|
+
base_parameters: { execution_count: "{{task.execution_count}}" }
|
|
2228
|
+
},
|
|
2229
|
+
max_retries: 1
|
|
2230
|
+
},
|
|
2231
|
+
{
|
|
2232
|
+
task_key: "seed",
|
|
2233
|
+
depends_on: [{ task_key: "retry" }],
|
|
2234
|
+
notebook_task: { notebook_path: paths.seed }
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
task_key: "branch",
|
|
2238
|
+
depends_on: [{ task_key: "seed" }],
|
|
2239
|
+
condition_task: {
|
|
2240
|
+
left: "{{tasks.seed.values.branch}}",
|
|
2241
|
+
op: "EQUAL_TO",
|
|
2242
|
+
right: "run"
|
|
2243
|
+
}
|
|
2244
|
+
},
|
|
2245
|
+
{
|
|
2246
|
+
task_key: "foreach",
|
|
2247
|
+
depends_on: [{ task_key: "branch", outcome: "true" }],
|
|
2248
|
+
for_each_task: {
|
|
2249
|
+
inputs: "{{tasks.seed.values.items}}",
|
|
2250
|
+
concurrency: 2,
|
|
2251
|
+
task: {
|
|
2252
|
+
task_key: "process_item",
|
|
2253
|
+
notebook_task: {
|
|
2254
|
+
notebook_path: paths.process,
|
|
2255
|
+
base_parameters: { item: "{{input}}" }
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
},
|
|
2260
|
+
{
|
|
2261
|
+
task_key: "repair",
|
|
2262
|
+
depends_on: [{ task_key: "foreach" }],
|
|
2263
|
+
notebook_task: {
|
|
2264
|
+
notebook_path: paths.repair,
|
|
2265
|
+
base_parameters: { repair_count: "{{job.repair_count}}" }
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
]
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
function cancellationRequest(paths) {
|
|
2272
|
+
return {
|
|
2273
|
+
run_name: "fabric-experiments-jobs-cancellation-certification",
|
|
2274
|
+
tasks: [{ task_key: "sleep", notebook_task: { notebook_path: paths.sleep } }]
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
function waitOptions(env) {
|
|
2278
|
+
return {
|
|
2279
|
+
timeoutMs: Number(env.DBX_TEST_TIMEOUT_MS ?? 9e5),
|
|
2280
|
+
pollIntervalMs: Number(env.DBX_TEST_POLL_INTERVAL_MS ?? 5e3)
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
function assertTask(tasks, taskKey, resultState) {
|
|
2284
|
+
const task = tasks.find((candidate) => candidate.taskKey === taskKey);
|
|
2285
|
+
if (!task) throw new Error(`Certification result did not contain task '${taskKey}'`);
|
|
2286
|
+
if (task.resultState !== resultState) {
|
|
2287
|
+
throw new Error(
|
|
2288
|
+
`Certification task '${taskKey}' ended in ${task.resultState}, expected ${resultState}`
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
return task;
|
|
2292
|
+
}
|
|
2006
2293
|
|
|
2007
2294
|
// src/jobs-target-pack.ts
|
|
2008
2295
|
function createLakeflowJobsTargetPack() {
|
|
@@ -2010,8 +2297,8 @@ function createLakeflowJobsTargetPack() {
|
|
|
2010
2297
|
id: "lakeflow-jobs",
|
|
2011
2298
|
name: "Lakeflow Jobs orchestration",
|
|
2012
2299
|
description: "Typed multi-task DAG, branch/loop, retry, cancellation, repair and task-outcome validation.",
|
|
2013
|
-
version: "0.
|
|
2014
|
-
maturity: "
|
|
2300
|
+
version: "0.2.0",
|
|
2301
|
+
maturity: "live-gated",
|
|
2015
2302
|
capabilities: [
|
|
2016
2303
|
"jobs.multi-task-dag",
|
|
2017
2304
|
"jobs.dependencies",
|
|
@@ -2022,7 +2309,12 @@ function createLakeflowJobsTargetPack() {
|
|
|
2022
2309
|
"jobs.repair",
|
|
2023
2310
|
"jobs.task-outcomes"
|
|
2024
2311
|
],
|
|
2025
|
-
checks: () => [
|
|
2312
|
+
checks: () => [
|
|
2313
|
+
restrictedPrincipalCheck(),
|
|
2314
|
+
jobRunCheck(),
|
|
2315
|
+
jobsOrchestrationCheck(),
|
|
2316
|
+
jobsCertificationCheck()
|
|
2317
|
+
],
|
|
2026
2318
|
requiredChecks: ["jobs-orchestration"],
|
|
2027
2319
|
requirements: [
|
|
2028
2320
|
{
|
|
@@ -2045,11 +2337,14 @@ function createLakeflowJobsTargetPack() {
|
|
|
2045
2337
|
{
|
|
2046
2338
|
id: "orchestration-spec",
|
|
2047
2339
|
description: "JSON multi-task orchestration scenario",
|
|
2048
|
-
anyOf: ["DBX_TEST_JOBS_ORCHESTRATION_JSON"]
|
|
2340
|
+
anyOf: ["DBX_TEST_JOBS_ORCHESTRATION_JSON"],
|
|
2341
|
+
required: false
|
|
2049
2342
|
}
|
|
2050
2343
|
],
|
|
2051
|
-
docsUrl: "https://experiments.fabric.pro/docs/testing/target-packs/#
|
|
2052
|
-
certificationScopes: [
|
|
2344
|
+
docsUrl: "https://experiments.fabric.pro/docs/testing/target-packs/#lakeflow-jobs-orchestration",
|
|
2345
|
+
certificationScopes: [
|
|
2346
|
+
"Azure Databricks serverless Jobs, West US, workspace OAuth, public network path"
|
|
2347
|
+
]
|
|
2053
2348
|
});
|
|
2054
2349
|
}
|
|
2055
2350
|
|
|
@@ -2106,6 +2401,7 @@ exports.expectTable = expectTable;
|
|
|
2106
2401
|
exports.failureRecoveryCheck = failureRecoveryCheck;
|
|
2107
2402
|
exports.foundationTargetPacks = foundationTargetPacks;
|
|
2108
2403
|
exports.jobRunCheck = jobRunCheck;
|
|
2404
|
+
exports.jobsCertificationCheck = jobsCertificationCheck;
|
|
2109
2405
|
exports.jobsOrchestrationCheck = jobsOrchestrationCheck;
|
|
2110
2406
|
exports.lakebaseCredentialCheck = lakebaseCredentialCheck;
|
|
2111
2407
|
exports.lakebaseRoundTripCheck = lakebaseRoundTripCheck;
|