test_docker.py 12.1 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
class NoDiscoveryTests(unittest.TestCase):
    """Test cases that don't require the discovery of database and runtime
    environments.
    """
57

Philip ABBET's avatar
Philip ABBET committed
58 59
    @classmethod
    def setUpClass(cls):
60
        cls.host = Host(raise_on_errors=False, discover=False)
61 62


Philip ABBET's avatar
Philip ABBET committed
63 64 65
    @classmethod
    def tearDownClass(cls):
        cls.host.teardown()
66 67


Philip ABBET's avatar
Philip ABBET committed
68 69 70
    def tearDown(self):
        self.host.teardown()
        assert not self.host.containers # All containers are gone
71 72


73 74 75
class NetworkTest(NoDiscoveryTests):

    @slow
76
    @skipif(not DOCKER_NETWORK_TEST_ENABLED, "Network test disabled")
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
    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
94
    @skipif(not DOCKER_NETWORK_TEST_ENABLED, "Network test disabled")
95 96 97 98 99 100 101 102 103 104 105
    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)
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124


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)))


125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
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))


142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
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")


167 168
class AsyncTest(NoDiscoveryTests):

Philip ABBET's avatar
Philip ABBET committed
169 170
    @slow
    def test_echo(self):
171

Philip ABBET's avatar
Philip ABBET committed
172
        string = "hello, world"
173

Philip ABBET's avatar
Philip ABBET committed
174 175 176
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
177

Philip ABBET's avatar
Philip ABBET committed
178
        self.assertEqual(status, 0)
179
        self.assertEqual(self.host.logs(container), string + '\n')
180

Philip ABBET's avatar
Philip ABBET committed
181 182
    @slow
    def test_non_existing(self):
183

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

186 187
        try:
            self.host.start(container)
188
        except Exception as e:
189
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
190 191

        self.assertFalse(self.host.containers) # All containers are gone
192 193


Philip ABBET's avatar
Philip ABBET committed
194 195
    @slow
    def test_timeout(self):
196

Philip ABBET's avatar
Philip ABBET committed
197
        sleep_for = 100 # seconds
198

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

202 203
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
204

Philip ABBET's avatar
Philip ABBET committed
205
        self.host.kill(container)
206

207
        retval = self.host.wait(container)
208

Philip ABBET's avatar
Philip ABBET committed
209
        self.assertEqual(self.host.status(container), 'exited')
210 211
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
212 213


Philip ABBET's avatar
Philip ABBET committed
214 215
    @slow
    def test_does_not_timeout(self):
216

Philip ABBET's avatar
Philip ABBET committed
217
        sleep_for = 0.5 # seconds
218

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

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

Philip ABBET's avatar
Philip ABBET committed
224 225
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
226
        self.assertEqual(self.host.logs(container), '')
227 228


229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245

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
246 247
    @slow
    def test_memory_limit(self):
248

Philip ABBET's avatar
Philip ABBET committed
249 250 251 252 253 254 255 256
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
257

Philip ABBET's avatar
Philip ABBET committed
258 259
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
260

Philip ABBET's avatar
Philip ABBET committed
261
        time.sleep(2)
262

Philip ABBET's avatar
Philip ABBET committed
263
        stats = self.host.statistics(container)
264

Philip ABBET's avatar
Philip ABBET committed
265
        status = self.host.wait(container)
266

Philip ABBET's avatar
Philip ABBET committed
267 268
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
269
        self.assertEqual(self.host.logs(container).strip(), 'Before')
270 271


Philip ABBET's avatar
Philip ABBET committed
272 273
    @slow
    def test_memory_limit2(self):
274

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

Philip ABBET's avatar
Philip ABBET committed
284 285
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
286

Philip ABBET's avatar
Philip ABBET committed
287
        time.sleep(2)
288

Philip ABBET's avatar
Philip ABBET committed
289
        stats = self.host.statistics(container)
290

Philip ABBET's avatar
Philip ABBET committed
291
        status = self.host.wait(container)
292

Philip ABBET's avatar
Philip ABBET committed
293 294
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
295

Philip ABBET's avatar
Philip ABBET committed
296 297
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
298

Philip ABBET's avatar
Philip ABBET committed
299 300
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
301
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
302 303


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

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

Philip ABBET's avatar
Philip ABBET committed
309 310
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
311

312
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
313

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

Philip ABBET's avatar
Philip ABBET committed
316
        time.sleep(sleep_time)
317

Philip ABBET's avatar
Philip ABBET committed
318
        stats = self.host.statistics(container)
319

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

Philip ABBET's avatar
Philip ABBET committed
322 323 324
        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)
325

Philip ABBET's avatar
Philip ABBET committed
326 327 328
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
329 330


Philip ABBET's avatar
Philip ABBET committed
331 332 333 334
    @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)
335 336


Philip ABBET's avatar
Philip ABBET committed
337 338 339 340
    @slow
    def test_cpulimit_at_100percent(self):
        # runs 4 processes that should consume 50% of the host CPU
        self._run_cpulimit(4, 100, 3)
341 342 343 344 345



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
346 347
    def setUp(self):
        Host.images_cache = {}
348 349


Philip ABBET's avatar
Philip ABBET committed
350 351 352
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
353

Philip ABBET's avatar
Philip ABBET committed
354 355
        # Might take some time
        start = time.time()
356

Philip ABBET's avatar
Philip ABBET committed
357 358
        host = Host(raise_on_errors=False)
        host.teardown()
359

Philip ABBET's avatar
Philip ABBET committed
360
        stop = time.time()
361

Philip ABBET's avatar
Philip ABBET committed
362 363
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
364

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

Philip ABBET's avatar
Philip ABBET committed
367 368
        # Should be instantaneous
        start = time.time()
369

Philip ABBET's avatar
Philip ABBET committed
370 371
        host = Host(raise_on_errors=False)
        host.teardown()
372

Philip ABBET's avatar
Philip ABBET committed
373
        stop = time.time()
374

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

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


Philip ABBET's avatar
Philip ABBET committed
380 381 382
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
383

Philip ABBET's avatar
Philip ABBET committed
384 385
        # Might take some time
        start = time.time()
386

Philip ABBET's avatar
Philip ABBET committed
387 388 389
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
390

Philip ABBET's avatar
Philip ABBET committed
391
        stop = time.time()
392

Philip ABBET's avatar
Philip ABBET committed
393 394
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
395

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

Philip ABBET's avatar
Philip ABBET committed
398
        Host.images_cache = {}
399

Philip ABBET's avatar
Philip ABBET committed
400 401
        # Should be instantaneous
        start = time.time()
402

Philip ABBET's avatar
Philip ABBET committed
403 404 405
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
406

Philip ABBET's avatar
Philip ABBET committed
407
        stop = time.time()
408

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

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