From d9be4673838235977024c036bf79f77d8b96323e Mon Sep 17 00:00:00 2001 From: Tibor Simko Date: Tue, 31 Jul 2018 18:14:02 +0200 Subject: [PATCH] cli: new setup-environment and run-example commands * Adds two new convenient helper commands to set up the shell environment and to run selected REANA examples with selected workflow engines. Useful for local integration testing purposes. Signed-off-by: Tibor Simko --- reana/cli.py | 172 ++++++++++++++++++++++++++++++++++++++++++---- tests/test_cli.py | 46 ++++++++++++- 2 files changed, 201 insertions(+), 17 deletions(-) diff --git a/reana/cli.py b/reana/cli.py index 4c3b8f09..aa9e442f 100644 --- a/reana/cli.py +++ b/reana/cli.py @@ -61,13 +61,11 @@ 'reana.io', ] - REPO_LIST_CLIENT = [ 'reana-commons', 'reana-client', ] - REPO_LIST_CLUSTER = [ 'reana-commons', 'reana-job-controller', @@ -81,12 +79,17 @@ 'reana-workflow-monitor', ] - REPO_LIST_CLUSTER_CLI = [ 'reana-commons', 'reana-cluster', ] +WORKFLOW_LIST_ALL = [ + 'cwl', + 'serial', + 'yadage' +] + @click.group() def cli(): # noqa: D301 @@ -123,6 +126,15 @@ def cli(): # noqa: D301 $ eval "$(reana git-fork -c ALL)" $ reana git-clone -c ALL + How to install latest ``master`` REANA cluster and client CLI scripts: + + .. code-block:: console + + \b + $ workon reana + $ reana install-client + $ reana install-cluster + How to compile and deploy latest ``master`` REANA cluster: .. code-block:: console @@ -134,23 +146,20 @@ def cli(): # noqa: D301 $ reana docker-images $ pip install reana-cluster $ reana-cluster -f reana-cluster-latest.yaml init - $ # we now have REANA cluster running "master" versions of components - How to install latest ``master`` REANA client CLI script: + How to set up your shell environment variables: .. code-block:: console \b - $ workon reana - $ reana install-client + $ eval $(reana setup-environment) - How to install latest ``master`` REANA cluster CLI script: + How to run full REANA example using a given workflow engine: .. code-block:: console \b - $ workon reana - $ reana install-cluster + $ reana run-example -c reana-demo-root6-roofit -w serial -s 10 How to test one component pull request: @@ -161,8 +170,6 @@ def cli(): # noqa: D301 $ reana git-checkout -b . 72 --fetch $ reana docker-build -c . $ kubectl delete pod -l app=job-controller - $ kubectl get pods - $ # we can now try to run an example How to test multiple component branches: @@ -175,8 +182,6 @@ def cli(): # noqa: D301 $ reana docker-build $ kubectl delete pod -l app=job-controller $ kubectl delete pod -l app=workflow-controller - $ kubectl get pods - $ # we can now try to run an example How to release and push cluster component images: @@ -190,6 +195,7 @@ def cli(): # noqa: D301 $ reana docker-build -t 0.3.0.dev20180625 $ reana docker-push -t 0.3.0.dev20180625 $ # we should now make PR for ``reana-cluster.yaml`` to use given tag + """ pass @@ -321,6 +327,26 @@ def select_components(components): return list(output) +def select_workflows(workflows): + """Return good workflow values that REANA supports for running examples. + + :param workflows: A list of workflow names such as 'cwl'. + :type components: list + + :return: Unique workflow names. + :rtype: list + + """ + output = set([]) + for workflow in workflows: + if workflow in WORKFLOW_LIST_ALL: + output.add(workflow) + else: + display_message('Ignoring unknown workflow {0}.'.format( + workflow)) + return list(output) + + def is_component_dockerised(component): """Return whether the component contains Dockerfile. @@ -339,6 +365,37 @@ def is_component_dockerised(component): return False +def is_component_runnable_example(component): + """Return whether the component contains reana.yaml. + + Useful for safety check when using some run-example commands for those + components that are not REANA examples. + + :param component: standard component name + :type component: str + + :return: True/False whether the component is a REANA example + :rtype: bool + + """ + if os.path.exists(get_srcdir(component) + os.sep + 'reana.yaml'): + return True + return False + + +def construct_workflow_name(example, workflow): + """Construct suitable workflow name for given REANA example and workflow. + + :param example: REANA example (e.g. reana-demo-root6-roofit) + :param workflow: workflow to use (cwl, serial, yadage) + :type example: str + :type workflow: str + """ + output = 'ci.{0}.{1}'.format(example.replace('reana-demo-', ''), + workflow) + return output + + def run_command(cmd, component=''): """Run given command in the given component source directory. @@ -355,7 +412,8 @@ def run_command(cmd, component=''): try: subprocess.run(cmd, shell=True, check=True) except subprocess.CalledProcessError as err: - sys.exit(err.cmd) + click.secho('[{0}] {1}'.format(component, err), bold=True) + sys.exit(err.returncode) def display_message(msg, component=''): @@ -899,3 +957,87 @@ def install_cluster(): # noqa: D301 'pip install . --upgrade', ]: run_command(cmd, component) + + +@cli.command(name='setup-environment') +def setup_environment(): # noqa: D301 + """Display commands to set up shell environment variables for local cluster. + + Display commands how to set up REANA_SERVER_URL and REANA_ACCESS_TOKEN + suitable for current local REANA cluster deployment. The output should be + passed to eval. + """ + my_reana_server_url_cmd = subprocess.getoutput('reana-cluster env') + my_reana_access_token = subprocess.getoutput('kubectl logs -l app=server |' + ' grep access_token: |' + ' awk \'{print $NF;}\' |' + ' tr -d \'[:space:]\'') + print(my_reana_server_url_cmd) + print('export REANA_ACCESS_TOKEN={0}'.format(my_reana_access_token)) + + +@click.option('--component', '-c', multiple=True, + default=['reana-demo-root6-roofit'], + help='Which examples to run? [reana-demo-root6-roofit]') +@click.option('--workflow', '-w', multiple=True, + default=['cwl', 'serial', 'yadage'], + help='Which workflow engine to run? [cwl,serial,yadage]') +@click.option('--output', '-o', multiple=True, + default=['plot.png'], + help='Expected output file? [plot.png]') +@click.option('--sleep', '-s', default=60, + help='How much seconds to wait for results? [60]') +@cli.command(name='run-example') +def run_example(component, workflow, output, sleep): # noqa: D301 + """Run given REANA example with given workflow engine. + + \b + :param component: The option ``component`` can be repeated. The value is + the repository name of the example. + [default=reana-demo-root6-roofit] + :param workflow: The option ``workflow`` can be repeated. The value is the + workflow engine to use to run the example. + [default=cwl,serial,yadage] + :param output: The option ``output`` can be repeated. The value is the + expected output file the workflow should produce. + [default=plot.png] + :param sleep: How much seconds to sleep in order to wait for workflow to be + finished before checking the results? [default=60] + + :type component: str + :type workflow: str + :type sleep: int + + """ + components = select_components(component) + workflows = select_workflows(workflow) + reana_yaml = { + 'cwl': 'reana-cwl.yaml', + 'serial': 'reana.yaml', + 'yadage': 'reana-yadage.yaml', + } + for component in components: + for workflow in workflows: + workflow_name = construct_workflow_name(component, workflow) + # run example workflow: + for cmd in [ + 'reana-client create -f {0} -n {1}'.format( + reana_yaml[workflow], workflow_name), + 'reana-client upload ./code ./inputs -w {0}'.format( + workflow_name), + 'reana-client start -w {0}'.format( + workflow_name), + 'sleep {0}'.format(sleep), + 'reana-client status -w {0}'.format( + workflow_name), + 'reana-client list -w {0}'.format( + workflow_name), + ]: + run_command(cmd, component) + # verify output file presence: + for output_file in output: + cmd = 'reana-client list -w {0} | grep -q {1}'.format( + workflow_name, output_file) + run_command(cmd, component) + # report status; everything was OK + run_command('echo OK', component) diff --git a/tests/test_cli.py b/tests/test_cli.py index 91aa95f1..47879916 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -42,8 +42,20 @@ def test_is_component_dockerised(): """Tests for is_component_dockerised().""" from reana.cli import is_component_dockerised if os.environ.get('REANA_SRCDIR'): - assert is_component_dockerised('reana-workflow-controller') is True - assert is_component_dockerised('reana-cluster') is False + assert is_component_dockerised( + 'reana-workflow-controller') is True + assert is_component_dockerised( + 'reana-cluster') is False + + +def test_is_component_runnable_example(): + """Tests for is_component_runnable_example().""" + from reana.cli import is_component_runnable_example + if os.environ.get('REANA_SRCDIR'): + assert is_component_runnable_example( + 'reana-workflow-controller') is False + assert is_component_runnable_example( + 'reana-demo-worldpopulation') is True def test_select_components(): @@ -76,6 +88,24 @@ def test_select_components(): assert output_obtained.sort() == output_expected.sort() +def test_select_workflows(): + """Tests for select_workflows().""" + from reana.cli import select_workflows + for (input_value, output_expected) in ( + # regular workflows: + (['cwl', ], ['cwl', ]), + (['serial', ], ['serial', ]), + (['cwl', 'yadage', ], ['cwl', 'yadage, ']), + # bad values: + (['nonsense', ], []), + (['nonsense', 'cwl', ], ['cwl', ]), + # output uniqueness: + (['cwl', 'cwl', ], ['cwl', ]), + ): + output_obtained = select_workflows(input_value) + assert output_obtained.sort() == output_expected.sort() + + def test_find_standard_component_name(): """Tests for find_standard_component_name().""" from reana.cli import find_standard_component_name @@ -97,3 +127,15 @@ def test_uniqueness_of_short_names(): if short_name in short_names: raise Exception('Found ') short_names.append(short_name) + + +def test_construct_workflow_name(): + """Tests for construct_workflow_name().""" + from reana.cli import construct_workflow_name + for (input_value, output_expected) in ( + (('reana', 'cwl'), 'ci.reana.cwl'), + (('reana-demo-root6-roofit', 'yadage'), 'ci.root6-roofit.yadage'), + ): + output_obtained = construct_workflow_name(input_value[0], + input_value[1]) + assert output_obtained == output_expected