Skip to content

Add oracle live ITs - #4172

Open
dhwanilpatel wants to merge 9 commits into
GoogleCloudPlatform:mainfrom
dhwanilpatel:oracle_live_it
Open

Add oracle live ITs#4172
dhwanilpatel wants to merge 9 commits into
GoogleCloudPlatform:mainfrom
dhwanilpatel:oracle_live_it

Conversation

@dhwanilpatel

Copy link
Copy Markdown
Contributor

Adding multiple live template ITs for the oracle source.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the integration testing coverage for Oracle-to-Spanner data migration templates. It introduces numerous new integration test classes to validate various scenarios, including DDL handling, DLQ retry logic, and sharded migrations. The changes also include necessary infrastructure updates to support Oracle database configuration in test environments and a fix for classloading in custom transformations.

Highlights

  • Oracle Integration Tests: Added a comprehensive suite of integration tests for Oracle source templates, covering DDL, retry logic, string overrides, and sharded migrations.
  • Configuration Support: Updated CI/CD flags to include support for static Oracle database passwords.
  • Infrastructure Utilities: Added utility methods for flushing Oracle redo logs and updated the Oracle resource manager URI format to ensure compatibility.
  • Classloading Fix: Updated CustomTransformationImplFetcher to use the current thread's context classloader, improving reliability when loading custom transformation jars.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/spanner-pr.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request adds a comprehensive suite of integration tests for the Datastream-to-Spanner template with an Oracle source, covering data types, sharding, reserved keywords, overrides, and dead-letter queue (DLQ) retry behaviors. It also updates the custom transformation class loader to use the current thread's context class loader. The review feedback highlights critical issues in the newly added tests that must be addressed before merging: several tests contain hardcoded absolute local file paths that will break CI/CD pipelines; multiple database resource managers are instantiated but never registered for cleanup, causing connection leaks; and a condition check lacks execution flags, which will trigger duplicate SQL inserts and unique constraint violations during retry loops.

Comment on lines +345 to +347
private String getCustomShardJarPath() {
return "/home/dhwanilpatel_google_com/MyStorage/OracleSupport/DataflowTemplates/v2/spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar";
}

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.

high

The custom shard jar path is hardcoded to an absolute local path specific to a developer's machine (/home/dhwanilpatel_google_com/...). This will cause the integration test to fail in CI/CD environments or on other developers' machines. Use a system property with a relative path fallback instead.

Suggested change
private String getCustomShardJarPath() {
return "/home/dhwanilpatel_google_com/MyStorage/OracleSupport/DataflowTemplates/v2/spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar";
}
private String getCustomShardJarPath() {
return System.getProperty("customShardJarPath", "../spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar");
}

Comment on lines +386 to +388
private String getCustomShardJarPath() {
return "/home/dhwanilpatel_google_com/MyStorage/OracleSupport/DataflowTemplates/v2/spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar";
}

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.

high

The custom shard jar path is hardcoded to an absolute local path specific to a developer's machine. This will cause the integration test to fail in CI/CD environments or on other developers' machines. Use a system property with a relative path fallback instead.

Suggested change
private String getCustomShardJarPath() {
return "/home/dhwanilpatel_google_com/MyStorage/OracleSupport/DataflowTemplates/v2/spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar";
}
private String getCustomShardJarPath() {
return System.getProperty("customShardJarPath", "../spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar");
}

Comment on lines +362 to +364
private String getCustomShardJarPath() {
return "/home/dhwanilpatel_google_com/MyStorage/OracleSupport/DataflowTemplates/v2/spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar";
}

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.

high

The custom shard jar path is hardcoded to an absolute local path specific to a developer's machine. This will cause the integration test to fail in CI/CD environments or on other developers' machines. Use a system property with a relative path fallback instead.

Suggested change
private String getCustomShardJarPath() {
return "/home/dhwanilpatel_google_com/MyStorage/OracleSupport/DataflowTemplates/v2/spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar";
}
private String getCustomShardJarPath() {
return System.getProperty("customShardJarPath", "../spanner-custom-shard/target/spanner-custom-shard-1.0-SNAPSHOT.jar");
}

