spring-cloud-function icon indicating copy to clipboard operation
spring-cloud-function copied to clipboard

4.1.3 GCP Adapter - GcfJarLauncher: Failed to lookup function

Open gfourny-sfeir opened this issue 1 year ago • 8 comments

Describe the bug We have still bug on spring-cloud-function-adapter-gcp 4.1.3. We using Spring Boot 3.3.1 with Spring Cloud Function. When the function is invoking, we have this error:

Failed to execute org.springframework.cloud.function.adapter.gcp.GcfJarLauncher java.lang.IllegalArgumentException: Failed to lookup function to route based on the value of 'spring.cloud.function.definition' property 'generateAvatar'

It was working with Spring Boot 3.1.x

Sample pom.xml:

    <properties>
        <java.version>17</java.version>
        <spring-boot.version>3.3.1</spring-boot.version>
        <spring-cloud.version>2023.0.3</spring-cloud.version>
        <function-maven-plugin.version>0.11.0</function-maven-plugin.version>
        <spring-boot-maven-plugin.version>3.3.1</spring-boot-maven-plugin.version>
        <spring-cloud-function-adapter-gcp.version>4.1.3</spring-cloud-function-adapter-gcp.version>
    </properties>


        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-context</artifactId>
        </dependency>
        <dependency>
	    <groupId>org.springframework.boot</groupId>
	    <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!-- Adapteur GCP -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-function-adapter-gcp</artifactId>
        </dependency>
        <!-- Cloud Function Web HTTP-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-function-web</artifactId>
        </dependency>

	<plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <outputDirectory>target/deploy</outputDirectory>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.springframework.cloud</groupId>
                        <artifactId>spring-cloud-function-adapter-gcp</artifactId>
                        <version>${spring-cloud-function-adapter-gcp.version}</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>build-info</goal>
                            <goal>repackage</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${basedir}/package</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
        </plugin>

The java Bean :

    @Bean
    Supplier<AvatarResponse> generateAvatar() {
        return avatarGenerator::generate;
    }

The properties:

spring:
  application:
    name: vod-profil-avatar
  cloud:
    function:
      definition: generateAvatar

I have two integration tests to validate the function, all passed:

    @Autowired
    private TestRestTemplate rest;
    @Autowired
    private Storage storage;
    @Value("${u-cf.bucket-name}")
    private String bucketName;
    @Autowired
    private FunctionCatalog functionCatalog;

    @DisplayName("Doit générer un avatar")
    @Test
    void should_generate_avatar() {
        //Given 🔨

        //When 👉
        ResponseEntity<AvatarResponse> avatarResponse = this.rest.getForEntity(URI.create("/"), AvatarResponse.class);

        //then ✅
        assertThat(avatarResponse).isNotNull()
                .satisfies(response -> response.getStatusCode().is2xxSuccessful())
                .extracting(ResponseEntity::getBody)
                .isNotNull()
                .satisfies(input -> {
                    var blob = storage.get(bucketName, input.blobId());
                    assertThat(blob).isNotNull();
                });
    }

    @DisplayName("Doit générer un avatar")
    @Test
    void should_generate_avatar_catalog() {
        //Given 🔨
        Supplier<AvatarResponse> generateAvatar = functionCatalog.lookup(Supplier.class, "generateAvatar");

        //When 👉
        AvatarResponse avatar = generateAvatar.get();

        //Then ✅
        assertThat(avatar)
                .isNotNull()
                .satisfies(input -> {
                    var blob = storage.get(bucketName, input.blobId());
                    assertThat(blob).isNotNull();
                });

    }

gfourny-sfeir avatar Jul 18 '24 07:07 gfourny-sfeir

On invocation, the beans of our application aren't scanned. However, locally with the function plugin, we saw all beans. To show this, the config of logback-spring.xml:

<logger name="org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLogger" level="DEBUG" additivity="false">
    <appender-ref ref="consoleAppender"/>
</logger>
<logger name="org.springframework.context.annotation.ClassPathBeanDefinitionScanner" level="DEBUG" additivity="false">
    <appender-ref ref="consoleAppender"/>
</logger>
<logger name="org.springframework.beans.factory.support.DefaultListableBeanFactory" level="DEBUG" additivity="false">
    <appender-ref ref="consoleAppender"/>
</logger>

gfourny-sfeir avatar Jul 18 '24 09:07 gfourny-sfeir

I have the same problem, it looks like beans are not being created unless they are defined in main class (@SpringBootApplication).

