test_docker.py 12.7 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
import requests
40
import nose
André Anjos's avatar
André Anjos committed
41

42 43
from tempfile import TemporaryDirectory

44
from ..dock import Host
45

46
from . import tmp_prefix
47
from .utils import slow
48 49
from .utils import skipif

50
from . import network_name
51
from . import DOCKER_NETWORK_TEST_ENABLED
52

53 54 55 56
# this will ensure we pull the required images for the tests
from . import setup_docker_test_images as setup_module


57 58 59 60
class NoDiscoveryTests(unittest.TestCase):
    """Test cases that don't require the discovery of database and runtime
    environments.
    """
61

Philip ABBET's avatar
Philip ABBET committed
62 63
    @classmethod
    def setUpClass(cls):
64
        cls.host = Host(raise_on_errors=False, discover=False)
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


77 78 79
class NetworkTest(NoDiscoveryTests):

    @slow
80
    @skipif(not DOCKER_NETWORK_TEST_ENABLED, "Network test disabled")
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    def test_network(self):
        string = "hello world"
        container = self.host.create_container('debian:8.4', ["echo", string])
        container.network_name = network_name

        try:
            self.host.start(container)
            status = self.host.wait(container)
        except Exception as e:
            network.remove()
            raise

        self.assertEqual(status, 0)
        self.assertEqual(self.host.logs(container), string + '\n')


    @slow
98
    @skipif(not DOCKER_NETWORK_TEST_ENABLED, "Network test disabled")
99 100 101 102 103 104 105 106 107 108 109
    def test_non_existing_network(self):

        string = "hello world"
        network_name = 'beat.core.fake'
        container = self.host.create_container('debian:8.4', ["echo", string])
        container.network_name = network_name

        try:
            self.host.start(container)
        except RuntimeError as e:
            self.assertTrue(str(e).find('network %s not found' % network_name) >= 0)
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128


class UserTest(NoDiscoveryTests):

    @slow
    def test_user(self):
        """Test that the uid property is correctly used.
        """

        container = self.host.create_container('debian:8.4', ["id"])
        container.uid = 10000

        self.host.start(container)
        status = self.host.wait(container)

        self.assertEqual(status, 0)
        self.assertTrue(self.host.logs(container).startswith('uid={0} gid={0}'.format(container.uid)))


129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
class EnvironmentVariableTest(NoDiscoveryTests):

    @slow
    def test_environment_variable(self):
        """Test that the uid property is correctly used.
        """

        container = self.host.create_container('debian:8.4', ["env"])
        container.add_environment_variable('DOCKER_TEST', 'good')

        self.host.start(container)
        status = self.host.wait(container)

        self.assertEqual(status, 0)
        self.assertTrue('DOCKER_TEST=good' in self.host.logs(container))


146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
class WorkdirTest(NoDiscoveryTests):

    @slow
    def test_workdir(self):
        """Test that the workdir property is correctly used.
        """

        with TemporaryDirectory() as tmp_folder:
            test_file = "test.txt"
            container = self.host.create_container('debian:8.4', ["cp", "/etc/debian_version", test_file])
            container.add_volume(tmp_folder, '/test_workdir', read_only=False)
            container.set_workdir('/test_workdir')

            self.host.start(container)
            status = self.host.wait(container)
            if status != 0:
                print(self.host.logs(container))

            self.assertEqual(status, 0)

            with open(os.path.join(tmp_folder, test_file), "rt") as file:
                content = file.read()
                self.assertEqual(content, "8.4\n")


171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
class EntrypointTest(NoDiscoveryTests):

    @slow
    def test_entrypoint(self):
        """Test that the entrypoint property is correctly used.
        """

        container = self.host.create_container('debian:8.4', ["42"])
        container.set_entrypoint('echo')

        self.host.start(container)
        status = self.host.wait(container)
        logs = self.host.logs(container)
        if status != 0:
            print(logs)

        self.assertEqual(status, 0)
        self.assertEqual(logs, "42\n")


191 192
class AsyncTest(NoDiscoveryTests):

Philip ABBET's avatar
Philip ABBET committed
193 194
    @slow
    def test_echo(self):
195

Philip ABBET's avatar
Philip ABBET committed
196
        string = "hello, world"
197

Philip ABBET's avatar
Philip ABBET committed
198 199 200
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
201

Philip ABBET's avatar
Philip ABBET committed
202
        self.assertEqual(status, 0)
203
        self.assertEqual(self.host.logs(container), string + '\n')
204

Philip ABBET's avatar
Philip ABBET committed
205 206
    @slow
    def test_non_existing(self):
207

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

210 211
        try:
            self.host.start(container)
212
        except Exception as e:
213
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
214 215

        self.assertFalse(self.host.containers) # All containers are gone
216 217


Philip ABBET's avatar
Philip ABBET committed
218 219
    @slow
    def test_timeout(self):
220

Philip ABBET's avatar
Philip ABBET committed
221
        sleep_for = 100 # seconds
222

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

226 227
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
228

Philip ABBET's avatar
Philip ABBET committed
229
        self.host.kill(container)
