Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(java): add configurations for Storage tests #1305

Merged
merged 7 commits into from
Mar 30, 2022
Merged

Conversation

mpeddada1
Copy link
Contributor

This PR adds a few reflection configurations for native image support in Storage.

prox-config.json:

JUnit Vintage:ITBlobWriteChannelTest:testJsonEOF_0B
    MethodSource [className = 'com.google.cloud.storage.it.ITBlobWriteChannelTest', methodName = 'testJsonEOF_0B', methodParameterTypes = '']
    => com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface com.google.cloud.storage.spi.v1.StorageRpc] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles=<comma-separated-config-files> and -H:DynamicProxyConfigurationResources=<comma-separated-config-resources> options.
       com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:87)
       com.oracle.svm.reflect.proxy.DynamicProxySupport.getProxyClass(DynamicProxySupport.java:146)
       java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:66)
       java.lang.reflect.Proxy.newProxyInstance(Proxy.java:1006)
       com.google.common.reflect.Reflection.newProxy(Reflection.java:86)
       com.google.cloud.storage.it.ITBlobWriteChannelTest.lambda$doJsonUnexpectedEOFTest$1(ITBlobWriteChannelTest.java:140)
       com.google.cloud.ServiceOptions.getRpc(ServiceOptions.java:560)
       com.google.cloud.storage.StorageOptions.getStorageRpcV1(StorageOptions.java:149)
       com.google.cloud.storage.StorageImpl.<init>(StorageImpl.java:115)
       com.google.cloud.storage.StorageOptions$DefaultStorageFactory.create(StorageOptions.java:50)

The reflect-config.json file registers some Java classes for reflection:

 JUnit Vintage:ITStorageTest:testUpdateBlobReplaceMetadata
    MethodSource [className = 'com.google.cloud.storage.it.ITStorageTest', methodName = 'testUpdateBlobReplaceMetadata', methodParameterTypes = '']
    => java.lang.IllegalArgumentException: unable to create new instance of class com.google.cloud.storage.BlobInfo$ImmutableEmptyMap because it has no accessible default constructor
       com.google.api.client.util.Types.handleExceptionForNewInstance(Types.java:162)
       com.google.api.client.util.Types.newInstance(Types.java:117)
       com.google.api.client.util.Data.createNullInstance(Data.java:166)
       com.google.api.client.util.Data.nullOf(Data.java:134)
       com.google.cloud.storage.BlobInfo$BuilderImpl.setMetadata(BlobInfo.java:585)
       [...]
     Caused by: java.lang.InstantiationException: Type `com.google.cloud.storage.BlobInfo.ImmutableEmptyMap` can not be instantiated reflectively as it does not have a no-parameter constructor or the no-parameter constructor has not been added explicitly to the native image.
       java.lang.Class.newInstance(DynamicHub.java:909)
       com.google.api.client.util.Types.newInstance(Types.java:113)
       [...]

The resource-config.json file makes certain test resource accessible to the native image builder. Specifically, testNamesWhichCanFail.txt. It is currently located under src/test as it applies to only tests.

The native-image.properties file is located under src/test as it only applies to the tests :

This file handles class initialization during native image generation. Classes can be initialized at either run-time or image build time and class initialization can result in recursive initialization of other classes. The tests in the Storage repo use Parameterized which is explicitly initialized at image build time by the configurations provided by GraalVM. This leads to a bunch of other unexpected classes being initialized at build time as well and leads to the following error:

Error: Classes that should be initialized at run time got initialized during image building:
 com.google.cloud.conformance.storage.v1.RetryTests was unintentionally initialized at build time. To see why com.google.cloud.conformance.storage.v1.RetryTests got initialized use --trace-class-initialization=com.google.cloud.conformance.storage.v1.RetryTests
com.google.cloud.conformance.storage.v1.InstructionList was unintentionally initialized at build time. To see why com.google.cloud.conformance.storage.v1.InstructionList got initialized use --trace-class-initialization=com.google.cloud.conformance.storage.v1.InstructionList

And initializing those same classes at run-time results in image generation failing with inconclusive results:

Error: Classes that should be initialized at run time got initialized during image building:
 com.google.cloud.storage.conformance.retry.ITRetryConformanceTest$RetryTestCaseResolver the class was requested to be initialized at run time

Initializing these classes explicitly at build-time results in a successful build.

