Commit 446aa9aa authored by Neil Williams's avatar Neil Williams 💬

Merge branch 'staging' into release

Change-Id: I
parents 39923dae 9c1165f8
......@@ -13,6 +13,7 @@ include .gitreview
include COPYING
include lava/dispatcher/lava-run
include lava/dispatcher/lava-slave
include lava/lxc-mocker/*
recursive-include lava_dispatcher/devices *.yaml
recursive-include lava_dispatcher/test *.yaml *.txt
include requirements.txt
......
......@@ -6,9 +6,4 @@
missingok
notifempty
create 644 root root
postrotate
if /etc/init.d/lava-slave status > /dev/null ; then \
/etc/init.d/lava-slave reload > /dev/null; \
fi;
endscript
}
......@@ -35,6 +35,7 @@ import atexit
import errno
import fcntl
import logging
import logging.handlers
import lzma
import os
import re
......@@ -66,7 +67,7 @@ from lava_dispatcher.job import ZMQConfig
# pylint: disable=too-many-statements
# Default values for:
PROTOCOL_VERSION = 2
PROTOCOL_VERSION = 3
# timeouts (in seconds)
TIMEOUT = 5
JOBS_CHECK_INTERVAL = 5
......@@ -265,7 +266,7 @@ def send_multipart_u(sock, data):
:param sock: The socket to use
:param data: Data to convert to byte strings
"""
return sock.send_multipart([b(d) for d in data])
return sock.send_multipart([b"master"] + [b(d) for d in data])
def send_end(sock, job_id, job, print_exception=False):
......@@ -294,10 +295,15 @@ def create_zmq_context(master_uri, hostname, ipv6, encrypt,
"""
# Connect to the master dispatcher.
context = zmq.Context()
# TODO: use a ROUTER socket
sock = context.socket(zmq.DEALER)
sock = context.socket(zmq.ROUTER)
sock.setsockopt(zmq.IDENTITY, b(hostname))
# Limit the number of messages in the queue
sock.setsockopt(zmq.SNDHWM, SEND_QUEUE)
# TODO: remove when Jessie is not supported
if hasattr(zmq, "CONNECT_RID"):
# From http://api.zeromq.org/4-2:zmq-setsockopt#toc5
# "Immediately readies that connection for data transfer with the master"
sock.setsockopt(zmq.CONNECT_RID, b"master")
if ipv6:
LOG.info("[INIT] Enabling IPv6")
......@@ -377,7 +383,7 @@ def configure_logger(log_file, level):
if log_file == "-":
handler = logging.StreamHandler(sys.stdout)
else:
handler = logging.FileHandler(log_file, "a")
handler = logging.handlers.WatchedFileHandler(log_file)
handler.setFormatter(logging.Formatter(FORMAT))
LOG.addHandler(handler)
......@@ -402,7 +408,6 @@ def connect_to_master(master, poller, pipe_r, sock, timeout):
:param timeout: the poll timeout
:return: True if the master had answered
"""
retry_msg = "HELLO_RETRY"
try:
sockets = dict(poller.poll(timeout * 1000))
except zmq.error.ZMQError:
......@@ -418,6 +423,12 @@ def connect_to_master(master, poller, pipe_r, sock, timeout):
msg = sock.recv_multipart()
try:
# Check master identity
master_id = u(msg.pop(0))
if master_id != "master":
LOG.error("Invalid master id '%s'. Should be 'master'",
master_id)
return False
message = u(msg[0])
except (IndexError, TypeError):
LOG.error("[INIT] Invalid message from master: %s", msg)
......@@ -430,9 +441,6 @@ def connect_to_master(master, poller, pipe_r, sock, timeout):
else:
LOG.info("[INIT] Unexpected message from master: %s", message)
LOG.info("[INIT] Greeting master => '%s' (using the same version?)",
retry_msg)
send_multipart_u(sock, [retry_msg, str(PROTOCOL_VERSION)])
return False
......@@ -619,16 +627,7 @@ def listen_to_master(master, jobs, poller, pipe_r, zmq_config, sock, timeout):
if sockets.get(pipe_r) == zmq.POLLIN:
signum = ord(os.read(pipe_r, 1))
if signum == signal.SIGHUP:
LOG.info("SIGHUP received, restarting loggers")
handler = LOG.handlers[0]
if isinstance(handler, logging.FileHandler):
# Keep the filename and remove the handler
log_file = handler.baseFilename
LOG.removeHandler(handler)
# Re-create the handler
handler = logging.FileHandler(log_file, "a")
handler.setFormatter(logging.Formatter(FORMAT))
LOG.addHandler(handler)
LOG.info("SIGHUP received, ignoring")
else:
LOG.info("Received a signal, leaving")
sys.exit(0)
......@@ -636,8 +635,13 @@ def listen_to_master(master, jobs, poller, pipe_r, zmq_config, sock, timeout):
if sockets.get(sock) == zmq.POLLIN:
msg = sock.recv_multipart()
# 1: the action
# 1: identity and action
try:
master_id = u(msg.pop(0))
if master_id != "master":
LOG.error("Invalid master id '%s'. Should be 'master'",
master_id)
return
action = u(msg[0])
except (IndexError, TypeError):
LOG.error("Invalid message from master: %s", msg)
......@@ -820,7 +824,8 @@ def main():
send_multipart_u(sock, ["HELLO", str(PROTOCOL_VERSION)])
while not connect_to_master(master, poller, pipe_r, sock, timeout):
pass
LOG.info("[INIT] Greeting master => 'HELLO_RETRY' (using the same version?)")
send_multipart_u(sock, ["HELLO_RETRY", str(PROTOCOL_VERSION)])
# Loop for server instructions
LOG.info("Waiting for instructions")
......
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Linaro Limited
#
# Author: Senthil Kumaran S <senthil.kumaran@linaro.org>
#
# This file is part of LAVA LXC mocker.
#
# Released under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
# Mocks lxc-attach command which is used by LAVA.
CMD=$(awk -F'-- ' '{print $2}' <<< "$@")
while getopts "n:" opt; do
case $opt in
n)
LXC_NAME="$OPTARG"
;;
*)
;;
esac
done
if [ "$CMD" ]; then
# execute the given command
$CMD
else
# when no commands are requested, open up a shell
exec /bin/bash
fi
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Linaro Limited
#
# Author: Senthil Kumaran S <senthil.kumaran@linaro.org>
#
# This file is part of LAVA LXC mocker.
#
# Released under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
# Mocks lxc-create command which is used by LAVA.
# Get the list of requested packages.
PACKAGES=$(sed 's/,/ /g' <<< $(cut -d' ' -f1 <<< $(awk -F'--packages ' '{print $2}' <<< "$@")))
while getopts "qt:n:" opt; do
case $opt in
q)
QUIET=1
;;
n)
LXC_NAME="$OPTARG"
;;
*)
;;
esac
done
if [ "$PACKAGES" ] && [ "$QUIET" ]; then
DEBIAN_FRONTEND=noninteractive apt update > /dev/null 2>&1
DEBIAN_FRONTEND=noninteractive apt upgrade -y > /dev/null 2>&1
# install the requested packages.
DEBIAN_FRONTEND=noninteractive apt install -y $PACKAGES > /dev/null 2>&1
elif [ "$PACKAGES" ]; then
DEBIAN_FRONTEND=noninteractive apt update
DEBIAN_FRONTEND=noninteractive apt upgrade -y
# install the requested packages.
DEBIAN_FRONTEND=noninteractive apt install -y $PACKAGES
fi
if [ "$LXC_NAME" ]; then
# create dummy lxc rootfs.
mkdir -p /var/lib/lxc/${LXC_NAME}
ln -s / /var/lib/lxc/${LXC_NAME}/rootfs
fi
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Linaro Limited
#
# Author: Senthil Kumaran S <senthil.kumaran@linaro.org>
#
# This file is part of LAVA LXC mocker.
#
# Released under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
# Mocks lxc-destroy command which is used by LAVA.
while getopts "fn:" opt; do
case $opt in
n)
LXC_NAME="$OPTARG"
;;
*)
;;
esac
done
if [ "$LXC_NAME" ]; then
# Remove lxc rootfs directory if any
rm -rf /var/lib/lxc/${LXC_NAME}
# echo container destroyed message
echo "Destroyed container $LXC_NAME"
exit 0
fi
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Linaro Limited
#
# Author: Senthil Kumaran S <senthil.kumaran@linaro.org>
#
# This file is part of LAVA LXC mocker.
#
# Released under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
# Mocks lxc-device command which is used by LAVA.
echo "True"
exit 0
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Linaro Limited
#
# Author: Senthil Kumaran S <senthil.kumaran@linaro.org>
#
# This file is part of LAVA LXC mocker.
#
# Released under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
# Mocks lxc-info command which is used by LAVA.
while getopts "s:i:n:" opt; do
case $opt in
n)
LXC_NAME="$OPTARG"
;;
s)
STATUS=1
;;
i)
IP=1
;;
*)
;;
esac
done
if [ "$STATUS" ]; then
# echo running state.
echo "'$LXC_NAME' state is RUNNING"
exit 0
fi
if [ "$IP" ]; then
# echo a dummy ip.
echo "'$LXC_NAME' IP address is: '0.0.0.0'"
exit 0
fi
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Linaro Limited
#
# Author: Senthil Kumaran S <senthil.kumaran@linaro.org>
#
# This file is part of LAVA LXC mocker.
#
# Released under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
# Mocks lxc-start command which is used by LAVA.
exit 0
#!/bin/bash
# -*- coding: utf-8 -*-
#
# Copyright (C) 2018 Linaro Limited
#
# Author: Senthil Kumaran S <senthil.kumaran@linaro.org>
#
# This file is part of LAVA LXC mocker.
#
# Released under the MIT License:
# http://www.opensource.org/licenses/mit-license.php
#
# Mocks lxc-stop command which is used by LAVA.
exit 0
This diff is collapsed.
......@@ -208,8 +208,8 @@ class DepthchargeStart(Action):
def validate(self):
super(DepthchargeStart, self).validate()
if self.job.device.connect_command is '':
self.errors = "Unable to connect to device %s"
if self.job.device.connect_command == '':
self.errors = "Unable to connect to device"
method = self.job.device['actions']['boot']['methods']['depthcharge']
self.start_message = method['parameters'].get('start_message')
if self.start_message is None:
......
......@@ -121,6 +121,13 @@ class BootFastbootAction(BootAction):
mapped[0](device_actions=mapped[1]))
elif mapped[0]:
self.internal_pipeline.add_action(mapped[0]())
if self.has_prompts(parameters):
self.internal_pipeline.add_action(AutoLoginAction())
if self.test_has_shell(parameters):
self.internal_pipeline.add_action(ExpectShellSession())
if 'transfer_overlay' in parameters:
self.internal_pipeline.add_action(OverlayUnpack())
self.internal_pipeline.add_action(ExportDeviceEnvironment())
class WaitFastBootInterrupt(Action):
......
......@@ -35,6 +35,7 @@ from lava_dispatcher.actions.boot import (
BootloaderSecondaryMedia,
BootloaderCommandsAction,
OverlayUnpack,
BootloaderInterruptAction
)
from lava_dispatcher.actions.boot.uefi_menu import (
UEFIMenuInterrupt,
......@@ -195,7 +196,7 @@ class GrubMainAction(BootAction):
if parameters['method'] == 'grub-efi':
self.internal_pipeline.add_action(UEFIMenuInterrupt())
self.internal_pipeline.add_action(GrubMenuSelector())
self.internal_pipeline.add_action(BootloaderInterrupt())
self.internal_pipeline.add_action(BootloaderInterruptAction())
self.internal_pipeline.add_action(BootloaderCommandsAction())
if self.has_prompts(parameters):
self.internal_pipeline.add_action(AutoLoginAction())
......@@ -216,42 +217,6 @@ class GrubMainAction(BootAction):
return connection
class BootloaderInterrupt(Action):
"""
Support for interrupting the bootloader.
"""
def __init__(self):
super(BootloaderInterrupt, self).__init__()
self.name = "bootloader-interrupt"
self.description = "interrupt bootloader"
self.summary = "interrupt bootloader to get a prompt"
self.type = "grub"
def validate(self):
super(BootloaderInterrupt, self).validate()
if self.job.device.connect_command is '':
self.errors = "Unable to connect to device"
device_methods = self.job.device['actions']['boot']['methods']
if self.parameters['method'] == 'grub-efi' and 'grub-efi' in device_methods:
self.type = 'grub-efi'
if 'bootloader_prompt' not in device_methods[self.type]['parameters']:
self.errors = "[%s] Missing bootloader prompt for device" % self.name
def run(self, connection, max_end_time, args=None):
if not connection:
raise LAVABug("%s started without a connection already in use" % self.name)
connection = super(BootloaderInterrupt, self).run(connection, max_end_time, args)
device_methods = self.job.device['actions']['boot']['methods']
interrupt_prompt = device_methods[self.type]['parameters'].get('interrupt_prompt', self.job.device.get_constant('grub-autoboot-prompt'))
# interrupt_char can actually be a sequence of ASCII characters - sendline does not care.
interrupt_char = device_methods[self.type]['parameters'].get('interrupt_char', self.job.device.get_constant('grub-interrupt-character'))
# device is to be put into a reset state, either by issuing 'reboot' or power-cycle
connection.prompt_str = interrupt_prompt
self.wait(connection)
connection.raw_connection.send(interrupt_char)
return connection
class GrubMenuSelector(UefiMenuSelector): # pylint: disable=too-many-instance-attributes
def __init__(self):
......@@ -276,8 +241,9 @@ class GrubMenuSelector(UefiMenuSelector): # pylint: disable=too-many-instance-a
super(GrubMenuSelector, self).validate()
def run(self, connection, max_end_time, args=None):
# Needs to get the interrupt_prompt from the bootloader device config
interrupt_prompt = self.params['parameters'].get(
'interrupt_prompt', self.job.device.get_constant('grub-autoboot-prompt'))
'interrupt_prompt', self.job.device.get_constant('interrupt-prompt', prefix='grub'))
self.logger.debug("Adding '%s' to prompt", interrupt_prompt)
connection.prompt_str = interrupt_prompt
# override base class behaviour to interact with grub.
......
......@@ -33,6 +33,7 @@ from lava_dispatcher.actions.boot import (
BootloaderCommandOverlay,
BootloaderCommandsAction,
OverlayUnpack,
BootloaderInterruptAction
)
from lava_dispatcher.actions.boot.environment import ExportDeviceEnvironment
from lava_dispatcher.shell import ExpectShellSession
......@@ -105,7 +106,7 @@ class BootloaderRetry(BootAction):
self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
# establish a new connection before trying the reset
self.internal_pipeline.add_action(ResetDevice())
self.internal_pipeline.add_action(BootloaderInterrupt())
self.internal_pipeline.add_action(BootloaderInterruptAction())
# need to look for Hit any key to stop autoboot
self.internal_pipeline.add_action(BootloaderCommandsAction())
if self.has_prompts(parameters):
......@@ -131,34 +132,3 @@ class BootloaderRetry(BootAction):
connection = super(BootloaderRetry, self).run(connection, max_end_time, args)
self.set_namespace_data(action='shared', label='shared', key='connection', value=connection)
return connection
class BootloaderInterrupt(Action):
"""
Support for interrupting the bootloader.
"""
def __init__(self):
super(BootloaderInterrupt, self).__init__()
self.name = "bootloader-interrupt"
self.description = "interrupt bootloader"
self.summary = "interrupt bootloader to get a prompt"
self.type = "ipxe"
def validate(self):
super(BootloaderInterrupt, self).validate()
if self.job.device.connect_command is '':
self.errors = "Unable to connect to device"
device_methods = self.job.device['actions']['boot']['methods']
if 'bootloader_prompt' not in device_methods[self.type]['parameters']:
self.errors = "Missing bootloader prompt for device"
def run(self, connection, max_end_time, args=None):
if not connection:
raise LAVABug("%s started without a connection already in use" % self.name)
connection = super(BootloaderInterrupt, self).run(connection, max_end_time, args)
self.logger.debug("Changing prompt to '%s'", IPXE_BOOT_PROMPT)
# device is to be put into a reset state, either by issuing 'reboot' or power-cycle
connection.prompt_str = IPXE_BOOT_PROMPT
self.wait(connection)
connection.sendcontrol("b")
return connection
......@@ -35,6 +35,7 @@ from lava_dispatcher.connections.lxc import (
from lava_dispatcher.shell import ExpectShellSession
from lava_dispatcher.utils.shell import infrastructure_error
from lava_dispatcher.utils.udev import get_udev_devices
from lava_dispatcher.utils.udev import allow_fs_label
class BootLxc(Boot):
......@@ -104,7 +105,8 @@ class LxcAddStaticDevices(Action):
"""
usb_devices = []
for device in self.job.device.get('static_info', []):
if 'board_id' in device:
if 'board_id' in device \
or 'fs_label' in device:
# This is a USB device
usb_devices.append(device)
return usb_devices
......@@ -112,9 +114,14 @@ class LxcAddStaticDevices(Action):
def validate(self):
super(LxcAddStaticDevices, self).validate()
# If there are no USB devices under static_info then this action should be idempotent.
# If we are allowed to use a filesystem label, we don't require a board_id
# By default, we do require a board_id (serial)
requires_board_id = not allow_fs_label(self.job.device)
try:
for usb_device in self.get_usb_devices():
if usb_device.get('board_id', '') in ['', '0000000000']:
if usb_device.get('board_id', '') in ['', '0000000000'] and \
requires_board_id:
self.errors = "board_id unset"
if usb_device.get('usb_vendor_id', '') == '0000':
self.errors = 'usb_vendor_id unset'
......
......@@ -35,6 +35,7 @@ from lava_dispatcher.actions.boot import (
BootloaderCommandsAction,
BootloaderSecondaryMedia,
OverlayUnpack,
BootloaderInterruptAction
)
from lava_dispatcher.actions.boot.environment import ExportDeviceEnvironment
from lava_dispatcher.shell import ExpectShellSession
......@@ -113,7 +114,7 @@ class UBootRetry(BootAction):
self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
# establish a new connection before trying the reset
self.internal_pipeline.add_action(ResetDevice())
self.internal_pipeline.add_action(UBootInterrupt())
self.internal_pipeline.add_action(BootloaderInterruptAction())
self.internal_pipeline.add_action(BootloaderCommandsAction())
if self.has_prompts(parameters):
self.internal_pipeline.add_action(AutoLoginAction())
......@@ -138,46 +139,6 @@ class UBootRetry(BootAction):
return connection
class UBootInterrupt(Action):
"""
Support for interrupting the bootloader.
"""
def __init__(self):
super(UBootInterrupt, self).__init__()
self.name = "u-boot-interrupt"
self.description = "interrupt u-boot"
self.summary = "interrupt u-boot to get a prompt"
def validate(self):
super(UBootInterrupt, self).validate()
if self.job.device.connect_command is '':
self.errors = "Unable to connect to device %s"
device_methods = self.job.device['actions']['boot']['methods']
if 'bootloader_prompt' not in device_methods['u-boot']['parameters']:
self.errors = "Missing bootloader prompt for device"
def run(self, connection, max_end_time, args=None):
if not connection:
raise LAVABug("%s started without a connection already in use" % self.name)
connection = super(UBootInterrupt, self).run(connection, max_end_time, args)
device_methods = self.job.device['actions']['boot']['methods']
# device is to be put into a reset state, either by issuing 'reboot' or power-cycle
interrupt_prompt = device_methods['u-boot']['parameters'].get('interrupt_prompt', self.job.device.get_constant('uboot-autoboot-prompt'))
# interrupt_char can actually be a sequence of ASCII characters - sendline does not care.
interrupt_char = device_methods['u-boot']['parameters'].get('interrupt_char', self.job.device.get_constant('uboot-interrupt-character'))
# vendor u-boot builds may require one or more control characters
interrupt_control_chars = device_methods['u-boot']['parameters'].get('interrupt_ctrl_list', [])
self.logger.debug("Changing prompt to '%s'", interrupt_prompt)
connection.prompt_str = interrupt_prompt
self.wait(connection)
if interrupt_control_chars:
for char in interrupt_control_chars:
connection.sendcontrol(char)
else:
connection.sendline(interrupt_char)
return connection
class UBootSecondaryMedia(BootloaderSecondaryMedia):
"""
Idempotent action which sets the static data only used when this is a boot of secondary media
......@@ -241,7 +202,7 @@ class UBootEnterFastbootAction(BootAction):
# establish a new connection before trying the reset
self.internal_pipeline.add_action(ResetDevice())
# need to look for Hit any key to stop autoboot
self.internal_pipeline.add_action(UBootInterrupt())
self.internal_pipeline.add_action(BootloaderInterruptAction())
self.internal_pipeline.add_action(ConnectLxc())
def validate(self):
......
......@@ -566,7 +566,7 @@ class CompressRamdisk(Action):
full_path = os.path.join(tftp_dir, os.path.basename(final_file))
shutil.move(final_file, full_path)
self.logger.debug("rename {} to {}".format(final_file, full_path))
self.logger.debug("rename %s to %s", final_file, full_path)
self.set_namespace_data(
action=self.name, label='file', key='full-path', value=full_path)
......
......@@ -54,6 +54,8 @@ class DockerAction(DeployAction):
self.logger.debug("docker client, installed at version: %s", out)
except subprocess.CalledProcessError as exc:
raise InfrastructureError("Unable to call '%s': %s" % (exc.cmd, exc.output))
except OSError:
raise InfrastructureError("Command 'docker' does not exist")
# Add the lava_test_results_dir to the right namespace
if self.test_needs_deployment(self.parameters):
......
......@@ -455,7 +455,7 @@ class HttpDownloadAction(DownloadHandler):
res = requests.get(
self.url.geturl(), allow_redirects=True, stream=True)
if res.status_code != requests.codes.OK: # pylint: disable=no-member
self.errors = "Resources not available at '%s'" % (self.url.geturl())
self.errors = "Resource unavailable at '%s' (%d)" % (self.url.geturl(), res.status_code)
self.size = int(res.headers.get('content-length', -1))
except requests.Timeout:
......
......@@ -153,6 +153,7 @@ class FastbootFlashOrderAction(DeployAction):
self.sleep = 10
self.interrupt_prompt = None
self.interrupt_string = None
self.reboot = None
def populate(self, parameters):
self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
......@@ -166,19 +167,22 @@ class FastbootFlashOrderAction(DeployAction):
if flash_cmd not in parameters['images']:
continue
self.internal_pipeline.add_action(FastbootFlashAction(cmd=flash_cmd))
reboot = parameters['images'][flash_cmd].get('reboot', None)
if reboot == 'fastboot-reboot':
self.reboot = parameters['images'][flash_cmd].get('reboot', None)
if self.reboot == 'fastboot-reboot':
self.internal_pipeline.add_action(FastbootReboot())
self.internal_pipeline.add_action(ReadFeedback(repeat=True))
elif reboot == 'fastboot-reboot-bootloader':
elif self.reboot == 'fastboot-reboot-bootloader':
self.internal_pipeline.add_action(FastbootRebootBootloader())
self.internal_pipeline.add_action(ReadFeedback(repeat=True))
elif reboot == 'hard-reset':
elif self.reboot == 'hard-reset':
self.internal_pipeline.add_action(PDUReboot())
self.internal_pipeline.add_action(ReadFeedback(repeat=True))
def validate(self):
super(FastbootFlashOrderAction, self).validate()
self.set_namespace_data(
action=FastbootFlashAction.name, label='interrupt',
key='reboot', value=self.reboot)
if 'fastboot_serial_number' not in self.job.device:
self.errors = "device fastboot serial number missing"
elif self.job.device['fastboot_serial_number'] == '0000000000':
......@@ -236,7 +240,11 @@ class FastbootFlashAction(Action):
if 'no-flash-boot' in sequence and self.command in ['boot']:
return connection
if self.interrupt_prompt:
# if a reboot is requested, will need to wait for the prompt
# if not, continue in the existing mode.
reboot = self.get_namespace_data(