test_docker.py 10.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
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
class AsyncTest(NoDiscoveryTests):

Philip ABBET's avatar
Philip ABBET committed
124 125
    @slow
    def test_echo(self):
126

Philip ABBET's avatar
Philip ABBET committed
127
        string = "hello, world"
128

Philip ABBET's avatar
Philip ABBET committed
129 130 131
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
132

Philip ABBET's avatar
Philip ABBET committed
133
        self.assertEqual(status, 0)
134
        self.assertEqual(self.host.logs(container), string + '\n')
135

Philip ABBET's avatar
Philip ABBET committed
136 137
    @slow
    def test_non_existing(self):
138

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

141 142
        try:
            self.host.start(container)
143
        except Exception as e:
144
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
145 146

        self.assertFalse(self.host.containers) # All containers are gone
147 148


Philip ABBET's avatar
Philip ABBET committed
149 150
    @slow
    def test_timeout(self):
151

Philip ABBET's avatar
Philip ABBET committed
152
        sleep_for = 100 # seconds
153

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

157 158
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
159

Philip ABBET's avatar
Philip ABBET committed
160
        self.host.kill(container)
161

162
        retval = self.host.wait(container)
163

Philip ABBET's avatar
Philip ABBET committed
164
        self.assertEqual(self.host.status(container), 'exited')
165 166
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
167 168


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

Philip ABBET's avatar
Philip ABBET committed
172
        sleep_for = 0.5 # seconds
173

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

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

Philip ABBET's avatar
Philip ABBET committed
179 180
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
181
        self.assertEqual(self.host.logs(container), '')
182 183


184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200

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
201 202
    @slow
    def test_memory_limit(self):
203

Philip ABBET's avatar
Philip ABBET committed
204 205 206 207 208 209 210 211
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
212

Philip ABBET's avatar
Philip ABBET committed
213 214
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
215

Philip ABBET's avatar
Philip ABBET committed
216
        time.sleep(2)
217

Philip ABBET's avatar
Philip ABBET committed
218
        stats = self.host.statistics(container)
219

Philip ABBET's avatar
Philip ABBET committed
220
        status = self.host.wait(container)
221

Philip ABBET's avatar
Philip ABBET committed
222 223
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
224
        self.assertEqual(self.host.logs(container).strip(), 'Before')
225 226


Philip ABBET's avatar
Philip ABBET committed
227 228
    @slow
    def test_memory_limit2(self):
229

Philip ABBET's avatar
Philip ABBET committed
230 231 232 233 234 235 236 237
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
238

Philip ABBET's avatar
Philip ABBET committed
239 240
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
241

Philip ABBET's avatar
Philip ABBET committed
242
        time.sleep(2)
243

Philip ABBET's avatar
Philip ABBET committed
244
        stats = self.host.statistics(container)
245

Philip ABBET's avatar
Philip ABBET committed
246
        status = self.host.wait(container)
247

Philip ABBET's avatar
Philip ABBET committed
248 249
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
250

Philip ABBET's avatar
Philip ABBET committed
251 252
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
253

Philip ABBET's avatar
Philip ABBET committed
254 255
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
256
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
257 258


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

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

Philip ABBET's avatar
Philip ABBET committed
264 265
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
266

267
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
268

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

Philip ABBET's avatar
Philip ABBET committed
271
        time.sleep(sleep_time)
272

Philip ABBET's avatar
Philip ABBET committed
273
        stats = self.host.statistics(container)
274

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

Philip ABBET's avatar
Philip ABBET committed
277 278 279
        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)
280

Philip ABBET's avatar
Philip ABBET committed
281 282 283
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
284 285


Philip ABBET's avatar
Philip ABBET committed
286 287 288 289
    @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)
290 291


Philip ABBET's avatar
Philip ABBET committed
292 293 294 295
    @slow
    def test_cpulimit_at_100percent(self):
        # runs 4 processes that should consume 50% of the host CPU
        self._run_cpulimit(4, 100, 3)
296 297 298 299 300



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
301 302
    def setUp(self):
        Host.images_cache = {}
303 304


Philip ABBET's avatar
Philip ABBET committed
305 306 307
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
308

Philip ABBET's avatar
Philip ABBET committed
309 310
        # Might take some time
        start = time.time()
311

Philip ABBET's avatar
Philip ABBET committed
312 313
        host = Host(raise_on_errors=False)
        host.teardown()
314

Philip ABBET's avatar
Philip ABBET committed
315
        stop = time.time()
316

Philip ABBET's avatar
Philip ABBET committed
317 318
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
319

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

Philip ABBET's avatar
Philip ABBET committed
322 323
        # Should be instantaneous
        start = time.time()
324

Philip ABBET's avatar
Philip ABBET committed
325 326
        host = Host(raise_on_errors=False)
        host.teardown()
327

Philip ABBET's avatar
Philip ABBET committed
328
        stop = time.time()
329

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

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


Philip ABBET's avatar
Philip ABBET committed
335 336 337
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
338

Philip ABBET's avatar
Philip ABBET committed
339 340
        # Might take some time
        start = time.time()
341

Philip ABBET's avatar
Philip ABBET committed
342 343 344
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
345

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

Philip ABBET's avatar
Philip ABBET committed
348 349
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
350

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

Philip ABBET's avatar
Philip ABBET committed
353
        Host.images_cache = {}
354

Philip ABBET's avatar
Philip ABBET committed
355 356
        # Should be instantaneous
        start = time.time()
357

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

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

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

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