com.ibm.cp4waiops.connectors.sdk.GRPCCloudEventProduceChannel 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.sdk;
import java.nio.charset.StandardCharsets;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ibm.aiops.connectors.bridge.ConnectorBridgeGrpc.ConnectorBridgeStub;
import io.cloudevents.CloudEvent;
import io.grpc.stub.StreamObserver;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
/**
* Streams events to the bridge using a single channel
*/
class GRPCCloudEventProduceChannel implements AutoCloseable {
private static final Logger logger = Logger.getLogger(GRPCCloudEventProduceChannel.class.getName());
private String _name;
private ConnectorBridgeStub _stub;
private int _maxMessageSize;
private AtomicReference _currentStreams;
private Deque _unverifiedEvents;
private Counter _sentCount;
private Counter _successCount;
private Counter _droppedMessageCount;
private Counter _streamStartCount;
private Gauge _unverifiedCount;
private static ObjectMapper _mapper = new ObjectMapper();
class CloudEventStreamObserverPair {
CloudEventStreamObserver _incoming;
CloudEventStreamObserver _outgoing;
CloudEventStreamObserverPair(CloudEventStreamObserver incoming, CloudEventStreamObserver outgoing) {
_incoming = incoming;
_outgoing = outgoing;
}
boolean areActive() {
return _incoming.isActive() && _outgoing.isActive();
}
}
class EventReceiptProcessor implements StreamObserver {
@Override
public void onNext(io.cloudevents.v1.proto.CloudEvent proto) {
CloudEvent event;
try {
event = Util.convertCloudEventFromProto(proto);
} catch (Exception error) {
logger.log(Level.SEVERE, "proto to cloud event conversion failed, dropping: id=" + proto.getId());
return;
}
logger.log(Level.FINER, "EventReceiptProcessor onNext(): type=" + event.getType() + " unverifiedSize="
+ _unverifiedEvents.size());
// Remove verified events from unverified list
switch (event.getType()) {
case Connector.CONNECTOR_BRIDGE_EVENT_RECEIVED_CE_TYPE:
processEventReceivedCE(event);
break;
case Connector.CONNECTOR_BRIDGE_BAD_REQUEST_CE_TYPE:
processBadRequestCE(event);
break;
default:
// Ignored
break;
}
}
@Override
public void onError(Throwable t) {
logger.log(Level.WARNING, "produce stream terminated with an error: channel=" + _name, t);
String improvedDiagnostic = Util.getDiagnosticMessage(t);
if (improvedDiagnostic != null) {
logger.log(Level.WARNING, improvedDiagnostic);
}
}
@Override
public void onCompleted() {
logger.log(Level.INFO, "produce stream terminated cleanly: channel=" + _name);
}
private void processEventReceivedCE(CloudEvent event) {
String id = new String(event.getData().toBytes(), StandardCharsets.UTF_8);
boolean success = _unverifiedEvents.removeIf((item) -> item.getId().equals(id));
if (success) {
_successCount.increment();
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "verified event produced: channel=" + _name + ",id=" + id);
}
}
private void processBadRequestCE(CloudEvent event) {
BridgeBadRequest info;
try {
info = _mapper.readValue(event.getData().toBytes(), BridgeBadRequest.class);
} catch (Exception error) {
logger.log(Level.WARNING, "failed to decode " + event.getType() + " event", error);
return;
}
if (info.getId() == null) {
logger.log(Level.WARNING, "missing id for " + event.getType() + " event");
return;
}
logger.log(Level.FINER, "server rejected cloud event, dropping: channel=" + _name + ",id=" + info.getId()
+ ",detail=" + info.getDetail());
boolean removed = _unverifiedEvents.removeIf((item) -> item.getId().equals(info.getId()));
if (removed) {
_droppedMessageCount.increment();
}
}
}
GRPCCloudEventProduceChannel(String name, ConnectorBridgeStub stub, int messageSizeLimit,
MeterRegistry meterRegistry) {
_name = name;
_stub = stub.withMaxOutboundMessageSize(messageSizeLimit);
_maxMessageSize = messageSizeLimit;
_currentStreams = new AtomicReference<>();
_unverifiedEvents = new ConcurrentLinkedDeque<>();
_sentCount = meterRegistry.counter("connector.sdk.produce.sent", Constant.CHANNEL_NAME_TAG, name);
_successCount = meterRegistry.counter("connector.sdk.produce.verified", Constant.CHANNEL_NAME_TAG, name);
_droppedMessageCount = meterRegistry.counter("connector.sdk.produce.dropped", Constant.CHANNEL_NAME_TAG, name);
_streamStartCount = meterRegistry.counter("connector.sdk.produce.starts", Constant.CHANNEL_NAME_TAG, name);
_unverifiedCount = Gauge.builder("connector.sdk.produce.unverified", _unverifiedEvents, Deque::size)
.tags(Constant.CHANNEL_NAME_TAG, name).register(meterRegistry);
}
double getSentCount() {
return _sentCount.count();
}
double getSuccessCount() {
return _successCount.count();
}
double getStreamRestartCount() {
return _streamStartCount.count() - 1;
}
double getDroppedCount() {
return _droppedMessageCount.count();
}
double getUnverifiedCount() {
return _unverifiedCount.value();
}
void fixIfBroken() {
CloudEventStreamObserverPair streams = _currentStreams.get();
if (streams == null || !streams.areActive()) {
logger.log(Level.FINER, "fixIfBroken(): about to call createChannel()");
createChannel();
}
}
void produce(CloudEvent event) {
CloudEventStreamObserverPair streams = _currentStreams.get();
if (streams == null || !streams.areActive()) {
logger.log(Level.FINER, "produce(): about to call createChannel()");
streams = createChannel();
}
_unverifiedEvents.add(event);
logger.log(Level.FINER, "produce(): unverified event type=" + event.getType() + " id=" + event.getId());
sendCloudEvent(streams._outgoing, event);
}
@Override
public void close() {
CloudEventStreamObserverPair oldStreams = _currentStreams.getAndSet(null);
if (oldStreams != null && oldStreams.areActive()) {
logger.log(Level.INFO, "closing produce stream: channel=" + _name);
try {
oldStreams._outgoing.onCompleted();
} catch (Throwable error) {
logger.log(Level.WARNING, "failed to close produce stream cleanly: channel=" + _name, error);
}
closeStreams(oldStreams);
}
}
void closeStreams(CloudEventStreamObserverPair pair) {
if (pair.areActive()) {
logger.log(Level.INFO, "closing produce stream: channel=" + _name);
try {
pair._outgoing.onCompleted();
} catch (Throwable error) {
logger.log(Level.WARNING, "failed to close produce stream cleanly: channel=" + _name, error);
}
}
}
synchronized CloudEventStreamObserverPair createChannel() {
// Return the current stream if it is active
CloudEventStreamObserverPair oldStreams = _currentStreams.get();
if (oldStreams != null && oldStreams.areActive()) {
return oldStreams;
}
// Construct a new stream and swap it out
logger.log(Level.INFO, "starting produce stream: channel=" + _name);
AtomicBoolean active = new AtomicBoolean(true); // Connect the two streams so that if either is closed, both
// become inactive
CloudEventStreamObserver incoming = new CloudEventStreamObserver(new EventReceiptProcessor(), active);
CloudEventStreamObserver outgoing = new CloudEventStreamObserver(_stub.produceSync(incoming), active);
CloudEventStreamObserverPair streams = new CloudEventStreamObserverPair(incoming, outgoing);
oldStreams = _currentStreams.getAndSet(streams);
if (oldStreams != null) {
closeStreams(oldStreams);
}
_streamStartCount.increment();
// Resend unverified events in the same order
int unverifiedSize = _unverifiedEvents.size();
if (unverifiedSize > 0) {
logger.log(Level.WARNING, "createChannel() re-running unverified size:" + unverifiedSize);
}
for (CloudEvent event : _unverifiedEvents) {
sendCloudEvent(outgoing, event);
}
return streams;
}
void sendCloudEvent(CloudEventStreamObserver stream, CloudEvent event) {
// Only send if the stream has not been closed
if (!stream.isActive()) {
return;
}
// Log
if (logger.isLoggable(Level.FINE)) {
try {
logger.log(Level.FINE,
"sending cloud event: channel=" + _name + ",event=" + Util.convertCloudEventToJSON(event));
} catch (Exception error) {
logger.log(Level.FINE, "failed to serialize event for logging", error);
}
}
// Convert
io.cloudevents.v1.proto.CloudEvent convertedEvent;
try {
convertedEvent = Util.convertCloudEventToProto(event);
} catch (Exception error) {
logger.log(Level.SEVERE,
"dropping event that failed to serialize: channel=" + _name + ",type=" + event.getType(), error);
_droppedMessageCount.increment();
return;
}
// Check message size
if (convertedEvent.getSerializedSize() > _maxMessageSize) {
logger.log(Level.SEVERE, "dropping event that exceeds the allowed message size: channel=" + _name + ",type="
+ event.getType());
_droppedMessageCount.increment();
return;
}
// Send
try {
stream.onNext(convertedEvent);
_sentCount.increment();
} catch (Throwable error) {
logger.log(Level.SEVERE, "failed to send cloudevent", error);
stream.onError(error);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy