All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.cp4waiops.connectors.sdk.GRPCCloudEventProduceChannel Maven / Gradle / Ivy

There is a newer version: 2.2.8
Show newest version
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