<commit_before>/*
 *  [2012] - [2017] Codenvy, S.A.
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Codenvy S.A. and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Codenvy S.A.
 * and its suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Codenvy S.A..
 */
package com.codenvy.swarm.client;

import com.codenvy.swarm.client.model.DockerNode;
import com.google.common.base.Strings;

import org.eclipse.che.plugin.docker.client.DockerApiVersionPathPrefixProvider;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.DockerConnectorConfiguration;
import org.eclipse.che.plugin.docker.client.DockerRegistryAuthResolver;
import org.eclipse.che.plugin.docker.client.ProgressMonitor;
import org.eclipse.che.plugin.docker.client.connection.DockerConnectionFactory;
import org.eclipse.che.plugin.docker.client.exception.DockerException;
import org.eclipse.che.plugin.docker.client.json.ContainerCreated;
import org.eclipse.che.plugin.docker.client.json.SystemInfo;
import org.eclipse.che.plugin.docker.client.params.BuildImageParams;
import org.eclipse.che.plugin.docker.client.params.CreateContainerParams;
import org.eclipse.che.plugin.docker.client.params.PullParams;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.primitives.Ints.tryParse;

/**
 * Swarm implementation of {@link DockerConnector} that can be used on distributed system
 *
 * @author <NAME>
 * @author <NAME>
 */
@Singleton
public class SwarmDockerConnector extends DockerConnector {

    private final NodeSelectionStrategy   strategy;
    //TODO should it be done in other way?
    private final String                  nodeDaemonScheme;
    private final int                     nodeDescriptionLength;

    private static final Pattern IMAGE_NOT_FOUND_BY_SWARM_ERROR_MESSAGE = Pattern.compile("^Error: image .* not found.*", Pattern.DOTALL);

