mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-04 16:09:05 +00:00
## Summary Applies the same node-fetch v2 fix from PR #12799 (pipelines) to all remaining CI jobs that show \"Premature close\" / ECONNRESET failures: - **apiUtils.js** (`sendRequest`): adds `compress:false` + `noKeepAliveAgent` — fixes **Streams** job (`multiselect-stream.spec.js`, `streamname.spec.js`) - **apiCleanup.js** (`_fetch`): same fix on the node-fetch fallback path — fixes **Reports** job (dashboard/report API calls) - **model-pricing.spec.js**: wraps `node-fetch` with a local safe-fetch shim — fixes **GeneralTests** job (`/llm/models` premature close) - **streamsPage.js**: replaces four dynamic `node-fetch` imports with a shared `_nodeFetchSafe` helper - **ingestionPage.js**: adds `fetchWithRetry` around native fetch to handle transient `ECONNRESET`/premature close in `beforeEach` ingestion **SDR fix:** `regexPatternManagement.spec.js` — `AddRegexPattern.vue` migrated to `OForm` with submit-time Zod validation (`AddRegexPattern.schema.ts`). The `ODrawer` primary button is no longer disabled for empty fields; validation triggers on click and keeps the drawer open on error. Replaces stale `expect(isDisabled).toBeTruthy()` checks with click-then-expect-drawer-still-visible assertions. ## Root cause (same as #12799) `node-fetch` v2 defaults to `keepAlive: true` (connection pooling) and `compress: true` (Gunzip stream). When the server closes a pooled connection early, the Gunzip stream throws \"Premature close\". Because the dead socket stays in the pool, retries hit the same socket. `keepAlive: false` + `compress: false` eliminates both failure modes. ## Test plan - [ ] GeneralTests / `model-pricing.spec.js` — no more premature close on `/llm/models` - [ ] Streams / `multiselect-stream.spec.js` + `streamname.spec.js` — no more premature close on ingestion - [ ] Reports / `reportsScheduleNow.spec.js` — no more premature close during beforeEach ingestion - [ ] SDR / `regexPatternManagement.spec.js` — validation test passes with updated assertion 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
2.1 KiB
JavaScript
63 lines
2.1 KiB
JavaScript
import fs from 'fs';
|
|
import path from 'path';
|
|
import http from 'http';
|
|
|
|
// Cache cloud config to avoid re-reading on every call
|
|
let _cachedCloudConfig = null;
|
|
|
|
// node-fetch v2 keep-alive pooling + gzip decompression is the root cause of
|
|
// "Premature close" / ECONNRESET flakiness in CI. A fresh connection per
|
|
// request (keepAlive: false) + skipping the Gunzip stream (compress: false)
|
|
// eliminates both failure modes.
|
|
const noKeepAliveAgent = new http.Agent({ keepAlive: false });
|
|
|
|
export const getHeaders = () => {
|
|
// On cloud, use email:passcode from cloud-config.json (written by global-setup-alpha1.js)
|
|
if (process.env.IS_CLOUD === 'true') {
|
|
try {
|
|
if (!_cachedCloudConfig) {
|
|
const configPath = path.join(process.cwd(), 'playwright-tests', 'utils', 'auth', 'cloud-config.json');
|
|
_cachedCloudConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
}
|
|
if (_cachedCloudConfig.passcode) {
|
|
const basicAuth = Buffer.from(
|
|
`${_cachedCloudConfig.userEmail}:${_cachedCloudConfig.passcode}`
|
|
).toString('base64');
|
|
return {
|
|
Authorization: `Basic ${basicAuth}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
}
|
|
} catch (e) {
|
|
console.warn('[apiUtils] Failed to read cloud-config.json, falling back to password auth:', e.message);
|
|
}
|
|
}
|
|
|
|
const basicAuthCredentials = Buffer.from(
|
|
`${process.env["ZO_ROOT_USER_EMAIL"]}:${process.env["ZO_ROOT_USER_PASSWORD"]}`
|
|
).toString("base64");
|
|
|
|
return {
|
|
Authorization: `Basic ${basicAuthCredentials}`,
|
|
"Content-Type": "application/json",
|
|
};
|
|
};
|
|
|
|
export const getIngestionUrl = (orgId, streamName) => {
|
|
return `${process.env.INGESTION_URL}/api/${orgId}/${streamName}/_json`;
|
|
};
|
|
|
|
export const sendRequest = async (page, url, payload, headers) => {
|
|
// Use Node.js fetch instead of browser fetch to avoid CORS issues with deployed environments
|
|
const fetch = (await import('node-fetch')).default;
|
|
|
|
const response = await fetch(url, {
|
|
method: "POST",
|
|
headers: headers,
|
|
body: JSON.stringify(payload),
|
|
compress: false,
|
|
agent: noKeepAliveAgent,
|
|
});
|
|
|
|
return await response.json();
|
|
};
|