Skip to content

Commit 16972c0

Browse files
authored
[Streams] Make ES|QL view creation best-effort and guard test against unsupported ES versions (elastic#258584)
Resolves elastic#256797 ## Summary Fixes a consistent CI failure in the `basic.ts` deployment-agnostic API integration test for Streams where `POST /api/streams/_enable` returned a `500 Internal Server Error`. The failure was introduced by elastic#256096, which added ES|QL view creation to the `enableStreams` flow, and had occurred 15+ times on `main` (tracked in elastic#256797). ### Root cause The `ExecutionPlan.execute()` method called `upsertEsqlViews()` inside the same `try/catch` block as all other Elasticsearch operations. The ES|QL views API (`/_query/view/`) is not available in all Elasticsearch builds used in CI (e.g. the 9.4.0-SNAPSHOT used by the deployment-agnostic stateful test cluster). When that call failed, the error propagated up through `FailedToExecuteElasticsearchActionsError` and ultimately caused `enableStreams` to return a 500. The test itself contributed by unconditionally enabling `OBSERVABILITY_STREAMS_ENABLE_WIRED_STREAM_VIEWS` via `uiSettings.update` before calling `enableStreams`, which activated the ES|QL view creation path regardless of whether the API was actually available in the environment. ### Changes **`execution_plan.ts` — make ES|QL view creation best-effort** The `upsertEsqlViews()` call is moved outside the main `try/catch` block and given its own `.catch()` that logs a warning instead of rethrowing. This ensures that stream enablement succeeds even when the ES|QL views API is unavailable, treating view creation as a non-critical side-effect. **`basic.ts` — runtime-probe the ES|QL views API before using it** Instead of unconditionally enabling the feature flag, the `before` hook now probes `GET /_query/view/__kibana_probe__` against the test Elasticsearch cluster: - A `404` response means the endpoint exists (but the probe view doesn't) → API is available, feature flag is enabled, view assertions run normally. - Any other error means the API is not supported in this environment → feature flag is left disabled, view-specific `it` blocks return early. This makes the test self-adapting to the ES version present in each CI environment without skipping the entire suite. ### Verification The root cause was confirmed from [Buildkite build 90333](https://buildkite.com/elastic/kibana-on-merge/builds/90333): the test cluster ran ES `9.4.0-SNAPSHOT` (March 9 2026 snapshot), the failure was the 15th consecutive occurrence, and no Kibana-level error log for `plugins.streams` was emitted — consistent with the error being swallowed by the framework router after `throw error` in the route handler, never reaching a log sink visible in FTR output.
1 parent b12d5a0 commit 16972c0

2 files changed

Lines changed: 42 additions & 12 deletions

File tree

  • x-pack/platform
    • plugins/shared/streams/server/lib/streams/state_management/execution_plan
    • test/api_integration_deployment_agnostic/apis/streams

x-pack/platform/plugins/shared/streams/server/lib/streams/state_management/execution_plan/execution_plan.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,6 @@ export class ExecutionPlan {
243243
...upsert_dot_streams_document,
244244
...delete_dot_streams_document,
245245
]);
246-
247-
// Upsert ES|QL views after the stream documents are created
248-
await this.upsertEsqlViews(upsert_esql_view);
249246
} catch (error) {
250247
if (error instanceof StatusError) {
251248
throw error;
@@ -254,6 +251,21 @@ export class ExecutionPlan {
254251
`Failed to execute Elasticsearch actions: ${getErrorMessage(error)}`
255252
);
256253
}
254+
255+
// Upsert ES|QL views after the stream documents are created.
256+
// This is best-effort: if the ES|QL views API is unavailable (e.g. older
257+
// Elasticsearch versions or serverless), we log a warning instead of
258+
// failing the whole operation.
259+
const viewsToUpsert = this.actionsByType.upsert_esql_view;
260+
if (viewsToUpsert.length > 0) {
261+
await this.upsertEsqlViews(viewsToUpsert).catch((error) => {
262+
this.dependencies.logger.warn(
263+
`Failed to upsert ES|QL views. The ES|QL views API may not be available in this Elasticsearch version: ${getErrorMessage(
264+
error
265+
)}`
266+
);
267+
});
268+
}
257269
}
258270

