/
Creating a LAVA boot action method

Creating a LAVA boot action method

A boot action method in LAVA is the component in the LAVA dispatcher that flashes and boots a device during test runs. This page details the procedure used to create a boot action method.

Prerequisites


This guide has code blocks to illustrate the code for the OpenOCD boot action method, which is still in a pull request pending approval. The procedure is expected to be similar when creating your own boot method.

Adding the 'plumbing'

  1. Look up the command line invoked when flashing and booting your device, if you normally just invoke a command to flash and boot. For example, if you are developing in Zephyr, a trick is to look up the command run by 'make flash' to get an idea of what is being done.
  2. Write the python file associated with your boot action method to define some python classes to invoke the command line (or to follow some other boot procedure), and put it in lava_dispatcher/actions/boot. In the file, you create the functions that correspond to some 'actions' to flash and boot the device, which LAVA can then add to an execution pipeline that is run when a test job is submitted. The parameters to the command line are typically passed in/inferred from the parameters defined in the device type template, which is covered in step 5. There are many examples of boot action methods in this directory, and it is best to study them as examples to get started. A couple of good ones are pyocd.py and cmsis-dap.py. In addition there is some official documentation in LAVA:
    1. boot action reference
  3. Add an entry for your boot method in lava_dispatcher/actions/boot/strategies.py
    from lava_dispatcher.actions.boot.openocd import OpenOCD

    This tells LAVA that there is a boot action method named 'OpenOCD' defined in lava_dispatcher/actions/boot/openocd.py.

  4. Add a schema for your boot method in lava_common/schemas/boot/<name of your boot method>.py

        from voluptuous import Msg, Required
    
        from lava_common.schemas import boot
    
    
        def schema():
            base = {Required("method"): Msg("openocd", "'method' should be 'openocd'")}
            return {**boot.schema(), **base}
  5. Add support for the boot action method in the device type template of your choice. For example, the template for the CC3220SF is located in lava_scheduler_app/tests/device-types/cc3220SF.jinja2. Modifications are made to add openocd as a supported boot action method:
          openocd:
            parameters:
              command:
                # Can set 'openocd_bin_override' in device dictionary to
                # override location of OpenOCD executable to point to TI OpenOCD
                # if necessary
                {{ openocd_bin_override|default('openocd') }}
              options:
                file:
                  - board/ti_cc3220sf_launchpad.cfg
                # Set 'openocd_scripts_dir_override' in device dictionary to
                # point to TI OpenOCD scripts if necessary
                search: [{{ openocd_scripts_dir_override }}]
                command:
                  - init
                  - targets
                  - 'reset halt'
                  - 'flash write_image erase {BINARY}'
                  - 'reset halt'
                  - 'verify_image {BINARY}'
                  - 'reset run'
                  - shutdown
                debug: 2

    This basically defines a dictionary of the parameters that are passed to the boot action method to invoke the openocd command line.

  6. Document the boot method in doc/v2/actions-boot.rst

