aboutsummaryrefslogtreecommitdiff
path: root/lava_dispatcher/actions/boot/docker.py
blob: 0f9ee007dfa4f0ddf4f4ec0e47512140549d18af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# Copyright (C) 2017 Linaro Limited
#
# Author: Remi Duraffort <remi.duraffort@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

from lava_dispatcher.action import (
    Pipeline,
    Action,
)
from lava_dispatcher.logical import Boot, RetryAction
from lava_dispatcher.actions.boot import BootAction
from lava_dispatcher.actions.boot.environment import ExportDeviceEnvironment
from lava_dispatcher.shell import (
    ExpectShellSession,
    ShellCommand,
    ShellSession
)


class BootDocker(Boot):
    compatibility = 4

    def __init__(self, parent, parameters):
        super().__init__(parent)
        self.action = BootDockerAction()
        self.action.section = self.action_type
        self.action.job = self.job
        parent.add_action(self.action, parameters)

    @classmethod
    def accepts(cls, device, parameters):
        if "docker" not in device['actions']['boot']['methods']:
            return False, '"docker" was not in the device configuration boot methods'
        if "command" not in parameters:
            return False, '"command" was not in boot parameters'
        return True, 'accepted'


class BootDockerAction(BootAction):

    name = 'boot-docker'
    description = "boot docker image"
    summary = "boot docker image"

    def populate(self, parameters):
        self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
        self.internal_pipeline.add_action(BootDockerRetry())
        if self.has_prompts(parameters):
            if self.test_has_shell(parameters):
                self.internal_pipeline.add_action(ExpectShellSession())
                self.internal_pipeline.add_action(ExportDeviceEnvironment())


class BootDockerRetry(RetryAction):

    name = 'boot-docker-retry'
    description = "boot docker image with retry"
    summary = "boot docker image"

    def populate(self, parameters):
        self.internal_pipeline = Pipeline(parent=self, job=self.job, parameters=parameters)
        self.internal_pipeline.add_action(CallDockerAction())


class CallDockerAction(Action):

    name = "docker-run"
    description = "call docker run on the image"
    summary = "call docker run"

    def __init__(self):
        super().__init__()
        self.cleanup_required = False
        self.extra_options = ''

    def validate(self):
        super().validate()
        self.container = "lava-%s-%s" % (self.job.job_id, self.level)

        options = self.job.device['actions']['boot']['methods']['docker']['options']

        if options['cpus']:
            self.extra_options += ' --cpus %s' % options['cpus']
        if options['memory']:
            self.extra_options += ' --memory %s' % options['memory']
        if options['volumes']:
            for volume in options['volumes']:
                self.extra_options += ' --volume %s' % volume

    def run(self, connection, max_end_time, args=None):
        location = self.get_namespace_data(action='test', label='shared', key='location')
        docker_image = self.get_namespace_data(action='deploy-docker', label='image', key='name')

        # Build the command line
        # The docker image is safe to be included in the command line
        cmd = "docker run --interactive --tty --hostname lava"
        cmd += " --name %s" % self.container
        if self.test_needs_overlay(self.parameters):
            overlay = self.get_namespace_data(action='test', label='results', key='lava_test_results_dir')
            cmd += " --volume %s:%s" % (os.path.join(location, overlay.strip("/")), overlay)
        cmd += self.extra_options
        cmd += " %s %s" % (docker_image, self.parameters["command"])

        self.logger.debug("Boot command: %s", cmd)
        shell = ShellCommand(cmd, self.timeout, logger=self.logger)
        self.cleanup_required = True

        shell_connection = ShellSession(self.job, shell)
        shell_connection = super().run(shell_connection, max_end_time, args)

        self.set_namespace_data(action='shared', label='shared', key='connection', value=shell_connection)
        return shell_connection

    def cleanup(self, connection):
        super().cleanup(connection)
        if self.cleanup_required:
            self.logger.debug("Stopping container %s", self.container)
            self.run_command(["docker", "stop", self.container], allow_fail=True)
            self.logger.debug("Removing container %s", self.container)
            self.run_command(["docker", "rm", self.container], allow_fail=True)
            self.cleanup_required = False