Comment on lines +207 to +230
ConditionCheck sendDataCondition = new ConditionCheck() {
@Override
public String getDescription() {
return "Insert data into Oracle and flush logs";
}

@Override
protected CheckResult check() {
try {
oracleResourceManager.runSQLUpdate("INSERT INTO \"person1\" (\"ID\", \"first_name1\", \"last_name1\") VALUES (1, 'John', 'Doe')");
oracleResourceManager.runSQLUpdate("INSERT INTO \"person1\" (\"ID\", \"first_name1\", \"last_name1\") VALUES (2, 'Alice', 'Johnson')");

try (java.sql.Connection conn = java.sql.DriverManager.getConnection(
"jdbc:oracle:thin:@" + System.getProperty("hostIp", "localhost") + ":1521/XEPDB1", "system", "TestPassword123");
java.sql.Statement stmt = conn.createStatement()) {
flushOracleRedoLogs(null);
}
return new CheckResult(true, "Data inserted and logs flushed");
} catch (Exception e) {
LOG.error("Failed to insert data or flush logs", e);
return new CheckResult(false, e.getMessage());
}
}
};

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.

high

The sendDataCondition check does not have an executed or inserted flag. Since check() is called repeatedly until the condition is met, it will attempt to run the INSERT statements multiple times, leading to unique constraint violations (ORA-00001) on the ID primary key. Introduce inserted and flushed flags to prevent duplicate executions.

    ConditionCheck sendDataCondition = new ConditionCheck() {
      private boolean inserted = false;
      private boolean flushed = false;

      @Override
      public String getDescription() {
        return "Insert data into Oracle and flush logs";
      }

      @Override
      protected CheckResult check() {
        try {
          if (!inserted) {
            oracleResourceManager.runSQLUpdate("INSERT INTO \"person1\" (\"ID\", \"first_name1\", \"last_name1\") VALUES (1, 'John', 'Doe')");
            oracleResourceManager.runSQLUpdate("INSERT INTO \"person1\" (\"ID\", \"first_name1\", \"last_name1\") VALUES (2, 'Alice', 'Johnson')");
            inserted = true;
          }
          if (!flushed) {
            try (java.sql.Connection conn = java.sql.DriverManager.getConnection(
                    "jdbc:oracle:thin:@" + System.getProperty("hostIp", "localhost") + ":1521/XEPDB1", "system", "TestPassword123");
                 java.sql.Statement stmt = conn.createStatement()) {
                flushOracleRedoLogs(null);
            }
            flushed = true;
          }
          return new CheckResult(true, "Data inserted and logs flushed");
        } catch (Exception e) {
          LOG.error("Failed to insert data or flush logs", e);
          return new CheckResult(false, e.getMessage());
        }
      }
    };

Comment on lines +73 to +82
CloudOracleResourceManager sysUser = setUpOracleResourceManager();

org.apache.beam.it.gcp.cloudsql.CloudOracleResourceManager.Builder sysBuilder =
CloudOracleResourceManager.builder(testName);
sysBuilder.setHost(sysUser.getHost());
sysBuilder.setPort(sysUser.getPort());
sysBuilder.setUsername("sys as sysdba");
sysBuilder.setPassword(System.getProperty("cloudProxyPassword"));
sysBuilder.setDatabaseName(sysUser.getDatabaseName());
CloudOracleResourceManager trueSysUser = (CloudOracleResourceManager) sysBuilder.build();

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.

medium

The sysUser and trueSysUser resource managers are instantiated to retrieve connection properties but are never closed or registered for cleanup, which will leak database connections. Additionally, trueSysUser is built but never used. We should use a try-with-resources block to safely close sysUser after extracting the properties, and remove the unused trueSysUser resource manager.

