Skip to content

Commit

Permalink
add Cylc VRO
Browse files Browse the repository at this point in the history
  • Loading branch information
wxtim committed Dec 14, 2022
1 parent b224a6a commit 6fc3ca3
Show file tree
Hide file tree
Showing 26 changed files with 643 additions and 29 deletions.
25 changes: 20 additions & 5 deletions cylc/flow/option_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@ def __init__(self, argslist, sources=None, useif=None, **kwargs):
self.kwargs.update({kwarg: value})

def __eq__(self, other):
"""Args and Kwargs, but not other props equal."""
"""Args and Kwargs, but not other props equal.
(Also make an exception for kwargs['help'] to allow lists of sources
prepended to 'help' to be passed through.)
"""
return (
self.kwargs == other.kwargs
(
{k: v for k, v in self.kwargs.items() if k != 'help'}
== {k: v for k, v in other.kwargs.items() if k != 'help'}
)
and self.args == other.args
)

Expand Down Expand Up @@ -729,9 +736,17 @@ def combine_options_pair(first_list, second_list):
and first & second
):
# if any of the args are different:
if first.args == second.args:
# if none of the arg names are different.
raise Exception(f'Clashing Options \n{first}\n{second}')

if (
first.args == second.args
# and (
# first.kwargs['help'].split('\x1b[0m ')[-1] !=
# second.kwargs['help'].split('\x1b[0m')[-1]
# )
):
# if none of the arg names are different
raise Exception(
f'Clashing Options \n{first.args}\n{second.args}')
else:
first_args = first - second
second.args = second - first
Expand Down
29 changes: 29 additions & 0 deletions cylc/flow/pathutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,3 +456,32 @@ def get_workflow_name_from_id(workflow_id: str) -> str:
name_path = id_path

return str(name_path.relative_to(cylc_run_dir))


def get_source_conf_from_id(workflow_id):
"""Give a workflow id, get the flow.cylc file of the source.
Additionally Check
1. Flow.cylc or suite.rc exists at source.
"""
# Avoid circular import:
from cylc.flow.workflow_files import WorkflowFiles
# Get path of source:
run_dir = Path(get_workflow_run_dir(workflow_id))
relative_path = run_dir.relative_to(get_cylc_run_dir())
src = (
Path(get_cylc_run_dir())
/ relative_path.parts[0]
/ WorkflowFiles.Install.DIRNAME
/ WorkflowFiles.Install.SOURCE
)

# Test whether there is a config file at source:
if (src / WorkflowFiles.FLOW_FILE).exists():
return src.resolve() / WorkflowFiles.FLOW_FILE
elif (src / WorkflowFiles.SUITE_RC).exists():
return src.resolve() / WorkflowFiles.SUITE_RC
else:
raise WorkflowFilesError(
f'Source file not present at: {src.resolve()}'
)
2 changes: 1 addition & 1 deletion cylc/flow/scripts/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ async def _main(
workflow_id,
flow_file,
options,
get_template_vars(options)
get_template_vars(options, flow_file)
)

config.pcfg.idump(
Expand Down
3 changes: 2 additions & 1 deletion cylc/flow/scripts/cylc.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ def get_version(long=False):
'shutdown': 'stop',
'task-message': 'message',
'unhold': 'release',
'validate-install-play': 'vip'
'validate-install-play': 'vip',
'validate-reinstall-reload': 'vro',
}


Expand Down
2 changes: 1 addition & 1 deletion cylc/flow/scripts/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ def _get_inheritance_nodes_and_edges(

def get_config(workflow_id: str, opts: 'Values', flow_file) -> WorkflowConfig:
"""Return a WorkflowConfig object for the provided reg / path."""
template_vars = get_template_vars(opts)
template_vars = get_template_vars(opts, flow_file)
return WorkflowConfig(
workflow_id, flow_file, opts, template_vars=template_vars
)
Expand Down
2 changes: 1 addition & 1 deletion cylc/flow/scripts/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ async def _main(parser: COP, options: 'Values', workflow_id: str) -> None:
src=True,
constraint='workflows',
)
template_vars = get_template_vars(options)
template_vars = get_template_vars(options, flow_file)

