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

org.yamcs.cascading.YamcsLink Maven / Gradle / Ivy

There is a newer version: 5.10.9
Show newest version
package org.yamcs.cascading;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.yamcs.ConfigurationException;
import org.yamcs.Spec;
import org.yamcs.Spec.OptionType;
import org.yamcs.YConfiguration;
import org.yamcs.client.ClientException;
import org.yamcs.client.ConnectionListener;
import org.yamcs.client.YamcsClient;
import org.yamcs.client.base.WebSocketClient;
import org.yamcs.client.mdb.MissionDatabaseClient;
import org.yamcs.cmdhistory.CommandHistoryPublisher;
import org.yamcs.tctm.AbstractLink;
import org.yamcs.tctm.AggregatedDataLink;
import org.yamcs.tctm.Link;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

public class YamcsLink extends AbstractLink implements AggregatedDataLink, ConnectionListener {
    YamcsClient yclient;
    String upstreamInstance;
    List subLinks = new ArrayList<>();
    YamcsTmLink tmLink;
    YamcsTcLink tcLink;
    YamcsParameterLink ppLink;
    YamcsTmArchiveLink tmArchiveLink;
    YamcsEventLink eventLink;

    String upstreamName;
    String upstreamProcessor;
    ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1,
            new ThreadFactoryBuilder().setNameFormat("YamcsLink").build());

    long reconnectionDelay;

    private String username;
    private char[] password;

    @Override
    public void init(String instance, String name, YConfiguration config) {
        super.init(instance, name, config);
        this.reconnectionDelay = config.getLong("reconnectionDelay", 5000);

        yclient = YamcsClient.newBuilder(config.getString("yamcsUrl"))
                .withConnectionAttempts(config.getInt("connectionAttempts", 20))
                .withRetryDelay(reconnectionDelay)
                .withVerifyTls(config.getBoolean("verifyTls", true))
                .build();
        yclient.addConnectionListener(this);

        if (config.containsKey("username")) {
            if (config.containsKey("password")) {
                username = config.getString("username");
                password = config.getString("password").toCharArray();
            } else {
                throw new ConfigurationException("Username provided with no password");
            }
        } else if (config.containsKey("password")) {
            throw new ConfigurationException("Password provided with no username");
        }

        if (config.getBoolean("tm", true)) {
            tmLink = new YamcsTmLink(this);
            tmLink.init(instance, name + ".tm", config);
            subLinks.add(tmLink);
        }

        if (config.getBoolean("tc", true)) {
            tcLink = new YamcsTcLink(this);
            tcLink.init(instance, name + ".tc", config);
            subLinks.add(tcLink);
        }

        if (config.getBoolean("pp", true)) {
            ppLink = new YamcsParameterLink(this);
            ppLink.init(instance, name + ".pp", config);
            subLinks.add(ppLink);
        }

        if (config.getBoolean("tmArchive", true)) {
            tmArchiveLink = new YamcsTmArchiveLink(this);
            tmArchiveLink.init(instance, name + ".tmArchive", config);
            subLinks.add(tmArchiveLink);
        }

        if (config.getBoolean("event", true)) {
            eventLink = new YamcsEventLink(this);
            eventLink.init(instance, name + ".event", config);
            subLinks.add(eventLink);
        }

        this.upstreamName = config.getString("upstreamName");

        if (upstreamName.contains("<") || upstreamName.contains(">")) {
            throw new ConfigurationException("Invalid upstream name '" + upstreamName + "'. It cannot contain < or >");
        }
        this.upstreamProcessor = config.getString("upstreamProcessor", "realtime");
        this.upstreamInstance = config.getString("upstreamInstance");
    }

    @Override
    public Spec getSpec() {
        Spec spec = getDefaultSpec();
        spec.addOption("yamcsUrl", OptionType.STRING).withRequired(true)
                .withDescription("The URL to connect to the server.");

        spec.addOption("username", OptionType.STRING)
                .withDescription("Username to connect to the server");

        spec.addOption("password", OptionType.STRING).withSecret(true)
                .withDescription("Password to connect to the server");

        spec.addOption("upstreamInstance", OptionType.STRING).withRequired(true)
                .withDescription("The instance to connect to.");

        spec.addOption("upstreamProcessor", OptionType.STRING).withDefault("realtime")
                .withDescription("The processor to connect to. Default is realtime");

        spec.addOption("upstreamName", OptionType.STRING).withRequired(true)
                .withDescription(
                        "The name of the upstream Yamcs server. The name will be used in the command history entries");

        spec.addOption("verifyTls", OptionType.BOOLEAN).withDefault(true)
                .withDescription("If the connection is over SSL, "
                        + "this option can enable/disable the verification of the server certificate against local accepted CA list");

        spec.addOption("reconnectionDelay", OptionType.INTEGER).withDefault(5000)
                .withDescription("If the connection fails or breaks, "
                        + "the time (in milliseconds) to wait before reconnection.");

        spec.addOption("connectionAttempts", OptionType.INTEGER).withDefault(20)
                .withDescription(
                        "How many times to attempt reconnection if the connection fails. "
                                + "Reconnection will not be reatempted if the authentication fails. "
                                + "Link disable/enable is required to reattempt the connection");

        /*
         * TM
         */
        spec.addOption("tm", OptionType.BOOLEAN).withDefault(true)
                .withDescription("Subscribe telemetry containers (packets). "
                        + "The list of containers (packets) has to be specified using the containers option.");
        spec.addOption("tmRealtimeStream", OptionType.STRING);

        spec.addOption("tmArchive", OptionType.BOOLEAN)
                .withAliases("archiveTm") // Legacy typo
                .withDefault(true);
        spec.addOption("tmArchiveStream", OptionType.STRING);

        spec.addOption("containers", OptionType.LIST).withAliases("packets")
                .withElementType(OptionType.STRING)
                .withDescription("The list of packets to subscribe to.");

        spec.when("tm", true).requireAll("containers");

        spec.addOption("retrievalDays", OptionType.INTEGER);
        spec.addOption("mergeTime", OptionType.INTEGER);
        spec.addOption("gapFillingInterval", OptionType.INTEGER);

        /*
         * PP
         */
        spec.addOption("pp", OptionType.BOOLEAN).withDefault(true)
                .withDescription("Subscribe parameters. "
                        + "The list of parameters has to be specified using the parameters option.");
        spec.addOption("ppRealtimeStream", OptionType.STRING);
        spec.addOption("parameters", OptionType.LIST).withElementType(OptionType.STRING)
                .withDescription("The list of parameters to subscribe to.");

        /*
         * TC
         */
        spec.addOption("tc", OptionType.BOOLEAN).withDefault(true)
                .withDescription("Allow to send TC and subscribe to command history.");

        spec.addOption("keepUpstreamAcks", OptionType.LIST)
                .withElementType(OptionType.STRING)
                .withDescription("List of command acknowledgements names received "
                        + "from the upstream server to keep unmodified")
                .withDefault(List.of(CommandHistoryPublisher.CcsdsSeq_KEY));

        spec.addOption("commandMapping", OptionType.LIST).withElementType(OptionType.MAP)
                .withSpec(CommandMapData.getSpec())
                .withDescription("The mapping of commands and arguments between downstream and upstream.");
        spec.addOption("failCommandIfNoMappingMatches", OptionType.BOOLEAN).withDefault(false)
                .withDescription("Fail the command if no mapping matches");

        /*
         * EV
         */
        spec.addOption("event", OptionType.BOOLEAN).withDefault(true)
                .withDescription("Allow to subscribe to realtime events.");
        spec.addOption("eventRealtimeStream", OptionType.STRING);

        return spec;
    }

    @Override
    public Status connectionStatus() {
        WebSocketClient wsclient = yclient.getWebSocketClient();
        if (wsclient == null) {
            return Status.UNAVAIL;
        }
        return wsclient.isConnected() ? Status.OK : Status.UNAVAIL;
    }

    @Override
    public List getSubLinks() {
        return subLinks;
    }

    @Override
    public long getDataInCount() {
        long count = 0;
        for (Link l : subLinks) {
            count += l.getDataInCount();
        }
        return count;
    }

    @Override
    public long getDataOutCount() {
        return tcLink == null ? 0 : tcLink.getDataOutCount();
    }

    @Override
    public void resetCounters() {
        for (Link l : subLinks) {
            l.resetCounters();
        }
    }

    @Override
    public void doDisable() {
        WebSocketClient wsclient = yclient.getWebSocketClient();
        if (wsclient != null && wsclient.isConnected()) {
            wsclient.disconnect();
        }
    }

    @Override
    public void doEnable() {
        timer.execute(() -> connectToUpstream());
    }

    private void connectToUpstream() {
        WebSocketClient wsclient = yclient.getWebSocketClient();
        if (wsclient != null && wsclient.isConnected()) {
            // we have to protect against double connection because there might be a timer and a user action that causes
            // it to come in here
            return;
        }

        try {
            if (username != null) {
                yclient.login(username, password);
            }
            yclient.connectWebSocket();
        } catch (ClientException cause) {
            log.warn("Connection to upstream Yamcs server failed", cause);
            eventProducer.sendWarning("Connection to upstream Yamcs failed: " + cause);
            return;
        }

        if (tmLink != null || tmArchiveLink != null) {
            retrieveContainers();
        }

        if (tcLink != null && !tcLink.isDisabled()) {
            tcLink.doEnable();
        }

        if (ppLink != null && !ppLink.isDisabled()) {
            ppLink.doEnable();
        }

        if (eventLink != null && !eventLink.isDisabled()) {
            eventLink.doEnable();
        }
    }

    private void retrieveContainers() {
        MissionDatabaseClient mdbClient = getClient().createMissionDatabaseClient(getUpstreamInstance());
        ContainerFetcher.fetchAndMatch(mdbClient, config.getList("containers"), log)
                .whenComplete((list, t) -> {
                    if (t != null) {
                        log.warn("Failed to fetch containers from remote: {}", t);
                    }

                    if (tmLink != null) {
                        tmLink.setContainers(list);
                        if (!tmLink.isDisabled()) {
                            tmLink.subscribeContainers();
                        }
                    }
                    if (tmArchiveLink != null) {
                        tmArchiveLink.setContainers(list);
                        if (!tmArchiveLink.isDisabled()) {
                            tmArchiveLink.scheduleDataRetrieval();
                        }
                    }
                });
    }

    @Override
    protected void doStart() {
        if (!isDisabled()) {
            doEnable();
        }
        notifyStarted();
    }

    @Override
    protected void doStop() {
        yclient.close();
        notifyStopped();
    }

    @Override
    public void connecting() {
        log.debug("Connecting to upstream Yamcs server");
    }

    @Override
    public void connected() {
        log.debug("Connected to upstream Yamcs server");
    }

    @Override
    public void connectionFailed(Throwable cause) {
        eventProducer.sendWarning("Connection to upstream Yamcs failed: " + cause);
    }

    @Override
    public void disconnected() {
        if (isRunningAndEnabled()) {
            log.warn("Disconnected from upstream Yamcs server");
            timer.schedule(() -> connectToUpstream(), reconnectionDelay, TimeUnit.MILLISECONDS);
        } else {
            log.debug("Disconnected from upstream Yamcs server");
        }
    }

    YamcsClient getClient() {
        return yclient;
    }

    String getUpstreamName() {
        return upstreamName;
    }

    String getUpstreamInstance() {
        return upstreamInstance;
    }

    String getUpstreamProcessor() {
        return upstreamProcessor;
    }

    public ScheduledThreadPoolExecutor getExecutor() {
        return timer;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy