Various CI fixes

* Output CSV on build stage
* Check more failure states in pipeline
* Allow configs.py to use multiple groups
* Add build log as artifact
* Add links to particular build configs
* Host CMSIS_5 pack file internally
* Adding mbedcrypto url as a param
* Move the LAVA job generation into a new jenkins job
* Make job_ids strings for adding to job description

Change-Id: I801a1a5d15a7f55e25477ad371e8ec59eb14fd7f
Signed-off-by: Dean Birch <dean.birch@arm.com>
diff --git a/lava_helper/jinja2_templates/template_tfm_mps2_fvp.jinja2 b/lava_helper/jinja2_templates/template_tfm_mps2_fvp.jinja2
new file mode 100644
index 0000000..aec5bd2
--- /dev/null
+++ b/lava_helper/jinja2_templates/template_tfm_mps2_fvp.jinja2
@@ -0,0 +1,77 @@
+{#------------------------------------------------------------------------------
+# Copyright (c) 2018-2019, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-----------------------------------------------------------------------------#}
+{% extends 'jinja2_templates/base.jinja2' %}
+{% block metadata %}
+{{ super() }}
+{% endblock %}
+{% block base %}
+{{ super() }}
+{% endblock %}
+{% block actions %}
+context:
+  kernel_start_message: ''
+
+actions:
+- deploy:
+    namespace: docker
+    to: fvp
+    images:
+      ns:
+        url: {{ firmware_url }}
+      s:
+        url: {{ bootloader_url }}
+
+- boot:
+    namespace: docker
+    method: fvp
+    docker:
+      name: 'replace_docker_prefix/lava-fvp-mps2'
+    prompts:
+    - 'root@lava '
+    image: /opt/model/FVP_MPS2_AEMv8M
+    timeout:
+      minutes: 5
+    console_string: 'telnetterminal0: Listening for serial connection on port (?P<PORT>\d+)'
+    license_variable: 'replace_licence_variable'
+    arguments:
+    -  "--application cpu0={S}"
+    -  "--data cpu0={NS}@0x00100000"
+    -  "--simlimit 1200"
+    -  "--parameter fvp_mps2.platform_type=2"
+    -  "--parameter cpu0.INITVTOR_S=0x10000000"
+    -  "--parameter cpu0.semihosting-enable=0"
+    -  "--parameter fvp_mps2.DISABLE_GATING=0"
+    -  "--parameter fvp_mps2.telnetterminal0.start_telnet=1"
+    -  "--parameter fvp_mps2.telnetterminal1.start_telnet=1"
+    -  "--parameter fvp_mps2.telnetterminal2.start_telnet=1"
+    -  "--parameter fvp_mps2.telnetterminal0.quiet=0"
+    -  "--parameter fvp_mps2.telnetterminal1.quiet=0"
+    -  "--parameter fvp_mps2.telnetterminal2.quiet=0"
+    -  "--parameter fvp_mps2.UART0.unbuffered_output=1"
+    -  "--parameter fvp_mps2.UART0.shutdown_on_eot=1"
+    -  "--parameter fvp_mps2.UART1.unbuffered_output=1"
+    -  "--parameter fvp_mps2.UART1.shutdown_on_eot=1"
+    -  "--parameter fvp_mps2.UART2.unbuffered_output=1"
+    -  "--parameter fvp_mps2.UART2.shutdown_on_eot=1"
+    -  "--parameter fvp_mps2.mps2_visualisation.disable-visualisation=1"
+    -  "--parameter cpu0.baseline=1"
+    prompts:
+    - 'Jumping to non-secure code'
+
+- test:
+    namespace: target
+    monitors:
+    {%- for monitor in test.monitors %}
+    - name: "{{monitor.name}}_{{ platform }}_{{ compiler }}_{{ name }}_{{ build_type }}_{{ boot_type }}"
+      start: "{{monitor.start}}"
+      end: "{{monitor.end}}"
+      pattern: "{{monitor.pattern}}"
+      fixupdict:
+         '{{monitor.fixup.pass}}': pass
+         '{{monitor.fixup.fail}}': fail
+    {% endfor %}
+{% endblock %}
diff --git a/lava_helper/lava_helper_configs.py b/lava_helper/lava_helper_configs.py
index b1ddd4e..888b0f9 100644
--- a/lava_helper/lava_helper_configs.py
+++ b/lava_helper/lava_helper_configs.py
@@ -172,8 +172,137 @@
     }  # Tests
 }
 
