test_docker.py 12.6 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 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
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")


187 188
class AsyncTest(NoDiscoveryTests):

Philip ABBET's avatar
Philip ABBET committed
189 190
    @slow
    def test_echo(self):
191

Philip ABBET's avatar
Philip ABBET committed
192
        string = "hello, world"
193

Philip ABBET's avatar
Philip ABBET committed
194 195 196
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
197

Philip ABBET's avatar
Philip ABBET committed
198
        self.assertEqual(status, 0)
199
        self.assertEqual(self.host.logs(container), string + '\n')
200

Philip ABBET's avatar
Philip ABBET committed
201 202
    @slow
    def test_non_existing(self):
203

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

206 207
        try:
            self.host.start(container)
208
        except Exception as e:
209
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
210 211

        self.assertFalse(self.host.containers) # All containers are gone
212 213


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

Philip ABBET's avatar
Philip ABBET committed
217
        sleep_for = 100 # 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

222 223
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
224

Philip ABBET's avatar
Philip ABBET committed
225
        self.host.kill(container)
226

227
        retval = self.host.wait(container)
228

Philip ABBET's avatar
Philip ABBET committed
229
        self.assertEqual(self.host.status(container), 'exited')
230 231
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
232 233


Philip ABBET's avatar
Philip ABBET committed
234 235
    @slow
    def test_does_not_timeout(self):
236

Philip ABBET's avatar
Philip ABBET committed
237
        sleep_for = 0.5 # seconds
238

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

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

Philip ABBET's avatar
Philip ABBET committed
244 245
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
246
        self.assertEqual(self.host.logs(container), '')
247 248


249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265

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
266 267
    @slow
    def test_memory_limit(self):
268

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

Philip ABBET's avatar
Philip ABBET committed
278 279
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
280

Philip ABBET's avatar
Philip ABBET committed
281
        time.sleep(2)
282

Philip ABBET's avatar
Philip ABBET committed
283
        stats = self.host.statistics(container)
284

Philip ABBET's avatar
Philip ABBET committed
285
        status = self.host.wait(container)
286

Philip ABBET's avatar
Philip ABBET committed
287 288
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
289
        self.assertEqual(self.host.logs(container).strip(), 'Before')
290 291


Philip ABBET's avatar
Philip ABBET committed
292 293
    @slow
    def test_memory_limit2(self):
294

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

Philip ABBET's avatar
Philip ABBET committed
304 305
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
306

Philip ABBET's avatar
Philip ABBET committed
307
        time.sleep(2)
308

Philip ABBET's avatar
Philip ABBET committed
309
        stats = self.host.statistics(container)
310

Philip ABBET's avatar
Philip ABBET committed
311
        status = self.host.wait(container)
312

Philip ABBET's avatar
Philip ABBET committed
313 314
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
315

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

Philip ABBET's avatar
Philip ABBET committed
319 320
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
321
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
322 323


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

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

Philip ABBET's avatar
Philip ABBET committed
329 330
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
331

332
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
333

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

Philip ABBET's avatar
Philip ABBET committed
336
        time.sleep(sleep_time)
337

Philip ABBET's avatar
Philip ABBET committed
338
        stats = self.host.statistics(container)
339

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

Philip ABBET's avatar
Philip ABBET committed
342 343 344
        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)
345

Philip ABBET's avatar
Philip ABBET committed
346 347 348
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
349 350


Philip ABBET's avatar
Philip ABBET committed
351 352 353 354
    @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)
355 356


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



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
366 367
    def setUp(self):
        Host.images_cache = {}
368 369


Philip ABBET's avatar
Philip ABBET committed
370 371 372
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
373

Philip ABBET's avatar
Philip ABBET committed
374 375
        # Might take some time
        start = time.time()
376

Philip ABBET's avatar
Philip ABBET committed
377 378
        host = Host(raise_on_errors=False)
        host.teardown()
379

Philip ABBET's avatar
Philip ABBET committed
380
        stop = time.time()
381

Philip ABBET's avatar
Philip ABBET committed
382 383
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
384

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

Philip ABBET's avatar
Philip ABBET committed
387 388
        # Should be instantaneous
        start = time.time()
389

Philip ABBET's avatar
Philip ABBET committed
390 391
        host = Host(raise_on_errors=False)
        host.teardown()
392

Philip ABBET's avatar
Philip ABBET committed
393
        stop = time.time()
394

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

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


Philip ABBET's avatar
Philip ABBET committed
400 401 402
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
403

Philip ABBET's avatar
Philip ABBET committed
404 405
        # Might take some time
        start = time.time()
406

Philip ABBET's avatar
Philip ABBET committed
407 408 409
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
410

Philip ABBET's avatar
Philip ABBET committed
411
        stop = time.time()
412

Philip ABBET's avatar
Philip ABBET committed
413 414
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
415

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

Philip ABBET's avatar
Philip ABBET committed
418
        Host.images_cache = {}
419

Philip ABBET's avatar
Philip ABBET committed
420 421
        # Should be instantaneous
        start = time.time()
422

Philip ABBET's avatar
Philip ABBET committed
423 424 425
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
426

Philip ABBET's avatar
Philip ABBET committed
427
        stop = time.time()
428

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

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