Suggested change
CloudOracleResourceManager sysUser = setUpOracleResourceManager();
org.apache.beam.it.gcp.cloudsql.CloudOracleResourceManager.Builder sysBuilder =
CloudOracleResourceManager.builder(testName);
sysBuilder.setHost(sysUser.getHost());
sysBuilder.setPort(sysUser.getPort());
sysBuilder.setUsername("sys as sysdba");
sysBuilder.setPassword(System.getProperty("cloudProxyPassword"));
sysBuilder.setDatabaseName(sysUser.getDatabaseName());
CloudOracleResourceManager trueSysUser = (CloudOracleResourceManager) sysBuilder.build();
String host;
int port;
String databaseName;
try (CloudOracleResourceManager sysUser = setUpOracleResourceManager()) {
host = sysUser.getHost();
port = sysUser.getPort();
databaseName = sysUser.getDatabaseName();
}
org.apache.beam.it.gcp.cloudsql.CloudOracleResourceManager.Builder sysBuilder =
CloudOracleResourceManager.builder(testName);
sysBuilder.setHost(host);
sysBuilder.setPort(port);
sysBuilder.setUsername("sys as sysdba");
sysBuilder.setPassword(System.getProperty("cloudProxyPassword", "TestPassword123"));
sysBuilder.setDatabaseName(databaseName);

Comment on lines +199 to +200
jdbcResourceManagerShardA,
jdbcResourceManagerShardA,

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.

medium

The jdbcResourceManagerShardA resource manager is passed twice to cleanResources, which is redundant.

Suggested change
jdbcResourceManagerShardA,
jdbcResourceManagerShardA,
jdbcResourceManagerShardA,

Comment on lines +158 to +163
public static void cleanUp() throws IOException {
for (OracleDataStreamToSpannerFileOverridesIT instance : testInstances) {
instance.tearDownBase();
}
ResourceManagerUtils.cleanResources(
oracleResourceManager,

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.

medium

The oracleSysUser resource manager is instantiated in setUp() but is never cleaned up in cleanUp(), which will leak database connections. Add it to cleanResources.

    ResourceManagerUtils.cleanResources(
        oracleSysUser,
        oracleResourceManager,
        spannerResourceManager,
        gcsResourceManager,
        pubsubResourceManager,
        datastreamResourceManager);

Comment on lines +427 to +432
public static void cleanUp() throws IOException {
LOG.info("Cleaning up resources...");
for (OracleDatastreamToSpannerDataTypesIT instance : testInstances) {
instance.tearDownBase();
}
ResourceManagerUtils.cleanResources(

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.

medium

The oracleSysUser resource manager is instantiated in setUp() but is never cleaned up in cleanUp(), which will leak database connections. Add it to cleanResources.

    ResourceManagerUtils.cleanResources(
        oracleSysUser,
        oracleResourceManager,
        spannerResourceManager,
        gcsResourceManager,
        pubsubResourceManager,
        datastreamResourceManager);

Comment on lines +188 to +193
spannerResourceManager, pubsubResourceManager, gcsResourceManager, jdbcResourceManagerShardA, datastreamResourceManager);
}

@Test
public void multiShardMigration() throws Exception {

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.

medium

The cloudOracleSysUser resource manager is instantiated in setUp() but is never cleaned up in cleanUp(), which will leak database connections. Add it to cleanResources.

    ResourceManagerUtils.cleanResources(
        spannerResourceManager,
        pubsubResourceManager,
        gcsResourceManager,
        jdbcResourceManagerShardA,
        datastreamResourceManager,
        cloudOracleSysUser);

@codecov

codecov Bot commented Aug 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.89%. Comparing base (65ea26d) to head (a6334ae).
⚠️ Report is 19 commits behind head on main.

Additional details and impacted files
@@              Coverage Diff              @@
##               main    #4172       +/-   ##
=============================================
+ Coverage     35.83%   61.89%   +26.06%     
- Complexity      711     3444     +2733     
=============================================
  Files           250      580      +330     
  Lines         17131    34839    +17708     
  Branches       1750     3869     +2119     
=============================================
+ Hits           6139    21564    +15425     
- Misses        10479    12167     +1688     
- Partials        513     1108      +595     
Components Coverage Δ
spanner-templates 84.63% <ø> (∅)
spanner-import-export ∅ <ø> (∅)
spanner-live-forward-migration 88.63% <ø> (∅)
spanner-live-reverse-replication 81.24% <ø> (∅)
spanner-bulk-migration 89.05% <ø> (∅)
gcs-spanner-dv 87.83% <ø> (∅)
see 427 files with indirect coverage changes
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant