spring-cloud / spring-cloud-function

Apache License 2.0
1.04k stars 618 forks source link

GCP HTTP Example fails to deploy - ClassNotFound exception - JarLauncher #1085

Closed ggranum closed 4 months ago

ggranum commented 1 year ago

Describe the bug Building and deploying the Main branch version of spring-cloud-function-samples/function-sample-gcp-http fails with 'java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher'

The 4.0.x branch still packages and deploys as expected.

Steps Clone project && cd project ./mvnw install cd spring-cloud-function-samples/function-sample-gcp-http mvn package gcloud functions deploy function-sample-gcp-http \ --entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \ --runtime java17 \ --trigger-http \ --source target/deploy \ --memory 512MB

deploy fails with above exception.

What works cd projectRoot git checkout 4.0.x mvn clean cd spring-cloud-function-samples/function-sample-gcp-http mvn package gcloud functions deploy function-sample-gcp-http \ --entry-point org.springframework.cloud.function.adapter.gcp.GcfJarLauncher \ --runtime java17 \ --trigger-http \ --source target/deploy \ --memory 512MB

Further Info I tried JDK20, 21 and 17. All Temurin.

SND33 commented 11 months ago

We're suffering from the same issue. After upgrading Spring Boot to 3.2.0, we started getting this error on startup when deployed on GCP. Upon inspecting the JAR built by the Spring Boot Maven plugin, we can see that indeed the JarLauncher class it not in that place. This causes the error because GcfJarLauncher extends JarLauncher.

The Spring Boot 3.2.0 release notes explain that the Spring Boot loader tools have moved to the launch package, which explains the error. They also provide a workaround to use the classic loader tools by adding following configuration to the Spring Boot Maven plugin:

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <executions>
        <execution>
          <goals>
            <goal>repackage</goal>
          </goals>
          <configuration>
            <loaderImplementation>CLASSIC</loaderImplementation>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

When doing that, the classic loader tools are packaged by the plugin, which should in theory resolve the issue. However, we found out that's in fact the spring-cloud-function-adapter-gcp dependency, which must be added as a dependency for the plugin, that overwrites this behaviour and ends up not including the classic loader tools (we confirmed that without the adapter, and with the above change, the built JAR does include the classic loader tools).

Is anyone aware of a workaround? If not, we're forced to downgrade Spring Boot, as well as Spring Cloud.

UPDATE: downgrading to Spring Boot 3.1.6 resolved the issue as expected

ecky-l commented 8 months ago

Same here. Also looking for a workaround or a solution.

cfranzen commented 7 months ago

Looking for a solution for Gradel. There seems to be no solution at the moment.

Masahito-I commented 7 months ago

I'm also facing this issue. Is there any solution?

P.S. for now, I decided to downgrade the SpringBoot version from 3.2.4 to 3.1.10. The deployment was successful after that. If you'd like to implement your function ASAP, please consider to downgrade it.

PMG-VascoSaavedra commented 7 months ago

I was able to sort this out, with the help of a friend.

Basically, i would have to change the signature to receive an Object, and then cast to a BufferedReader. Afterwards, i would have to read from it line by line, parse the lines and create a Key/Value Map.

I tested this and it worked.

@SpringBootApplication
public class CloudFunctionMain {

    private static final Logger log = LoggerFactory.getLogger(CloudFunctionMain.class);

    public static void main(String[] args) {
        SpringApplication.run(CloudFunctionMain.class, args);
    }

    @Bean
    public Function<Object, ResponseEntity<Object>> function() {
    return this::handleNotify;
    }

    private ResponseEntity<Object> handleNotify(final Object values) {

        BufferedReader request = ((BufferedReader) values);

                //Read BufferedReader, parse the lines and add values to a Map.

        return new ResponseEntity<>(null, new HttpHeaders(), HttpStatus.OK);
    }
}

In the end, i opted to use Quarkus.

MawsFr commented 6 months ago

Here is a working pom.xml based on what @Masahito-I said. It uses GCP spring cloud function + Webflux + gcp secret manager.

I think this is linked to the fact that JarLauncher has been moved but the current version of spring-cloud-function seems to point to the old JarLauncher folder (Source).