    @Inject
    public SwarmDockerConnector(DockerConnectorConfiguration connectorConfiguration,
                                DockerConnectionFactory connectionFactory,
                                DockerRegistryAuthResolver authManager,
                                @Named("swarm.client.node_description_length") int nodeDescriptionLength,
                                DockerApiVersionPathPrefixProvider dockerApiVersionPathPrefixProvider) {
        super(connectorConfiguration, connectionFactory, authManager, dockerApiVersionPathPrefixProvider);
        this.nodeDescriptionLength = nodeDescriptionLength;
        this.strategy = new RandomNodeSelectionStrategy();
        this.nodeDaemonScheme = "http";
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated Very unstable behavior with multiple nodes, try to workaround somehow
     */
    @Deprecated
    @Override
    public void pull(PullParams params, ProgressMonitor progressMonitor) throws IOException {
        final DockerNode node = strategy.select(getAvailableNodes());
        super.pull(params, progressMonitor, addrToUri(node.getAddr()));
    }

    @Override
    public String buildImage(BuildImageParams params, ProgressMonitor progressMonitor) throws IOException {
        try {
            return super.buildImage(params, progressMonitor);
        } catch (DockerException e) {
            throw decorateMessage(e);
        }
    }

    @Override
    public ContainerCreated createContainer(CreateContainerParams params) throws IOException {
        try {
            return super.createContainer(params);
        } catch (DockerException e) {
            // TODO fix this workaround. Is needed for https://github.com/codenvy/codenvy/issues/1215
            if (e.getStatus() == 500 &&
                IMAGE_NOT_FOUND_BY_SWARM_ERROR_MESSAGE.matcher(e.getOriginError()).matches()) { // if swarm failed to see image
                try {
                    Thread.sleep(5000);                   // wait a bit
                    return super.createContainer(params); // and retry after pause
                } catch (DockerException de) {
                    throw decorateMessage(de);
                } catch (InterruptedException ie) {
                    throw decorateMessage(e);
                }
            }
            throw decorateMessage(e);
        }
    }

    private DockerException decorateMessage(DockerException e) {
        if (e.getOriginError() != null && e.getOriginError().contains("no resources available to schedule container")) {
            e = new DockerException("The system is out of resources. Please contact your system admin.",
                                    e.getOriginError(),
                                    e.getStatus());
        }
        return e;
    }

    /**
     * Fetches nodes from {@link SystemInfo#getDriverStatus()} which contains
     * information about all available nodes(addresses available RAM etc).
     * <pre>
     * Scheme of driver status content:
     *
     * [0] -> ["Nodes", "number of nodes"]
     * [1] -> ["hostname", "ip:port"]
     * [2] -> ["Containers", "number of containers"]
     * [3] -> ["Reserved CPUs", "number of free/reserved CPUs"]<commit_msg>CODENVY-1734: workaround docker exec not found problem in swarm (#2011)<commit_after>/*
 *  [2012] - [2017] Codenvy, S.A.
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Codenvy S.A. and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Codenvy S.A.
 * and its suppliers and may be covered by U.S. and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Codenvy S.A..
 */
package com.codenvy.swarm.client;

import com.codenvy.swarm.client.model.DockerNode;
import com.google.common.base.Strings;

import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.plugin.docker.client.DockerApiVersionPathPrefixProvider;
import org.eclipse.che.plugin.docker.client.DockerConnector;
import org.eclipse.che.plugin.docker.client.DockerConnectorConfiguration;
import org.eclipse.che.plugin.docker.client.DockerRegistryAuthResolver;
import org.eclipse.che.plugin.docker.client.LogMessage;
import org.eclipse.che.plugin.docker.client.MessageProcessor;
import org.eclipse.che.plugin.docker.client.ProgressMonitor;
import org.eclipse.che.plugin.docker.client.connection.DockerConnectionFactory;
import org.eclipse.che.plugin.docker.client.exception.DockerException;
import org.eclipse.che.plugin.docker.client.exception.ExecNotFoundException;
import org.eclipse.che.plugin.docker.client.json.ContainerCreated;
import org.eclipse.che.plugin.docker.client.json.ExecInfo;
import org.eclipse.che.plugin.docker.client.json.SystemInfo;
import org.eclipse.che.plugin.docker.client.params.BuildImageParams;
import org.eclipse.che.plugin.docker.client.params.CreateContainerParams;
import org.eclipse.che.plugin.docker.client.params.PullParams;
import org.eclipse.che.plugin.docker.client.params.StartExecParams;
import org.slf4j.Logger;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.primitives.Ints.tryParse;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * Swarm implementation of {@link DockerConnector} that can be used on distributed system
 *
 * @author <NAME>
 * @author <NAME>
 */
@Singleton
public class SwarmDockerConnector extends DockerConnector {
    private static final Logger LOG = getLogger(SwarmDockerConnector.class);

    private final NodeSelectionStrategy   strategy;
    //TODO should it be done in other way?
    private final String                  nodeDaemonScheme;
    private final int                     nodeDescriptionLength;

    private static final Pattern IMAGE_NOT_FOUND_BY_SWARM_ERROR_MESSAGE = Pattern.compile("^Error: image .* not found.*", Pattern.DOTALL);

    @Inject
    public SwarmDockerConnector(DockerConnectorConfiguration connectorConfiguration,
                                DockerConnectionFactory connectionFactory,
                                DockerRegistryAuthResolver authManager,
                                @Named("swarm.client.node_description_length") int nodeDescriptionLength,
                                DockerApiVersionPathPrefixProvider dockerApiVersionPathPrefixProvider) {
        super(connectorConfiguration, connectionFactory, authManager, dockerApiVersionPathPrefixProvider);
        this.nodeDescriptionLength = nodeDescriptionLength;
        this.strategy = new RandomNodeSelectionStrategy();
        this.nodeDaemonScheme = "http";
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated Very unstable behavior with multiple nodes, try to workaround somehow
     */
    @Deprecated
    @Override
    public void pull(PullParams params, ProgressMonitor progressMonitor) throws IOException {
        final DockerNode node = strategy.select(getAvailableNodes());
        super.pull(params, progressMonitor, addrToUri(node.getAddr()));
    }

    @Override
    public String buildImage(BuildImageParams params, ProgressMonitor progressMonitor) throws IOException {
        try {
            return super.buildImage(params, progressMonitor);
        } catch (DockerException e) {
            throw decorateMessage(e);
        }
    }

    @Override
    public ContainerCreated createContainer(CreateContainerParams params) throws IOException {
        try {
            return super.createContainer(params);
        } catch (DockerException e) {
            // TODO fix this workaround. Is needed for https://github.com/codenvy/codenvy/issues/1215
            if (e.getStatus() == 500 &&
                IMAGE_NOT_FOUND_BY_SWARM_ERROR_MESSAGE.matcher(e.getOriginError()).matches()) { // if swarm failed to see image
                try {
                    Thread.sleep(5000);                   // wait a bit
                    return super.createContainer(params); // and retry after pause
                } catch (DockerException de) {
                    throw decorateMessage(de);
                } catch (InterruptedException ie) {
                    throw decorateMessage(e);
                }
            }
            throw decorateMessage(e);
        }
    }

    @Override
    public void startExec(StartExecParams params, @Nullable MessageProcessor<LogMessage> execOutputProcessor)
            throws IOException {
        try {
            super.startExec(params, execOutputProcessor);
        } catch (ExecNotFoundException e) {
            // Sometimes swarm returns this error for unknown reason, see https://github.com/docker/swarm/issues/2664
            // Log additional info to find out if this endpoint knows about exec at exactly that time
            logMissingExecInfo(params.getExecId());
            try {
                // Wait in case swarm needs some time to find exec
                Thread.sleep(3000);
                super.startExec(params, execOutputProcessor);
            } catch (InterruptedException ie) {
                // throw original error
                throw new IOException(e);
            }
        }
    }

    private void logMissingExecInfo(String execId) {
        try {
            ExecInfo execInfo = super.getExecInfo(execId);
            LOG.error("Exec not found problem appeared. Exec: " + execInfo);
        } catch (IOException ignored) {}
    }

    private DockerException decorateMessage(DockerException e) {
        if (e.getOriginError() != null && e.getOriginError().contains("no resources available to schedule container")) {
            e = new DockerException("The system is out of resources. Please contact your system admin.",
                                    e.getOriginError(),
                                    e.getStatus());
        }
        return e;
    }

    /**
     * Fetches nodes from {@link SystemInfo#getDriverStatus()} which contains
     * information about all available nodes(addresses available RAM etc).
     * <pre>
     * Scheme of driver status content:
     *
     * [0] -> ["Nodes", "number of nodes"]
     * [1] -> ["hostname", "ip:port"]
     * [2] -> ["Containers", "number of containers"]
     * [3] -> ["Reserved CPUs", "number of free/reserved CPUs"]
{'fraction_non_alphanumeric': 0.08883809523809524, 'fraction_numerical': 0.004876190476190476, 'mean_word_length': 2.830172162241027, 'pattern_counts': {'":': 0, '<': 10, '<?xml version=': 0, '>': 18, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6278275', 'n_tokens_mistral': 3473, 'n_tokens_neox': 3306, 'n_words': 881}