230

231
        retval = self.host.wait(container)
232

Philip ABBET's avatar
Philip ABBET committed
233
        self.assertEqual(self.host.status(container), 'exited')
234 235
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
236 237


Philip ABBET's avatar
Philip ABBET committed
238 239
    @slow
    def test_does_not_timeout(self):
240

Philip ABBET's avatar
Philip ABBET committed
241
        sleep_for = 0.5 # seconds
242

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

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

Philip ABBET's avatar
Philip ABBET committed
248 249
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
250
        self.assertEqual(self.host.logs(container), '')
251 252


253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269

class AsyncWithEnvironmentTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.host = Host(raise_on_errors=False)
        cls.test_environment = cls.host.full_environment_name('Python 2.7')


    @classmethod
    def tearDownClass(cls):
        cls.host.teardown()


    def tearDown(self):
        self.host.teardown()
        assert not self.host.containers # All containers are gone

Philip ABBET's avatar
Philip ABBET committed
270 271
    @slow
    def test_memory_limit(self):
272

Philip ABBET's avatar
Philip ABBET committed
273 274 275 276 277 278 279 280
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
281

Philip ABBET's avatar
Philip ABBET committed
282 283
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
284

Philip ABBET's avatar
Philip ABBET committed
285
        time.sleep(2)
286

Philip ABBET's avatar
Philip ABBET committed
287
        stats = self.host.statistics(container)
288

Philip ABBET's avatar
Philip ABBET committed
289
        status = self.host.wait(container)
290

Philip ABBET's avatar
Philip ABBET committed
291 292
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
293
        self.assertEqual(self.host.logs(container).strip(), 'Before')
294 295


Philip ABBET's avatar
Philip ABBET committed
296 297
    @slow
    def test_memory_limit2(self):
298

Philip ABBET's avatar
Philip ABBET committed
299 300 301 302 303 304 305 306
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
307

Philip ABBET's avatar
Philip ABBET committed
308 309
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
310

Philip ABBET's avatar
Philip ABBET committed
311
        time.sleep(2)
312

Philip ABBET's avatar
Philip ABBET committed
313
        stats = self.host.statistics(container)
314

Philip ABBET's avatar
Philip ABBET committed
315
        status = self.host.wait(container)
316

Philip ABBET's avatar
Philip ABBET committed
317 318
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
319

Philip ABBET's avatar
Philip ABBET committed
320 321
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
322

Philip ABBET's avatar
Philip ABBET committed
323 324
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
325
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
326 327


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

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

Philip ABBET's avatar
Philip ABBET committed
333 334
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
335

336
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
337

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

Philip ABBET's avatar
Philip ABBET committed
340
        time.sleep(sleep_time)
341

Philip ABBET's avatar
Philip ABBET committed
342
        stats = self.host.statistics(container)
343

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

Philip ABBET's avatar
Philip ABBET committed
346 347 348
        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)
349

Philip ABBET's avatar
Philip ABBET committed
350 351 352
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
353 354


Philip ABBET's avatar
Philip ABBET committed
355 356 357 358
    @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)
359 360


Philip ABBET's avatar
Philip ABBET committed
361 362 363 364
    @slow
    def test_cpulimit_at_100percent(self):
        # runs 4 processes that should consume 50% of the host CPU
        self._run_cpulimit(4, 100, 3)
365 366 367 368 369



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
370 371
    def setUp(self):
        Host.images_cache = {}
372 373


Philip ABBET's avatar
Philip ABBET committed
374 375 376
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
377

Philip ABBET's avatar
Philip ABBET committed
378 379
        # Might take some time
        start = time.time()
380

Philip ABBET's avatar
Philip ABBET committed
381 382
        host = Host(raise_on_errors=False)
        host.teardown()
383

Philip ABBET's avatar
Philip ABBET committed
384
        stop = time.time()
385

Philip ABBET's avatar
Philip ABBET committed
386 387
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
388

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

Philip ABBET's avatar
Philip ABBET committed
391 392
        # Should be instantaneous
        start = time.time()
393

Philip ABBET's avatar
Philip ABBET committed
394 395
        host = Host(raise_on_errors=False)
        host.teardown()
396

Philip ABBET's avatar
Philip ABBET committed
397
        stop = time.time()
398

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

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


Philip ABBET's avatar
Philip ABBET committed
404 405 406
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
407

Philip ABBET's avatar
Philip ABBET committed
408 409
        # Might take some time
        start = time.time()
410

Philip ABBET's avatar
Philip ABBET committed
411 412 413
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
414

Philip ABBET's avatar
Philip ABBET committed
415
        stop = time.time()
416

Philip ABBET's avatar
Philip ABBET committed
417 418
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
419

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

Philip ABBET's avatar
Philip ABBET committed
422
        Host.images_cache = {}
423

Philip ABBET's avatar
Philip ABBET committed
424 425
        # Should be instantaneous
        start = time.time()
426

Philip ABBET's avatar
Philip ABBET committed
427 428 429
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
430

Philip ABBET's avatar
Philip ABBET committed
431
        stop = time.time()
432

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

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