Modify lava-docker-compose to try it out

  1. Inspect your lava-docker-compose/.env file. It shows the version tag of the LAVA repository that is used by the container images. 

    DC_POSTGRES_IMAGE=postgres:11.2-alpine
    DC_SERVER_IMAGE=lavasoftware/lava-server:2019.04
    DC_DISPATCHER_IMAGE=lavasoftware/lava-dispatcher:2019.04

    Ideally, you want to ensure your cloned LAVA repository is pointing to the same version. Then you can share your work with the containers by adding to the 'volumes' section for each container in the docker-compose.yaml file. For example, say your cloned repository is located under the directory lava-work, which is located right alongside the lava-docker-compose directory.  if you want to share your changes from the lava_scheduler_app and lava_common directories with the lava-dispatcher container, you can adding volume bindings as follow: 

      lava-dispatcher:
    ...
        volumes:
    ...
          # Example for development
          # If you wanted to point to a local git checkout of lava for development
          # of lava_dispatcher, you can uncomment out the lines below and
          # set the 'source:' to point to where your lava checkout is
          # The example assumes its relative in ../lava
          #
        - type: bind
          source: ../lava-work/lava_dispatcher
          target: /usr/lib/python3/dist-packages/lava_dispatcher
        - type: bind
          source: ../lava-work/lava_common
          target: /usr/lib/python3/dist-packages/lava_common

    Depending on how far off your cloned repository is from the version tag '2019.04', you may need to share more directories to ensure the various LAVA-related directories under /usr/lib/python3/dist-packages in each container are in sync. Runtime errors may result if different containers are referencing vastly different code.

  2. Create a job that uses the new boot method, e.g. lava_openocd.job in our example for OpenOCD. The key thing is to specify your boot method under the boot section:

    - boot:
        method: openocd
        timeout:
          minutes: 1
  3. Kick off the new job as you normally would:

    lavacli -i dispatcher jobs submit lava_openocd.job

    If you did everything correctly, the test job would run successfully to completion, and invoke your boot action method in the process.


