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

43
from ..dock import Host
44
from . import tmp_prefix
45
from .utils import slow
46
from .utils import id_generator
André Anjos's avatar
André Anjos committed
47

48

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

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


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


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


69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
class NetworkTest(NoDiscoveryTests):

    @slow
    def test_network(self):
        network_name = 'beat_core_test_docker_' + id_generator()
        ipam_pool = docker.types.IPAMPool(subnet='193.169.52.0/24',
                                          gateway='193.169.52.254')
        ipam_config = docker.types.IPAMConfig(pool_configs=[ipam_pool])

        client = docker.from_env()
        network = client.networks.create(network_name,
                                         driver="bridge",
                                         ipam=ipam_config)


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

        network.remove()


    @slow
    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)
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131


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


132 133
class AsyncTest(NoDiscoveryTests):

Philip ABBET's avatar
Philip ABBET committed
134 135
    @slow
    def test_echo(self):
136

Philip ABBET's avatar
Philip ABBET committed
137
        string = "hello, world"
138

Philip ABBET's avatar
Philip ABBET committed
139 140 141
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
142

Philip ABBET's avatar
Philip ABBET committed
143
        self.assertEqual(status, 0)
144
        self.assertEqual(self.host.logs(container), string + '\n')
145

Philip ABBET's avatar
Philip ABBET committed
146 147
    @slow
    def test_non_existing(self):
148

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

151 152
        try:
            self.host.start(container)
153
        except Exception as e:
154
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
155 156

        self.assertFalse(self.host.containers) # All containers are gone
157 158


Philip ABBET's avatar
Philip ABBET committed
159 160
    @slow
    def test_timeout(self):
161

Philip ABBET's avatar
Philip ABBET committed
162
        sleep_for = 100 # seconds
163

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

167 168
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
169

Philip ABBET's avatar
Philip ABBET committed
170
        self.host.kill(container)
171

172
        retval = self.host.wait(container)
173

Philip ABBET's avatar
Philip ABBET committed
174
        self.assertEqual(self.host.status(container), 'exited')
175 176
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
177 178


Philip ABBET's avatar
Philip ABBET committed
179 180
    @slow
    def test_does_not_timeout(self):
181

Philip ABBET's avatar
Philip ABBET committed
182
        sleep_for = 0.5 # seconds
183

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

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

Philip ABBET's avatar
Philip ABBET committed
189 190
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
191
        self.assertEqual(self.host.logs(container), '')
192 193


194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210

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
211 212
    @slow
    def test_memory_limit(self):
213

Philip ABBET's avatar
Philip ABBET committed
214 215 216 217 218 219 220 221
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
222

Philip ABBET's avatar
Philip ABBET committed
223 224
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
225

Philip ABBET's avatar
Philip ABBET committed
226
        time.sleep(2)
227

Philip ABBET's avatar
Philip ABBET committed
228
        stats = self.host.statistics(container)
229

Philip ABBET's avatar
Philip ABBET committed
230
        status = self.host.wait(container)
231

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


Philip ABBET's avatar
Philip ABBET committed
237 238
    @slow
    def test_memory_limit2(self):
239

Philip ABBET's avatar
Philip ABBET committed
240 241 242 243 244 245 246 247
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
248

Philip ABBET's avatar
Philip ABBET committed
249 250
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
251

Philip ABBET's avatar
Philip ABBET committed
252
        time.sleep(2)
253

Philip ABBET's avatar
Philip ABBET committed
254
        stats = self.host.statistics(container)
255

Philip ABBET's avatar
Philip ABBET committed
256
        status = self.host.wait(container)
257

Philip ABBET's avatar
Philip ABBET committed
258 259
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
260

Philip ABBET's avatar
Philip ABBET committed
261 262
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
263

Philip ABBET's avatar
Philip ABBET committed
264 265
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
266
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
267 268


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

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

Philip ABBET's avatar
Philip ABBET committed
274 275
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
276

277
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
278

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

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

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

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

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

Philip ABBET's avatar
Philip ABBET committed
291 292 293
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
294 295


Philip ABBET's avatar
Philip ABBET committed
296 297 298 299
    @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)
300 301


Philip ABBET's avatar
Philip ABBET committed
302 303 304 305
    @slow
    def test_cpulimit_at_100percent(self):
        # runs 4 processes that should consume 50% of the host CPU
        self._run_cpulimit(4, 100, 3)
306 307 308 309 310



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
311 312
    def setUp(self):
        Host.images_cache = {}
313 314


Philip ABBET's avatar
Philip ABBET committed
315 316 317
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
318

Philip ABBET's avatar
Philip ABBET committed
319 320
        # Might take some time
        start = time.time()
321

Philip ABBET's avatar
Philip ABBET committed
322 323
        host = Host(raise_on_errors=False)
        host.teardown()
324

Philip ABBET's avatar
Philip ABBET committed
325
        stop = time.time()
326

Philip ABBET's avatar
Philip ABBET committed
327 328
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
329

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

Philip ABBET's avatar
Philip ABBET committed
332 333
        # Should be instantaneous
        start = time.time()
334

Philip ABBET's avatar
Philip ABBET committed
335 336
        host = Host(raise_on_errors=False)
        host.teardown()
337

Philip ABBET's avatar
Philip ABBET committed
338
        stop = time.time()
339

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

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


Philip ABBET's avatar
Philip ABBET committed
345 346 347
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
348

Philip ABBET's avatar
Philip ABBET committed
349 350
        # Might take some time
        start = time.time()
351

Philip ABBET's avatar
Philip ABBET committed
352 353 354
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
355

Philip ABBET's avatar
Philip ABBET committed
356
        stop = time.time()
357

Philip ABBET's avatar
Philip ABBET committed
358 359
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
360

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

Philip ABBET's avatar
Philip ABBET committed
363
        Host.images_cache = {}
364

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

Philip ABBET's avatar
Philip ABBET committed
368 369 370
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
371

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

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

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