From 228a1f59d27c508dfe3a603258c1dce805d76b77 Mon Sep 17 00:00:00 2001 From: Qi Yu Date: Wed, 11 Sep 2024 10:08:59 +0800 Subject: [PATCH] [#4882] improvement(core): Add some ut for MySQL and PostgreSQL storage backend. (#4898) ### What changes were proposed in this pull request? Add some UTs to test MySQL and PostgreSQL storage backend. ### Why are the changes needed? To improve code robustness. Fix: #4882 ### Does this PR introduce _any_ user-facing change? N/A. ### How was this patch tested? N/A --------- Co-authored-by: Jerry Shao --- core/build.gradle.kts | 3 + .../gravitino/storage/TestEntityStorage.java | 128 ++++++++++++------ .../test/container/ContainerSuite.java | 11 ++ .../integration/test/util/AbstractIT.java | 67 +++++---- 4 files changed, 130 insertions(+), 79 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index d44023001aa..cb07b49d971 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -51,11 +51,14 @@ dependencies { testCompileOnly(libs.lombok) testImplementation(project(":integration-test-common", "testArtifacts")) + testImplementation(project(":server-common")) + testImplementation(project(":clients:client-java")) testImplementation(libs.awaitility) testImplementation(libs.junit.jupiter.api) testImplementation(libs.junit.jupiter.params) testImplementation(libs.mockito.core) testImplementation(libs.mysql.driver) + testImplementation(libs.postgresql.driver) testImplementation(libs.testcontainers) testRuntimeOnly(libs.junit.jupiter.engine) diff --git a/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java b/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java index d233ac5bafc..ecb13c06b96 100644 --- a/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java +++ b/core/src/test/java/org/apache/gravitino/storage/TestEntityStorage.java @@ -19,10 +19,7 @@ package org.apache.gravitino.storage; -import static org.apache.gravitino.Configs.DEFAULT_ENTITY_KV_STORE; import static org.apache.gravitino.Configs.DEFAULT_ENTITY_RELATIONAL_STORE; -import static org.apache.gravitino.Configs.ENTITY_KV_ROCKSDB_BACKEND_PATH; -import static org.apache.gravitino.Configs.ENTITY_KV_STORE; import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER; import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD; import static org.apache.gravitino.Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PATH; @@ -32,7 +29,6 @@ import static org.apache.gravitino.Configs.ENTITY_STORE; import static org.apache.gravitino.Configs.RELATIONAL_ENTITY_STORE; import static org.apache.gravitino.Configs.STORE_DELETE_AFTER_TIME; -import static org.apache.gravitino.Configs.STORE_TRANSACTION_MAX_SKEW_TIME; import static org.apache.gravitino.Configs.VERSION_RETENTION_COUNT; import com.google.common.base.Preconditions; @@ -51,6 +47,7 @@ import java.util.UUID; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.gravitino.Catalog; import org.apache.gravitino.Config; import org.apache.gravitino.Configs; @@ -68,6 +65,8 @@ import org.apache.gravitino.exceptions.NoSuchEntityException; import org.apache.gravitino.exceptions.NonEmptyEntityException; import org.apache.gravitino.file.Fileset; +import org.apache.gravitino.integration.test.container.ContainerSuite; +import org.apache.gravitino.integration.test.util.AbstractIT; import org.apache.gravitino.meta.AuditInfo; import org.apache.gravitino.meta.BaseMetalake; import org.apache.gravitino.meta.CatalogEntity; @@ -79,14 +78,25 @@ import org.apache.gravitino.meta.TableEntity; import org.apache.gravitino.meta.TopicEntity; import org.apache.gravitino.meta.UserEntity; +import org.apache.gravitino.storage.relational.converters.H2ExceptionConverter; +import org.apache.gravitino.storage.relational.converters.MySQLExceptionConverter; +import org.apache.gravitino.storage.relational.converters.PostgreSQLExceptionConverter; +import org.apache.gravitino.storage.relational.converters.SQLExceptionConverterFactory; import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper; import org.apache.ibatis.session.SqlSession; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +@Tag("gravitino-docker-test") public class TestEntityStorage { + private static final Logger LOG = LoggerFactory.getLogger(TestEntityStorage.class); + public static final String KV_STORE_PATH = "/tmp/gravitino_kv_entityStore_" + UUID.randomUUID().toString().replace("-", ""); @@ -96,50 +106,72 @@ public class TestEntityStorage { private static final String H2_FILE = DB_DIR + ".mv.db"; static Object[] storageProvider() { - return new Object[] {Configs.RELATIONAL_ENTITY_STORE}; + return new Object[] {"h2", "mysql", "postgresql"}; + } + + @AfterEach + void closeSuit() throws IOException { + ContainerSuite.getInstance().close(); } private void init(String type, Config config) { Preconditions.checkArgument(StringUtils.isNotBlank(type)); - if (type.equals(Configs.KV_STORE_KEY)) { - try { - FileUtils.deleteDirectory(FileUtils.getFile(KV_STORE_PATH)); - } catch (Exception e) { - // Ignore - } - Mockito.when(config.get(ENTITY_STORE)).thenReturn("kv"); - Mockito.when(config.get(ENTITY_KV_STORE)).thenReturn(DEFAULT_ENTITY_KV_STORE); - Mockito.when(config.get(Configs.ENTITY_SERDE)).thenReturn("proto"); - Mockito.when(config.get(ENTITY_KV_ROCKSDB_BACKEND_PATH)).thenReturn(KV_STORE_PATH); - - Assertions.assertEquals(KV_STORE_PATH, config.get(ENTITY_KV_ROCKSDB_BACKEND_PATH)); - Mockito.when(config.get(STORE_TRANSACTION_MAX_SKEW_TIME)).thenReturn(1000L); - Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 1000L); - Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L); - } else if (type.equals(Configs.RELATIONAL_ENTITY_STORE)) { - File dir = new File(DB_DIR); - if (dir.exists() || !dir.isDirectory()) { - dir.delete(); + File dir = new File(DB_DIR); + if (dir.exists() || !dir.isDirectory()) { + dir.delete(); + } + dir.mkdirs(); + Mockito.when(config.get(ENTITY_STORE)).thenReturn(RELATIONAL_ENTITY_STORE); + Mockito.when(config.get(ENTITY_RELATIONAL_STORE)).thenReturn(DEFAULT_ENTITY_RELATIONAL_STORE); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PATH)).thenReturn(DB_DIR); + Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 1000L); + Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L); + + try { + if (type.equalsIgnoreCase("h2")) { + // The following properties are used to create the JDBC connection; they are just for test, + // in the real world, they will be set automatically by the configuration file if you set + // ENTITY_RELATIONAL_STOR as EMBEDDED_ENTITY_RELATIONAL_STORE. + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_URL)) + .thenReturn(String.format("jdbc:h2:%s;DB_CLOSE_DELAY=-1;MODE=MYSQL", DB_DIR)); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_USER)).thenReturn("gravitino"); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD)).thenReturn("gravitino"); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER)).thenReturn("org.h2.Driver"); + + FieldUtils.writeStaticField( + SQLExceptionConverterFactory.class, "converter", new H2ExceptionConverter(), true); + + } else if (type.equalsIgnoreCase("mysql")) { + String mysqlJdbcUrl = AbstractIT.startAndInitMySQLBackend(); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_URL)).thenReturn(mysqlJdbcUrl); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_USER)).thenReturn("root"); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD)).thenReturn("root"); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER)) + .thenReturn("com.mysql.cj.jdbc.Driver"); + + FieldUtils.writeStaticField( + SQLExceptionConverterFactory.class, "converter", new MySQLExceptionConverter(), true); + + } else if (type.equalsIgnoreCase("postgresql")) { + String postgreSQLJdbcUrl = AbstractIT.startAndInitPGBackend(); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_URL)).thenReturn(postgreSQLJdbcUrl); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_USER)).thenReturn("root"); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD)).thenReturn("root"); + Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER)) + .thenReturn("org.postgresql.Driver"); + + FieldUtils.writeStaticField( + SQLExceptionConverterFactory.class, + "converter", + new PostgreSQLExceptionConverter(), + true); + + } else { + throw new UnsupportedOperationException("Unsupported entity store type: " + type); } - dir.mkdirs(); - Mockito.when(config.get(ENTITY_STORE)).thenReturn(RELATIONAL_ENTITY_STORE); - Mockito.when(config.get(ENTITY_RELATIONAL_STORE)).thenReturn(DEFAULT_ENTITY_RELATIONAL_STORE); - Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PATH)).thenReturn(DB_DIR); - - // The following properties are used to create the JDBC connection; they are just for test, in - // the real world, - // they will be set automatically by the configuration file if you set ENTITY_RELATIONAL_STORE - // as EMBEDDED_ENTITY_RELATIONAL_STORE. - Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_URL)) - .thenReturn(String.format("jdbc:h2:%s;DB_CLOSE_DELAY=-1;MODE=MYSQL", DB_DIR)); - Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_USER)).thenReturn("gravitino"); - Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD)).thenReturn("gravitino"); - Mockito.when(config.get(ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER)).thenReturn("org.h2.Driver"); - - Mockito.when(config.get(STORE_DELETE_AFTER_TIME)).thenReturn(20 * 60 * 1000L); - Mockito.when(config.get(VERSION_RETENTION_COUNT)).thenReturn(1L); - } else { - throw new UnsupportedOperationException("Unsupported entity store type: " + type); + } catch (Exception e) { + LOG.error("Failed to init entity store", e); + throw new RuntimeException(e); } } @@ -151,7 +183,7 @@ private void destroy(String type) { } catch (Exception e) { // Ignore } - } else if (type.equals(Configs.RELATIONAL_ENTITY_STORE)) { + } else if (type.equalsIgnoreCase("h2") || type.equalsIgnoreCase("mysql")) { dropAllTables(); File dir = new File(DB_DIR); if (dir.exists()) { @@ -159,6 +191,8 @@ private void destroy(String type) { } FileUtils.deleteQuietly(new File(H2_FILE)); + } else if (type.equalsIgnoreCase("postgresql")) { + // Do nothing } else { throw new UnsupportedOperationException("Unsupported entity store type: " + type); } @@ -876,6 +910,8 @@ void testSameNameUnderANameSpace(String type) throws IOException { store.get(identifier, Entity.EntityType.TABLE, TableEntity.class); store.get(identifier, Entity.EntityType.FILESET, FilesetEntity.class); store.get(changedNameIdentifier, Entity.EntityType.TOPIC, TopicEntity.class); + + destroy(type); } } @@ -2104,7 +2140,7 @@ private void validateDeletedTable(EntityStore store) throws IOException { @ParameterizedTest @MethodSource("storageProvider") void testOptimizedDeleteForKv(String type) throws IOException { - if ("relational".equalsIgnoreCase(type)) { + if (!"kv".equalsIgnoreCase(type)) { return; } @@ -2182,6 +2218,8 @@ void testOptimizedDeleteForKv(String type) throws IOException { Assertions.assertDoesNotThrow( () -> store.get(filesetEntity1.nameIdentifier(), EntityType.FILESET, FilesetEntity.class)); + + destroy(type); } } } diff --git a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java index 4f477e13649..bc311c4bcda 100644 --- a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java +++ b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/container/ContainerSuite.java @@ -563,6 +563,17 @@ private static long[] cidrToRange(String cidr) throws Exception { public void close() throws IOException { try { closer.close(); + mySQLContainer = null; + mySQLVersion5Container = null; + hiveContainer = null; + hiveRangerContainer = null; + trinoContainer = null; + trinoITContainers = null; + rangerContainer = null; + kafkaContainer = null; + dorisContainer = null; + kerberosHiveContainer = null; + pgContainerMap.clear(); } catch (Exception e) { LOG.error("Failed to close ContainerEnvironment", e); } diff --git a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/AbstractIT.java b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/AbstractIT.java index 3e264652b31..6644e1f646c 100644 --- a/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/AbstractIT.java +++ b/integration-test-common/src/test/java/org/apache/gravitino/integration/test/util/AbstractIT.java @@ -32,7 +32,6 @@ import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; -import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; @@ -161,21 +160,12 @@ protected static void downLoadJDBCDriver() throws IOException { } } - protected static void setPGBackend() throws SQLException { - String pgUrlWithoutSchema = POSTGRESQL_CONTAINER.getJdbcUrl(META_DATA); - customConfigs.put(Configs.ENTITY_STORE_KEY, "relational"); - customConfigs.put(Configs.ENTITY_RELATIONAL_STORE_KEY, "JDBCBackend"); - customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL_KEY, pgUrlWithoutSchema); - customConfigs.put( - Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER_KEY, - POSTGRESQL_CONTAINER.getDriverClassName(META_DATA)); - customConfigs.put( - Configs.ENTITY_RELATIONAL_JDBC_BACKEND_USER_KEY, POSTGRESQL_CONTAINER.getUsername()); - customConfigs.put( - Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD_KEY, POSTGRESQL_CONTAINER.getPassword()); - - LOG.info("PG URL: {}", pgUrlWithoutSchema); + public static String startAndInitPGBackend() { + META_DATA = PG_JDBC_BACKEND; + containerSuite.startPostgreSQLContainer(META_DATA); + POSTGRESQL_CONTAINER = containerSuite.getPostgreSQLContainer(); + String pgUrlWithoutSchema = POSTGRESQL_CONTAINER.getJdbcUrl(META_DATA); String randomSchemaName = RandomStringUtils.random(10, true, false); // Connect to the PostgreSQL docker and create a schema String currentExecuteSql = ""; @@ -216,18 +206,17 @@ protected static void setPGBackend() throws SQLException { pgUrlWithoutSchema = pgUrlWithoutSchema + "?currentSchema=" + randomSchemaName; customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL_KEY, pgUrlWithoutSchema); + + LOG.info("PG URL: {}", pgUrlWithoutSchema); + return pgUrlWithoutSchema; } - private static void setMySQLBackend() { - String mysqlUrl = MYSQL_CONTAINER.getJdbcUrl(META_DATA); - customConfigs.put(Configs.ENTITY_STORE_KEY, "relational"); - customConfigs.put(Configs.ENTITY_RELATIONAL_STORE_KEY, "JDBCBackend"); - customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL_KEY, mysqlUrl); - customConfigs.put( - Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER_KEY, "com.mysql.cj.jdbc.Driver"); - customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_USER_KEY, "root"); - customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD_KEY, "root"); + public static String startAndInitMySQLBackend() { + META_DATA = TestDatabaseName.MYSQL_JDBC_BACKEND; + containerSuite.startMySQLContainer(META_DATA); + MYSQL_CONTAINER = containerSuite.getMySQLContainer(); + String mysqlUrl = MYSQL_CONTAINER.getJdbcUrl(META_DATA); LOG.info("MySQL URL: {}", mysqlUrl); // Connect to the mysql docker and create a databases try (Connection connection = @@ -255,6 +244,7 @@ private static void setMySQLBackend() { for (String sql : initMySQLBackendSqls) { statement.execute(sql); } + return mysqlUrl; } catch (Exception e) { LOG.error("Failed to create database in mysql", e); throw new RuntimeException(e); @@ -279,18 +269,27 @@ public static void startIntegrationTest() throws Exception { if ("MySQL".equalsIgnoreCase(System.getenv("jdbcBackend"))) { // Start MySQL docker instance. - META_DATA = TestDatabaseName.MYSQL_JDBC_BACKEND; - containerSuite.startMySQLContainer(META_DATA); - MYSQL_CONTAINER = containerSuite.getMySQLContainer(); - - setMySQLBackend(); + String jdbcURL = startAndInitMySQLBackend(); + customConfigs.put(Configs.ENTITY_STORE_KEY, "relational"); + customConfigs.put(Configs.ENTITY_RELATIONAL_STORE_KEY, "JDBCBackend"); + customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL_KEY, jdbcURL); + customConfigs.put( + Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER_KEY, "com.mysql.cj.jdbc.Driver"); + customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_USER_KEY, "root"); + customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD_KEY, "root"); } else if ("PostgreSQL".equalsIgnoreCase(System.getenv("jdbcBackend"))) { // Start PostgreSQL docker instance. - META_DATA = PG_JDBC_BACKEND; - containerSuite.startPostgreSQLContainer(META_DATA); - POSTGRESQL_CONTAINER = containerSuite.getPostgreSQLContainer(); - - setPGBackend(); + String pgJdbcUrl = startAndInitPGBackend(); + customConfigs.put(Configs.ENTITY_STORE_KEY, "relational"); + customConfigs.put(Configs.ENTITY_RELATIONAL_STORE_KEY, "JDBCBackend"); + customConfigs.put(Configs.ENTITY_RELATIONAL_JDBC_BACKEND_URL_KEY, pgJdbcUrl); + customConfigs.put( + Configs.ENTITY_RELATIONAL_JDBC_BACKEND_DRIVER_KEY, + POSTGRESQL_CONTAINER.getDriverClassName(META_DATA)); + customConfigs.put( + Configs.ENTITY_RELATIONAL_JDBC_BACKEND_USER_KEY, POSTGRESQL_CONTAINER.getUsername()); + customConfigs.put( + Configs.ENTITY_RELATIONAL_JDBC_BACKEND_PASSWORD_KEY, POSTGRESQL_CONTAINER.getPassword()); } File baseDir = new File(System.getProperty("java.io.tmpdir"));