com.ibm.cp4waiops.connectors.test.MockBridgeServer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of connectors-sdk Show documentation
Show all versions of connectors-sdk Show documentation
A developer SDK for creating connectors for CP4WAIOps.
package com.ibm.cp4waiops.connectors.test;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.protobuf.InvalidProtocolBufferException;
import com.ibm.aiops.connectors.bridge.ConnectorStatus;
import com.ibm.aiops.connectors.bridge.ConnectorBridgeGrpc.ConnectorBridgeImplBase;
import com.ibm.cp4waiops.connectors.sdk.Connector;
import com.ibm.cp4waiops.connectors.sdk.ConnectorConfigurationHelper;
import com.ibm.cp4waiops.connectors.sdk.Util;
import io.cloudevents.CloudEventData;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.core.format.EventSerializationException;
import io.cloudevents.jackson.JsonCloudEventData;
import io.cloudevents.v1.proto.CloudEvent;
import io.grpc.ManagedChannel;
import io.grpc.Server;
import io.grpc.Status;
import io.grpc.inprocess.InProcessChannelBuilder;
import io.grpc.inprocess.InProcessServerBuilder;
import io.grpc.stub.StreamObserver;
/**
* The MockBridgeServer can be used to test how a connector behaves during different parts of the connector lifecycle in
* unit tests. However, it is not a replacement for validating against an actual server in integration tests. This mock
* only aproximates the behavior of the bridge server.
*/
public class MockBridgeServer extends ConnectorBridgeImplBase {
static final ObjectMapper _mapper = new ObjectMapper();
private ConcurrentLinkedDeque _configurationEvents = new ConcurrentLinkedDeque<>();
private ConcurrentLinkedDeque _actions = new ConcurrentLinkedDeque<>();
private ConcurrentLinkedQueue _producedEvents = new ConcurrentLinkedQueue<>();
private AtomicReference _lastStatus = new AtomicReference<>();
private ExecutorService _executor;
private Server _server;
private ManagedChannel _channel;
/**
* Runs the in process server, retrieve the channel to access it
*
* @throws IOException
*/
public void start() throws IOException {
_executor = Executors.newCachedThreadPool();
String serverName = InProcessServerBuilder.generateName();
_server = InProcessServerBuilder.forName(serverName).addService(this).executor(_executor).build().start();
_channel = InProcessChannelBuilder.forName(serverName).build();
}
/**
* Stops the in process server
*/
public void stop() throws InterruptedException {
_channel.shutdown();
_server.shutdown();
Thread.sleep(1000);
_channel.shutdownNow();
_server.shutdownNow();
_executor.shutdownNow();
}
/**
* Retrieves a channel that can access the in process server. Requires that start was ran beforehand.
*
* @return
*/
public ManagedChannel getChannel() {
return _channel;
}
/**
*
* adds a configuration cloud event, no validation is done
*
* @param id
* the id of the cloud event
* @param eventType
* the type of the cloud event
* @param connectorType
* the connector type
* @param terminating
* true if the configuration is terminating, false if not
* @param connectorID
* id of the connector configuration
* @param componentName
* name of the connector component
* @param jsonData
* the raw json data
*/
public void setConfiguration(String id, String eventType, String connectorType, boolean terminating,
String connectorID, String componentName, byte[] jsonData) {
String method = terminating ? ConnectorConfigurationHelper.DELETE_METHOD
: ConnectorConfigurationHelper.POST_METHOD;
CloudEventData eventData;
try {
eventData = JsonCloudEventData.wrap(_mapper.readTree(jsonData));
} catch (IOException error) {
throw new RuntimeException("failed to serialize json object", error);
}
io.cloudevents.CloudEvent event = CloudEventBuilder.v1().withId(id).withSource(URI.create("/mock-server"))
.withType(eventType).withTime(OffsetDateTime.now())
.withExtension("host", "https://connector-bridge.aiopsedge.svc:9443/v1/async")
.withExtension("method", method).withSubject("some-configuration-xyz")
.withExtension("connectionid", "6accb5c2-c3a2-4f3c-b959-debef0a2e033")
.withExtension("connectortype", connectorType).withExtension("componentname", componentName)
.withData(eventData).build();
setConfiguration(event);
}
/**
* adds a configuration cloud event, no validation is done
*
* @param id
* the id of the cloud event
* @param eventType
* the type of the cloud event
* @param connectorType
* the connector type
* @param terminating
* true if the configuration is terminating, false if not
* @param connectorID
* id of the connector configuration
* @param componentName
* name of the connector component
* @param jsonObject
* an object that can be serialized by jackson
*/
public void setConfiguration(String id, String eventType, String connectorType, boolean terminating,
String connectorID, String componentName, Object jsonObject) {
byte[] data;
try {
data = _mapper.writeValueAsBytes(jsonObject);
} catch (JsonProcessingException error) {
throw new RuntimeException("failed to serialize json object", error);
}
setConfiguration(id, eventType, connectorType, terminating, connectorID, componentName, data);
}
/**
* adds the configuration cloud event, no validation is done
*
* @param event
* the configuration event
*/
public void setConfiguration(io.cloudevents.CloudEvent event) {
_configurationEvents.add(event);
}
/**
* adds an action event that can be consumed by the connector, no validation is done
*
* @param event
*/
public void addAction(io.cloudevents.CloudEvent event) {
_actions.add(event);
}
/**
* retrieves all events produced by the connector, no validation is done
*
* @return
*/
public io.cloudevents.CloudEvent[] getEvents() {
return _producedEvents.toArray(new io.cloudevents.CloudEvent[0]);
}
/**
* retrieve the last status received from the connector, no validation is done, may be null
*
* @return
*/
public ConnectorStatus getStatus() {
return _lastStatus.get();
}
@Override
public void configuration(CloudEvent request, StreamObserver responseObserver) {
for (;;) {
io.cloudevents.CloudEvent event = _configurationEvents.poll();
if (event == null) {
try {
Thread.sleep(100);
continue;
} catch (InterruptedException error) {
System.out.println("server configuration interrupted");
RuntimeException t = Status.fromThrowable(error).asRuntimeException();
responseObserver.onError(t);
Thread.currentThread().interrupt();
break;
}
}
CloudEvent converted;
try {
converted = Util.convertCloudEventToProto(event);
} catch (EventSerializationException | InvalidProtocolBufferException error) {
System.out.println("failed to convert cloud event to proto");
RuntimeException t = Status.fromThrowable(error).asRuntimeException();
responseObserver.onError(t);
break;
}
System.out.println("server configuration sending: id=" + event.getId());
responseObserver.onNext(converted);
}
}
@Override
public void consume(CloudEvent protoRequest, StreamObserver responseObserver) {
io.cloudevents.CloudEvent request = Util.convertCloudEventFromProto(protoRequest);
String connectionID = (String) request.getExtension(Connector.CONNECTION_ID_CE_EXTENSION_NAME);
String componentName = (String) request.getExtension(Connector.COMPONENT_NAME_CE_EXTENSION_NAME);
String topicName = (String) request.getExtension(Connector.TOPIC_CE_EXTENSION_NAME);
// Event loop
for (;;) {
// Send event if it exists
io.cloudevents.CloudEvent event = null;
for (io.cloudevents.CloudEvent candidate : _actions) {
if (connectionID.equals(candidate.getExtension(Connector.CONNECTION_ID_CE_EXTENSION_NAME))
&& componentName.equals(candidate.getExtension(Connector.COMPONENT_NAME_CE_EXTENSION_NAME))
&& topicName.equals(candidate.getExtension(Connector.TOPIC_CE_EXTENSION_NAME))) {
event = candidate;
break;
}
}
if (event == null) {
try {
Thread.sleep(100);
continue;
} catch (InterruptedException error) {
System.out.println("server consume interrupted");
RuntimeException t = Status.fromThrowable(error).asRuntimeException();
responseObserver.onError(t);
Thread.currentThread().interrupt();
break;
}
}
_actions.remove(event);
CloudEvent converted;
try {
converted = Util.convertCloudEventToProto(event);
} catch (EventSerializationException | InvalidProtocolBufferException error) {
System.out.println("failed to convert cloud event to proto");
RuntimeException t = Status.fromThrowable(error).asRuntimeException();
responseObserver.onError(t);
break;
}
System.out.println("server consume sending: id=" + event.getId());
responseObserver.onNext(converted);
}
}
@Override
public StreamObserver produceSync(StreamObserver responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(CloudEvent proto) {
// Receive client event
io.cloudevents.CloudEvent converted = Util.convertCloudEventFromProto(proto);
_producedEvents.add(converted);
System.out.println("server received event=" + converted.getId());
// Server response
io.cloudevents.CloudEvent acknowledgeEvent = CloudEventBuilder.v1().withId(UUID.randomUUID().toString())
.withSource(URI.create("/blah")).withType(Connector.CONNECTOR_BRIDGE_EVENT_RECEIVED_CE_TYPE)
.withData("text", converted.getId().getBytes(StandardCharsets.UTF_8)).build();
CloudEvent acknowledgeProto;
try {
acknowledgeProto = Util.convertCloudEventToProto(acknowledgeEvent);
responseObserver.onNext(acknowledgeProto);
} catch (EventSerializationException | InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
@Override
public void onError(Throwable t) {
responseObserver.onError(t);
}
@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
@Override
public void status(ConnectorStatus request, StreamObserver responseObserver) {
_lastStatus.set(request);
CloudEvent accepted = CloudEvent.newBuilder().setId("some-id-0").setSource("/mock-server").setType("mock-event")
.build();
responseObserver.onNext(accepted);
responseObserver.onCompleted();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy