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

Philip ABBET's avatar
Philip ABBET committed
115 116
    @slow
    def test_echo(self):
117

Philip ABBET's avatar
Philip ABBET committed
118
        string = "hello, world"
119

Philip ABBET's avatar
Philip ABBET committed
120 121 122
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
123

Philip ABBET's avatar
Philip ABBET committed
124
        self.assertEqual(status, 0)
125
        self.assertEqual(self.host.logs(container), string + '\n')
126

Philip ABBET's avatar
Philip ABBET committed
127 128
    @slow
    def test_non_existing(self):
129

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

132 133
        try:
            self.host.start(container)
134
        except Exception as e:
135
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
136 137

        self.assertFalse(self.host.containers) # All containers are gone
138 139


Philip ABBET's avatar
Philip ABBET committed
140 141
    @slow
    def test_timeout(self):
142

Philip ABBET's avatar
Philip ABBET committed
143
        sleep_for = 100 # seconds
144

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

148 149
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
150

Philip ABBET's avatar
Philip ABBET committed
151
        self.host.kill(container)
152

153
        retval = self.host.wait(container)
154

Philip ABBET's avatar
Philip ABBET committed
155
        self.assertEqual(self.host.status(container), 'exited')
156 157
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
158 159


Philip ABBET's avatar
Philip ABBET committed
160 161
    @slow
    def test_does_not_timeout(self):
162

Philip ABBET's avatar
Philip ABBET committed
163
        sleep_for = 0.5 # seconds
164

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

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

Philip ABBET's avatar
Philip ABBET committed
170 171
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
172
        self.assertEqual(self.host.logs(container), '')
173 174


175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191

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
192 193
    @slow
    def test_memory_limit(self):
194

Philip ABBET's avatar
Philip ABBET committed
195 196 197 198 199 200 201 202
        cmd = ['python', '-c', '; '.join([
            "print('Before')",
            "import sys; sys.stdout.flush()",
            "d = '0' * (10 * 1024 * 1024)",
            "import time; time.sleep(5)",
            "print('After')",
          ])
        ]
203

Philip ABBET's avatar
Philip ABBET committed
204 205
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
206

Philip ABBET's avatar
Philip ABBET committed
207
        time.sleep(2)
208

Philip ABBET's avatar
Philip ABBET committed
209
        stats = self.host.statistics(container)
210

Philip ABBET's avatar
Philip ABBET committed
211
        status = self.host.wait(container)
212

Philip ABBET's avatar
Philip ABBET committed
213 214
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
215
        self.assertEqual(self.host.logs(container).strip(), 'Before')
216 217


Philip ABBET's avatar
Philip ABBET committed
218 219
    @slow
    def test_memory_limit2(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=100)
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
        assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
            '%d%% <= 10%%' % stats['memory']['percent']
241

Philip ABBET's avatar
Philip ABBET committed
242 243
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
244

Philip ABBET's avatar
Philip ABBET committed
245 246
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
247
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
248 249


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

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

Philip ABBET's avatar
Philip ABBET committed
255 256
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
257

258
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
259

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

Philip ABBET's avatar
Philip ABBET committed
262
        time.sleep(sleep_time)
263

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

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

Philip ABBET's avatar
Philip ABBET committed
268 269 270
        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)
271

Philip ABBET's avatar
Philip ABBET committed
272 273 274
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
275 276


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


Philip ABBET's avatar
Philip ABBET committed
283 284 285 286
    @slow
    def test_cpulimit_at_100percent(self):
        # runs 4 processes that should consume 50% of the host CPU
        self._run_cpulimit(4, 100, 3)
287 288 289 290 291



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
292 293
    def setUp(self):
        Host.images_cache = {}
294 295


Philip ABBET's avatar
Philip ABBET committed
296 297 298
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
299

Philip ABBET's avatar
Philip ABBET committed
300 301
        # Might take some time
        start = time.time()
302

Philip ABBET's avatar
Philip ABBET committed
303 304
        host = Host(raise_on_errors=False)
        host.teardown()
305

Philip ABBET's avatar
Philip ABBET committed
306
        stop = time.time()
307

Philip ABBET's avatar
Philip ABBET committed
308 309
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
310

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

Philip ABBET's avatar
Philip ABBET committed
313 314
        # Should be instantaneous
        start = time.time()
315

Philip ABBET's avatar
Philip ABBET committed
316 317
        host = Host(raise_on_errors=False)
        host.teardown()
318

Philip ABBET's avatar
Philip ABBET committed
319
        stop = time.time()
320

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

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


Philip ABBET's avatar
Philip ABBET committed
326 327 328
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
329

Philip ABBET's avatar
Philip ABBET committed
330 331
        # Might take some time
        start = time.time()
332

Philip ABBET's avatar
Philip ABBET committed
333 334 335
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
336

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

Philip ABBET's avatar
Philip ABBET committed
339 340
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
341

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

Philip ABBET's avatar
Philip ABBET committed
344
        Host.images_cache = {}
345

Philip ABBET's avatar
Philip ABBET committed
346 347
        # Should be instantaneous
        start = time.time()
348

Philip ABBET's avatar
Philip ABBET committed
349 350 351
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
352

Philip ABBET's avatar
Philip ABBET committed
353
        stop = time.time()
354

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

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