Skip to content

Commit 8f68fe5

Browse files
committed
Add snapshots support to nf-seqera
Signed-off-by: Lorenzo Fontana <fontanalorenz@gmail.com>
1 parent 0fa7dfb commit 8f68fe5

File tree

4 files changed

+66
-12
lines changed

4 files changed

+66
-12
lines changed

plugins/nf-seqera/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ dependencies {
4646
compileOnly project(':nextflow')
4747
compileOnly 'org.slf4j:slf4j-api:2.0.17'
4848
compileOnly 'org.pf4j:pf4j:3.12.0'
49-
api 'io.seqera:sched-client:0.19.0-SNAPSHOT'
49+
api 'io.seqera:sched-client:0.20.1'
5050

5151
testImplementation(testFixtures(project(":nextflow")))
5252
testImplementation "org.apache.groovy:groovy:4.0.29"

plugins/nf-seqera/src/main/io/seqera/executor/SeqeraTaskHandler.groovy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,12 @@ class SeqeraTaskHandler extends TaskHandler implements FusionAwareTask {
103103
if( accelerator.type )
104104
resourceReq.acceleratorName(accelerator.type)
105105
}
106-
// build machine requirement merging config settings with task arch and disk
106+
// build machine requirement merging config settings with task arch, disk, and snapshot settings
107107
final machineReq = MapperUtil.toMachineRequirement(
108108
executor.getSeqeraConfig().machineRequirement,
109109
task.getContainerPlatform(),
110-
task.config.getDisk()
110+
task.config.getDisk(),
111+
fusionConfig().snapshotsEnabled()
111112
)
112113
// validate container - Seqera executor requires all processes to specify a container image
113114
final container = task.getContainer()

plugins/nf-seqera/src/main/io/seqera/util/MapperUtil.groovy

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import io.seqera.sched.api.schema.v1a1.MachineRequirement
2525
import io.seqera.sched.api.schema.v1a1.PriceModel as SchedPriceModel
2626
import io.seqera.sched.api.schema.v1a1.ProvisioningModel
2727
import nextflow.cloud.types.PriceModel
28+
import nextflow.fusion.FusionConfig
2829
import nextflow.util.MemoryUnit
2930

3031
/**
@@ -80,35 +81,38 @@ class MapperUtil {
8081
* @return the MachineRequirement API object, or null if no settings
8182
*/
8283
static MachineRequirement toMachineRequirement(MachineRequirementOpts opts, String taskArch) {
83-
return toMachineRequirement(opts, taskArch, null)
84+
return toMachineRequirement(opts, taskArch, null, false)
8485
}
8586

8687
/**
87-
* Maps MachineRequirementOpts to MachineRequirement API object, merging with task arch and disk.
88+
* Maps MachineRequirementOpts to MachineRequirement API object, merging with task arch, disk, and snapshots.
8889
* Task arch overrides config arch if specified.
8990
*
9091
* @param opts the config options (can be null)
9192
* @param taskArch the task container platform/arch (can be null)
9293
* @param diskSize the disk size from task config (can be null)
94+
* @param snapshotEnabled whether Fusion snapshots are enabled
9395
* @return the MachineRequirement API object, or null if no settings
9496
*/
95-
static MachineRequirement toMachineRequirement(MachineRequirementOpts opts, String taskArch, MemoryUnit diskSize) {
97+
static MachineRequirement toMachineRequirement(MachineRequirementOpts opts, String taskArch, MemoryUnit diskSize, boolean snapshotEnabled) {
9698
final arch = taskArch ?: opts?.arch
9799
final provisioning = opts?.provisioning
98100
final maxSpotAttempts = opts?.maxSpotAttempts
101+
?: (snapshotEnabled ? FusionConfig.DEFAULT_SNAPSHOT_MAX_SPOT_ATTEMPTS : null)
99102
final machineFamilies = opts?.machineFamilies
100103
// task disk overrides config disk
101104
final effectiveDiskSize = diskSize ?: opts?.diskSize
102105
final diskReq = toDiskRequirement(effectiveDiskSize, opts)
103106
// return null if no settings
104-
if (!arch && !provisioning && !maxSpotAttempts && !machineFamilies && !diskReq)
107+
if (!arch && !provisioning && !maxSpotAttempts && !machineFamilies && !diskReq && !snapshotEnabled)
105108
return null
106109
new MachineRequirement()
107110
.arch(arch)
108111
.provisioning(toProvisioningModel(provisioning))
109112
.maxSpotAttempts(maxSpotAttempts)
110113
.machineFamilies(machineFamilies)
111114
.disk(diskReq)
115+
.snapshotEnabled(snapshotEnabled ? Boolean.TRUE : null)
112116
}
113117

114118
/**

plugins/nf-seqera/src/test/io/seqera/util/MapperUtilTest.groovy

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import io.seqera.sched.api.schema.v1a1.DiskAllocation
2222
import io.seqera.sched.api.schema.v1a1.PriceModel as SchedPriceModel
2323
import io.seqera.sched.api.schema.v1a1.ProvisioningModel
2424
import nextflow.cloud.types.PriceModel
25+
import nextflow.fusion.FusionConfig
2526
import nextflow.util.MemoryUnit
2627
import spock.lang.Specification
2728

@@ -182,7 +183,8 @@ class MapperUtilTest extends Specification {
182183
def result = MapperUtil.toMachineRequirement(
183184
new MachineRequirementOpts([arch: 'x86_64']),
184185
null,
185-
MemoryUnit.of('200 GB')
186+
MemoryUnit.of('200 GB'),
187+
false
186188
)
187189

188190
then:
@@ -193,7 +195,7 @@ class MapperUtilTest extends Specification {
193195

194196
def 'should return machine requirement with only disk' () {
195197
when:
196-
def result = MapperUtil.toMachineRequirement(null, null, MemoryUnit.of('100 GB'))
198+
def result = MapperUtil.toMachineRequirement(null, null, MemoryUnit.of('100 GB'), false)
197199

198200
then:
199201
result != null
@@ -204,7 +206,7 @@ class MapperUtilTest extends Specification {
204206

205207
def 'should return null when no arch, no opts, and no disk' () {
206208
expect:
207-
MapperUtil.toMachineRequirement(null, null, null) == null
209+
MapperUtil.toMachineRequirement(null, null, null, false) == null
208210
}
209211

210212
// tests for custom disk configuration options
@@ -295,7 +297,7 @@ class MapperUtilTest extends Specification {
295297
])
296298

297299
when:
298-
def result = MapperUtil.toMachineRequirement(opts, null, MemoryUnit.of('500 GB'))
300+
def result = MapperUtil.toMachineRequirement(opts, null, MemoryUnit.of('500 GB'), false)
299301

300302
then:
301303
result.arch == 'arm64'
@@ -367,7 +369,7 @@ class MapperUtilTest extends Specification {
367369
])
368370

369371
when:
370-
def result = MapperUtil.toMachineRequirement(opts, null, MemoryUnit.of('200 GB'))
372+
def result = MapperUtil.toMachineRequirement(opts, null, MemoryUnit.of('200 GB'), false)
371373

372374
then:
373375
result.arch == 'x86_64'
@@ -457,4 +459,51 @@ class MapperUtilTest extends Specification {
457459
e.message.contains('diskEncrypted')
458460
}
459461

462+
// tests for snapshot maxSpotAttempts defaulting
463+
464+
def 'should return machine requirement with only snapshot enabled' () {
465+
when:
466+
def result = MapperUtil.toMachineRequirement(null, null, null, true)
467+
468+
then:
469+
result != null
470+
result.snapshotEnabled == true
471+
result.maxSpotAttempts == FusionConfig.DEFAULT_SNAPSHOT_MAX_SPOT_ATTEMPTS
472+
}
473+
474+
def 'should use explicit maxSpotAttempts when snapshot enabled' () {
475+
when:
476+
def result = MapperUtil.toMachineRequirement(new MachineRequirementOpts([maxSpotAttempts: 2]), null, null, true)
477+
478+
then:
479+
result.snapshotEnabled == true
480+
result.maxSpotAttempts == 2
481+
}
482+
483+
def 'should not default maxSpotAttempts when snapshot disabled' () {
484+
when:
485+
def result = MapperUtil.toMachineRequirement(new MachineRequirementOpts([arch: 'x86_64']), null, null, false)
486+
487+
then:
488+
result.snapshotEnabled == null
489+
result.maxSpotAttempts == null
490+
}
491+
492+
def 'should combine snapshot with other machine requirement settings' () {
493+
when:
494+
def result = MapperUtil.toMachineRequirement(
495+
new MachineRequirementOpts([arch: 'arm64', provisioning: 'spot']),
496+
null,
497+
MemoryUnit.of('100 GB'),
498+
true
499+
)
500+
501+
then:
502+
result.arch == 'arm64'
503+
result.provisioning == ProvisioningModel.SPOT
504+
result.disk.sizeGiB == 100
505+
result.snapshotEnabled == true
506+
result.maxSpotAttempts == FusionConfig.DEFAULT_SNAPSHOT_MAX_SPOT_ATTEMPTS
507+
}
508+
460509
}

0 commit comments

Comments
 (0)