+
+tfm_mps2_fvp = {
+    "templ": "template_tfm_mps2_fvp.jinja2",
+    "job_name": "mps-fvp",
+    "device_type": "fvp",
+    "job_timeout": 180,
+    "action_timeout": 90,
+    "monitor_timeout": 90,
+    "poweroff_timeout": 5,
+    "recovery_store_url": "%(jenkins_url)s/"
+                          "job/%(jenkins_job)s",
+    "artifact_store_url": "%(jenkins_url)s/"
+                          "job/%(jenkins_job)s",
+    "platforms": {"AN519": "mps2_an521_v3.0.tar.gz"},
+    "compilers": ["GNUARM"],
+    "build_types": ["Debug", "Release"],
+    "boot_types": ["BL2"],
+    "tests": {
+        'Default': {
+            "binaries": {
+                "firmware": "tfm_s.axf",
+                "bootloader": "tfm_ns.bin"
+            },
+            "monitors": [
+                {
+                    'name': 'Secure_Test_Suites_Summary',
+                    'start': '[Sec Thread]',
+                    'end': '\\x1b\\\[0m',
+                    'pattern': r'\x1b\\[1;34m\\[Sec Thread\\] '
+                               r'(?P<test_case_id>Secure image '
+                               r'initializing)(?P<result>!)',
+                    'fixup': {"pass": "!", "fail": ""},
+                    'required': ["secure_image_initializing"]
+                }  # Monitors
+            ]
+        },  # Default
+        'Regression': {
+            "binaries": {
+                "firmware": "tfm_s.axf",
+                "bootloader": "tfm_ns.bin"
+            },
+            "monitors": [
+                {
+                    'name': 'Secure_Test_Suites_Summary',
+                    'start': 'Secure test suites summary',
+                    'end': 'End of Secure test suites',
+                    'pattern': r"[\x1b]\\[37mTest suite '(?P<"
+                               r"test_case_id>[^\n]+)' has [\x1b]\\[32m "
+                               r"(?P<result>PASSED|FAILED)",
+                    'fixup': {"pass": "PASSED", "fail": "FAILED"},
+                    'required': [
+                        ("psa_protected_storage_"
+                           "s_interface_tests_tfm_sst_test_2xxx_"),
+                        "sst_reliability_tests_tfm_sst_test_3xxx_",
+                        "sst_rollback_protection_tests_tfm_sst_test_4xxx_",
+                        ("psa_internal_trusted_storage_"
+                           "s_interface_tests_tfm_its_test_2xxx_"),
+                        "its_reliability_tests_tfm_its_test_3xxx_",
+                        ("audit_"
+                         "logging_secure_interface_test_tfm_audit_test_1xxx_"),
+                        "crypto_secure_interface_tests_tfm_crypto_test_5xxx_",
+                        ("initial_attestation_service_"
+                         "secure_interface_tests_tfm_attest_test_1xxx_"),
+                    ]
+                },
+                {
+                    'name': 'Non_Secure_Test_Suites_Summary',
+                    'start': 'Non-secure test suites summary',
+                    'end': r'End of Non-secure test suites',
+                    'pattern': r"[\x1b]\\[37mTest suite '(?P"
+                               r"<test_case_id>[^\n]+)' has [\x1b]\\[32m "
+                               r"(?P<result>PASSED|FAILED)",
+                    'fixup': {"pass": "PASSED", "fail": "FAILED"},
+                    'required': [
+                        ("psa_protected_storage"
+                         "_ns_interface_tests_tfm_sst_test_1xxx_"),
+                        ("psa_internal_trusted_storage"
+                         "_ns_interface_tests_tfm_its_test_1xxx_"),
+                        ("auditlog_"
+                         "non_secure_interface_test_tfm_audit_test_1xxx_"),
+                        ("crypto_"
+                         "non_secure_interface_test_tfm_crypto_test_6xxx_"),
+                        ("initial_attestation_service_"
+                         "non_secure_interface_tests_tfm_attest_test_2xxx_"),
+                        "core_non_secure_positive_tests_tfm_core_test_1xxx_"
+                    ]
+                }
+            ]  # Monitors
+        },  # Regression
+        'CoreIPC': {
+            "binaries": {
+                "firmware": "tfm_s.axf",
+                "bootloader": "tfm_ns.bin"
+            },
+            "monitors": [
+                {
+                    'name': 'Secure_Test_Suites_Summary',
+                    'start': '[Sec Thread]',
+                    'end': '\\x1b\\\[0m',
+                    'pattern': r'\x1b\\[1;34m\\[Sec Thread\\] '
+                               r'(?P<test_case_id>Secure image '
+                               r'initializing)(?P<result>!)',
+                    'fixup': {"pass": "!", "fail": ""},
+                    'required': ["secure_image_initializing"]
+                }  # Monitors
+            ]
+        },  # CoreIPC
+        'CoreIPCTfmLevel2': {
+            "binaries": {
+                "firmware": "tfm_s.axf",
+                "bootloader": "tfm_ns.bin"
+            },
+            "monitors": [
+                {
+                    'name': 'Secure_Test_Suites_Summary',
+                    'start': '[Sec Thread]',
+                    'end': '\\x1b\\\[0m',
+                    'pattern': r'\x1b\\[1;34m\\[Sec Thread\\] '
+                               r'(?P<test_case_id>Secure image '
+                               r'initializing)(?P<result>!)',
+                    'fixup': {"pass": "!", "fail": ""},
+                    'required': ["secure_image_initializing"]
+                }  # Monitors
+            ]
+        },  # CoreIPCTfmLevel2
+    }  # Tests
+}
+
 # All configurations should be mapped here