Hope it will be fixed soon ^^

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.examples</groupId>
    <artifactId>example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>example</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.10</version> <!-- DO NOT UPGRADE see : https://github.com/spring-cloud/spring-cloud-function/issues/1085 -->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <java.version>21</java.version>
        <spring-cloud-function.version>4.1.1</spring-cloud-function.version>
        <mockito-core.version>5.11.0</mockito-core.version>
        <logstash-logback-encoder.version>7.4</logstash-logback-encoder.version>
        <google-cloud-storage.version>2.36.1</google-cloud-storage.version>
        <java-function-invoker.version>1.3.1</java-function-invoker.version>
        <spring-cloud-gcp-starter-secretmanager.version>5.1.2</spring-cloud-gcp-starter-secretmanager.version>
        <spring-cloud-gcp-dependencies.version>4.8.4</spring-cloud-gcp-dependencies.version>

        <!-- JUnit Properties -->
        <junit-bom.version>5.10.2</junit-bom.version>

        <!-- JaCoCo Properties -->
        <jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version>
        <maven-failsafe-plugin.version>3.2.5</maven-failsafe-plugin.version>
        <junit-jupiter-engine.version>5.10.2</junit-jupiter-engine.version>
        <jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-function-webflux</artifactId>
            <version>${spring-cloud-function.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-adapter-gcp</artifactId>
            <version>${spring-cloud-function.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- DO NOT DELETE THIS https://stackoverflow.com/a/78164824 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-server</artifactId>
        </dependency>

        <!-- logs -->
        <dependency>
            <groupId>net.logstash.logback</groupId>
            <artifactId>logstash-logback-encoder</artifactId>
            <version>${logstash-logback-encoder.version}</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>

        <!-- To use gcp bucket -->
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>google-cloud-storage</artifactId>
            <version>${google-cloud-storage.version}</version>
        </dependency>

        <!-- Add Secret Manager Starter -->
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>spring-cloud-gcp-starter-secretmanager</artifactId>
            <version>${spring-cloud-gcp-starter-secretmanager.version}</version>
        </dependency>

        <!-- tools -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- test dependencies -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webflux</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>${mockito-core.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.google.cloud.functions.invoker</groupId>
            <artifactId>java-function-invoker</artifactId>
            <version>${java-function-invoker.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>${mockito-core.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-deploy-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <outputDirectory>target/deploy</outputDirectory>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-function-adapter-gcp</artifactId>
                        <version>${spring-cloud-function.version}</version>
                    </dependency>
                </dependencies>
            </plugin>

            <plugin>
                <groupId>com.google.cloud.functions</groupId>
                <artifactId>function-maven-plugin</artifactId>
                <version>0.9.1</version>
                <configuration>
                    <functionTarget>org.springframework.cloud.function.adapter.gcp.GcfJarLauncher</functionTarget>
                    <port>8080</port>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${maven-surefire-plugin.version}</version>
                <dependencies>
                    <dependency>
                        <groupId>org.junit.jupiter</groupId>
                        <artifactId>junit-jupiter-engine</artifactId>
                        <version>${junit-jupiter-engine.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>test</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${maven-failsafe-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <configuration>
                            <includes>
                                <include>**/*IT.java</include>
                            </includes>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <!-- failsafe coverage per test reports can be shown in Sonar
                    only if they are put in the same dir as surefire reports -->
                    <reportsDirectory>${project.build.directory}/surefire-reports</reportsDirectory>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <id>jacoco-initialize</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>prepare-agent-it</id>
                        <goals>
                            <goal>prepare-agent-integration</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>jacoco-site</id>
                        <phase>package</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/jacoco.exec</dataFile>
                        </configuration>
                    </execution>
                    <execution>
                        <id>report-it</id>
                        <goals>
                            <goal>report-integration</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/jacoco-it.exec</dataFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <!-- DO NOT CHANGE : https://stackoverflow.com/a/78207517 -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>${junit-bom.version}</version>
                <scope>import</scope>
                <type>pom</type>
            </dependency>
            <dependency>
                <groupId>com.google.cloud</groupId>
                <artifactId>spring-cloud-gcp-dependencies</artifactId>
                <version>${spring-cloud-gcp-dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/libs-snapshot-local</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </repository>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone-local</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/release</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/libs-snapshot-local</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
            <releases>
                <enabled>false</enabled>
            </releases>
        </pluginRepository>
        <pluginRepository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone-local</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
        <pluginRepository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release-local</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <profiles>
        <profile>
            <id>macos-aarch64</id>
            <activation>
                <os>
                    <family>mac</family>
                    <arch>aarch64</arch>
                </os>
            </activation>
            <dependencies>
                <dependency>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-resolver-dns-native-macos</artifactId>
                    <version>${netty.version}</version>
                    <classifier>osx-aarch_64</classifier>
                </dependency>
            </dependencies>
        </profile>
    </profiles>

</project>
petrosyanar commented 5 months ago

The same issue here. The Spring Boot 3.3.x is blocked now in our cloud functions

Mowee commented 5 months ago

We have the same problem with Gradle. We are currently still using Spring Boot 3.1 which unfortunately lost support last month.

gfourny-sfeir commented 5 months ago

This is becoming problematic, no one is taking care of these issues that have been open for several months... 🥺

The Spring Boot 3.1 version is no longer supported and there is no solution from Spring other than forking the adapter project...😥

tdadashov1-clgx commented 3 months ago

I've updated the spring-cloud-function to 4.1.3, but still got the same error. Are there any additional steps required to fix the error?

Cathesso commented 3 months ago

@tdadashov1-clgx have you found a solution for this problem yet? We also can't get the cloud functions to work properly in 4.1.3. The functions can be called and the logs say that they did execute, but actually nothing happens, so they are not being launched for real.

btrajkovski-mms commented 3 months ago

spring-cloud-function 4.1.3 not working for me also. I got same error as the one described here: https://github.com/spring-cloud/spring-cloud-function/issues/1164

@olegz could it be related to the fix you provided ?

Mowee commented 3 months ago

@olegz I can still reproduce the issue. As a test setup I've used the function-sample-gcp-background (https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-gcp-background) I just followed the build and deploy process as described within the README.

While the application works fine locally I still get the same exception once I'm trying to deploy the function using the described gcloud command.

Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/boot/loader/JarLauncher
        at java.base/java.lang.ClassLoader.defineClass1(Native Method)
        at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1017)
        at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
        at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:524)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:427)
        at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:421)
        at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:420)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
        at com.google.cloud.functions.invoker.runner.Invoker.loadFunctionClass(Invoker.java:357)
        at com.google.cloud.functions.invoker.runner.Invoker.startServer(Invoker.java:294)
        at com.google.cloud.functions.invoker.runner.Invoker.startServer(Invoker.java:244)
        at com.google.cloud.functions.invoker.runner.Invoker.main(Invoker.java:127)
Caused by: java.lang.ClassNotFoundException: org.springframework.boot.loader.JarLauncher
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
        ... 14 more. Please visit https://cloud.google.com/functions/docs/troubleshooting for in-depth troubleshooting documentation.