PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/helios-services/src/main/java/com/spotify/helios/agent/HealthCheckerFactory.java

https://gitlab.com/kidaa/helios
Java | 213 lines | 152 code | 38 blank | 23 comment | 18 complexity | 98eded0baa494b2fef612a80be44b851 MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright (c) 2014 Spotify AB.
  3. *
  4. * Licensed to the Apache Software Foundation (ASF) under one
  5. * or more contributor license agreements. See the NOTICE file
  6. * distributed with this work for additional information
  7. * regarding copyright ownership. The ASF licenses this file
  8. * to you under the Apache License, Version 2.0 (the
  9. * "License"); you may not use this file except in compliance
  10. * with the License. You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing,
  15. * software distributed under the License is distributed on an
  16. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  17. * KIND, either express or implied. See the License for the
  18. * specific language governing permissions and limitations
  19. * under the License.
  20. */
  21. package com.spotify.helios.agent;
  22. import com.google.common.base.Splitter;
  23. import com.google.common.base.Strings;
  24. import com.google.common.collect.Iterables;
  25. import com.spotify.docker.client.DockerClient;
  26. import com.spotify.docker.client.DockerException;
  27. import com.spotify.docker.client.LogStream;
  28. import com.spotify.helios.common.descriptors.ExecHealthCheck;
  29. import com.spotify.helios.common.descriptors.HealthCheck;
  30. import com.spotify.helios.common.descriptors.HttpHealthCheck;
  31. import com.spotify.helios.common.descriptors.TcpHealthCheck;
  32. import com.spotify.helios.servicescommon.DockerHost;
  33. import org.slf4j.Logger;
  34. import org.slf4j.LoggerFactory;
  35. import java.net.HttpURLConnection;
  36. import java.net.InetSocketAddress;
  37. import java.net.Socket;
  38. import java.net.URL;
  39. import java.util.List;
  40. import static java.util.concurrent.TimeUnit.SECONDS;
  41. public class HealthCheckerFactory {
  42. public static HealthChecker create(final TaskConfig taskConfig, final DockerClient docker,
  43. final DockerHost dockerHost) {
  44. final HealthCheck healthCheck = taskConfig.healthCheck();
  45. if (healthCheck == null) {
  46. return null;
  47. } else if (healthCheck instanceof ExecHealthCheck) {
  48. return new ExecHealthChecker((ExecHealthCheck) healthCheck, docker);
  49. } else if (healthCheck instanceof HttpHealthCheck) {
  50. return new HttpHealthChecker((HttpHealthCheck) healthCheck, taskConfig, dockerHost);
  51. } else if (healthCheck instanceof TcpHealthCheck) {
  52. return new TcpHealthChecker((TcpHealthCheck) healthCheck, taskConfig, docker, dockerHost);
  53. }
  54. throw new IllegalArgumentException("Unknown healthCheck type");
  55. }
  56. static class ExecHealthChecker implements HealthChecker {
  57. private static final Logger log = LoggerFactory.getLogger(ExecHealthChecker.class);
  58. private final ExecHealthCheck healthCheck;
  59. private final DockerClient docker;
  60. ExecHealthChecker(final ExecHealthCheck healthCheck, final DockerClient docker) {
  61. this.healthCheck = healthCheck;
  62. this.docker = docker;
  63. }
  64. @Override
  65. public boolean check(final String containerId) {
  66. // Make sure we are on a docker version that supports exec health checks
  67. if (!compatibleDockerVersion(docker)) {
  68. throw new UnsupportedOperationException(
  69. "docker exec healthcheck is not supported on your docker version");
  70. }
  71. try {
  72. final List<String> cmd = healthCheck.getCommand();
  73. final String execId = docker.execCreate(containerId, cmd.toArray(new String[cmd.size()]),
  74. DockerClient.ExecParameter.STDOUT,
  75. DockerClient.ExecParameter.STDERR);
  76. final String output;
  77. try (LogStream stream = docker.execStart(execId)) {
  78. output = stream.readFully();
  79. }
  80. final int exitCode = docker.execInspect(execId).exitCode();
  81. if (exitCode != 0) {
  82. log.info("healthcheck failed with exit code {}. output {}", exitCode, output);
  83. return false;
  84. }
  85. return true;
  86. } catch (DockerException e) {
  87. return false;
  88. } catch (InterruptedException e) {
  89. Thread.currentThread().interrupt();
  90. return false;
  91. }
  92. }
  93. private static boolean compatibleDockerVersion(final DockerClient docker) {
  94. final String executionDriver;
  95. final String apiVersion;
  96. try {
  97. executionDriver = docker.info().executionDriver();
  98. apiVersion = docker.version().apiVersion();
  99. } catch (DockerException e) {
  100. return false;
  101. } catch (InterruptedException e) {
  102. Thread.currentThread().interrupt();
  103. return false;
  104. }
  105. if (Strings.isNullOrEmpty(executionDriver) || !executionDriver.startsWith("native")) {
  106. return false;
  107. }
  108. if (Strings.isNullOrEmpty(apiVersion)) {
  109. return false;
  110. }
  111. final Iterable<String> split = Splitter.on(".").split(apiVersion);
  112. final int major = Integer.parseInt(Iterables.get(split, 0, "0"));
  113. final int minor = Integer.parseInt(Iterables.get(split, 1, "0"));
  114. return major == 1 && minor >= 18;
  115. }
  116. }
  117. private static class HttpHealthChecker implements HealthChecker {
  118. private static final int CONNECT_TIMEOUT_MILLIS = 500;
  119. private static final long READ_TIMEOUT_MILLIS = SECONDS.toMillis(10);
  120. private final HttpHealthCheck healthCheck;
  121. private final TaskConfig taskConfig;
  122. private final DockerHost dockerHost;
  123. private HttpHealthChecker(final HttpHealthCheck healthCheck, final TaskConfig taskConfig,
  124. final DockerHost dockerHost) {
  125. this.healthCheck = healthCheck;
  126. this.taskConfig = taskConfig;
  127. this.dockerHost = dockerHost;
  128. }
  129. @Override
  130. public boolean check(final String containerId) throws InterruptedException, DockerException {
  131. final Integer port = taskConfig.ports().get(healthCheck.getPort()).getExternalPort();
  132. try {
  133. final URL url = new URL("http", dockerHost.address(), port, healthCheck.getPath());
  134. HttpURLConnection conn = (HttpURLConnection) url.openConnection();
  135. conn.setConnectTimeout(CONNECT_TIMEOUT_MILLIS);
  136. conn.setReadTimeout((int) READ_TIMEOUT_MILLIS);
  137. final int response = conn.getResponseCode();
  138. return response >= 200 && response <= 399;
  139. } catch (Exception e) {
  140. return false;
  141. }
  142. }
  143. }
  144. private static class TcpHealthChecker implements HealthChecker {
  145. private static final int CONNECT_TIMEOUT_MILLIS = 500;
  146. private final TcpHealthCheck healthCheck;
  147. private final TaskConfig taskConfig;
  148. private final DockerClient docker;
  149. private final DockerHost dockerHost;
  150. private TcpHealthChecker(final TcpHealthCheck healthCheck, final TaskConfig taskConfig,
  151. final DockerClient docker, final DockerHost dockerHost) {
  152. this.healthCheck = healthCheck;
  153. this.taskConfig = taskConfig;
  154. this.docker = docker;
  155. this.dockerHost = dockerHost;
  156. }
  157. @Override
  158. public boolean check(final String containerId) throws InterruptedException, DockerException {
  159. final Integer port = taskConfig.ports().get(healthCheck.getPort()).getExternalPort();
  160. InetSocketAddress address = new InetSocketAddress(dockerHost.address(), port);
  161. if (address.getAddress().isLoopbackAddress()) {
  162. // tcp connections to a container-mapped port on loopback always succeed,
  163. // regardless of if the container is listening or not. use the bridge address instead.
  164. final String bridge = docker.inspectContainer(containerId).networkSettings().gateway();
  165. address = new InetSocketAddress(bridge, port);
  166. }
  167. try (final Socket s = new Socket()) {
  168. s.connect(address, CONNECT_TIMEOUT_MILLIS);
  169. } catch (Exception e) {
  170. return false;
  171. }
  172. return true;
  173. }
  174. }
  175. }