diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/CassandraShard.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/CassandraShard.java index d37aef909a..dedaad142d 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/CassandraShard.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/CassandraShard.java @@ -30,7 +30,8 @@ public CassandraShard(OptionsMap optionsMap) { extractAndSetHostAndPort(); } - private void validateFields() { + @Override + public void validateFields() { if (getContactPoints() == null || getContactPoints().isEmpty()) { throw new IllegalArgumentException("CONTACT_POINTS cannot be null or empty."); } diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/Shard.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/Shard.java index 6629f1e426..7690e0f837 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/Shard.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/shard/Shard.java @@ -197,4 +197,37 @@ public int hashCode() { secretManagerUri, dbNameToLogicalShardIdMap); } + + public void validateFields() { + if (logicalShardId == null) { + throw new IllegalArgumentException("logicalShardId cannot be null"); + } + if (host == null) { + throw new IllegalArgumentException("host cannot be null"); + } + if (port == null) { + throw new IllegalArgumentException("port cannot be null"); + } + if (user == null) { + throw new IllegalArgumentException("user cannot be null"); + } + if (password == null) { + throw new IllegalArgumentException("password cannot be null"); + } + if (dbName == null) { + throw new IllegalArgumentException("dbName cannot be null"); + } + if (namespace == null) { + throw new IllegalArgumentException("namespace cannot be null"); + } + if (secretManagerUri == null) { + throw new IllegalArgumentException("secretManagerUri cannot be null"); + } + if (connectionProperties == null) { + throw new IllegalArgumentException("connectionProperties cannot be null"); + } + if (dbNameToLogicalShardIdMap == null) { + throw new IllegalArgumentException("dbNameToLogicalShardIdMap cannot be null"); + } + } } diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/AstraConnectionConfig.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/AstraConnectionConfig.java index 5922570bd0..84ad16e8df 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/AstraConnectionConfig.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/AstraConnectionConfig.java @@ -54,4 +54,20 @@ public String getAstraDbRegion() { public void setAstraDbRegion(String astraDbRegion) { this.astraDbRegion = astraDbRegion; } + + @Override + public void validateFields() { + if (databaseId == null) { + throw new IllegalArgumentException("databaseId cannot be null"); + } + if (astraToken == null) { + throw new IllegalArgumentException("astraToken cannot be null"); + } + if (keySpace == null) { + throw new IllegalArgumentException("keySpace cannot be null"); + } + if (astraDbRegion == null) { + throw new IllegalArgumentException("astraDbRegion cannot be null"); + } + } } diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/CassandraConnectionConfig.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/CassandraConnectionConfig.java index dfb611ce00..942dd79c48 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/CassandraConnectionConfig.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/CassandraConnectionConfig.java @@ -29,4 +29,11 @@ public CassandraConnectionConfig(OptionsMap optionsMap) { public OptionsMap getOptionsMap() { return optionsMap; } + + @Override + public void validateFields() { + if (optionsMap == null) { + throw new IllegalArgumentException("optionsMap cannot be null"); + } + } } diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/JdbcShardConfig.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/JdbcShardConfig.java index 05dd817284..681f923bba 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/JdbcShardConfig.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/JdbcShardConfig.java @@ -33,4 +33,14 @@ public List getShardConfigs() { public void setShardConfigs(List shardConfigs) { this.shardConfigs = shardConfigs; } + + @Override + public void validateFields() { + if (shardConfigs == null || shardConfigs.isEmpty()) { + throw new IllegalArgumentException("shardConfigs cannot be null or empty"); + } + for (Shard shard : shardConfigs) { + shard.validateFields(); + } + } } diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParser.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParser.java index 0039b21091..55dbe6cf27 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParser.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParser.java @@ -69,20 +69,25 @@ public SourceConnectionConfig parseConfiguration( String sourceTypeStr, String sourceConfigFilePath) throws Exception { SourceType sourceType = SourceType.parseSourceType(sourceTypeStr); - switch (SourceType.parseSourceType(sourceTypeStr)) { + SourceConnectionConfig sourceConfig; + switch (sourceType) { case CASSANDRA: // Maps directly to the DataStax OptionsMap - return new CassandraConnectionConfig( - CassandraDriverConfigLoader.getOptionsMapFromFile(sourceConfigFilePath)); + sourceConfig = + new CassandraConnectionConfig( + CassandraDriverConfigLoader.getOptionsMapFromFile(sourceConfigFilePath)); + break; case ASTRA_DB: String astraFileContent = FileLoader.readConfigFilePath(sourceConfigFilePath); Map astraConfigMap = parseConfigToConfigMap(astraFileContent); - return mapper.convertValue(astraConfigMap, AstraConnectionConfig.class); + sourceConfig = mapper.convertValue(astraConfigMap, AstraConnectionConfig.class); + break; case MYSQL: case PG: String jdbcFileContent = FileLoader.readConfigFilePath(sourceConfigFilePath); Map jdbcConfigMap = parseConfigToConfigMap(jdbcFileContent); JdbcShardConfig jdbcShardConfig = mapper.convertValue(jdbcConfigMap, JdbcShardConfig.class); + jdbcShardConfig.validateFields(); // Returns ordered list of shards jdbcShardConfig.getShardConfigs().sort(Comparator.comparing(Shard::getLogicalShardId)); resolveShardSecret(jdbcShardConfig, sourceConfigFilePath); @@ -90,6 +95,8 @@ public SourceConnectionConfig parseConfiguration( default: throw new IllegalArgumentException("Unsupported source type: " + sourceType); } + sourceConfig.validateFields(); + return sourceConfig; } /** diff --git a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConnectionConfig.java b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConnectionConfig.java index 16dc7a1b03..f0b79d74ef 100644 --- a/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConnectionConfig.java +++ b/v2/spanner-common/src/main/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConnectionConfig.java @@ -23,4 +23,11 @@ * *

This interface will be expanded in Phase 2 during the platformization phase. */ -public interface SourceConnectionConfig {} +public interface SourceConnectionConfig { + + /** + * Validates that the source configuration is valid. Throws IllegalArgumentException if the + * configuration is invalid. + */ + void validateFields(); +} diff --git a/v2/spanner-common/src/test/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParserTest.java b/v2/spanner-common/src/test/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParserTest.java index 2feb5ff20b..4a2ac7ab64 100644 --- a/v2/spanner-common/src/test/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParserTest.java +++ b/v2/spanner-common/src/test/java/com/google/cloud/teleport/v2/spanner/migrations/source/config/SourceConfigParserTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import com.google.cloud.teleport.v2.spanner.migrations.shard.Shard; +import com.google.cloud.teleport.v2.spanner.migrations.utils.CassandraDriverConfigLoader; import com.google.cloud.teleport.v2.spanner.migrations.utils.ISecretManagerAccessor; import com.google.cloud.teleport.v2.spanner.migrations.utils.JarFileReader; import com.typesafe.config.ConfigException; @@ -381,4 +382,34 @@ public void testParseConfiguration_UnsupportedSourceType() { () -> parser.parseConfiguration("unsupported_db", "dummy/path.json")); assertEquals("Unsupported source type: unsupported_db", exception.getMessage()); } + + @Test + public void testParseConfiguration_Cassandra_ValidationFails() throws Exception { + try (MockedStatic mockLoader = + mockStatic(CassandraDriverConfigLoader.class)) { + String testGcsPath = "gs://smt-test-bucket/cassandraConfig.conf"; + mockLoader + .when(() -> CassandraDriverConfigLoader.getOptionsMapFromFile(testGcsPath)) + .thenReturn(null); + + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> parser.parseConfiguration("cassandra", testGcsPath)); + assertEquals("optionsMap cannot be null", exception.getMessage()); + } + } + + @Test + public void testParseConfiguration_Jdbc_EmptyShardConfigs() throws Exception { + File tempFile = tempFolder.newFile("jdbc-empty-shards-config.json"); + String jdbcJson = "{\n \"shardConfigs\": []\n}"; + Files.writeString(tempFile.toPath(), jdbcJson); + + Exception exception = + assertThrows( + IllegalArgumentException.class, + () -> parser.parseConfiguration("mysql", tempFile.getAbsolutePath())); + assertEquals("shardConfigs cannot be null or empty", exception.getMessage()); + } }