diff -pruN 2015.9-1/debian/changelog 2015.9-1ubuntu1/debian/changelog
--- 2015.9-1/debian/changelog	2015-09-10 08:31:03.000000000 +0000
+++ 2015.9-1ubuntu1/debian/changelog	2015-10-29 22:05:02.000000000 +0000
@@ -1,3 +1,11 @@
+lava-dispatcher (2015.9-1ubuntu1) xenial; urgency=medium
+
+  * d/patches/skip-missing-images.patch: Skip some tests when run under
+    autopkgtest because they reference URLs that no longer exist.
+  * d/control: update-maintainer
+
+ -- Barry Warsaw <barry@ubuntu.com>  Thu, 29 Oct 2015 18:04:04 -0400
+
 lava-dispatcher (2015.9-1) unstable; urgency=medium
 
   * New production release
diff -pruN 2015.9-1/debian/control 2015.9-1ubuntu1/debian/control
--- 2015.9-1/debian/control	2015-09-10 08:31:03.000000000 +0000
+++ 2015.9-1ubuntu1/debian/control	2015-10-29 22:05:02.000000000 +0000
@@ -1,7 +1,8 @@
 Source: lava-dispatcher
 Section: net
 Priority: optional
-Maintainer: Debian LAVA team <pkg-linaro-lava-devel@lists.alioth.debian.org>
+Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+XSBC-Original-Maintainer: Debian LAVA team <pkg-linaro-lava-devel@lists.alioth.debian.org>
 Uploaders: Antonio Terceiro <terceiro@debian.org>,
  Neil Williams <codehelp@debian.org>,
  Fathi Boudra <fabo@debian.org>, Jordi Mallach <jordi@debian.org>,
diff -pruN 2015.9-1/debian/patches/series 2015.9-1ubuntu1/debian/patches/series
--- 2015.9-1/debian/patches/series	1970-01-01 00:00:00.000000000 +0000
+++ 2015.9-1ubuntu1/debian/patches/series	2015-10-29 22:05:02.000000000 +0000
@@ -0,0 +1 @@
+skip-missing-images.patch
diff -pruN 2015.9-1/debian/patches/skip-missing-images.patch 2015.9-1ubuntu1/debian/patches/skip-missing-images.patch
--- 2015.9-1/debian/patches/skip-missing-images.patch	1970-01-01 00:00:00.000000000 +0000
+++ 2015.9-1ubuntu1/debian/patches/skip-missing-images.patch	2015-10-29 22:05:02.000000000 +0000
@@ -0,0 +1,57 @@
+From: Barry Warsaw <barry@ubuntu.com>
+Date: Thu, 29 Oct 2015 17:45:45 -0400
+Subject: Skip tests which reference a nonexistent image.
+Forwarded: no
+
+---
+ lava_dispatcher/pipeline/test/test_removable.py | 9 +++++++++
+ lava_dispatcher/pipeline/test/test_uboot.py     | 3 +++
+ 2 files changed, 12 insertions(+)
+diff --git a/lava_dispatcher/pipeline/test/test_removable.py b/lava_dispatcher/pipeline/test/test_removable.py
+index 6ed73a0..9078af6 100644
+--- a/lava_dispatcher/pipeline/test/test_removable.py
++++ b/lava_dispatcher/pipeline/test/test_removable.py
+@@ -53,6 +53,9 @@ class TestRemovable(unittest.TestCase):  # pylint: disable=too-many-public-metho
+         self.assertIn('boot_message', u_boot_params['parameters'])
+         self.assertIn('bootloader_prompt', u_boot_params['parameters'])
+ 
++    @unittest.skipIf(
++        os.environ.get('ADTTMP'),
++        'The referenced image file no longer exists')
+     def test_job_parameters(self):
+         """
+         Test that the job parameters match expected structure
+@@ -87,6 +90,9 @@ class TestRemovable(unittest.TestCase):  # pylint: disable=too-many-public-metho
+         u_boot_params = cubie['actions']['boot']['methods']['u-boot']
+         self.assertEqual(mass_storage.get_common_data('bootloader_prompt', 'prompt'), u_boot_params['parameters']['bootloader_prompt'])
+ 
++    @unittest.skipIf(
++        os.environ.get('ADTTMP'),
++        'The referenced image file no longer exists')
+     def test_deployment(self):
+         job_parser = JobParser()
+         cubie = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/cubie1.yaml'))
+@@ -128,6 +134,9 @@ class TestRemovable(unittest.TestCase):  # pylint: disable=too-many-public-metho
+         self.assertEqual(job.pipeline.errors, [])
+         self.assertIn('usb', bbb['parameters']['media'].keys())
+ 
++    @unittest.skipIf(
++        os.environ.get('ADTTMP'),
++        'The referenced image file no longer exists')
+     def test_substitutions(self):
+         """
+         Test substitution of secondary media values into u-boot commands
+diff --git a/lava_dispatcher/pipeline/test/test_uboot.py b/lava_dispatcher/pipeline/test/test_uboot.py
+index e70c174..0b84bd0 100644
+--- a/lava_dispatcher/pipeline/test/test_uboot.py
++++ b/lava_dispatcher/pipeline/test/test_uboot.py
+@@ -278,6 +278,9 @@ class TestUbootAction(unittest.TestCase):  # pylint: disable=too-many-public-met
+         self.assertIn('pdu_reboot', names)
+         self.assertIn('power_on', names)
+ 
++    @unittest.skipIf(
++        os.environ.get('ADTTMP'),
++        'The referenced image file no longer exists')
+     def test_secondary_media(self):
+         """
+         Test UBootSecondaryMedia validation
diff -pruN 2015.9-1/lava_dispatcher/pipeline/test/test_removable.py 2015.9-1ubuntu1/lava_dispatcher/pipeline/test/test_removable.py
--- 2015.9-1/lava_dispatcher/pipeline/test/test_removable.py	2015-09-09 14:30:35.000000000 +0000
+++ 2015.9-1ubuntu1/lava_dispatcher/pipeline/test/test_removable.py	2015-10-30 00:36:47.000000000 +0000
@@ -53,6 +53,9 @@ class TestRemovable(unittest.TestCase):
         self.assertIn('boot_message', u_boot_params['parameters'])
         self.assertIn('bootloader_prompt', u_boot_params['parameters'])
 
+    @unittest.skipIf(
+        os.environ.get('ADTTMP'),
+        'The referenced image file no longer exists')
     def test_job_parameters(self):
         """
         Test that the job parameters match expected structure
