move to standalone plugin
This commit is contained in:
@@ -0,0 +1,718 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
private K8sNodeInitDeployer initDeployer
|
||||
|
||||
/**
|
||||
* 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()
|
||||
|
||||
initDeployer.deploy()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user