259271
private async deleteQueries(actions: DeleteQueriesAction[]) {

x-pack/platform/test/api_integration_deployment_agnostic/apis/streams/basic.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,25 +70,39 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
7070
});
7171
}
7272

73-
// Failing: See https://github.com/elastic/kibana/issues/256797
74-
describe.skip('Basic functionality', () => {
73+
describe('Basic functionality', () => {
7574
async function getWiredStatus() {
7675
const response = await viewerApiClient.fetch('GET /api/streams/_status').expect(200);
7776
return response.body;
7877
}
7978

79+
// Tracks whether the ES|QL views API is available in the current test environment.
80+
// The API is only available in stateful (non-serverless) Elasticsearch and only in
81+
// recent-enough versions. When unavailable, view-specific assertions are skipped.
82+
let viewsApiAvailable = false;
83+
8084
before(async () => {
8185
apiClient = await createStreamsRepositoryAdminClient(roleScopedSupertest);
8286
viewerApiClient = await createStreamsRepositoryViewerClient(roleScopedSupertest);
8387
if (!isServerless) {
84-
await kibanaServer.uiSettings.update({
85-
[OBSERVABILITY_STREAMS_ENABLE_WIRED_STREAM_VIEWS]: true,
86-
});
88+
// Probe whether the ES|QL views API exists. A 404 means the endpoint
89+
// exists but the view resource was not found – i.e. the API is available.
90+
// Any other error (e.g. 400/405) indicates the API is not yet supported.
91+
viewsApiAvailable = await esClient.transport
92+
.request({ method: 'GET', path: '/_query/view/__kibana_probe__' })
93+
.then(() => true)
94+
.catch((err: { statusCode?: number }) => err?.statusCode === 404);
95+
96+
if (viewsApiAvailable) {
97+
await kibanaServer.uiSettings.update({
98+
[OBSERVABILITY_STREAMS_ENABLE_WIRED_STREAM_VIEWS]: true,
99+
});
100+
}
87101
}
88102
});
89103

90104
after(async () => {
91-
if (!isServerless) {
105+
if (!isServerless && viewsApiAvailable) {
92106
await kibanaServer.uiSettings.update({
93107
[OBSERVABILITY_STREAMS_ENABLE_WIRED_STREAM_VIEWS]: false,
94108
});
@@ -127,9 +141,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
127141
expect(typeof parsed.privileges.create_snapshot_repository).to.eql('boolean');
128142
});
129143

130-
// ES|QL views API is not available in serverless
144+
// ES|QL views API is not available in all environments
131145
if (!isServerless) {
132146
it('creates ES|QL views for wired root streams', async () => {
147+
if (!viewsApiAvailable) return;
133148
for (const streamName of ['logs.otel', 'logs.ecs']) {
134149
const response = await esClient.transport.request<{
135150
views: Array<{ name: string; query: string }>;
@@ -220,9 +235,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
220235
expect(wiredStatus['logs.ecs']).to.eql(false);
221236
});
222237

223-
// ES|QL views API is not available in serverless
238+
// ES|QL views API is not available in all environments
224239
if (!isServerless) {
225240
it('removes ES|QL views for wired root streams', async () => {
241+
if (!viewsApiAvailable) return;
226242
for (const streamName of ['logs.otel', 'logs.ecs']) {
227243
await esClient.transport
228244
.request<{ views: Array<{ name: string; query: string }> }>({
@@ -320,9 +336,10 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
320336
expect(response).to.have.property('acknowledged', true);
321337
});
322338

323-
// ES|QL views API is not available in serverless
339+
// ES|QL views API is not available in all environments
324340
if (!isServerless) {
325341
it(`creates ES|QL view $.${rootStream}.nginx for the forked child stream`, async () => {
342+
if (!viewsApiAvailable) return;
326343
const childStreamName = `${rootStream}.nginx`;
327344
const response = await esClient.transport.request<{
328345
views: Array<{ name: string; query: string }>;
@@ -336,6 +353,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) {
336353
});
337354

338355
it(`updates parent $.${rootStream} view to reference the forked child's view`, async () => {
356+
if (!viewsApiAvailable) return;
339357
const response = await esClient.transport.request<{
340358
views: Array<{ name: string; query: string }>;
341359
}>({

0 commit comments

Comments
 (0)