From f9d8834adbeac621d302fda3ff1b6321664b6895 Mon Sep 17 00:00:00 2001 From: Jorge Date: Mon, 15 Jul 2024 21:42:13 +0200 Subject: [PATCH] add Constraints (Node and Attr) (#66) * implement ConstraintNodeSpec Signed-off-by: Jorge Aguilera * implement ConstraintAttrSpec Signed-off-by: Jorge Aguilera * Constraints refactor to introduce a specific models package (#68) * refactor the package organization to create a separate package for models * Refactor the test folder and add license headers * move contraintsbuilder to model Signed-off-by: Jorge Aguilera * refactor and new "raw" dsl to include open constraints Signed-off-by: Jorge Aguilera * iterate upon the constraint config [ci skip] * fix nomadlab typos Signed-off-by: Jorge Aguilera * use cloud cache in sun-nomadlab [ci skip] --------- Signed-off-by: Jorge Aguilera Co-authored-by: Abhinav Sharma Co-authored-by: Abhinav Sharma --- gradle.properties | 2 +- .../nextflow/nomad/config/NomadJobOpts.groovy | 47 ++- .../nomad/executor/NomadService.groovy | 39 ++- .../nomad/executor/TaskDirectives.groovy | 5 +- .../nomad/models/ConstraintsBuilder.groovy | 47 +++ .../JobAffinity.groovy} | 12 +- .../JobConstraint.groovy} | 10 +- .../nomad/models/JobConstraints.groovy | 61 ++++ .../nomad/models/JobConstraintsAttr.groovy | 83 +++++ .../nomad/models/JobConstraintsNode.groovy | 89 +++++ .../JobVolume.groovy} | 14 +- .../nomad/{ => config}/NomadConfigSpec.groovy | 24 +- .../config/NomadJobConstraintsSpec.groovy | 120 +++++++ .../nomad/executor/NomadServiceSpec.groovy | 1 + .../nomad/models/JobConstraintsSpec.groovy | 320 ++++++++++++++++++ validation/constraints/main.nf | 18 + validation/constraints/node-nextflow.config | 41 +++ validation/run-all.sh | 10 +- validation/run-pipeline.sh | 2 + validation/sun-nomadlab/nextflow.config | 25 +- 20 files changed, 908 insertions(+), 62 deletions(-) create mode 100644 plugins/nf-nomad/src/main/nextflow/nomad/models/ConstraintsBuilder.groovy rename plugins/nf-nomad/src/main/nextflow/nomad/{config/AffinitySpec.groovy => models/JobAffinity.groovy} (86%) rename plugins/nf-nomad/src/main/nextflow/nomad/{config/ConstraintSpec.groovy => models/JobConstraint.groovy} (86%) create mode 100644 plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraints.groovy create mode 100644 plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsAttr.groovy create mode 100644 plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsNode.groovy rename plugins/nf-nomad/src/main/nextflow/nomad/{config/VolumeSpec.groovy => models/JobVolume.groovy} (92%) rename plugins/nf-nomad/src/test/nextflow/nomad/{ => config}/NomadConfigSpec.groovy (91%) create mode 100644 plugins/nf-nomad/src/test/nextflow/nomad/config/NomadJobConstraintsSpec.groovy create mode 100644 plugins/nf-nomad/src/test/nextflow/nomad/models/JobConstraintsSpec.groovy create mode 100644 validation/constraints/main.nf create mode 100644 validation/constraints/node-nextflow.config diff --git a/gradle.properties b/gradle.properties index 4ed0a15..9626efd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ -version=0.1.1 +version=0.1.2 github_organization=nextflow-io \ No newline at end of file diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy index 2d61344..a31dd3f 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/config/NomadJobOpts.groovy @@ -19,6 +19,10 @@ package nextflow.nomad.config import groovy.transform.CompileStatic import groovy.util.logging.Slf4j +import nextflow.nomad.models.JobAffinity +import nextflow.nomad.models.JobConstraint +import nextflow.nomad.models.JobConstraints +import nextflow.nomad.models.JobVolume /** @@ -37,9 +41,11 @@ class NomadJobOpts{ String region String namespace String dockerVolume - VolumeSpec[] volumeSpec - AffinitySpec affinitySpec - ConstraintSpec constraintSpec + JobVolume[] volumeSpec + JobAffinity affinitySpec + JobConstraint constraintSpec + + JobConstraints constraintsSpec NomadJobOpts(Map nomadJobOpts, Map env=null){ assert nomadJobOpts!=null @@ -69,12 +75,13 @@ class NomadJobOpts{ this.volumeSpec = parseVolumes(nomadJobOpts) this.affinitySpec = parseAffinity(nomadJobOpts) this.constraintSpec = parseConstraint(nomadJobOpts) + this.constraintsSpec = parseConstraints(nomadJobOpts) } - VolumeSpec[] parseVolumes(Map nomadJobOpts){ - List ret = [] + JobVolume[] parseVolumes(Map nomadJobOpts){ + List ret = [] if( nomadJobOpts.volume && nomadJobOpts.volume instanceof Closure){ - def volumeSpec = new VolumeSpec() + def volumeSpec = new JobVolume() def closure = (nomadJobOpts.volume as Closure) def clone = closure.rehydrate(volumeSpec, closure.owner, closure.thisObject) clone.resolveStrategy = Closure.DELEGATE_FIRST @@ -86,7 +93,7 @@ class NomadJobOpts{ if( nomadJobOpts.volumes && nomadJobOpts.volumes instanceof List){ nomadJobOpts.volumes.each{ closure -> if( closure instanceof Closure){ - def volumeSpec = new VolumeSpec() + def volumeSpec = new JobVolume() def clone = closure.rehydrate(volumeSpec, closure.owner, closure.thisObject) clone.resolveStrategy = Closure.DELEGATE_FIRST clone() @@ -105,12 +112,13 @@ class NomadJobOpts{ throw new IllegalArgumentException("No more than a workdir volume allowed") } - return ret as VolumeSpec[] + return ret as JobVolume[] } - AffinitySpec parseAffinity(Map nomadJobOpts) { + JobAffinity parseAffinity(Map nomadJobOpts) { if (nomadJobOpts.affinity && nomadJobOpts.affinity instanceof Closure) { - def affinitySpec = new AffinitySpec() + log.info "affinity config will be deprecated, use affinities closure instead" + def affinitySpec = new JobAffinity() def closure = (nomadJobOpts.affinity as Closure) def clone = closure.rehydrate(affinitySpec, closure.owner, closure.thisObject) clone.resolveStrategy = Closure.DELEGATE_FIRST @@ -122,9 +130,10 @@ class NomadJobOpts{ } } - ConstraintSpec parseConstraint(Map nomadJobOpts){ + JobConstraint parseConstraint(Map nomadJobOpts){ if (nomadJobOpts.constraint && nomadJobOpts.constraint instanceof Closure) { - def constraintSpec = new ConstraintSpec() + log.info "constraint config will be deprecated, use constraints closure instead" + def constraintSpec = new JobConstraint() def closure = (nomadJobOpts.constraint as Closure) def clone = closure.rehydrate(constraintSpec, closure.owner, closure.thisObject) clone.resolveStrategy = Closure.DELEGATE_FIRST @@ -135,4 +144,18 @@ class NomadJobOpts{ null } } + + JobConstraints parseConstraints(Map nomadJobOpts){ + if (nomadJobOpts.constraints && nomadJobOpts.constraints instanceof Closure) { + def constraintsSpec = new JobConstraints() + def closure = (nomadJobOpts.constraints as Closure) + def clone = closure.rehydrate(constraintsSpec, closure.owner, closure.thisObject) + clone.resolveStrategy = Closure.DELEGATE_FIRST + clone() + constraintsSpec.validate() + constraintsSpec + }else{ + null + } + } } \ No newline at end of file diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy index bc6cbf6..21cc80c 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/executor/NomadService.groovy @@ -22,8 +22,10 @@ import groovy.util.logging.Slf4j import io.nomadproject.client.ApiClient import io.nomadproject.client.api.JobsApi import io.nomadproject.client.model.* +import nextflow.nomad.models.ConstraintsBuilder +import nextflow.nomad.models.JobConstraints import nextflow.nomad.config.NomadConfig -import nextflow.nomad.config.VolumeSpec +import nextflow.nomad.models.JobVolume import nextflow.processor.TaskRun import nextflow.util.MemoryUnit import nextflow.exception.ProcessSubmitException @@ -135,7 +137,7 @@ class NomadService implements Closeable{ if( config.jobOpts().volumeSpec ) { taskGroup.volumes = [:] config.jobOpts().volumeSpec.eachWithIndex { volumeSpec , idx-> - if (volumeSpec && volumeSpec.type == VolumeSpec.VOLUME_CSI_TYPE) { + if (volumeSpec && volumeSpec.type == JobVolume.VOLUME_CSI_TYPE) { taskGroup.volumes["vol_${idx}".toString()] = new VolumeRequest( type: volumeSpec.type, source: volumeSpec.name, @@ -145,7 +147,7 @@ class NomadService implements Closeable{ ) } - if (volumeSpec && volumeSpec.type == VolumeSpec.VOLUME_HOST_TYPE) { + if (volumeSpec && volumeSpec.type == JobVolume.VOLUME_HOST_TYPE) { taskGroup.volumes["vol_${idx}".toString()] = new VolumeRequest( type: volumeSpec.type, source: volumeSpec.name, @@ -182,7 +184,8 @@ class NomadService implements Closeable{ volumes(task, taskDef, workingDir) affinity(task, taskDef) - constrains(task, taskDef) + constraint(task, taskDef) + constraints(task, taskDef) return taskDef } @@ -233,7 +236,7 @@ class NomadService implements Closeable{ taskDef } - protected Task constrains(TaskRun task, Task taskDef){ + protected Task constraint(TaskRun task, Task taskDef){ if( config.jobOpts().constraintSpec ){ def constraint = new Constraint() if(config.jobOpts().constraintSpec.attribute){ @@ -251,8 +254,32 @@ class NomadService implements Closeable{ taskDef } + protected Task constraints(TaskRun task, Task taskDef){ + def constraints = [] as List + + if( config.jobOpts().constraintsSpec ){ + def list = ConstraintsBuilder.constraintsSpecToList(config.jobOpts().constraintsSpec) + constraints.addAll(list) + } + + if( task.processor?.config?.get(TaskDirectives.CONSTRAINTS) && + task.processor?.config?.get(TaskDirectives.CONSTRAINTS) instanceof Closure) { + Closure closure = task.processor?.config?.get(TaskDirectives.CONSTRAINTS) as Closure + JobConstraints constraintsSpec = JobConstraints.parse(closure) + def list = ConstraintsBuilder.constraintsSpecToList(constraintsSpec) + constraints.addAll(list) + } + + if( constraints.size()) { + taskDef.constraints(constraints) + } + taskDef + } + + + protected Job assignDatacenters(TaskRun task, Job job){ - def datacenters = task.processor?.config?.get("datacenters") + def datacenters = task.processor?.config?.get(TaskDirectives.DATACENTERS) if( datacenters ){ if( datacenters instanceof List) { job.datacenters( datacenters as List) diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/executor/TaskDirectives.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/executor/TaskDirectives.groovy index 669b617..edc1722 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/executor/TaskDirectives.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/executor/TaskDirectives.groovy @@ -4,7 +4,10 @@ class TaskDirectives { public static final String DATACENTERS = "datacenters" + public static final String CONSTRAINTS = "constraints" + public static final List ALL = [ - DATACENTERS + DATACENTERS, + CONSTRAINTS ] } diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/models/ConstraintsBuilder.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/models/ConstraintsBuilder.groovy new file mode 100644 index 0000000..3275e19 --- /dev/null +++ b/plugins/nf-nomad/src/main/nextflow/nomad/models/ConstraintsBuilder.groovy @@ -0,0 +1,47 @@ +package nextflow.nomad.models + +import io.nomadproject.client.model.Constraint +import nextflow.nomad.models.JobConstraintsAttr +import nextflow.nomad.models.JobConstraintsNode +import nextflow.nomad.models.JobConstraints + +class ConstraintsBuilder { + + static List constraintsSpecToList(JobConstraints spec){ + def constraints = [] as List + if( spec?.nodeSpecs ){ + def nodes = spec.nodeSpecs + ?.collect({ nodeConstraints(it)}) + ?.flatten() as List + constraints.addAll(nodes) + } + if( spec?.attrSpecs ){ + def nodes = spec.attrSpecs + ?.collect({ attrConstraints(it)}) + ?.flatten() as List + constraints.addAll(nodes) + } + return constraints + } + + protected static List nodeConstraints(JobConstraintsNode nodeSpec){ + def ret = nodeSpec.raws?.collect{ triple-> + return new Constraint() + .ltarget('${'+triple.left+'}') + .operand(triple.middle) + .rtarget(triple.right) + } as List + ret + } + + protected static List attrConstraints(JobConstraintsAttr nodeSpec) { + def ret = nodeSpec.raws?.collect{ triple-> + return new Constraint() + .ltarget('${'+triple.left+'}') + .operand(triple.middle) + .rtarget(triple.right) + } as List + ret + } + +} diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/config/AffinitySpec.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobAffinity.groovy similarity index 86% rename from plugins/nf-nomad/src/main/nextflow/nomad/config/AffinitySpec.groovy rename to plugins/nf-nomad/src/main/nextflow/nomad/models/JobAffinity.groovy index 7d8277e..9e96584 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/config/AffinitySpec.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobAffinity.groovy @@ -15,13 +15,13 @@ * limitations under the License. */ -package nextflow.nomad.config +package nextflow.nomad.models /** * Nomad Job Affinity Spec * * @author Jorge Aguilera */ -class AffinitySpec{ +class JobAffinity { private String attribute private String operator @@ -44,22 +44,22 @@ class AffinitySpec{ return weight } - AffinitySpec attribute(String attribute){ + JobAffinity attribute(String attribute){ this.attribute=attribute this } - AffinitySpec operator(String operator){ + JobAffinity operator(String operator){ this.operator = operator this } - AffinitySpec value(String value){ + JobAffinity value(String value){ this.value = value this } - AffinitySpec weight(int weight){ + JobAffinity weight(int weight){ this.weight = weight this } diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/config/ConstraintSpec.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraint.groovy similarity index 86% rename from plugins/nf-nomad/src/main/nextflow/nomad/config/ConstraintSpec.groovy rename to plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraint.groovy index 266bfd2..22a57f1 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/config/ConstraintSpec.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraint.groovy @@ -15,14 +15,14 @@ * limitations under the License. */ -package nextflow.nomad.config +package nextflow.nomad.models /** * Nomad Job Constraint Spec * * @author Jorge Aguilera */ -class ConstraintSpec { +class JobConstraint { private String attribute private String operator @@ -40,17 +40,17 @@ class ConstraintSpec { return value } - ConstraintSpec attribute(String attribute){ + JobConstraint attribute(String attribute){ this.attribute=attribute this } - ConstraintSpec operator(String operator){ + JobConstraint operator(String operator){ this.operator = operator this } - ConstraintSpec value(String value){ + JobConstraint value(String value){ this.value = value this } diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraints.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraints.groovy new file mode 100644 index 0000000..dff45d5 --- /dev/null +++ b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraints.groovy @@ -0,0 +1,61 @@ +/* + * Copyright 2023-, Stellenbosch University, South Africa + * Copyright 2024, Evaluacion y Desarrollo de Negocios, Spain + * + * 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 nextflow.nomad.models + +/** + * Nomad Job Constraint Spec + * + * @author Jorge Aguilera + */ + +class JobConstraints { + + List nodeSpecs = [] + List attrSpecs = [] + + JobConstraints node(@DelegatesTo(JobConstraintsNode)Closure closure){ + JobConstraintsNode constraintSpec = new JobConstraintsNode() + def clone = closure.rehydrate(constraintSpec, closure.owner, closure.thisObject) + clone.resolveStrategy = Closure.DELEGATE_FIRST + clone() + nodeSpecs << constraintSpec + this + } + + JobConstraints attr(@DelegatesTo(JobConstraintsAttr)Closure closure){ + JobConstraintsAttr constraintSpec = new JobConstraintsAttr() + def clone = closure.rehydrate(constraintSpec, closure.owner, closure.thisObject) + clone.resolveStrategy = Closure.DELEGATE_FIRST + clone() + attrSpecs << constraintSpec + this + } + + void validate(){ + + } + + static JobConstraints parse(@DelegatesTo(JobConstraints)Closure closure){ + JobConstraints constraintsSpec = new JobConstraints() + def clone = closure.rehydrate(constraintsSpec, closure.owner, closure.thisObject) + clone.resolveStrategy = Closure.DELEGATE_FIRST + clone() + constraintsSpec.validate() + constraintsSpec + } +} diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsAttr.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsAttr.groovy new file mode 100644 index 0000000..875b158 --- /dev/null +++ b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsAttr.groovy @@ -0,0 +1,83 @@ +/* + * Copyright 2023-, Stellenbosch University, South Africa + * Copyright 2024, Evaluacion y Desarrollo de Negocios, Spain + * + * 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 nextflow.nomad.models + +import org.apache.commons.lang3.tuple.Triple + +/** + * Nomad Job Constraint Spec + * + * @author Jorge Aguilera + */ + +class JobConstraintsAttr { + + private List> raws= [] + + List> getRaws() { + return raws + } + + JobConstraintsAttr setCpu(Map map){ + cpu(map) + } + + JobConstraintsAttr cpu(Map map){ + if( map.containsKey('arch')) + raw("cpu.arch","=", map['arch'].toString()) + if( map.containsKey('numcores')) + raw("cpu.numcores",">=", map['numcores'].toString()) + if( map.containsKey('reservablecores')) + raw("cpu.reservablecores",">=", map['reservablecores'].toString()) + if( map.containsKey('totalcompute')) + raw("cpu.totalcompute","=", map['totalcompute'].toString()) + this + } + + JobConstraintsAttr setUnique(Map map){ + unique(map) + } + + JobConstraintsAttr unique(Map map){ + if( map.containsKey('hostname')) + raw("unique.hostname","=", map['hostname'].toString()) + if( map.containsKey('ip-address')) + raw("unique.network.ip-address","=", map['ip-address'].toString()) + this + } + + JobConstraintsAttr setKernel(Map map){ + kernel(map) + } + + JobConstraintsAttr kernel(Map map){ + if( map.containsKey('arch')) + raw("kernel.arch","=", map['arch'].toString()) + if( map.containsKey('name')) + raw("kernel.name","=", map['name'].toString()) + if( map.containsKey('version')) + raw("kernel.version","=", map['version'].toString()) + this + } + + JobConstraintsAttr raw(String attr, String operator, String value){ + raws.add Triple.of("attr."+attr, operator, value) + this + } +} diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsNode.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsNode.groovy new file mode 100644 index 0000000..3ac0f9e --- /dev/null +++ b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobConstraintsNode.groovy @@ -0,0 +1,89 @@ +/* + * Copyright 2023-, Stellenbosch University, South Africa + * Copyright 2024, Evaluacion y Desarrollo de Negocios, Spain + * + * 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 nextflow.nomad.models + +import org.apache.commons.lang3.tuple.Triple + +/** + * Nomad Job Constraint Spec + * + * @author Jorge Aguilera + */ + +class JobConstraintsNode { + + private List> raws= [] + + List> getRaws() { + return raws + } + + JobConstraintsNode setUnique(Map map){ + unique(map) + } + + JobConstraintsNode unique(Map map){ + ['id', 'name'].each { key-> + if( map.containsKey(key)) + raw("unique.${key}","=", map[key].toString()) + } + this + } + + JobConstraintsNode setClazz(Object map){ // class is a reserved word, in java we used clazz + clazz(map) + } + + JobConstraintsNode clazz(Object cls){ + raw("class","=", cls.toString()) + this + } + + JobConstraintsNode setPool(Object map){ + pool(map) + } + + JobConstraintsNode pool(Object pool){ + raw("pool","=", pool.toString()) + this + } + + JobConstraintsNode setDataCenter(Object map){ + dataCenter(map) + } + + JobConstraintsNode dataCenter(Object dataCenter){ + raw("datacenter","=", dataCenter.toString()) + this + } + + JobConstraintsNode setRegion(Object map){ + region(map) + } + + JobConstraintsNode region(Object region){ + raw("region","=", region.toString()) + this + } + + JobConstraintsNode raw(String attr, String operator, String value){ + raws.add Triple.of("node."+attr, operator, value) + this + } +} diff --git a/plugins/nf-nomad/src/main/nextflow/nomad/config/VolumeSpec.groovy b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobVolume.groovy similarity index 92% rename from plugins/nf-nomad/src/main/nextflow/nomad/config/VolumeSpec.groovy rename to plugins/nf-nomad/src/main/nextflow/nomad/models/JobVolume.groovy index 529439b..70d3c80 100644 --- a/plugins/nf-nomad/src/main/nextflow/nomad/config/VolumeSpec.groovy +++ b/plugins/nf-nomad/src/main/nextflow/nomad/models/JobVolume.groovy @@ -15,13 +15,13 @@ * limitations under the License. */ -package nextflow.nomad.config +package nextflow.nomad.models /** * Nomad Volume Spec * * @author Jorge Aguilera */ -class VolumeSpec { +class JobVolume { final static public String VOLUME_DOCKER_TYPE = "docker" final static public String VOLUME_CSI_TYPE = "csi" @@ -57,27 +57,27 @@ class VolumeSpec { return readOnly } - VolumeSpec type(String type){ + JobVolume type(String type){ this.type = type this } - VolumeSpec name(String name){ + JobVolume name(String name){ this.name = name this } - VolumeSpec workDir(boolean b){ + JobVolume workDir(boolean b){ this.workDir = b this } - VolumeSpec path(String path){ + JobVolume path(String path){ this.path = path this } - VolumeSpec readOnly(boolean readOnly){ + JobVolume readOnly(boolean readOnly){ this.readOnly = readOnly this } diff --git a/plugins/nf-nomad/src/test/nextflow/nomad/NomadConfigSpec.groovy b/plugins/nf-nomad/src/test/nextflow/nomad/config/NomadConfigSpec.groovy similarity index 91% rename from plugins/nf-nomad/src/test/nextflow/nomad/NomadConfigSpec.groovy rename to plugins/nf-nomad/src/test/nextflow/nomad/config/NomadConfigSpec.groovy index 028514f..3856a57 100644 --- a/plugins/nf-nomad/src/test/nextflow/nomad/NomadConfigSpec.groovy +++ b/plugins/nf-nomad/src/test/nextflow/nomad/config/NomadConfigSpec.groovy @@ -15,10 +15,10 @@ * limitations under the License. */ -package nextflow.nomad +package nextflow.nomad.config -import nextflow.nomad.config.NomadConfig -import nextflow.nomad.config.VolumeSpec + +import nextflow.nomad.models.JobVolume import spock.lang.Specification import spock.lang.Unroll @@ -140,7 +140,7 @@ class NomadConfigSpec extends Specification { then: config.jobOpts.volumeSpec - config.jobOpts.volumeSpec[0].type == VolumeSpec.VOLUME_DOCKER_TYPE + config.jobOpts.volumeSpec[0].type == JobVolume.VOLUME_DOCKER_TYPE config.jobOpts.volumeSpec[0].name == "test" when: @@ -150,7 +150,7 @@ class NomadConfigSpec extends Specification { then: config2.jobOpts.volumeSpec - config2.jobOpts.volumeSpec[0].type == VolumeSpec.VOLUME_CSI_TYPE + config2.jobOpts.volumeSpec[0].type == JobVolume.VOLUME_CSI_TYPE config2.jobOpts.volumeSpec[0].name == "test" when: @@ -160,7 +160,7 @@ class NomadConfigSpec extends Specification { then: config3.jobOpts.volumeSpec - config3.jobOpts.volumeSpec[0].type == VolumeSpec.VOLUME_HOST_TYPE + config3.jobOpts.volumeSpec[0].type == JobVolume.VOLUME_HOST_TYPE config3.jobOpts.volumeSpec[0].name == "test" when: @@ -220,7 +220,7 @@ class NomadConfigSpec extends Specification { then: config.jobOpts.volumeSpec - config.jobOpts.volumeSpec[0].type == VolumeSpec.VOLUME_DOCKER_TYPE + config.jobOpts.volumeSpec[0].type == JobVolume.VOLUME_DOCKER_TYPE config.jobOpts.volumeSpec[0].name == "test" config.jobOpts.volumeSpec[0].workDir @@ -249,9 +249,9 @@ class NomadConfigSpec extends Specification { then: config2.jobOpts.volumeSpec.size()==2 - config2.jobOpts.volumeSpec[0].type == VolumeSpec.VOLUME_CSI_TYPE + config2.jobOpts.volumeSpec[0].type == JobVolume.VOLUME_CSI_TYPE config2.jobOpts.volumeSpec[0].name == "test" - config2.jobOpts.volumeSpec[1].type == VolumeSpec.VOLUME_DOCKER_TYPE + config2.jobOpts.volumeSpec[1].type == JobVolume.VOLUME_DOCKER_TYPE config2.jobOpts.volumeSpec[1].name == "test" config.jobOpts.volumeSpec[0].workDir @@ -270,9 +270,9 @@ class NomadConfigSpec extends Specification { then: config3.jobOpts.volumeSpec.size()==3 - config3.jobOpts.volumeSpec[0].type == VolumeSpec.VOLUME_CSI_TYPE - config3.jobOpts.volumeSpec[1].type == VolumeSpec.VOLUME_CSI_TYPE - config3.jobOpts.volumeSpec[2].type == VolumeSpec.VOLUME_DOCKER_TYPE + config3.jobOpts.volumeSpec[0].type == JobVolume.VOLUME_CSI_TYPE + config3.jobOpts.volumeSpec[1].type == JobVolume.VOLUME_CSI_TYPE + config3.jobOpts.volumeSpec[2].type == JobVolume.VOLUME_DOCKER_TYPE config3.jobOpts.volumeSpec[0].workDir config3.jobOpts.volumeSpec.findAll{ it.workDir}.size() == 1 diff --git a/plugins/nf-nomad/src/test/nextflow/nomad/config/NomadJobConstraintsSpec.groovy b/plugins/nf-nomad/src/test/nextflow/nomad/config/NomadJobConstraintsSpec.groovy new file mode 100644 index 0000000..c0b5d92 --- /dev/null +++ b/plugins/nf-nomad/src/test/nextflow/nomad/config/NomadJobConstraintsSpec.groovy @@ -0,0 +1,120 @@ +/* + * Copyright 2023-, Stellenbosch University, South Africa + * Copyright 2024, Evaluacion y Desarrollo de Negocios, Spain + * + * 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 nextflow.nomad.config + +import spock.lang.Specification + +/** + * Unit test for Nomad Config + * + * @author : Jorge Aguilera + * @author : Abhinav Sharma + */ +class NomadJobConstraintsSpec extends Specification { + + + void "should instantiate a constraints spec if specified"() { + when: + def config = new NomadConfig([ + jobs: [ + constraints: { + node { + unique = [id :"node-id", name: "node-name"] + clazz = "linux-64bit" + pool = "custom-pool" + dataCenter = 'dc1' + region = 'us' + } + attr{ + cpu = [arch:'286'] + } + } + ] + ]) + + then: + config.jobOpts.constraintsSpec + config.jobOpts.constraintsSpec.nodeSpecs.size() == 1 + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("id")}.right == "node-id" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("name")}.right == "node-name" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("class")}.right == "linux-64bit" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("pool")}.right == "custom-pool" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("datacenter")}.right == "dc1" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("region")}.right == "us" + + config.jobOpts.constraintsSpec.attrSpecs.size() == 1 + config.jobOpts.constraintsSpec.attrSpecs[0].raws[0].right == '286' + } + + void "should instantiate a no completed constraints spec"() { + when: + def config = new NomadConfig([ + jobs: [ + constraints: { + node { + unique = [id :"node-id", name: "node-name"] + clazz = "linux-64bit" + } + } + ] + ]) + + then: + config.jobOpts.constraintsSpec + config.jobOpts.constraintsSpec.nodeSpecs.size() + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("id")}.right == "node-id" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("name")}.right == "node-name" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("class")}.right == "linux-64bit" + !config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("pool")} + !config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("datacenter")} + } + + void "should instantiate a list of constraints spec if specified"() { + when: + def config = new NomadConfig([ + jobs: [ + constraints: { + node { + unique = [id :"node-id", name: "node-name"] + clazz = "linux-64bit" + pool = "custom-pool" + dataCenter = 'dc1' + region = 'us' + } + node { + unique = [id :"node-id", name: "node-name"] + clazz = "linux-64bit" + pool = "custom-pool" + dataCenter = 'dc1' + region = 'us' + } + } + ] + ]) + + then: + config.jobOpts.constraintsSpec + config.jobOpts.constraintsSpec.nodeSpecs.size() + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("id")}.right == "node-id" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("name")}.right == "node-name" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("class")}.right == "linux-64bit" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("pool")}.right == "custom-pool" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("datacenter")}.right == "dc1" + config.jobOpts.constraintsSpec.nodeSpecs[0].raws.find{it.left.endsWith("region")}.right == "us" + } +} diff --git a/plugins/nf-nomad/src/test/nextflow/nomad/executor/NomadServiceSpec.groovy b/plugins/nf-nomad/src/test/nextflow/nomad/executor/NomadServiceSpec.groovy index b29089d..4683617 100644 --- a/plugins/nf-nomad/src/test/nextflow/nomad/executor/NomadServiceSpec.groovy +++ b/plugins/nf-nomad/src/test/nextflow/nomad/executor/NomadServiceSpec.groovy @@ -622,4 +622,5 @@ class NomadServiceSpec extends Specification{ 1 | ['1'] ({ 'a'*10 }) | ['aaaaaaaaaa'] } + } diff --git a/plugins/nf-nomad/src/test/nextflow/nomad/models/JobConstraintsSpec.groovy b/plugins/nf-nomad/src/test/nextflow/nomad/models/JobConstraintsSpec.groovy new file mode 100644 index 0000000..0fc3ae6 --- /dev/null +++ b/plugins/nf-nomad/src/test/nextflow/nomad/models/JobConstraintsSpec.groovy @@ -0,0 +1,320 @@ +/* + * Copyright 2023-, Stellenbosch University, South Africa + * Copyright 2024, Evaluacion y Desarrollo de Negocios, Spain + * + * 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 nextflow.nomad.models + +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import nextflow.executor.Executor +import nextflow.nomad.config.NomadConfig +import nextflow.nomad.executor.NomadService +import nextflow.processor.TaskBean +import nextflow.processor.TaskConfig +import nextflow.processor.TaskProcessor +import nextflow.processor.TaskRun +import nextflow.script.ProcessConfig +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import spock.lang.Specification + +import java.nio.file.Files +import java.nio.file.Path + +/** + * Unit test for Nomad Service + * + * Validate requests using a Mock WebServer + * + * @author : Jorge Aguilera + */ +class JobConstraintsSpec extends Specification{ + + MockWebServer mockWebServer + + def setup() { + mockWebServer = new MockWebServer() + mockWebServer.start() + } + + def cleanup() { + mockWebServer.shutdown() + } + + void "submit a task with a node constraint"(){ + given: + def config = new NomadConfig( + client:[ + address : "http://${mockWebServer.hostName}:${mockWebServer.port}" + ], + jobs:[ + constraints: { + node { + unique = [name:'test'] + } + } + ] + ) + def service = new NomadService(config) + + String id = "theId" + String name = "theName" + String image = "theImage" + List args = ["theCommand", "theArgs"] + String workingDir = "/a/b/c" + Mapenv = [test:"test"] + + def mockTask = Mock(TaskRun){ + getName() >> name + getContainer() >> image + getConfig() >> Mock(TaskConfig) + getWorkDirStr() >> workingDir + getContainer() >> "ubuntu" + getProcessor() >> Mock(TaskProcessor){ + getExecutor() >> Mock(Executor){ + isFusionEnabled() >> false + } + } + getWorkDir() >> Path.of(workingDir) + toTaskBean() >> Mock(TaskBean){ + getWorkDir() >> Path.of(workingDir) + getScript() >> "theScript" + getShell() >> ["bash"] + getInputFiles() >> [:] + } + } + + mockWebServer.enqueue(new MockResponse() + .setBody(JsonOutput.toJson(["EvalID":"test"]).toString()) + .addHeader("Content-Type", "application/json")); + when: + + def idJob = service.submitTask(id, mockTask, args, env) + def recordedRequest = mockWebServer.takeRequest(); + def body = new JsonSlurper().parseText(recordedRequest.body.readUtf8()) + + then: + idJob + + and: + recordedRequest.method == "POST" + recordedRequest.path == "/v1/jobs" + + and: + body.Job.TaskGroups[0].Tasks[0].Constraints[0].LTarget == '${node.unique.name}' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].RTarget == 'test' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].Operand == '=' + } + + void "submit a task with a config attr constraint"(){ + given: + def config = new NomadConfig( + client:[ + address : "http://${mockWebServer.hostName}:${mockWebServer.port}" + ], + jobs:[ + constraints: { + attr { + cpu = [arch:'286'] + } + } + ] + ) + def service = new NomadService(config) + + String id = "theId" + String name = "theName" + String image = "theImage" + List args = ["theCommand", "theArgs"] + String workingDir = "/a/b/c" + Mapenv = [test:"test"] + + def mockTask = Mock(TaskRun){ + getName() >> name + getContainer() >> image + getConfig() >> Mock(TaskConfig) + getWorkDirStr() >> workingDir + getContainer() >> "ubuntu" + getProcessor() >> Mock(TaskProcessor){ + getExecutor() >> Mock(Executor){ + isFusionEnabled() >> false + } + } + getWorkDir() >> Path.of(workingDir) + toTaskBean() >> Mock(TaskBean){ + getWorkDir() >> Path.of(workingDir) + getScript() >> "theScript" + getShell() >> ["bash"] + getInputFiles() >> [:] + } + } + + mockWebServer.enqueue(new MockResponse() + .setBody(JsonOutput.toJson(["EvalID":"test"]).toString()) + .addHeader("Content-Type", "application/json")); + when: + + def idJob = service.submitTask(id, mockTask, args, env) + def recordedRequest = mockWebServer.takeRequest(); + def body = new JsonSlurper().parseText(recordedRequest.body.readUtf8()) + + then: + idJob + + and: + recordedRequest.method == "POST" + recordedRequest.path == "/v1/jobs" + + and: + body.Job.TaskGroups[0].Tasks[0].Constraints[0].LTarget == '${attr.cpu.arch}' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].RTarget == '286' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].Operand == '=' + } + + void "submit a task with an attr constraint"(){ + given: + def config = new NomadConfig( + client:[ + address : "http://${mockWebServer.hostName}:${mockWebServer.port}" + ], + ) + def service = new NomadService(config) + + String id = "theId" + String name = "theName" + String image = "theImage" + List args = ["theCommand", "theArgs"] + String workingDir = "/a/b/c" + Mapenv = [test:"test"] + + def contraints = { + attr { + cpu = [arch:'286'] + } + } + + def mockTask = Mock(TaskRun){ + getName() >> name + getContainer() >> image + getConfig() >> Mock(TaskConfig) + getWorkDirStr() >> workingDir + getContainer() >> "ubuntu" + getProcessor() >> Mock(TaskProcessor){ + getExecutor() >> Mock(Executor){ + isFusionEnabled() >> false + } + getConfig() >> Mock(ProcessConfig){ + get("constraints") >> contraints + } + } + getWorkDir() >> Path.of(workingDir) + toTaskBean() >> Mock(TaskBean){ + getWorkDir() >> Path.of(workingDir) + getScript() >> "theScript" + getShell() >> ["bash"] + getInputFiles() >> [:] + } + } + + mockWebServer.enqueue(new MockResponse() + .setBody(JsonOutput.toJson(["EvalID":"test"]).toString()) + .addHeader("Content-Type", "application/json")); + when: + + def idJob = service.submitTask(id, mockTask, args, env) + def recordedRequest = mockWebServer.takeRequest(); + def body = new JsonSlurper().parseText(recordedRequest.body.readUtf8()) + + then: + idJob + + and: + recordedRequest.method == "POST" + recordedRequest.path == "/v1/jobs" + + and: + body.Job.TaskGroups[0].Tasks[0].Constraints[0].LTarget == '${attr.cpu.arch}' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].RTarget == '286' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].Operand == '=' + } + + void "submit a task with a raw attr constraint"(){ + given: + def config = new NomadConfig( + client:[ + address : "http://${mockWebServer.hostName}:${mockWebServer.port}" + ], + ) + def service = new NomadService(config) + + String id = "theId" + String name = "theName" + String image = "theImage" + List args = ["theCommand", "theArgs"] + String workingDir = "/a/b/c" + Mapenv = [test:"test"] + + def contraints = { + attr { + raw 'platform.aws.instance-type', '=', 'm4.xlarge' + } + } + + def mockTask = Mock(TaskRun){ + getName() >> name + getContainer() >> image + getConfig() >> Mock(TaskConfig) + getWorkDirStr() >> workingDir + getContainer() >> "ubuntu" + getProcessor() >> Mock(TaskProcessor){ + getExecutor() >> Mock(Executor){ + isFusionEnabled() >> false + } + getConfig() >> Mock(ProcessConfig){ + get("constraints") >> contraints + } + } + getWorkDir() >> Path.of(workingDir) + toTaskBean() >> Mock(TaskBean){ + getWorkDir() >> Path.of(workingDir) + getScript() >> "theScript" + getShell() >> ["bash"] + getInputFiles() >> [:] + } + } + + mockWebServer.enqueue(new MockResponse() + .setBody(JsonOutput.toJson(["EvalID":"test"]).toString()) + .addHeader("Content-Type", "application/json")); + when: + + def idJob = service.submitTask(id, mockTask, args, env) + def recordedRequest = mockWebServer.takeRequest(); + def body = new JsonSlurper().parseText(recordedRequest.body.readUtf8()) + + then: + idJob + + and: + recordedRequest.method == "POST" + recordedRequest.path == "/v1/jobs" + + and: + body.Job.TaskGroups[0].Tasks[0].Constraints[0].LTarget == '${attr.platform.aws.instance-type}' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].RTarget == 'm4.xlarge' + body.Job.TaskGroups[0].Tasks[0].Constraints[0].Operand == '=' + } +} diff --git a/validation/constraints/main.nf b/validation/constraints/main.nf new file mode 100644 index 0000000..87f26f8 --- /dev/null +++ b/validation/constraints/main.nf @@ -0,0 +1,18 @@ +#!/usr/bin/env nextflow + +process sayHello { + container 'ubuntu:20.04' + + input: + val x + output: + stdout + script: + """ + echo '$x world!' + """ +} + +workflow { + Channel.of('Bonjour', 'Ciao', 'Hello', 'Hola') | sayHello | view +} \ No newline at end of file diff --git a/validation/constraints/node-nextflow.config b/validation/constraints/node-nextflow.config new file mode 100644 index 0000000..99077e5 --- /dev/null +++ b/validation/constraints/node-nextflow.config @@ -0,0 +1,41 @@ +plugins { + id 'nf-nomad@latest' +} + +process { + executor = "nomad" +} + +nomad { + + client { + address = "http://localhost:4646" + } + + jobs { + deleteOnCompletion = false + volume = { type "host" name "scratchdir" } + + constraints = { + node { + unique = [ name: params.RUN_IN_NODE ] + } + } + } + +} + +profiles{ + localnomad{ + process { + withName: sayHello { + datacenters = ['test-datacenter', 'demo-datacenter'] + constraints = { + node { + unique = [ name: params.RUN_IN_NODE ] + } + } + } + } + } +} \ No newline at end of file diff --git a/validation/run-all.sh b/validation/run-all.sh index 81867e0..f1136c0 100755 --- a/validation/run-all.sh +++ b/validation/run-all.sh @@ -29,12 +29,14 @@ if [ "$SKIPLOCAL" == 0 ]; then ./run-pipeline.sh -c basic/nextflow.config basic/main.nf - ./run-pipeline.sh -c directives/nextflow.config directives/main.nf + ./run-pipeline.sh -c directives/nextflow.config directives/main.nf -profile localnomad ./run-pipeline.sh -c multiple-volumes/2-volumes.config multiple-volumes/main.nf ./run-pipeline.sh -c multiple-volumes/3-volumes.config multiple-volumes/main.nf + ./run-pipeline.sh -c constraints/node-nextflow.config constraints/main.nf -profile localnomad --RUN_IN_NODE $HOSTNAME + ./run-pipeline.sh -c basic/nextflow.config nf-core/demo \ -r dev -profile test,docker \ --outdir $(pwd)/nomad_temp/scratchdir/out @@ -69,8 +71,8 @@ fi #NOTE: In this use-case you need to be in the same network of sun-nomadlab server, for example using a tailscale connection #NOTE2: You need to have 2 secrets stored in your Nextlow: SUN_NOMADLAB_ACCESS_KEY and SUN_NOMADLAB_SECRET_KEY if [ "$NFSUN" == 1 ]; then - - if [ "$NFSLEEP" == 1 ]; then + NXF_CLOUDCACHE_PATH="s3://fusionfs/integration-test/cache" + if [ "$NFSLEEP" == 1 ]; then nextflow run -w s3://fusionfs/integration-test/work -c sun-nomadlab/nextflow.config abhi18av/nf-sleep --timeout 360 elif [ "$NFDEMO" == 1 ]; then @@ -84,7 +86,7 @@ if [ "$NFSUN" == 1 ]; then -w s3://fusionfs/integration-test/work -c sun-nomadlab/nextflow.config \ -profile test,docker --outdir s3://fusionfs/integration-test/bactopia/outdir \ --accession SRX4563634 --coverage 100 --genome_size 2800000 \ - --datasets_cache s3://fusionfs/integration-test/bactopia/assets + --datasets_cache s3://fusionfs/integration-test/bactopia/assets -resume fi else diff --git a/validation/run-pipeline.sh b/validation/run-pipeline.sh index 6986600..bec6922 100755 --- a/validation/run-pipeline.sh +++ b/validation/run-pipeline.sh @@ -2,6 +2,8 @@ ./wait-nomad.sh +./nomad system gc + NXF_ASSETS=$(pwd)/nomad_temp/scratchdir/assets \ NXF_CACHE_DIR=$(pwd)/nomad_temp/scratchdir/cache \ nextflow run -w $(pwd)/nomad_temp/scratchdir/ "$@" diff --git a/validation/sun-nomadlab/nextflow.config b/validation/sun-nomadlab/nextflow.config index 5affd7b..263a7e1 100644 --- a/validation/sun-nomadlab/nextflow.config +++ b/validation/sun-nomadlab/nextflow.config @@ -15,24 +15,33 @@ aws { } wave { - enabled = true + enabled = true } fusion { - enabled = true - exportStorageCredentials = true + enabled = true + exportStorageCredentials = true } nomad { client { - address = 'http://100.119.165.23:4646' + address = 'http://100.119.165.23:4646' } jobs { - deleteOnCompletion = false - volumes = [ - { type "csi" name "juicefs-volume" } - ] + deleteOnCompletion = false + + constraints = { + + node { + unique = [name: "nomad03"] + } + + attr { + unique = [hostname:'nomad03'] + //raw 'platform.aws.instance-type', '=', 'm4.xlarge' + } + } } }