Validation and submission

  1. Add a unit test for your boot action method:
    1. Install lava-dev and python3-django-testscenarios

      sudo apt install lava-dev python3-django-testscenarios

      If the above does not work because lava-dev is not found, take a look at https://docs.lavasoftware.org/lava/installing_on_debian.html regarding LAVA repositories.  If you are on Ubuntu, which is not officially supported, you may need to add this line in your /etc/apt/sources.list file:

      deb [allow-insecure=yes allow-downgrade-to-insecure=yes] http://deb.debian.org/debian stretch-backports main

      Then run

      sudo apt update
      sudo apt install lava-dev

      It is going to complain that the package is insecure, but I haven't found a way around this (suggestions welcome). The installation still goes through however.

    2. Install a few more dependencies:
      pip3 install --user django-tables2
      pip3 install --user djangorestframework
      sudo apt-get install python-psycopg2
      sudo apt-get install libpq-dev
      pip3 install --user psycopg2
      pip3 install --user django-restricted-resource
      sudo apt-get install libsasl2-dev python-dev libldap2-dev libssl-dev
      sudo pip3 install --user pyldap
      pip3 install --user pytest-tap
    3. In the cloned LAVA repository, create a test under lava_dispatcher/tests/test_<boot method name>.py:
      import unittest
      from lava_dispatcher.tests.utils import infrastructure_error
      from lava_dispatcher.tests.test_basic import Factory, StdoutTestCase
      from lava_common.exceptions import InfrastructureError
      from lava_dispatcher.utils.shell import which
      
      
      def check_openocd():
          try:
              which("openocd")
              return False
          except InfrastructureError:
              return True
      
      
      class OpenOCDFactory(Factory):
          """
          Not Model based, this is not a Django factory.
          Factory objects are dispatcher based classes, independent
          of any database objects.
          """
      
          @unittest.skipIf(infrastructure_error("openocd"), "openocd not installed")
          def create_cc3230SF_job(self, filename):
              return self.create_job("cc3220SF-02.jinja2", filename)
      
      class TestOpenOCDAction(StdoutTestCase):
          @unittest.skipIf(check_openocd(), "openocd not available")
          def test_openocd_pipeline(self):
              factory = OpenOCDFactory()
              job = factory.create_cc3230SF_job("sample_jobs/cc3220SF-openocd.yaml")
              job.validate()
              description_ref = self.pipeline_reference("openocd.yaml", job=job)
              self.assertEqual(description_ref, job.pipeline.describe(False))
      
              # Check BootOpenOCDRetry action
              action = job.pipeline.actions[1].internal_pipeline.actions[0]
              self.assertEqual(action.name, "boot-openocd-image")
      
              # Check FlashOpenOCDAction
              action = (
                  job.pipeline.actions[1]
                  .internal_pipeline.actions[0]
                  .internal_pipeline.actions[0]
              )
              self.assertEqual(action.name, "flash-openocd")
              self.assertEqual(len(action.base_command), 20)
              self.assertEqual(action.base_command[0], "openocd")
              self.assertEqual(action.base_command[1], "-f")
              self.assertEqual(action.base_command[2], "board/ti_cc3220sf_launchpad.cfg")
              self.assertEqual(action.base_command[3], "-d2")
              self.assertEqual(action.base_command[19], "shutdown")
      
              action = (
                  job.pipeline.actions[1]
                  .internal_pipeline.actions[0]
                  .internal_pipeline.actions[1]
              )
              self.assertEqual(action.name, "connect-device")
                                                                      
      There are many examples of tests in the same directory. Feel free to take a look at them to come up with something that makes sense for your boot action method. Some useful documentation regarding how to add unit tests is here. In essence, the role of the unit test is to create a job using a test device dictionary file (lava_scheduler_app/tests/devices/cc3220SF-02.jinja2 in the code above), and sample test job definition (lava_dispatcher/tests/sample_jobs/cc3220SF-openocd.yaml), and then verify that internal variables of the various actions executed as part of the execution pipeline of the test get set to the expected values. As part of creating your test, you should create a file that defines the expected pipeline for the test job (lava_dispatcher/tests/pipeline_refs/openocd.yaml), so that the unit test can validate against it, in addition to creating your own test device dictionary and/or sample test job definition. 
    4. The lava-dispatcher section here has some commands for either running your specific unit test or running all unit tests for the dispatcher. Here's how it looks on a successful run:

      $ python3 -m unittest -v -c -f lava_dispatcher.tests.test_openocd
      test_openocd_pipeline (lava_dispatcher.tests.test_openocd.TestOpenOCDAction) ... dpkg-query: no packages found matching lava-dispatcher
      DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): snapshots.linaro.org:443
      DEBUG:urllib3.connectionpool:https://snapshots.linaro.org:443 "HEAD /components/kernel/zephyr/master/zephyr/cc3220sf_launchxl/3885/tests/subsys/logging/log_list/logging.log_list/zephyr/zephyr.elf HTTP/1.1" 302 0
      DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): publishing-ie-linaro-org.s3.amazonaws.com:443
      DEBUG:urllib3.connectionpool:https://publishing-ie-linaro-org.s3.amazonaws.com:443 "HEAD /snapshots/components/kernel/zephyr/master/zephyr/cc3220sf_launchxl/3885/tests/subsys/logging/log_list/logging.log_list/zephyr/zephyr.elf?Signature=A80F8UEVFVAwHtZZ%2FwWL0dEZ5L8%3D&Expires=1560818349&AWSAccessKeyId=AKIAIJR2J6C42QCU7ITA HTTP/1.1" 403 0
      DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): snapshots.linaro.org:443
      DEBUG:urllib3.connectionpool:https://snapshots.linaro.org:443 "GET /components/kernel/zephyr/master/zephyr/cc3220sf_launchxl/3885/tests/subsys/logging/log_list/logging.log_list/zephyr/zephyr.elf HTTP/1.1" 302 0
      DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): publishing-ie-linaro-org.s3.amazonaws.com:443
      DEBUG:urllib3.connectionpool:https://publishing-ie-linaro-org.s3.amazonaws.com:443 "GET /snapshots/components/kernel/zephyr/master/zephyr/cc3220sf_launchxl/3885/tests/subsys/logging/log_list/logging.log_list/zephyr/zephyr.elf?Signature=kzYqsz96Pm%2Bc0Kp3ycqGyQzTq2o%3D&Expires=1560818351&AWSAccessKeyId=AKIAIJR2J6C42QCU7ITA HTTP/1.1" 200 572552
      ok
      
      ----------------------------------------------------------------------
      Ran 1 test in 4.035s
      
      OK
  2. Submit a PR for your changes to lavasoftware.org, as per the contribution guide.





Related content