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

GCF v2 and Cloud Event support #21073

Merged
merged 1 commit into from
Dec 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
<google-http-client.version>1.38.0</google-http-client.version>
<scram-client.version>2.1</scram-client.version>
<picocli.version>4.6.2</picocli.version>
<google-cloud-functions.version>1.0.1</google-cloud-functions.version>
<google-cloud-functions.version>1.0.4</google-cloud-functions.version>
<commons-compress.version>1.21</commons-compress.version>
<gson.version>2.8.6</gson.version>
<webjars-locator-core.version>0.46</webjars-locator-core.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class FunqyBackgroundFunction implements RawBackgroundFunction {
Thread.currentThread().setContextClassLoader(FunqyBackgroundFunction.class.getClassLoader());
Class<?> appClass = Class.forName("io.quarkus.runner.ApplicationImpl");
String[] args = {};
Application app = (Application) appClass.newInstance();
Application app = (Application) appClass.getConstructor().newInstance();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.quarkus.funqy.gcp.functions;

import java.io.PrintWriter;
import java.io.StringWriter;

import com.google.cloud.functions.CloudEventsFunction;

import io.cloudevents.CloudEvent;
import io.quarkus.runtime.Application;

public class FunqyCloudEventsFunction implements CloudEventsFunction {
protected static final String deploymentStatus;
protected static boolean started = false;

static {
StringWriter error = new StringWriter();
PrintWriter errorWriter = new PrintWriter(error, true);
if (Application.currentApplication() == null) { // were we already bootstrapped? Needed for mock unit testing.
// For GCP functions, we need to set the TCCL to the QuarkusHttpFunction classloader then restore it.
// Without this, we have a lot of classloading issues (ClassNotFoundException on existing classes)
// during static init.
ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(FunqyCloudEventsFunction.class.getClassLoader());
Class<?> appClass = Class.forName("io.quarkus.runner.ApplicationImpl");
String[] args = {};
Application app = (Application) appClass.getConstructor().newInstance();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
app.stop();
}
});
app.start(args);
errorWriter.println("Quarkus bootstrapped successfully.");
started = true;
} catch (Exception ex) {
errorWriter.println("Quarkus bootstrap failed.");
ex.printStackTrace(errorWriter);
} finally {
Thread.currentThread().setContextClassLoader(currentCl);
}
} else {
errorWriter.println("Quarkus bootstrapped successfully.");
started = true;
}
deploymentStatus = error.toString();
}

