716 lines
22 KiB
Groovy
716 lines
22 KiB
Groovy
/*
|
|
* Copyright 2013-2026, Seqera Labs
|
|
*
|
|
* 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 recreationaltech.plugin
|
|
|
|
import groovy.transform.MapConstructor
|
|
|
|
import java.lang.reflect.Field
|
|
import java.nio.file.NoSuchFileException
|
|
import java.nio.file.Path
|
|
import java.nio.file.Paths
|
|
|
|
import com.beust.jcommander.DynamicParameter
|
|
import com.beust.jcommander.Parameter
|
|
import com.google.common.hash.Hashing
|
|
import groovy.util.logging.Slf4j
|
|
import nextflow.cli.CmdKubeRun
|
|
import nextflow.cli.CmdRun
|
|
import nextflow.config.ConfigBuilder
|
|
import nextflow.exception.AbortOperationException
|
|
import nextflow.file.FileHelper
|
|
import recreationaltech.plugin.client.K8sClient
|
|
import recreationaltech.plugin.client.K8sResponseException
|
|
import recreationaltech.plugin.model.PodEnv
|
|
import recreationaltech.plugin.model.PodMountConfig
|
|
import recreationaltech.plugin.model.PodSpecBuilder
|
|
import recreationaltech.plugin.model.ResourceType
|
|
import nextflow.scm.AssetManager
|
|
import nextflow.scm.ProviderConfig
|
|
import nextflow.util.ConfigHelper
|
|
import nextflow.util.Escape
|
|
import org.codehaus.groovy.runtime.MethodClosure
|
|
/**
|
|
* Configure and submit the execution of pod running the Nextflow main application
|
|
*
|
|
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
|
|
*/
|
|
@Slf4j
|
|
@MapConstructor(includeFields = true)
|
|
class K8sDriverLauncher {
|
|
|
|
/**
|
|
* Either a Pod or Job
|
|
*/
|
|
private ResourceType resourceType = ResourceType.Pod
|
|
|
|
/**
|
|
* Container image to be used for the Nextflow driver pod
|
|
*/
|
|
private String headImage
|
|
|
|
/**
|
|
* Request CPUs to be used for the Nextflow driver pod
|
|
*/
|
|
private int headCpus
|
|
|
|
/**
|
|
* Request memory to be used for the Nextflow driver pod
|
|
*/
|
|
private String headMemory
|
|
|
|
/**
|
|
* Pre-script to run before nextflow
|
|
*/
|
|
private String headPreScript
|
|
|
|
/**
|
|
* Nextflow execution run name
|
|
*/
|
|
private String runName
|
|
|
|
/**
|
|
* Workflow project to launch
|
|
*/
|
|
private String pipelineName
|
|
|
|
/**
|
|
* Command run options
|
|
*/
|
|
private CmdKubeRun cmd
|
|
|
|
/**
|
|
* Kubernetes client
|
|
*/
|
|
private K8sClient k8sClient
|
|
|
|
/**
|
|
* Nextflow resolved config object
|
|
*/
|
|
private ConfigObject config
|
|
|
|
/**
|
|
* Name of the config map used to propagate the nextflow
|
|
* setting in the container
|
|
*/
|
|
private String configMapName
|
|
|
|
/**
|
|
* Kubernetes specific config settings
|
|
*/
|
|
private K8sConfig k8sConfig
|
|
|
|
private String paramsFile
|
|
|
|
private boolean interactive
|
|
|
|
/**
|
|
* Runs in background mode
|
|
*/
|
|
private boolean background
|
|
|
|
/**
|
|
* Workflow script positional parameters
|
|
*/
|
|
private List<String> args
|
|
|
|
/**
|
|
* Plugins to run the workflow
|
|
*/
|
|
private String plugins
|
|
|
|
/**
|
|
* Launcher entry point. Set-up the environment and create a pod that run the Nextflow
|
|
* application (which in turns executed each task as a pod)
|
|
*
|
|
* @param name Workflow project entry name
|
|
* @param args Workflow script positional parameters
|
|
*/
|
|
void run(String name, List<String> args) {
|
|
this.args = args
|
|
this.pipelineName = name
|
|
this.interactive = name == 'login'
|
|
if( background && interactive )
|
|
throw new AbortOperationException("Option -bg conflicts with interactive mode")
|
|
this.config = makeConfig(pipelineName)
|
|
this.k8sConfig = makeK8sConfig(config.toMap())
|
|
this.k8sClient = makeK8sClient(k8sConfig)
|
|
this.k8sConfig.checkStorageAndPaths(k8sClient)
|
|
this.initDeployer = new K8sNodeInitDeployer(k8sClient, k8sConfig)
|
|
|
|
createK8sConfigMap()
|
|
|
|
createK8sLauncherPod()
|
|
waitPodStart()
|
|
// login into container session
|
|
if( interactive )
|
|
launchLogin()
|
|
// dump pod output
|
|
else if( !background )
|
|
printK8sPodOutput()
|
|
else
|
|
log.debug "Nextflow driver launched in background mode -- pod: $runName"
|
|
waitPodEnd()
|
|
}
|
|
|
|
int shutdown() {
|
|
if( background )
|
|
return 0
|
|
// fetch the container exit status
|
|
final exitCode = waitPodTermination()
|
|
// cleanup the config map if OK
|
|
def deleteOnSuccessByDefault = exitCode==0
|
|
if( k8sConfig.getCleanup(deleteOnSuccessByDefault) ) {
|
|
deleteConfigMap()
|
|
}
|
|
// cleanup pre-workflow pods
|
|
initDeployer.cleanup()
|
|
|
|
return exitCode
|
|
}
|
|
|
|
protected void waitPodEnd() {
|
|
if( background )
|
|
return
|
|
final currentState = k8sConfig.useJobResource() ? k8sClient.jobState(runName) : k8sClient.podState(runName)
|
|
if (currentState && currentState?.running instanceof Map) {
|
|
final name = runName
|
|
println "${resourceType} running: $name ... waiting for ${resourceType.lower()} to stop running"
|
|
try {
|
|
while( true ) {
|
|
sleep 10000
|
|
final state = k8sConfig.useJobResource() ? k8sClient.jobState(name) : k8sClient.podState(name)
|
|
if ( state && !(state?.running instanceof Map) ) {
|
|
println "${resourceType} $name has changed from running state $state"
|
|
break
|
|
}
|
|
}
|
|
}
|
|
catch( Exception e ) {
|
|
log.warn "Caught exception while waiting for ${resourceType.lower()} to stop running"
|
|
}
|
|
}
|
|
}
|
|
|
|
protected boolean isWaitTimedOut(long time) {
|
|
System.currentTimeMillis()-time > 90_000
|
|
}
|
|
|
|
protected int waitPodTermination() {
|
|
log.debug "Wait for ${resourceType.lower()} termination name=$runName"
|
|
final rnd = new Random()
|
|
final time = System.currentTimeMillis()
|
|
Map state = null
|
|
try {
|
|
while( true ) {
|
|
sleep rnd.nextInt(500)
|
|
state = k8sConfig.useJobResource() ? k8sClient.jobState(runName) : k8sClient.podState(runName)
|
|
if( state?.terminated instanceof Map )
|
|
return state.terminated.exitCode as int
|
|
|
|
else if( isWaitTimedOut(time) )
|
|
throw new IllegalStateException("Timeout waiting for ${resourceType.lower()} terminated state=$state")
|
|
}
|
|
}
|
|
catch( Exception e ) {
|
|
log.warn "Unable to fetch ${resourceType.lower()} exit status -- ${resourceType.lower()}=$runName state=$state"
|
|
return 127
|
|
}
|
|
}
|
|
|
|
protected void deleteConfigMap() {
|
|
try {
|
|
k8sClient.configDelete(configMapName)
|
|
log.debug "Deleted K8s configMap with name: $configMapName"
|
|
}
|
|
catch ( Exception e ) {
|
|
log.warn "Unable to delete configMap: $configMapName", e
|
|
}
|
|
}
|
|
|
|
protected void waitPodStart() {
|
|
final name = runName
|
|
print "${resourceType} submitted: $name .. waiting to start"
|
|
while( true ) {
|
|
sleep 1000
|
|
final state = k8sConfig.useJobResource() ? k8sClient.jobState(name) : k8sClient.podState(name)
|
|
if( state && !state.containsKey('waiting') ) {
|
|
break
|
|
}
|
|
}
|
|
print "\33[2K\r"
|
|
println "${resourceType} started: $name"
|
|
}
|
|
|
|
/**
|
|
* Wait for the driver pod creation and prints the log to the
|
|
* console standard output
|
|
*/
|
|
protected void printK8sPodOutput() {
|
|
if ( k8sConfig.useJobResource() )
|
|
k8sClient.jobLog(runName, follow:true).eachLine { println it }
|
|
else
|
|
k8sClient.podLog(runName, follow:true).eachLine { println it }
|
|
}
|
|
|
|
protected ConfigObject loadConfig( String pipelineName ) {
|
|
|
|
// -- load local config if available
|
|
final builder = new ConfigBuilder()
|
|
.setShowClosures(true)
|
|
.setOptions(cmd.launcher.options)
|
|
.setProfile(cmd.profile)
|
|
.setCmdRun(cmd)
|
|
|
|
if( !interactive && !pipelineName.startsWith('/') && !cmd.remoteProfile && !cmd.runRemoteConfig ) {
|
|
// -- check and parse project remote config
|
|
final pipelineConfig = new AssetManager(pipelineName, cmd.revision, cmd.mainScript, cmd).getConfigFile()
|
|
builder.setUserConfigFiles(pipelineConfig)
|
|
}
|
|
|
|
return builder.buildConfigObject()
|
|
}
|
|
|
|
protected K8sConfig makeK8sConfig(Map config) {
|
|
config.k8s instanceof Map ? new K8sConfig(config.k8s as Map) : new K8sConfig()
|
|
}
|
|
|
|
protected makeK8sClient( K8sConfig k8sConfig ) {
|
|
new K8sClient(k8sConfig.getClient())
|
|
}
|
|
|
|
/**
|
|
* Retrieve the workflow configuration and merge with the current local one.
|
|
*
|
|
* @param pipelineName Workflow project name
|
|
* @return A {@link Map} modeling the execution configuration settings
|
|
*/
|
|
protected ConfigObject makeConfig(String pipelineName) {
|
|
|
|
def file = new File(pipelineName)
|
|
if( !interactive && file.exists() ) {
|
|
def message = "The k8s executor cannot run local ${file.directory ? 'project' : 'script'}: $pipelineName"
|
|
message += " -- provide the absolute path of a project available in the Kubernetes cluster or the URL of a project hosted in a Git repository"
|
|
throw new AbortOperationException(message)
|
|
}
|
|
|
|
def config = loadConfig(pipelineName)
|
|
|
|
// normalize pod entries
|
|
def k8s = config.k8s
|
|
|
|
if( !k8s.isSet('pod') )
|
|
k8s.pod = []
|
|
else if( k8s.pod instanceof Map ) {
|
|
k8s.pod = [ k8s.pod ]
|
|
}
|
|
else if( !(k8s.pod instanceof List) )
|
|
throw new IllegalArgumentException("Illegal k8s.pod configuratun value: ${k8s.pod}")
|
|
|
|
// -- use the volume claims specified in the command line
|
|
// to populate the pod config
|
|
for( int i=0; i<cmd.volMounts?.size(); i++ ){
|
|
def entry = cmd.volMounts.get(i)
|
|
def parts = entry.tokenize(':')
|
|
def name = parts[0]
|
|
def path = parts[1]
|
|
if( i==0 ) {
|
|
k8s.storageClaimName = name
|
|
k8s.storageMountPath = path
|
|
}
|
|
else {
|
|
k8s.pod.add( [volumeClaim: name, mountPath: path] )
|
|
}
|
|
}
|
|
|
|
// -- backward compatibility
|
|
if( k8s.isSet('volumeClaims') ) {
|
|
log.warn "Config setting k8s.volumeClaims has been deprecated -- Use k8s.storageClaimName and k8s.storageMountPath instead"
|
|
k8s.volumeClaims.each { k,v ->
|
|
def name = k as String
|
|
def path = v instanceof Map ? v.mountPath : v.toString()
|
|
if( !k8s.isSet('storageClaimName') ) {
|
|
k8s.storageClaimName = name
|
|
k8s.storageMountPath = path
|
|
}
|
|
else if( !cmd.volMounts ) {
|
|
k8s.pod.add( [volumeClaim: name, mountPath: path] )
|
|
}
|
|
}
|
|
// remove it
|
|
k8s.remove('volumeClaims')
|
|
}
|
|
|
|
// -- set k8s executor
|
|
config.process.executor = 'k8s'
|
|
|
|
// -- strip default work dir
|
|
if( config.workDir == 'work' )
|
|
config.remove('workDir')
|
|
|
|
// -- check work dir
|
|
if( cmd?.workDir )
|
|
k8s.workDir = cmd.workDir
|
|
else if( !k8s.isSet('workDir') && config.workDir )
|
|
k8s.workDir = config.workDir
|
|
|
|
if ( plugins ) {
|
|
LinkedList<String> plugins = config.plugins ?: []
|
|
plugins.addAll( this.plugins.tokenize(',') )
|
|
config.plugins = plugins
|
|
}
|
|
|
|
// -- some cleanup
|
|
if( !k8s.pod )
|
|
k8s.remove('pod')
|
|
|
|
if( !k8s.storageClaimName )
|
|
k8s.remove('storageClaimName')
|
|
if( !k8s.storageMountPath )
|
|
k8s.remove('storageMountPath')
|
|
|
|
if( !config.libDir )
|
|
config.remove('libDir')
|
|
|
|
log.trace "K8s config object:\n${ConfigHelper.toCanonicalString(config).indent(' ')}"
|
|
return config
|
|
}
|
|
|
|
|
|
private Field getField(CmdRun cmd, String name) {
|
|
def clazz = cmd.class
|
|
while( clazz != CmdRun ) {
|
|
clazz = cmd.class.getSuperclass()
|
|
}
|
|
clazz.getDeclaredField(name)
|
|
}
|
|
|
|
private void checkUnsupportedOption(String name) {
|
|
def field = getField(cmd,name)
|
|
if( !field ) {
|
|
log.warn "Unknown command-line option to check: $name"
|
|
return
|
|
}
|
|
field.setAccessible(true)
|
|
if( field.get(cmd) ) {
|
|
def param = field.getAnnotation(Parameter)
|
|
def opt = param.names() ? param.names()[0] : "-$name"
|
|
abort(opt)
|
|
}
|
|
}
|
|
|
|
private void abort(String opt) {
|
|
throw new AbortOperationException("Option `$opt` not supported with Kubernetes deployment")
|
|
}
|
|
|
|
private void unsupportedCliOptions(MethodClosure... fields) {
|
|
unsupportedCliOptions( fields.collect { it.getMethod()} )
|
|
}
|
|
|
|
private void unsupportedCliOptions(List<String> names) {
|
|
for( String x : names ) {
|
|
checkUnsupportedOption(x)
|
|
}
|
|
}
|
|
|
|
private void addOption(List result, MethodClosure m, Closure eval=null) {
|
|
def name = m.getMethod()
|
|
def field = getField(cmd,name)
|
|
field.setAccessible(true)
|
|
def val = field.get(cmd)
|
|
if( ( eval ? eval(val) : val ) ) {
|
|
def param = field.getAnnotation(Parameter)
|
|
if( param ) {
|
|
result << "${param.names()[0]} ${Escape.wildcards(String.valueOf(val))}"
|
|
return
|
|
}
|
|
|
|
param = field.getAnnotation(DynamicParameter)
|
|
if( param && val instanceof Map ) {
|
|
val.each { k,v ->
|
|
result << "${param.names()[0]}$k ${Escape.wildcards(String.valueOf(v))}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return The nextflow driver command line
|
|
*/
|
|
protected String getLaunchCli() {
|
|
assert cmd
|
|
assert pipelineName
|
|
|
|
if( interactive ) {
|
|
return "tail -f /dev/null"
|
|
}
|
|
|
|
def result = []
|
|
// -- configure NF command line
|
|
result << "nextflow"
|
|
|
|
if( cmd.launcher.options.trace )
|
|
result << "-trace ${cmd.launcher.options.trace.join(',')}"
|
|
if( cmd.launcher.options.debug )
|
|
result << "-debug ${cmd.launcher.options.debug.join(',')}"
|
|
if( cmd.launcher.options.jvmOpts )
|
|
cmd.launcher.options.jvmOpts.each { k,v -> result << "-D$k=$v" }
|
|
|
|
result << "run"
|
|
result << pipelineName
|
|
|
|
if( runName )
|
|
result << '-name' << runName
|
|
|
|
addOption(result, cmd.&cacheable, { it==false } )
|
|
addOption(result, cmd.&resume )
|
|
addOption(result, cmd.&poolSize )
|
|
addOption(result, cmd.&pollInterval )
|
|
addOption(result, cmd.&queueSize)
|
|
addOption(result, cmd.&revision )
|
|
addOption(result, cmd.&latest )
|
|
addOption(result, cmd.&withTrace )
|
|
addOption(result, cmd.&withTimeline )
|
|
addOption(result, cmd.&withDag )
|
|
addOption(result, cmd.&dumpHashes )
|
|
addOption(result, cmd.&dumpChannels )
|
|
addOption(result, cmd.&env )
|
|
addOption(result, cmd.&process )
|
|
addOption(result, cmd.¶ms )
|
|
addOption(result, cmd.&entryName )
|
|
|
|
if( paramsFile ) {
|
|
result << "-params-file $paramsFile"
|
|
}
|
|
|
|
if ( cmd.runRemoteConfig )
|
|
cmd.runRemoteConfig.forEach { result << "-config $it" }
|
|
|
|
if ( cmd.remoteProfile )
|
|
result << "-profile ${cmd.remoteProfile}"
|
|
|
|
if( cmd.process?.executor )
|
|
abort('process.executor')
|
|
|
|
unsupportedCliOptions(
|
|
cmd.&libPath,
|
|
cmd.&test,
|
|
cmd.&executorOptions,
|
|
cmd.&stdin,
|
|
cmd.&withSingularity,
|
|
cmd.&withApptainer,
|
|
cmd.&withDocker,
|
|
cmd.&withoutDocker,
|
|
cmd.&withMpi,
|
|
cmd.&clusterOptions,
|
|
cmd.&exportSysEnv
|
|
)
|
|
|
|
if( args )
|
|
result.add(args)
|
|
|
|
return result.join(' ')
|
|
}
|
|
|
|
/**
|
|
* @return A {@link Map} modeling driver pod specification
|
|
*/
|
|
protected Map makeLauncherSpec() {
|
|
assert runName
|
|
assert k8sClient
|
|
|
|
// -- setup config file
|
|
String cmd = "source /etc/nextflow/init.sh; ${getLaunchCli()}" +
|
|
"; if [ -x /etc/nextflow/node-init-cleanup.sh ]; then /etc/nextflow/node-init-cleanup.sh || true; fi; " +
|
|
"exit \$status"
|
|
|
|
// create the launcher pod
|
|
PodSpecBuilder builder = new PodSpecBuilder()
|
|
.withPodName(runName)
|
|
.withImageName(headImage ?: k8sConfig.getNextflowImageName())
|
|
.withCommand(['/bin/bash', '-c', cmd])
|
|
.withLabels([ app: 'nextflow', runName: runName ])
|
|
.withNamespace(k8sClient.config.namespace)
|
|
.withServiceAccount(k8sClient.config.serviceAccount)
|
|
.withPodOptions(k8sConfig.getPodOptions())
|
|
.withEnv( PodEnv.value('NXF_WORK', k8sConfig.getWorkDir()) )
|
|
.withEnv( PodEnv.value('NXF_ASSETS', k8sConfig.getProjectDir()) )
|
|
.withEnv( PodEnv.value('NXF_EXECUTOR', 'k8s'))
|
|
.withEnv( PodEnv.value('NXF_ANSI_LOG', 'false'))
|
|
.withMemory(headMemory?:"")
|
|
.withCpus(headCpus)
|
|
.withCpuLimits(k8sConfig.cpuLimitsEnabled())
|
|
|
|
if ( k8sConfig.useJobResource()) {
|
|
this.resourceType = ResourceType.Job
|
|
return builder.buildAsJob()
|
|
}
|
|
else {
|
|
return builder.build()
|
|
}
|
|
|
|
// note: do *not* set the work directory because it may need to be created by the init script
|
|
}
|
|
|
|
/**
|
|
* Creates and executes the nextflow driver pod
|
|
* @return A {@link recreationaltech.plugin.client.K8sResponseJson} response object
|
|
*/
|
|
protected createK8sLauncherPod() {
|
|
final spec = makeLauncherSpec()
|
|
if ( k8sConfig.useJobResource() ) {
|
|
k8sClient.jobCreate(spec, yamlDebugPath())
|
|
} else {
|
|
k8sClient.podCreate(spec, yamlDebugPath())
|
|
}
|
|
}
|
|
|
|
protected Path yamlDebugPath() {
|
|
boolean debug = config.k8s.debug?.yaml?.toString() == 'true'
|
|
final result = debug ? Paths.get(".nextflow.${resourceType.lower()}.yaml") : null
|
|
if( result )
|
|
log.info "Launcher ${resourceType.lower()} spec file: $result"
|
|
return result
|
|
}
|
|
|
|
protected Path getScmFile() {
|
|
ProviderConfig.getScmConfigPath()
|
|
}
|
|
|
|
String getPodImage() {
|
|
return podImage
|
|
}
|
|
|
|
int getHeadCpus() {
|
|
return headCpus
|
|
}
|
|
|
|
String getHeadMemory() {
|
|
return headMemory
|
|
}
|
|
|
|
String getRunName() {
|
|
return runName
|
|
}
|
|
|
|
CmdKubeRun getCmd() {
|
|
return cmd
|
|
}
|
|
|
|
protected String getPipelineName() {
|
|
return pipelineName
|
|
}
|
|
|
|
protected boolean getInteractive() {
|
|
return interactive
|
|
}
|
|
|
|
protected ConfigObject getConfig() {
|
|
return config
|
|
}
|
|
|
|
protected K8sConfig getK8sConfig() {
|
|
return k8sConfig
|
|
}
|
|
|
|
protected K8sClient getK8sClient() {
|
|
return k8sClient
|
|
}
|
|
|
|
/**
|
|
* Creates a K8s ConfigMap to share the nextflow configuration in the K8s cluster
|
|
*/
|
|
protected void createK8sConfigMap() {
|
|
Map<String,String> configMap = [:]
|
|
|
|
final launchDir = k8sConfig.getLaunchDir()
|
|
// init file
|
|
String initScript = ''
|
|
initScript += "mkdir -p '$launchDir'; if [ -d '$launchDir' ]; then cd '$launchDir'; else echo 'Cannot create directory: $launchDir'; exit 1; fi; "
|
|
initScript += '[ -f /etc/nextflow/scm ] && ln -s /etc/nextflow/scm $NXF_HOME/scm; '
|
|
initScript += '[ -f /etc/nextflow/nextflow.config ] && cp /etc/nextflow/nextflow.config $PWD/nextflow.config; '
|
|
if( headPreScript )
|
|
initScript += "[ -f '$headPreScript' ] && '$headPreScript'; "
|
|
configMap['init.sh'] = initScript
|
|
|
|
// nextflow config file
|
|
if( this.config ) {
|
|
configMap['nextflow.config'] = ConfigHelper.toCanonicalString( this.config )
|
|
}
|
|
|
|
// scm config file
|
|
final scmFile = getScmFile()
|
|
if( scmFile.exists() ) {
|
|
configMap['scm'] = scmFile.text
|
|
}
|
|
|
|
// params file
|
|
if( cmd.paramsFile ) {
|
|
final file = FileHelper.asPath(cmd.paramsFile)
|
|
if( !file.exists() ) throw new NoSuchFileException("Params file does not exist: $file")
|
|
configMap[ file.getName() ] = file.text
|
|
paramsFile = "/etc/nextflow/$file.name"
|
|
}
|
|
|
|
// pre-workflow pod cleanup
|
|
if ( background )
|
|
configMap['node-init-cleanup.sh'] = initDeployer.buildCleanupScript()
|
|
|
|
// create the config map
|
|
configMapName = makeConfigMapName(configMap)
|
|
tryCreateConfigMap(configMapName, configMap)
|
|
log.debug "Created K8s configMap with name: $configMapName"
|
|
k8sConfig.getPodOptions().getMountConfigMaps().add( new PodMountConfig(configMapName, '/etc/nextflow') )
|
|
}
|
|
|
|
protected void tryCreateConfigMap(String name, Map data) {
|
|
try {
|
|
k8sClient.configCreate(name, data)
|
|
}
|
|
catch( K8sResponseException e ) {
|
|
if( e.response.reason != 'AlreadyExists' )
|
|
throw e
|
|
}
|
|
}
|
|
|
|
protected String makeConfigMapName( Map configMap ) {
|
|
"nf-config-${hash(configMap.values())}"
|
|
}
|
|
|
|
protected String hash(Collection<String> text) {
|
|
def hasher = Hashing .murmur3_32() .newHasher()
|
|
def itr = text.iterator()
|
|
while( itr.hasNext() ) {
|
|
hasher.putUnencodedChars(itr.next())
|
|
}
|
|
|
|
return hasher.hash().toString()
|
|
}
|
|
|
|
protected void launchLogin() {
|
|
def launchDir = k8sConfig.getLaunchDir()
|
|
def cmd = "kubectl -n ${k8sClient.config.namespace} exec -it $runName -- /bin/bash -c 'cd $launchDir; exec bash --login -i'"
|
|
def proc = new ProcessBuilder().command('bash','-c',cmd).inheritIO().start()
|
|
def result = proc.waitFor()
|
|
if( result == 0 ) {
|
|
if ( k8sConfig.useJobResource() )
|
|
k8sClient.jobDelete(runName)
|
|
else
|
|
k8sClient.podDelete(runName)
|
|
}
|
|
}
|
|
}
|