Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = DatashieldContainerConfig.class, name = "ds"),
@JsonSubTypes.Type(value = VanillaContainerConfig.class, name = "vanilla")
@JsonSubTypes.Type(value = VanillaContainerConfig.class, name = "vanilla"),
@JsonSubTypes.Type(value = FlowerSupernodeContainerConfig.class, name = "flower-supernode"),
@JsonSubTypes.Type(value = FlowerSuperexecContainerConfig.class, name = "flower-superexec")
})
public interface ContainerConfig {

String getName();

String getImage();

@Nullable
String getHost();

@Nullable
Integer getPort();

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,9 @@
containerStatusService.updateStatus(containerName, "Container installed", null, null);
stopContainer(dockerContainerName);
removeContainer(dockerContainerName);
if (containerConfig instanceof FlowerContainer) {
createNetworkIfNotExists(FlowerContainer.NETWORK_NAME);
}
installImage(containerConfig);
startContainer(dockerContainerName);

Expand Down Expand Up @@ -250,27 +253,87 @@
}
}

void installImage(ContainerConfig containerConfig) {
if (containerConfig.getImage() == null) {
throw new MissingImageException(containerConfig.getImage());
void installImage(ContainerConfig config) {
if (config.getImage() == null) {
throw new MissingImageException(config.getImage());
}

// if rock is in the image name, it's rock
int imageExposed = containerConfig.getImage().contains("rock") ? 8085 : 6311;
try (CreateContainerCmd cmd = dockerClient.createContainerCmd(config.getImage())) {
HostConfig hostConfig =
new HostConfig().withRestartPolicy(RestartPolicy.unlessStoppedRestart());

configurePortBindings(cmd, hostConfig, config);
configureNetworkMode(hostConfig, config);
configureVolumes(hostConfig, config);

cmd.withHostConfig(hostConfig).withName(config.getName());
configureEnv(cmd, config);
configureDockerCmd(cmd, config);

cmd.exec();
} catch (DockerException e) {
throw new ImageStartFailedException(config.getImage(), e);
}
}

/** Binds container port to host. Skipped when port is null (e.g. flower containers). */
private void configurePortBindings(
CreateContainerCmd cmd, HostConfig hostConfig, ContainerConfig config) {
if (config.getPort() == null) return;

int imageExposed = config.getImage().contains("rock") ? 8085 : 6311;
ExposedPort exposed = ExposedPort.tcp(imageExposed);
Ports portBindings = new Ports();
portBindings.bind(exposed, Ports.Binding.bindPort(containerConfig.getPort()));
try (CreateContainerCmd cmd = dockerClient.createContainerCmd(containerConfig.getImage())) {
cmd.withExposedPorts(exposed)
.withHostConfig(
new HostConfig()
.withPortBindings(portBindings)
.withRestartPolicy(RestartPolicy.unlessStoppedRestart()))
.withName(containerConfig.getName())
.withEnv("DEBUG=FALSE")
.exec();
} catch (DockerException e) {
throw new ImageStartFailedException(containerConfig.getImage(), e);
portBindings.bind(exposed, Ports.Binding.bindPort(config.getPort()));
hostConfig.withPortBindings(portBindings);
cmd.withExposedPorts(exposed);
}

private void configureNetworkMode(HostConfig hostConfig, ContainerConfig config) {
if (config instanceof FlowerContainer) {
hostConfig.withNetworkMode(FlowerContainer.NETWORK_NAME);
}
}

private void configureEnv(CreateContainerCmd cmd, ContainerConfig config) {
if (config instanceof FlowerSuperexecContainerConfig) {
cmd.withEnv("DEBUG=FALSE", "ARMADILLO_CONTAINER_NAME=" + config.getName());
} else {
cmd.withEnv("DEBUG=FALSE");
}
}

/** Mounts host paths into the container. Only supported for flower supernodes. */
private void configureVolumes(HostConfig hostConfig, ContainerConfig config) {
if (!(config instanceof FlowerSupernodeContainerConfig)) return;
Map<String, Object> opts = config.getDockerOptions();
if (opts == null || !opts.containsKey("volumes")) return;

Object volumesObj = opts.get("volumes");
if (!(volumesObj instanceof Map<?, ?> rawMap)) return;

Bind[] binds =
rawMap.entrySet().stream()
.filter(e -> e.getKey() instanceof String && e.getValue() instanceof String)
.map(
e ->
new Bind((String) e.getKey(), new Volume((String) e.getValue()), AccessMode.ro))
.toArray(Bind[]::new);
hostConfig.withBinds(binds);
}

private void configureDockerCmd(CreateContainerCmd cmd, ContainerConfig config) {
List<String> args = config.getDockerArgs();
if (args != null && !args.isEmpty()) {
cmd.withCmd(args.toArray(new String[0]));
}
}

private void createNetworkIfNotExists(String networkName) {
List<Network> networks = dockerClient.listNetworksCmd().withNameFilter(networkName).exec();
if (networks.isEmpty()) {
LOG.info("Creating docker network '{}'", networkName);
dockerClient.createNetworkCmd().withName(networkName).withDriver("bridge").exec();

Check failure on line 336 in armadillo/src/main/java/org/molgenis/armadillo/container/DockerService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use try-with-resources or close this "CreateNetworkCmd" in a "finally" clause.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB16oII49zzZPvjhR&open=AZyvB16oII49zzZPvjhR&pullRequest=985
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.molgenis.armadillo.container;

/** Marker interface for Flower federated learning containers. */
public interface FlowerContainer {

Check failure on line 4 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerContainer.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move constants defined in this interfaces to another class or enum.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_JII49zzZPvjhS&open=AZyvB1_JII49zzZPvjhS&pullRequest=985
String NETWORK_NAME = "flower-network";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.molgenis.armadillo.container;

import com.fasterxml.jackson.annotation.*;
import com.google.auto.value.AutoValue;
import jakarta.annotation.Nullable;
import java.util.List;
import java.util.Map;

@AutoValue
@JsonTypeName("flower-superexec")
// AutoValue requires redeclaring interface methods as abstract - suppress S1161
@SuppressWarnings("java:S1161")
public abstract class FlowerSuperexecContainerConfig
implements ContainerConfig, OpenContainer, FlowerContainer {

@Override
public abstract String getName();

Check warning on line 17 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getName" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhe&open=AZyvB1_fII49zzZPvjhe&pullRequest=985

@Override
public abstract String getImage();

Check warning on line 20 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getImage" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhf&open=AZyvB1_fII49zzZPvjhf&pullRequest=985

@Override
@Nullable
public abstract String getHost();

Check warning on line 24 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getHost" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhg&open=AZyvB1_fII49zzZPvjhg&pullRequest=985

@Override
@Nullable
public abstract Integer getPort();

Check warning on line 28 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getPort" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhh&open=AZyvB1_fII49zzZPvjhh&pullRequest=985

@Override
@Nullable
public abstract Long getImageSize();

Check warning on line 32 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getImageSize" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhi&open=AZyvB1_fII49zzZPvjhi&pullRequest=985

@Override
@Nullable
public abstract String getInstallDate();

Check warning on line 36 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getInstallDate" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhj&open=AZyvB1_fII49zzZPvjhj&pullRequest=985

@Override
@Nullable
public abstract String getLastImageId();

Check warning on line 40 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getLastImageId" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhk&open=AZyvB1_fII49zzZPvjhk&pullRequest=985

@Override
@Nullable
public abstract List<String> getDockerArgs();

Check warning on line 44 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getDockerArgs" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhl&open=AZyvB1_fII49zzZPvjhl&pullRequest=985

@Override
@Nullable
public abstract Map<String, Object> getDockerOptions();

Check warning on line 48 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getDockerOptions" is defined in the "ContainerConfig" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhm&open=AZyvB1_fII49zzZPvjhm&pullRequest=985

@Override
@Nullable
public abstract String getVersionId();

Check warning on line 52 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getVersionId" is defined in the "OpenContainer" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjhn&open=AZyvB1_fII49zzZPvjhn&pullRequest=985

@Override
@Nullable
public abstract String getCreationDate();

Check warning on line 56 in armadillo/src/main/java/org/molgenis/armadillo/container/FlowerSuperexecContainerConfig.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

"getCreationDate" is defined in the "OpenContainer" interface and can be removed from this class.

See more on https://sonarcloud.io/project/issues?id=org.molgenis%3Aarmadillo-service&issues=AZyvB1_fII49zzZPvjho&open=AZyvB1_fII49zzZPvjho&pullRequest=985

@Override
@JsonIgnore
public String getType() {
return "flower-superexec";
}

@JsonCreator
public static FlowerSuperexecContainerConfig create(
@JsonProperty("name") String name,
@JsonProperty("image") String image,
@JsonProperty("host") @Nullable String host,
@JsonProperty("port") @Nullable Integer port,
@JsonProperty("imageSize") @Nullable Long imageSize,
@JsonProperty("installDate") @Nullable String installDate,
@JsonProperty("lastImageId") @Nullable String lastImageId,
@JsonProperty("dockerArgs") @Nullable List<String> dockerArgs,
@JsonProperty("dockerOptions") @Nullable Map<String, Object> dockerOptions,
@JsonProperty("versionId") @Nullable String versionId,
@JsonProperty("creationDate") @Nullable String creationDate) {

return builder()
.name(name)
.image(image)
.host(host)
.port(port)
.imageSize(imageSize)
.installDate(installDate)
.lastImageId(lastImageId)
.dockerArgs(dockerArgs)
.dockerOptions(dockerOptions)
.versionId(versionId)
.creationDate(creationDate)
.build();
}

public static FlowerSuperexecContainerConfig.Builder builder() {
return new AutoValue_FlowerSuperexecContainerConfig.Builder();
}

public abstract Builder toBuilder();

@AutoValue.Builder
public abstract static class Builder {

public abstract Builder name(String name);

public abstract Builder image(String image);

public abstract Builder host(@Nullable String host);

public abstract Builder port(@Nullable Integer port);

public abstract Builder imageSize(@Nullable Long imageSize);

public abstract Builder installDate(@Nullable String installDate);

public abstract Builder lastImageId(@Nullable String lastImageId);

public abstract Builder dockerArgs(@Nullable List<String> dockerArgs);

public abstract Builder dockerOptions(@Nullable Map<String, Object> dockerOptions);

public abstract Builder versionId(@Nullable String versionId);

public abstract Builder creationDate(@Nullable String creationDate);

public abstract FlowerSuperexecContainerConfig build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.molgenis.armadillo.container;

import org.molgenis.armadillo.metadata.DefaultImageMetadata;
import org.molgenis.armadillo.metadata.OpenContainersImageMetadata;
import org.springframework.stereotype.Component;

@Component
public class FlowerSuperexecContainerUpdater
implements ContainerUpdater<FlowerSuperexecContainerConfig>,
OpenContainersUpdater<FlowerSuperexecContainerConfig> {

@Override
public Class<FlowerSuperexecContainerConfig> getSupportedType() {
return FlowerSuperexecContainerConfig.class;
}

@Override
public ContainerConfig updateDefaultImageMetadata(
FlowerSuperexecContainerConfig existingConfig, DefaultImageMetadata metadata) {
return existingConfig.toBuilder()
.lastImageId(metadata.currentImageId())
.imageSize(metadata.imageSize())
.installDate(
metadata.installDate() != null
? metadata.installDate()
: existingConfig.getInstallDate())
.build();
}

@Override
public ContainerConfig updateOpenContainersMetaData(
FlowerSuperexecContainerConfig existingConfig, OpenContainersImageMetadata metadata) {
return existingConfig.toBuilder()
.versionId(metadata.openContainersId())
.creationDate(metadata.creationDate())
.build();
}
}
Loading