14 | implements DockerServerEntityService {
15 | @Override
16 | public DockerServerEntity getServer() {
17 | return getDao().getUniqueEnabledServer();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/container/auto/ContainerMessage.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.container.auto;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.auto.value.AutoValue;
6 |
7 | import javax.annotation.Nullable;
8 | import java.io.Serializable;
9 |
10 | @AutoValue
11 | public abstract class ContainerMessage implements Serializable {
12 | private static final long serialVersionUID = 3977595045080393805L;
13 |
14 | @JsonProperty("id") public abstract String id();
15 | @Nullable @JsonProperty("status") public abstract String status();
16 |
17 | @JsonCreator
18 | public static ContainerMessage create(@JsonProperty("id") final String id,
19 | @JsonProperty("status") final String status) {
20 | return new AutoValue_ContainerMessage(id, status);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/exceptions/ContainerFinalizationException.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.exceptions;
2 |
3 | import org.nrg.containers.model.container.auto.Container;
4 |
5 | public class ContainerFinalizationException extends ContainerException {
6 | public ContainerFinalizationException(final Container container, final String message) {
7 | super(message);
8 | this.container = container;
9 | }
10 |
11 | public ContainerFinalizationException(final Container container, final String message, final Throwable e) {
12 | super(message, e);
13 | this.container = container;
14 | }
15 |
16 | public ContainerFinalizationException(final Container container, final Throwable e) {
17 | super(e);
18 | this.container = container;
19 | }
20 |
21 | private Container container;
22 |
23 | public Container getContainer() {
24 | return container;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/illegal-args-command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "param-test",
3 | "description": "Test resolving params",
4 | "type": "docker",
5 | "image": "busybox:latest",
6 | "command-line": "echo #ANYTHING#",
7 | "inputs": [
8 | {
9 | "name": "ANYTHING",
10 | "type": "string",
11 | "required": true
12 | }
13 | ],
14 | "xnat": [
15 | {
16 | "name": "identity-wrapper",
17 | "label": "Param test: identity",
18 | "description": "Test resolving the command with wrapper params",
19 | "external-inputs": [
20 | {
21 | "name": "anything",
22 | "type": "string",
23 | "required": true,
24 | "provides-value-to-command-input": "ANYTHING"
25 | }
26 | ],
27 | "derived-inputs": []
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/testSessionScanResource/session.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "session1",
3 | "type": "Session",
4 | "label": "session1",
5 | "uri": "/experiments/session1",
6 | "scans": [
7 | {
8 | "id": "scan1",
9 | "type": "Scan",
10 | "scan-type": "SCANTYPE",
11 | "uri": "/experiments/session1/scans/scan1",
12 | "frames": "12",
13 | "series-description": "great!",
14 | "modality": "super!",
15 | "quality": "the best!",
16 | "note": "I love it - me",
17 | "resources": [
18 | {
19 | "id": 0,
20 | "type": "Resource",
21 | "label": "DICOM",
22 | "directory": "this will be overwritten at runtime",
23 | "uri": "/experiments/session1/scans/scan1/resources/0"
24 | }
25 | ]
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/docs/md2confluencehtml.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | die(){
4 | popd > /dev/null
5 | echo >&2 "$@"
6 | exit -1
7 | }
8 |
9 | BASEDIR=$(dirname "$0")
10 |
11 | MDFILE=$1
12 | if [[ -z "$MDFILE" ]]; then
13 | echo "Missing arg: path to Markdown file" &>2
14 | exit 1
15 | fi
16 |
17 | HTMLFILE=${MDFILE%.md}.html
18 |
19 | pandoc -o $HTMLFILE $MDFILE || die "Failed to convert $MDFILE from markdown to HTML"
20 |
21 | gawk '//,/<\/code><\/pre>/ {
22 | gsub(/"/, "\"");
23 | gsub(/</, "<");
24 | gsub(/>/, ">");
25 | gsub(/'/, "");
26 | sub(//, "<\/pre>/, "]]>");
28 | }
29 | {print}' $HTMLFILE > $HTMLFILE.confluence || die "Failed to convert HTML code blocks to Confluence macros"
30 |
31 | $BASEDIR/html2confluence.py $HTMLFILE.confluence || die "Failed to run python HTML to Confluence HTML script"
32 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/jms/requests/ContainerStagingRequest.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.jms.requests;
2 |
3 | import lombok.Data;
4 | import lombok.EqualsAndHashCode;
5 |
6 | import java.io.Serializable;
7 | import java.util.Map;
8 |
9 | @Data
10 | @EqualsAndHashCode(callSuper = false)
11 | public class ContainerStagingRequest extends ContainerRequest implements Serializable {
12 | public static final String DESTINATION = "containerStagingRequest";
13 |
14 | private static final long serialVersionUID = -1608362951558668332L;
15 |
16 | private final String project;
17 | private final long wrapperId;
18 | private final long commandId;
19 | private final String wrapperName;
20 | private final Map inputValues;
21 | private final String username;
22 | private final String workflowId;
23 |
24 | public String getDestination() {
25 | return DESTINATION;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/resources/templates/screens/project/project_bundle_tabs.vm:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | #set($bundles = $project.getBundles())
7 |
26 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/exceptions/CommandMountResolutionException.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.exceptions;
2 |
3 | import org.nrg.containers.model.command.auto.Command.CommandMount;
4 |
5 | public class CommandMountResolutionException extends CommandResolutionException {
6 | final CommandMount mount;
7 |
8 | public CommandMountResolutionException(final String message) {
9 | this(message, null, null);
10 | }
11 |
12 | public CommandMountResolutionException(final String message, final CommandMount mount) {
13 | this(message, mount, null);
14 | }
15 |
16 | public CommandMountResolutionException(final String message, final Throwable cause) {
17 | this(message, null, cause);
18 | }
19 |
20 | public CommandMountResolutionException(final String message, final CommandMount mount, final Throwable cause) {
21 | super(message, cause);
22 | this.mount = mount;
23 | }
24 |
25 | public CommandMount getMount() {
26 | return mount;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/mountTests/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "description": "a dummy command that does nothing",
4 | "type": "docker",
5 | "image": "busybox:latest",
6 | "command-line": "echo hello world",
7 | "mounts": [
8 | {
9 | "name": "mount",
10 | "writable": "false",
11 | "path": "/input",
12 | "required": true
13 | }
14 | ],
15 | "xnat": [
16 | {
17 | "name": "resource-wrapper",
18 | "label": "Hello world: Resource",
19 | "description": "run the dummy command with a resource, which we mount",
20 | "external-inputs": [
21 | {
22 | "name": "resource",
23 | "description": "a resource",
24 | "type": "Resource",
25 | "required": true,
26 | "provides-files-for-command-mount": "mount"
27 | }
28 | ]
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/rest/ContainerLogPollResponse.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.rest;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import lombok.Data;
5 |
6 | @Data
7 | public class ContainerLogPollResponse {
8 | @JsonIgnore public static final String LOG_COMPLETE_TIMESTAMP = "-1";
9 | private final String content;
10 | private final boolean fromFile;
11 | private final String timestamp;
12 | private final long bytesRead;
13 |
14 | public static ContainerLogPollResponse fromFile(final String content) {
15 | return new ContainerLogPollResponse(content, true, LOG_COMPLETE_TIMESTAMP, -1);
16 | }
17 |
18 | public static ContainerLogPollResponse fromLive(final String content, final String timestamp) {
19 | return new ContainerLogPollResponse(content, false, timestamp, -1);
20 | }
21 |
22 | public static ContainerLogPollResponse fromComplete(final String content) {
23 | return new ContainerLogPollResponse(content, false, LOG_COMPLETE_TIMESTAMP, -1);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/testPathTranslation/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "description": "a dummy command that does nothing",
4 | "type": "docker",
5 | "image": "busybox:latest",
6 | "command-line": "echo hello world",
7 | "mounts": [
8 | {
9 | "name": "mount",
10 | "writable": "false",
11 | "path": "/a/path",
12 | "required": true
13 | }
14 | ],
15 | "xnat": [
16 | {
17 | "name": "resource-wrapper",
18 | "label": "Hello world: Resource",
19 | "description": "run the dummy command with a resource, which we mount",
20 | "external-inputs": [
21 | {
22 | "name": "resource",
23 | "description": "a resource",
24 | "type": "Resource",
25 | "required": true,
26 | "provides-files-for-command-mount": "mount"
27 | }
28 | ]
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/mountTests/command-writable-mount.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-world",
3 | "description": "a dummy command that does nothing",
4 | "type": "docker",
5 | "image": "busybox:latest",
6 | "command-line": "echo hello world",
7 | "mounts": [
8 | {
9 | "name": "mount",
10 | "writable": true,
11 | "path": "/input",
12 | "required": true
13 | }
14 | ],
15 | "xnat": [
16 | {
17 | "name": "resource-wrapper",
18 | "label": "Hello world: Resource",
19 | "description": "run the dummy command with a resource, which we mount",
20 | "external-inputs": [
21 | {
22 | "name": "resource",
23 | "description": "a resource",
24 | "type": "Resource",
25 | "required": true,
26 | "provides-files-for-command-mount": "mount"
27 | }
28 | ]
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | working_directory: ~/container-service
5 | docker:
6 | - image: circleci/openjdk:8
7 | steps:
8 | - checkout
9 | - restore_cache:
10 | key: cache-{{ .Branch }}-{{ checksum "build.gradle" }}
11 | - run:
12 | name: Refresh dependencies
13 | command: ./gradlew --refresh-dependencies dependencies
14 | - run:
15 | name: Compile
16 | command: ./gradlew compileJava compileTestJava
17 | - save_cache:
18 | key: cache-{{ .Branch }}-{{ checksum "build.gradle" }}
19 | paths:
20 | - ~/.gradle
21 | - setup_remote_docker
22 | - run:
23 | name: Run tests
24 | command: ./gradlew test
25 | - store_test_results:
26 | path: build/test-results/
27 | - store_artifacts:
28 | path: build/reports/tests/
29 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/exceptions/CommandValidationException.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.exceptions;
2 |
3 | import com.google.common.collect.ImmutableList;
4 |
5 | import java.util.List;
6 |
7 | public class CommandValidationException extends Exception {
8 | private ImmutableList errors;
9 |
10 | public CommandValidationException(final List errors) {
11 | super();
12 | setErrors(errors);
13 | }
14 |
15 | public CommandValidationException(final String error) {
16 | super();
17 | this.errors = error == null ? ImmutableList.of() : ImmutableList.of(error);
18 | }
19 |
20 | public CommandValidationException(final List errors, final Throwable e) {
21 | super(e);
22 | setErrors(errors);
23 | }
24 |
25 | private void setErrors(final List errors) {
26 | this.errors = errors == null ? ImmutableList.of() : ImmutableList.copyOf(errors);
27 | }
28 |
29 | public List getErrors() {
30 | return errors;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/daos/DockerHubDao.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.daos;
2 |
3 | import org.nrg.containers.exceptions.NotUniqueException;
4 | import org.nrg.containers.model.dockerhub.DockerHubEntity;
5 | import org.nrg.framework.orm.hibernate.AbstractHibernateDAO;
6 | import org.springframework.stereotype.Repository;
7 |
8 | @Repository
9 | public class DockerHubDao extends AbstractHibernateDAO {
10 | public DockerHubEntity findByName(final String name) throws NotUniqueException {
11 | try {
12 | return findByUniqueProperty("name", name);
13 | } catch (RuntimeException e) {
14 | throw new NotUniqueException("More than one result with name " + name + ".");
15 | }
16 | }
17 |
18 | public DockerHubEntity findByUrl(final String url) throws NotUniqueException {
19 | try {
20 | return findByUniqueProperty("url", url);
21 | } catch (RuntimeException e) {
22 | throw new NotUniqueException("More than one result with url " + url + ".");
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/CommandEventMappingService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 |
4 | import org.nrg.containers.model.CommandEventMapping;
5 | import org.nrg.framework.exceptions.NotFoundException;
6 | import org.nrg.framework.orm.hibernate.BaseHibernateService;
7 |
8 | import java.util.List;
9 |
10 |
11 | public interface CommandEventMappingService extends BaseHibernateService {
12 | void convert(long id) throws Exception;
13 | @Deprecated void enable(long id) throws NotFoundException;
14 | @Deprecated void enable(CommandEventMapping commandEventMapping);
15 | @Deprecated void disable(long id) throws NotFoundException;
16 | @Deprecated void disable(CommandEventMapping commandEventMapping);
17 |
18 | List findByEventType(String eventType);
19 | List findByEventType(String eventType, boolean onlyEnabled);
20 |
21 | List findByProject(String project);
22 | List findByProject(String project, boolean onlyEnabled);
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/resources/dockerRestApiTest/commands.json:
--------------------------------------------------------------------------------
1 | [{
2 | "name": "label-test",
3 | "description": "Command to test label-parsing and command-importing code",
4 | "type": "docker",
5 | "command-line": "#CMD#",
6 | "inputs": [
7 | {
8 | "name": "CMD",
9 | "description": "Command to run",
10 | "required": true
11 | }
12 | ]
13 | },
14 | {
15 | "name": "dcm2niix-scan",
16 | "label": "Label test: dcm2niix",
17 | "description": "Run dcm2niix on a scan's DICOMs",
18 | "type": "docker",
19 | "command-line": "/run/dcm2niix-scan.sh #scanId# #sessionId#",
20 | "mounts": [
21 | {
22 | "name": "DICOM",
23 | "path": "/input"
24 | },
25 | {
26 | "name": "NIFTI",
27 | "path": "/output"
28 | }
29 | ],
30 | "inputs": [
31 | {
32 | "name": "scanId",
33 | "required": true
34 | },
35 | {
36 | "name": "sessionId",
37 | "required":true
38 | }
39 | ]
40 | }]
41 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/model/xnat/OuterTestPojo.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.xnat;
2 |
3 | import com.google.common.base.MoreObjects;
4 |
5 | import java.util.Objects;
6 |
7 | public class OuterTestPojo {
8 | private InnerTestPojo outerKey1;
9 |
10 | public InnerTestPojo getOuterKey1() {
11 | return outerKey1;
12 | }
13 |
14 | public void setOuterKey1(final InnerTestPojo outerKey1) {
15 | this.outerKey1 = outerKey1;
16 | }
17 |
18 | @Override
19 | public boolean equals(final Object o) {
20 | if (this == o) return true;
21 | if (o == null || getClass() != o.getClass()) return false;
22 | final OuterTestPojo that = (OuterTestPojo) o;
23 | return Objects.equals(this.outerKey1, that.outerKey1);
24 | }
25 |
26 | @Override
27 | public int hashCode() {
28 | return Objects.hash(outerKey1);
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return MoreObjects.toStringHelper(this)
34 | .add("key1", outerKey1)
35 | .toString();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/orchestration/entity/OrchestrationProjectEntity.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.orchestration.entity;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.nrg.framework.orm.hibernate.AbstractHibernateEntity;
5 |
6 | import javax.persistence.*;
7 | import javax.validation.constraints.NotNull;
8 |
9 | @Entity
10 | @Slf4j
11 | public class OrchestrationProjectEntity extends AbstractHibernateEntity {
12 | private String projectId;
13 | private OrchestrationEntity orchestrationEntity;
14 |
15 | @NotNull
16 | @Column(unique = true)
17 | public String getProjectId() {
18 | return projectId;
19 | }
20 |
21 | public void setProjectId(String projectId) {
22 | this.projectId = projectId;
23 | }
24 |
25 | @ManyToOne(fetch = FetchType.LAZY, optional = false)
26 | public OrchestrationEntity getOrchestrationEntity() {
27 | return orchestrationEntity;
28 | }
29 |
30 | public void setOrchestrationEntity(OrchestrationEntity orchestrationEntity) {
31 | this.orchestrationEntity = orchestrationEntity;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/security/ContainerManagerUserAuthorization.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.security;
2 |
3 | import org.nrg.containers.utils.ContainerUtils;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.aspectj.lang.JoinPoint;
6 | import org.nrg.xapi.authorization.AbstractXapiAuthorization;
7 | import org.nrg.xdat.security.helpers.Roles;
8 | import org.nrg.xft.security.UserI;
9 | import javax.servlet.http.HttpServletRequest;
10 | import org.nrg.xdat.security.helpers.AccessLevel;
11 |
12 |
13 | import org.springframework.stereotype.Component;
14 |
15 |
16 | /**
17 | * Checks whether user has a ContainerManager Role.
18 | */
19 | @Slf4j
20 | @Component
21 | public class ContainerManagerUserAuthorization extends AbstractXapiAuthorization {
22 | @Override
23 | protected boolean checkImpl(final AccessLevel accessLevel, final JoinPoint joinPoint, final UserI user, final HttpServletRequest request) {
24 | return Roles.checkRole(user, ContainerUtils.CONTAINER_MANAGER_ROLE);
25 | }
26 |
27 | @Override
28 | protected boolean considerGuests() {
29 | return false;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/ContainerFinalizeService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import org.apache.commons.lang3.tuple.Pair;
4 | import org.nrg.containers.model.container.auto.Container;
5 | import org.nrg.xft.security.UserI;
6 |
7 | import javax.annotation.Nullable;
8 | import java.util.List;
9 |
10 | public interface ContainerFinalizeService {
11 | Pair finalizeContainer(Container toFinalize,
12 | UserI userI,
13 | boolean isFailed, final List wrapupContainers);
14 |
15 | void sendContainerStatusUpdateEmail(UserI user,
16 | boolean completionStatus,
17 | String pipelineName,
18 | String xnatId,
19 | String xnatLabel,
20 | String project,
21 | @Nullable List filePaths);
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/jms/requests/ContainerRequest.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.jms.requests;
2 |
3 |
4 | import org.apache.commons.lang3.StringUtils;
5 |
6 | abstract public class ContainerRequest {
7 | public static String inQueueStatusPrefix = "_";
8 |
9 | /**
10 | * Returns true if workflow status indicates that the request is in the JMS queue
11 | * @param workflowStatus the workflow status
12 | * @return T/F
13 | */
14 | public boolean inJMSQueue(String workflowStatus) {
15 | // We'd prefer to store the workflow & user as part of the request but since they're not serializable, we don't
16 | // have access to them. Thus, whatever calls this method has to do the work of retrieving the workflow status
17 | return StringUtils.startsWith(workflowStatus, inQueueStatusPrefix);
18 | }
19 |
20 | /**
21 | * Return the status that'll indicate that this request is in the JMS queue
22 | * @param workflowStatus the current status
23 | * @return the status indicating queued
24 | */
25 | public String makeJMSQueuedStatus(String workflowStatus) {
26 | return inQueueStatusPrefix + workflowStatus;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.rootCategory=DEBUG, container-service-test
2 |
3 | log4j.appender.container-service-test=org.apache.log4j.FileAppender
4 | log4j.appender.container-service-test.File=container-service-test.log
5 | log4j.appender.container-service-test.layout=org.apache.log4j.PatternLayout
6 | log4j.appender.container-service-test.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L [%tid] - %m%n
7 |
8 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender
9 | log4j.appender.stdout.Target=System.out
10 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
11 | log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L [%tid] - %m%n
12 |
13 | # Root logger option
14 | log4j.rootLogger=INFO, stdout
15 |
16 | log4j.logger.org.nrg.containers=DEBUG
17 | log4j.logger.org.nrg.transporter=DEBUG
18 |
19 | #log4j.logger.org.nrg.framework.orm=DEBUG
20 |
21 | # Log everything. Good for troubleshooting
22 | #log4j.logger.org.hibernate=DEBUG
23 |
24 | # Log all JDBC parameters
25 | #log4j.logger.org.hibernate.type=ALL
26 |
27 | #log4j.logger.org.springframework.security=DEBUG
28 | #log4j.logger.org.nrg.xdat=DEBUG
--------------------------------------------------------------------------------
/src/test/resources/setupCommand/command-with-setup-command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "command-with-setup-command",
3 | "description": "A command with a setup command",
4 | "type": "docker",
5 | "image": "busybox:latest",
6 | "command-line": "ls /input",
7 | "inputs": [],
8 | "mounts": [
9 | {
10 | "name": "input",
11 | "path": "/input"
12 | }
13 | ],
14 | "xnat": [
15 | {
16 | "name": "wrapper",
17 | "label": "Command with setup command",
18 | "description": "Insert a setup command before the command",
19 | "external-inputs": [
20 | {
21 | "name": "resource",
22 | "description": "A fake resource, which will be mounted",
23 | "type": "Resource",
24 | "required": true,
25 | "provides-files-for-command-mount": "input",
26 | "via-setup-command": "xnat/test-setup-command:latest:setup-command"
27 | }
28 | ],
29 | "derived-inputs": [],
30 | "output-handlers": []
31 | }
32 | ]
33 | }
--------------------------------------------------------------------------------
/src/test/resources/wrapupCommand/Dockerfile.main:
--------------------------------------------------------------------------------
1 | FROM busybox:latest
2 | COPY main-command-script.sh /usr/local/bin/
3 | LABEL org.nrg.commands="[{\"inputs\": [], \"name\": \"command-with-wrapup-command\", \"command-line\": \"main-command-script.sh\", \"outputs\": [{\"mount\": \"out\", \"name\": \"output\"}], \"image\": \"xnat/test-wrapup-command-main:latest\", \"xnat\": [{\"output-handlers\": [{\"accepts-command-output\": \"output\", \"label\": \"WRAPUP\", \"via-wrapup-command\": \"xnat/test-wrapup-command:latest:wrapup-command\", \"name\": \"output-handler\", \"as-a-child-of\": \"session\"}], \"derived-inputs\": [{\"type\": \"Resource\", \"name\": \"resource\", \"provides-files-for-command-mount\": \"in\", \"description\": \"A fake resource, which will be mounted\", \"derived-from-wrapper-input\": \"session\"}], \"name\": \"wrapper\", \"external-inputs\": [{\"required\": true, \"type\": \"Session\", \"name\": \"session\", \"description\": \"A session\"}], \"description\": \"Use a wrapup command on the output handler\"}], \"mounts\": [{\"path\": \"/input\", \"name\": \"in\"}, {\"writable\": true, \"path\": \"/output\", \"name\": \"out\"}], \"type\": \"docker\", \"description\": \"A command with a wrapup command\"}]"
4 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/configuration/ProjectEnabledReport.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.configuration;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.google.auto.value.AutoValue;
6 |
7 | import java.io.Serializable;
8 |
9 | @AutoValue
10 | public abstract class ProjectEnabledReport implements Serializable {
11 | private static final long serialVersionUID = -6360752238770532920L;
12 |
13 | @JsonProperty("enabled-for-site") public abstract Boolean enabledForSite();
14 | @JsonProperty("enabled-for-project") public abstract Boolean enabledForProject();
15 | @JsonProperty("project") public abstract String project();
16 |
17 | @JsonCreator
18 | public static ProjectEnabledReport create(@JsonProperty("enabled-for-site") final Boolean enabledForSite,
19 | @JsonProperty("enabled-for-project") final Boolean enabledForProject,
20 | @JsonProperty("project") final String project) {
21 | return new AutoValue_ProjectEnabledReport(enabledForSite, enabledForProject, project);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/secrets/EnvironmentVariableResolvedSecret.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.secrets;
2 |
3 | import org.nrg.containers.exceptions.ContainerServiceSecretException;
4 |
5 | import java.util.Optional;
6 |
7 | @ResolverFor(destination = EnvironmentVariableSecretDestination.class)
8 | public class EnvironmentVariableResolvedSecret extends ResolvedSecret.WithValue {
9 | @SuppressWarnings("unused")
10 | public EnvironmentVariableResolvedSecret(
11 | final Secret secret,
12 | @SuppressWarnings("OptionalUsedAsFieldOrParameterType") final Optional value
13 | ) throws ContainerServiceSecretException {
14 | super(secret, value);
15 | }
16 |
17 | public EnvironmentVariableResolvedSecret(final Secret secret, final String value){
18 | super(secret, value);
19 | }
20 |
21 | public String envName() {
22 | return destination().identifier();
23 | }
24 |
25 | public String envValue() {
26 | return value();
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return "EnvironmentVariableResolvedSecret{" +
32 | "envName=\"" + envName() + "\"" +
33 | "}";
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/config/CommandConfigurationTestConfig.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.config;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import org.mockito.Mockito;
5 | import org.nrg.config.services.ConfigService;
6 | import org.nrg.containers.services.ContainerConfigService;
7 | import org.nrg.containers.services.OrchestrationEntityService;
8 | import org.nrg.containers.services.OrchestrationProjectEntityService;
9 | import org.nrg.containers.services.impl.ContainerConfigServiceImpl;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 | import org.springframework.context.annotation.Import;
13 |
14 | @Configuration
15 | @Import({ObjectMapperConfig.class})
16 | public class CommandConfigurationTestConfig {
17 | @Bean
18 | public ConfigService configService() {
19 | return Mockito.mock(ConfigService.class);
20 | }
21 |
22 | @Bean
23 | public ContainerConfigService containerConfigService(ConfigService configService, ObjectMapper mapper) {
24 | return new ContainerConfigServiceImpl(configService, mapper, Mockito.mock(OrchestrationProjectEntityService.class), Mockito.mock(OrchestrationEntityService.class));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/api/KubernetesClient.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.api;
2 |
3 | import io.kubernetes.client.openapi.ApiClient;
4 | import org.nrg.containers.exceptions.ContainerBackendException;
5 | import org.nrg.containers.exceptions.ContainerException;
6 | import org.nrg.containers.model.container.auto.Container;
7 | import org.nrg.framework.exceptions.NotFoundException;
8 |
9 | import java.time.OffsetDateTime;
10 |
11 | public interface KubernetesClient {
12 | ApiClient getBackendClient();
13 | void setBackendClient(ApiClient apiClient);
14 | String getNamespace();
15 | void setNamespace(String namespace);
16 |
17 | void start();
18 | void stop();
19 |
20 | String ping() throws ContainerBackendException;
21 | String getLog(String podName, final LogType logType, final Boolean withTimestamp, final OffsetDateTime since) throws ContainerBackendException;
22 |
23 | String createJob(final Container toCreate, final DockerControlApi.NumReplicas numReplicas, String serverContainerUser, final String gpuVendor)
24 | throws ContainerBackendException, ContainerException;
25 | void unsuspendJob(final String jobName) throws ContainerBackendException;
26 | void removeJob(String jobName) throws NotFoundException, ContainerBackendException;
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/events/model/ServiceTaskEvent.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.events.model;
2 |
3 | import com.google.auto.value.AutoValue;
4 | import org.nrg.containers.model.container.auto.Container;
5 | import org.nrg.containers.model.container.auto.ServiceTask;
6 | import org.nrg.framework.event.EventI;
7 |
8 | import javax.annotation.Nonnull;
9 |
10 | @AutoValue
11 | public abstract class ServiceTaskEvent implements EventI {
12 | private static final long serialVersionUID = 5423148597895355320L;
13 |
14 | public static final String QUEUE = "serviceTaskEventQueue";
15 |
16 | public abstract ServiceTask task();
17 | public abstract Container service();
18 | public abstract EventType eventType();
19 |
20 | public enum EventType {
21 | ProcessTask,
22 | Restart,
23 | Waiting
24 | }
25 |
26 | public static ServiceTaskEvent create(final @Nonnull ServiceTask task, final @Nonnull Container service) {
27 | return create(task, service, EventType.ProcessTask);
28 | }
29 | public static ServiceTaskEvent create(final @Nonnull ServiceTask task, final @Nonnull Container service,
30 | final EventType eventType) {
31 | return new AutoValue_ServiceTaskEvent(task, service, eventType);
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/command/entity/CommandType.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.command.entity;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonValue;
5 |
6 | import java.util.EnumSet;
7 | import java.util.List;
8 | import java.util.stream.Collectors;
9 |
10 | public enum CommandType {
11 |
12 | DOCKER("docker"),
13 | DOCKER_SETUP("docker-setup"),
14 | DOCKER_WRAPUP("docker-wrapup");
15 |
16 | private final String name;
17 |
18 | @JsonCreator
19 | CommandType(final String name) {
20 | this.name = name;
21 | }
22 |
23 | @JsonValue
24 | public String getName() {
25 | return name;
26 | }
27 |
28 | public String shortName() {
29 | // Remove docker
30 | return name.substring("docker".length());
31 | }
32 |
33 | public static List names() {
34 | return EnumSet.allOf(CommandType.class).stream().map(CommandType::getName).collect(Collectors.toList());
35 | }
36 |
37 | public static CommandType withName(final String name) {
38 | for (final CommandType type : EnumSet.allOf(CommandType.class)) {
39 | if (type.name.equals(name)) {
40 | return type;
41 | }
42 | }
43 | return null;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/impl/OrchestrationProjectEntityServiceImpl.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services.impl;
2 |
3 | import org.nrg.containers.daos.OrchestrationProjectEntityDao;
4 | import org.nrg.containers.model.orchestration.entity.OrchestrationProjectEntity;
5 | import org.nrg.containers.services.OrchestrationProjectEntityService;
6 | import org.nrg.framework.orm.hibernate.AbstractHibernateEntityService;
7 | import org.springframework.stereotype.Service;
8 |
9 | import javax.annotation.Nullable;
10 | import javax.transaction.Transactional;
11 |
12 | @Transactional
13 | @Service
14 | public class OrchestrationProjectEntityServiceImpl extends AbstractHibernateEntityService implements OrchestrationProjectEntityService {
15 | @Override
16 | @Nullable
17 | public OrchestrationProjectEntity find(String project) {
18 | return getDao().findByUniqueProperty("projectId", project);
19 | }
20 |
21 | @Override
22 | public synchronized void checkAndDisable(String project, long wrapperId) {
23 | OrchestrationProjectEntity ope = find(project);
24 | if (ope == null) {
25 | return;
26 | }
27 | if (ope.getOrchestrationEntity().usesWrapper(wrapperId)) {
28 | delete(ope);
29 | }
30 | flush();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/events/model/ScanArchiveEventToLaunchCommands.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.events.model;
2 |
3 | import com.google.auto.value.AutoAnnotation;
4 | import com.google.auto.value.AutoValue;
5 | import org.nrg.containers.model.xnat.Scan;
6 | import org.nrg.framework.event.EventI;
7 | import org.nrg.xft.security.UserI;
8 |
9 | import java.util.HashSet;
10 |
11 | @AutoValue
12 | public abstract class ScanArchiveEventToLaunchCommands implements EventI {
13 | private static final long serialVersionUID = 6052791990440957304L;
14 |
15 | public static final String QUEUE = "scanArchiveEventToLaunchCommandsQueue";
16 |
17 | public abstract Scan scan();
18 | public abstract String project();
19 | public abstract UserI user();
20 |
21 | public static ScanArchiveEventToLaunchCommands create(final Scan scan,
22 | final String project,
23 | final UserI user) {
24 | return new AutoValue_ScanArchiveEventToLaunchCommands(scan, project, user);
25 | }
26 |
27 | public static ScanArchiveEventToLaunchCommands create(final Scan scan,
28 | final UserI user) {
29 | return create(scan, scan.getProject(user, false, new HashSet<>()).getId(), user);
30 | }
31 | }
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/configuration/PluginVersionCheck.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.configuration;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.google.auto.value.AutoValue;
5 |
6 | import javax.annotation.Nullable;
7 | import java.io.Serializable;
8 |
9 | @AutoValue
10 | public abstract class PluginVersionCheck implements Serializable {
11 | private static final long serialVersionUID = 270432226185468124L;
12 |
13 | @JsonProperty("compatible") public abstract Boolean compatible();
14 | @Nullable @JsonProperty("xnat-version-detected") public abstract String xnatVersionDetected();
15 | @Nullable @JsonProperty("min-xnat-version-required") public abstract String xnatVersionRequired();
16 | @Nullable @JsonProperty("message") public abstract String message();
17 |
18 | public static Builder builder() {return new AutoValue_PluginVersionCheck.Builder();}
19 |
20 | @AutoValue.Builder
21 | public abstract static class Builder {
22 | public abstract Builder compatible(Boolean compatible);
23 |
24 | public abstract Builder xnatVersionDetected(String xnatVersionDetected);
25 |
26 | public abstract Builder xnatVersionRequired(String xnatVersionRequired);
27 |
28 | public abstract Builder message(String message);
29 |
30 | public abstract PluginVersionCheck build();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/orchestration/auto/OrchestrationProject.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.orchestration.auto;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 |
5 | import javax.annotation.Nullable;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | @JsonInclude
10 | public class OrchestrationProject {
11 | private List availableOrchestrations = new ArrayList<>();
12 | private Long selectedOrchestrationId;
13 |
14 | public OrchestrationProject() {}
15 |
16 | public OrchestrationProject(List availableOrchestrations, Long selectedOrchestrationId) {
17 | this.availableOrchestrations = availableOrchestrations;
18 | this.selectedOrchestrationId = selectedOrchestrationId;
19 | }
20 |
21 | public List getAvailableOrchestrations() {
22 | return availableOrchestrations;
23 | }
24 |
25 | public void setAvailableOrchestrations(List availableOrchestrations) {
26 | this.availableOrchestrations = availableOrchestrations;
27 | }
28 |
29 | @Nullable
30 | public Long getSelectedOrchestrationId() {
31 | return selectedOrchestrationId;
32 | }
33 |
34 | public void setSelectedOrchestrationId(Long selectedOrchestrationId) {
35 | this.selectedOrchestrationId = selectedOrchestrationId;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/command/entity/CommandVisibility.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.command.entity;
2 |
3 | import java.util.EnumSet;
4 | import java.util.List;
5 | import java.util.stream.Collectors;
6 | import com.fasterxml.jackson.annotation.JsonCreator;
7 | import com.fasterxml.jackson.annotation.JsonValue;
8 | import org.omg.CORBA.PRIVATE_MEMBER;
9 |
10 |
11 | public enum CommandVisibility {
12 |
13 | PUBLIC_CONTAINER("public"),
14 | PRIVATE_CONTAINER("private"),
15 | PROTECTED_CONTAINER("protected");
16 |
17 | private final String visibilityType;
18 |
19 | @JsonCreator
20 | CommandVisibility(final String visibilityType) {
21 | this.visibilityType = visibilityType;
22 | }
23 |
24 | @JsonValue
25 | public String getVisibilityType() {
26 | return visibilityType;
27 | }
28 |
29 | public static List visibilityTypes() {
30 | return EnumSet.allOf(CommandVisibility.class).stream().map(CommandVisibility::getVisibilityType).collect(Collectors.toList());
31 | }
32 |
33 | public static CommandVisibility withVisibilityType(final String visibilityType) {
34 | for (final CommandVisibility type : EnumSet.allOf(CommandVisibility.class)) {
35 | if (type.visibilityType.equals(visibilityType)) {
36 | return type;
37 | }
38 | }
39 | return null;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/config/ObjectMapperConfig.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.config;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.core.JsonParser;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import com.fasterxml.jackson.databind.SerializationFeature;
7 | import com.fasterxml.jackson.datatype.guava.GuavaModule;
8 | import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
9 | import org.springframework.context.annotation.Bean;
10 | import org.springframework.context.annotation.Configuration;
11 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
12 |
13 | @Configuration
14 | public class ObjectMapperConfig {
15 | @Bean
16 | public Jackson2ObjectMapperBuilder objectMapperBuilder() {
17 | return new Jackson2ObjectMapperBuilder()
18 | .serializationInclusion(JsonInclude.Include.NON_NULL)
19 | .failOnEmptyBeans(false)
20 | .featuresToEnable(JsonParser.Feature.ALLOW_SINGLE_QUOTES, JsonParser.Feature.ALLOW_YAML_COMMENTS)
21 | .featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS, SerializationFeature.WRITE_NULL_MAP_VALUES)
22 | .modulesToInstall(new Hibernate5Module(), new GuavaModule());
23 | }
24 |
25 | @Bean
26 | public ObjectMapper objectMapper() {
27 | return objectMapperBuilder().build();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/config/DockerServerEntityTestConfig.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.config;
2 |
3 | import org.mockito.Mockito;
4 | import org.nrg.containers.api.KubernetesClientFactoryImpl;
5 | import org.nrg.containers.daos.DockerServerEntityRepository;
6 | import org.nrg.containers.services.DockerServerEntityService;
7 | import org.nrg.containers.services.DockerServerService;
8 | import org.nrg.containers.services.impl.DockerServerServiceImpl;
9 | import org.nrg.containers.services.impl.HibernateDockerServerEntityService;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.context.annotation.Configuration;
12 | import org.springframework.context.annotation.Import;
13 |
14 | @Configuration
15 | @Import({HibernateConfig.class, ObjectMapperConfig.class})
16 | public class DockerServerEntityTestConfig {
17 | @Bean
18 | public DockerServerEntityService dockerServerEntityService() {
19 | return new HibernateDockerServerEntityService();
20 | }
21 |
22 | @Bean
23 | public DockerServerService dockerServerService(final DockerServerEntityService dockerServerEntityService) {
24 | return new DockerServerServiceImpl(dockerServerEntityService, Mockito.mock(KubernetesClientFactoryImpl.class));
25 | }
26 |
27 | @Bean
28 | public DockerServerEntityRepository dockerServerEntityRepository() {
29 | return new DockerServerEntityRepository();
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/xnat/turbine/modules/screens/AdminContainerService.java:
--------------------------------------------------------------------------------
1 | /*
2 | * org.nrg.xnat.turbine.modules.screens.AdminContainerService
3 | * XNAT http://www.xnat.org
4 | * Copyright (c) 2014, Washington University School of Medicine
5 | * All Rights Reserved
6 | *
7 | * Released under the Simplified BSD.
8 | *
9 | * Last modified 7/10/13 9:04 PM
10 | */
11 | package org.nrg.xnat.turbine.modules.screens;
12 |
13 | import org.apache.turbine.util.RunData;
14 | import org.apache.velocity.context.Context;
15 | import org.nrg.xdat.turbine.modules.screens.AdminScreen;
16 |
17 | public class AdminContainerService extends AdminScreen {
18 | @Override
19 | protected void doBuildTemplate(RunData data, Context context) throws Exception {
20 | /*
21 | ArcArchivespecification arcSpec = ArcSpecManager.GetInstance();
22 | if (arcSpec == null) {
23 | arcSpec = ArcSpecManager.initialize(TurbineUtils.getUser(data));
24 | }
25 | if (!ArcSpecManager.HasPersisted()) {
26 | context.put("initialize", true);
27 | } else {
28 | context.put("initialize", false);
29 | }
30 | context.put("arc", arcSpec);
31 | setDefaultTabs("siteInfo", "fileSystem", "registration", "notifications", "anonymization", "applet", "dicomReceiver");
32 | cacheTabs(context, "configuration");
33 | context.put("features", Features.getAllFeatures());
34 | */
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.appender.containers=org.apache.log4j.DailyRollingFileAppender
2 | log4j.appender.containers.File=${xnat.home}/logs/containers.log
3 | log4j.appender.containers.DatePattern='.'yyyy-MM-dd
4 | log4j.appender.containers.layout=org.apache.log4j.PatternLayout
5 | log4j.appender.containers.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
6 |
7 | log4j.appender.spotify=org.apache.log4j.DailyRollingFileAppender
8 | log4j.appender.spotify.File=${xnat.home}/logs/containers-spotify.log
9 | log4j.appender.spotify.DatePattern='.'yyyy-MM-dd
10 | log4j.appender.spotify.layout=org.apache.log4j.PatternLayout
11 | log4j.appender.spotify.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
12 |
13 | log4j.appender.services=org.apache.log4j.DailyRollingFileAppender
14 | log4j.appender.services.File=${xnat.home}/logs/containers-services-commandresolution.log
15 | log4j.appender.services.DatePattern='.'yyyy-MM-dd
16 | log4j.appender.services.layout=org.apache.log4j.PatternLayout
17 | log4j.appender.services.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
18 |
19 | log4j.category.org.nrg.containers=DEBUG, containers
20 | log4j.additivity.org.nrg.containers=false
21 |
22 | log4j.category.org.mandas=INFO, spotify
23 | log4j.additivity.org.mandas=false
24 |
25 | log4j.category.org.nrg.containers.services.impl.CommandResolutionServiceImpl=INFO, services
26 | log4j.additivity.org.nrg.containers.services.impl.CommandResolutionServiceImpl=false
27 |
28 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/events/model/SessionMergeOrArchiveEvent.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.events.model;
2 |
3 | import com.google.auto.value.AutoValue;
4 | import org.nrg.framework.event.EventI;
5 | import org.nrg.xdat.om.XnatImagesessiondata;
6 | import org.nrg.xft.security.UserI;
7 |
8 | @AutoValue
9 | public abstract class SessionMergeOrArchiveEvent implements EventI {
10 | private static final long serialVersionUID = 5840070113252303212L;
11 |
12 | public static final String QUEUE = "sessionMergeOrArchiveEventQueue";
13 |
14 | public abstract XnatImagesessiondata session();
15 | public abstract String project();
16 | public abstract UserI user();
17 | public abstract String eventId();
18 |
19 | public static SessionMergeOrArchiveEvent create(final XnatImagesessiondata session,
20 | final String project,
21 | final UserI user,
22 | final String eventId) {
23 | return new AutoValue_SessionMergeOrArchiveEvent(session, project, user, eventId);
24 | }
25 |
26 | public static SessionMergeOrArchiveEvent create(final XnatImagesessiondata session,
27 | final UserI userI,
28 | final String eventId) {
29 | return create(session, session.getProject(), userI, eventId);
30 | }
31 | }
--------------------------------------------------------------------------------
/src/test/resources/commandLaunchTest/testCommandWithGenericResourcesGpu/debug_command_with_gpu.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "debug-gpu",
3 | "description": "Used to ensure we can request GPU resources. Will not run.",
4 | "label": "Debug - GPU",
5 | "version": "1.0",
6 | "schema-version": "1.0",
7 | "type": "docker",
8 | "command-line": "#COMMAND#",
9 | "image": "xnat/debug-command:latest",
10 | "override-entrypoint": true,
11 | "generic-resources": {
12 | "gpu": "1"
13 | },
14 | "inputs": [
15 | {
16 | "name": "command",
17 | "description": "The command to run",
18 | "type": "string",
19 | "required": true,
20 | "default-value": "echo hello world",
21 | "replacement-key": "#COMMAND#"
22 | }
23 | ],
24 | "xnat": [
25 | {
26 | "name": "debug-gpu",
27 | "description": "Run the debug gpu container with a project",
28 | "label": "Debug - GPU",
29 | "contexts": ["xnat:projectData"],
30 | "external-inputs": [
31 | {
32 | "name": "project",
33 | "description": "Input project",
34 | "type": "Project",
35 | "required": true,
36 | "load-children": false
37 | }
38 | ],
39 | "derived-inputs": [],
40 | "output-handlers": []
41 | }
42 | ]
43 | }
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/daos/OrchestrationEntityDao.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.daos;
2 |
3 |
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.hibernate.Criteria;
6 | import org.hibernate.criterion.Order;
7 | import org.hibernate.criterion.Restrictions;
8 | import org.nrg.containers.model.orchestration.entity.OrchestrationEntity;
9 | import org.nrg.framework.generics.GenericUtils;
10 | import org.nrg.framework.orm.hibernate.AbstractHibernateDAO;
11 | import org.springframework.stereotype.Repository;
12 |
13 | import java.util.List;
14 |
15 | @Slf4j
16 | @Repository
17 | public class OrchestrationEntityDao extends AbstractHibernateDAO {
18 | public List findAllWithDisabledAndOrder() {
19 | final Criteria criteria = getSession().createCriteria(getParameterizedType());
20 | criteria.addOrder(Order.asc("id"));
21 | return GenericUtils.convertToTypedList(criteria.list(), getParameterizedType());
22 | }
23 |
24 | public List findEnabledUsingWrapper(long wrapperId) {
25 | final Criteria criteria = getSession().createCriteria(getParameterizedType());
26 | criteria.add(Restrictions.eq("enabled", true));
27 | final Criteria wrapperCriteria = criteria.createCriteria("wrapperList");
28 | final Criteria commandCriteria = wrapperCriteria.createCriteria("commandWrapperEntity");
29 | commandCriteria.add(Restrictions.eq("id", wrapperId));
30 | return GenericUtils.convertToTypedList(criteria.list(), getParameterizedType());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/CommandEntityService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import org.nrg.containers.model.command.entity.CommandEntity;
4 | import org.nrg.containers.model.command.entity.CommandWrapperEntity;
5 | import org.nrg.framework.exceptions.NotFoundException;
6 | import org.nrg.framework.exceptions.NrgRuntimeException;
7 | import org.nrg.framework.orm.hibernate.BaseHibernateService;
8 |
9 | import java.util.List;
10 | import java.util.Map;
11 |
12 | public interface CommandEntityService extends BaseHibernateService {
13 | List findByProperties(Map properties);
14 |
15 | CommandWrapperEntity addWrapper(CommandEntity commandEntity, CommandWrapperEntity wrapperToAdd);
16 | CommandWrapperEntity retrieveWrapper(long wrapperId);
17 | CommandWrapperEntity retrieveWrapper(long commandId, String wrapperName);
18 | CommandWrapperEntity getWrapper(long wrapperId) throws NotFoundException;
19 | CommandWrapperEntity getWrapper(long commandId, String wrapperName) throws NotFoundException;
20 | CommandWrapperEntity update(CommandWrapperEntity updates) throws NotFoundException;
21 | void deleteWrapper(long wrapperId);
22 |
23 | long getWrapperId(long commandId, String wrapperName) throws NotFoundException;
24 |
25 | CommandEntity getCommandByWrapperId(long wrapperId) throws NotFoundException;
26 | List getByImage(String image);
27 | void deleteByImage(String image);
28 |
29 | void throwExceptionIfCommandExists(CommandEntity commandEntity) throws NrgRuntimeException;
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/OrchestrationService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import org.nrg.containers.model.command.auto.Command;
4 | import org.nrg.containers.model.orchestration.auto.Orchestration;
5 | import org.nrg.containers.model.orchestration.auto.OrchestrationProject;
6 | import org.nrg.framework.exceptions.NotFoundException;
7 | import org.nrg.xft.security.UserI;
8 |
9 | import javax.annotation.Nullable;
10 | import java.util.List;
11 |
12 | public interface OrchestrationService {
13 | Orchestration createOrUpdate(Orchestration orchestration) throws NotFoundException;
14 |
15 | @Nullable
16 | Orchestration retrieve(final long orchestrationId) throws NotFoundException;
17 |
18 | @Nullable
19 | Orchestration findWhereWrapperIsFirst(Orchestration.OrchestrationIdentifier oi);
20 |
21 | @Nullable
22 | Command.CommandWrapper findNextWrapper(long orchestrationId, int currentStepIdx, long currentWrapperId)
23 | throws NotFoundException;
24 |
25 | void setEnabled(long id, boolean enabled, UserI user) throws NotFoundException, ContainerConfigService.CommandConfigurationException;
26 |
27 | List getAllPojos();
28 |
29 | OrchestrationProject getAvailableForProject(String project);
30 |
31 | @Nullable
32 | Orchestration findForProject(String project);
33 |
34 | void setProjectOrchestration(String project, long orchestrationId, UserI user) throws NotFoundException, ContainerConfigService.CommandConfigurationException;
35 |
36 | void removeProjectOrchestration(String project);
37 |
38 | void delete(long id);
39 | }
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/secrets/Secret.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.secrets;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | import javax.annotation.Nonnull;
7 | import java.util.Objects;
8 |
9 | public class Secret {
10 | @Nonnull @JsonProperty("source") private final SecretSource source;
11 | @Nonnull @JsonProperty("destination") private final SecretDestination destination;
12 |
13 | @JsonCreator
14 | public Secret(
15 | @Nonnull @JsonProperty("source") final SecretSource source,
16 | @Nonnull @JsonProperty("destination") final SecretDestination destination
17 | ) {
18 | this.source = source;
19 | this.destination = destination;
20 | }
21 |
22 | public SecretSource source() {
23 | return source;
24 | }
25 |
26 | public SecretDestination destination() {
27 | return destination;
28 | }
29 |
30 | @Override
31 | public boolean equals(final Object o) {
32 | if (this == o) return true;
33 | if (o == null || getClass() != o.getClass()) return false;
34 | final Secret that = (Secret) o;
35 | return Objects.equals(source, that.source) &&
36 | Objects.equals(destination, that.destination);
37 | }
38 |
39 | @Override
40 | public int hashCode() {
41 | return Objects.hash(source, destination);
42 | }
43 |
44 | @Override
45 | public String toString() {
46 | return "Secret{" +
47 | "source=" + source +
48 | ", destination=" + destination +
49 | "}";
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/model/xnat/InnerTestPojo.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.xnat;
2 |
3 | import com.google.common.base.MoreObjects;
4 |
5 | import java.util.Objects;
6 |
7 | public class InnerTestPojo {
8 | private String innerKey1;
9 | private String innerKey2;
10 |
11 | public InnerTestPojo() {}
12 |
13 | public InnerTestPojo(final String innerKey1, final String innerKey2) {
14 | this.innerKey1 = innerKey1;
15 | this.innerKey2 = innerKey2;
16 | }
17 |
18 | public String getInnerKey1() {
19 | return innerKey1;
20 | }
21 |
22 | public void setInnerKey1(final String innerKey1) {
23 | this.innerKey1 = innerKey1;
24 | }
25 |
26 |
27 | public String getInnerKey2() {
28 | return innerKey2;
29 | }
30 |
31 | public void setInnerKey2(final String innerKey2) {
32 | this.innerKey2 = innerKey2;
33 | }
34 |
35 | @Override
36 | public boolean equals(final Object o) {
37 | if (this == o) return true;
38 | if (o == null || getClass() != o.getClass()) return false;
39 | final InnerTestPojo that = (InnerTestPojo) o;
40 | return Objects.equals(this.innerKey1, that.innerKey1) &&
41 | Objects.equals(this.innerKey2, that.innerKey2);
42 | }
43 |
44 | @Override
45 | public int hashCode() {
46 | return Objects.hash(innerKey1, innerKey2);
47 | }
48 |
49 | @Override
50 | public String toString() {
51 | return MoreObjects.toStringHelper(this)
52 | .add("innerKey1", innerKey1)
53 | .add("innerKey2", innerKey2)
54 | .toString();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/jms/tasks/QueueManager.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.jms.tasks;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.nrg.containers.services.ContainerService;
5 | import org.nrg.xdat.security.helpers.Users;
6 | import org.nrg.xft.schema.XFTManager;
7 | import org.nrg.xft.security.UserI;
8 | import org.nrg.xnat.services.XnatAppInfo;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.stereotype.Component;
11 |
12 | @Slf4j
13 | @Component
14 | public class QueueManager implements Runnable {
15 |
16 | private final ContainerService containerService;
17 | private final XnatAppInfo xnatAppInfo;
18 | private boolean haveLoggedXftInitFailure = false;
19 |
20 | @Autowired
21 | public QueueManager(final ContainerService containerService, final XnatAppInfo appInfo) {
22 | this.containerService = containerService;
23 | this.xnatAppInfo = appInfo;
24 | }
25 |
26 | @Override
27 | public void run() {
28 | if (!xnatAppInfo.isPrimaryNode()) {
29 | return;
30 | }
31 |
32 | if (!XFTManager.isInitialized()) {
33 | if (!haveLoggedXftInitFailure) {
34 | log.info("XFT is not initialized, skipping queue manager task");
35 | haveLoggedXftInitFailure = true;
36 | }
37 | return;
38 | }
39 |
40 | log.trace("Queue manager task running");
41 | UserI user = Users.getAdminUser();
42 | containerService.checkQueuedContainerJobs(user);
43 | containerService.checkWaitingContainerJobs(user);
44 | log.trace("Queue manager task done");
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/initialization/tasks/CheckContainerServiceVersion.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.initialization.tasks;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.nrg.containers.model.configuration.PluginVersionCheck;
5 | import org.nrg.containers.services.ContainerService;
6 | import org.nrg.xnat.initialization.tasks.AbstractInitializingTask;
7 | import org.nrg.xnat.initialization.tasks.InitializingTaskException;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.stereotype.Component;
10 |
11 | @Slf4j
12 | @Component
13 | public class CheckContainerServiceVersion extends AbstractInitializingTask {
14 | private final ContainerService containerService;
15 |
16 | @Autowired
17 | public CheckContainerServiceVersion(final ContainerService containerService) {
18 | this.containerService = containerService;
19 | }
20 |
21 | @Override
22 | public String getTaskName() {
23 | return "Check XNAT version compatibility with this plugin.";
24 | }
25 |
26 | @Override
27 | protected void callImpl() throws InitializingTaskException {
28 |
29 | log.debug("Checking Container Service plugin / XNAT compatibility.");
30 | PluginVersionCheck versionCheck = containerService.checkXnatVersion();
31 | if(versionCheck != null && versionCheck.compatible() == false){
32 | log.error(versionCheck.message());
33 | log.error("XNAT version: " + versionCheck.xnatVersionDetected());
34 | } else if(log.isDebugEnabled() && versionCheck != null){
35 | if(versionCheck.compatible()) log.debug("Version compatibility check: Passed");
36 | }
37 |
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/kubernetes/KubernetesPodPhase.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.kubernetes;
2 |
3 | import org.apache.commons.lang.WordUtils;
4 |
5 | import javax.annotation.Nullable;
6 | import java.util.Collections;
7 | import java.util.Map;
8 | import java.util.function.Function;
9 | import java.util.stream.Collectors;
10 | import java.util.stream.Stream;
11 |
12 | /**
13 | * See kubernetes docs
14 | * Pod Lifecycle: Pod Phase
15 | */
16 | public enum KubernetesPodPhase {
17 | PENDING,
18 | RUNNING,
19 | SUCCEEDED,
20 | FAILED,
21 | UNKNOWN;
22 |
23 | private static final Map ENUM_MAP;
24 | static {
25 | Map map = Stream.of(KubernetesPodPhase.values())
26 | .collect(Collectors.toMap(KubernetesPodPhase::toString, Function.identity()));
27 | ENUM_MAP = Collections.unmodifiableMap(map);
28 | }
29 |
30 | @Nullable
31 | public static KubernetesPodPhase fromString(String phase) {
32 | return ENUM_MAP.get(phase);
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return WordUtils.capitalizeFully(this.name());
38 | }
39 |
40 | public static boolean isSuccessful(String phase) {
41 | final KubernetesPodPhase enumPhase = KubernetesPodPhase.fromString(phase);
42 | return enumPhase != null && enumPhase.isSuccessful();
43 | }
44 |
45 | public boolean isSuccessful() {
46 | return SUCCEEDED == this;
47 | }
48 |
49 | public boolean isTerminal() {
50 | return SUCCEEDED == this || FAILED == this;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/resources/scripts/xnat/plugin/containerService/containerConfig.css:
--------------------------------------------------------------------------------
1 | .imageContainer {
2 | margin: 0 0 2.5em;
3 | }
4 | .imageContainer:last-of-type {
5 | margin: 0;
6 | }
7 | .imageContainer h3 {
8 | border: none;
9 | /*margin-bottom: 10px;*/
10 | overflow: auto;
11 | padding: 20px 0 0 2em;
12 | position: relative;
13 |
14 | }
15 | .imageContainer h3:before {
16 | font-family: "FontAwesome";
17 | content: "\f0a0";
18 | font-size: 1.5em;
19 | color: #808080;
20 | position: absolute;
21 | top: .9em; left: 1px;
22 | }
23 | .commandConfigInputRow {
24 | padding: 5px 0;
25 | }
26 | .commandConfigInputMain {
27 | display: inline-block;
28 | width: 70%;
29 | }
30 | .commandConfigInputToggle {
31 | display: inline-block;
32 | width: 30%;
33 | }
34 |
35 | .imageActionList {
36 | margin: .25em 0;
37 | padding: 0 0 0 2em;
38 | }
39 | .imageActionList li + li {
40 | margin-top: .5em;
41 | }
42 |
43 | /* enhancement to core XNAT switchbox style. Will be committed back into core code. */
44 | label.switchbox.disabled {
45 | cursor: not-allowed;
46 | }
47 | label.switchbox.disabled > input + .switchbox-outer {
48 | background: #f3f3f3;
49 | border-color: #d3d3d3;
50 | box-shadow: none;
51 | }
52 |
53 | /* modal divider */
54 |
55 | p.divider,
56 | div.xnat-dialog > .body p.divider {
57 | border-top: 1px solid #ccc;
58 | margin-top: 1em;
59 | margin-bottom: 2em;
60 | padding-top: 1em;
61 | }
62 |
63 | div.swarm-constraint {
64 | border: 1px solid #ccc;
65 | padding: 5px;
66 | margin: 5px;
67 | border-radius: 10px;
68 | }
69 | div.swarm-constraint a.close{
70 | float: right;
71 | margin: 0 3px 0 0;
72 | }
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/utils/ShellSplitter.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.utils;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * Intended to split a string into tokens the way a shell would.
8 | * Taken from https://gist.github.com/raymyers/8077031
9 | *
10 | * For use cases, see tests in org.nrg.containers.utils.ShellSplitterTest.
11 | */
12 | public class ShellSplitter {
13 | public static List shellSplit(CharSequence string) {
14 | final List tokens = new ArrayList<>();
15 |
16 | boolean escaping = false;
17 | char quoteChar = ' ';
18 | boolean quoting = false;
19 | StringBuilder current = new StringBuilder() ;
20 | for (int i = 0; i 0) {
34 | tokens.add(current.toString());
35 | current = new StringBuilder();
36 | }
37 | } else {
38 | current.append(c);
39 | }
40 | }
41 | if (current.length() > 0) {
42 | tokens.add(current.toString());
43 | }
44 | return tokens;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/testSessionScanMult/session.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "session1",
3 | "type": "Session",
4 | "label": "session1",
5 | "uri": "/experiments/session1",
6 | "scans": [
7 | {
8 | "id": "scan1",
9 | "type": "Scan",
10 | "scan-type": "SCANTYPE",
11 | "uri": "/experiments/session1/scans/scan1",
12 | "frames": "12",
13 | "series-description": "great!",
14 | "modality": "super!",
15 | "quality": "the best!",
16 | "note": "I love it - me",
17 | "resources": [
18 | {
19 | "id": 0,
20 | "type": "Resource",
21 | "label": "DICOM",
22 | "directory": "this will be overwritten at runtime",
23 | "uri": "/experiments/session1/scans/scan1/resources/0"
24 | }
25 | ]
26 | },
27 | {
28 | "id": "scan2",
29 | "type": "Scan",
30 | "scan-type": "OTHER_SCANTYPE",
31 | "uri": "/experiments/session1/scans/scan2",
32 | "frames": "12",
33 | "series-description": "great!",
34 | "modality": "super!",
35 | "quality": "the best!",
36 | "note": "I love it - me",
37 | "resources": [
38 | {
39 | "id": 0,
40 | "type": "Resource",
41 | "label": "DICOM",
42 | "directory": "this will be overwritten at runtime",
43 | "uri": "/experiments/session1/scans/scan2/resources/0"
44 | }
45 | ]
46 | }
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/secrets/ContainerPropertiesWithSecretValues.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.secrets;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.nrg.containers.model.container.auto.Container;
5 |
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | @Slf4j
11 | public class ContainerPropertiesWithSecretValues {
12 | // TODO add more secret properties as they are implemented
13 | private final Map environmentVariables;
14 |
15 | private ContainerPropertiesWithSecretValues(final Map environmentVariables) {
16 | this.environmentVariables = environmentVariables;
17 | }
18 |
19 | public static ContainerPropertiesWithSecretValues prepareSecretsForLaunch(final Container container) {
20 |
21 | // Secrets
22 | final Map, List> secretsByType = container.secretsByType();
23 |
24 | final Map environmentVariables = new HashMap<>(container.environmentVariables());
25 | environmentVariables.putAll(SecretUtils.extractEnvironmentVariableSecrets(secretsByType));
26 |
27 | // TODO add handling for more secret types as they are implemented
28 |
29 | secretsByType.forEach((clazz, resolvedSecrets) ->
30 | log.warn("Have not implemented handling for resolved secret type {}. Ignoring {} secret values.",
31 | clazz.getSimpleName(), resolvedSecrets.size()
32 | )
33 | );
34 |
35 | return new ContainerPropertiesWithSecretValues(environmentVariables);
36 | }
37 |
38 | public Map environmentVariables() {
39 | return environmentVariables;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/OrchestrationEntityService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import org.nrg.containers.model.command.auto.Command;
4 | import org.nrg.containers.model.command.entity.CommandWrapperEntity;
5 | import org.nrg.containers.model.orchestration.auto.Orchestration;
6 | import org.nrg.containers.model.orchestration.auto.OrchestrationProject;
7 | import org.nrg.containers.model.orchestration.entity.OrchestrationEntity;
8 | import org.nrg.framework.exceptions.NotFoundException;
9 | import org.nrg.framework.orm.hibernate.BaseHibernateService;
10 |
11 | import javax.annotation.Nullable;
12 | import java.util.List;
13 |
14 | public interface OrchestrationEntityService extends BaseHibernateService {
15 |
16 | @Nullable
17 | Orchestration findWhereWrapperIsFirst(String project, long wrapperId);
18 |
19 | Orchestration createOrUpdate(Orchestration orchestration, List wrapperList)
20 | throws NotFoundException;
21 |
22 | @Nullable
23 | Command.CommandWrapper findNextWrapper(long orchestrationId, int currentStepIdx, long currentWrapperId)
24 | throws NotFoundException;
25 |
26 | List setEnabled(long id, boolean enabled) throws NotFoundException;
27 |
28 | void setEnabled(OrchestrationEntity oe, boolean enabled);
29 |
30 | List getAllPojos();
31 |
32 | OrchestrationProject getAvailableForProject(String project);
33 |
34 | List setProjectOrchestration(String project, long orchestrationId) throws NotFoundException;
35 |
36 | void removeProjectOrchestration(String project);
37 |
38 | void checkAndDisable(long wrapperId);
39 |
40 | @Nullable
41 | Orchestration findForProject(String project);
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/secrets/SecretUtils.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.secrets;
2 |
3 | import java.util.List;
4 | import java.util.Map;
5 | import java.util.stream.Collectors;
6 | import java.util.stream.Stream;
7 |
8 | public class SecretUtils {
9 |
10 | /**
11 | * Remove environment variable resolved secrets from resolved secrets map, return them as a name: value map
12 | * @param resolvedSecretsByClass Map from resolved secret classes to a list of secrets of that type
13 | * @return Map of environment variable secrets: name to value
14 | */
15 | public static Map extractEnvironmentVariableSecrets(final Map, List> resolvedSecretsByClass) {
16 | return extractResolvedSecretType(EnvironmentVariableResolvedSecret.class, resolvedSecretsByClass)
17 | .collect(Collectors.toMap(EnvironmentVariableResolvedSecret::envName, EnvironmentVariableResolvedSecret::envValue));
18 | }
19 |
20 | /**
21 | * Remove and return a particular type of ResolvedSecrets from the resolved secrets by type map
22 | * @param type A subclass of {@link ResolvedSecret}
23 | * @param resolvedSecretsByClass Map from resolved secret classes to a list of secrets of that type
24 | * @return A stream of the ResolvedSecrets of the specified type
25 | */
26 | public static Stream extractResolvedSecretType(
27 | Class type, final Map, List> resolvedSecretsByClass
28 | ) {
29 | final List secretTs = resolvedSecretsByClass.remove(type);
30 | return secretTs == null ? Stream.empty() : secretTs.stream().map(type::cast);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/docs/launching-containers-for-users.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | You can launch containers through the ordinary XNAT UI.
4 |
5 | First, a command must be added to your XNAT, and its wrapper must be enabled in your project. Then when you are on a page where a command and wrapper are available, a Run Containers menu will appear in the Actions box.
6 |
7 | SCREENSHOT Page with Run containers menu
8 |
9 | Inside the Run Containers menu are all the different command wrappers that can be used to launch a container with the object you are viewing.
10 |
11 | SCREENSHOT Open Run containers menu, show some wrappers inside
12 |
13 | Click on one of those command wrappers, and you will be presented with a Launch Container dialog. Here you can provide or customize the values of various arguments that will be used to launch the container.
14 |
15 | SCREENSHOT Launch container dialog
16 |
17 | After you submit, the container will be launched.
18 |
19 | ## A note on scan-level launching
20 |
21 | Some command wrappers are configured to launch on a particular scan. There is no User Interface in any existing XNAT version to launch containers from these command wrappers, because there are no buttons on the scan listings that can be customized in this way.
22 |
23 | However, there is an XNAT plugin that can provide this function: the [selectable table plugin](https://bitbucket.org/xnatdev/selectable-table-plugin). You can download this plugin jar (from the [downloads page](https://bitbucket.org/xnatdev/selectable-table-plugin/downloads/) in the repo) and install it into your XNAT. It will customize the scan tables and enable you to launch any command wrappers that are defined to launch on scans. You can even launch in bulk on multiple scans at once.
24 |
25 | SCREENSHOT Scan table with selectable table installed
26 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/api/KubernetesClientFactoryImpl.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.api;
2 |
3 | import org.nrg.containers.exceptions.NoContainerServerException;
4 | import org.nrg.framework.services.NrgEventServiceI;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.io.IOException;
8 | import java.util.concurrent.ExecutorService;
9 |
10 | @Service
11 | public class KubernetesClientFactoryImpl implements KubernetesClientFactory {
12 | private final ExecutorService executorService;
13 | private final NrgEventServiceI eventService;
14 |
15 | private volatile KubernetesClientImpl kubernetesClient = null;
16 |
17 | public KubernetesClientFactoryImpl(final ExecutorService executorService,
18 | final NrgEventServiceI eventService) {
19 | this.executorService = executorService;
20 | this.eventService = eventService;
21 | }
22 |
23 | @Override
24 | public KubernetesClient getKubernetesClient() throws NoContainerServerException {
25 | if (kubernetesClient == null) {
26 | synchronized (this) {
27 | if (kubernetesClient == null) {
28 | try {
29 | kubernetesClient = new KubernetesClientImpl(executorService, eventService);
30 | } catch (IOException e) {
31 | throw new NoContainerServerException("Could not create kubernetes client", e);
32 | }
33 | }
34 | }
35 | }
36 | return kubernetesClient;
37 | }
38 |
39 | @Override
40 | public synchronized void shutdown() {
41 | if (kubernetesClient != null) {
42 | kubernetesClient.stop();
43 | kubernetesClient = null;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/config/CommandTestConfig.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.config;
2 |
3 | import org.mockito.Mockito;
4 | import org.nrg.containers.api.ContainerControlApi;
5 | import org.nrg.containers.services.ContainerEntityService;
6 | import org.nrg.framework.services.SerializerService;
7 | import org.nrg.xdat.preferences.SiteConfigPreferences;
8 | import org.nrg.xdat.security.services.PermissionsServiceI;
9 | import org.nrg.xdat.services.AliasTokenService;
10 | import org.nrg.xnat.services.archive.CatalogService;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Configuration;
13 | import org.springframework.context.annotation.Import;
14 |
15 | @Configuration
16 | @Import({CommandConfig.class, HibernateConfig.class})
17 | public class CommandTestConfig {
18 | @Bean
19 | public ContainerControlApi controlApi() {
20 | return Mockito.mock(ContainerControlApi.class);
21 | }
22 |
23 | @Bean
24 | public AliasTokenService aliasTokenService() {
25 | return Mockito.mock(AliasTokenService.class);
26 | }
27 |
28 | @Bean
29 | public SiteConfigPreferences siteConfigPreferences() {
30 | return Mockito.mock(SiteConfigPreferences.class);
31 | }
32 |
33 | @Bean
34 | public SerializerService serializerService() {
35 | return Mockito.mock(SerializerService.class);
36 | }
37 |
38 | @Bean
39 | public ContainerEntityService mockContainerEntityService() {
40 | return Mockito.mock(ContainerEntityService.class);
41 | }
42 |
43 | @Bean
44 | public PermissionsServiceI permissionsService() {
45 | return Mockito.mock(PermissionsServiceI.class);
46 | }
47 |
48 | @Bean
49 | public CatalogService catalogService() {
50 | return Mockito.mock(CatalogService.class);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XNAT Container Service
2 |
3 | [XNAT](http://www.xnat.org/) plugin for controlling containers (primarily [Docker](https://www.docker.com/) containers).
4 |
5 | To use it, you will need an XNAT running 1.8.0+. Get the `containers--fat` jar (through one of the methods below) and put it into your `{xnat.home}/plugins` directory. Restart tomcat and you are ready to run containers. See the [guide to getting started](https://wiki.xnat.org/display/CS/Getting+Started).
6 |
7 | This document is cross-posted on the [XNAT wiki](https://wiki.xnat.org/display/CS/Introduction) and the README in the [source repository](https://bitbucket.org/xnatdev/container-service).
8 |
9 | ## Getting the jar
10 | ### Download
11 | Releases are posted to the repository's [Downloads page](https://bitbucket.org/xnatdev/container-service/downloads/) on Bitbucket. Download the version you want (probably the latest release) and [deploy it to XNAT](#deploy-to-XNAT).
12 |
13 | ### Build the jar
14 | If you clone the source repository, you can build an XNAT plugin jar by running
15 |
16 | ```
17 | [container-service] $ ./gradlew fatJar
18 | ```
19 |
20 | The jar will be created as `build/libs/containers-${VERSION}-fat.jar`
21 |
22 | ## Deploy to XNAT
23 | One you [have a jar](#getting-the-jar), copy it to the `${xnat.home}/plugins` directory, and restart tomcat.
24 |
25 | Where is `${xnat.home}`? If you are using a VM generated by the [XNAT Vagrant project](https://bitbucket.org/xnatdev/xnat_vagrant), `xnat.home` is in the `~/${PROJECT}` directory (and the default value for `${PROJECT}` is `xnat`). If you aren't using the Vagrant project, or even if you are and you're still confused, then `${xnat.home}/logs` is where XNAT writes its logs; you'll want the `plugins` directory which should be right next to the `logs` directory.
26 |
27 | ## Contributing
28 | See [CONTRIBUTING.md](CONTRIBUTING.md).
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/ContainerEntityService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import org.nrg.containers.events.model.ContainerEvent;
4 | import org.nrg.containers.model.container.entity.ContainerEntity;
5 | import org.nrg.containers.model.container.entity.ContainerEntityHistory;
6 | import org.nrg.framework.exceptions.NotFoundException;
7 | import org.nrg.framework.orm.hibernate.BaseHibernateService;
8 | import org.nrg.xft.security.UserI;
9 |
10 | import java.util.List;
11 |
12 | public interface ContainerEntityService extends BaseHibernateService {
13 | ContainerEntity save(final ContainerEntity toCreate,
14 | final UserI userI);
15 |
16 | ContainerEntity retrieve(final String containerId);
17 | ContainerEntity get(final String containerId) throws NotFoundException;
18 | void delete(final String containerId);
19 |
20 | List getAll(Boolean nonfinalized, String project);
21 | List getAll(Boolean nonfinalized);
22 |
23 |
24 | List retrieveServices();
25 | List retrieveNonfinalizedServices();
26 | List retrieveContainersInFinalizingState();
27 | List retrieveServicesInWaitingState();
28 |
29 | int howManyContainersAreBeingFinalized();
30 |
31 | List retrieveSetupContainersForParent(long parentId);
32 | List retrieveWrapupContainersForParent(long parentId);
33 |
34 | ContainerEntity addContainerEventToHistory(final ContainerEvent containerEvent, final UserI userI);
35 | ContainerEntityHistory addContainerHistoryItem(final ContainerEntity containerEntity,
36 | final ContainerEntityHistory history, final UserI userI);
37 |
38 | int howManyContainersAreWaiting();
39 | }
40 |
--------------------------------------------------------------------------------
/scripts/set_up_kubernetes.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | set -o pipefail
4 |
5 | _usage="
6 | Usage: $(basename $0) KUBECONFIG_OUT [NAMESPACE]
7 |
8 | Configure Kubernetes cluster namespace and service account for Container Service
9 |
10 | The following cluster configuration will be performed:
11 | * Create a namespace. You can pass an optional input to the script which will be used for the
12 | name of the Namespace, or use the default \"container-service\".
13 | * Create a ServiceAccount within the Namespace, named \"\${namespace}-account\".
14 | The Container Service will use this ServiceAccount to connect to Kubernetes.
15 | * Create Roles for the API permissions the Container Service needs
16 | * Assign those Roles to the ServiceAccount through RoleBindings.
17 |
18 | The credentials for the ServiceAccount will be written to a kubeconfig file at the path you
19 | specify.
20 |
21 | Requirements:
22 | * \`base64' must be installed
23 | * \`kubectl' must be installed
24 | * When \`kubectl' is invoked, the account must have create permissions on
25 | Namespaces, ServiceAccounts, Roles, ClusterRoles, and RoleBindings.
26 | An admin account, in short.
27 | "
28 |
29 | # Input
30 | kubeconfig=${1:?$_usage}
31 | namespace=${2:-"container-service"}
32 |
33 | # Help
34 | if [ $kubeconfig = "-h" -o $kubeconfig = "--help" ]; then
35 | echo $_usage
36 | exit 0
37 | fi
38 |
39 | # Find directory where this script is running so we can locate partner scripts.
40 | # Taken from https://stackoverflow.com/a/53122736
41 | __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
42 |
43 | # Apply cluster configuration
44 | echo "Configuring cluster"
45 | bash ${__dir}/configure_cluster_namespace_roles.sh "$namespace"
46 |
47 | # Create kubeconfig
48 | echo "Writing values to kubeconfig ${kubeconfig}"
49 | bash ${__dir}/create_kubeconfig.sh "$kubeconfig" "$namespace"
50 |
51 | echo "Done"
52 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/secrets/EnvironmentVariableSecretDestination.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.secrets;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonIgnore;
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 |
7 | import java.util.Collections;
8 | import java.util.Map;
9 | import java.util.Objects;
10 |
11 | public class EnvironmentVariableSecretDestination implements SecretDestination {
12 | @JsonIgnore public static final String JSON_TYPE_NAME = "environment-variable";
13 |
14 | @JsonProperty("identifier") private final String envName;
15 |
16 | @JsonCreator
17 | public EnvironmentVariableSecretDestination(@JsonProperty("identifier") final String envName) {
18 | this.envName = envName;
19 | }
20 |
21 | public String envName() {
22 | return envName;
23 | }
24 |
25 | @Override
26 | @JsonIgnore
27 | public String type() {
28 | return JSON_TYPE_NAME;
29 | }
30 |
31 | @Override
32 | public String identifier() {
33 | return envName;
34 | }
35 |
36 | @Override
37 | public Map otherProperties() {
38 | return Collections.emptyMap();
39 | }
40 |
41 | @Override
42 | public boolean equals(final Object o) {
43 | if (this == o) return true;
44 | if (o == null || getClass() != o.getClass()) return false;
45 | final EnvironmentVariableSecretDestination that = (EnvironmentVariableSecretDestination) o;
46 | return Objects.equals(envName, that.envName);
47 | }
48 |
49 | @Override
50 | public int hashCode() {
51 | return Objects.hash(envName);
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return "EnvironmentVariable{" +
57 | "name=\"" + envName + "\"" +
58 | "}";
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/resources/scripts/xnat/plugin/containerService/commandUI.css:
--------------------------------------------------------------------------------
1 |
2 | .panel .standard-settings {
3 | margin-bottom: 2em;
4 | }
5 | .panel .child-input {
6 | padding-bottom: 1em;
7 | }
8 | .panel .advanced-settings-container {
9 | }
10 | .panel .advanced-settings-toggle {
11 | border-bottom: 1px solid #ccc;
12 | text-align: center;
13 | }
14 | .panel .advanced-settings-toggle:before {
15 | background-color: #f0f0f0;
16 | content: "Advanced Settings";
17 | color: #848484;
18 | cursor: pointer;
19 | display: inline-block;
20 | font-size: 10px;
21 | padding: 2px 6px;
22 | text-transform: uppercase;
23 | }
24 | .panel .advanced-settings-toggle.active:before {
25 | background-color: #e4efff;
26 | color: #4080cc;
27 | }
28 | .panel .advanced-settings {
29 | border: solid #ccc;
30 | border-width: 0 1px 1px;
31 | display: none;
32 | padding: 18px 0 6px;
33 | }
34 | .bulkRootList {
35 | height: 140px;
36 | overflow-y: auto;
37 | }
38 |
39 | .bulk-inputs {
40 | margin-bottom:15px;
41 | }
42 |
43 | #containerMenu > .bd {
44 | border-top: none;
45 | }
46 |
47 |
48 | #actionsMenu li.wrapped a {
49 | white-space: normal;
50 | width: 180px;
51 | }
52 |
53 | div.swarm-constraints .panel-element .element-label{
54 | width:50%;
55 | }
56 | div.swarm-constraints .panel-element .element-wrapper {
57 | width:50%;
58 | }
59 | span.swarm-constraints-heading {
60 | background-color: #f0f0f0;
61 | color: #848484;
62 | display: inline-block;
63 | font-size: 10px;
64 | padding: 2px 6px;
65 | text-transform: uppercase;
66 | }
67 |
68 | kbd {
69 | padding: .2rem .4rem;
70 | font-size: 87.5%;
71 | color: #fff;
72 | background-color: #212529;
73 | border-radius: .2rem;
74 | display: inline-block;
75 | margin-top: 3px;
76 | }
77 |
78 | .spinner {
79 | margin: 0 10px 10px;
80 | }
--------------------------------------------------------------------------------
/src/test/resources/wrapupCommand/command-with-wrapup-command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "command-with-wrapup-command",
3 | "description": "A command with a wrapup command",
4 | "type": "docker",
5 | "image": "xnat/test-wrapup-command-main:latest",
6 | "command-line": "main-command-script.sh",
7 | "inputs": [],
8 | "mounts": [
9 | {
10 | "name": "in",
11 | "path": "/input"
12 | },
13 | {
14 | "name": "out",
15 | "path": "/output",
16 | "writable": true
17 | }
18 | ],
19 | "outputs": [
20 | {
21 | "name": "output",
22 | "mount": "out"
23 | }
24 | ],
25 | "xnat": [
26 | {
27 | "name": "wrapper",
28 | "description": "Use a wrapup command on the output handler",
29 | "external-inputs": [
30 | {
31 | "name": "session",
32 | "description": "A session",
33 | "type": "Session",
34 | "required": true
35 | }
36 | ],
37 | "derived-inputs": [
38 | {
39 | "name": "resource",
40 | "description": "A fake resource, which will be mounted",
41 | "type": "Resource",
42 | "provides-files-for-command-mount": "in",
43 | "derived-from-wrapper-input": "session"
44 | }
45 | ],
46 | "output-handlers": [
47 | {
48 | "name": "output-handler",
49 | "accepts-command-output": "output",
50 | "via-wrapup-command": "xnat/test-wrapup-command:latest:wrapup-command",
51 | "as-a-child-of": "session",
52 | "label": "WRAPUP"
53 | }
54 | ]
55 | }
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/resources/commandLaunchTest/project-mount-command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "find",
3 | "description": "find all the files and directories in the input mount.",
4 | "type": "docker",
5 | "version": "1.0",
6 | "schema-version": "1.0",
7 | "image": "busybox:latest",
8 | "command-line": "find /input -type f > /output/files.txt; find /input -type d > /output/dirs.txt",
9 | "override-entrypoint": true,
10 | "mounts": [
11 | {
12 | "name": "input",
13 | "writable": false,
14 | "path": "/input"
15 | },
16 | {
17 | "name": "output",
18 | "writable": true,
19 | "path": "/output"
20 | }
21 | ],
22 | "inputs": [],
23 | "outputs": [
24 | {
25 | "name": "outputs",
26 | "description": "The files produced by the command.",
27 | "mount": "output",
28 | "required": true
29 | }
30 | ],
31 | "xnat": [
32 | {
33 | "name": "find-in-project",
34 | "description": "Proof of concept that we can mount an entire project from the archive into a container.",
35 | "contexts": ["xnat:projectData"],
36 | "external-inputs": [
37 | {
38 | "name": "project",
39 | "description": "A project",
40 | "type": "Project",
41 | "required": true,
42 | "provides-files-for-command-mount": "input"
43 | }
44 | ],
45 | "output-handlers": [
46 | {
47 | "name": "file-and-dir-lists",
48 | "accepts-command-output": "outputs",
49 | "as-a-child-of": "project",
50 | "type": "Resource",
51 | "label": "FILES_AND_DIRS"
52 | }
53 | ]
54 | }
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The XNAT web application and server platform are written and maintained by the
2 | Neuroinformatics Research Group at the Washington University School of
3 | Medicine. They are made available for general use under the 2-clause or
4 | Simplified BSD license:
5 |
6 | ==============================================================================
7 |
8 | Copyright (c) 2017, Washington University School of Medicine
9 | All rights reserved.
10 |
11 | Redistribution and use in source and binary forms, with or without
12 | modification, are permitted provided that the following conditions are met:
13 |
14 | 1. Redistributions of source code must retain the above copyright notice, this
15 | list of conditions and the following disclaimer.
16 | 2. Redistributions in binary form must reproduce the above copyright notice,
17 | this list of conditions and the following disclaimer in the documentation
18 | and/or other materials provided with the distribution.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
31 | The views and conclusions contained in the software and documentation are those
32 | of the authors and should not be interpreted as representing official policies,
33 | either expressed or implied, of the FreeBSD Project.
34 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/events/model/KubernetesStatusChangeEvent.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.events.model;
2 |
3 | import lombok.Value;
4 | import org.nrg.containers.model.kubernetes.KubernetesPodPhase;
5 |
6 | import java.time.OffsetDateTime;
7 | import java.util.Collections;
8 | import java.util.Map;
9 |
10 | @Value
11 | public class KubernetesStatusChangeEvent implements ContainerEvent {
12 | String jobName;
13 | String podName;
14 | String containerId;
15 | KubernetesPodPhase podPhase;
16 | String podPhaseReason;
17 | KubernetesContainerState containerState;
18 | String containerStateReason;
19 | Integer exitCode;
20 | OffsetDateTime timestamp;
21 | String nodeId;
22 |
23 | @Override
24 | public String backendId() {
25 | return jobName;
26 | }
27 |
28 | @Override
29 | public String status() {
30 | return podPhase.toString();
31 | }
32 |
33 | @Override
34 | public String externalTimestamp() {
35 | return timestamp == null ? null : timestamp.toString();
36 | }
37 |
38 | @Override
39 | public Map attributes() {
40 | // We don't get any attributes from kubernetes
41 | return Collections.emptyMap();
42 | }
43 |
44 | @Override
45 | public boolean isExitStatus() {
46 | return podPhase.isTerminal();
47 | }
48 |
49 | @Override
50 | public String exitCode() {
51 | return exitCode == null ? null : String.valueOf(exitCode);
52 | }
53 |
54 | @Override
55 | public String details() {
56 | final String details;
57 | if (podPhaseReason != null &&
58 | containerStateReason != null) {
59 | details = "Pod: " + podPhaseReason + " Container: " + containerStateReason;
60 | } else if (podPhaseReason != null) {
61 | details = podPhaseReason;
62 | } else {
63 | details = containerStateReason;
64 | }
65 | return details;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/exceptions/CommandInputResolutionException.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.exceptions;
2 |
3 | import org.nrg.containers.model.command.auto.Command.Input;
4 |
5 | public class CommandInputResolutionException extends CommandResolutionException {
6 | private final Input input;
7 | private final String value;
8 |
9 | public CommandInputResolutionException(final String message, final Throwable cause) {
10 | super(message, cause);
11 | this.input = null;
12 | this.value = null;
13 | }
14 |
15 | public CommandInputResolutionException(final String message, final Input input) {
16 | super(message);
17 | this.input = input;
18 | this.value = null;
19 | }
20 |
21 | public CommandInputResolutionException(final String message, final Input input, final Throwable cause) {
22 | super(message, cause);
23 | this.input = input;
24 | this.value = null;
25 | }
26 |
27 | public CommandInputResolutionException(final String message, final String value) {
28 | super(message);
29 | this.input = null;
30 | this.value = value;
31 | }
32 |
33 | public CommandInputResolutionException(final String message, final String value, final Throwable cause) {
34 | super(message, cause);
35 | this.input = null;
36 | this.value = value;
37 | }
38 |
39 | public CommandInputResolutionException(final String message, final Input input, final String value) {
40 | super(message);
41 | this.input = input;
42 | this.value = value;
43 | }
44 |
45 | public CommandInputResolutionException(final String message, final Input input, final String value, final Throwable cause) {
46 | super(message, cause);
47 | this.input = input;
48 | this.value = value;
49 | }
50 |
51 | public Input getInput() {
52 | return input;
53 | }
54 | public String getValue() {
55 | return value;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/orchestration/entity/OrchestratedWrapperEntity.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.orchestration.entity;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.nrg.containers.model.command.entity.CommandWrapperEntity;
5 | import org.nrg.framework.orm.hibernate.AbstractHibernateEntity;
6 |
7 | import javax.persistence.Entity;
8 | import javax.persistence.FetchType;
9 | import javax.persistence.ManyToOne;
10 |
11 | @Entity
12 | @Slf4j
13 | public class OrchestratedWrapperEntity extends AbstractHibernateEntity {
14 | private OrchestrationEntity orchestrationEntity;
15 | private CommandWrapperEntity commandWrapperEntity;
16 | private int orchestratedOrder;
17 |
18 | public int getOrchestratedOrder() {
19 | return orchestratedOrder;
20 | }
21 |
22 | public void setOrchestratedOrder(int order) {
23 | this.orchestratedOrder = order;
24 | }
25 |
26 | @ManyToOne(fetch = FetchType.LAZY)
27 | public OrchestrationEntity getOrchestrationEntity() {
28 | return orchestrationEntity;
29 | }
30 |
31 | public void setOrchestrationEntity(OrchestrationEntity orchestrationEntity) {
32 | this.orchestrationEntity = orchestrationEntity;
33 | }
34 |
35 | @ManyToOne(fetch = FetchType.LAZY, optional = false)
36 | public CommandWrapperEntity getCommandWrapperEntity() {
37 | return commandWrapperEntity;
38 | }
39 |
40 | public void setCommandWrapperEntity(CommandWrapperEntity commandEntity) {
41 | this.commandWrapperEntity = commandEntity;
42 | }
43 |
44 | public long wrapperId() {
45 | return commandWrapperEntity.getId();
46 | }
47 |
48 | @Override
49 | public boolean equals(Object o) {
50 | if (this == o) return true;
51 | if (!(o instanceof OrchestratedWrapperEntity)) return false;
52 | return getId() == ((OrchestratedWrapperEntity) o).getId();
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | return getClass().hashCode();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/services/CommandLabelServiceTest.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.nrg.containers.config.CommandLabelServiceTestConfig;
7 | import org.nrg.containers.model.command.auto.Command;
8 | import org.nrg.containers.model.image.docker.DockerImage;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.test.context.ContextConfiguration;
11 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
12 |
13 | import java.util.Collections;
14 | import java.util.List;
15 |
16 | import static org.hamcrest.Matchers.hasSize;
17 | import static org.hamcrest.Matchers.is;
18 | import static org.junit.Assert.assertThat;
19 |
20 | @RunWith(SpringJUnit4ClassRunner.class)
21 | @ContextConfiguration(classes = CommandLabelServiceTestConfig.class)
22 | public class CommandLabelServiceTest {
23 |
24 | @Autowired private CommandLabelService commandLabelService;
25 | @Autowired private ObjectMapper objectMapper;
26 |
27 | @Test
28 | public void testCommandLabelsCanDeserialize() throws Exception {
29 | final String commandName = "command";
30 | final String imageName = "an image";
31 | final Command command = Command.builder()
32 | .name(commandName)
33 | .image(imageName)
34 | .build();
35 |
36 | final String dockerImageLabel = objectMapper.writeValueAsString(Collections.singletonList(command));
37 | final DockerImage dockerImage = DockerImage.builder()
38 | .imageId("image id")
39 | .addLabel(CommandLabelService.LABEL_KEY, dockerImageLabel)
40 | .build();
41 |
42 | final List commandsFromLabels = commandLabelService.parseLabels(imageName, dockerImage);
43 | assertThat(commandsFromLabels, hasSize(1));
44 | assertThat(commandsFromLabels.get(0).name(), is(commandName));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/api/KubernetesClientTest.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.api;
2 |
3 | import io.kubernetes.client.openapi.models.V1Affinity;
4 | import io.kubernetes.client.openapi.models.V1NodeSelectorRequirement;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.junit.Test;
7 |
8 | import java.util.Collections;
9 |
10 | import static org.hamcrest.CoreMatchers.is;
11 | import static org.junit.Assert.assertThat;
12 |
13 | @Slf4j
14 | public class KubernetesClientTest {
15 |
16 | @Test
17 | public void parseSwarmConstraint() throws Exception {
18 | final String constraintKey = "a-key";
19 | final String constraintValue = "a-value";
20 | final String swarmComparator = "==";
21 | final String expectedKubernetesOperator = "In";
22 |
23 | final String swarmConstraint = constraintKey + swarmComparator + constraintValue;
24 |
25 | // Test the underlying method that takes one constraint string
26 | final KubernetesClientImpl.ParsedConstraint parsedConstraint = KubernetesClientImpl.parseSwarmConstraint(swarmConstraint);
27 |
28 | assertThat(parsedConstraint.label(), is(constraintKey));
29 | assertThat(parsedConstraint.operator(), is(expectedKubernetesOperator));
30 | assertThat(parsedConstraint.value(), is(constraintValue));
31 |
32 | // Test the method that takes a list and puts it into the kubernetes object format
33 | final V1Affinity affinity = KubernetesClientImpl.parseSwarmConstraints(Collections.singletonList(swarmConstraint));
34 |
35 | // Dig out the properties at the bottom of the nested objects
36 | V1NodeSelectorRequirement nodeSelector = affinity.getNodeAffinity().getRequiredDuringSchedulingIgnoredDuringExecution().getNodeSelectorTerms().get(0).getMatchExpressions().get(0);
37 | assertThat(nodeSelector.getKey(), is(constraintKey));
38 | assertThat(nodeSelector.getOperator(), is(expectedKubernetesOperator));
39 | assertThat(nodeSelector.getValues(), is(Collections.singletonList(constraintValue)));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/resources/commandLaunchTest/testScanUpload/fakeDirForCopy/scan.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | session1
4 | usable
5 | 3
6 | containerUpload
7 | T1w
8 |
9 | 2.4
10 | 0.00316
11 | 1.0
12 | 8
13 | GR_IR
14 | ORIGINAL\PRIMARY\M\ND\NORM
15 | SP_MP_OSP
16 | i-
17 | 2013-04-29T00:00:00
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/params-command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "param-test",
3 | "description": "Test resolving params",
4 | "type": "docker",
5 | "image": "busybox:latest",
6 | "command-line": "echo #REQUIRED_NO_FLAG# #REQUIRED_WITH_FLAG# #NOT_REQUIRED#",
7 | "inputs": [
8 | {
9 | "name": "REQUIRED_NO_FLAG",
10 | "type": "string",
11 | "required": true
12 | },
13 | {
14 | "name": "REQUIRED_WITH_FLAG",
15 | "type": "string",
16 | "required": true,
17 | "command-line-flag": "--flag"
18 | },
19 | {
20 | "name": "NOT_REQUIRED",
21 | "type": "string",
22 | "required": false
23 | }
24 | ],
25 | "xnat": [
26 | {
27 | "name": "blank-wrapper",
28 | "label": "Param test: blank",
29 | "description": "Test resolving the command by itself",
30 | "external-inputs": [],
31 | "derived-inputs": [],
32 | "output-handlers": []
33 | },
34 | {
35 | "name": "identity-wrapper",
36 | "label": "Param test: identity",
37 | "description": "Test resolving the command with wrapper params",
38 | "external-inputs": [
39 | {
40 | "name": "required-no-flag",
41 | "type": "string",
42 | "required": true,
43 | "provides-value-to-command-input": "REQUIRED_NO_FLAG"
44 | },
45 | {
46 | "name": "required-with-flag",
47 | "type": "string",
48 | "required": true,
49 | "provides-value-to-command-input": "REQUIRED_WITH_FLAG"
50 | },
51 | {
52 | "name": "not-required",
53 | "type": "string",
54 | "required": false,
55 | "provides-value-to-command-input": "NOT_REQUIRED"
56 | }
57 | ],
58 | "derived-inputs": []
59 | }
60 | ]
61 | }
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/DockerHubService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import org.nrg.containers.exceptions.NotUniqueException;
4 | import org.nrg.containers.model.dockerhub.DockerHubBase.DockerHub;
5 | import org.nrg.containers.model.dockerhub.DockerHubEntity;
6 | import org.nrg.framework.exceptions.NotFoundException;
7 | import org.nrg.framework.orm.hibernate.BaseHibernateService;
8 |
9 | import java.util.List;
10 |
11 | public interface DockerHubService extends BaseHibernateService {
12 | DockerHubEntity retrieve(String name) throws NotUniqueException;
13 | DockerHubEntity get(String name) throws NotUniqueException, NotFoundException;
14 | DockerHub retrieveHub(long id);
15 | DockerHub retrieveHub(String name) throws NotUniqueException;
16 | DockerHub getHub(long hubId) throws NotFoundException;
17 | DockerHub getHub(String hubName) throws NotFoundException, NotUniqueException;
18 | DockerHub getByUrl(final String url);
19 | List getHubs();
20 |
21 | void setDefault(DockerHub dockerHub, String username, String reason);
22 | DockerHub create(DockerHub dockerHub);
23 | DockerHubEntity createAndSetDefault(DockerHubEntity dockerHubEntity, String username, String reason);
24 | DockerHub createAndSetDefault(DockerHub dockerHub, String username, String reason);
25 | void update(DockerHub dockerHub);
26 | void updateAndSetDefault(DockerHubEntity dockerHubEntity, String username, String reason);
27 | void updateAndSetDefault(DockerHub dockerHub, String username, String reason);
28 | void setDefault(long id, String username, String reason);
29 | void delete(long id) throws DockerHubDeleteDefaultException;
30 | void delete(String name) throws DockerHubDeleteDefaultException, NotUniqueException;
31 |
32 | void delete(DockerHubEntity entity) throws DockerHubDeleteDefaultException;
33 |
34 | long getDefaultHubId();
35 | DockerHub getDefault();
36 |
37 | class DockerHubDeleteDefaultException extends RuntimeException {
38 | public DockerHubDeleteDefaultException(final String message) {
39 | super(message);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/docs/html2confluence.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import re
4 | import sys
5 |
6 | inputAndOutputFile = sys.argv[1]
7 |
8 | xnatIssueLinkRe = re.compile(r'.+?<\/a>')
9 | xnatIssueLinkReplacement = "Neuroinformatics Research Group JIRAkey,summary,type,created,updated,due,assignee,reporter,priority,status,resolutioncd48cfbe-36e3-3ab6-af43-5d0331c561fb\\1"
10 |
11 | headerAnchorRe = re.compile(r'')
12 | headerAnchorReplacement = "\\2"
13 |
14 | anchorLinkRe = re.compile(r'(.*?)<\/a>')
15 | anchorLinkReplacement = ""
16 |
17 | wikiLinkRe = re.compile(r'(.+?)')
18 | def wikiLinkReplacement(matchobj):
19 | return "".format(matchobj.group(1).replace('+', ' '), matchobj.group(2))
20 |
21 | regexAndReplacements = ((xnatIssueLinkRe, xnatIssueLinkReplacement),
22 | (headerAnchorRe, headerAnchorReplacement),
23 | (anchorLinkRe, anchorLinkReplacement),
24 | (wikiLinkRe, wikiLinkReplacement))
25 |
26 | with open(inputAndOutputFile, "r") as f:
27 | confluenceOriginal = [line.rstrip("\n") for line in f.readlines()]
28 |
29 | confluenceProcessed = []
30 | for line in confluenceOriginal:
31 | for regex, replacement in regexAndReplacements:
32 | line = regex.sub(replacement, line)
33 | confluenceProcessed.append(line)
34 |
35 | with open(inputAndOutputFile, "w") as f:
36 | f.write('\n'.join(confluenceProcessed))
37 |
--------------------------------------------------------------------------------
/docs/upload-docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | die(){
4 | popd > /dev/null
5 | echo >&2 "$@"
6 | exit -1
7 | }
8 |
9 | username=${1?Please pass XNAT Wiki username and password as arguments to this script}
10 | password=${2?Please pass XNAT Wiki username and password as arguments to this script}
11 |
12 | docsdir=$(dirname "$0")
13 | pushd $docsdir > /dev/null
14 |
15 | mostRecentReleaseTag=$(git describe --abbrev=0 --tags)
16 |
17 | # Make container-service-api.md from template and swagger.json
18 | echo "Making container-service-api.md from template"
19 | ./make-container-service-api-md.py
20 |
21 | # For each markdown file...
22 | # Convert it to HTML and confluence HTML
23 | # Upload it to wiki
24 | for mdFilePath in $(ls *.md); do
25 | mdName=$(basename $mdFilePath)
26 | base=${mdName%.md}
27 | htmlName="${base}.html"
28 | confluenceName="${htmlName}.confluence"
29 |
30 | echo
31 | echo "Next file: $mdName"
32 |
33 | # Check if the file should be skipped
34 | if $(head -1 $mdName | grep 'NO UPLOAD' > /dev/null); then
35 | echo "${mdName} has requested NO UPLOAD. Skipping."
36 | continue
37 | fi
38 |
39 | if [[ "$base" == "container-service-api" ]]; then
40 | fileToCompare="swagger.json"
41 | else
42 | fileToCompare=${mdName}
43 | fi
44 | d=$(git diff ${mostRecentReleaseTag} ${fileToCompare}) || (echo "Failed to check if $fileToCompare has changed since last release. Skipping."; continue)
45 |
46 | if [[ -n "$d" ]]; then
47 | echo "$fileToCompare has changed since last release. Proceeding with conversion and upload."
48 | else
49 | echo "$fileToCompare has not changed since last release. Skipping."
50 | continue
51 | fi
52 |
53 | echo "Converting ${mdName} from markdown to confluence HTML"
54 | ./md2confluencehtml.sh ${mdName} || die "Failed to convert ${mdName} from markdown to confluence HTML"
55 |
56 | echo "Uploading converted ${mdName} to wiki"
57 | ./upload-confluence.py ${username} ${password} ${confluenceName} || die "Failed to upload ${confluenceName} to wiki"
58 |
59 | done
60 |
61 | echo "Cleaning up"
62 | rm *.html
63 | rm *.html.confluence
64 | rm container-service-api.md
65 |
66 | popd > /dev/null
67 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/utils/ShellSplitterTest.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.utils;
2 |
3 | import org.hamcrest.Matchers;
4 | import org.junit.Test;
5 |
6 | import java.util.Arrays;
7 | import java.util.Collections;
8 |
9 | import static org.hamcrest.Matchers.equalTo;
10 | import static org.hamcrest.Matchers.is;
11 | import static org.junit.Assert.assertThat;
12 |
13 | public class ShellSplitterTest {
14 | @Test
15 | public void blankYieldsEmptyArgs() {
16 | assertThat(ShellSplitter.shellSplit(""), is(Matchers.empty()));
17 | }
18 |
19 | @Test
20 | public void whitespacesOnlyYieldsEmptyArgs() {
21 | assertThat(ShellSplitter.shellSplit(" \t \n"), is(Matchers.empty()));
22 | }
23 |
24 | @Test
25 | public void normalTokens() {
26 | assertThat(ShellSplitter.shellSplit("a\tbee cee"), is(equalTo(Arrays.asList("a", "bee", "cee"))));
27 | }
28 |
29 | @Test
30 | public void doubleQuotes() {
31 | assertThat(ShellSplitter.shellSplit("\"hello world\""), is(equalTo(Collections.singletonList("hello world"))));
32 | }
33 |
34 | @Test
35 | public void singleQuotes() {
36 | assertThat(ShellSplitter.shellSplit("'hello world'"), is(equalTo(Collections.singletonList("hello world"))));
37 | }
38 |
39 |
40 | @Test
41 | public void escapedDoubleQuotes() {
42 | assertThat(ShellSplitter.shellSplit("\"\\\"hello world\\\""), is(equalTo(Collections.singletonList("\"hello world\""))));
43 | }
44 |
45 | @Test
46 | public void noEscapeWithinSingleQuotes() {
47 | assertThat(ShellSplitter.shellSplit("'hello \\\" world'"), is(equalTo(Collections.singletonList("hello \\\" world"))));
48 | }
49 |
50 | @Test
51 | public void backToBackQuotedStringsShouldFormSingleToken() {
52 | assertThat(ShellSplitter.shellSplit("\"foo\"'bar'baz"), is(equalTo(Collections.singletonList("foobarbaz"))));
53 | assertThat(ShellSplitter.shellSplit("\"three\"' 'four"), is(equalTo(Collections.singletonList("three four"))));
54 | }
55 |
56 | @Test
57 | public void escapedSpacesDoNotBreakUpTokens() {
58 | assertThat(ShellSplitter.shellSplit("three\\ four"), is(equalTo(Collections.singletonList("three four"))));
59 | }
60 | }
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/services/ContainerConfigService.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.services;
2 |
3 | import org.nrg.containers.model.configuration.CommandConfigurationInternal;
4 |
5 | import java.util.List;
6 |
7 | public interface ContainerConfigService {
8 | String TOOL_ID = "container-service";
9 | String DEFAULT_DOCKER_HUB_PATH = "default-docker-hub-id";
10 | String WRAPPER_CONFIG_PATH_TEMPLATE = "wrapper-%d";
11 |
12 | long getDefaultDockerHubId();
13 | void setDefaultDockerHubId(long hubId, String username, String reason);
14 |
15 | void configureForSite(CommandConfigurationInternal commandConfiguration, long wrapperId, String username, String reason) throws CommandConfigurationException;
16 | void configureForProject(CommandConfigurationInternal commandConfiguration, String project, long wrapperId, String username, String reason) throws CommandConfigurationException;
17 |
18 | CommandConfigurationInternal getSiteConfiguration(long wrapperId);
19 | CommandConfigurationInternal getProjectConfiguration(String project, long wrapperId);
20 |
21 | void deleteSiteConfiguration(long commandId, final String username) throws CommandConfigurationException;
22 | void deleteProjectConfiguration(String project, long wrapperId, final String username) throws CommandConfigurationException;
23 |
24 | void enableForSite(long wrapperId, final String username, final String reason) throws CommandConfigurationException;
25 |
26 | void disableForSite(long wrapperId, final String username, final String reason) throws CommandConfigurationException;
27 | boolean isEnabledForSite(long wrapperId);
28 | void enableForProject(String project, long wrapperId, final String username, final String reason) throws CommandConfigurationException;
29 | void disableForProject(String project, long wrapperId, final String username, final String reason) throws CommandConfigurationException;
30 | boolean isEnabledForProject(String project, long wrapperId);
31 | boolean isEnabled(String project, long wrapperId);
32 | List getProjects(long wrapperId, String status);
33 |
34 | class CommandConfigurationException extends Exception {
35 | public CommandConfigurationException(final String message, final Throwable e) {
36 | super(message, e);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/utils/JsonStringToDateSerializer.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.utils;
2 |
3 | import com.fasterxml.jackson.core.JsonGenerator;
4 | import com.fasterxml.jackson.core.JsonProcessingException;
5 | import com.fasterxml.jackson.databind.JsonSerializer;
6 | import com.fasterxml.jackson.databind.SerializerProvider;
7 | import org.apache.commons.lang3.StringUtils;
8 |
9 | import java.io.IOException;
10 | import java.text.SimpleDateFormat;
11 | import java.util.Date;
12 | import java.util.regex.Pattern;
13 |
14 | import static org.nrg.containers.utils.JsonDateSerializer.DATE_FORMAT;
15 |
16 | /**
17 | * @author Mohana Ramaratnam
18 | *
19 | */
20 | public class JsonStringToDateSerializer extends JsonSerializer {
21 | private static final Pattern NUMERIC_TIMESTAMP = Pattern.compile("^\\d{12,}$"); // Twelve or more digits and that's all
22 |
23 | private static final int NUM_DIGITS_IN_NANOSECOND_VALUE = 16;
24 | private static final int NUM_DIGITS_TO_TRUNCATE_NANO_TO_MILLI = 6;
25 |
26 | @Override
27 | public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException,
28 | JsonProcessingException {
29 | if (StringUtils.isBlank(value)) {
30 | return;
31 | }
32 |
33 | if (!NUMERIC_TIMESTAMP.matcher(value).matches()) {
34 | // Timestamp isn't numeric. Return as-is.
35 | jgen.writeString(value);
36 | return;
37 | }
38 |
39 | if (value.length() >= NUM_DIGITS_IN_NANOSECOND_VALUE) {
40 | // This value is in nanoseconds - convert to milliseconds
41 | value = value.substring(0, value.length() - NUM_DIGITS_TO_TRUNCATE_NANO_TO_MILLI);
42 | }
43 |
44 | long longVal = Long.parseLong(value);
45 |
46 | Date longAsDate = new Date(longVal);
47 | jgen.writeString(DATE_FORMAT.format(longAsDate));
48 | }
49 |
50 | public static void main(String[] args) {
51 | long longVal = Long.parseLong("1542051871478");
52 | Date longAsDate = new Date(longVal);
53 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS Z");
54 | //System.out.println("String to Date: " + format.format(longAsDate));
55 | try {
56 | Date d = format.parse("2018-11-03T12:45:38.615-05:00");
57 | long milliseconds = d.getTime();
58 | } catch (Exception e) {
59 | e.printStackTrace();
60 | }
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/config/SpringJUnit4ClassRunnerFactory.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.config;
2 |
3 | import org.junit.runner.Runner;
4 | import org.junit.runners.model.FrameworkMethod;
5 | import org.junit.runners.model.InitializationError;
6 | import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParameters;
7 | import org.junit.runners.parameterized.ParametersRunnerFactory;
8 | import org.junit.runners.parameterized.TestWithParameters;
9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10 |
11 | public class SpringJUnit4ClassRunnerFactory implements ParametersRunnerFactory {
12 | //https://gist.github.com/bademux/5ed07e564c879994b93c
13 | //https://stackoverflow.com/questions/28560734/how-to-run-junit-springjunit4classrunner-with-parametrized
14 | //https://knowjavathings.blogspot.com/2014/02/junit4-for-both-parameterized-and.html
15 | // So as to use parameterized tests, we cannot also use @RunWith(SpringJUnit4ClassRunner.class), use this instead
16 |
17 | @Override
18 | public Runner createRunnerForTestWithParameters(final TestWithParameters test) throws InitializationError {
19 | final LocalBlockJUnit4ClassRunnerWithParameters testRunner = new LocalBlockJUnit4ClassRunnerWithParameters(test);
20 |
21 | return new SpringJUnit4ClassRunner(test.getTestClass().getJavaClass()) {
22 | @Override
23 | protected Object createTest() throws Exception {
24 | final Object testInstance = testRunner.createTest();
25 | getTestContextManager().prepareTestInstance(testInstance);
26 | return testInstance;
27 | }
28 |
29 | @Override
30 | protected String getName() {
31 | return testRunner.getRealName();
32 | }
33 |
34 | @Override
35 | protected String testName(FrameworkMethod method) {
36 | return method.getName() + testRunner.getRealName();
37 | }
38 | };
39 | }
40 |
41 | private class LocalBlockJUnit4ClassRunnerWithParameters extends BlockJUnit4ClassRunnerWithParameters {
42 |
43 | public LocalBlockJUnit4ClassRunnerWithParameters(final TestWithParameters test) throws InitializationError {
44 | super(test);
45 | }
46 |
47 | String getRealName() {
48 | return getName();
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the XNAT Container Service
2 |
3 | First off, thanks for taking the time to contribute! 🎉👍
4 |
5 | We welcome any [bug reports](#report-an-issue), feature requests, or [questions](#ask-a-question). And we super-duper welcome any [pull requests](#make-a-pull-request).
6 |
7 | This document is a little sparse now, but will hopefully evolve in the future.
8 |
9 | ## Report an issue
10 |
11 | If you have (or want to open) an account on the [XNAT JIRA](https://issues.xnat.org), you could make an issue there; just add it to the [Container Service project](https://issues.xnat.org/projects/CS).
12 | Alternatively, [ask a question on the discussion group](#ask-a-question)
13 |
14 | ## Ask a question
15 |
16 | First, check the [XNAT Discussion Board](https://groups.google.com/forum/#!forum/xnat_discussion). It is possible that someone has already asked your question and gotten an answer, so it could save you a lot of time to search around first. And if no one has asked your question, the discussion board is a great first place to ask.
17 |
18 |
19 | ## Run the Tests
20 | The various unit tests can be run with:
21 | ```
22 | [container-service]$ ./gradlew unitTest
23 | ```
24 |
25 | If you have a docker server that you can use for testing, there are some additional tests that can run. Make sure your docker environment is all set up (you can test this by making sure `$ docker version` works). Then run all the tests with
26 | ```
27 | [container-service]$ ./gradlew test
28 | ```
29 | In order to synchronize your Docker VM clock, you may need to initially run the command as:
30 | ```
31 | [container-service]$ docker run --rm --privileged alpine:latest hwclock -s && ./gradlew clean test
32 | ```
33 | We do not have any tests that can integrate with a running XNAT. All of the tests in this library use bespoke databases and mocked interfaces any time the code intends to communicate with XNAT. We welcome your contributions!
34 |
35 | ## Make a Pull Request
36 | If you want to contribute code, we will be very happy to have it.
37 |
38 | The first thing you should know is that we do almost all of our work on the `dev` branch, or on smaller "feature" branches that come from and merge into `dev`. So if you want to do something with the code, you, too, should start a new branch from `dev` on our [BitBucket repo](https://bitbucket.org/xnatdev/container-service).
39 |
40 |
41 |
42 |
43 | And thanks again!
44 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/command/auto/ResolvedInputValue.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.command.auto;
2 |
3 | import com.fasterxml.jackson.annotation.JsonCreator;
4 | import com.fasterxml.jackson.annotation.JsonIgnore;
5 | import com.fasterxml.jackson.annotation.JsonProperty;
6 | import com.google.auto.value.AutoValue;
7 | import org.nrg.containers.model.command.entity.CommandInputEntity;
8 | import org.nrg.containers.model.xnat.XnatModelObject;
9 |
10 | import javax.annotation.Nullable;
11 | import java.io.Serializable;
12 |
13 | @AutoValue
14 | public abstract class ResolvedInputValue implements Serializable {
15 | private static final long serialVersionUID = 3736382357316063998L;
16 |
17 | @JsonProperty("type") public abstract String type();
18 | @Nullable @JsonProperty("value") public abstract String value();
19 | @Nullable @JsonProperty("value-label") public abstract String valueLabel();
20 | @JsonIgnore @Nullable public abstract XnatModelObject xnatModelObject();
21 | @Nullable @JsonProperty("json-value") public abstract String jsonValue();
22 |
23 | @JsonCreator
24 | public static ResolvedInputValue create(@JsonProperty("type") final String type,
25 | @JsonProperty("value") final String value,
26 | @JsonProperty("value-label") final String valueLabel,
27 | @JsonProperty("json-value") final String jsonValue) {
28 | return builder()
29 | .type(type)
30 | .value(value)
31 | .valueLabel(valueLabel)
32 | .jsonValue(jsonValue == null ? value : jsonValue)
33 | .build();
34 | }
35 |
36 | public static Builder builder() {
37 | return new AutoValue_ResolvedInputValue.Builder()
38 | .type(CommandInputEntity.DEFAULT_TYPE.getName());
39 | }
40 |
41 | @AutoValue.Builder
42 | public static abstract class Builder {
43 | public abstract Builder type(String type);
44 | public abstract Builder value(String value);
45 | public abstract Builder valueLabel(String valueLabel);
46 | public abstract Builder xnatModelObject(XnatModelObject xnatModelObject);
47 | public abstract Builder jsonValue(String jsonValue);
48 |
49 | public abstract ResolvedInputValue build();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/docs/set-up-vms-for-swarm-dev.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | * Make local directories
4 | * One to serve as the XNAT archive, which I will call "archive"
5 | * Another for the build directory, which I will call "build"
6 | * Set up XNAT VM (use XNAT Vagrant project)
7 | * Start from xnat-release config
8 | * Edit local.yaml
9 | * Pointing to 1.7.3 war
10 | * Add shares
11 | * archive to `/data/xnat/archive`
12 | * build to `/data/xnat/build`
13 | * Run vagrant setup
14 | * If you are on the Ubuntu 14.04 VM, update docker. If you are using 16.04 or later, you can skip this.
15 | * Fix bad puppet PGP key
16 | * `sudo gpg --keyserver pgp.mit.edu --recv-key 7F438280EF8D349F`
17 | * `sudo gpg -a --export EF8D349F | sudo apt-key add -`
18 | * `sudo apt-get update`
19 | * `sudo apt-get upgrade`
20 | * Say "No" to everything
21 | * Find version with `docker version`. (Right now it is 17.05.0-ce)
22 | * If you are using the Ubuntu 16.04 VM, find the docker version
23 | * SSH onto machine
24 | * `docker version`
25 | * Note docker version for later.
26 | * set up swarm
27 | * `docker swarm init --advertise-addr ${YOUR_VM_IP}`
28 | * Note the swarm join command that gets spit out. We will use it later.
29 | * `docker node update --availability drain xnat`
30 | * Set up Worker node(s)
31 | * Go to [boot2docker releases page](https://github.com/boot2docker/boot2docker/releases)
32 | * Find boot2docker release with the same version of docker as you noted earlier
33 | * `docker-machine create --boot2docker-url ${above url} worker${n}` (where n is an index, recommended if you are making more than one worker)
34 | * ssh to node just created
35 | * Use swarm join command that got spit out when we made the swarm
36 | * Share local XNAT archive (do this for each worker node)
37 | * In virtualbox, change VM settings. Add shares to archive and build (use the local paths on your machine)
38 | * ssh to VM
39 | * `sudo mkdir -p /data/xnat/archive`
40 | * `sudo mkdir /data/xnat/build`
41 | * `sudo chown -R docker /data`
42 | * Share archive to worker node: `sudo mount -t vboxsf -o uid=1000,gid=100 archive /data/xnat/archive`
43 | * Share build to worker node: `sudo mount -t vboxsf -o uid=1000,gid=100 build /data/xnat/build`
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/config/DockerRestApiTestConfig.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.config;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import org.mockito.Mockito;
5 | import org.nrg.containers.rest.DockerRestApi;
6 | import org.nrg.containers.services.DockerService;
7 | import org.nrg.framework.services.ContextService;
8 | import org.nrg.xdat.security.services.RoleHolder;
9 | import org.nrg.xdat.security.services.UserManagementServiceI;
10 | import org.springframework.context.ApplicationContext;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Configuration;
13 | import org.springframework.context.annotation.Import;
14 | import org.springframework.security.authentication.TestingAuthenticationProvider;
15 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
16 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
17 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
18 | import org.springframework.web.servlet.config.annotation.EnableWebMvc;
19 |
20 | @Configuration
21 | @EnableWebMvc
22 | @EnableWebSecurity
23 | @Import(RestApiTestConfig.class)
24 | public class DockerRestApiTestConfig extends WebSecurityConfigurerAdapter {
25 | @Bean
26 | public DockerRestApi dockerRestApi(final DockerService dockerService,
27 | final ObjectMapper objectMapper,
28 | final UserManagementServiceI userManagementService,
29 | final RoleHolder roleHolder) {
30 | return new DockerRestApi(dockerService, objectMapper, userManagementService, roleHolder);
31 | }
32 |
33 | @Bean
34 | public DockerService dockerService() {
35 | return Mockito.mock(DockerService.class);
36 | }
37 |
38 | @Bean
39 | public ContextService contextService(final ApplicationContext applicationContext) {
40 | final ContextService contextService = new ContextService();
41 | contextService.setApplicationContext(applicationContext);
42 | return contextService;
43 | }
44 |
45 | @Override
46 | protected void configure(AuthenticationManagerBuilder auth) throws Exception {
47 | auth.authenticationProvider(new TestingAuthenticationProvider());
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/swarm-mode-vs-container-mode.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | This document sketches out the differences in the container service backend that will be implemented to enable "swarm mode", i.e. to run containers that are scheduled by a swarm rather than directly on a docker server.
4 |
5 | ## Server configuration
6 | Our current model has XNAT configured to communicate with a single docker server. We will not change that one-to-one connection at this time. Instead, we will add a global flag to the server configuration called "Swarm mode". The system admin can set this flag to indicate that the docker server they have configured is a swarm manager and they wish to launch their jobs as swarm services. As of version 2.0.2, the system admin can configure [swarm node constraints](https://docs.docker.com/engine/swarm/services/#placement-constraints), which can be configured to be passed along to users upon launch (e.g., if you want users to be able to select a high-memory node, etc).
7 |
8 | When this flag is set, all jobs will be launched as swarm services; when it is not set, all jobs will be launched as containers directly on the configured server.
9 |
10 | ## Commands
11 | Right now I anticipate no differences to the command format. Users will not need to make any changes to existing commands.
12 |
13 | ## Launching containers
14 | The actions taken by users to launch jobs will not change, although if swarm constraints are configured to be user-settable by the system admin, then these options will be selectable as advanced options from the container launch screen.
15 |
16 | The only difference will be in the back-end code to create the job. In the current code, after resolving the command using the runtime parameter values, the container service uses that resolved command to create and launch a container on the server. After this update, the container service will still resolve the command the same way, but will then check the global flag indicating whether the server is operating in swarm mode. If the server is not in swarm mode, then the resolved command will be used to create a container just as is done now. If the server is in swarm mode, then the resolved command will be used to create a service spec with one replica and no restart condition. In this way, the service will be used to create a task that replicates the existing mode of launching a job as a container with a finite life cycle. We are using the swarm merely as a job scheduler.
17 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/events/model/DockerContainerEvent.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.events.model;
2 |
3 | import com.google.auto.value.AutoValue;
4 | import com.google.common.collect.ImmutableMap;
5 |
6 | import java.io.Serializable;
7 | import java.util.Date;
8 | import java.util.Map;
9 | import java.util.regex.Pattern;
10 |
11 | @AutoValue
12 | public abstract class DockerContainerEvent implements ContainerEvent, Serializable {
13 | private static final long serialVersionUID = -6676309798270638416L;
14 |
15 | private static final Pattern ignoreStatusPattern = Pattern.compile("kill|destroy");
16 | private static final Pattern exitStatusPattern = Pattern.compile("die");
17 |
18 | public abstract String status();
19 | public abstract String backendId();
20 | public abstract String externalTimestamp();
21 | public abstract ImmutableMap attributes();
22 |
23 | public boolean isIgnoreStatus() {
24 | // These statuses come after "die" and thus we want to ignore them
25 | final String status = status();
26 | return status != null && ignoreStatusPattern.matcher(status).matches();
27 | }
28 |
29 | public boolean isExitStatus() {
30 | final String status = status();
31 | return status != null && exitStatusPattern.matcher(status).matches();
32 | }
33 |
34 | public String exitCode() {
35 | return isExitStatus() ?
36 | (attributes().getOrDefault("exitCode", "")) :
37 | null;
38 | }
39 |
40 | @Override
41 | public String details() {
42 | return null;
43 | }
44 |
45 | public static DockerContainerEvent create(final String status,
46 | final String containerId,
47 | final Date time,
48 | final Long timeNano,
49 | final Map attributes) {
50 | final ImmutableMap attributesCopy = attributes == null ?
51 | ImmutableMap.of() :
52 | ImmutableMap.copyOf(attributes);
53 | final String externalTimestamp = timeNano != null ? String.valueOf(timeNano) : time.toInstant().toString();
54 | return new AutoValue_DockerContainerEvent(status, containerId, externalTimestamp, attributesCopy);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/jms/utils/QueueUtils.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.jms.utils;
2 |
3 | import java.util.Enumeration;
4 | import java.util.function.Supplier;
5 |
6 | import javax.jms.Destination;
7 | import javax.jms.JMSException;
8 | import javax.jms.Message;
9 | import javax.jms.QueueBrowser;
10 | import javax.jms.Session;
11 |
12 | import org.apache.commons.lang3.StringUtils;
13 | import org.nrg.xdat.XDAT;
14 | import org.springframework.jms.core.BrowserCallback;
15 | import org.springframework.jms.core.JmsTemplate;
16 |
17 | import lombok.extern.slf4j.Slf4j;
18 | import org.springframework.util.ClassUtils;
19 |
20 | @Slf4j
21 | public class QueueUtils {
22 | /*
23 | * Get the count of the current messages in this queue.
24 | */
25 | public static int count(String destination){
26 | int count = XDAT.getContextService().getBean(JmsTemplate.class).browse(destination, new BrowserCallback() {
27 | public Integer doInJms(final Session session, final QueueBrowser browser) throws JMSException {
28 | Enumeration enumeration = browser.getEnumeration();
29 | int counter = 0;
30 | while (enumeration.hasMoreElements()) {
31 | Message msg = (Message) enumeration.nextElement();
32 | //System.out.println(String.format("\tFound : %s", msg));
33 | counter += 1;
34 | }
35 | return counter;
36 | }
37 | });
38 |
39 | log.debug("There are {} messages in queue {}", count, destination);
40 | return count;
41 | }
42 |
43 | public static void sendJmsRequest(final String destination, final String message) {
44 | sendJmsRequest(XDAT.getContextService().getBean(JmsTemplate.class), destination, message);
45 | }
46 |
47 | public static void sendJmsRequest(final JmsTemplate template, final String queueName, final Object request) {
48 | final Destination destination = XDAT.getContextService().getBeanSafely(queueName, Destination.class);
49 | if (destination == null) {
50 | log.error("Unable to find destination for queue name {}", queueName);
51 | return;
52 | }
53 | template.convertAndSend(destination, request, (processor) -> {
54 | processor.setStringProperty("taskId", queueName);
55 | return processor;
56 | });
57 |
58 | }
59 | }
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/initialization/tasks/UpdateVisibilityOfExistingCommands.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.initialization.tasks;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.nrg.containers.model.command.entity.CommandVisibility;
5 | import org.nrg.framework.orm.DatabaseHelper;
6 | import org.nrg.xft.schema.XFTManager;
7 | import org.nrg.xnat.initialization.tasks.AbstractInitializingTask;
8 | import org.nrg.xnat.initialization.tasks.InitializingTaskException;
9 | import org.nrg.xnat.services.XnatAppInfo;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.jdbc.core.JdbcTemplate;
12 | import org.springframework.stereotype.Component;
13 |
14 | @Component
15 | @Slf4j
16 | public class UpdateVisibilityOfExistingCommands extends AbstractInitializingTask {
17 |
18 | @Autowired
19 | public UpdateVisibilityOfExistingCommands(final XnatAppInfo appInfo, final JdbcTemplate template) {
20 | super();
21 | this.appInfo = appInfo;
22 | this.databaseHelper = new DatabaseHelper(template);
23 | this.jdbcTemplate = template;
24 | }
25 |
26 | @Override
27 | public String getTaskName() {
28 | return "UpdateVisibilityOfExistingCommands";
29 | }
30 |
31 | @Override
32 | protected void callImpl() throws InitializingTaskException {
33 | try {
34 | if (!appInfo.isInitialized() || !XFTManager.isComplete()) {
35 | throw new InitializingTaskException(InitializingTaskException.Level.RequiresInitialization);
36 | }
37 | jdbcTemplate.execute("UPDATE " + COMMAND_ENTITY_TABLE_NAME + " SET " + VISIBILITY_COLUMN + " = 0 where " + VISIBILITY_COLUMN + " is null");
38 | log.debug("Table " + COMMAND_ENTITY_TABLE_NAME + " updated. Set the " + VISIBILITY_COLUMN + " column value to " + CommandVisibility.PUBLIC_CONTAINER);
39 | } catch(Exception e) {
40 | log.error("Encountered while setting visibility column value",e);
41 | throw new InitializingTaskException(InitializingTaskException.Level.Error);
42 | }
43 | }
44 |
45 | private final JdbcTemplate jdbcTemplate;
46 | private final DatabaseHelper databaseHelper;
47 | private final XnatAppInfo appInfo;
48 | private final String COMMAND_ENTITY_TABLE_NAME = "xhbm_command_entity";
49 | private final String VISIBILITY_COLUMN = "visibility_type";
50 |
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/test/java/org/nrg/containers/config/ContainerEntityTestConfig.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.config;
2 |
3 | import org.mockito.Mockito;
4 | import org.nrg.containers.api.ContainerControlApi;
5 | import org.nrg.containers.daos.ContainerEntityRepository;
6 | import org.nrg.containers.services.ContainerEntityService;
7 | import org.nrg.containers.services.impl.HibernateContainerEntityService;
8 | import org.nrg.framework.services.NrgEventServiceI;
9 | import org.nrg.framework.services.SerializerService;
10 | import org.nrg.prefs.services.NrgPreferenceService;
11 | import org.nrg.xdat.preferences.SiteConfigPreferences;
12 | import org.nrg.xdat.security.services.PermissionsServiceI;
13 | import org.nrg.xnat.services.archive.CatalogService;
14 | import org.springframework.context.annotation.Bean;
15 | import org.springframework.context.annotation.Configuration;
16 | import org.springframework.context.annotation.Import;
17 | import reactor.bus.EventBus;
18 |
19 | @Configuration
20 | @Import({HibernateConfig.class, ObjectMapperConfig.class})
21 | public class ContainerEntityTestConfig {
22 | @Bean
23 | public EventBus eventBus() {
24 | return Mockito.mock(EventBus.class);
25 | }
26 |
27 | @Bean
28 | public ContainerControlApi containerControlApi() {
29 | return Mockito.mock(ContainerControlApi.class);
30 | }
31 |
32 | @Bean
33 | public SiteConfigPreferences siteConfigPreferences() {
34 | return Mockito.mock(SiteConfigPreferences.class);
35 | }
36 |
37 | @Bean
38 | public NrgEventServiceI nrgEventService() {
39 | return Mockito.mock(NrgEventServiceI.class);
40 | }
41 |
42 | @Bean
43 | public NrgPreferenceService nrgPreferenceService() {
44 | return Mockito.mock(NrgPreferenceService.class);
45 | }
46 |
47 | @Bean
48 | public SerializerService serializerService() {
49 | return Mockito.mock(SerializerService.class);
50 | }
51 |
52 | @Bean
53 | public PermissionsServiceI permissionsService() {
54 | return Mockito.mock(PermissionsServiceI.class);
55 | }
56 |
57 | @Bean
58 | public CatalogService catalogService() {
59 | return Mockito.mock(CatalogService.class);
60 | }
61 |
62 | @Bean
63 | public ContainerEntityService containerEntityService() {
64 | return new HibernateContainerEntityService();
65 | }
66 |
67 | @Bean
68 | public ContainerEntityRepository containerEntityRepository() {
69 | return new ContainerEntityRepository();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/model/command/auto/PreresolvedInputTreeNode.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.model.command.auto;
2 |
3 | import com.google.auto.value.AutoValue;
4 | import com.google.common.collect.Lists;
5 | import org.nrg.containers.model.command.auto.Command.CommandInput;
6 | import org.nrg.containers.model.command.auto.Command.CommandWrapperDerivedInput;
7 | import org.nrg.containers.model.command.auto.Command.CommandWrapperExternalInput;
8 | import org.nrg.containers.model.command.auto.Command.Input;
9 |
10 | import java.io.Serializable;
11 | import java.util.List;
12 |
13 | @AutoValue
14 | public abstract class PreresolvedInputTreeNode implements Serializable {
15 | private static final long serialVersionUID = -2965708997601698228L;
16 |
17 | public abstract T input();
18 | public abstract List> children();
19 |
20 | public static PreresolvedInputTreeNode create(final CommandWrapperExternalInput externalInput) {
21 | return new AutoValue_PreresolvedInputTreeNode<>(externalInput, Lists.>newArrayList());
22 | }
23 |
24 | public static PreresolvedInputTreeNode create(final CommandWrapperDerivedInput derivedInput,
25 | final PreresolvedInputTreeNode extends Input> parent) {
26 | final PreresolvedInputTreeNode node =
27 | new AutoValue_PreresolvedInputTreeNode<>(derivedInput, Lists.>newArrayList());
28 |
29 | parent.children().add(node);
30 |
31 | return node;
32 | }
33 |
34 | public static PreresolvedInputTreeNode create(final CommandInput commandInput) {
35 | return new AutoValue_PreresolvedInputTreeNode<>(commandInput, Lists.>newArrayList());
36 | }
37 |
38 | public static PreresolvedInputTreeNode create(final CommandInput commandInput,
39 | final PreresolvedInputTreeNode extends Input> parent) {
40 | final PreresolvedInputTreeNode node =
41 | new AutoValue_PreresolvedInputTreeNode<>(commandInput, Lists.>newArrayList());
42 | parent.children().add(node);
43 | return node;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/secrets/SystemPropertySecretSource.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.secrets;
2 |
3 | import com.fasterxml.jackson.annotation.JsonAnyGetter;
4 | import com.fasterxml.jackson.annotation.JsonCreator;
5 | import com.fasterxml.jackson.annotation.JsonIgnore;
6 | import com.fasterxml.jackson.annotation.JsonProperty;
7 | import org.springframework.stereotype.Component;
8 |
9 | import java.util.Collections;
10 | import java.util.Map;
11 | import java.util.Objects;
12 | import java.util.Optional;
13 |
14 | public class SystemPropertySecretSource implements SecretSource.ValueObtainingSecretSource {
15 | @JsonIgnore public static final String JSON_TYPE_NAME = "system-property";
16 |
17 | @JsonProperty("identifier") private final String systemPropertyName;
18 |
19 | @JsonCreator
20 | public SystemPropertySecretSource(@JsonProperty("identifier") final String systemPropertyName) {
21 | this.systemPropertyName = systemPropertyName;
22 | }
23 |
24 | public String systemPropertyName() {
25 | return systemPropertyName;
26 | }
27 |
28 | @Override
29 | @JsonIgnore
30 | public String type() {
31 | return JSON_TYPE_NAME;
32 | }
33 |
34 | @Override
35 | @JsonIgnore
36 | public String identifier() {
37 | return systemPropertyName;
38 | }
39 |
40 | @JsonAnyGetter
41 | @Override
42 | public Map otherProperties() {
43 | return Collections.emptyMap();
44 | }
45 |
46 | @Override
47 | public String toString() {
48 | return "SystemProperty{\"" + systemPropertyName + "\"}";
49 | }
50 |
51 | @Override
52 | public boolean equals(final Object o) {
53 | if (this == o) return true;
54 | if (o == null || getClass() != o.getClass()) return false;
55 | final SystemPropertySecretSource that = (SystemPropertySecretSource) o;
56 | return Objects.equals(systemPropertyName, that.systemPropertyName);
57 | }
58 |
59 | @Override
60 | public int hashCode() {
61 | return Objects.hash(systemPropertyName);
62 | }
63 |
64 | @Component
65 | public static class ValueObtainer extends SecretValueObtainer {
66 | public ValueObtainer() {
67 | super(SystemPropertySecretSource.class);
68 | }
69 |
70 | @Override
71 | public Optional obtainValue(final ValueObtainingSecretSource source) {
72 | return Optional.ofNullable(System.getProperty(source.identifier()));
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/test/resources/ecatHeaderDump/command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ecat-dump",
3 | "description": "Runs ECAT dump tools",
4 | "type": "docker",
5 | "image": "ecat-dump",
6 | "command-line": "lmhdr /input >> /output/ecat-dump.txt && lshdr /input >> /output/ecat-dump.txt",
7 | "mounts": [
8 | {
9 | "name": "ecat-in",
10 | "writable": "false",
11 | "path": "/input"
12 | },
13 | {
14 | "name": "dump-out",
15 | "writable": "true",
16 | "path": "/output"
17 | }
18 | ],
19 | "inputs": [],
20 | "outputs": [
21 | {
22 | "name": "dump-out",
23 | "description": "The ECAT file dump",
24 | "required": true,
25 | "mount": "dump-out",
26 | "path": "ecat-dump.txt"
27 | }
28 | ],
29 | "xnat": [
30 | {
31 | "name": "ecat-dump scan",
32 | "label": "ecat-dump - scan",
33 | "description": "",
34 | "contexts": ["xnat:imageScanData"],
35 | "external-inputs": [
36 | {
37 | "name": "scan",
38 | "description": "Input scan",
39 | "type": "Scan",
40 | "required": true,
41 | "matcher": "'ECAT' in @.resources[*].label"
42 | }
43 | ],
44 | "derived-inputs": [
45 | {
46 | "name": "scan-ecats",
47 | "description": "The scan's ecat resource",
48 | "type": "Resource",
49 | "derived-from-wrapper-input": "scan",
50 | "matcher": "@.label == 'ECAT'"
51 | },
52 | {
53 | "name": "scan-ecat-file",
54 | "description": "The scan's ecat resource's files",
55 | "type": "File[]",
56 | "derived-from-wrapper-input": "scan-ecats",
57 | "matcher": "@.name =~ /.*v/",
58 | "provides-files-for-command-mount": "ecat-in"
59 | }
60 | ],
61 | "output-handlers": [
62 | {
63 | "name": "headerdump-resource",
64 | "accepts-command-output": "dump-out",
65 | "as-a-child-of": "scan",
66 | "type": "Resource",
67 | "label": "HEADERDUMP"
68 | }
69 | ]
70 | }
71 | ]
72 | }
--------------------------------------------------------------------------------
/src/main/java/org/nrg/containers/jms/listeners/ContainerFinalizingRequestListener.java:
--------------------------------------------------------------------------------
1 | package org.nrg.containers.jms.listeners;
2 |
3 | import lombok.extern.slf4j.Slf4j;
4 | import org.nrg.containers.config.ContainersConfig;
5 | import org.nrg.containers.exceptions.ContainerException;
6 | import org.nrg.containers.jms.requests.ContainerFinalizingRequest;
7 | import org.nrg.containers.jms.utils.QueueUtils;
8 | import org.nrg.containers.model.container.auto.Container;
9 | import org.nrg.containers.services.ContainerService;
10 | import org.nrg.framework.exceptions.NotFoundException;
11 | import org.nrg.xdat.security.services.UserManagementServiceI;
12 | import org.nrg.xdat.security.user.exceptions.UserInitException;
13 | import org.nrg.xdat.security.user.exceptions.UserNotFoundException;
14 | import org.nrg.xft.security.UserI;
15 | import org.springframework.beans.factory.annotation.Autowired;
16 | import org.springframework.jms.annotation.JmsListener;
17 | import org.springframework.stereotype.Component;
18 |
19 | @Slf4j
20 | @Component
21 | public class ContainerFinalizingRequestListener {
22 | private final ContainerService containerService;
23 | private final UserManagementServiceI userManagementServiceI;
24 |
25 | @Autowired
26 | public ContainerFinalizingRequestListener(ContainerService containerService,
27 | UserManagementServiceI userManagementServiceI) {
28 | this.containerService = containerService;
29 | this.userManagementServiceI = userManagementServiceI;
30 | }
31 |
32 | @JmsListener(id = ContainersConfig.FINALIZING_QUEUE_CONTAINER_ID,
33 | containerFactory = ContainersConfig.FINALIZING_QUEUE_LISTENER_FACTORY,
34 | destination = ContainerFinalizingRequest.DESTINATION)
35 | public void onRequest(ContainerFinalizingRequest request)
36 | throws UserNotFoundException, NotFoundException, UserInitException, ContainerException {
37 | Container container = containerService.get(request.getId());
38 | UserI user = userManagementServiceI.getUser(request.getUsername());
39 | if (log.isDebugEnabled()) {
40 | log.debug("Consuming finalizing queue: count {}, exitcode {}, is successful {}, id {}, username {}, status {}",
41 | QueueUtils.count(request.getDestination()), request.getExitCodeString(), request.isSuccessful(), request.getId(), request.getUsername(), container.status());
42 | }
43 | containerService.consumeFinalize(request.getExitCodeString(), request.isSuccessful(), container, user);
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/resources/commandResolutionTest/select-command.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "param-test",
3 | "description": "Test resolving params",
4 | "type": "docker",
5 | "image": "busybox:latest",
6 | "command-line": "echo #MULTI_FLAG1# #MULTI_FLAG2# #MULTI_QSPACE# #MULTI_COMMA# #MULTI_SPACE# #MULTI_DEFAULT#",
7 | "inputs": [
8 | {
9 | "name": "MULTI_FLAG1",
10 | "type": "select-many",
11 | "required": true,
12 | "command-line-flag": "--flag",
13 | "command-line-separator": "=",
14 | "multiple-delimiter": "flag",
15 | "default-value": "[\"scan1\",\"scan2\"]",
16 | "select-values": ["scan1","junk","scan2"]
17 | },
18 | {
19 | "name": "MULTI_FLAG2",
20 | "type": "select-many",
21 | "required": true,
22 | "command-line-flag": "--flag",
23 | "multiple-delimiter": "flag",
24 | "default-value": "[\"scan1\",\"scan2\"]",
25 | "select-values": ["scan1","junk","scan2"]
26 | },
27 | {
28 | "name": "MULTI_QSPACE",
29 | "type": "select-many",
30 | "required": true,
31 | "multiple-delimiter": "quoted-space",
32 | "default-value": "[\"scan1\",\"scan2\"]",
33 | "select-values": ["scan1","junk","scan2"]
34 | },
35 | {
36 | "name": "MULTI_COMMA",
37 | "type": "select-many",
38 | "required": true,
39 | "multiple-delimiter": "comma",
40 | "default-value": "[\"scan1\",\"scan2\"]",
41 | "select-values": ["scan1","junk","scan2"]
42 | },
43 | {
44 | "name": "MULTI_SPACE",
45 | "type": "select-many",
46 | "required": true,
47 | "multiple-delimiter": "space",
48 | "default-value": "[\"scan1\",\"scan2\"]",
49 | "select-values": ["scan1","junk","scan2"]
50 | },
51 | {
52 | "name": "MULTI_DEFAULT",
53 | "type": "select-many",
54 | "required": true,
55 | "default-value": "scan1",
56 | "select-values": ["scan1","junk","scan2"]
57 | }
58 | ],
59 | "xnat": [
60 | {
61 | "name": "multiple",
62 | "label": "Dummy: Session multiple scan",
63 | "description": "run the dummy command with a session with multiple scans",
64 | "external-inputs": [],
65 | "derived-inputs": [],
66 | "output-handlers": []
67 | }
68 | ]
69 | }
70 |
--------------------------------------------------------------------------------