diff --git a/.github/workflows/java-test-oceanbase-ce.yml b/.github/workflows/java-test-oceanbase-ce.yml new file mode 100644 index 0000000..ebcc67b --- /dev/null +++ b/.github/workflows/java-test-oceanbase-ce.yml @@ -0,0 +1,102 @@ +name: java test oceanbase-ce + +on: + workflow_call: + inputs: + cache_key: + required: true + type: string + image_file: + required: true + type: string + mode: + required: true + type: string + cluster_name: + required: false + type: string + default: 'obcluster' + port: + required: true + type: string + sys_password: + required: false + type: string + default: '' + test_tenant: + required: false + type: string + default: 'test' + test_password: + required: false + type: string + default: '' + init_sql: + required: false + type: string + default: '' + +jobs: + test-oceanbase-ce: + runs-on: ubuntu-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.cache_key }} + path: /tmp + + - name: Load Docker image + run: docker load -i /tmp/${{ inputs.image_file }} + + - name: Start Docker container + uses: oceanbase/setup-oceanbase-ce@v1 + with: + image_name: oceanbase-ce + container_name: oceanbase-ce + mode: ${{ inputs.mode }} + cluster_name: ${{ inputs.cluster_name }} + sql_port: ${{ inputs.port }} + sys_root_password: ${{ inputs.sys_password }} + tenant_name: ${{ inputs.test_tenant }} + tenant_root_password: ${{ inputs.test_password }} + init_sql: ${{ inputs.init_sql }} + + - name: Set server IP + id: set_server_ip + run: | + if [ "${{ inputs.mode }}" == "slim" ]; then + echo "Use '127.0.0.1' as server_ip on slim mode." + echo "server_ip=127.0.0.1" >> $GITHUB_OUTPUT + else + echo "Getting IP from container..." + container_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' oceanbase-ce) + echo "Container IP is $container_ip. Setting server_ip to container IP." + echo "server_ip=$container_ip" >> $GITHUB_OUTPUT + fi + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'zulu' + + - name: Build test project + run: | + cd test + mvn install -DskipTests=true + + - name: Test Docker container + env: + server_ip: ${{ steps.set_server_ip.outputs.server_ip }} + cluster_name: ${{ inputs.cluster_name }} + port: ${{ inputs.port }} + sys_password: ${{ inputs.sys_password }} + test_tenant: ${{ inputs.test_tenant }} + test_password: ${{ inputs.test_password }} + run: | + cd test + mvn verify -Dtest=OceanBaseCETest -DfailIfNoTests=false diff --git a/.github/workflows/test-oceanbase-ce.yml b/.github/workflows/test-oceanbase-ce.yml index b6186f9..178235c 100644 --- a/.github/workflows/test-oceanbase-ce.yml +++ b/.github/workflows/test-oceanbase-ce.yml @@ -2,13 +2,16 @@ name: test oceanbase-ce on: push: - paths: - - '.github/workflows/test-oceanbase-ce.yml' - - 'oceanbase-ce/**' + branches: [ main ] pull_request: paths: - - '.github/workflows/test-oceanbase-ce.yml' + - '.github/workflows/**-oceanbase-ce.yml' - 'oceanbase-ce/**' + - 'test/**' + +concurrency: + group: test-oceanbase-ce-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: build: @@ -48,26 +51,30 @@ jobs: path: oceanbase-ce.tar test-slim: - runs-on: ubuntu-latest needs: build - steps: - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: oceanbase-ce - path: /tmp - - - name: Load Docker image - run: docker load -i /tmp/oceanbase-ce.tar + uses: ./.github/workflows/java-test-oceanbase-ce.yml + with: + cache_key: oceanbase-ce + image_file: oceanbase-ce.tar + mode: slim + port: 1234 + test_password: 123456 + init_sql: "USE test; + CREATE TABLE user(id INT(10) PRIMARY KEY, name VARCHAR(20)); + INSERT INTO user VALUES (1, 'tom'), (2, 'jerry');" - - name: Start Docker container - uses: oceanbase/setup-oceanbase-ce@v1 - with: - image_name: oceanbase-ce - container_name: ob-slim - fastboot: false - - - name: Test Docker container - run: | - docker exec ob-slim obclient -h127.0.0.1 -P2881 -uroot -e 'select version()' - docker exec ob-slim obclient -h127.0.0.1 -P2881 -uroot@test -e 'show databases' + test-mini: + needs: build + uses: ./.github/workflows/java-test-oceanbase-ce.yml + with: + cache_key: oceanbase-ce + image_file: oceanbase-ce.tar + cluster_name: github-action + mode: mini + port: 1234 + sys_password: 1234567 + test_tenant: mini + test_password: 7654321 + init_sql: "USE test; + CREATE TABLE user(id INT(10) PRIMARY KEY, name VARCHAR(20)); + INSERT INTO user VALUES (1, 'tom'), (2, 'jerry');" diff --git a/.gitignore b/.gitignore index 7c6ddf0..72cc5b1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .idea .vscode *.log +target oceanbase-ce/Dockerfile.inner diff --git a/test/pom.xml b/test/pom.xml new file mode 100644 index 0000000..c82fb8b --- /dev/null +++ b/test/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + com.oceanbase + docker-images-test + 1.0-SNAPSHOT + + + UTF-8 + ${encoding} + ${encoding} + + 1.8 + ${java.version} + ${java.version} + + + + + mysql + mysql-connector-java + 5.1.47 + test + + + com.mysql + mysql-connector-j + 8.4.0 + test + + + com.google.protobuf + protobuf-java + + + + + org.junit.jupiter + junit-jupiter + 5.10.2 + test + + + org.apache.logging.log4j + log4j-slf4j-impl + 2.23.1 + test + + + + + + + maven-surefire-plugin + 3.1.2 + + + maven-failsafe-plugin + 3.1.2 + + + com.diffplug.spotless + spotless-maven-plugin + 2.27.1 + + + + 1.7 + + + + + + + UTF-8 + 4 + true + false + false + true + false + false + false + false + + + Leading blank line + project + project + + + + true + + + + + spotless-check + + check + + validate + + + + + + + diff --git a/test/src/test/java/com/oceanbase/test/OceanBaseCETest.java b/test/src/test/java/com/oceanbase/test/OceanBaseCETest.java new file mode 100644 index 0000000..1f5481a --- /dev/null +++ b/test/src/test/java/com/oceanbase/test/OceanBaseCETest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2024 OceanBase. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.oceanbase.test; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLException; +import java.util.Properties; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OceanBaseCETest { + + private static final Logger LOG = LoggerFactory.getLogger(OceanBaseCETest.class); + + static Stream testOceanBaseCEArgs() { + // non-null env vars + String serverIP = Utils.getNonEmptyEnv("server_ip"); + String clusterName = Utils.getNonEmptyEnv("cluster_name"); + String port = Utils.getNonEmptyEnv("port"); + String testTenant = Utils.getNonEmptyEnv("test_tenant"); + + // nullable env vars + String sysPassword = System.getenv("sys_password"); + String testPassword = System.getenv("test_password"); + + return Stream.of( + Arguments.of(true, serverIP, clusterName, port, "sys", "root", sysPassword), + Arguments.of(false, serverIP, clusterName, port, "sys", "root", sysPassword), + Arguments.of( + true, + serverIP, + clusterName, + port, + testTenant, + "root@" + testTenant, + testPassword), + Arguments.of( + false, + serverIP, + clusterName, + port, + testTenant, + "root@" + testTenant, + testPassword)); + } + + @ParameterizedTest + @MethodSource("testOceanBaseCEArgs") + public void testOceanBaseCE( + boolean useLegacyDriver, + String serverIP, + String clusterName, + String port, + String tenantName, + String username, + String password) { + + boolean slimMode = "127.0.0.1".equals(serverIP); + + LOG.info( + "Testing with args: [useLegacyDriver: {}, server_ip: {}, cluster_name: {}, port: {}, username: {}, password: {}]", + useLegacyDriver, + serverIP, + clusterName, + port, + username, + password); + + String jdbcUrl = String.format("jdbc:mysql://127.0.0.1:%s/test?useSSL=false", port); + + Properties props = new Properties(); + props.setProperty("user", username); + if (password != null) { + props.put("password", password); + } + + Driver driver = Utils.getDriver(useLegacyDriver); + try (Connection conn = driver.connect(jdbcUrl, props)) { + LOG.info("Connected to OceanBase CE successfully"); + + Assertions.assertNotNull(Utils.getVersionComment(conn)); + + Assertions.assertEquals(serverIP + ":2882:2881", Utils.getRSList(conn)); + Assertions.assertEquals(clusterName, Utils.getClusterName(conn)); + Assertions.assertEquals(tenantName, Utils.getTenantName(conn)); + + if (!slimMode) { + Assertions.assertNotNull(Utils.getConfigUrl(conn)); + } + + if ("sys".equals(tenantName)) { + Assertions.assertEquals(serverIP, Utils.getServerIP(conn)); + } else { + Assertions.assertEquals(2, Utils.getTableRowsCount(conn, "user")); + } + } catch (SQLException e) { + Assertions.fail(e); + } + } +} diff --git a/test/src/test/java/com/oceanbase/test/Utils.java b/test/src/test/java/com/oceanbase/test/Utils.java new file mode 100644 index 0000000..9b767d2 --- /dev/null +++ b/test/src/test/java/com/oceanbase/test/Utils.java @@ -0,0 +1,133 @@ +/* + * Copyright 2024 OceanBase. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.oceanbase.test; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class Utils { + + private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(Utils.class); + + public static Driver getDriver(boolean legacy) { + String className = legacy ? "com.mysql.jdbc.Driver" : "com.mysql.cj.jdbc.Driver"; + try { + return (Driver) Class.forName(className).getDeclaredConstructor().newInstance(); + } catch (Throwable throwable) { + throw new RuntimeException("Failed to load JDBC driver", throwable); + } + } + + public static String getNonEmptyEnv(String key) { + String value = System.getenv(key); + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException("Environment variable '" + key + "' is required"); + } + return value; + } + + public static String getEnvOrDefault(String name, String defaultValue) { + String env = System.getenv(name); + return env == null ? defaultValue : env; + } + + public static String getVersionComment(Connection connection) { + return (String) + query( + connection, + "version_comment", + "SHOW VARIABLES LIKE 'version_comment'", + rs -> rs.next() ? rs.getString("VALUE") : null); + } + + public static String getClusterName(Connection connection) { + return (String) + query( + connection, + "cluster", + "SHOW PARAMETERS LIKE 'cluster'", + rs -> rs.next() ? rs.getString("VALUE") : null); + } + + public static String getTenantName(Connection connection) { + return (String) + query( + connection, + "tenant", + "SHOW TENANT", + rs -> rs.next() ? rs.getString(1) : null); + } + + public static String getServerIP(Connection connection) { + return (String) + query( + connection, + "svr_ip", + "SELECT svr_ip FROM oceanbase.__all_server", + rs -> rs.next() ? rs.getString(1) : null); + } + + public static String getRSList(Connection connection) { + return (String) + query( + connection, + "rootservice_list", + "SHOW PARAMETERS LIKE 'rootservice_list'", + rs -> rs.next() ? rs.getString("VALUE") : null); + } + + public static String getConfigUrl(Connection connection) { + return (String) + query( + connection, + "obconfig_url", + "SHOW PARAMETERS LIKE 'obconfig_url'", + rs -> rs.next() ? rs.getString("VALUE") : null); + } + + public static int getTableRowsCount(Connection connection, String tableName) { + return (int) + query( + connection, + "table '" + tableName + "' rows count", + "SELECT COUNT(1) FROM " + tableName, + rs -> rs.next() ? rs.getInt(1) : 0); + } + + @FunctionalInterface + interface ResultSetConsumer { + Object apply(ResultSet rs) throws SQLException; + } + + static Object query( + Connection connection, + String queryName, + String sql, + ResultSetConsumer resultSetConsumer) { + try (Statement statement = connection.createStatement()) { + ResultSet rs = statement.executeQuery(sql); + Object result = resultSetConsumer.apply(rs); + LOG.info("Query: {}, got result: {}", queryName, result); + return result; + } catch (SQLException e) { + throw new RuntimeException("Failed to execute sql: " + sql, e); + } + } +} diff --git a/test/src/test/resources/log4j2-test.properties b/test/src/test/resources/log4j2-test.properties new file mode 100644 index 0000000..3964819 --- /dev/null +++ b/test/src/test/resources/log4j2-test.properties @@ -0,0 +1,27 @@ +#################################################################### +# Copyright 2024 OceanBase. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +#################################################################### + +# Set root logger level to OFF to not flood build logs +# set manually to INFO for debugging purposes +rootLogger.level=INFO +rootLogger.appenderRef.test.ref = TestLogger + +appender.testlogger.name = TestLogger +appender.testlogger.type = CONSOLE +appender.testlogger.target = SYSTEM_ERR +appender.testlogger.layout.type = PatternLayout +appender.testlogger.layout.pattern = %-4r [%t] %-5p %c - %m%n