Skip to content

Commit

Permalink
PLAT-1242 - Add DataStudio start command.
Browse files Browse the repository at this point in the history
  • Loading branch information
georgi-seqera committed Jan 23, 2025
1 parent bcb3d9d commit e5dc023
Show file tree
Hide file tree
Showing 13 changed files with 980 additions and 311 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ dependencies {
implementation 'org.slf4j:slf4j-api:1.7.36'
implementation 'ch.qos.logback:logback-core:1.2.11'
implementation 'ch.qos.logback:logback-classic:1.2.11'
implementation 'io.seqera.tower:tower-java-sdk:1.9.9'
implementation 'io.seqera.tower:tower-java-sdk:1.9.10'
implementation 'info.picocli:picocli:4.6.3'
implementation 'org.apache.commons:commons-compress:1.22'
implementation 'org.tukaani:xz:1.9'
Expand Down
35 changes: 34 additions & 1 deletion conf/reflect-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,12 @@
"allDeclaredFields":true,
"queryAllDeclaredMethods":true
},
{
"name":"io.seqera.tower.cli.commands.datastudios.DataStudioConfigurationOptions",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.datastudios.DataStudioRefOptions",
"allDeclaredFields":true,
Expand All @@ -1142,6 +1148,12 @@
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.datastudios.StartCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.datastudios.ViewCmd",
"allDeclaredFields":true,
Expand Down Expand Up @@ -1835,6 +1847,12 @@
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.datastudios.DataStudioStartSubmitted",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.datastudios.DataStudiosList",
"allDeclaredFields":true,
Expand Down Expand Up @@ -2639,7 +2657,8 @@
"name":"io.seqera.tower.model.DataStudioDtoParentCheckpoint",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getCheckpointId","parameterTypes":[] }, {"name":"getCheckpointName","parameterTypes":[] }, {"name":"getSessionId","parameterTypes":[] }, {"name":"getStudioName","parameterTypes":[] }, {"name":"setCheckpointId","parameterTypes":["java.lang.Long"] }, {"name":"setCheckpointName","parameterTypes":["java.lang.String"] }, {"name":"setSessionId","parameterTypes":["java.lang.String"] }, {"name":"setStudioName","parameterTypes":["java.lang.String"] }]
},
{
"name":"io.seqera.tower.model.DataStudioListResponse",
Expand All @@ -2661,6 +2680,20 @@
"queryAllDeclaredMethods":true,
"methods":[{"name":"fromValue","parameterTypes":["java.lang.String"] }, {"name":"getValue","parameterTypes":[] }, {"name":"setValue","parameterTypes":["java.lang.String"] }]
},
{
"name":"io.seqera.tower.model.DataStudioStartRequest",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"getConfiguration_JsonNullable","parameterTypes":[] }, {"name":"getDescription_JsonNullable","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.model.DataStudioStartResponse",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setJobSubmitted","parameterTypes":["java.lang.Boolean"] }, {"name":"setSessionId","parameterTypes":["java.lang.String"] }, {"name":"setStatusInfo","parameterTypes":["io.seqera.tower.model.DataStudioStatusInfo"] }]
},
{
"name":"io.seqera.tower.model.DataStudioStatus",
"allDeclaredFields":true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package io.seqera.tower.cli.commands;

import io.seqera.tower.cli.commands.datastudios.ListCmd;
import io.seqera.tower.cli.commands.datastudios.StartCmd;
import io.seqera.tower.cli.commands.datastudios.ViewCmd;
import picocli.CommandLine;

Expand All @@ -27,6 +28,7 @@
subcommands = {
ViewCmd.class,
ListCmd.class,
StartCmd.class,
}
)
public class DataStudiosCmd extends AbstractRootCmd {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,56 @@

package io.seqera.tower.cli.commands.datastudios;

import java.util.Optional;
import java.util.function.Supplier;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.AbstractApiCmd;
import io.seqera.tower.model.DataStudioDto;
import io.seqera.tower.model.DataStudioProgressStep;

import static io.seqera.tower.model.DataStudioProgressStepStatus.ERRORED;
import static io.seqera.tower.model.DataStudioProgressStepStatus.IN_PROGRESS;

public class AbstractStudiosCmd extends AbstractApiCmd {

protected DataStudioDto fetchDataStudio(DataStudioRefOptions dataStudioRefOptions, Long wspId) throws ApiException {
return api().describeDataStudio(dataStudioRefOptions.dataStudio.sessionId, wspId);
}

public class ProgressStepMessageSupplier implements Supplier<String> {

private final String sessionId;
private final Long workspaceId;
private DataStudioProgressStep currentProgressStep;

public ProgressStepMessageSupplier(String sessionId, Long workspaceId) {
this.sessionId = sessionId;
this.workspaceId = workspaceId;
this.currentProgressStep = new DataStudioProgressStep();
}

@Override
public String get() {

try {
DataStudioDto dataStudioDto = api().describeDataStudio(sessionId, workspaceId);

Optional<DataStudioProgressStep> inProgressStep = dataStudioDto.getProgress().stream()
.filter(step -> step.getStatus() == IN_PROGRESS || step.getStatus() == ERRORED)
.findFirst();

if (inProgressStep.isPresent() && !inProgressStep.get().equals(currentProgressStep)) {
currentProgressStep = inProgressStep.get();
return currentProgressStep.getStatus() != ERRORED
? String.format("\n %s", currentProgressStep.getMessage())
: String.format("\n %s - Error encountered: %s", currentProgressStep.getMessage(), dataStudioDto.getStatusInfo().getMessage());
}

return null;
} catch (Exception e) {
return null;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2021-2023, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package io.seqera.tower.cli.commands.datastudios;

import java.util.List;

import picocli.CommandLine;

public class DataStudioConfigurationOptions {

@CommandLine.Option(names = {"-g", "--gpu"}, description = "Optional configuration override for 'gpu' setting (Integer representing number of cores)")
public Integer gpu;

@CommandLine.Option(names = {"-c", "--cpu"}, description = "Optional configuration override for 'cpu' setting (Integer representing number of cores)")
public Integer cpu;

@CommandLine.Option(names = {"-m", "--memory"}, description = "Optional configuration override for 'memory' setting (Integer representing memory in MBs)")
public Integer memory;

@CommandLine.Option(names = {"--mount-data"}, description = "Optional configuration override for 'mountData' setting (comma separate list of datalinkIds)", split = ",")
public List<String> mountData;

@CommandLine.Option(names = {"--conda-env"}, description = "Optional configuration override for 'condaEnvironment' setting (YAML conda packages configurations)")
public String condaEnvironment;

}
155 changes: 155 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/datastudios/StartCmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2021-2023, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package io.seqera.tower.cli.commands.datastudios;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.enums.OutputType;
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
import io.seqera.tower.cli.exceptions.DataStudioNotFoundException;
import io.seqera.tower.cli.exceptions.TowerException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.datastudios.DataStudioStartSubmitted;
import io.seqera.tower.model.DataStudioConfiguration;
import io.seqera.tower.model.DataStudioDto;
import io.seqera.tower.model.DataStudioStartRequest;
import io.seqera.tower.model.DataStudioStartResponse;
import io.seqera.tower.model.DataStudioStatus;
import picocli.CommandLine;

import static io.seqera.tower.cli.utils.ResponseHelper.waitStatus;
import static java.lang.Boolean.FALSE;

@CommandLine.Command(
name = "start",
description = "Start a data studio."
)
public class StartCmd extends AbstractStudiosCmd {

@CommandLine.Mixin
public WorkspaceOptionalOptions workspace;

@CommandLine.Mixin
public DataStudioRefOptions dataStudioRefOptions;

@CommandLine.Mixin
public DataStudioConfigurationOptions dataStudioConfigOptions;

@CommandLine.Option(names = {"--wait"}, description = "Wait until given status or fail. Valid options: ${COMPLETION-CANDIDATES}.")
public DataStudioStatus wait;

@CommandLine.Option(names = {"--description"}, description = "Optional configuration override for 'description'.")
public String description;

@Override
protected Response exec() throws ApiException {
Long wspId = workspaceId(workspace.workspace);

try {
DataStudioDto dataStudioDto = fetchDataStudio(dataStudioRefOptions, wspId);

DataStudioStartRequest request = getStartRequestWithOverridesApplied(dataStudioDto);

DataStudioStartResponse response = api().startDataStudio(dataStudioRefOptions.dataStudio.sessionId, request, wspId);

return new DataStudioStartSubmitted(dataStudioRefOptions.dataStudio.sessionId, wspId, workspaceRef(wspId), dataStudioDto.getStudioUrl(), response.getJobSubmitted());
} catch (ApiException e) {
if (e.getCode() == 404) {
throw new DataStudioNotFoundException(dataStudioRefOptions.dataStudio.sessionId, wspId);
}
if (e.getCode() == 403) {
throw new TowerException(String.format("User not entitled to view studio '%s' at %s workspace", dataStudioRefOptions.dataStudio.sessionId, wspId));
}
throw e;
}
}

@Override
protected Integer onBeforeExit(int exitCode, Response response) {

if (exitCode != 0 || wait == null || response == null) {
return exitCode;
}

DataStudioStartSubmitted submitted = (DataStudioStartSubmitted) response;

// If response declares job failed to submit, don't wait and exit early.
if (FALSE.equals(submitted.jobSubmitted)) {
return exitCode;
}

boolean showProgress = app().output != OutputType.json;

try {
return waitStatus(
app().getOut(),
showProgress,
new ProgressStepMessageSupplier(submitted.sessionId, submitted.workspaceId),
wait,
DataStudioStatus.values(),
() -> checkDataStudioStatus(submitted.sessionId, submitted.workspaceId),
DataStudioStatus.STOPPED, DataStudioStatus.ERRORED, DataStudioStatus.RUNNING
);
} catch (InterruptedException e) {
return exitCode;
}
}

private DataStudioStatus checkDataStudioStatus(String sessionId, Long workspaceId) {
try {
return api().describeDataStudio(sessionId, workspaceId).getStatusInfo().getStatus();
} catch (ApiException | NullPointerException e) {
return null;
}
}

private DataStudioStartRequest getStartRequestWithOverridesApplied(DataStudioDto dataStudioDto) {
DataStudioConfiguration dataStudioConfiguration = dataStudioDto.getConfiguration() == null
? new DataStudioConfiguration()
: dataStudioDto.getConfiguration();

dataStudioConfiguration.setGpu(dataStudioConfigOptions.gpu == null
? dataStudioConfiguration.getGpu()
: dataStudioConfigOptions.gpu);
dataStudioConfiguration.setCpu(dataStudioConfigOptions.cpu == null
? dataStudioConfiguration.getCpu()
: dataStudioConfigOptions.cpu);
dataStudioConfiguration.setMemory(dataStudioConfigOptions.memory == null
? dataStudioConfiguration.getMemory()
: dataStudioConfigOptions.memory);
dataStudioConfiguration.setMountData(dataStudioConfigOptions.mountData == null
? dataStudioConfiguration.getMountData()
: dataStudioConfigOptions.mountData);
dataStudioConfiguration.setCondaEnvironment(dataStudioConfigOptions.condaEnvironment == null
? dataStudioConfiguration.getCondaEnvironment()
: dataStudioConfigOptions.condaEnvironment);

String appliedDescription = description == null
? dataStudioDto.getDescription()
: description;

DataStudioStartRequest request = new DataStudioStartRequest();

request.setConfiguration(dataStudioConfiguration);
request.setDescription(appliedDescription);

return request;
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2021-2023, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package io.seqera.tower.cli.responses.datastudios;

import io.seqera.tower.cli.responses.Response;

public class DataStudioStartSubmitted extends Response {

public final String sessionId;

public final String studioUrl;

public final Long workspaceId;
public final String workspaceRef;
public final Boolean jobSubmitted;

public DataStudioStartSubmitted(String sessionId, Long workspaceId, String workspaceRef, String studioUrl, Boolean jobSubmitted) {
this.sessionId = sessionId;
this.workspaceId = workspaceId;
this.workspaceRef = workspaceRef;
this.studioUrl = studioUrl;
this.jobSubmitted = jobSubmitted;
}

@Override
public String toString() {
String isSuccess = jobSubmitted ? "successfully submitted" : "failed to submit";
return ansi(String.format("%n @|yellow Data Studio %s START %s at %s workspace.|@%n%n @|bold %s|@%n", sessionId, isSuccess, workspaceRef, studioUrl));
}

}
Loading

0 comments on commit e5dc023

Please sign in to comment.