Skip to content

Commit

Permalink
implemented working container layer
Browse files Browse the repository at this point in the history
mostly tested but there may still be a few bugs, but i could run a few instances and connect to them from the game so thats a win i guess
anyways this will be the first working release
next step: containerize the backend itself
  • Loading branch information
grimsi committed Mar 16, 2019
1 parent ee9b285 commit 61328a6
Show file tree
Hide file tree
Showing 16 changed files with 184 additions and 88 deletions.
11 changes: 1 addition & 10 deletions pom.xml
Expand Up @@ -6,7 +6,7 @@
<name>swagger-spring</name>
<version>0.1.0</version>
<properties>
<java.version>1.11</java.version>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<springfox-version>2.7.0</springfox-version>
Expand Down Expand Up @@ -63,15 +63,6 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
</dependency>
<dependency>
<groupId>cz.jirutka.spring</groupId>
<artifactId>embedmongo-spring</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
Expand Down
Expand Up @@ -2,7 +2,10 @@

import grimsi.accservermanager.backend.configuration.ApplicationConfiguration;
import grimsi.accservermanager.backend.dto.UserDto;
import grimsi.accservermanager.backend.repository.ConfigRepository;
import grimsi.accservermanager.backend.repository.InstanceRepository;
import grimsi.accservermanager.backend.repository.UserRepository;
import grimsi.accservermanager.backend.service.FileSystemService;
import grimsi.accservermanager.backend.service.UserService;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
Expand All @@ -23,6 +26,8 @@ public class ACCServerManager implements CommandLineRunner {
@Autowired
private UserRepository userRepository;
@Autowired
private FileSystemService fileSystemService;
@Autowired
private ApplicationConfiguration config;
@Autowired
private UserService userService;
Expand All @@ -37,6 +42,9 @@ public void run(String... arg0) throws Exception {
throw new ExitException();
}

fileSystemService.initFileSystem();
log.info("Initialized filesystem.");

userRepository.deleteAll();
userService.registerUser(new UserDto(config.getUsername(), config.getPassword()));
log.info("Created user with username '" + config.getUsername() + "' and pre-defined password.");
Expand Down
Expand Up @@ -20,7 +20,6 @@ public class ApplicationConfiguration {
private String password;
private String folderPath;
private String serverExecutableName;
private String containerNamePrefix;
private boolean containerNamePostfixEnabled;
private String containerImage;
}
Expand Up @@ -75,19 +75,22 @@ public ResponseEntity<Void> startInstanceById(@PathVariable("instanceId") String
@Override
public ResponseEntity<Void> stopInstanceById(@PathVariable("instanceId") String instanceId) {
String accept = request.getHeader("Accept");
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
instanceService.stopInstance(instanceId);
return new ResponseEntity<>(HttpStatus.OK);
}

@Override
public ResponseEntity<Void> pauseInstanceById(@PathVariable("instanceId") String instanceId) {
String accept = request.getHeader("Accept");
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
instanceService.pauseInstance(instanceId);
return new ResponseEntity<>(HttpStatus.OK);
}

@Override
public ResponseEntity<Void> resumeInstanceById(@PathVariable("instanceId") String instanceId) {
String accept = request.getHeader("Accept");
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
instanceService.resumeInstance(instanceId);
return new ResponseEntity<>(HttpStatus.OK);
}

@Override
Expand Down
@@ -0,0 +1,11 @@
package grimsi.accservermanager.backend.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.CONFLICT)
public class ConflictException extends RuntimeException {
public ConflictException(String message){
super(message);
}
}
@@ -1,12 +1,13 @@
package grimsi.accservermanager.backend.exception;


import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class CouldNotCreateContainerException extends RuntimeException {
public class ContainerException extends RuntimeException{

public CouldNotCreateContainerException(String reason){
super(reason);
public ContainerException(String message){
super(message);
}
}

This file was deleted.

This file was deleted.

@@ -0,0 +1,13 @@
package grimsi.accservermanager.backend.exception;

import grimsi.accservermanager.backend.enums.InstanceState;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.CONFLICT)
public class IllegalInstanceStateException extends RuntimeException {

public IllegalInstanceStateException(String action, String instanceId, InstanceState state) {
super("Cant " + action + " instance '" + instanceId + "' because its current state is '" + state + "'");
}
}
Expand Up @@ -14,7 +14,7 @@ public interface InstanceRepository extends MongoRepository<Instance, String> {

Optional<List<Instance>> findAllByConfig_Id(String configId);

Optional<List<Instance>> findByState(InstanceState state);
Optional<List<Instance>> findAllByState(InstanceState state);

Optional<Instance> findByContainer(String containerName);
}
Expand Up @@ -57,7 +57,12 @@ public void deleteById(String id) {
if(instanceService.isConfigInUse(id)){
throw new ConfigInUseException(id);
}
findById(id);

Config config = configRepository.findById(id).orElseThrow(NotFoundException::new);
eventJsonRepository.deleteById(config.eventJson.id);
settingsJsonRepository.deleteById(config.settingsJson.id);
configurationJsonRepository.deleteById(config.configurationJson.id);

configRepository.deleteById(id);
}

Expand Down
Expand Up @@ -6,21 +6,19 @@
import com.spotify.docker.client.messages.*;
import grimsi.accservermanager.backend.configuration.ApplicationConfiguration;
import grimsi.accservermanager.backend.dto.InstanceDto;
import grimsi.accservermanager.backend.enums.OperatingSystem;
import grimsi.accservermanager.backend.exception.CouldNotCreateContainerException;
import grimsi.accservermanager.backend.exception.CouldNotStartContainerException;
import org.modelmapper.internal.bytebuddy.asm.Advice;
import grimsi.accservermanager.backend.entity.Instance;
import grimsi.accservermanager.backend.exception.ContainerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class ContainerService {
Expand All @@ -29,6 +27,8 @@ public class ContainerService {
@Autowired
InstanceService instanceService;
@Autowired
FileSystemService fileSystemService;
@Autowired
Environment env;
@Autowired
ApplicationConfiguration config;
Expand All @@ -55,27 +55,38 @@ public ContainerService(UtilityService utilityService) {
public void deployInstance(InstanceDto instance) {

String[] ports = {
String.valueOf(instance.getConfig().getConfigurationJson().getTcpPort()),
String.valueOf(instance.getConfig().getConfigurationJson().getUdpPort())};
(instance.getConfig().getConfigurationJson().getTcpPort() + "/tcp"),
(instance.getConfig().getConfigurationJson().getUdpPort() + "/udp")
};

Map<String, List<PortBinding>> portBindings = new HashMap<>();
for (String port : ports) {
portBindings.put(port, Collections.singletonList(PortBinding.of("0.0.0.0", port)));
}

HostConfig hostConfig = HostConfig.builder().portBindings(portBindings).build();
HostConfig hostConfig = HostConfig.builder()
.portBindings(portBindings)
.binds(fileSystemService.getInstanceFolderPath(instance) + ":/opt/server")
.build();

try{
docker.pull(config.getContainerImage());
} catch (DockerException | InterruptedException e){
throw new CouldNotCreateContainerException(e.getMessage());
}
try{
List<Image> images = docker.listImages().stream().filter(
image -> image.repoTags().stream()
.anyMatch(tag -> tag.equals(config.getContainerImage())
)
).collect(Collectors.toList());

if(images.isEmpty()){
docker.pull(config.getContainerImage());
}
} catch (DockerException | InterruptedException e){
throw new ContainerException("Could not find/pull image '" + config.getContainerImage() + "': " + e.getMessage());
}

final ContainerConfig containerConfig = ContainerConfig.builder()
.hostConfig(hostConfig)
.image(config.getContainerImage())
.exposedPorts(ports)
.cmd("sh", "-c", "while :; do sleep 1; done")
.build();

try {
Expand All @@ -86,38 +97,52 @@ public void deployInstance(InstanceDto instance) {
instanceService.save(instance);

} catch (DockerException | InterruptedException e) {
throw new CouldNotCreateContainerException(e.getMessage());
throw new ContainerException("Could not create container for instance '" + instance.getId() + "': " + e.getMessage());
}
}

public void startInstance(InstanceDto instance) {
try {
docker.startContainer(instance.getContainer());
} catch (DockerException | InterruptedException e) {
throw new CouldNotStartContainerException(instance.getContainer(), e.getMessage());
throw new ContainerException("Could not start container '" + instance.getContainer() +"': " + e.getMessage());
}
}

public void stopInstance(InstanceDto instance) {
try {
docker.stopContainer(instance.getContainer(), 10);
} catch (DockerException | InterruptedException e) {
log.error(e.toString());
throw new ContainerException("Could not stop container '" + instance.getContainer() +"': " + e.getMessage());
}
}

public void pauseInstance(InstanceDto instance) {
try {
docker.pauseContainer(instance.getContainer());
} catch (DockerException | InterruptedException e) {
log.error(e.toString());
throw new ContainerException("Could not pause container '" + instance.getContainer() +"': " + e.getMessage());
}
}

public void resumeInstance(InstanceDto instance) {
try {
docker.unpauseContainer(instance.getContainer());
} catch (DockerException | InterruptedException e){
throw new ContainerException("Could not resume container '" + instance.getContainer() +"': " + e.getMessage());
}
}

public void deleteInstance(InstanceDto instance){
try {
stopInstance(instance);
docker.removeContainer(instance.getContainer());
} catch (DockerException | InterruptedException e){
throw new ContainerException("Could not delete container '" + instance.getContainer() +"': " + e.getMessage());
}
}


public ContainerStats getContainerStats(InstanceDto instance) {
try {
return docker.stats(instance.getContainer());
Expand All @@ -127,18 +152,13 @@ public ContainerStats getContainerStats(InstanceDto instance) {
return null;
}

public String buildContainerName(InstanceDto instance){
String name = "";

/* Prefix */
name = name.concat(config.getContainerNamePrefix()).concat("_");

private String buildContainerName(InstanceDto instance){
/* main part */
name = name.concat(instance.getName());
String name = instance.getName();

/* Postfix */
if(config.isContainerNamePostfixEnabled()){
name = name.concat("_").concat(instance.getVersion());
name = name.concat("-v").concat(instance.getVersion());
}

return name;
Expand Down
Expand Up @@ -44,6 +44,14 @@ public class FileSystemService {
private final String EVENT_JSON = "event.json";
private final String SETTINGS_JSON = "settings.json";

public void initFileSystem(){
File instanceRootFolder = new File(config.getFolderPath() + File.separator + INSTANCES_FOLDER);
/* create 'instances' folder */
if(!instanceRootFolder.exists()){
instanceRootFolder.mkdirs();
}
}

public List<String> getInstalledServerVersions(){
File serverRootFolder = new File(config.getFolderPath() + File.separator + SERVERS_FOLDER);

Expand All @@ -60,15 +68,13 @@ public List<String> getInstalledServerVersions(){
return Collections.emptyList();
}

public String getInstanceFolderPath(InstanceDto instance){
return config.getFolderPath() + File.separator + INSTANCES_FOLDER + File.separator + instance.getName();
}

public void createInstanceFolder(InstanceDto instance){
File instanceRootFolder = new File(config.getFolderPath() + File.separator + INSTANCES_FOLDER);
File serverRootFolder = new File(config.getFolderPath() + File.separator + SERVERS_FOLDER);

if(!instanceRootFolder.exists()){
log.info("Creating folder '" + instanceRootFolder.getAbsolutePath() + "'.");
instanceRootFolder.mkdirs();
}

File instanceFolder = new File(instanceRootFolder.getAbsolutePath() + File.separator + instance.getName());

try {
Expand All @@ -86,13 +92,17 @@ public void createInstanceFolder(InstanceDto instance){

public void deleteInstanceFolder(InstanceDto instance){
File folderToDelete = new File(config.getFolderPath() + File.separator + INSTANCES_FOLDER + File.separator + instance.getName());
deleteFolder(folderToDelete);
}

private void deleteFolder(File folder){
try{
Files.walk(folderToDelete.toPath())
Files.walk(folder.toPath())
.map(Path::toFile)
.sorted((o1, o2) -> -o1.compareTo(o2))
.forEach(File::delete);
} catch (IOException e){
throw new CouldNotDeleteFolderException(folderToDelete.toPath());
throw new CouldNotDeleteFolderException(folder.toPath());
}
}

Expand Down

0 comments on commit 61328a6

Please sign in to comment.