@Override
public void accept(CloudEvent cloudEvent) throws Exception {
if (!started) {
throw new RuntimeException(deploymentStatus);
}
FunqyCloudFunctionsBindingRecorder.handle(cloudEvent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.cloud.functions.Context;

import io.cloudevents.CloudEvent;
import io.quarkus.arc.ManagedContext;
import io.quarkus.arc.runtime.BeanContainer;
import io.quarkus.funqy.runtime.FunctionConstructor;
Expand Down Expand Up @@ -96,6 +97,20 @@ public static void handle(String event, Context context) {
}
}

/**
* Handle CloudEventsFunction
*
* @param cloudEvent
*/
public static void handle(CloudEvent cloudEvent) {
FunqyServerResponse response = dispatch(cloudEvent);

Object value = response.getOutput().await().indefinitely();
if (value != null) {
throw new RuntimeException("A background function cannot return a value");
}
}

private static FunqyServerResponse dispatch(Object input) {
ManagedContext requestContext = beanContainer.requestContext();
requestContext.activate();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.jboss.jandex.IndexView;

import com.google.cloud.functions.BackgroundFunction;
import com.google.cloud.functions.CloudEventsFunction;
import com.google.cloud.functions.HttpFunction;
import com.google.cloud.functions.RawBackgroundFunction;

Expand All @@ -36,6 +37,7 @@ public class GoogleCloudFunctionsProcessor {
public static final DotName DOTNAME_HTTP_FUNCTION = DotName.createSimple(HttpFunction.class.getName());
public static final DotName DOTNAME_BACKGROUND_FUNCTION = DotName.createSimple(BackgroundFunction.class.getName());
public static final DotName DOTNAME_RAW_BACKGROUND_FUNCTION = DotName.createSimple(RawBackgroundFunction.class.getName());
public static final DotName DOTNAME_CLOUD_EVENT_FUNCTION = DotName.createSimple(CloudEventsFunction.class.getName());

@BuildStep
public FeatureBuildItem feature() {
Expand All @@ -56,6 +58,7 @@ public List<CloudFunctionBuildItem> discoverFunctionClass(CombinedIndexBuildItem
Collection<ClassInfo> httpFunctions = index.getAllKnownImplementors(DOTNAME_HTTP_FUNCTION);
Collection<ClassInfo> backgroundFunctions = index.getAllKnownImplementors(DOTNAME_BACKGROUND_FUNCTION);
Collection<ClassInfo> rawBackgroundFunctions = index.getAllKnownImplementors(DOTNAME_RAW_BACKGROUND_FUNCTION);
Collection<ClassInfo> cloudEventFunctions = index.getAllKnownImplementors(DOTNAME_CLOUD_EVENT_FUNCTION);

List<CloudFunctionBuildItem> cloudFunctions = new ArrayList<>();
cloudFunctions.addAll(
Expand All @@ -65,6 +68,8 @@ public List<CloudFunctionBuildItem> discoverFunctionClass(CombinedIndexBuildItem
cloudFunctions.addAll(
registerFunctions(unremovableBeans, rawBackgroundFunctions,
GoogleCloudFunctionInfo.FunctionType.RAW_BACKGROUND));
cloudFunctions.addAll(
registerFunctions(unremovableBeans, cloudEventFunctions, GoogleCloudFunctionInfo.FunctionType.CLOUD_EVENT));

if (cloudFunctions.isEmpty()) {
throw new BuildException("No Google Cloud Function found on the classpath", Collections.emptyList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ public void setFunctionType(FunctionType functionType) {
this.functionType = functionType;
}

public static enum FunctionType {
public enum FunctionType {
HTTP,
BACKGROUND,
RAW_BACKGROUND;
RAW_BACKGROUND,
CLOUD_EVENT
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ public void selectDelegate(GoogleCloudFunctionsConfig config, List<GoogleCloudFu
QuarkusHttpFunction.setDelegate(delegates.get(GoogleCloudFunctionInfo.FunctionType.HTTP));
QuarkusBackgroundFunction.setDelegates(delegates.get(GoogleCloudFunctionInfo.FunctionType.BACKGROUND),
delegates.get(GoogleCloudFunctionInfo.FunctionType.RAW_BACKGROUND));
QuarkusCloudEventsFunction.setDelegate(delegates.get(GoogleCloudFunctionInfo.FunctionType.CLOUD_EVENT));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package io.quarkus.gcp.functions;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import com.google.cloud.functions.CloudEventsFunction;

import io.cloudevents.CloudEvent;
import io.quarkus.arc.Arc;
import io.quarkus.runtime.Application;

public final class QuarkusCloudEventsFunction implements CloudEventsFunction {

protected static final String deploymentStatus;
protected static boolean started = false;

private static volatile CloudEventsFunction delegate;

static {
StringWriter error = new StringWriter();
PrintWriter errorWriter = new PrintWriter(error, true);
if (Application.currentApplication() == null) { // were we already bootstrapped? Needed for mock unit testing.
ClassLoader currentCl = Thread.currentThread().getContextClassLoader();
try {
// For GCP functions, we need to set the TCCL to the QuarkusHttpFunction classloader then restore it.
// Without this, we have a lot of classloading issues (ClassNotFoundException on existing classes)
// during static init.
Thread.currentThread().setContextClassLoader(QuarkusCloudEventsFunction.class.getClassLoader());
Class<?> appClass = Class.forName("io.quarkus.runner.ApplicationImpl");
String[] args = {};
Application app = (Application) appClass.getConstructor().newInstance();
app.start(args);
errorWriter.println("Quarkus bootstrapped successfully.");
started = true;
} catch (Exception ex) {
errorWriter.println("Quarkus bootstrap failed.");
ex.printStackTrace(errorWriter);
} finally {
Thread.currentThread().setContextClassLoader(currentCl);
}
} else {
errorWriter.println("Quarkus bootstrapped successfully.");
started = true;
}
deploymentStatus = error.toString();
}

static void setDelegate(String selectedDelegate) {
if (selectedDelegate != null) {
try {
Class<?> clazz = Class.forName(selectedDelegate, false, Thread.currentThread().getContextClassLoader());
delegate = (CloudEventsFunction) Arc.container().instance(clazz).get();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}

@Override
public void accept(CloudEvent cloudEvent) throws Exception {
if (!started) {
throw new IOException(deploymentStatus);
}

// TODO maybe we can check this at static init
if (delegate == null) {
throw new IOException("We didn't found any CloudEventsFunction to run " +
"(or there is multiple one and none selected inside your application.properties)");
}

delegate.accept(cloudEvent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import javax.inject.Inject;

import io.cloudevents.CloudEvent;
import io.quarkus.funqy.Funq;
import io.quarkus.funqy.gcp.functions.event.PubsubMessage;
import io.quarkus.funqy.gcp.functions.event.StorageEvent;
Expand All @@ -23,4 +24,13 @@ public void helloGCSWorld(StorageEvent storageEvent) {
System.out.println(storageEvent.name + " - " + message);
}

@Funq
public void helloCloudEvent(CloudEvent cloudEvent) {
System.out.println("Receive event Id: " + cloudEvent.getId());
System.out.println("Receive event Subject: " + cloudEvent.getSubject());
System.out.println("Receive event Type: " + cloudEvent.getType());
System.out.println("Receive event Data: " + new String(cloudEvent.getData().toBytes()));
System.out.println("Be polite, say " + service.hello("world"));
}

}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
quarkus.funqy.export=helloGCSWorld
#quarkus.funqy.export=helloGCSWorld
#quarkus.funqy.export=helloPubSubWorld
quarkus.funqy.export=helloCloudEvent
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.gcp.function.test;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;

import com.google.cloud.functions.CloudEventsFunction;

import io.cloudevents.CloudEvent;
import io.quarkus.gcp.function.test.service.GreetingService;

@Named("cloudEventTest")
@ApplicationScoped
public class CloudEventStorageTest implements CloudEventsFunction {
@Inject
GreetingService greetingService;

@Override
public void accept(CloudEvent cloudEvent) throws Exception {
System.out.println("Receive event Id: " + cloudEvent.getId());
System.out.println("Receive event Subject: " + cloudEvent.getSubject());
System.out.println("Receive event Type: " + cloudEvent.getType());
System.out.println("Receive event Data: " + new String(cloudEvent.getData().toBytes()));
System.out.println("Be polite, say " + greetingService.hello());
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
quarkus.google-cloud-functions.function=httpTest
#quarkus.google-cloud-functions.function=httpTest
quarkus.google-cloud-functions.function=cloudEventTest
#quarkus.google-cloud-functions.function=rawPubSubTest
#quarkus.google-cloud-functions.function=storageTest