Skip to content

Commit

Permalink
GCF v2 and Cloud Event support
Browse files Browse the repository at this point in the history
  • Loading branch information
loicmathieu committed Nov 3, 2021
1 parent 5cf5685 commit 648efac
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 6 deletions.
2 changes: 1 addition & 1 deletion bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
<protobuf-java.version>3.17.3</protobuf-java.version>
<protoc.version>${protobuf-java.version}</protoc.version>
<picocli.version>4.6.1</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

0 comments on commit 648efac

Please sign in to comment.