Add template for assignment 3

This commit is contained in:
2021-05-17 15:44:23 +02:00
parent 1363e83aa3
commit be629aa432
64 changed files with 3556 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>at.ac.tuwien.infosys.dst</groupId>
<artifactId>dst</artifactId>
<version>2021.1</version>
<relativePath>..</relativePath>
</parent>
<artifactId>ass3-elastic</artifactId>
<name>DST :: Assignment 3 :: Elasticity</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>at.ac.tuwien.infosys.dst</groupId>
<artifactId>ass3-messaging</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>at.ac.tuwien.infosys.dst</groupId>
<artifactId>ass3-messaging</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,22 @@
package dst.ass3.elastic;
/**
* Exception indicating that the ContainerService encountered an error when performing a task.
*/
public class ContainerException extends Exception {
public ContainerException() {
}
public ContainerException(String message) {
super(message);
}
public ContainerException(String message, Throwable cause) {
super(message, cause);
}
public ContainerException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,95 @@
package dst.ass3.elastic;
import dst.ass3.messaging.Region;
import java.util.Objects;
/**
* Value type that represents a container.
*/
public class ContainerInfo {
/**
* The name of the container image.
*/
private String image;
/**
* The container ID.
*/
private String containerId;
/**
* True if the container is running.
*/
private boolean running;
/**
* If the container is a worker (indicated by the image dst/ass3-worker), then this field should contain the worker
* region. Otherwise it can be null.
*/
private Region workerRegion;
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public String getContainerId() {
return containerId;
}
public void setContainerId(String containerId) {
this.containerId = containerId;
}
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
public Region getWorkerRegion() {
return workerRegion;
}
public void setWorkerRegion(Region workerRegion) {
this.workerRegion = workerRegion;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ContainerInfo that = (ContainerInfo) o;
return isRunning() == that.isRunning() &&
Objects.equals(getImage(), that.getImage()) &&
Objects.equals(getContainerId(), that.getContainerId()) &&
getWorkerRegion() == that.getWorkerRegion();
}
@Override
public int hashCode() {
return Objects.hash(getImage(), getContainerId(), isRunning(), getWorkerRegion());
}
@Override
public String toString() {
return "ContainerInfo{" +
"image='" + image + '\'' +
", containerId='" + containerId + '\'' +
", running=" + running +
", workerRegion=" + workerRegion +
'}';
}
}
@@ -0,0 +1,23 @@
package dst.ass3.elastic;
/**
* Indicates that a container could not be found.
*/
public class ContainerNotFoundException extends ContainerException {
public ContainerNotFoundException() {
}
public ContainerNotFoundException(String message) {
super(message);
}
public ContainerNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public ContainerNotFoundException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,36 @@
package dst.ass3.elastic;
import dst.ass3.messaging.Region;
import java.util.List;
public interface IContainerService {
/**
* Returns a list of all running containers.
*
* @return a list of ContainerInfo objects
* @throws ContainerException if an error occurred when trying to fetch the running containers.
*/
List<ContainerInfo> listContainers() throws ContainerException;
/**
* Stops the container with the given container ID.
*
* @param containerId ID of the container to stop.
* @throws ContainerNotFoundException if the container to stop is not running
* @throws ContainerException if another error occurred when trying to stop the container
*/
void stopContainer(String containerId) throws ContainerException;
/**
* Starts a worker for the specific {@link dst.ass3.messaging.Region}.
*
* @param region {@link Region} of the worker to start
* @return ContainerInfo of the started container / worker
* @throws ImageNotFoundException if the worker docker image is not available
* @throws ContainerException if another error occurred when trying to start the worker
*/
ContainerInfo startWorker(Region region) throws ContainerException;
}
@@ -0,0 +1,7 @@
package dst.ass3.elastic;
public interface IElasticityController {
void adjustWorkers() throws ContainerException;
}
@@ -0,0 +1,10 @@
package dst.ass3.elastic;
import dst.ass3.messaging.IWorkloadMonitor;
public interface IElasticityFactory {
IContainerService createContainerService();
IElasticityController createElasticityController(IContainerService containerService,
IWorkloadMonitor workloadMonitor);
}
@@ -0,0 +1,22 @@
package dst.ass3.elastic;
/**
* Exception indicating that the image which should be used for a container start is not available.
*/
public class ImageNotFoundException extends ContainerException {
public ImageNotFoundException() {
}
public ImageNotFoundException(String message) {
super(message);
}
public ImageNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public ImageNotFoundException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,24 @@
package dst.ass3.elastic.impl;
import dst.ass3.elastic.IContainerService;
import dst.ass3.elastic.IElasticityController;
import dst.ass3.elastic.IElasticityFactory;
import dst.ass3.messaging.IWorkloadMonitor;
import dst.ass3.messaging.impl.MessagingFactory;
public class ElasticityFactory implements IElasticityFactory {
@Override
public IContainerService createContainerService() {
// TODO
return null;
}
@Override
public IElasticityController createElasticityController(IContainerService containerService,
IWorkloadMonitor workloadMonitor) {
// TODO
return null;
}
}
@@ -0,0 +1,16 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} - %highlight(%5p) [%12.12thread] %cyan(%-40.40logger{39}): %m%n</pattern>
</encoder>
</appender>
<logger name="org.apache.http" level="WARN"/>
<root level="${log.level:-INFO}">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
@@ -0,0 +1,107 @@
package dst.ass3.elastic;
import dst.ass3.elastic.impl.ElasticityFactory;
import dst.ass3.messaging.RabbitResource;
import dst.ass3.messaging.Region;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Queue;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
public class ContainerServiceTest {
private static final Logger LOG = LoggerFactory.getLogger(ContainerServiceTest.class);
@Rule
public RabbitResource rabbit = new RabbitResource();
@Rule
public Timeout timeout = new Timeout(30, TimeUnit.SECONDS);
IContainerService containerService;
IElasticityFactory factory;
@Before
public void setUp() throws Exception {
factory = new ElasticityFactory();
containerService = factory.createContainerService();
rabbit.getAdmin().declareQueue(new Queue("dst.at_vienna"));
rabbit.getAdmin().declareQueue(new Queue("dst.at_linz"));
rabbit.getAdmin().declareQueue(new Queue("dst.de_berlin"));
}
@After
public void tearDown() throws Exception {
rabbit.getAdmin().deleteQueue("dst.at_vienna");
rabbit.getAdmin().deleteQueue("dst.at_linz");
rabbit.getAdmin().deleteQueue("dst.de_berlin");
}
@Test
public void spawnListStop_lifecycleWorks() throws Exception {
List<ContainerInfo> containers = containerService.listContainers();
assertThat("Please stop all containers before running the test", containers.size(), is(0));
ContainerInfo c1 = containerService.startWorker(Region.AT_VIENNA);
LOG.info("Started container {}", c1);
ContainerInfo c2 = containerService.startWorker(Region.AT_LINZ);
LOG.info("Started container {}", c2);
ContainerInfo c3 = containerService.startWorker(Region.DE_BERLIN);
LOG.info("Started container {}", c3);
LOG.info("Waiting for containers to boot...");
Thread.sleep(5000);
containers = containerService.listContainers();
assertThat(containers.size(), is(3));
LOG.info("Stopping containers...");
containerService.stopContainer(c1.getContainerId());
containerService.stopContainer(c2.getContainerId());
containerService.stopContainer(c3.getContainerId());
Thread.sleep(5000);
containers = containerService.listContainers();
assertThat(containers.size(), is(0));
}
@Test(expected = ContainerNotFoundException.class)
public void stopNonExistingContainer_throwsException() throws Exception {
containerService.stopContainer("Non-Existing-Id");
}
@Test
public void listContainers_containsCompleteInfo() throws Exception {
ContainerInfo c1 = containerService.startWorker(Region.AT_VIENNA);
LOG.info("Started container {}", c1);
LOG.info("Waiting for container to boot...");
Thread.sleep(5000);
List<ContainerInfo> containers = containerService.listContainers();
ContainerInfo containerInfo = containers.stream()
.filter(c -> c1.getContainerId().equals(c.getContainerId()))
.findFirst().get();
assertThat(containerInfo, notNullValue());
assertThat(containerInfo.getImage(), equalTo("dst/ass3-worker"));
assertThat(containerInfo.getWorkerRegion(), equalTo(Region.AT_VIENNA));
assertThat(containerInfo.isRunning(), is(true));
LOG.info("Stopping container...");
containerService.stopContainer(containerInfo.getContainerId());
}
}
@@ -0,0 +1,136 @@
package dst.ass3.elastic;
import dst.ass3.elastic.impl.ElasticityFactory;
import dst.ass3.messaging.IWorkloadMonitor;
import dst.ass3.messaging.Region;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@RunWith(MockitoJUnitRunner.class)
public class ElasticityControllerTest {
private static final String WORKER_IMAGE = "dst/ass3-worker";
IElasticityFactory factory;
@Mock
IContainerService containerService;
@Mock
IWorkloadMonitor workloadMonitor;
IElasticityController elasticityController;
Map<Region, Double> processingTimes = new HashMap<>();
Map<Region, Long> workerCount = new HashMap<>();
Map<Region, Long> requestCount = new HashMap<>();
List<ContainerInfo> containers = new ArrayList<>();
@Before
public void setUp() throws Exception {
factory = new ElasticityFactory();
elasticityController = factory.createElasticityController(containerService, workloadMonitor);
processingTimes.clear();
processingTimes.put(Region.AT_VIENNA, 5000.0);
processingTimes.put(Region.DE_BERLIN, 10000.0);
processingTimes.put(Region.AT_LINZ, 2000.0);
when(workloadMonitor.getAverageProcessingTime()).thenReturn(processingTimes);
workerCount.clear();
workerCount.put(Region.AT_VIENNA, 95L);
workerCount.put(Region.DE_BERLIN, 87L);
workerCount.put(Region.AT_LINZ, 61L);
when(workloadMonitor.getWorkerCount()).thenReturn(workerCount);
requestCount.clear();
requestCount.put(Region.AT_VIENNA, 600L);
requestCount.put(Region.DE_BERLIN, 1000L);
requestCount.put(Region.AT_LINZ, 1005L);
when(workloadMonitor.getRequestCount()).thenReturn(requestCount);
containers.clear();
for (int i = 0; i < 95; i++) {
containers.add(containerInfo("vienna" + i, WORKER_IMAGE, Region.AT_VIENNA, true));
}
for (int i = 0; i < 87; i++) {
containers.add(containerInfo("berlin" + i, WORKER_IMAGE, Region.DE_BERLIN, true));
}
for (int i = 0; i < 61; i++) {
containers.add(containerInfo("linz" + i, WORKER_IMAGE, Region.AT_LINZ, true));
}
when(containerService.listContainers()).thenReturn(containers);
}
@After
public void tearDown() {
verify(workloadMonitor, atLeast(1)).getWorkerCount();
verify(workloadMonitor, atLeast(1)).getRequestCount();
verify(workloadMonitor, atLeast(1)).getAverageProcessingTime();
}
@Test
public void notEnoughWorkers_scaleUp() throws Exception {
// remove 10 vienna workers and 10 linz workers
List<ContainerInfo> containersToRemove = containers.stream()
.filter(c -> c.getContainerId().startsWith("vienna7") || c.getContainerId().startsWith("linz1"))
.collect(Collectors.toList());
containers.removeAll(containersToRemove);
workerCount.put(Region.AT_VIENNA, 85L);
workerCount.put(Region.AT_LINZ, 51L);
elasticityController.adjustWorkers();
verify(containerService, never()).stopContainer((String) any(String.class));
verify(containerService, times(15)).startWorker(Region.AT_VIENNA);
verify(containerService, times(16)).startWorker(Region.AT_LINZ);
verify(containerService, never()).startWorker(Region.DE_BERLIN);
verify(containerService, never()).listContainers();
}
@Test
public void tooManyWorkers_scaleDown() throws Exception {
// add 20 more, some should be stopped
for (int i = 0; i < 20; i++) {
containers.add(containerInfo("linz1" + i, WORKER_IMAGE, Region.AT_LINZ, true));
}
workerCount.put(Region.AT_LINZ, 81L);
elasticityController.adjustWorkers();
verify(containerService, times(14)).stopContainer(contains("linz"));
verify(containerService, never()).stopContainer(contains("berlin"));
verify(containerService, never()).stopContainer(contains("vienna"));
verify(containerService, never()).startWorker((Region) any(Region.class));
verify(containerService, times(1)).listContainers();
}
@Test
public void justEnoughWorkers_doNotScale() throws Exception {
elasticityController.adjustWorkers();
verify(containerService, never()).startWorker((Region) any(Region.class));
verify(containerService, never()).stopContainer((String) any());
verify(containerService, never()).listContainers();
}
private ContainerInfo containerInfo(String id, String image, Region workerRegion, boolean running) {
ContainerInfo info = new ContainerInfo();
info.setContainerId(id);
info.setImage(image);
info.setWorkerRegion(workerRegion);
info.setRunning(running);
return info;
}
}