Skip to content

Commit c00c255

Browse files
feat(jdbc/postgres): add compatibility for GraalVM native image (#805)
* ci: graalVM native image profile * ci: kokoro build config * ci: fixed execution of GraalVM native image directory * ci: explanation for changing directories * revert irrelevant changes * no need to specify old surefire plugin With recent surefire plugin release (> 3.0 M4), we no longer need to specify old version. https://graalvm.github.io/native-build-tools/latest/maven-plugin.html#testing-support-version-compatibility * ci: build file to run test directory * ci: run all GraalVM tests regardless of the failure statuses * format * ci: relying on new GAX release for native-image metadata * ci: adding junit dependency declaration * ci: not specifying ProtobufFeature * moving CloudSqlFeature to be in this repo CloudSqlFeature used to be in a different repository ( https://github.com/GoogleCloudPlatform/native-image-support-java). Moving the class to this file because t's better to move the GraalVM native-image configuration to the configured class. * Forcing the versions for Enforcer's dependency convergence rule * Copyright holder Google LLC * addResource with ConfigurationCondition * removed dependencyManagement from root * debug-print access.findClassByName for CJException * CJException is only for mysql-j-8 * Adding mysql-j-5's LocalizedErrorMessages * registering com.mysql.jdbc.log.StandardLogger for mysql-j-5 * Registering subtype of MYSQLNonTransientException * format * access.registerAsUsed * Registering classes for reflection * BCSSLEngine initialize at runtime * Initializing BouncyCastleAlpnSslUtils at runtime * test: limit the scope of the test to jdbc/postgres * test: not measuring nativeimage test coverage * ci: added comments for dependencies * build: removed unnecessary transitive dependencies * ci: moved enforcer rule suppression for native to native profile * deps: reverted exlusions in r2dbc modules * ci: dependencyManagement for svm to pass enforcer rule Co-authored-by: Shubha Rajan <shubhadayini@google.com>
1 parent d796faf commit c00c255

File tree

12 files changed

+463
-12
lines changed

12 files changed

+463
-12
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Format: //devtools/kokoro/config/proto/build.proto
16+
17+
# Get secrets for tests.
18+
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/cloud-sql/java-connector"
19+
20+
# Download trampoline resources.
21+
gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline"
22+
23+
# Use the trampoline script to run in docker.
24+
build_file: "cloud-sql-jdbc-socket-factory/.kokoro/trampoline.sh"
25+
26+
env_vars: {
27+
key: "TRAMPOLINE_IMAGE"
28+
value: "gcr.io/cloud-devrel-kokoro-resources/graalvm"
29+
}
30+
31+
# Tell the trampoline which tests to run.
32+
env_vars: {
33+
key: "TRAMPOLINE_BUILD_FILE"
34+
value: "github/cloud-sql-jdbc-socket-factory/.kokoro/tests/run_tests_graalvm_native.sh"
35+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Format: //devtools/kokoro/config/proto/build.proto
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Format: //devtools/kokoro/config/proto/build.proto
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Format: //devtools/kokoro/config/proto/build.proto
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#! /bin/bash
2+
# Copyright 2020 Google Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# `-e` enables the script to automatically fail when a command fails
17+
set -e
18+
19+
# Kokoro setup
20+
if [ -n "$KOKORO_GFILE_DIR" ]; then
21+
# Move into project directory
22+
cd github/cloud-sql-jdbc-socket-factory
23+
# source secrets
24+
source "${KOKORO_GFILE_DIR}/TEST_SECRETS.sh"
25+
export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/testing-service-account.json"
26+
fi
27+
28+
echo -e "******************** Installing modules... ********************\n"
29+
mvn -e -B install -DskipTests
30+
echo -e "******************** Installation complete. ********************\n"
31+
echo "JAVA_HOME: $JAVA_HOME"
32+
echo "Java version:"
33+
java -version
34+
35+
# Why change directories to run the tests? Because GraalVM test execution
36+
# requires at least one matching test per Maven module. Not all modules in this
37+
# repository have "*IntegrationTests.java".
38+
# https://github.com/graalvm/native-build-tools/issues/188
39+
set +e
40+
declare -i return_code=0
41+
42+
# Currently, jdbc/postgres works with GraalVM native image.
43+
# TODO(#824): Provide GraalVM configuration and enable native image tests below:
44+
# jdbc/mysql-j-5 jdbc/mysql-j-8 jdbc/sqlserver r2dbc/sqlserver r2dbc/sqlserver
45+
# r2dbc/mysql
46+
# https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory/issues/824
47+
for test_directory in jdbc/postgres; do
48+
pushd ${test_directory}
49+
echo -e "******************** Running tests in ${test_directory} ********************\n"
50+
# Dependency convergence enforcer rule would fail with the junit dependencies
51+
# specified in "native" profile. The test-scope dependencies do not have any
52+
# effect to library users' class path.
53+
mvn -e -B clean verify -P e2e,native
54+
result=$?
55+
return_code=$((return_code || result))
56+
echo -e "******************** Tests complete in ${test_directory}, result: $result ********************\n"
57+
popd
58+
done
59+
exit ${return_code}

core/pom.xml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,71 @@
180180
<version>1.6.0</version>
181181
</dependency>
182182

183+
<!-- com.google.cloud.sql.nativeimage.CloudSqlFeature needs the GraalVM
184+
dependencies for compilation. The provided-scope dependencies do not add
185+
additional dependencies to library users. -->
186+
<dependency>
187+
<groupId>org.graalvm.nativeimage</groupId>
188+
<artifactId>svm</artifactId>
189+
<version>${graalvm.version}</version>
190+
<scope>provided</scope>
191+
</dependency>
192+
<dependency>
193+
<groupId>org.graalvm.sdk</groupId>
194+
<artifactId>graal-sdk</artifactId>
195+
<version>${graalvm.version}</version>
196+
<scope>provided</scope>
197+
</dependency>
198+
<!-- GAX provides native-image metadata for core Google libraries, such as
199+
Google API Client and Google HTTP Client. Users' GraalVM native image
200+
compilation needs the metadata. Therefore, this is not part of a profile
201+
or provided-scope. -->
202+
<dependency>
203+
<groupId>com.google.api</groupId>
204+
<artifactId>gax</artifactId>
205+
<version>2.16.0</version>
206+
<exclusions>
207+
<!-- Native image users only need GAX's native-image metadata. GAX's
208+
dependencies are unnecessary for normal users and native image users -->
209+
<exclusion>
210+
<groupId>com.google.api</groupId>
211+
<artifactId>api-common</artifactId>
212+
</exclusion>
213+
<exclusion>
214+
<groupId>com.google.api.grpc</groupId>
215+
<artifactId>proto-google-common-protos</artifactId>
216+
</exclusion>
217+
<exclusion>
218+
<groupId>com.google.auth</groupId>
219+
<artifactId>google-auth-library-credentials</artifactId>
220+
</exclusion>
221+
<exclusion>
222+
<groupId>com.google.auth</groupId>
223+
<artifactId>google-auth-library-oauth2-http</artifactId>
224+
</exclusion>
225+
<exclusion>
226+
<groupId>com.google.code.findbugs</groupId>
227+
<artifactId>jsr305</artifactId>
228+
</exclusion>
229+
<exclusion>
230+
<groupId>com.google.guava</groupId>
231+
<artifactId>guava</artifactId>
232+
</exclusion>
233+
<exclusion>
234+
<groupId>com.google.protobuf</groupId>
235+
<artifactId>protobuf-java</artifactId>
236+
</exclusion>
237+
<exclusion>
238+
<groupId>io.opencensus</groupId>
239+
<artifactId>opencensus-api</artifactId>
240+
</exclusion>
241+
<exclusion>
242+
<groupId>org.threeten</groupId>
243+
<artifactId>threetenbp</artifactId>
244+
</exclusion>
245+
</exclusions>
246+
</dependency>
247+
183248
<dependency>
184249
<groupId>org.mockito</groupId>
185250
<artifactId>mockito-core</artifactId>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2022 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.sql.nativeimage;
18+
19+
import com.google.api.gax.nativeimage.NativeImageUtils;
20+
import com.oracle.svm.core.annotate.AutomaticFeature;
21+
import com.oracle.svm.core.configure.ResourcesRegistry;
22+
import org.graalvm.nativeimage.ImageSingletons;
23+
import org.graalvm.nativeimage.hosted.Feature;
24+
import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
25+
import org.graalvm.nativeimage.hosted.RuntimeReflection;
26+
import org.graalvm.nativeimage.impl.ConfigurationCondition;
27+
28+
/**
29+
* Registers GraalVM configuration for the Cloud SQL libraries.
30+
*
31+
* <p>This class is only used when this library is used in <a
32+
* href="https://www.graalvm.org/22.0/reference-manual/native-image/">GraalVM native image</a>
33+
* compilation.
34+
*/
35+
@AutomaticFeature
36+
final class CloudSqlFeature implements Feature {
37+
38+
private static final String CLOUD_SQL_SOCKET_CLASS =
39+
"com.google.cloud.sql.core.CoreSocketFactory";
40+
41+
private static final String POSTGRES_SOCKET_CLASS = "com.google.cloud.sql.postgres.SocketFactory";
42+
43+
private static final String MYSQL_SOCKET_CLASS = "com.google.cloud.sql.mysql.SocketFactory";
44+
45+
@Override
46+
public void beforeAnalysis(BeforeAnalysisAccess access) {
47+
if (access.findClassByName(CLOUD_SQL_SOCKET_CLASS) == null) {
48+
return;
49+
}
50+
51+
// The Core Cloud SQL Socket
52+
NativeImageUtils.registerClassForReflection(access, CLOUD_SQL_SOCKET_CLASS);
53+
54+
// Resources for Cloud SQL
55+
ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class);
56+
resourcesRegistry.addResources(
57+
ConfigurationCondition.alwaysTrue(), "\\Qcom.google.cloud.sql/project.properties\\E");
58+
resourcesRegistry.addResources(
59+
ConfigurationCondition.alwaysTrue(), "\\QMETA-INF/services/java.sql.Driver\\E");
60+
61+
// Register Hikari configs if used with Cloud SQL.
62+
if (access.findClassByName("com.zaxxer.hikari.HikariConfig") != null) {
63+
NativeImageUtils.registerClassForReflection(access, "com.zaxxer.hikari.HikariConfig");
64+
65+
RuntimeReflection.register(
66+
access.findClassByName("[Lcom.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry;"));
67+
68+
RuntimeReflection.register(access.findClassByName("[Ljava.sql.Statement;"));
69+
}
70+
71+
// Register PostgreSQL driver config.
72+
if (access.findClassByName(POSTGRES_SOCKET_CLASS) != null) {
73+
NativeImageUtils.registerClassForReflection(
74+
access, "com.google.cloud.sql.postgres.SocketFactory");
75+
NativeImageUtils.registerClassForReflection(access, "org.postgresql.PGProperty");
76+
}
77+
78+
// Register MySQL driver config.
79+
if (access.findClassByName(MYSQL_SOCKET_CLASS) != null) {
80+
NativeImageUtils.registerClassForReflection(access, MYSQL_SOCKET_CLASS);
81+
82+
NativeImageUtils.registerClassForReflection(access, "com.mysql.jdbc.StandardSocketFactory");
83+
84+
NativeImageUtils.registerConstructorsForReflection(
85+
access, "com.mysql.cj.conf.url.SingleConnectionUrl");
86+
87+
// for mysql-j-5
88+
NativeImageUtils.registerConstructorsForReflection(
89+
access, "com.mysql.jdbc.log.StandardLogger");
90+
// for mysql-j-8
91+
NativeImageUtils.registerConstructorsForReflection(access, "com.mysql.cj.log.StandardLogger");
92+
93+
Class<?> cjExceptionClass = access.findClassByName("com.mysql.cj.exceptions.CJException");
94+
if (cjExceptionClass != null) {
95+
// The CJException exists only jdbc/mysql-j-8 module's dependency
96+
access.registerSubtypeReachabilityHandler(
97+
(duringAccess, exceptionClass) ->
98+
NativeImageUtils.registerClassForReflection(duringAccess, exceptionClass.getName()),
99+
cjExceptionClass);
100+
}
101+
102+
Class<?> mySqlNonTransientConnectionException =
103+
access.findClassByName(
104+
"com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException");
105+
if (mySqlNonTransientConnectionException != null) {
106+
NativeImageUtils.registerConstructorsForReflection(
107+
access, mySqlNonTransientConnectionException.getName());
108+
}
109+
110+
Class<?> mySqlNonTransientException =
111+
access.findClassByName("com.mysql.jdbc.exceptions.MySQLNonTransientException");
112+
if (mySqlNonTransientException != null) {
113+
NativeImageUtils.registerConstructorsForReflection(
114+
access, mySqlNonTransientException.getName());
115+
}
116+
117+
// JDBC classes create socket connections which must be initialized at run time.
118+
RuntimeClassInitialization.initializeAtRunTime("com.mysql.cj.jdbc");
119+
120+
// Additional MySQL resources.
121+
resourcesRegistry.addResourceBundles(
122+
ConfigurationCondition.alwaysTrue(), "com.mysql.cj.LocalizedErrorMessages");
123+
resourcesRegistry.addResourceBundles(
124+
ConfigurationCondition.alwaysTrue(), "com.mysql.jdbc.LocalizedErrorMessages");
125+
}
126+
127+
// This Netty class should be initialized at runtime
128+
// https://github.com/netty/netty/issues/11638
129+
Class<?> bouncyCastleAlpnSslUtils =
130+
access.findClassByName("io.netty.handler.ssl.BouncyCastleAlpnSslUtils");
131+
if (bouncyCastleAlpnSslUtils != null) {
132+
RuntimeClassInitialization.initializeAtRunTime(bouncyCastleAlpnSslUtils);
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)