@@ -250,9 +250,15 @@ def _handle_pod_message(self, msg: PodMessage) -> None:
250
250
self ._set_step_error (step_name , r_wfid , r_wfsid , exit_code , "Job failed" )
251
251
return
252
252
253
- # If we get here the prior step completed successfully
254
- # and so we can mark the Step as DOne (successfully),
255
- # and then inspect the Workflow to determine the next step.
253
+ # If we get here the prior step completed successfully and we can decide
254
+ # whether the step has outputs (files) that need to be written to the
255
+ # Project directory, while also marking the Step as DONE (successfully).
256
+ # We pass the outputs to the DM via a call to the API adapter's realise_outputs().
257
+ # In return it copies (links) these files to the Project directory.
258
+ #
259
+ # We then inspect the Workflow to determine the next step.
260
+
261
+ # ToDo
256
262
257
263
self ._wapi_adapter .set_running_workflow_step_done (
258
264
running_workflow_step_id = r_wfsid ,
@@ -319,11 +325,18 @@ def _validate_step_command(
319
325
* ,
320
326
running_workflow_step_id : str ,
321
327
step : dict [str , Any ],
328
+ workflow_steps : list [dict [str , Any ]],
329
+ our_step_index : int ,
322
330
running_workflow_variables : dict [str , Any ] | None = None ,
323
331
) -> str | dict [str , Any ]:
324
332
"""Returns an error message if the command isn't valid.
325
333
Without a message we return all the variables that were (successfully)
326
- applied to the command."""
334
+ applied to the command.
335
+
336
+ We are also given a list of steps in workflow_steps and out position in
337
+ the list with our_step_index."""
338
+ assert our_step_index >= 0
339
+
327
340
# We get the Job from the step specification, which must contain
328
341
# the keys "collection", "job", and "version". Here we assume that
329
342
# the workflow definition has passed the RUN-level validation
@@ -380,52 +393,46 @@ def _validate_step_command(
380
393
if running_workflow_variables :
381
394
all_variables |= running_workflow_variables
382
395
383
- # This gives all the running workflow and step-specific variables.
384
- # Now we have to inspect the workflow step 'inputs' (and 'options')
385
- # and see if there are further variables that need constructing
386
- # and then adding (merging) into the 'all_variables' dictionary.
387
-
388
- wf_step_data , _ = self ._wapi_adapter .get_workflow_steps_driving_this_step (
389
- running_workflow_step_id = running_workflow_step_id ,
390
- )
391
-
392
396
# We must always process the current step's variables
393
397
_LOGGER .debug ("Validating step %s (%s)" , step , running_workflow_step_id )
394
398
inputs = step .get ("inputs" , [])
395
399
outputs = step .get ("outputs" , [])
396
400
previous_step_outputs = []
397
- our_index : int = wf_step_data ["caller_step_index" ]
398
- assert our_index >= 0
399
401
_LOGGER .debug (
400
- "We are at workflow step index %d (%s)" , our_index , running_workflow_step_id
402
+ "We are at workflow step index %d (%s)" ,
403
+ our_step_index ,
404
+ running_workflow_step_id ,
401
405
)
402
406
403
- if our_index > 0 :
407
+ if our_step_index > 0 :
404
408
# resolve all previous steps
405
409
previous_step_names = set ()
406
410
for inp in inputs :
407
411
if step_name := inp ["from" ].get ("step" , None ):
408
412
previous_step_names .add (step_name )
409
413
410
- for step in wf_step_data [ "steps" ] :
414
+ for step in workflow_steps :
411
415
if step ["name" ] in previous_step_names :
412
416
previous_step_outputs .extend (step .get ("outputs" , []))
413
417
414
418
_LOGGER .debug (
415
419
"Index %s (%s) workflow_variables=%s" ,
416
- our_index ,
420
+ our_step_index ,
417
421
running_workflow_step_id ,
418
422
all_variables ,
419
423
)
420
424
_LOGGER .debug (
421
- "Index %s (%s) inputs=%s" , our_index , running_workflow_step_id , inputs
425
+ "Index %s (%s) inputs=%s" , our_step_index , running_workflow_step_id , inputs
422
426
)
423
427
_LOGGER .debug (
424
- "Index %s (%s) outputs=%s" , our_index , running_workflow_step_id , outputs
428
+ "Index %s (%s) outputs=%s" ,
429
+ our_step_index ,
430
+ running_workflow_step_id ,
431
+ outputs ,
425
432
)
426
433
_LOGGER .debug (
427
434
"Index %s (%s) previous_step_outputs=%s" ,
428
- our_index ,
435
+ our_step_index ,
429
436
running_workflow_step_id ,
430
437
previous_step_outputs ,
431
438
)
@@ -452,7 +459,7 @@ def _validate_step_command(
452
459
all_variables |= step_vars
453
460
_LOGGER .debug (
454
461
"Index %s (%s) all_variables=%s" ,
455
- our_index ,
462
+ our_step_index ,
456
463
running_workflow_step_id ,
457
464
all_variables ,
458
465
)
@@ -481,6 +488,15 @@ def _launch(
481
488
482
489
_LOGGER .info ("Validating step command: %s (step=%s)..." , rwf_id , step_name )
483
490
491
+ # Get step data - importantly, giving us the sequence of steps in the response.
492
+ # Steps will be in wf_step_data["steps"] and our position in the list
493
+ # is wf_step_data["caller_step_index"]
494
+ wf_step_data , _ = self ._wapi_adapter .get_workflow_steps_driving_this_step (
495
+ running_workflow_step_id = rwfs_id ,
496
+ )
497
+ assert wf_step_data ["caller_step_index" ] >= 0
498
+ our_step_index : int = wf_step_data ["caller_step_index" ]
499
+
484
500
# Now check the step command can be executed
485
501
# (by trying to decoding the Job command).
486
502
#
@@ -491,6 +507,8 @@ def _launch(
491
507
error_or_variables : str | dict [str , Any ] = self ._validate_step_command (
492
508
running_workflow_step_id = rwfs_id ,
493
509
step = step ,
510
+ workflow_steps = wf_step_data ["steps" ],
511
+ our_step_index = our_step_index ,
494
512
running_workflow_variables = running_workflow_variables ,
495
513
)
496
514
if isinstance (error_or_variables , str ):
@@ -514,6 +532,36 @@ def _launch(
514
532
variables ,
515
533
)
516
534
535
+ # When we launch a step we need to identify all the prior steps in the workflow,
536
+ # those we depend on. The DataManager will then link their outputs to
537
+ # out instance directory. For simple workflows there is only one prior step,
538
+ # and it's the one immediately prior to this one.
539
+ #
540
+ # We put all the prior step IDs in: -
541
+ # 'running_workflow_step_prior_steps'
542
+ # A list of step UUID strings.
543
+ #
544
+ # In this 'simple' linear implementation that is simply the immediately
545
+ # preceding step.
546
+ prior_steps : list [str ] = []
547
+ if our_step_index > 0 :
548
+ # We need the step ID of the prior step.
549
+ prior_step_name : str = wf_step_data ["steps" ][our_step_index - 1 ]["name" ]
550
+ step_response , _ = self ._wapi_adapter .get_running_workflow_step_by_name (
551
+ name = prior_step_name ,
552
+ running_workflow_id = rwf_id ,
553
+ )
554
+ assert "id" in step_response
555
+ prior_steps .append (step_response ["id" ])
556
+
557
+ # We must also identify workflow inputs that are required by the step we are
558
+ # about to launch and pass those using: -
559
+ #
560
+ # 'running_workflow_step_inputs'
561
+ # A list of string pairs (input filename and output filename)
562
+ # (with relative paths if appropriate.
563
+ inputs : list [tuple [str , str ]] | None = None
564
+
517
565
lp : LaunchParameters = LaunchParameters (
518
566
project_id = project_id ,
519
567
name = step_name ,
@@ -524,6 +572,8 @@ def _launch(
524
572
specification_variables = variables ,
525
573
running_workflow_id = rwf_id ,
526
574
running_workflow_step_id = rwfs_id ,
575
+ running_workflow_step_prior_steps = prior_steps ,
576
+ running_workflow_step_inputs = inputs ,
527
577
)
528
578
lr : LaunchResult = self ._instance_launcher .launch (launch_parameters = lp )
529
579
if lr .error_num :
0 commit comments