@mpeddada1 mpeddada1 requested review from a team as code owners March 23, 2022 15:52
@product-auto-label product-auto-label bot added the api: storage Issues related to the googleapis/java-storage API. label Mar 23, 2022
Comment on lines 2 to 17
{
"name":"org.apache.commons.logging.LogFactory",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"allDeclaredConstructors": true
},
{
"name":"org.apache.commons.logging.impl.Jdk14Logger",
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]}
,
{
"name":"org.apache.commons.logging.impl.LogFactoryImpl",
"allDeclaredFields":true,
"allDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]}
,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not supposed to have any dependency on apache logging. Do you know how I might find the reference path for these classes to try and eliminate them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this a bit by removing the LogFactoryImpl configuration and running mvn clean install -DskipTests and mvn test -Pnative. This results in the following stacktrace:

JUnit Vintage:ITStorageTest:testWriteChannelWithConnectionPool
    MethodSource [className = 'com.google.cloud.storage.it.ITStorageTest', methodName = 'testWriteChannelWithConnectionPool', methodParameterTypes = '']
    => java.lang.ExceptionInInitializerError
       org.apache.http.conn.ssl.SSLConnectionSocketFactory.<clinit>(SSLConnectionSocketFactory.java:151)
       org.apache.http.impl.conn.PoolingHttpClientConnectionManager.getDefaultRegistry(PoolingHttpClientConnectionManager.java:116)
       org.apache.http.impl.conn.PoolingHttpClientConnectionManager.<init>(PoolingHttpClientConnectionManager.java:123)
       com.google.cloud.storage.it.ITStorageTest$CustomHttpTransportFactory.create(ITStorageTest.java:282)
       com.google.cloud.storage.spi.v1.HttpStorageRpc.<init>(HttpStorageRpc.java:110)
       [...]
     Caused by: org.apache.commons.logging.LogConfigurationException: java.lang.ClassNotFoundException: org.apache.commons.logging.impl.LogFactoryImpl (Caused by java.lang.ClassNotFoundException: org.apache.commons.logging.impl.LogFactoryImpl)
       org.apache.commons.logging.LogFactory.createFactory(LogFactory.java:1158)
       org.apache.commons.logging.LogFactory$2.run(LogFactory.java:960)
       java.security.AccessController.doPrivileged(AccessController.java:87)
       org.apache.commons.logging.LogFactory.newFactory(LogFactory.java:957)
       org.apache.commons.logging.LogFactory.getFactory(LogFactory.java:624)
       [...]
     Caused by: java.lang.ClassNotFoundException: org.apache.commons.logging.impl.LogFactoryImpl
       jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:53)
       jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
       java.lang.ClassLoader.loadClass(ClassLoader.java:139)
       org.apache.commons.logging.LogFactory.createFactory(LogFactory.java:1020)
       [...]

It looks the usage of LogFactory is coming from PoolingHttpClientConnectionManager which is referenced in ITStorageTest.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, so that's testing for an optional feature. Thanks for trying it out

com.google.gson.LongSerializationPolicy$2



Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that com.google.cloud.storage.conformance.retry.ITRetryConformanceTest#testCases is being invoked during the building of the native image?

We're using @RunWith(ParallelParameterized.class) to resolve test cases relative to a classpath resource, and run them in parallel using a threadpool.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While standard Java initializes classes the first time they are accessed at run-time, Native image compilation tries to do some optimizations were it initializes some classes at image build time to decrease the start-up time of an application.

In our case, the JunitFeature which is brought in by the native-maven-plugin (plugin we use to run tests with native image compilation) explicitly initializes Parameterized at image build time. This causes ParallelParameterized and subsequently com.google.cloud.storage.conformance.retry.ITRetryConformanceTest and other classes ITRetryConformanceTest references to also be initialized at build time. While the class itself is initialized at build time, it still runs at run-time (after the native image has been built).

This article has been helping me understand class initialization a bit in graal.

@mpeddada1 mpeddada1 changed the title fix(java): add configurations for Storage classes and tests fix(java): add configurations for Storage tests Mar 24, 2022
@mpeddada1
Copy link
Contributor Author

@BenWhitehead any idea why some checks are stalled at the moment?

@suztomo suztomo closed this Mar 25, 2022
@suztomo suztomo reopened this Mar 25, 2022
@@ -0,0 +1,26 @@
Args = \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file that tells build-time initialization of classes of the library shouldn't be in test. Because the file is not shipped as part of the library.

As discussed today, this file being under the test directory means the users would have to write something similar to use google-cloud-storage with native-image.

Copy link
Contributor Author

@mpeddada1 mpeddada1 Mar 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only reason why these configurations are initialized at build-time is because com.google.cloud.storage.conformance.retry.ITRetryConformanceTest which references them is initialized at build-time (following this logic). I'm not sure if this will apply to the regular application? WDYT

Copy link
Member

@suztomo suztomo Mar 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's my misunderstanding then. Sorry I missed the discussion. Would you add that comment in the file about the configuration being for the test classes?

An explanation on why so many classes (not test classes) are needed here is nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! Added a comment to summarize this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now the --initialize-at-build-time doesn't have google-cloud-storage's main package. Nice.

"methods":[{"name":"<init>","parameterTypes":[] }]}
,
{
"name":"com.google.cloud.storage.BlobInfo$ImmutableEmptyMap",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you add comment explaining why this configuration is not in main?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my bad. I unintentionally placed this class config in the test directory. This configuration is used outside of the tests so needs to in the main/resources directory.

@@ -0,0 +1,5 @@
[
{
"interfaces":["com.google.cloud.storage.spi.v1.StorageRpc"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you add comment explaining why this configuration is not in main?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for point this out! I realized that this class also needs to be in the main config directory since it is referenced outside of tests. For example:


Apologies for the confusion @BenWhitehead!

@suztomo
Copy link
Member

suztomo commented Mar 26, 2022

Now the checks are running (Kokoro sample failed)

Copy link
Collaborator

@BenWhitehead BenWhitehead left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified config by running:

export JAVA_HOME=~/opt/java/11-native
mvn -Pnative clean verify

@BenWhitehead
Copy link
Collaborator

Feel free to merge when you're ready. The test failure in Samples is being dealt with separately and isn't related to any change here.

@mpeddada1
Copy link
Contributor Author

Thank you!

@mpeddada1 mpeddada1 merged commit 2bacf92 into main Mar 30, 2022
@mpeddada1 mpeddada1 deleted the add-native-config branch March 30, 2022 13:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api: storage Issues related to the googleapis/java-storage API.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants