Skip to content

Commit

Permalink
✨ : save stack status on job completion
Browse files Browse the repository at this point in the history
  • Loading branch information
juwit committed Jun 10, 2019
1 parent 63efc78 commit d8098f3
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.apache.http.impl.io.IdentityInputStream;
import org.apache.http.impl.io.SessionInputBufferImpl;
import org.glassfish.jersey.message.internal.EntityInputStream;
import org.springframework.stereotype.Component;

import java.io.FilterInputStream;
import java.io.InputStream;
Expand All @@ -19,8 +20,10 @@
import java.util.LinkedList;
import java.util.List;

@Component
public class HttpHijackWorkaround {
public static WritableByteChannel getOutputStream(final LogStream stream, final String uri) throws Exception {

WritableByteChannel getOutputStream(final LogStream stream, final String uri) throws Exception {
// @formatter:off
final String[] fields =
new String[] {"reader",
Expand Down
38 changes: 25 additions & 13 deletions src/main/java/io/codeka/gaia/runner/StackRunner.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package io.codeka.gaia.runner;

import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.LogStream;
import com.spotify.docker.client.messages.ContainerConfig;
import io.codeka.gaia.bo.Job;
import io.codeka.gaia.bo.Settings;
import io.codeka.gaia.bo.Stack;
import io.codeka.gaia.bo.TerraformModule;
import io.codeka.gaia.bo.*;
import io.codeka.gaia.repository.JobRepository;
import io.codeka.gaia.repository.StackRepository;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
Expand Down Expand Up @@ -37,12 +35,21 @@ public class StackRunner {

private Map<String, Job> jobs = new HashMap<>();

private StackRepository stackRepository;

private HttpHijackWorkaround httpHijackWorkaround;

private JobRepository jobRepository;

@Autowired
public StackRunner(DockerClient dockerClient, ContainerConfig.Builder containerConfigBuilder, Settings settings, StackCommandBuilder stackCommandBuilder) {
public StackRunner(DockerClient dockerClient, ContainerConfig.Builder containerConfigBuilder, Settings settings, StackCommandBuilder stackCommandBuilder, StackRepository stackRepository, HttpHijackWorkaround httpHijackWorkaround, JobRepository jobRepository) {
this.dockerClient = dockerClient;
this.containerConfigBuilder = containerConfigBuilder;
this.settings = settings;
this.stackCommandBuilder = stackCommandBuilder;
this.stackRepository = stackRepository;
this.httpHijackWorkaround = httpHijackWorkaround;
this.jobRepository = jobRepository;
}

@Async
Expand All @@ -52,7 +59,9 @@ public void run(Job job, TerraformModule module, Stack stack) {

try{
// FIXME This is certainly no thread safe
var containerConfig = containerConfigBuilder.env(settings.env()).build();
var containerConfig = containerConfigBuilder
.env(settings.env())
.build();

System.out.println("Create container");
var containerCreation = dockerClient.createContainer(containerConfig);
Expand All @@ -61,7 +70,7 @@ public void run(Job job, TerraformModule module, Stack stack) {
// attach stdin
System.err.println("Attach container");
var logStream = dockerClient.attachContainer(containerId, DockerClient.AttachParameter.STDIN, DockerClient.AttachParameter.STDOUT, DockerClient.AttachParameter.STDERR, DockerClient.AttachParameter.STREAM);
var writable = HttpHijackWorkaround.getOutputStream(logStream, "unix:///var/run/docker.sock");
var writable = httpHijackWorkaround.getOutputStream(logStream, "unix:///var/run/docker.sock");

System.err.println("Starting container");
dockerClient.startContainer(containerId);
Expand Down Expand Up @@ -118,21 +127,24 @@ public void run(Job job, TerraformModule module, Stack stack) {
// docker.killContainer(containerId);

// get full logs to validate the output
String logs;
try (LogStream stream = dockerClient.logs(containerCreation.id(), DockerClient.LogsParam.stdout(), DockerClient.LogsParam.stderr())) {
logs = stream.readFully();
}
// String logs;
// try (LogStream stream = dockerClient.logs(containerCreation.id(), DockerClient.LogsParam.stdout(), DockerClient.LogsParam.stderr())) {
// logs = stream.readFully();
// }

if(containerExit.statusCode() == 0){
job.end();
// delete container :)
dockerClient.removeContainer(containerCreation.id());

// update stack information
stack.setState(StackState.RUNNING);
stackRepository.save(stack);
}
else{
job.fail();
}


} catch (Exception e) {
job.fail();
e.printStackTrace();
Expand Down
11 changes: 9 additions & 2 deletions src/main/resources/templates/stacks.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ <h2>Stacks</h2>
<div class="card-columns">
<div class="card" v-for="stack in stacks">
<div class="card-body">
<h5 class="card-title">{{stack.name}}</h5>
<h5 class="card-title">
{{stack.name}}
<span class="badge badge-pill badge-success" v-if="stack.state === 'NEW'"><i class="fas fa-star-of-life"></i> new</span>
<span class="badge badge-pill badge-primary" v-if="stack.state === 'RUNNING'"><i class="far fa-check-square"></i> running</span>
<span class="badge badge-pill badge-warning" v-if="stack.state === 'TO_UPDATE'"><i class="fas fa-upload"></i> to update</span>
<span class="badge badge-pill badge-danger" v-if="stack.state === 'DESTROYED'"><i class="fas fa-snowplow"></i> destroyed</span>
</h5>
<a :href="'/stacks/' + stack.id" data-toggle="tooltip" title="Edit this stack" class="btn btn-primary"><i class="far fa-edit"></i></a>
</div>
</div>
Expand All @@ -55,9 +61,10 @@ <h5 class="card-title">{{stack.name}}</h5>
<script src="/webjars/vue/2.5.16/vue.js"></script>

<script type="application/ecmascript">
let stacks;
$.get("/api/stacks")
.then(data => {
const stacks = data._embedded.stacks;
stacks = data._embedded.stacks;

new Vue({
el: "#placeholder",
Expand Down
108 changes: 108 additions & 0 deletions src/test/java/io/codeka/gaia/runner/StackRunnerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.codeka.gaia.runner;

import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.messages.ContainerConfig;
import com.spotify.docker.client.messages.ContainerCreation;
import com.spotify.docker.client.messages.ContainerExit;
import io.codeka.gaia.bo.*;
import io.codeka.gaia.repository.JobRepository;
import io.codeka.gaia.repository.StackRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.nio.channels.WritableByteChannel;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class StackRunnerTest {

@Mock
private DockerClient dockerClient;

@Mock(answer = Answers.RETURNS_SELF)
private ContainerConfig.Builder builder;

@Mock
private Settings settings;

@Mock
private StackCommandBuilder stackCommandBuilder;

@Mock
private StackRepository stackRepository;

@Mock
private HttpHijackWorkaround httpHijackWorkaround;

@Mock
private JobRepository jobRepository;

@Test
void job_shouldBeSavedToDatabaseAfterRun() throws Exception {
var job = new Job();
var module = new TerraformModule();
var stack = new Stack();

var stackRunner = new StackRunner(dockerClient, builder, settings, stackCommandBuilder, stackRepository, httpHijackWorkaround, jobRepository);

// simulating a container with id 12
var containerCreation = mock(ContainerCreation.class);
when(containerCreation.id()).thenReturn("12");
when(dockerClient.createContainer(any())).thenReturn(containerCreation);

// setting mocks to let test pass till the end
var writableByteChannel = mock(WritableByteChannel.class);
when(httpHijackWorkaround.getOutputStream(any(), any())).thenReturn(writableByteChannel);

when(stackCommandBuilder.buildApplyScript(stack, module)).thenReturn("");

// given
var containerExit = mock(ContainerExit.class);
when(containerExit.statusCode()).thenReturn(0L);
when(dockerClient.waitContainer("12")).thenReturn(containerExit);

// when
stackRunner.run(job, module, stack);

// then
verify(jobRepository).save(job);
}

@Test
void successfullJob_shouldSetTheStackStateToRunning() throws Exception {
var job = new Job();
var module = new TerraformModule();
var stack = new Stack();

var stackRunner = new StackRunner(dockerClient, builder, settings, stackCommandBuilder, stackRepository, httpHijackWorkaround, jobRepository);

// simulating a container with id 12
var containerCreation = mock(ContainerCreation.class);
when(containerCreation.id()).thenReturn("12");
when(dockerClient.createContainer(any())).thenReturn(containerCreation);

// setting mocks to let test pass till the end
var writableByteChannel = mock(WritableByteChannel.class);
when(httpHijackWorkaround.getOutputStream(any(), any())).thenReturn(writableByteChannel);

when(stackCommandBuilder.buildApplyScript(stack, module)).thenReturn("");

// given
var containerExit = mock(ContainerExit.class);
when(containerExit.statusCode()).thenReturn(0L);
when(dockerClient.waitContainer("12")).thenReturn(containerExit);

// when
stackRunner.run(job, module, stack);

// then
assertEquals(StackState.RUNNING, stack.getState());
verify(stackRepository).save(stack);
}

}

0 comments on commit d8098f3

Please # to comment.