Add template for assignment 3
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user