diff options
author | Valerij Fredriksen <valerijf@oath.com> | 2018-02-22 16:53:29 +0100 |
---|---|---|
committer | Valerij Fredriksen <valerij92@gmail.com> | 2018-02-22 17:53:42 +0100 |
commit | 30505ca4b061767e72e4ba783d8d339b60bcdfbd (patch) | |
tree | 0489f4e9e00ba9e59ed30e5d0f31641ece80f4f5 /docker-api/src/main | |
parent | 7358ce6a49eedf51b3761a855921c58a5f813321 (diff) |
Simplify DockerImpl
Diffstat (limited to 'docker-api/src/main')
4 files changed, 30 insertions, 415 deletions
diff --git a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerImpl.java b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerImpl.java index e81c6325922..2da18e12e40 100644 --- a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerImpl.java +++ b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerImpl.java @@ -15,15 +15,14 @@ import com.github.dockerjava.api.model.Image; import com.github.dockerjava.api.model.Network; import com.github.dockerjava.api.model.Statistics; import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.core.DockerClientImpl; -import com.github.dockerjava.core.RemoteApiVersion; import com.github.dockerjava.core.async.ResultCallbackTemplate; import com.github.dockerjava.core.command.BuildImageResultCallback; import com.github.dockerjava.core.command.ExecStartResultCallback; import com.github.dockerjava.core.command.PullImageResultCallback; import com.github.dockerjava.jaxrs.JerseyDockerCmdExecFactory; import com.google.inject.Inject; -import com.yahoo.log.LogLevel; import com.yahoo.vespa.hosted.dockerapi.metrics.CounterWrapper; import com.yahoo.vespa.hosted.dockerapi.metrics.Dimensions; import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper; @@ -34,7 +33,6 @@ import java.io.File; import java.io.IOException; import java.net.Inet6Address; import java.net.InetAddress; -import java.net.URI; import java.time.Duration; import java.util.Arrays; import java.util.Collections; @@ -58,13 +56,11 @@ public class DockerImpl implements Docker { public static final String DOCKER_CUSTOM_MACVLAN_NETWORK_NAME = "vespa-macvlan"; static final String LABEL_NAME_MANAGEDBY = "com.yahoo.vespa.managedby"; - - private final int SECONDS_TO_WAIT_BEFORE_KILLING; - private final boolean fallbackTo123OnErrors; private static final String FRAMEWORK_CONTAINER_PREFIX = "/"; + private final DockerConfig config; - private final boolean inProduction; - private Optional<DockerImageGarbageCollector> dockerImageGC = Optional.empty(); + private final Optional<DockerImageGarbageCollector> dockerImageGC; + private final int secondsToWaitBeforeKilling; private CounterWrapper numberOfDockerDaemonFails; private boolean started = false; @@ -76,63 +72,40 @@ public class DockerImpl implements Docker { DockerClient dockerClient; @Inject - public DockerImpl(final DockerConfig config, MetricReceiverWrapper metricReceiver) { - this(config, - true, /* fallback to 1.23 on errors */ - metricReceiver, - !config.isRunningLocally()); - } - - private DockerImpl(final DockerConfig config, - boolean fallbackTo123OnErrors, - MetricReceiverWrapper metricReceiverWrapper, - boolean inProduction) { + public DockerImpl(DockerConfig config, MetricReceiverWrapper metricReceiverWrapper) { this.config = config; - this.fallbackTo123OnErrors = fallbackTo123OnErrors; - this.inProduction = inProduction; - if (config == null) { - this.SECONDS_TO_WAIT_BEFORE_KILLING = 10; - } else { - SECONDS_TO_WAIT_BEFORE_KILLING = config.secondsToWaitBeforeKillingContainer(); - } - if (metricReceiverWrapper != null) { - setMetrics(metricReceiverWrapper); - } + + secondsToWaitBeforeKilling = Optional.ofNullable(config) + .map(DockerConfig::secondsToWaitBeforeKillingContainer) + .orElse(10); + + dockerImageGC = Optional.ofNullable(config) + .map(DockerConfig::imageGCMinTimeToLiveMinutes) + .map(Duration::ofMinutes) + .map(DockerImageGarbageCollector::new); + + Optional.ofNullable(metricReceiverWrapper).ifPresent(this::setMetrics); } // For testing DockerImpl(final DockerClient dockerClient) { - this(null, false, null, false); + this(null, null); this.dockerClient = dockerClient; } - // For testing - DockerImpl(final DockerConfig config, - boolean fallbackTo123OnErrors, - MetricReceiverWrapper metricReceiverWrapper) { - this(config, fallbackTo123OnErrors, metricReceiverWrapper, false); - } - @Override public void start() { if (started) return; started = true; if (config != null) { - if (dockerClient == null) { - dockerClient = initDockerConnection(); - } - if (inProduction) { - Duration minAgeToDelete = Duration.ofMinutes(config.imageGCMinTimeToLiveMinutes()); - dockerImageGC = Optional.of(new DockerImageGarbageCollector(minAgeToDelete)); - + dockerClient = createDockerClient(config); - if (!config.networkNATed()) { - try { - setupDockerNetworkIfNeeded(); - } catch (Exception e) { - throw new DockerException("Could not setup docker network", e); - } + if (!config.networkNATed()) { + try { + setupDockerNetworkIfNeeded(); + } catch (Exception e) { + throw new DockerException("Could not setup docker network", e); } } } @@ -143,21 +116,6 @@ public class DockerImpl implements Docker { return config.networkNATed(); } - static DefaultDockerClientConfig.Builder buildDockerClientConfig(DockerConfig config) { - DefaultDockerClientConfig.Builder dockerConfigBuilder = new DefaultDockerClientConfig.Builder() - .withDockerHost(config.uri()); - - if (URI.create(config.uri()).getScheme().equals("tcp") && !config.caCertPath().isEmpty()) { - // In current version of docker-java (3.0.2), withDockerTlsVerify() only effect is when using it together - // with withDockerCertPath(), where setting withDockerTlsVerify() must be set to true, otherwise the - // cert path parameter will be ignored. - // withDockerTlsVerify() has no effect when used with withCustomSslConfig() - dockerConfigBuilder.withCustomSslConfig(new VespaSSLConfig(config)); - } - - return dockerConfigBuilder; - } - private void setupDockerNetworkIfNeeded() throws IOException { if (!dockerClient.listNetworksCmd().withNameFilter(DOCKER_CUSTOM_MACVLAN_NETWORK_NAME).exec().isEmpty()) return; @@ -366,7 +324,7 @@ public class DockerImpl implements Docker { @Override public void stopContainer(final ContainerName containerName) { try { - dockerClient.stopContainerCmd(containerName.asString()).withTimeout(SECONDS_TO_WAIT_BEFORE_KILLING).exec(); + dockerClient.stopContainerCmd(containerName.asString()).withTimeout(secondsToWaitBeforeKilling).exec(); } catch (NotModifiedException ignored) { // If is already stopped, ignore } catch (RuntimeException e) { @@ -545,36 +503,18 @@ public class DockerImpl implements Docker { } } - private DockerClient initDockerConnection() { + private static DockerClient createDockerClient(DockerConfig config) { JerseyDockerCmdExecFactory dockerFactory = new JerseyDockerCmdExecFactory() .withMaxPerRouteConnections(config.maxPerRouteConnections()) .withMaxTotalConnections(config.maxTotalConnections()) .withConnectTimeout(config.connectTimeoutMillis()) .withReadTimeout(config.readTimeoutMillis()); - RemoteApiVersion remoteApiVersion; - try { - remoteApiVersion = RemoteApiVersion.parseConfig(DockerClientImpl.getInstance( - buildDockerClientConfig(config).build()) - .withDockerCmdExecFactory(dockerFactory).versionCmd().exec().getApiVersion()); - logger.info("Found version of remote docker API: " + remoteApiVersion); - // From version 1.24 a field was removed which causes trouble with the current docker java code. - // When this is fixed, we can remove this and do not specify version. - if (remoteApiVersion.isGreaterOrEqual(RemoteApiVersion.VERSION_1_24)) { - remoteApiVersion = RemoteApiVersion.VERSION_1_23; - logger.info("Found version 1.24 or newer of remote API, using 1.23."); - } - } catch (Exception e) { - if (!fallbackTo123OnErrors) { - throw e; - } - logger.log(LogLevel.ERROR, "Failed when trying to figure out remote API version of docker, using 1.23", e); - remoteApiVersion = RemoteApiVersion.VERSION_1_23; - } - return DockerClientImpl.getInstance( - buildDockerClientConfig(config) - .withApiVersion(remoteApiVersion) - .build()) + DockerClientConfig dockerClientConfig = new DefaultDockerClientConfig.Builder() + .withDockerHost(config.uri()) + .build(); + + return DockerClientImpl.getInstance(dockerClientConfig) .withDockerCmdExecFactory(dockerFactory); } diff --git a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerTestUtils.java b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerTestUtils.java deleted file mode 100644 index 549af0d85cb..00000000000 --- a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/DockerTestUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.dockerapi; - -import com.github.dockerjava.api.model.Network; -import com.yahoo.metrics.simple.MetricReceiver; -import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper; - -import java.io.File; - -/** - * Helper class for testing full integration with docker daemon, requires running daemon. To run these tests: - * - * MAC: - * 1. Install Docker Toolbox, and start it (Docker Quickstart Terminal) (you can close terminal window afterwards) - * 2. For network test, we need to make docker containers visible for Mac: sudo route add 172.18.0.0/16 192.168.99.100 - * - * @author freva - */ -public class DockerTestUtils { - private static final OS operatingSystem = getSystemOS(); - private static final String prefix = "/Users/" + System.getProperty("user.name") + "/.docker/machine/machines/default/"; - private static final DockerConfig dockerConfig = new DockerConfig(new DockerConfig.Builder() - .caCertPath( operatingSystem == OS.Mac_OS_X ? prefix + "ca.pem" : "") - .clientCertPath(operatingSystem == OS.Mac_OS_X ? prefix + "cert.pem" : "") - .clientKeyPath( operatingSystem == OS.Mac_OS_X ? prefix + "key.pem" : "") - .uri( operatingSystem == OS.Mac_OS_X ? "tcp://192.168.99.100:2376" : "tcp://localhost:2376") - .secondsToWaitBeforeKillingContainer(0)); - private static DockerImpl docker; - - public static boolean dockerDaemonIsPresent() { - if (docker != null) return true; - if (operatingSystem == OS.Unsupported) { - System.err.println("This test does not support " + System.getProperty("os.name") + " yet, ignoring test."); - return false; - } - - try { - getDocker(); // Will throw an exception if docker is not installed/incorrectly configured - return true; - } catch (Exception e) { - System.err.println("Please install Docker Toolbox and start Docker Quick Start Terminal once, ignoring test."); - System.err.println(e.getMessage()); - return false; - } - } - - public static DockerImpl getDocker() { - if (docker == null) { - DockerImpl tmpDocker = new DockerImpl( - dockerConfig, - false, /* fallback to 1.23 on errors */ - new MetricReceiverWrapper(MetricReceiver.nullImplementation)); - tmpDocker.start(); - createDockerTestNetworkIfNeeded(tmpDocker); - docker = tmpDocker; - } - - return docker; - } - - public static void createDockerTestNetworkIfNeeded(DockerImpl docker) { - if (! docker.dockerClient.listNetworksCmd().withNameFilter(DockerImpl.DOCKER_CUSTOM_MACVLAN_NETWORK_NAME).exec().isEmpty()) return; - - Network.Ipam ipam = new Network.Ipam().withConfig(new Network.Ipam.Config() - .withSubnet("172.18.0.0/16") - .withGateway("172.18.0.1")); - docker.dockerClient.createNetworkCmd() - .withName(DockerImpl.DOCKER_CUSTOM_MACVLAN_NETWORK_NAME).withDriver("bridge").withIpam(ipam).exec(); - } - - public static void buildSimpleHttpServerDockerImage(DockerImpl docker, DockerImage dockerImage) { - try { - docker.deleteImage(dockerImage); - } catch (Exception e) { - if (! e.getMessage().equals("Failed to delete docker image " + dockerImage.asString())) { - throw e; - } - } - - // Build the image locally - File dockerFileStream = new File("src/test/resources/simple-ipv6-server"); - docker.buildImage(dockerFileStream, dockerImage); - } - - public enum OS { Linux, Mac_OS_X, Unsupported } - - public static OS getSystemOS() { - switch (System.getProperty("os.name").toLowerCase()) { - case "linux": return OS.Linux; - case "mac os x": return OS.Mac_OS_X; - default: return OS.Unsupported; - } - } -} diff --git a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/VespaSSLConfig.java b/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/VespaSSLConfig.java deleted file mode 100644 index e9bc0181dd7..00000000000 --- a/docker-api/src/main/java/com/yahoo/vespa/hosted/dockerapi/VespaSSLConfig.java +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.dockerapi; - -import com.github.dockerjava.api.exception.DockerClientException; -import com.github.dockerjava.core.SSLConfig; -import org.bouncycastle.asn1.ASN1ObjectIdentifier; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.glassfish.jersey.SslConfigurator; - -import javax.net.ssl.SSLContext; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.Security; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.ArrayList; -import java.util.List; - -import static java.util.Objects.requireNonNull; - - -/** - * This class is based off {@link com.github.dockerjava.core.LocalDirectorySSLConfig}, but with the ability to - * specify path to each of the certificates instead of directory path. Additionally it includes - * {@link com.github.dockerjava.core.util.CertificateUtils} because of version conflict of with - * com.google.code.findbugs.annotations - */ -public class VespaSSLConfig implements SSLConfig { - private final DockerConfig config; - - public VespaSSLConfig(DockerConfig config) { - this.config = config; - } - - @Override - public SSLContext getSSLContext() { - try { - Security.addProvider(new BouncyCastleProvider()); - - // properties acrobatics not needed for java > 1.6 - String httpProtocols = System.getProperty("https.protocols"); - System.setProperty("https.protocols", "TLSv1"); - SslConfigurator sslConfig = SslConfigurator.newInstance(true); - if (httpProtocols != null) { - System.setProperty("https.protocols", httpProtocols); - } - - String keypem = new String(Files.readAllBytes(Paths.get(config.clientKeyPath()))); - String certpem = new String(Files.readAllBytes(Paths.get(config.clientCertPath()))); - String capem = new String(Files.readAllBytes(Paths.get(config.caCertPath()))); - - sslConfig.keyStore(createKeyStore(keypem, certpem)); - sslConfig.keyStorePassword("docker"); - sslConfig.trustStore(createTrustStore(capem)); - - return sslConfig.createSSLContext(); - } catch (Exception e) { - throw new DockerClientException(e.getMessage(), e); - } - } - - public static KeyStore createKeyStore(final String keypem, final String certpem) throws NoSuchAlgorithmException, - IOException, CertificateException, KeyStoreException { - PrivateKey privateKey = loadPrivateKey(keypem); - requireNonNull(privateKey); - List<Certificate> privateCertificates = loadCertificates(certpem); - - KeyStore keyStore = KeyStore.getInstance("JKS"); - keyStore.load(null); - - keyStore.setKeyEntry("docker", - privateKey, - "docker".toCharArray(), - privateCertificates.toArray(new Certificate[privateCertificates.size()]) - ); - - return keyStore; - } - - /** - * from "cert.pem" String - */ - private static List<Certificate> loadCertificates(final String certpem) throws IOException, - CertificateException { - final StringReader certReader = new StringReader(certpem); - try (BufferedReader reader = new BufferedReader(certReader)) { - return loadCertificates(reader); - } - } - - /** - * "cert.pem" from reader - */ - private static List<Certificate> loadCertificates(final Reader reader) throws IOException, - CertificateException { - try (PEMParser pemParser = new PEMParser(reader)) { - List<Certificate> certificates = new ArrayList<>(); - - JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter().setProvider("BC"); - Object certObj = pemParser.readObject(); - - if (certObj instanceof X509CertificateHolder) { - X509CertificateHolder certificateHolder = (X509CertificateHolder) certObj; - certificates.add(certificateConverter.getCertificate(certificateHolder)); - } - - return certificates; - } - } - - - /** - * Return private key ("key.pem") from Reader - */ - private static PrivateKey loadPrivateKey(final Reader reader) throws IOException, NoSuchAlgorithmException { - try (PEMParser pemParser = new PEMParser(reader)) { - Object readObject = pemParser.readObject(); - while (readObject != null) { - if (readObject instanceof PEMKeyPair) { - PEMKeyPair pemKeyPair = (PEMKeyPair) readObject; - PrivateKey privateKey = guessKey(pemKeyPair.getPrivateKeyInfo().getEncoded()); - if (privateKey != null) { - return privateKey; - } - } else if (readObject instanceof PrivateKeyInfo) { - PrivateKeyInfo privateKeyInfo = (PrivateKeyInfo) readObject; - PrivateKey privateKey = guessKey(privateKeyInfo.getEncoded()); - if (privateKey != null) { - return privateKey; - } - } else if (readObject instanceof ASN1ObjectIdentifier) { - // no idea how it can be used - final ASN1ObjectIdentifier asn1ObjectIdentifier = (ASN1ObjectIdentifier) readObject; - } - - readObject = pemParser.readObject(); - } - } - - return null; - } - - private static PrivateKey guessKey(byte[] encodedKey) throws NoSuchAlgorithmException { - //no way to know, so iterate - for (String guessFactory : new String[]{"RSA", "ECDSA"}) { - try { - KeyFactory factory = KeyFactory.getInstance(guessFactory); - - PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedKey); - return factory.generatePrivate(privateKeySpec); - } catch (InvalidKeySpecException ignore) { - } - } - - return null; - } - - /** - * Return KeyPair from "key.pem" - */ - private static PrivateKey loadPrivateKey(final String keypem) throws IOException, NoSuchAlgorithmException { - try (StringReader certReader = new StringReader(keypem); - BufferedReader reader = new BufferedReader(certReader)) { - return loadPrivateKey(reader); - } - } - - /** - * "ca.pem" from String - */ - public static KeyStore createTrustStore(String capem) throws IOException, CertificateException, - KeyStoreException, NoSuchAlgorithmException { - try (Reader certReader = new StringReader(capem)) { - return createTrustStore(certReader); - } - } - - /** - * "ca.pem" from Reader - */ - public static KeyStore createTrustStore(final Reader certReader) throws IOException, CertificateException, - KeyStoreException, NoSuchAlgorithmException { - try (PEMParser pemParser = new PEMParser(certReader)) { - X509CertificateHolder certificateHolder = (X509CertificateHolder) pemParser.readObject(); - Certificate caCertificate = new JcaX509CertificateConverter() - .setProvider("BC") - .getCertificate(certificateHolder); - - KeyStore trustStore = KeyStore.getInstance("JKS"); - trustStore.load(null); - trustStore.setCertificateEntry("ca", caCertificate); - - return trustStore; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - VespaSSLConfig that = (VespaSSLConfig) o; - - return config.equals(that.config); - - } - - @Override - public int hashCode() { - return config.hashCode(); - } -} diff --git a/docker-api/src/main/resources/configdefinitions/docker.def b/docker-api/src/main/resources/configdefinitions/docker.def index b4585318cd8..83fee05dff6 100644 --- a/docker-api/src/main/resources/configdefinitions/docker.def +++ b/docker-api/src/main/resources/configdefinitions/docker.def @@ -1,9 +1,6 @@ # Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. namespace=vespa.hosted.dockerapi -caCertPath string default = "" -clientCertPath string default = "" -clientKeyPath string default = "" uri string default = "unix:///host/var/run/docker.sock" secondsToWaitBeforeKillingContainer int default = 10 |