I ended up defining all beans like this:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(JiraToolsApplication.class, args);
    }

    @Bean
    public WorklogService worklogService() {
        return new WorklogService();
    }

    @Bean
    public WorklogReportFunction worklogFunction() {
        return new WorklogReportFunction(worklogService());
    }
}

It works if you define a bean as functional interface or as a separate class, as I have it:

public class WorklogReportFunction implements Function<Message<BufferedReader>, Message<byte[]>> {
...

ihoroshko-n-ix avatar Sep 10 '24 11:09 ihoroshko-n-ix

@ihoroshko-n-ix I confirm, this solution works fine.

So the original problem becomes from the scanning step treat by the Spring IoC to find the stereotyps.

We loose on simplicity to create beans but we gain on cold start 😅

Once again, thanks for your solution

gfourny-sfeir avatar Oct 02 '24 11:10 gfourny-sfeir

@ihoroshko-n-ix & @gfourny-sfeir Could you please provide a link to a GitHub repo with the code using spring boot 3.3.x and spring-cloud-function-adapter-gcp 4.1.3? I'd like to test it and understand how it behaves, especially if the beans are not being scanned. How could it still function in this case?

laurentiu-miu avatar Oct 17 '24 14:10 laurentiu-miu

After hours of working on this issue, I found that the issue is with Spring boot newer version which does not have support yet and hence downgrade your Spring boot version to 3.1.x in my case tested with 3.1.10 version and it works fine. Look at my example in https://github.com/balaji1974/functions

And I solved this issue by following this thread on the same issue: https://github.com/spring-cloud/spring-cloud-function/issues/1085

balaji1974 avatar Oct 21 '24 20:10 balaji1974

@ihoroshko-n-ix @gfourny-sfeir Can you post the code in Github repo as I am not sure of how to inject the service into the Function interface

rajakumare1 avatar Oct 22 '24 20:10 rajakumare1

@rajakumare1 I couldn't share our repos because they're private. You need to declare your Bean in the main class like this:

@SpringBootApplication
public class VodProfilAvatarApplication {

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

    @Bean
    RestClient restClient(RestClient.Builder builder, VodProfilAvatarProperties properties) {
        return builder.baseUrl(properties.avatarGeneratorBaseUrl()).build();
    }

    //Business service without @Service or @Component annotation
    @Bean
    AvatarWriter avatarWriter(Storage storage, VodProfilAvatarProperties properties) {
        return new AvatarWriter(storage, properties);
    }

    //Business service without @Service or @Component annotation
    @Bean
    AvatarClient avatarClient(RestClient restClient) {
        return new AvatarClient(restClient);
    }

    //Business service without @Service or @Component annotation
    @Bean
    AvatarGenerator avatarGenerator(AvatarClient avatarClient, AvatarWriter avatarWriter) {
        return new AvatarGenerator(avatarClient, avatarWriter);
    }

    /**
     * {@link Supplier} called by Cloud Function and refers in application.yaml
     *
     * @return {@link Supplier<String>}
     */
    @Bean
    Supplier<AvatarResponse> generateAvatar(AvatarGenerator avatarGenerator) {
        return avatarGenerator::generate;
    }
}

In your application.yaml:

spring:
  application:
    name: vod-profil-avatar
  cloud:
    function:
      definition: generateAvatar #Name of the bean

gfourny-sfeir avatar Oct 23 '24 07:10 gfourny-sfeir

@olegz We suspect the bug comes from the FunctionInvoker class in the adapter code by these lines:

Thread.currentThread() // TODO: remove after upgrading to 1.0.0-alpha-2-rc5
			.setContextClassLoader(FunctionInvoker.class.getClassLoader());

❓ Why

Because the classLoader used to scan is provides these lines and cannot scan the package created by our applications

gfourny-sfeir avatar Oct 23 '24 07:10 gfourny-sfeir

Happy to report that both the issue mentioned in https://github.com/spring-cloud/spring-cloud-function/issues/1085 and this one are resolved for me after upgrading to version 4.1.4 (tested with Spring Cloud Release Train 2023.0.4 and Spring Boot 3.3.6). My Spring Boot cloud function app starts correctly again when deployed to GCP, and all beans are correctly scanned 👍.

SND33 avatar Nov 27 '24 17:11 SND33

This issue has been stale for over 60 days

github-actions[bot] avatar Jan 27 '25 17:01 github-actions[bot]