test_docker.py 11.2 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
from ..dock import Host
43
from . import tmp_prefix
44
from .utils import slow
45 46
from .utils import skipif

47
from . import network_name
48
from . import DOCKER_NETWORK_TEST_ENABLED
49

50 51 52 53
class NoDiscoveryTests(unittest.TestCase):
    """Test cases that don't require the discovery of database and runtime
    environments.
    """
54

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


Philip ABBET's avatar
Philip ABBET committed
60 61 62
    @classmethod
    def tearDownClass(cls):
        cls.host.teardown()
63 64


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


70 71 72
class NetworkTest(NoDiscoveryTests):

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


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


122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
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))


139 140
class AsyncTest(NoDiscoveryTests):

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

Philip ABBET's avatar
Philip ABBET committed
144
        string = "hello, world"
145

Philip ABBET's avatar
Philip ABBET committed
146 147 148
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
149

Philip ABBET's avatar
Philip ABBET committed
150
        self.assertEqual(status, 0)
151
        self.assertEqual(self.host.logs(container), string + '\n')
152

Philip ABBET's avatar
Philip ABBET committed
153 154
    @slow
    def test_non_existing(self):
155

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

158 159
        try:
            self.host.start(container)
160
        except Exception as e:
161
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
162 163

        self.assertFalse(self.host.containers) # All containers are gone
164 165


Philip ABBET's avatar
Philip ABBET committed
166 167
    @slow
    def test_timeout(self):
168

Philip ABBET's avatar
Philip ABBET committed
169
        sleep_for = 100 # seconds
170

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

174 175
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
176

Philip ABBET's avatar
Philip ABBET committed
177
        self.host.kill(container)
178

179
        retval = self.host.wait(container)
180

Philip ABBET's avatar
Philip ABBET committed
181
        self.assertEqual(self.host.status(container), 'exited')
182 183
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
184 185


Philip ABBET's avatar
Philip ABBET committed
186 187
    @slow
    def test_does_not_timeout(self):
188

Philip ABBET's avatar
Philip ABBET committed
189
        sleep_for = 0.5 # seconds
190

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

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

Philip ABBET's avatar
Philip ABBET committed
196 197
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
198
        self.assertEqual(self.host.logs(container), '')
199 200


201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

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
218 219
    @slow
    def test_memory_limit(self):
220

Philip ABBET's avatar
Philip ABBET committed
221 222 223 224 225 226 227 228
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
229

Philip ABBET's avatar
Philip ABBET committed
230 231
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
232

Philip ABBET's avatar
Philip ABBET committed
233
        time.sleep(2)
234

Philip ABBET's avatar
Philip ABBET committed
235
        stats = self.host.statistics(container)
236

Philip ABBET's avatar
Philip ABBET committed
237
        status = self.host.wait(container)
238

Philip ABBET's avatar
Philip ABBET committed
239 240
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
241
        self.assertEqual(self.host.logs(container).strip(), 'Before')
242 243


Philip ABBET's avatar
Philip ABBET committed
244 245
    @slow
    def test_memory_limit2(self):
246

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

Philip ABBET's avatar
Philip ABBET committed
256 257
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
258

Philip ABBET's avatar
Philip ABBET committed
259
        time.sleep(2)
260

Philip ABBET's avatar
Philip ABBET committed
261
        stats = self.host.statistics(container)
262

Philip ABBET's avatar
Philip ABBET committed
263
        status = self.host.wait(container)
264

Philip ABBET's avatar
Philip ABBET committed
265 266
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
267

Philip ABBET's avatar
Philip ABBET committed
268 269
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
270

Philip ABBET's avatar
Philip ABBET committed
271 272
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
273
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
274 275


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

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

Philip ABBET's avatar
Philip ABBET committed
281 282
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
283

284
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
285

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

Philip ABBET's avatar
Philip ABBET committed
288
        time.sleep(sleep_time)
289

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

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

Philip ABBET's avatar
Philip ABBET committed
294 295 296
        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)
297

Philip ABBET's avatar
Philip ABBET committed
298 299 300
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
301 302


Philip ABBET's avatar
Philip ABBET committed
303 304 305 306
    @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)
307 308


Philip ABBET's avatar
Philip ABBET committed
309 310 311 312
    @slow
    def test_cpulimit_at_100percent(self):
        # runs 4 processes that should consume 50% of the host CPU
        self._run_cpulimit(4, 100, 3)
313 314 315 316 317



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
318 319
    def setUp(self):
        Host.images_cache = {}
320 321


Philip ABBET's avatar
Philip ABBET committed
322 323 324
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
325

Philip ABBET's avatar
Philip ABBET committed
326 327
        # Might take some time
        start = time.time()
328

Philip ABBET's avatar
Philip ABBET committed
329 330
        host = Host(raise_on_errors=False)
        host.teardown()
331

Philip ABBET's avatar
Philip ABBET committed
332
        stop = time.time()
333

Philip ABBET's avatar
Philip ABBET committed
334 335
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
336

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

Philip ABBET's avatar
Philip ABBET committed
339 340
        # Should be instantaneous
        start = time.time()
341

Philip ABBET's avatar
Philip ABBET committed
342 343
        host = Host(raise_on_errors=False)
        host.teardown()
344

Philip ABBET's avatar
Philip ABBET committed
345
        stop = time.time()
346

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

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


Philip ABBET's avatar
Philip ABBET committed
352 353 354
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
355

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

Philip ABBET's avatar
Philip ABBET committed
359 360 361
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
362

Philip ABBET's avatar
Philip ABBET committed
363
        stop = time.time()
364

Philip ABBET's avatar
Philip ABBET committed
365 366
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
367

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

Philip ABBET's avatar
Philip ABBET committed
370
        Host.images_cache = {}
371

Philip ABBET's avatar
Philip ABBET committed
372 373
        # Should be instantaneous
        start = time.time()
374

Philip ABBET's avatar
Philip ABBET committed
375 376 377
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
378

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

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

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