@@ -87,6 +90,9 @@ class TestRemovable(unittest.TestCase):
         u_boot_params = cubie['actions']['boot']['methods']['u-boot']
         self.assertEqual(mass_storage.get_common_data('bootloader_prompt', 'prompt'), u_boot_params['parameters']['bootloader_prompt'])
 
+    @unittest.skipIf(
+        os.environ.get('ADTTMP'),
+        'The referenced image file no longer exists')
     def test_deployment(self):
         job_parser = JobParser()
         cubie = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/cubie1.yaml'))
@@ -128,6 +134,9 @@ class TestRemovable(unittest.TestCase):
         self.assertEqual(job.pipeline.errors, [])
         self.assertIn('usb', bbb['parameters']['media'].keys())
 
+    @unittest.skipIf(
+        os.environ.get('ADTTMP'),
+        'The referenced image file no longer exists')
     def test_substitutions(self):
         """
         Test substitution of secondary media values into u-boot commands
diff -pruN 2015.9-1/lava_dispatcher/pipeline/test/test_uboot.py 2015.9-1ubuntu1/lava_dispatcher/pipeline/test/test_uboot.py
--- 2015.9-1/lava_dispatcher/pipeline/test/test_uboot.py	2015-09-09 14:30:35.000000000 +0000
+++ 2015.9-1ubuntu1/lava_dispatcher/pipeline/test/test_uboot.py	2015-10-30 00:36:47.000000000 +0000
@@ -278,6 +278,9 @@ class TestUbootAction(unittest.TestCase)
         self.assertIn('pdu_reboot', names)
         self.assertIn('power_on', names)
 
+    @unittest.skipIf(
+        os.environ.get('ADTTMP'),
+        'The referenced image file no longer exists')
     def test_secondary_media(self):
         """
         Test UBootSecondaryMedia validation