-lava_gen_config_map = {"tfm_mps2_sse_200": tfm_mps2_sse_200}
+lava_gen_config_map = {"tfm_mps2_sse_200": tfm_mps2_sse_200,
+                       "tfm_mps2_fvp": tfm_mps2_fvp}
 lavagen_config_sort_order = [
     "templ",
     "job_name",
diff --git a/lava_helper/lava_submit_jobs.py b/lava_helper/lava_submit_jobs.py
new file mode 100755
index 0000000..b52bed1
--- /dev/null
+++ b/lava_helper/lava_submit_jobs.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+
+from __future__ import print_function
+
+"""
+Script for submitting multiple LAVA definitions
+"""
+
+__copyright__ = """
+/*
+ * Copyright (c) 2020, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+ """
+
+import os
+import glob
+import sys
+import shutil
+import argparse
+from copy import deepcopy
+from collections import OrderedDict
+from jinja2 import Environment, FileSystemLoader
+from lava_helper_configs import *
+
+try:
+    from tfm_ci_pylib.utils import (
+        save_json,
+        load_json,
+        sort_dict,
+        load_yaml,
+        test,
+        print_test,
+    )
+    from tfm_ci_pylib.lava_rpc_connector import LAVA_RPC_connector
+except ImportError:
+    dir_path = os.path.dirname(os.path.realpath(__file__))
+    sys.path.append(os.path.join(dir_path, "../"))
+    from tfm_ci_pylib.utils import (
+        save_json,
+        load_json,
+        sort_dict,
+        load_yaml,
+        test,
+        print_test,
+    )
+    from tfm_ci_pylib.lava_rpc_connector import LAVA_RPC_connector
+
+
+def test_lava_dispatch_credentials(user_args):
+    """ Will validate if provided token/credentials are valid. It will return
+    a valid connection or exit program if not"""
+
+    # Collect the authentication tokens
+    try:
+        if user_args.token_from_env:
+            usr = os.environ['LAVA_USER']
+            secret = os.environ['LAVA_TOKEN']
+        elif user_args.lava_token and user_args.lava_user:
+            usr = user_args.lava_user
+            secret = user_args.lava_token
+
+        # Do not submit job without complete credentials
+        if not len(usr) or not len(secret):
+            raise Exception("Credentials not set")
+
+        lava = LAVA_RPC_connector(usr,
+                                  secret,
+                                  user_args.lava_url)
+
+        # Test the credentials againist the backend
+        if not lava.test_credentials():
+            raise Exception("Server rejected user authentication")
+    except Exception as e:
+        print("Credential validation failed with : %s" % e)
+        print("Did you set set --lava-token, --lava-user?")
+        sys.exit(1)
+    return lava
+
+def list_files_from_dir(user_args):
+    file_list = []
+    for filename in glob.iglob(user_args.job_dir + '**/*.yaml', recursive=True):
+        file_list.append(filename)
+        print("Found job {}".format(filename))
+    return file_list
+
+def lava_dispatch(user_args):
+    """ Submit a job to LAVA backend, block untill it is completed, and
+    fetch the results files if successful. If not, calls sys exit with 1
+    return code """
+
+    lava = test_lava_dispatch_credentials(user_args)
+    file_list = list_files_from_dir(user_args)
+    job_id_list = []
+    for job_file in file_list:
+        job_id, job_url = lava.submit_job(job_file)
+
+        # The reason of failure will be reported to user by LAVA_RPC_connector
+        if job_id is None and job_url is None:
+            print("Job failed")
+        else:
+            print("Job submitted at: " + job_url)
+            job_id_list.append(job_id)
+
+    print("\n".join(str(x) for x in job_id_list))
+
+def main(user_args):
+    lava_dispatch(user_args)
+
+
+def get_cmd_args():
+    """ Parse command line arguments """
+
+    # Parse command line arguments to override config
+    parser = argparse.ArgumentParser(description="Lava Create Jobs")
+    cmdargs = parser.add_argument_group("Create LAVA Jobs")
+
+    # Configuration control
+    cmdargs.add_argument(
+        "--lava-url", dest="lava_url", action="store", help="LAVA lab URL (without RPC2)"
+    )
+    cmdargs.add_argument(
+        "--job-dir", dest="job_dir", action="store", help="LAVA jobs directory"
+    )
+    cmdargs.add_argument(
+        "--lava-token", dest="lava_token", action="store", help="LAVA auth token"
+    )
+    cmdargs.add_argument(
+        "--lava-user", dest="lava_user", action="store", help="LAVA username"
+    )
+    cmdargs.add_argument(
+        "--use-env", dest="token_from_env", action="store_true", default=False, help="LAVA username"
+    )
+    cmdargs.add_argument(
+        "--lava-timeout", dest="dispatch_timeout", action="store", default=3600, help="LAVA username"
+    )
+    return parser.parse_args()
+
+
+if __name__ == "__main__":
+    main(get_cmd_args())