Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate platform settings (background job runner) #4938

Merged
merged 9 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ Fourth Release Candidate for Cylc 8 suitable for acceptance testing.

### Enhancements

[#4936](https://github.com/cylc/cylc-flow/pull/4936) - Fix incorrect
error messages when workflow CLI commands fail.
[#4938](https://github.com/cylc/cylc-flow/pull/4938) - Detect bad Platforms
config: background and at job runners should have a single host.

[#4877](https://github.com/cylc/cylc-flow/pull/4877) - Upgrade the version of
Jinja2 used by Cylc from 2.11 to 3.0.
Expand All @@ -49,6 +49,9 @@ in `global.cylc[install]source dirs`.

### Fixes

[#4936](https://github.com/cylc/cylc-flow/pull/4936) - Fix incorrect
error messages when workflow CLI commands fail.

[#4931](https://github.com/cylc/cylc-flow/pull/4931) - Fix cylc install for
installing workflows from multi-level directories.

Expand Down
10 changes: 10 additions & 0 deletions cylc/flow/cfgspec/globalcfg.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

from cylc.flow import LOG
from cylc.flow import __version__ as CYLC_VERSION
from cylc.flow.platforms import validate_platforms
from cylc.flow.exceptions import GlobalConfigError
from cylc.flow.hostuserutil import get_user_home
from cylc.flow.network.client_factory import CommsMeth
Expand Down Expand Up @@ -1679,6 +1680,13 @@ def default_for(
the name of this platform group.

.. versionadded:: 8.0.0

.. note::

Some job runners ("background", "at") require a single-host
platform, because the job ID is only valid on the submission
host.

''')
with Conf('selection'):
Conf(
Expand Down Expand Up @@ -1858,6 +1866,8 @@ def load(self) -> None:

self._set_default_editors()
self._no_platform_group_name_overlap()
with suppress(KeyError):
validate_platforms(self.sparse['platforms'])

def _validate_source_dirs(self) -> None:
"""Check source dirs are absolute paths."""
Expand Down
38 changes: 37 additions & 1 deletion cylc/flow/platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@
from cylc.flow import LOG

from cylc.flow.exceptions import (
GlobalConfigError,
PlatformLookupError, CylcError, NoHostsError, NoPlatformsError)
from cylc.flow.cfgspec.glbl_cfg import glbl_cfg
from cylc.flow.hostuserutil import is_remote_host


UNKNOWN_TASK = 'unknown task'

FORBIDDEN_WITH_PLATFORM: Tuple[Tuple[str, str, List[Optional[str]]], ...] = (
Expand All @@ -39,6 +39,9 @@
('job', 'batch submit command template', [None])
)

DEFAULT_JOB_RUNNER = 'background'
SINGLE_HOST_JOB_RUNNERS = ['background', 'at']

# Regex to check whether a string is a command
HOST_REC_COMMAND = re.compile(r'(`|\$\()\s*(.*)\s*([`)])$')
PLATFORM_REC_COMMAND = re.compile(r'(\$\()\s*(.*)\s*([)])$')
Expand Down Expand Up @@ -658,3 +661,36 @@ def get_localhost_install_target() -> str:
"""Returns the install target of localhost platform"""
localhost = get_platform()
return get_install_target_from_platform(localhost)


def _validate_single_host(platforms_cfg) -> None:
"""Check that single-host platforms only specify a single host.

Some job runners don't work across multiple hosts; the job ID is only valid
on the specific submission host.
"""
bad_platforms = []
runners = set()
for name, config in platforms_cfg.items():
runner = config.get('job runner', DEFAULT_JOB_RUNNER)
hosts = config.get('hosts', [])
if runner in SINGLE_HOST_JOB_RUNNERS and len(hosts) > 1:
bad_platforms.append((name, runner, hosts))
runners.add(runner)
if bad_platforms:
if len(runners) > 1:
grammar = ["are", "s"]
else:
grammar = ["is a", ""]
msg = (
f"{', '.join(runners)} {grammar[0]} single-host"
f" job runner{grammar[1]}:"
)
for name, runner, hosts in bad_platforms:
msg += f'\n * Platform {name} ({runner}) hosts: {", ".join(hosts)}'
raise GlobalConfigError(msg)


def validate_platforms(platforms_cfg: Dict[str, Any]) -> None:
"""Check for invalid or inconsistent platforms config."""
_validate_single_host(platforms_cfg)
4 changes: 3 additions & 1 deletion tests/functional/cylc-config/09-platforms.t
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ set_test_number 5
cat > "global.cylc" <<__HEREDOC__
[platforms]
[[foo]]
job runner = slurm
hosts = of_melkor, of_valar
[[bar]]
job runner = slurm
hosts = of_orcs, of_gondor
[platform groups]
[[FOO]]
Expand Down Expand Up @@ -58,7 +60,7 @@ Platform Groups
__HEREDOC__

TEST_NAME="${TEST_NAME_BASE}-s"
head -n 8 > just_platforms < global.cylc
head -n 10 > just_platforms < global.cylc
run_ok "${TEST_NAME}" cylc config --platforms
cmp_ok "${TEST_NAME}.stdout" "just_platforms"

Expand Down
1 change: 1 addition & 0 deletions tests/functional/cylc-config/10-platform-expansion.t
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ cat > "global.cylc" <<__HEREDOC__
baz\d\d, qux\S\S \
]]
hosts = of_melkor, of_valar
job runner = slurm
__HEREDOC__

export CYLC_CONF_PATH="${PWD}"
Expand Down
6 changes: 6 additions & 0 deletions tests/functional/intelligent-host-selection/00-mixedhost.t
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export REQUIRE_PLATFORM='loc:remote fs:indep comms:tcp'
#-------------------------------------------------------------------------------
set_test_number 4

# Uses a fake background job runner to get around the single host restriction.

create_test_global_config "" "
[platforms]
[[goodhostplatform]]
Expand All @@ -32,6 +34,7 @@ create_test_global_config "" "
retrieve job logs = True

[[mixedhostplatform]]
job runner = my_background
hosts = unreachable_host, ${CYLC_TEST_HOST}
install target = ${CYLC_TEST_INSTALL_TARGET}
retrieve job logs = True
Expand All @@ -42,6 +45,9 @@ create_test_global_config "" "

install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"

# Install the fake background job runner.
cp -r "${TEST_SOURCE_DIR}/lib" "${WORKFLOW_RUN_DIR}"

run_ok "${TEST_NAME_BASE}-validate" cylc validate "${WORKFLOW_NAME}"

workflow_run_ok "${TEST_NAME_BASE}-run" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ set_test_number 3

install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"

# Install the fake background job runner.
cp -r "${TEST_SOURCE_DIR}/lib" "${WORKFLOW_RUN_DIR}"

create_test_global_config '' "
[scheduler]
[[main loop]]
Expand Down
7 changes: 7 additions & 0 deletions tests/functional/intelligent-host-selection/02-badhosts.t
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@ export REQUIRE_PLATFORM='loc:remote fs:indep comms:tcp'
#-------------------------------------------------------------------------------
set_test_number 6

# Uses a fake background job runner to get around the single host restriction.

create_test_global_config "" "
[platforms]
[[badhostplatform]]
job runner = my_background
hosts = e9755ca30f5, 3c0b4799402
install target = ${CYLC_TEST_INSTALL_TARGET}
[[[selection]]]
Expand All @@ -39,6 +42,7 @@ create_test_global_config "" "
retrieve job logs = True

[[mixedhostplatform]]
job runner = my_background
hosts = unreachable_host, ${CYLC_TEST_HOST}
install target = ${CYLC_TEST_INSTALL_TARGET}
retrieve job logs = True
Expand All @@ -51,6 +55,9 @@ create_test_global_config "" "

install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"

# Install the fake background job runner.
cp -r "${TEST_SOURCE_DIR}/lib" "${WORKFLOW_RUN_DIR}"

run_ok "${TEST_NAME_BASE}-validate" cylc validate "${WORKFLOW_NAME}"

workflow_run_ok "${TEST_NAME_BASE}-run" \
Expand Down
6 changes: 6 additions & 0 deletions tests/functional/intelligent-host-selection/03-polling.t
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export REQUIRE_PLATFORM='loc:remote fs:indep'
#-------------------------------------------------------------------------------
set_test_number 4

# Uses a fake background job runner to get around the single host restriction.

create_test_global_config "" "
[scheduler]
[[main loop]]
Expand All @@ -46,6 +48,7 @@ create_test_global_config "" "
submission polling intervals = PT0S, 5*PT2S

[[mixedhostplatform]]
job runner = my_background
hosts = unreachable_host, ${CYLC_TEST_HOST}
install target = ${CYLC_TEST_INSTALL_TARGET}
retrieve job logs = True
Expand All @@ -58,6 +61,9 @@ create_test_global_config "" "
#-------------------------------------------------------------------------------
install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"

# Install the fake background job runner.
cp -r "${TEST_SOURCE_DIR}/lib" "${WORKFLOW_RUN_DIR}"

run_ok "${TEST_NAME_BASE}-validate" cylc validate "${WORKFLOW_NAME}"

workflow_run_ok "${TEST_NAME_BASE}-run" \
Expand Down
6 changes: 6 additions & 0 deletions tests/functional/intelligent-host-selection/04-kill.t
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export REQUIRE_PLATFORM='loc:remote fs:indep comms:tcp'
#-------------------------------------------------------------------------------
set_test_number 4

# Uses a fake background job runner to get around the single host restriction.

create_test_global_config "" "
[scheduler]
[[main loop]]
Expand All @@ -36,6 +38,7 @@ create_test_global_config "" "
install target = ${CYLC_TEST_INSTALL_TARGET}

[[mixedhostplatform]]
job runner = my_background
hosts = unreachable_host, ${CYLC_TEST_HOST}
install target = ${CYLC_TEST_INSTALL_TARGET}
[[[selection]]]
Expand All @@ -45,6 +48,9 @@ create_test_global_config "" "

install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"

# Install the fake background job runner.
cp -r "${TEST_SOURCE_DIR}/lib" "${WORKFLOW_RUN_DIR}"

run_ok "${TEST_NAME_BASE}-validate" cylc validate "${WORKFLOW_NAME}"

workflow_run_ok "${TEST_NAME_BASE}-run" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ export REQUIRE_PLATFORM='loc:remote fs:indep comms:tcp'
#-------------------------------------------------------------------------------
set_test_number 11

# Uses a fake background job runner to get around the single host restriction.

create_test_global_config "" "
[platforms]
[[${CYLC_TEST_PLATFORM}]]
# mixed host platform
job runner = my_background
hosts = unreachable_host, ${CYLC_TEST_HOST}
[[[selection]]]
method = 'definition order'
[[badhostplatform]]
job runner = my_background
hosts = bad_host1, bad_host2
[[[selection]]]
method = 'definition order'
Expand All @@ -54,6 +58,9 @@ create_test_global_config "" "

install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"

# Install the fake background job runner.
cp -r "${TEST_SOURCE_DIR}/lib" "${WORKFLOW_RUN_DIR}"

run_ok "${TEST_NAME_BASE}-validate" cylc validate "${WORKFLOW_NAME}"

workflow_run_ok "${TEST_NAME_BASE}-run" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@
. "$(dirname "$0")/test_header"
set_test_number 12
#-------------------------------------------------------------------------------

# Uses a fake background job runner to get around the single host restriction.

create_test_global_config "" "
[platforms]
[[badhostplatform1]]
job runner = my_background
hosts = bad_host1, bad_host2
[[badhostplatform2]]
job runner = my_background
hosts = bad_host3, bad_host4

[platform groups]
Expand All @@ -36,6 +41,9 @@ create_test_global_config "" "

install_workflow "${TEST_NAME_BASE}" "${TEST_NAME_BASE}"

# Install the fake background job runner.
cp -r "${TEST_SOURCE_DIR}/lib" "${WORKFLOW_RUN_DIR}"

run_ok "${TEST_NAME_BASE}-validate" cylc validate "${WORKFLOW_NAME}"

workflow_run_fail "${TEST_NAME_BASE}-run" \
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3

# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from cylc.flow.job_runner_handlers.background import BgCommandHandler


class MyBgCommandHandler(BgCommandHandler):
pass


JOB_RUNNER_HANDLER = MyBgCommandHandler()
33 changes: 31 additions & 2 deletions tests/unit/test_platforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@
platform_from_name, platform_from_job_info,
get_install_target_from_platform,
get_install_target_to_platforms_map,
generic_items_match
generic_items_match,
_validate_single_host
)
from cylc.flow.exceptions import (
PlatformLookupError,
GlobalConfigError
)
from cylc.flow.exceptions import PlatformLookupError

PLATFORMS = {
'desktop[0-9]{2}|laptop[0-9]{2}': {
Expand Down Expand Up @@ -108,6 +112,20 @@
}


PLATFORMS_INVALID = {
'enterprise': {
'hosts': ['kirk', 'picard'],
'install target': 'picard',
'job runner': 'background' # requires one host
},
'voyager': {
'hosts': ['janeway', 'seven-of-nine'],
'install target': 'janeway',
'job runner': 'at' # requires one host
}
}


# ----------------------------------------------------------------------------
# Tests of platform_from_name
# ----------------------------------------------------------------------------
Expand Down Expand Up @@ -171,6 +189,17 @@ def test_platform_not_there():
platform_from_name('moooo', PLATFORMS)


@pytest.mark.parametrize(
'platform',
[
{i: j} for i, j in PLATFORMS_INVALID.items()
]
)
def test_invalid_platforms(platform):
with pytest.raises(GlobalConfigError):
_validate_single_host(platform)


def test_similar_but_not_exact_match():
with pytest.raises(PlatformLookupError):
platform_from_name('vld1', PLATFORMS_WITH_RE)
Expand Down