Skip to content

Commit c3e1d20

Browse files
wxtimMetRonnie
andauthored
6808.fix Platform selection from subshell (#6836)
Prevent evaluated expression being pushed permenantly into a task rtconfig. * Avoid changing the task runtime config object. --------- Co-authored-by: Ronnie Dutta <[email protected]>
1 parent 40670d6 commit c3e1d20

File tree

8 files changed

+181
-14
lines changed

8 files changed

+181
-14
lines changed

changes.d/6836.fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug causing the results of `platform = $(subshell commands)` to be cached, and preventing re-evaluation for each task with the same config.

cylc/flow/platforms.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ def log_platform_event(
8383
def get_platform(
8484
task_conf: Optional[str] = None,
8585
task_name: str = UNKNOWN_TASK,
86-
bad_hosts: Optional[Set[str]] = None
86+
bad_hosts: Optional[Set[str]] = None,
87+
evaluated_host: Optional[str] = None,
8788
) -> Dict[str, Any]:
8889
...
8990

@@ -92,7 +93,8 @@ def get_platform(
9293
def get_platform(
9394
task_conf: Union[dict, 'OrderedDictWithDefaults'],
9495
task_name: str = UNKNOWN_TASK,
95-
bad_hosts: Optional[Set[str]] = None
96+
bad_hosts: Optional[Set[str]] = None,
97+
evaluated_host: Optional[str] = None,
9698
) -> Optional[Dict[str, Any]]:
9799
...
98100

@@ -108,7 +110,8 @@ def get_platform(
108110
def get_platform(
109111
task_conf: Union[str, dict, 'OrderedDictWithDefaults', None] = None,
110112
task_name: str = UNKNOWN_TASK,
111-
bad_hosts: Optional[Set[str]] = None
113+
bad_hosts: Optional[Set[str]] = None,
114+
evaluated_host: Optional[str] = None,
112115
) -> Optional[Dict[str, Any]]:
113116
"""Get a platform.
114117
@@ -121,6 +124,7 @@ def get_platform(
121124
task_name: Help produce more helpful error messages.
122125
bad_hosts: A set of hosts known to be unreachable (had an ssh 255
123126
error)
127+
evaluated_host: Host name evaluated from platform subshell.
124128
125129
Returns:
126130
platform: A platform definition dictionary. Uses either
@@ -169,6 +173,7 @@ def get_platform(
169173
glbl_cfg().get(['platforms']),
170174
task_job_section,
171175
task_remote_section,
176+
evaluated_host,
172177
),
173178
bad_hosts=bad_hosts,
174179
)
@@ -330,7 +335,8 @@ def get_platform_from_group(
330335
def platform_name_from_job_info(
331336
platforms: Union[dict, 'OrderedDictWithDefaults'],
332337
job: Dict[str, Any],
333-
remote: Dict[str, Any]
338+
remote: Dict[str, Any],
339+
evaluated_host: Optional[str] = None,
334340
) -> str:
335341
"""
336342
Find out which job platform to use given a list of possible platforms
@@ -385,6 +391,7 @@ def platform_name_from_job_info(
385391
job: Workflow config [runtime][TASK][job] section.
386392
remote: Workflow config [runtime][TASK][remote] section.
387393
platforms: Dictionary containing platform definitions.
394+
evaluated_host: Host is the result of evaluating a subshell.
388395
389396
Returns:
390397
platform: string representing a platform from the global config.
@@ -422,7 +429,9 @@ def platform_name_from_job_info(
422429

423430
# NOTE: Do NOT use .get() on OrderedDictWithDefaults -
424431
# https://github.com/cylc/cylc-flow/pull/4975
425-
if 'host' in remote and remote['host']:
432+
if evaluated_host:
433+
task_host = evaluated_host
434+
elif 'host' in remote and remote['host']:
426435
task_host = remote['host']
427436
else:
428437
task_host = 'localhost'

cylc/flow/task_job_mgr.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,10 +1220,10 @@ def _prep_submit_task_job(
12201220
f"\"{itask.identity}\" the following are not compatible:\n"
12211221
)
12221222

1223-
host_n, platform_name = None, None
1223+
host_name, platform_name = None, None
12241224
try:
12251225
if rtconfig['remote']['host'] is not None:
1226-
host_n = self.task_remote_mgr.eval_host(
1226+
host_name = self.task_remote_mgr.eval_host(
12271227
rtconfig['remote']['host']
12281228
)
12291229
else:
@@ -1242,27 +1242,26 @@ def _prep_submit_task_job(
12421242
return False
12431243
else:
12441244
# host/platform select not ready
1245-
if host_n is None and platform_name is None:
1245+
if host_name is None and platform_name is None:
12461246
return None
12471247
elif (
1248-
host_n is None
1248+
host_name is None
12491249
and rtconfig['platform']
12501250
and rtconfig['platform'] != platform_name
12511251
):
12521252
LOG.debug(
12531253
f"for task {itask.identity}: platform = "
12541254
f"{rtconfig['platform']} evaluated as {platform_name}"
12551255
)
1256-
rtconfig['platform'] = platform_name
1256+
12571257
elif (
12581258
platform_name is None
1259-
and rtconfig['remote']['host'] != host_n
1259+
and rtconfig['remote']['host'] != host_name
12601260
):
12611261
LOG.debug(
12621262
f"[{itask}] host = "
1263-
f"{rtconfig['remote']['host']} evaluated as {host_n}"
1263+
f"{rtconfig['remote']['host']} evaluated as {host_name}"
12641264
)
1265-
rtconfig['remote']['host'] = host_n
12661265

12671266
try:
12681267
platform = cast(
@@ -1271,7 +1270,10 @@ def _prep_submit_task_job(
12711270
# return early if the subshell is still evaluating.
12721271
'dict',
12731272
get_platform(
1274-
rtconfig, itask.tdef.name, bad_hosts=self.bad_hosts
1273+
platform_name or rtconfig,
1274+
itask.tdef.name,
1275+
bad_hosts=self.bad_hosts,
1276+
evaluated_host=host_name,
12751277
),
12761278
)
12771279
except PlatformLookupError as exc:
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
3+
# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
#-----------------------------------------------------------------------------
18+
19+
# If a task has a platform set using a subshell this should be evaluated
20+
# every time the task is run.
21+
# https://github.com/cylc/cylc-flow/issues/6808
22+
export REQUIRE_PLATFORM='loc:remote'
23+
24+
. "$(dirname "$0")/test_header"
25+
26+
set_test_number 3
27+
28+
install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"
29+
30+
workflow_run_ok "${TEST_NAME_BASE}-run" \
31+
cylc play "${WORKFLOW_NAME}" --debug --no-detach
32+
33+
named_grep_ok "1/remote_task submits to ${CYLC_TEST_PLATFORM}" \
34+
"\[1/remote_task/01:preparing\] submitted to ${CYLC_TEST_PLATFORM}" \
35+
"${WORKFLOW_RUN_DIR}/log/scheduler/log"
36+
37+
named_grep_ok "2/remote_task submits to localhost" \
38+
"\[2/remote_task/01:preparing\] submitted to localhost" \
39+
"${WORKFLOW_RUN_DIR}/log/scheduler/log"
40+
41+
purge
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!jinja2
2+
[scheduler]
3+
[[events]]
4+
stall timeout = PT0S
5+
6+
[scheduling]
7+
cycling mode = integer
8+
final cycle point = 2
9+
[[graph]]
10+
P1 = remote_task[-P1] => toggler => remote_task
11+
12+
[runtime]
13+
[[remote_task]]
14+
platform = $(cat ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info)
15+
16+
[[toggler]]
17+
script = """
18+
# Toggle the platform between localhost and the remote host
19+
# using the content of a file, ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info.
20+
# between localhost and the remote.
21+
22+
if (( $CYLC_TASK_CYCLE_POINT % 2 == 1 )); then
23+
echo ${REMOTE_PLATFORM} > ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info
24+
cylc message -- "changing platform to ${REMOTE_PLATFORM}"
25+
else
26+
echo "localhost" > ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info
27+
cylc message -- "changing platform to localhost"
28+
fi
29+
"""
30+
[[[environment]]]
31+
REMOTE_PLATFORM = {{ CYLC_TEST_PLATFORM }}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
3+
# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
4+
#
5+
# This program is free software: you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License as published by
7+
# the Free Software Foundation, either version 3 of the License, or
8+
# (at your option) any later version.
9+
#
10+
# This program is distributed in the hope that it will be useful,
11+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
# GNU General Public License for more details.
14+
#
15+
# You should have received a copy of the GNU General Public License
16+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
#-----------------------------------------------------------------------------
18+
19+
# If a task has [remote]host=$(subshell) this should be evaluated
20+
# every time the task is run.
21+
# https://github.com/cylc/cylc-flow/issues/6808
22+
export REQUIRE_PLATFORM='loc:remote'
23+
24+
. "$(dirname "$0")/test_header"
25+
26+
set_test_number 3
27+
28+
install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"
29+
30+
workflow_run_ok "${TEST_NAME_BASE}-run" \
31+
cylc play "${WORKFLOW_NAME}" --debug --no-detach
32+
33+
named_grep_ok "1/remote_task submits to ${CYLC_TEST_PLATFORM}" \
34+
"\[1/remote_task/01:preparing\] submitted to ${CYLC_TEST_HOST}" \
35+
"${WORKFLOW_RUN_DIR}/log/scheduler/log"
36+
37+
named_grep_ok "2/remote_task submits to localhost" \
38+
"\[2/remote_task/01:preparing\] submitted to localhost" \
39+
"${WORKFLOW_RUN_DIR}/log/scheduler/log"
40+
41+
purge
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!jinja2
2+
[scheduler]
3+
[[events]]
4+
stall timeout = PT0S
5+
6+
[scheduling]
7+
cycling mode = integer
8+
final cycle point = 2
9+
[[graph]]
10+
P1 = remote_task[-P1] => toggler => remote_task
11+
12+
[runtime]
13+
[[remote_task]]
14+
[[[remote]]]
15+
host = $(cat ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info)
16+
17+
[[toggler]]
18+
script = """
19+
# Toggle the platform between localhost and the remote host
20+
# using the content of a file, ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info.
21+
# between localhost and the remote.
22+
23+
if (( $CYLC_TASK_CYCLE_POINT % 2 == 1 )); then
24+
echo ${REMOTE_HOST} > ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info
25+
cylc message -- "changing platform to ${REMOTE_HOST}"
26+
else
27+
echo "localhost" > ${CYLC_WORKFLOW_RUN_DIR}/pretend-hall-info
28+
cylc message -- "changing platform to localhost"
29+
fi
30+
"""
31+
[[[environment]]]
32+
REMOTE_HOST = {{ CYLC_TEST_HOST }}

tests/unit/test_platforms.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ def test_platform_name_from_job_info_basic(job, remote, returns):
273273
assert platform_name_from_job_info(PLATFORMS, job, remote) == returns
274274

275275

276+
def test_platform_name_from_job_info_evaluated_hostname():
277+
result = platform_name_from_job_info(
278+
PLATFORMS,
279+
{'batch system': 'background'},
280+
{'host': '$(cat tiddles)'},
281+
evaluated_host='hpc2',
282+
)
283+
assert result == 'hpc2-bg'
284+
285+
276286
def test_platform_name_from_job_info_ordered_dict_comparison():
277287
"""Check that we are only comparing set items in OrderedDictWithDefaults.
278288
"""

0 commit comments

Comments
 (0)