package org.apache.mesos.logstash.systemtest; import com.containersol.minimesos.cluster.MesosCluster; import com.containersol.minimesos.mesos.ClusterArchitecture; import com.containersol.minimesos.mesos.DockerClientFactory; import com.containersol.minimesos.state.State; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.command.CreateContainerResponse; import com.github.dockerjava.api.model.Container; import com.github.dockerjava.api.model.Link; import com.github.dockerjava.core.command.PullImageResultCallback; import com.mashape.unirest.http.exceptions.UnirestException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.RandomStringUtils; import org.elasticsearch.client.Client; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.SearchHits; import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.IntStream; import java.util.stream.Stream; import static com.jayway.awaitility.Awaitility.await; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.*; /** * Tests whether the framework is deployed correctly */ @SuppressWarnings("Duplicates") public class DeploymentSystemTest { private static DockerClient dockerClient = DockerClientFactory.build(); private static final Logger LOGGER = LoggerFactory.getLogger(DeploymentSystemTest.class); private MesosCluster cluster = new MesosCluster(new ClusterArchitecture.Builder() .withZooKeeper() .withMaster(zooKeeper -> new LogstashMesosMaster(dockerClient, zooKeeper)) .withAgent(zooKeeper -> new LogstashMesosSlave(dockerClient, zooKeeper)) .build()); Optional<LogstashSchedulerContainer> scheduler = Optional.empty(); protected File tmpRoot = new File(".tmp"); protected File tmpDir; @Before public void before() throws Exception { tmpDir = new File(tmpRoot, String.valueOf(System.currentTimeMillis())); FileUtils.forceMkdir(tmpDir); tmpRoot.deleteOnExit(); cluster.start(); } @After public void after() throws Exception { scheduler.ifPresent(scheduler -> dockerClient.stopContainerCmd(scheduler.getContainerId()).withTimeout(30).exec()); await().atMost(30, SECONDS).pollInterval(1, SECONDS).until(() -> { JSONArray frameworks = cluster.getClusterStateInfo().getJSONArray("frameworks"); assertEquals(0, frameworks.length()); }); cluster.stop(); } private void deployScheduler(String mesosRole, String elasticsearchHost, boolean useDocker, File logstashConfig, boolean enableSyslog) { String zookeeperIpAddress = cluster.getZkContainer().getIpAddress(); String mesosMasterIpAddress = cluster.getMasterContainer().getIpAddress(); this.scheduler = Optional.of(new LogstashSchedulerContainer(dockerClient, mesosMasterIpAddress, zookeeperIpAddress, mesosRole, elasticsearchHost)); this.scheduler.get().setDocker(useDocker); if (enableSyslog) { this.scheduler.get().enableSyslog(); } if (logstashConfig != null) { this.scheduler.get().setLogstashConfig(logstashConfig); } cluster.addAndStartContainer(this.scheduler.get()); waitForFramework(); } private void waitForFramework() { await().atMost(15, SECONDS).pollInterval(1, SECONDS).until(() -> { JSONArray frameworks = getFrameworks(); assertEquals("only one framework is expected", 1, frameworks.length()); JSONObject framework = frameworks.getJSONObject(0); assertTrue("framework is expected to have running tasks", framework.has("tasks")); }); await().atMost(10, SECONDS).pollInterval(1, SECONDS).until(() -> { JSONArray tasks = getFrameworks().getJSONObject(0).getJSONArray("tasks"); assertEquals("only one task of the framework is expected", 1, tasks.length()); assertTrue("the task is expected to have a name", tasks.getJSONObject(0).has("name")); assertEquals("the task should have the predefined name", "logstash.task", tasks.getJSONObject(0).getString("name")); }); await().atMost(60, SECONDS).pollInterval(1, SECONDS).until(() -> { assertEquals("task expected to be running", "TASK_RUNNING", getFrameworks().getJSONObject(0).getJSONArray("tasks").getJSONObject(0).getString("state")); }); } protected JSONArray getFrameworks() { return cluster.getClusterStateInfo().getJSONArray("frameworks"); } @Test public void testDeploymentDocker() throws JsonParseException, UnirestException, JsonMappingException { deployScheduler(null, null, true, null, false); } @Test public void testDeploymentJar() throws JsonParseException, UnirestException, JsonMappingException { deployScheduler(null, null, false, null, false); } @Test public void testDeploymentExternalConfiguration() throws Exception { final ElasticsearchContainer elasticsearchInstance = new ElasticsearchContainer(dockerClient); cluster.addAndStartContainer(elasticsearchInstance); final File logstashConfig = new File(tmpDir, "logstash.config"); FileUtils.writeStringToFile(logstashConfig, "input { generator {} } output { elasticsearch { hosts => \"" + elasticsearchInstance.getIpAddress() + ":9200" + "\" } }"); Client elasticsearchClient = elasticsearchInstance.createClient(); deployScheduler(null, null, false, logstashConfig, false); SECONDS.sleep(2); await().atMost(20, SECONDS).pollDelay(1, SECONDS).until(() -> { final SearchHits hits = elasticsearchClient.prepareSearch("logstash-*").setQuery(QueryBuilders.simpleQueryStringQuery("Hello*")).addField("message").addField("mesos_agent_id").execute().actionGet().getHits(); assertNotEquals(0, hits.totalHits()); Map<String, SearchHitField> fields = hits.getAt(0).fields(); String esMessage = fields.get("message").getValue(); assertEquals("Hello world!", esMessage.trim()); }); } @Test public void willForwardDataToElasticsearchInDockerMode() throws Exception { final ElasticsearchContainer elasticsearchInstance = new ElasticsearchContainer(dockerClient); cluster.addAndStartContainer(elasticsearchInstance); Client elasticsearchClient = elasticsearchInstance.createClient(); deployScheduler("logstash", elasticsearchInstance.getIpAddress() + ":9200", true, null, true); final String sysLogPort = "514"; final String randomLogLine = "Hello " + RandomStringUtils.randomAlphanumeric(32); dockerClient.pullImageCmd("ubuntu:15.10").exec(new PullImageResultCallback()).awaitSuccess(); final String logstashSlave = dockerClient.listContainersCmd().withSince(cluster.getAgents().get(0).getContainerId()).exec().stream().filter(container -> container.getImage().endsWith("/logstash-executor:latest")).findFirst().map(Container::getId).orElseThrow(() -> new AssertionError("Unable to find logstash container")); assertTrue("logstash slave is expected to be running", dockerClient.inspectContainerCmd(logstashSlave).exec().getState().isRunning()); final CreateContainerResponse loggerContainer = dockerClient.createContainerCmd("ubuntu:15.10").withLinks(new Link(logstashSlave, "logstash")).withCmd("logger", "--server=logstash", "--port=" + sysLogPort, "--udp", "--rfc3164", randomLogLine).exec(); dockerClient.startContainerCmd(loggerContainer.getId()).exec(); await().atMost(5, SECONDS).pollDelay(1, SECONDS).until(() -> { final String finishedAt = dockerClient.inspectContainerCmd(loggerContainer.getId()).exec().getState().getFinishedAt(); assertNotEquals("", finishedAt.trim()); assertNotEquals("0001-01-01T00:00:00Z", finishedAt); }); final int exitCode = dockerClient.inspectContainerCmd(loggerContainer.getId()).exec().getState().getExitCode(); dockerClient.removeContainerCmd(loggerContainer.getId()).exec(); assertEquals(0, exitCode); await().atMost(10, SECONDS).pollDelay(1, SECONDS).until(() -> { final SearchHits hits = elasticsearchClient.prepareSearch("logstash-*").setQuery(QueryBuilders.simpleQueryStringQuery("Hello")).addField("message").addField("mesos_agent_id").execute().actionGet().getHits(); assertEquals(1, hits.totalHits()); Map<String, SearchHitField> fields = hits.getAt(0).fields(); String esMessage = fields.get("message").getValue(); assertEquals(randomLogLine, esMessage.trim()); String esMesosSlaveId = fields.get("mesos_agent_id").getValue(); String trueSlaveId; try { trueSlaveId = cluster.getClusterStateInfo().getJSONArray("slaves").getJSONObject(0).getString("id"); } catch (Exception e) { throw new RuntimeException(e); } assertEquals(trueSlaveId, esMesosSlaveId.trim()); }); } @Test public void willForwardDataToElasticsearchInJarMode() throws Exception { final ElasticsearchContainer elasticsearchInstance = new ElasticsearchContainer(dockerClient); cluster.addAndStartContainer(elasticsearchInstance); Client elasticsearchClient = elasticsearchInstance.createClient(); deployScheduler("logstash", elasticsearchInstance.getIpAddress() + ":9200", false, null, true); final String sysLogPort = "514"; final String randomLogLine = "Hello " + RandomStringUtils.randomAlphanumeric(32); dockerClient.pullImageCmd("ubuntu:15.10").exec(new PullImageResultCallback()).awaitSuccess(); final String logstashSlave = cluster.getAgents().get(0).getContainerId(); assertTrue(dockerClient.inspectContainerCmd(logstashSlave).exec().getState().isRunning()); final CreateContainerResponse loggerContainer = dockerClient.createContainerCmd("ubuntu:15.10").withLinks(new Link(logstashSlave, "logstash")).withCmd("logger", "--server=logstash", "--port=" + sysLogPort, "--udp", "--rfc3164", randomLogLine).exec(); dockerClient.startContainerCmd(loggerContainer.getId()).exec(); await().atMost(5, SECONDS).pollDelay(1, SECONDS).until(() -> { final String finishedAt = dockerClient.inspectContainerCmd(loggerContainer.getId()).exec().getState().getFinishedAt(); assertNotEquals("", finishedAt.trim()); assertNotEquals("0001-01-01T00:00:00Z", finishedAt); }); final int exitCode = dockerClient.inspectContainerCmd(loggerContainer.getId()).exec().getState().getExitCode(); dockerClient.removeContainerCmd(loggerContainer.getId()).exec(); assertEquals(0, exitCode); await().atMost(10, SECONDS).pollDelay(1, SECONDS).until(() -> { final SearchHits hits = elasticsearchClient.prepareSearch("logstash-*").setQuery(QueryBuilders.simpleQueryStringQuery("Hello")).addField("message").addField("mesos_agent_id").execute().actionGet().getHits(); assertEquals(1, hits.totalHits()); Map<String, SearchHitField> fields = hits.getAt(0).fields(); String esMessage = fields.get("message").getValue(); assertEquals(randomLogLine, esMessage.trim()); String esMesosSlaveId = fields.get("mesos_agent_id").getValue(); String trueSlaveId; try { trueSlaveId = cluster.getClusterStateInfo().getJSONArray("slaves").getJSONObject(0).getString("id"); } catch (Exception e) { throw new RuntimeException(e); } assertEquals(trueSlaveId, esMesosSlaveId.trim()); }); } @Test public void willAddExecutorOnNewNodes() throws JsonParseException, UnirestException, JsonMappingException { deployScheduler(null, null, true, null, false); IntStream.range(0, 2).forEach(value -> cluster.addAndStartContainer(new LogstashMesosSlave(dockerClient, cluster.getZkContainer()))); await().atMost(1, TimeUnit.MINUTES).pollInterval(1, SECONDS).until( () -> State.fromJSON(cluster.getClusterStateInfo().toString()).getFramework("logstash").getTasks().stream().filter(task -> task.getState().equals("TASK_RUNNING")).count() == 3 ); // TODO use com.containersol.minimesos.state.Task when it exposes the slave_id property https://github.com/ContainerSolutions/minimesos/issues/168 JSONArray tasks = cluster.getClusterStateInfo().getJSONArray("frameworks").getJSONObject(0).getJSONArray("tasks"); Set<String> slaveIds = new TreeSet<>(); for (int i = 0; i < tasks.length(); i++) { slaveIds.add(tasks.getJSONObject(i).getString("slave_id")); } assertEquals(3, slaveIds.size()); } @Test public void willStartNewExecutorIfOldExecutorFails() throws Exception { deployScheduler("logstash", null, true, null, false); Function<String, Stream<Container>> getLogstashExecutorsSince = containerId -> dockerClient .listContainersCmd() .withSince(containerId) .exec() .stream() .filter(container -> container.getImage().endsWith("/logstash-executor:latest")); await().atMost(1, TimeUnit.MINUTES).pollDelay(1, SECONDS).until(() -> { long count = getLogstashExecutorsSince.apply(cluster.getAgents().get(0).getContainerId()).count(); LOGGER.info("There are " + count + " executors since " + cluster.getAgents().get(0).getContainerId()); assertEquals(1, count); }); final String slaveToKillContainerId = getLogstashExecutorsSince.apply(cluster.getAgents().get(0).getContainerId()).findFirst().map(Container::getId).orElseThrow(() -> new RuntimeException("Unable to find logstash container")); dockerClient.killContainerCmd(slaveToKillContainerId).exec(); await().atMost(1, TimeUnit.MINUTES).pollDelay(1, SECONDS).until(() -> { assertEquals(1, getLogstashExecutorsSince.apply(slaveToKillContainerId).count()); }); } }