test_docker.py 9.34 KB
Newer Older
André Anjos's avatar
André Anjos committed
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
#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

###############################################################################
#                                                                             #
# Copyright (c) 2016 Idiap Research Institute, http://www.idiap.ch/           #
# Contact: beat.support@idiap.ch                                              #
#                                                                             #
# This file is part of the beat.core module of the BEAT platform.             #
#                                                                             #
# Commercial License Usage                                                    #
# Licensees holding valid commercial BEAT licenses may use this file in       #
# accordance with the terms contained in a written agreement between you      #
# and Idiap. For further information contact tto@idiap.ch                     #
#                                                                             #
# Alternatively, this file may be used under the terms of the GNU Affero      #
# Public License version 3 as published by the Free Software and appearing    #
# in the file LICENSE.AGPL included in the packaging of this file.            #
# The BEAT platform 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.                                        #
#                                                                             #
# You should have received a copy of the GNU Affero Public License along      #
# with the BEAT platform. If not, see http://www.gnu.org/licenses/.           #
#                                                                             #
###############################################################################


"""Asynchronous process I/O with the Subprocess module
"""

import os
import sys
import time
35
import unittest
André Anjos's avatar
André Anjos committed
36
import pkg_resources
37
import time
André Anjos's avatar
André Anjos committed
38

39 40
import docker
import requests
André Anjos's avatar
André Anjos committed
41

42
from ..dock import Host
43
from . import tmp_prefix
44
from .utils import slow
André Anjos's avatar
André Anjos committed
45

46

André Anjos's avatar
André Anjos committed
47 48
# in case you want to see the printouts dynamically, set to ``True``
if False:
Philip ABBET's avatar
Philip ABBET committed
49 50 51 52 53 54 55
    import logging
    logger = logging.getLogger() #root logger
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
    logger.addHandler(ch)
André Anjos's avatar
André Anjos committed
56 57


58

59 60
class AsyncTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
61 62 63 64
    @classmethod
    def setUpClass(cls):
        cls.host = Host(raise_on_errors=False)
        cls.test_environment = cls.host.full_environment_name('Python 2.7')
65 66


Philip ABBET's avatar
Philip ABBET committed
67 68 69
    @classmethod
    def tearDownClass(cls):
        cls.host.teardown()
70 71


Philip ABBET's avatar
Philip ABBET committed
72 73 74
    def tearDown(self):
        self.host.teardown()
        assert not self.host.containers # All containers are gone
75 76


Philip ABBET's avatar
Philip ABBET committed
77 78
    @slow
    def test_echo(self):
79

Philip ABBET's avatar
Philip ABBET committed
80
        string = "hello, world"
81

Philip ABBET's avatar
Philip ABBET committed
82 83 84
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
85

Philip ABBET's avatar
Philip ABBET committed
86 87 88
        self.assertEqual(status, 0)
        self.assertEqual(self.host.stdout(container), string + '\n')
        self.assertEqual(self.host.stderr(container), '')
89 90


Philip ABBET's avatar
Philip ABBET committed
91 92
    @slow
    def test_non_existing(self):
93

Philip ABBET's avatar
Philip ABBET committed
94
        container = self.host.create_container('debian:8.4', ["sdfsdfdsf329909092"])
95

Philip ABBET's avatar
Philip ABBET committed
96
        self.assertRaises(docker.errors.NotFound, self.host.start, container)
97

Philip ABBET's avatar
Philip ABBET committed
98
        assert not self.host.containers # All containers are gone
99 100


Philip ABBET's avatar
Philip ABBET committed
101 102
    @slow
    def test_timeout(self):
103

Philip ABBET's avatar
Philip ABBET committed
104
        sleep_for = 100 # seconds
105

Philip ABBET's avatar
Philip ABBET committed
106 107
        container = self.host.create_container('debian:8.4', ["sleep", str(sleep_for)])
        self.host.start(container)
108

Philip ABBET's avatar
Philip ABBET committed
109 110 111 112 113
        try:
            retval = self.host.wait(container, timeout=0.5)
            assert False, "timeout never occurred after %d seconds" % sleep_for
        except requests.exceptions.ReadTimeout as e:
            self.assertEqual(self.host.status(container), 'running')
114

Philip ABBET's avatar
Philip ABBET committed
115
        self.host.kill(container)
116

Philip ABBET's avatar
Philip ABBET committed
117
        status = self.host.wait(container)
118

Philip ABBET's avatar
Philip ABBET committed
119 120 121 122
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
        self.assertEqual(self.host.stdout(container), '')
        self.assertEqual(self.host.stderr(container), '')
123 124


Philip ABBET's avatar
Philip ABBET committed
125 126
    @slow
    def test_does_not_timeout(self):
127

Philip ABBET's avatar
Philip ABBET committed
128
        sleep_for = 0.5 # seconds
129

Philip ABBET's avatar
Philip ABBET committed
130 131
        container = self.host.create_container('debian:8.4', ["sleep", str(sleep_for)])
        self.host.start(container)
132

Philip ABBET's avatar
Philip ABBET committed
133
        status = self.host.wait(container, timeout=5) # Should not timeout
134

Philip ABBET's avatar
Philip ABBET committed
135 136 137 138
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
        self.assertEqual(self.host.stdout(container), '')
        self.assertEqual(self.host.stderr(container), '')
139 140


Philip ABBET's avatar
Philip ABBET committed
141 142
    @slow
    def test_memory_limit(self):
143

Philip ABBET's avatar
Philip ABBET committed
144 145 146 147 148 149 150 151
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
152

Philip ABBET's avatar
Philip ABBET committed
153 154
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
155

Philip ABBET's avatar
Philip ABBET committed
156
        time.sleep(2)
157