if options.all_tasks and options.all_namespaces:
parser.error("Choose either -a or -n")
Expand Down
2 changes: 1 addition & 1 deletion cylc/flow/scripts/psutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
the `psutil` on remote platforms.
Exits:
0 - If successfull.
0 - If successful.
2 - For errors in extracting results from psutil
1 - For all other errors.
"""
Expand Down
23 changes: 15 additions & 8 deletions cylc/flow/scripts/reinstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
from cylc.flow.id_cli import parse_id
from cylc.flow.option_parsers import (
CylcOptionParser as COP,
OptionSettings,
Options,
WORKFLOW_ID_ARG_DOC,
)
Expand All @@ -100,6 +101,18 @@
_input = input # to enable testing


REINSTALL_CYLC_ROSE_OPTIONS = [
OptionSettings(
['--clear-rose-install-options'],
help="Clear options previously set by cylc-rose.",
action='store_true',
default=False,
dest="clear_rose_install_opts",
sources={'reinstall'}
)
]


def get_option_parser() -> COP:
parser = COP(
__doc__, comms=True, argdoc=[WORKFLOW_ID_ARG_DOC]
Expand All @@ -112,14 +125,8 @@ def get_option_parser() -> COP:
except ImportError:
pass
else:
parser.add_option(
"--clear-rose-install-options",
help="Clear options previously set by cylc-rose.",
action='store_true',
default=False,
dest="clear_rose_install_opts"
)

for option in REINSTALL_CYLC_ROSE_OPTIONS:
parser.add_option(*option.args, **option.kwargs)
return parser


Expand Down
4 changes: 4 additions & 0 deletions cylc/flow/scripts/reload.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ async def run(options: 'Values', workflow_id: str) -> None:

@cli_function(get_option_parser)
def main(parser: COP, options: 'Values', *ids) -> None:
reload_cli(options, *ids)


def reload_cli(options: 'Values', *ids) -> None:
call_multi(
partial(run, options),
*ids,
Expand Down
2 changes: 1 addition & 1 deletion cylc/flow/scripts/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ async def wrapped_main(
workflow_id,
flow_file,
options,
get_template_vars(options),
get_template_vars(options, flow_file),
output_fname=options.output,
mem_log_func=profiler.log_memory
)
Expand Down
9 changes: 3 additions & 6 deletions cylc/flow/scripts/validate_install_play.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

from cylc.flow.scripts.validate import (
VALIDATE_OPTIONS,
_main as validate_main
_main as cylc_validate
)
from cylc.flow.scripts.install import (
INSTALL_OPTIONS, install_cli as cylc_install, get_source_location
Expand Down Expand Up @@ -75,10 +75,7 @@ def get_option_parser() -> COP:
]
)
for option in VIP_OPTIONS:
# Make a special exception for option against_source which makes
# no sense in a VIP context.
if option.kwargs.get('dest') != 'against_source':
parser.add_option(*option.args, **option.kwargs)
parser.add_option(*option.args, **option.kwargs)
return parser


Expand All @@ -92,7 +89,7 @@ def main(parser: COP, options: 'Values', workflow_id: Optional[str] = None):
source = get_source_location(workflow_id)

log_subcommand('validate', source)
validate_main(parser, options, str(source))
cylc_validate(parser, options, str(source))

log_subcommand('install', source)
workflow_id = cylc_install(options, workflow_id)
Expand Down
150 changes: 150 additions & 0 deletions cylc/flow/scripts/validate_reinstall_reload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
#!/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/>.

"""cylc validate-reinstall-reload/play [OPTIONS] ARGS
Validate and install a single workflow. Then if:
- Workflow running => reload.
- Workflow paused => resume.
- Workflow stopped => play.
This script is equivalent to:
$ cylc validate myworkflow --against-source # See note 1
$ cylc reinstall myworkflow
$ if [[ my workflow is running ]];
cylc reload myworkflow
else
cylc play myworkflow
Note 1:
Cylc validate myworkflow --against-source is eqivelent of (It doesn't write
any temporary files though):
# Install from run directory
$ cylc install ~/cylc-run/myworkflow -n temporary
# Install from source directory over the top
$ cylc install /path/to/myworkflow -n temporary
# Validate combined config
$ cylc validate ~/cylc-run/temporary
"""

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from optparse import Values

from cylc.flow.exceptions import ServiceFileError, CylcError
from cylc.flow.scheduler_cli import PLAY_OPTIONS, scheduler_cli
from cylc.flow.scripts.validate import (
VALIDATE_OPTIONS,
_main as cylc_validate
)
from cylc.flow.scripts.reinstall import (
REINSTALL_CYLC_ROSE_OPTIONS, reinstall_cli as cylc_reinstall
)
from cylc.flow.scripts.reload import (
reload_cli as cylc_reload
)
from cylc.flow.option_parsers import (
WORKFLOW_ID_ARG_DOC,
CylcOptionParser as COP,
combine_options,
log_subcommand,
cleanup_sysargv
)
from cylc.flow.terminal import cli_function
from cylc.flow.workflow_files import detect_old_contact_file


CYLC_ROSE_OPTIONS = COP.get_cylc_rose_options()
VRO_OPTIONS = combine_options(
VALIDATE_OPTIONS,
REINSTALL_CYLC_ROSE_OPTIONS,
PLAY_OPTIONS,
CYLC_ROSE_OPTIONS,
modify={'cylc-rose': 'validate, install'}
)


def get_option_parser() -> COP:
parser = COP(
__doc__,
comms=True,
jset=True,
argdoc=[WORKFLOW_ID_ARG_DOC],
)
for option in VRO_OPTIONS:
parser.add_option(*option.args, **option.kwargs)
return parser


@cli_function(get_option_parser)
def main(parser: COP, options: 'Values', workflow_id: str):
vro_cli(parser, options, workflow_id)


def vro_cli(parser: COP, options: 'Values', workflow_id: str):
"""Run Cylc (re)validate - reinstall - reload in sequence."""
# Attempt to work out whether the workflow is running.
# We are trying to avoid reinstalling a subsequently being
# unable to play or reload because we cannot identify workflow state.
try:
detect_old_contact_file(workflow_id, quiet=True)
except ServiceFileError:
# Workflow is definately still running:
workflow_running = True
except CylcError as exc:
# We can't tell whether the workflow is running.
# TODO - consider a more helpful error
raise exc
exit(1)
else:
# Workflow is definately stopped:
workflow_running = False

# Force on the against_source option:
options.against_source = True # Make validate check against source.
log_subcommand('validate --against-source', workflow_id)
cylc_validate(parser, options, workflow_id)

log_subcommand('reinstall', workflow_id)
cylc_reinstall(options, workflow_id)

# Run reload if workflow is running, else play:
if workflow_running:
log_subcommand('reload', workflow_id)
cylc_reload(options, workflow_id)

# run play anyway, to resume a paused workflow:
else:
cleanup_sysargv(
'play',
workflow_id,
options,
compound_script_opts=VRO_OPTIONS,
script_opts=(
PLAY_OPTIONS + CYLC_ROSE_OPTIONS
+ parser.get_std_options()
),
source='', # Intentionally blank
)
log_subcommand('play', workflow_id)
scheduler_cli(options, workflow_id)
1 change: 0 additions & 1 deletion cylc/flow/scripts/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def main(parser: COP, options: 'Values', workflow_id: str) -> None:

async def _main(parser: COP, options: 'Values', workflow_id: str) -> None:
workflow_id, _, flow_file = await parse_id_async(

workflow_id,
src=True,
constraint='workflows',
Expand Down
Loading

0 comments on commit 6fc3ca3

Please sign in to comment.