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

com.ibm.cp4waiops.connectors.test.MockBridgeServer Maven / Gradle / Ivy

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