diff -pruN 2015.9-1/.pc/applied-patches 2015.9-1ubuntu1/.pc/applied-patches
--- 2015.9-1/.pc/applied-patches	1970-01-01 00:00:00.000000000 +0000
+++ 2015.9-1ubuntu1/.pc/applied-patches	2015-10-30 00:36:47.337831788 +0000
@@ -0,0 +1 @@
+skip-missing-images.patch
diff -pruN 2015.9-1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_removable.py 2015.9-1ubuntu1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_removable.py
--- 2015.9-1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_removable.py	1970-01-01 00:00:00.000000000 +0000
+++ 2015.9-1ubuntu1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_removable.py	2015-09-09 14:30:35.000000000 +0000
@@ -0,0 +1,202 @@
+# Copyright (C) 2014 Linaro Limited
+#
+# Author: Neil Williams <neil.williams@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher 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 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher 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>.
+
+import os
+import unittest
+from lava_dispatcher.pipeline.test.test_basic import pipeline_reference
+from lava_dispatcher.pipeline.action import JobError
+from lava_dispatcher.pipeline.device import NewDevice
+from lava_dispatcher.pipeline.parser import JobParser
+from lava_dispatcher.pipeline.actions.deploy import DeployAction
+from lava_dispatcher.pipeline.actions.deploy.removable import MassStorage
+from lava_dispatcher.pipeline.utils.strings import substitute
+from lava_dispatcher.pipeline.utils.shell import infrastructure_error
+
+
+class TestRemovable(unittest.TestCase):  # pylint: disable=too-many-public-methods
+
+    def test_device_parameters(self):
+        """
+        Test that the correct parameters have been set for the device
+        """
+        cubie = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/cubie1.yaml'))
+        self.assertIsNotNone(cubie['parameters']['media'].get('usb', None))
+        self.assertIsNotNone(cubie.get('commands', None))
+        self.assertIsNotNone(cubie.get('actions', None))
+        self.assertIsNotNone(cubie['actions'].get('deploy', None))
+        self.assertIsNotNone(cubie['actions']['deploy'].get('methods', None))
+        self.assertIn('usb', cubie['actions']['deploy']['methods'])
+        self.assertIsNotNone(cubie['actions'].get('boot', None))
+        self.assertIsNotNone(cubie['actions']['boot'].get('methods', None))
+        self.assertIn('u-boot', cubie['actions']['boot']['methods'])
+        u_boot_params = cubie['actions']['boot']['methods']['u-boot']
+        self.assertIn('usb', u_boot_params)
+        self.assertIn('commands', u_boot_params['usb'])
+        self.assertIn('parameters', u_boot_params)
+        self.assertIn('boot_message', u_boot_params['parameters'])
+        self.assertIn('bootloader_prompt', u_boot_params['parameters'])
+
+    def test_job_parameters(self):
+        """
+        Test that the job parameters match expected structure
+        """
+        self.maxDiff = None  # pylint: disable=invalid-name
+        job_parser = JobParser()
+        cubie = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/cubie1.yaml'))
+        sample_job_file = os.path.join(os.path.dirname(__file__), 'sample_jobs/cubietruck-removable.yaml')
+        sample_job_data = open(sample_job_file)
+        job = job_parser.parse(sample_job_data, cubie, 4212, None, output_dir='/tmp/')
+        try:
+            job.validate()
+        except JobError:
+            self.fail(job.pipeline.errors)
+
+        description_ref = pipeline_reference('cubietruck-removable.yaml')
+        self.assertEqual(description_ref, job.pipeline.describe(False))
+
+        mass_storage = None  # deploy
+        for action in job.pipeline.actions:
+            if isinstance(action, DeployAction):
+                if isinstance(action, MassStorage):
+                    self.assertTrue(action.valid)
+                    agent = action.parameters['download']
+                    self.assertTrue(agent.startswith('/'))  # needs to be a full path but on the device, so avoid os.path
+                    self.assertIn(action.parameters['device'], job.device['parameters']['media']['usb'])
+                    mass_storage = action
+        self.assertIsNotNone(mass_storage)
+        self.assertIn('device', mass_storage.parameters)
+        self.assertIn(mass_storage.parameters['device'], cubie['parameters']['media']['usb'])
+        self.assertIsNotNone(mass_storage.get_common_data('u-boot', 'device'))
+        u_boot_params = cubie['actions']['boot']['methods']['u-boot']
+        self.assertEqual(mass_storage.get_common_data('bootloader_prompt', 'prompt'), u_boot_params['parameters']['bootloader_prompt'])
+
+    def test_deployment(self):
+        job_parser = JobParser()
+        cubie = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/cubie1.yaml'))
+        sample_job_file = os.path.join(os.path.dirname(__file__), 'sample_jobs/cubietruck-removable.yaml')
+        sample_job_data = open(sample_job_file)
+        job = job_parser.parse(sample_job_data, cubie, 4212, None, output_dir='/tmp/')
+        job.validate()
+        self.assertIn('usb', cubie['parameters']['media'].keys())
+        deploy_params = [methods for methods in job.parameters['actions'] if 'deploy' in methods.keys()][0]['deploy']
+        self.assertIn('device', deploy_params)
+        self.assertIn(deploy_params['device'], cubie['parameters']['media']['usb'])
+        self.assertIn('uuid', cubie['parameters']['media']['usb'][deploy_params['device']])
+        self.assertIn('device_id', cubie['parameters']['media']['usb'][deploy_params['device']])
+        self.assertNotIn('boot_part', cubie['parameters']['media']['usb'][deploy_params['device']])
+        deploy_action = job.pipeline.actions[0]
+        self.assertIn('lava_test_results_dir', deploy_action.data)
+        self.assertIn('/lava-', deploy_action.data['lava_test_results_dir'])
+        self.assertIsInstance(deploy_action, MassStorage)
+        self.assertIn('image', deploy_action.parameters.keys())
+        dd_action = deploy_action.internal_pipeline.actions[1]
+        self.assertEqual(
+            dd_action.boot_params[dd_action.parameters['device']]['uuid'],
+            'usb-SanDisk_Ultra_20060775320F43006019-0:0')
+        self.assertEqual('0', '%s' % dd_action.get_common_data('u-boot', 'boot_part'))
+        self.assertTrue(type(dd_action.get_common_data('uuid', 'boot_part')) is str)
+        self.assertEqual('0:1', dd_action.get_common_data('uuid', 'boot_part'))
+
+    @unittest.skipIf(infrastructure_error('mkimage'), "u-boot-tools not installed")
+    def test_primary_media(self):
+        """
+        Test that definitions of secondary media do not block submissions using primary media
+        """
+        job_parser = JobParser()
+        bbb = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/bbb-01.yaml'))
+        sample_job_file = os.path.join(os.path.dirname(__file__), 'sample_jobs/uboot-ramdisk.yaml')
+        sample_job_data = open(sample_job_file)
+        job = job_parser.parse(sample_job_data, bbb, 4212, None, output_dir='/tmp/')
+        job.validate()
+        self.assertEqual(job.pipeline.errors, [])
+        self.assertIn('usb', bbb['parameters']['media'].keys())
+
+    def test_substitutions(self):
+        """
+        Test substitution of secondary media values into u-boot commands
+
+        Unlike most u-boot calls, removable knows in advance all the values it needs to substitute
+        into the boot commands for the secondary deployment as these are fixed by the device config
+        and the image details from the job submission.
+        """
+        job_parser = JobParser()
+        cubie = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/cubie1.yaml'))
+        sample_job_file = os.path.join(os.path.dirname(__file__), 'sample_jobs/cubietruck-removable.yaml')
+        sample_job_data = open(sample_job_file)
+        job = job_parser.parse(sample_job_data, cubie, 4212, None, output_dir='/tmp/')
+        job.validate()
+        boot_params = [methods for methods in job.parameters['actions'] if 'boot' in methods.keys()][0]['boot']
+        self.assertIn('ramdisk', boot_params)
+        self.assertIn('kernel', boot_params)
+        self.assertIn('dtb', boot_params)
+        self.assertIn('root_uuid', boot_params)
+        self.assertIn('boot_part', boot_params)
+        self.assertIn('type', boot_params)
+        self.assertGreater(len(job.pipeline.actions), 1)
+        self.assertIsNotNone(job.pipeline.actions[1].internal_pipeline)
+        u_boot_action = job.pipeline.actions[1].internal_pipeline.actions[1]
+        self.assertIsNotNone(u_boot_action.get_common_data('u-boot', 'device'))
+        self.assertEqual(u_boot_action.name, "uboot-overlay")
+
+        methods = cubie['actions']['boot']['methods']
+        self.assertIn('u-boot', methods)
+        self.assertIn('usb', methods['u-boot'])
+        self.assertIn('commands', methods['u-boot']['usb'])
+        commands_list = methods['u-boot']['usb']['commands']
+        device_id = u_boot_action.get_common_data('u-boot', 'device')
+        substitutions = {
+            '{BOOTX}': "%s %s %s %s" % (
+                u_boot_action.parameters['type'],
+                cubie['parameters'][u_boot_action.parameters['type']]['kernel'],
+                cubie['parameters'][u_boot_action.parameters['type']]['ramdisk'],
+                cubie['parameters'][u_boot_action.parameters['type']]['dtb'],),
+            '{RAMDISK}': boot_params['ramdisk'],
+            '{KERNEL}': boot_params['kernel'],
+            '{DTB}': boot_params['dtb'],
+            '{ROOT}': boot_params['root_uuid'],
+            '{ROOT_PART}': "%s:%s" % (
+                cubie['parameters']['media']['usb'][device_id]['device_id'],
+                u_boot_action.parameters['boot_part']
+            )
+        }
+        self.assertEqual('bootz 0x42000000 0x43300000 0x43000000', substitutions['{BOOTX}'])
+        self.assertEqual('/boot/initrd.img-3.16.0-4-armmp-lpae.u-boot', substitutions['{RAMDISK}'])
+        commands = substitute(commands_list, substitutions)
+        self.assertEqual(
+            commands,
+            [
+                'usb start',
+                'usb info',
+                'setenv autoload no',
+                "setenv initrd_high '0xffffffff'",
+                "setenv fdt_high '0xffffffff'",
+                'setenv initrd_addr_r ${ramdisk_addr_r}',
+                "setenv loadkernel 'load usb 0:1 ${kernel_addr_r} /boot/vmlinuz-3.16.0-4-armmp-lpae'",
+                "setenv loadinitrd 'load usb 0:1 ${initrd_addr_r} /boot/initrd.img-3.16.0-4-armmp-lpae.u-boot; setenv initrd_size ${filesize}'",
+                "setenv loadfdt 'load usb 0:1 ${fdt_addr_r} /boot/dtb-3.16.0-4-armmp-lpae'",
+                "setenv bootargs 'console=ttyS0,115200n8 root=UUID=159d17cc-697c-4125-95a0-a3775e1deabe ip=dhcp'",
+                "setenv bootcmd 'run loadkernel; run loadinitrd; run loadfdt; bootz 0x42000000 0x43300000 0x43000000'", 'boot'
+            ]
+        )
+        # reference commands:
+        #        setenv loadkernel 'load usb 0:1 ${kernel_addr_r} /boot/vmlinuz-3.16.0-4-armmp-lpae'
+        #        setenv loadinitrd 'load usb 0:1 ${initrd_addr_r} /boot/initrd.img-3.16.0-4-armmp-lpae.u-boot; setenv initrd_size ${filesize}'
+        #        setenv loadfdt 'load usb 0:1 ${fdt_addr_r} /boot/dtb-3.16.0-4-armmp-lpae'
+        #        setenv bootargs 'console=ttyS0,115200 rw root=UUID=159d17cc-697c-4125-95a0-a3775e1deabe ip=dhcp'
diff -pruN 2015.9-1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_uboot.py 2015.9-1ubuntu1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_uboot.py
--- 2015.9-1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_uboot.py	1970-01-01 00:00:00.000000000 +0000
+++ 2015.9-1ubuntu1/.pc/skip-missing-images.patch/lava_dispatcher/pipeline/test/test_uboot.py	2015-09-09 14:30:35.000000000 +0000
@@ -0,0 +1,359 @@
+# Copyright (C) 2014 Linaro Limited
+#
+# Author: Neil Williams <neil.williams@linaro.org>
+#
+# This file is part of LAVA Dispatcher.
+#
+# LAVA Dispatcher 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 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Dispatcher 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>.
+
+
+import os
+import yaml
+import tarfile
+import unittest
+from lava_dispatcher.pipeline.device import NewDevice
+from lava_dispatcher.pipeline.parser import JobParser
+from lava_dispatcher.pipeline.actions.boot.u_boot import (
+    UBootAction,
+    UBootCommandOverlay,
+    UBootSecondaryMedia
+)
+from lava_dispatcher.pipeline.actions.deploy.tftp import TftpAction
+from lava_dispatcher.pipeline.job import Job
+from lava_dispatcher.pipeline.action import Pipeline, InfrastructureError, JobError
+from lava_dispatcher.pipeline.test.test_basic import pipeline_reference
+from lava_dispatcher.pipeline.utils.network import dispatcher_ip
+from lava_dispatcher.pipeline.utils.shell import infrastructure_error
+from lava_dispatcher.pipeline.utils.filesystem import mkdtemp, tftpd_dir
+from lava_dispatcher.pipeline.utils.strings import substitute
+from lava_dispatcher.pipeline.utils.constants import (
+    SHUTDOWN_MESSAGE,
+    BOOT_MESSAGE,
+)
+
+
+class Factory(object):  # pylint: disable=too-few-public-methods
+    """
+    Not Model based, this is not a Django factory.
+    Factory objects are dispatcher based classes, independent
+    of any database objects.
+    """
+    def create_bbb_job(self, filename, output_dir='/tmp/'):  # pylint: disable=no-self-use
+        device = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/bbb-01.yaml'))
+        kvm_yaml = os.path.join(os.path.dirname(__file__), filename)
+        with open(kvm_yaml) as sample_job_data:
+            parser = JobParser()
+            job = parser.parse(sample_job_data, device, 4212, None, output_dir=output_dir)
+        return job
+
+
+class TestUbootAction(unittest.TestCase):  # pylint: disable=too-many-public-methods
+
+    @unittest.skipIf(infrastructure_error('mkimage'), "u-boot-tools not installed")
+    def test_simulated_action(self):
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot-ramdisk.yaml')
+        self.assertIsNotNone(job)
+
+        # uboot and uboot-ramdisk have the same pipeline structure
+        description_ref = pipeline_reference('uboot.yaml')
+        self.assertEqual(description_ref, job.pipeline.describe(False))
+
+        self.assertIsNone(job.validate())
+        self.assertEqual(job.device['device_type'], 'beaglebone-black')
+
+    def test_tftp_pipeline(self):
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot-ramdisk.yaml')
+        self.assertEqual(
+            [action.name for action in job.pipeline.actions],
+            ['tftp-deploy', 'uboot-action', 'lava-test-retry', 'finalize']
+        )
+        tftp = [action for action in job.pipeline.actions if action.name == 'tftp-deploy'][0]
+        self.assertTrue(tftp.get_common_data('tftp', 'ramdisk'))
+        self.assertIsNotNone(tftp.internal_pipeline)
+        self.assertEqual(
+            [action.name for action in tftp.internal_pipeline.actions],
+            ['download_retry', 'download_retry', 'download_retry', 'prepare-tftp-overlay', 'deploy-device-env']
+        )
+        self.assertIn('ramdisk', [action.key for action in tftp.internal_pipeline.actions if hasattr(action, 'key')])
+        self.assertIn('kernel', [action.key for action in tftp.internal_pipeline.actions if hasattr(action, 'key')])
+        self.assertIn('dtb', [action.key for action in tftp.internal_pipeline.actions if hasattr(action, 'key')])
+        # allow root to compare the path (with the mkdtemp added)
+        paths = {action.path for action in tftp.internal_pipeline.actions if hasattr(action, 'path')}
+        self.assertIn(
+            tftpd_dir(),
+            [item for item in paths][0]
+        )
+
+    def test_device_bbb(self):
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot.yaml')
+        self.assertEqual(
+            job.device['commands']['connect'],
+            'telnet localhost 6000'
+        )
+        self.assertEqual(job.device['commands'].get('interrupt', ' '), ' ')
+        methods = job.device['actions']['boot']['methods']
+        self.assertIn('u-boot', methods)
+        self.assertEqual(methods['u-boot']['parameters'].get('bootloader_prompt', None), 'U-Boot')
+
+    @unittest.skipIf(infrastructure_error('mkimage'), "u-boot-tools not installed")
+    def test_uboot_action(self):
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot-ramdisk.yaml')
+        job.validate()
+        self.assertEqual(job.pipeline.errors, [])
+        self.assertIn('u-boot', job.device['actions']['boot']['methods'])
+        params = job.device['actions']['boot']['methods']['u-boot']['parameters']
+        boot_message = params.get('boot_message', BOOT_MESSAGE)
+        self.assertIsNotNone(boot_message)
+        for action in job.pipeline.actions:
+            action.validate()
+            if isinstance(action, UBootAction):
+                self.assertIn('method', action.parameters)
+                self.assertEqual('u-boot', action.parameters['method'])
+                self.assertEqual(
+                    'reboot: Restarting system',
+                    action.parameters.get('parameters', {}).get('shutdown-message', SHUTDOWN_MESSAGE)
+                )
+            if isinstance(action, TftpAction):
+                self.assertIn('ramdisk', action.parameters)
+                self.assertIn('kernel', action.parameters)
+                self.assertIn('to', action.parameters)
+                self.assertEqual('tftp', action.parameters['to'])
+            self.assertTrue(action.valid)
+
+    def test_overlay_action(self):  # pylint: disable=too-many-locals
+        parameters = {
+            'device_type': 'beaglebone-black',
+            'job_name': 'uboot-pipeline',
+            'job_timeout': '15m',
+            'action_timeout': '5m',
+            'priority': 'medium',
+            'output_dir': mkdtemp(),
+            'actions': {
+                'boot': {
+                    'method': 'u-boot',
+                    'commands': 'ramdisk',
+                    'type': 'bootz'
+                },
+                'deploy': {
+                    'ramdisk': 'initrd.gz',
+                    'kernel': 'zImage',
+                    'dtb': 'broken.dtb'
+                }
+            }
+        }
+        device = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/bbb-01.yaml'))
+        job = Job(4212, None, parameters)
+        job.device = device
+        pipeline = Pipeline(job=job, parameters=parameters['actions']['boot'])
+        job.set_pipeline(pipeline)
+        overlay = UBootCommandOverlay()
+        pipeline.add_action(overlay)
+        try:
+            ip_addr = dispatcher_ip()
+        except InfrastructureError as exc:
+            raise RuntimeError("Unable to get dispatcher IP address: %s" % exc)
+        parsed = []
+        kernel_addr = job.device['parameters'][overlay.parameters['type']]['ramdisk']
+        ramdisk_addr = job.device['parameters'][overlay.parameters['type']]['ramdisk']
+        dtb_addr = job.device['parameters'][overlay.parameters['type']]['dtb']
+        kernel = parameters['actions']['deploy']['kernel']
+        ramdisk = parameters['actions']['deploy']['ramdisk']
+        dtb = parameters['actions']['deploy']['dtb']
+
+        substitution_dictionary = {
+            '{SERVER_IP}': ip_addr,
+            # the addresses need to be hexadecimal
+            '{KERNEL_ADDR}': kernel_addr,
+            '{DTB_ADDR}': dtb_addr,
+            '{RAMDISK_ADDR}': ramdisk_addr,
+            '{BOOTX}': "%s %s %s %s" % (
+                overlay.parameters['type'], kernel_addr, ramdisk_addr, dtb_addr),
+            '{RAMDISK}': ramdisk,
+            '{KERNEL}': kernel,
+            '{DTB}': dtb
+        }
+        params = device['actions']['boot']['methods']
+        params['u-boot']['ramdisk']['commands'] = substitute(params['u-boot']['ramdisk']['commands'], substitution_dictionary)
+
+        commands = params['u-boot']['ramdisk']['commands']
+        self.assertIs(type(commands), list)
+        self.assertIn("setenv loadkernel 'tftp ${kernel_addr_r} zImage'", commands)
+        self.assertIn("setenv loadinitrd 'tftp ${initrd_addr_r} initrd.gz; setenv initrd_size ${filesize}'", commands)
+        self.assertIn("setenv loadfdt 'tftp ${fdt_addr_r} broken.dtb'", commands)
+        self.assertNotIn("setenv kernel_addr_r '{KERNEL_ADDR}'", commands)
+        self.assertNotIn("setenv initrd_addr_r '{RAMDISK_ADDR}'", commands)
+        self.assertNotIn("setenv fdt_addr_r '{DTB_ADDR}'", commands)
+
+        for line in params['u-boot']['ramdisk']['commands']:
+            line = line.replace('{SERVER_IP}', ip_addr)
+            # the addresses need to be hexadecimal
+            line = line.replace('{KERNEL_ADDR}', kernel_addr)
+            line = line.replace('{DTB_ADDR}', dtb_addr)
+            line = line.replace('{RAMDISK_ADDR}', ramdisk_addr)
+            line = line.replace('{BOOTX}', "%s %s %s %s" % (
+                overlay.parameters['type'], kernel_addr, ramdisk_addr, dtb_addr))
+            line = line.replace('{RAMDISK}', ramdisk)
+            line = line.replace('{KERNEL}', kernel)
+            line = line.replace('{DTB}', dtb)
+            parsed.append(line)
+        self.assertIn("setenv loadkernel 'tftp ${kernel_addr_r} zImage'", parsed)
+        self.assertIn("setenv loadinitrd 'tftp ${initrd_addr_r} initrd.gz; setenv initrd_size ${filesize}'", parsed)
+        self.assertIn("setenv loadfdt 'tftp ${fdt_addr_r} broken.dtb'", parsed)
+        self.assertNotIn("setenv kernel_addr_r '{KERNEL_ADDR}'", parsed)
+        self.assertNotIn("setenv initrd_addr_r '{RAMDISK_ADDR}'", parsed)
+        self.assertNotIn("setenv fdt_addr_r '{DTB_ADDR}'", parsed)
+
+    @unittest.skipIf(not os.path.exists('/dev/loop0'), "loopback support not found")
+    def test_download_action(self):
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot.yaml')
+        for action in job.pipeline.actions:
+            action.validate()
+            self.assertTrue(action.valid)
+        job.validate()
+        self.assertEqual(job.pipeline.errors, [])
+        deploy = None
+        overlay = None
+        extract = None
+        for action in job.pipeline.actions:
+            if action.name == 'tftp-deploy':
+                deploy = action
+        if deploy:
+            for action in deploy.internal_pipeline.actions:
+                if action.name == 'prepare-tftp-overlay':
+                    overlay = action
+        if overlay:
+            for action in overlay.internal_pipeline.actions:
+                if action.name == 'extract-nfsrootfs':
+                    extract = action
+        self.assertIn('lava_test_results_dir', overlay.data)
+        self.assertIn('/lava-', overlay.data['lava_test_results_dir'])
+        self.assertIsNotNone(extract)
+        self.assertEqual(extract.timeout.duration, job.parameters['timeouts'][extract.name]['seconds'])
+
+    @unittest.skipIf(not os.path.exists('/dev/loop0'), "loopback support not found")
+    def test_reset_actions(self):
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot.yaml')
+        uboot_action = None
+        uboot_retry = None
+        reset_action = None
+        for action in job.pipeline.actions:
+            action.validate()
+            self.assertTrue(action.valid)
+            if action.name == 'uboot-action':
+                uboot_action = action
+        names = [r_action.name for r_action in uboot_action.internal_pipeline.actions]
+        self.assertIn('connect-device', names)
+        self.assertIn('uboot-retry', names)
+        for action in uboot_action.internal_pipeline.actions:
+            if action.name == 'uboot-retry':
+                uboot_retry = action
+        names = [r_action.name for r_action in uboot_retry.internal_pipeline.actions]
+        self.assertIn('reboot-device', names)
+        self.assertIn('u-boot-interrupt', names)
+        self.assertIn('expect-shell-connection', names)
+        self.assertIn('u-boot-commands', names)
+        for action in uboot_retry.internal_pipeline.actions:
+            if action.name == 'reboot-device':
+                reset_action = action
+        names = [r_action.name for r_action in reset_action.internal_pipeline.actions]
+        self.assertIn('soft-reboot', names)
+        self.assertIn('pdu_reboot', names)
+        self.assertIn('power_on', names)
+
+    def test_secondary_media(self):
+        """
+        Test UBootSecondaryMedia validation
+        """
+        job_parser = JobParser()
+        cubie = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/cubie1.yaml'))
+        sample_job_file = os.path.join(os.path.dirname(__file__), 'sample_jobs/cubietruck-removable.yaml')
+        sample_job_data = open(sample_job_file)
+        job = job_parser.parse(sample_job_data, cubie, 4212, None, output_dir='/tmp/')
+        job.validate()
+        u_boot_media = job.pipeline.actions[1].internal_pipeline.actions[0]
+        self.assertIsInstance(u_boot_media, UBootSecondaryMedia)
+        self.assertEqual([], u_boot_media.errors)
+        self.assertEqual(u_boot_media.parameters['kernel'], '/boot/vmlinuz-3.16.0-4-armmp-lpae')
+        self.assertEqual(u_boot_media.parameters['kernel'], u_boot_media.get_common_data('file', 'kernel'))
+        self.assertEqual(u_boot_media.parameters['ramdisk'], u_boot_media.get_common_data('file', 'ramdisk'))
+        self.assertEqual(u_boot_media.parameters['dtb'], u_boot_media.get_common_data('file', 'dtb'))
+        self.assertEqual(u_boot_media.parameters['root_uuid'], u_boot_media.get_common_data('uuid', 'root'))
+        part_reference = '%s:%s' % (
+            job.device['parameters']['media']['usb'][u_boot_media.get_common_data('u-boot', 'device')]['device_id'],
+            u_boot_media.parameters['boot_part']
+        )
+        self.assertEqual(part_reference, u_boot_media.get_common_data('uuid', 'boot_part'))
+        self.assertEqual(part_reference, "0:1")
+
+    @unittest.skipIf(infrastructure_error('telnet'), "telnet not installed")
+    def test_prompt_from_job(self):
+        """
+        Support setting the prompt after login via the job
+
+        Loads a known YAML, adds a prompt to the dict and re-parses the job.
+        Checks that the prompt is available in the expect_shell_connection action.
+        """
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot.yaml')
+        job.validate()
+        uboot = [action for action in job.pipeline.actions if action.name == 'uboot-action'][0]
+        retry = [action for action in uboot.internal_pipeline.actions
+                 if action.name == 'uboot-retry'][0]
+        expect = [action for action in retry.internal_pipeline.actions
+                  if action.name == 'expect-shell-connection'][0]
+        check = expect.parameters
+        device = NewDevice(os.path.join(os.path.dirname(__file__), '../devices/bbb-01.yaml'))
+        extra_yaml = os.path.join(os.path.dirname(__file__), 'sample_jobs/uboot.yaml')
+        with open(extra_yaml) as data:
+            sample_job_string = data.read()
+        parser = JobParser()
+        sample_job_data = yaml.load(sample_job_string)
+        boot = [item['boot'] for item in sample_job_data['actions'] if 'boot' in item][0]
+        boot.update({'parameters': {'boot_prompt': 'root@bbb'}})
+        sample_job_string = yaml.dump(sample_job_data)
+        job = parser.parse(sample_job_string, device, 4212, None, output_dir='/tmp')
+        job.validate()
+        uboot = [action for action in job.pipeline.actions if action.name == 'uboot-action'][0]
+        retry = [action for action in uboot.internal_pipeline.actions
+                 if action.name == 'uboot-retry'][0]
+        expect = [action for action in retry.internal_pipeline.actions
+                  if action.name == 'expect-shell-connection'][0]
+        self.assertNotEqual(check, expect.parameters)
+        self.assertIn('root@bbb', expect.prompts)
+
+    def test_xz_nfs(self):
+        factory = Factory()
+        job = factory.create_bbb_job('sample_jobs/uboot-nfs.yaml')
+        # this job won't validate as the .xz nfsrootfs URL is a fiction
+        self.assertRaises(JobError, job.validate)
+        tftp_deploy = [action for action in job.pipeline.actions if action.name == 'tftp-deploy'][0]
+        prepare = [action for action in tftp_deploy.internal_pipeline.actions if action.name == 'prepare-tftp-overlay'][0]
+        nfs = [action for action in prepare.internal_pipeline.actions if action.name == 'extract-nfsrootfs'][0]
+        self.assertIn('rootfs_compression', nfs.parameters)
+        self.assertEqual(nfs.parameters['rootfs_compression'], 'xz')
+        valid = tarfile.TarFile
+        if 'xz' not in valid.__dict__['OPEN_METH'].keys():
+            self.assertTrue(nfs.use_lzma)
+            self.assertFalse(nfs.use_tarfile)
+        else:
+            # python3 has xz support in tarfile.
+            self.assertFalse(nfs.use_lzma)
+            self.assertTrue(nfs.use_tarfile)