Philip ABBET's avatar
Philip ABBET committed
158
        stats = self.host.statistics(container)
159

Philip ABBET's avatar
Philip ABBET committed
160
        status = self.host.wait(container)
161

Philip ABBET's avatar
Philip ABBET committed
162 163 164 165
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
        self.assertEqual(self.host.stdout(container).strip(), 'Before')
        self.assertEqual(self.host.stderr(container), '')
166 167


Philip ABBET's avatar
Philip ABBET committed
168 169
    @slow
    def test_memory_limit2(self):
170

Philip ABBET's avatar
Philip ABBET committed
171 172 173 174 175 176 177 178
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
179

Philip ABBET's avatar
Philip ABBET committed
180 181
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
182

Philip ABBET's avatar
Philip ABBET committed
183
        time.sleep(2)
184

Philip ABBET's avatar
Philip ABBET committed
185
        stats = self.host.statistics(container)
186

Philip ABBET's avatar
Philip ABBET committed
187
        status = self.host.wait(container)
188

Philip ABBET's avatar
Philip ABBET committed
189 190
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
191

Philip ABBET's avatar
Philip ABBET committed
192 193
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
194

Philip ABBET's avatar
Philip ABBET committed
195 196 197 198
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
        self.assertEqual(self.host.stdout(container).strip(), 'Before\nAfter')
        self.assertEqual(self.host.stderr(container), '')
199 200


Philip ABBET's avatar
Philip ABBET committed
201
    def _run_cpulimit(self, processes, max_cpu_percent, sleep_time):
202

Philip ABBET's avatar
Philip ABBET committed
203 204
        program = pkg_resources.resource_filename(__name__, 'cpu_stress.py')
        dst_name = os.path.join('/tmp', os.path.basename(program))
205

Philip ABBET's avatar
Philip ABBET committed
206 207
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
208

Philip ABBET's avatar
Philip ABBET committed
209
        container.copy_path(program, '/tmp')
210

Philip ABBET's avatar
Philip ABBET committed
211
        self.host.start(container, max_cpu_percent=max_cpu_percent)
212

Philip ABBET's avatar
Philip ABBET committed
213
        time.sleep(sleep_time)
214

Philip ABBET's avatar
Philip ABBET committed
215
        stats = self.host.statistics(container)
216

Philip ABBET's avatar
Philip ABBET committed
217
        self.assertEqual(self.host.status(container), 'running')
218

Philip ABBET's avatar
Philip ABBET committed
219 220 221
        percent = stats['cpu']['percent']
        assert percent < (1.1 * max_cpu_percent), \
               "%.2f%% is more than 20%% off the expected ceiling at %d%%!" % (percent, max_cpu_percent)
222

Philip ABBET's avatar
Philip ABBET committed
223 224 225
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
226 227


Philip ABBET's avatar
Philip ABBET committed
228 229 230 231
    @slow
    def test_cpulimit_at_20percent(self):
        # runs 1 process that should consume at most 20% of the host CPU
        self._run_cpulimit(1, 20, 3)
232 233


Philip ABBET's avatar
Philip ABBET committed
234 235 236 237
    @slow
    def test_cpulimit_at_100percent(self):
        # runs 4 processes that should consume 50% of the host CPU
        self._run_cpulimit(4, 100, 3)
238 239 240 241 242



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
243 244
    def setUp(self):
        Host.images_cache = {}
245 246


Philip ABBET's avatar
Philip ABBET committed
247 248 249
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
250

Philip ABBET's avatar
Philip ABBET committed
251 252
        # Might take some time
        start = time.time()
253

Philip ABBET's avatar
Philip ABBET committed
254 255
        host = Host(raise_on_errors=False)
        host.teardown()
256

Philip ABBET's avatar
Philip ABBET committed
257
        stop = time.time()
258

Philip ABBET's avatar
Philip ABBET committed
259 260
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
261

Philip ABBET's avatar
Philip ABBET committed
262
        self.assertTrue(stop - start > 2.0)
263

Philip ABBET's avatar
Philip ABBET committed
264 265
        # Should be instantaneous
        start = time.time()
266

Philip ABBET's avatar
Philip ABBET committed
267 268
        host = Host(raise_on_errors=False)
        host.teardown()
269

Philip ABBET's avatar
Philip ABBET committed
270
        stop = time.time()
271

Philip ABBET's avatar
Philip ABBET committed
272
        self.assertEqual(len(Host.images_cache), nb_images)
273

Philip ABBET's avatar
Philip ABBET committed
274
        self.assertTrue(stop - start < 1.0)
275 276


Philip ABBET's avatar
Philip ABBET committed
277 278 279
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
280

Philip ABBET's avatar
Philip ABBET committed
281 282
        # Might take some time
        start = time.time()
283

Philip ABBET's avatar
Philip ABBET committed
284 285 286
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
287

Philip ABBET's avatar
Philip ABBET committed
288
        stop = time.time()
289

Philip ABBET's avatar
Philip ABBET committed
290 291
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
292

Philip ABBET's avatar
Philip ABBET committed
293
        self.assertTrue(stop - start > 2.0)
294

Philip ABBET's avatar
Philip ABBET committed
295
        Host.images_cache = {}
296

Philip ABBET's avatar
Philip ABBET committed
297 298
        # Should be instantaneous
        start = time.time()
299

Philip ABBET's avatar
Philip ABBET committed
300 301 302
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
303

Philip ABBET's avatar
Philip ABBET committed
304
        stop = time.time()
305

Philip ABBET's avatar
Philip ABBET committed
306
        self.assertEqual(len(Host.images_cache), nb_images)
307

Philip ABBET's avatar
Philip ABBET committed
308
        self.assertTrue(stop - start < 1.0)