Skip to content

Migrate Teradata test connection to the @check framework - #31642

Merged
Khairajani merged 11 commits into
mainfrom
feat/teradata-test-connection-checks
Sep 10, 2026
Merged

Migrate Teradata test connection to the @check framework#31642
Khairajani merged 11 commits into
mainfrom
feat/teradata-test-connection-checks

Conversation

@Khairajani

@Khairajani Khairajani commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Describe your changes:

Fixes #

Teradata was still on the legacy test_connection_db_common path, so every test connection failure surfaced as the raw teradatasql exception with no structured diagnosis. A wrong password rendered as a 25-line gosqldriver/teradatasql Go stack trace.

This migrates it onto the @check / ChecksProvider framework, following the same shape as the Postgres (#29342) and MySQL (#28793) migrations.

Changes

  • TeradataChecks implements the seeded steps on the shared ping / list_schemas / list_tables / list_views / run_sql helpers — no new abstraction. It inherits the shared TCP reachability preflight, so an unreachable host now fails as a network problem before credentials are exercised.

  • TERADATA_ERRORS maps the driver's own codes to diagnoses. teradatasql exposes no .errno/.sqlstate attribute — it raises a single OperationalError whose message embeds both, e.g.

    [Version 20.0.0.65] [Session 1038] [Teradata Database] [Error 8017] [SQLState 28000] The UserId, Password or Account is invalid.
    

    so both codes are read back out of the text. The codes themselves are stable and locale-independent, unlike the prose that follows them.

    Auth is keyed on SQLState 28000 (the SQL-standard "invalid authorization specification" class) rather than Error 8017 alone. 8017-under-28000 is what Teradata reports for LOGMECH=TD2 and LOGMECH=LDAP alike; keying on the class means any other rejection Teradata files as an authorization failure is covered without the pack enumerating — or guessing at — codes that have not been observed. NETWORK_ERRORS is composed in via .including.

    Only codes whose text is confirmed against Teradata's published references are encoded (8017/28000, 3802, 3807, 3523). Anything else keeps its raw errorLog rather than being given a guessed diagnosis.

  • teradata.json: CheckAccess gains category: "ConnectionGate". The runner short-circuits on the category, not the legacy shortCircuit flag. Without it a failed gate lets every later step open its own connection — the multi-minute hang measured in Fixes #29341: migrate Postgres test connection to the @check framework #29342. A test asserts this and fails when the line is removed.

  • teradata.json: adds the GetDatabases step, mandatory: false. Not scope creep: the legacy handler already passed TERADATA_GET_DATABASE as a GetDatabases query, but the seeded definition had no such step and test_connection_steps iterates the definition — so that query had never actually run. It is deliberately not mandatory: the step is new, so making it required would newly fail a service whose user cannot read dbc.databasesvx but which tests and ingests fine today — and a failed mandatory step also aborts ingestion via raise_test_connection_exception. A test pins this.

  • _get_client registers engine.dispose for deterministic teardown.

Related: #31639 fixes the UI side — the failure card was rendering errorLog in preference to the step's message, which is why the Teradata trace filled the modal. That fix helps every connector without an error pack; this PR is what makes Teradata's failures sharp rather than merely readable.

Type of change:

  • Improvement

High-level design:

N/A — follows the established per-connector migration pattern (#29342, #28793); no new shared abstractions.

Tests:

Use cases covered

  • An invalid Teradata username/password/account is diagnosed as "Authentication failed" with a remediation, instead of surfacing a Go stack trace.
  • The same holds under LOGMECH=LDAP, which reports the same 8017/28000 pairing; and any other rejection Teradata files under SQLState 28000 classifies without the pack having to know its code.
  • A missing database / missing object / missing SELECT privilege each get their own diagnosis.
  • An unreachable host fails at the TCP preflight as a network problem, before credentials are exercised.
  • A failed gate short-circuits: later steps report Skipped / ConnectionNotEstablished rather than each opening its own connection.
  • An unrecognised driver error still reports its raw errorLog, with no diagnosis invented for it.
  • GetDatabases reports what it found, including an empty result and the sampling cap, without failing the connection test.

Unit tests

  • Added.

  • File: ingestion/tests/unit/source/database/teradata/test_connection.py — 21 tests, all passing.

    • One per classifier rule, plus rule precedence when two rules could match the same message.
    • A bare number in a message ("query returned 3802 rows") is not read as a code.
    • test_checks_cover_exactly_the_seeded_steps — the provider's checks match the seeded teradata.json, read from the resource rather than transcribed, so the two cannot drift.
    • test_the_seeded_gate_step_is_tagged_as_the_connection_gate — verified load-bearing: removing the category line makes this and the short-circuit test fail (later steps go Failed instead of Skipped).
    • An end-to-end TestConnectionRunner pass over the real seeded definition, asserting the gate diagnosis and the skip cascade.
    • GetDatabases success path — normal count, empty result, and the DEFAULT_SAMPLE_ROWS cap. Every runner test fails at the gate, so without these the summarizer had no runtime coverage at all.
    • test_the_new_get_databases_step_is_not_mandatory — pins the decision above so it cannot be flipped without a reviewer seeing it.

    Note: the tests deliberately do not require the teradatasqlalchemy dialect (an optional extra, absent from the base unit-test environment). The driver-error stub subclasses sqlite3.OperationalError so a plain sqlite engine wraps it into a DBAPIError in exactly the production shape; the classifier reads the message, which is identical either way.

Backend integration tests

  • Not applicable — the only backend change is a seeded JSON resource.

Ingestion integration tests

  • Not applicable — no live Teradata system in CI; the failure shapes are covered by unit tests against the real message format.

Playwright (UI) tests

  • Not applicable (no UI changes).

Manual testing performed

The originating report came from a live Teradata 20.0 system: an invalid credential test connection returned [Error 8017] [SQLState 28000] The UserId, Password or Account is invalid. — that exact message is reproduced verbatim in the unit tests. Re-verification against the live system is pending.

Checks run

python -m pytest tests/unit/source/database/teradata/ -q     # 21 passed
python -m pytest tests/unit/source/database/{teradata,postgres,mysql,glue} -q   # 70 passed
ruff check / ruff format (ingestion/pyproject.toml)          # clean

(Collection errors in a wider local run are missing optional driver extras — pyathena, pyodbc, teradatasqlalchemy — not related to this change.)

Seeding was checked: TestConnectionDefinitionResource calls repository.createOrUpdate(...) on startup, so upgraded deployments do receive the new category and the added step rather than keeping a stale row.

UI screen recording / screenshots:

Not applicable — no UI changes.

Checklist:

  • I have read the CONTRIBUTING document.

  • My PR title is Fixes <issue-number>: <short explanation>

  • My PR is linked to a GitHub issue via Fixes #<issue-number> above.

  • I have commented on my code, particularly in hard-to-understand areas.

  • For JSON Schema changes: no schema change — only a seeded data resource.

  • I have added tests and listed them above.

  • I have added tests around the new logic.

  • For connector/ingestion changes: no user-facing documentation change needed — the step list and diagnoses are self-describing in the UI.

Greptile Summary

The PR migrates Teradata connection testing from the legacy database helper to the structured checks framework.

  • Adds classified Teradata authentication, network, object, database, and privilege errors.
  • Adds TCP preflight, database enumeration, schema/table/view checks, and deterministic engine disposal.
  • Updates the seeded connection-test definition with a connection gate and optional database-enumeration step.
  • Adds unit coverage for classification, short-circuiting, summaries, preflight behavior, and seeded-step consistency.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
ingestion/src/metadata/ingestion/source/database/teradata/connection.py Replaces the legacy connection test with structured Teradata checks, error classification, network preflight, and owned engine teardown.
ingestion/tests/unit/source/database/teradata/test_connection.py Adds focused tests for error rules, seeded definitions, runner short-circuiting, database summaries, and TCP probe selection.
openmetadata-service/src/main/resources/json/data/testConnections/database/teradata.json Marks CheckAccess as the connection gate and adds optional GetDatabases coverage.

Sequence Diagram

sequenceDiagram
  participant Runner as TestConnectionRunner
  participant Checks as TeradataChecks
  participant Network as TCP preflight
  participant DB as Teradata
  Runner->>Checks: CheckAccess
  Checks->>Network: Probe configured host and port
  alt Network unavailable
    Network-->>Runner: Classified network failure
    Runner-->>Runner: Skip remaining steps
  else Network reachable
    Checks->>DB: SELECT 1
    alt Authentication or connection failure
      DB-->>Runner: Classified Teradata error
      Runner-->>Runner: Skip remaining steps
    else Connection established
      Runner->>Checks: GetDatabases
      Runner->>Checks: GetSchemas
      Runner->>Checks: GetTables
      Runner->>Checks: GetViews
    end
  end
Loading

Reviews (7): Last reviewed commit: "test(ingestion): keep the Teradata test ..." | Re-trigger Greptile

Context used:

…work

Teradata was still on the legacy test_connection_db_common path, so every
failure surfaced as the raw teradatasql exception with no structured
diagnosis - a bad password rendered as a 25-line Go stack trace from
gosqldriver/teradatasql.

- TeradataChecks implements the seeded steps on the shared ping/list_*/
  run_sql helpers, and inherits the TCP reachability preflight, so an
  unreachable host fails as a network problem before credentials are
  exercised.
- TERADATA_ERRORS maps the driver's own codes to diagnoses. teradatasql
  exposes no .errno/.sqlstate, so both are read back out of the message
  text, which is where the driver puts them - but the codes are stable and
  locale-independent unlike the prose that follows. Only codes confirmed
  against Teradata's published references are encoded; anything else keeps
  its raw errorLog rather than getting a guessed diagnosis. Auth is keyed
  on SQLState 28000 rather than Error 8017 alone so the LDAP and Kerberos
  logmech rejections, which carry their own codes, are covered too.
- teradata.json: CheckAccess gains category=ConnectionGate. The runner
  short-circuits on the category, not the legacy shortCircuit flag, so
  without it a failed gate let every later step open its own connection.
  A test asserts this and fails when the line is removed.
- teradata.json: add the GetDatabases step. The legacy handler already
  passed TERADATA_GET_DATABASE as a GetDatabases query, but the seeded
  definition had no such step and the runner iterates the definition - so
  that query had never run.
- _get_client registers engine.dispose for deterministic teardown.

Tests cover each classifier rule, rule precedence where two could match,
that a bare number in a message is not read as a code, that the provider's
checks match the seeded definition exactly, and an end-to-end runner pass
asserting the gate diagnosis and that later steps skip.
@Khairajani
Khairajani requested review from a team as code owners August 17, 2026 13:43
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit 5ca750c18a8c6eb96640d4d91d7c86c4c0245389 in Playwright run 34446038508, attempt 1.

✅ 4485 passed · ❌ 0 failed · 🟡 9 flaky · ⏭️ 1 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 1h 0m 18s

⏱️ Max setup 6m 9s · max shard execution 22m 32s · max shard-job elapsed before upload 25m 36s · reporting 22s

🌐 216.89 requests/attempt · 2.31 app boots/UI scenario · 31.36% common-shard skew

Optimization targets still in progress:

  • Common shard skew was 31.36% (convergence target: at most 15%).
  • Browser traffic was 216.89 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.31 per UI scenario (10959 boots / 4749 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
✅ Shard advanced-search-01 130 0 0 0 0 0
✅ Shard chromium-01 189 0 0 0 0 0
✅ Shard chromium-02 172 0 0 0 0 0
✅ Shard chromium-03 184 0 0 0 0 0
🟡 Shard chromium-04 153 0 2 0 0 0
✅ Shard chromium-05 160 0 0 0 0 0
✅ Shard chromium-06 186 0 0 0 0 0
🟡 Shard chromium-07 203 0 1 0 0 0
✅ Shard chromium-08 199 0 0 0 0 0
🟡 Shard chromium-09 165 0 1 0 0 0
✅ Shard chromium-10 190 0 0 0 0 0
✅ Shard chromium-11 182 0 0 0 0 0
🟡 Shard chromium-12 199 0 2 0 0 0
✅ Shard chromium-13 171 0 0 0 0 0
✅ Shard chromium-14 157 0 0 0 0 0
🟡 Shard chromium-15 202 0 1 0 0 0
✅ Shard chromium-16 181 0 0 1 0 0
✅ Shard chromium-17 156 0 0 0 0 0
🟡 Shard chromium-18 216 0 1 0 0 0
✅ Shard chromium-19 125 0 0 0 0 0
✅ Shard chromium-20 196 0 0 0 0 0
🟡 Shard chromium-21 186 0 1 0 0 0
✅ Shard chromium-22 163 0 0 0 0 0
✅ Shard data-asset-rules-01 65 0 0 0 0 0
✅ Shard domain-isolation-01 16 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard import-export-01 80 0 0 0 0 0
✅ Shard import-export-02 70 0 0 0 0 0
✅ Shard ingestion-01 39 0 0 0 0 0
✅ Shard ingestion-02 47 0 0 0 0 0
✅ Shard reindex-01 28 0 0 0 0 0
✅ Shard search-01 12 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0
🟡 9 flaky test(s) (passed on retry)
  • Features/UserProfileOnlineStatus.spec.tsShould show "Active recently" for users active within last hour (shard chromium-04, 1 retry)
  • Pages/UserDetails.spec.tsCreate team with domain and verify visibility of inherited domain in user profile after team removal (shard chromium-04, 1 retry)
  • Features/ActivityFeed.spec.tsReacting to an activity updates its reactions in the right panel (shard chromium-07, 1 retry)
  • Pages/TasksUIFlow.spec.tsCreate and resolve description task for Pipeline via UI (shard chromium-09, 1 retry)
  • Features/ImpactAnalysis.spec.tsverify tier for Asset level impact analysis (shard chromium-12, 1 retry)
  • Flow/Metric.spec.tsVerify Unit of Measurement Update (shard chromium-12, 1 retry)
  • Features/Glossary/GlossaryMiscOperations.spec.tsshould delete term and remove tag from assets (shard chromium-15, 1 retry)
  • Features/PersonaAIContext.spec.tsmeasures the large-document preview render cost (shard chromium-18, 1 retry)
  • Pages/Lineage/DataAssetLineage.spec.tsColumn lineage for container -> topic (shard chromium-21, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

The step is newly introduced - the legacy handler passed the query but the
seeded definition had no step for it, so it never ran. As mandatory it
would newly fail a service whose user cannot read dbc.databasesvx but which
tests and ingests fine today, and a failed mandatory step also aborts
ingestion. Non-mandatory keeps the diagnosis without the regression.

Also:
- Reword the auth rule's rationale to what is sourced. Error 8017 under
  SQLState 28000 is what Teradata reports for both LOGMECH=TD2 and
  LOGMECH=LDAP; the earlier comment claimed the class additionally covered
  Kerberos rejections carrying their own codes, which was not verified. The
  rule is unchanged - keying on the standard class rather than a single
  code stands on its own, without asserting coverage that has not been
  observed.
- Cover get_databases' success path, which no test reached: the runner
  tests all fail at the gate, so the summarizer had no runtime coverage at
  all. Three cases - a normal count, an empty result, and the sample cap.
@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

…rts itself

A hostname typo came back as a raw 25-line stack trace with no diagnosis.
Two separate causes:

The reachability preflight never ran. ping() derives the probe target from
the engine URL and skips the probe when no port is present - and Teradata's
hostPort is commonly a bare hostname, so the skip was the normal case, not
the edge case. check_access now probes the port the driver would dial,
defaulting to 1025, so a DNS or firewall failure is caught in Python and
fails fast.

Nothing in the error pack could catch what got through. teradatasql is a Go
driver behind cgo: it resolves the hostname itself and reports the failure
as its own OperationalError, with no Python socket exception in the chain.
NETWORK_ERRORS matches by exception type, so it is structurally blind to
every network failure teradatasql detects on its own. Added Error 493
(hostname lookup, observed) and a generic rule on SQLState 08000, the
SQL-standard connection-exception class, as the backstop for whatever still
reaches the driver.

Tests cover both codes, the rule ordering between them, and that the
preflight probes the configured port when given and 1025 when not.
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-09-09T21:24:39Z)

Blocked the queue: playwright-summary

@gitar-bot

gitar-bot Bot commented Sep 10, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 resolved / 1 findings

Migrates the Teradata test connection framework to use structured checks and error diagnoses. No issues found.

✅ 1 resolved
Quality: Test module now hard-imports teradatasql at collection time

📄 ingestion/tests/unit/source/database/teradata/test_connection.py:17 📄 ingestion/tests/unit/source/database/teradata/test_connection.py:262
The test previously subclassed sqlite3.OperationalError and used a sqlite:// engine specifically so the suite could run without the optional Teradata driver. This commit replaces that with a top-level import teradatasql and create_engine("teradatasql://..."), which requires the teradatasqlalchemy/teradatasql extra to be installed or the whole module fails to collect. It is present in the test_unit extra so CI passes, but the change removes the previous decoupling; if you want the module to remain importable without the extra, guard it with pytest.importorskip("teradatasql").

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs skip-pr-checks Bypass PR metadata validation check

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants