test_docker.py 10.5 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
from . import network_name
46

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

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


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


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


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
class NetworkTest(NoDiscoveryTests):

    @slow
    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
    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)
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116


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


117 118
class AsyncTest(NoDiscoveryTests):

Philip ABBET's avatar
Philip ABBET committed
119 120
    @slow
    def test_echo(self):
121

Philip ABBET's avatar
Philip ABBET committed
122
        string = "hello, world"
123

Philip ABBET's avatar
Philip ABBET committed
124 125 126
        container = self.host.create_container('debian:8.4', ["echo", string])
        self.host.start(container)
        status = self.host.wait(container)
127

Philip ABBET's avatar
Philip ABBET committed
128
        self.assertEqual(status, 0)
129
        self.assertEqual(self.host.logs(container), string + '\n')
130

Philip ABBET's avatar
Philip ABBET committed
131 132
    @slow
    def test_non_existing(self):
133

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

136 137
        try:
            self.host.start(container)
138
        except Exception as e:
139
            self.assertTrue(str(e).find('Failed to create the container') >= 0)
140 141

        self.assertFalse(self.host.containers) # All containers are gone
142 143


Philip ABBET's avatar
Philip ABBET committed
144 145
    @slow
    def test_timeout(self):
146

Philip ABBET's avatar
Philip ABBET committed
147
        sleep_for = 100 # seconds
148

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

152 153
        retval = self.host.wait(container, timeout=0.5)
        self.assertTrue(retval is None)
154

Philip ABBET's avatar
Philip ABBET committed
155
        self.host.kill(container)
156

157
        retval = self.host.wait(container)
158

Philip ABBET's avatar
Philip ABBET committed
159
        self.assertEqual(self.host.status(container), 'exited')
160 161
        self.assertEqual(retval, 137)
        self.assertEqual(self.host.logs(container), '')
162 163


Philip ABBET's avatar
Philip ABBET committed
164 165
    @slow
    def test_does_not_timeout(self):
166

Philip ABBET's avatar
Philip ABBET committed
167
        sleep_for = 0.5 # seconds
168

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

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

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


179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

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
196 197
    @slow
    def test_memory_limit(self):
198

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

Philip ABBET's avatar
Philip ABBET committed
208 209
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=4)
210

Philip ABBET's avatar
Philip ABBET committed
211
        time.sleep(2)
212

Philip ABBET's avatar
Philip ABBET committed
213
        stats = self.host.statistics(container)
214

Philip ABBET's avatar
Philip ABBET committed
215
        status = self.host.wait(container)
216

Philip ABBET's avatar
Philip ABBET committed
217 218
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 137)
219
        self.assertEqual(self.host.logs(container).strip(), 'Before')
220 221


Philip ABBET's avatar
Philip ABBET committed
222 223
    @slow
    def test_memory_limit2(self):
224

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

Philip ABBET's avatar
Philip ABBET committed
234 235
        container = self.host.create_container(self.test_environment, cmd)
        self.host.start(container, virtual_memory_in_megabytes=100)
236

Philip ABBET's avatar
Philip ABBET committed
237
        time.sleep(2)
238

Philip ABBET's avatar
Philip ABBET committed
239
        stats = self.host.statistics(container)
240

Philip ABBET's avatar
Philip ABBET committed
241
        status = self.host.wait(container)
242

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

Philip ABBET's avatar
Philip ABBET committed
246 247
        assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
            '%d%% >= 15%%' % stats['memory']['percent']
248

Philip ABBET's avatar
Philip ABBET committed
249 250
        self.assertEqual(self.host.status(container), 'exited')
        self.assertEqual(status, 0)
251
        self.assertEqual(self.host.logs(container).strip(), 'Before\nAfter')
252 253


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

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

Philip ABBET's avatar
Philip ABBET committed
259 260
        container = self.host.create_container(self.test_environment,
                                               ['python', dst_name, str(processes)])
261

262
        container.add_volume(program, os.path.join('/tmp', 'cpu_stress.py'))
263

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

Philip ABBET's avatar
Philip ABBET committed
266
        time.sleep(sleep_time)
267

Philip ABBET's avatar
Philip ABBET committed
268
        stats = self.host.statistics(container)
269

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

Philip ABBET's avatar
Philip ABBET committed
272 273 274
        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)
275

Philip ABBET's avatar
Philip ABBET committed
276 277 278
        # make sure nothing is there anymore
        self.host.kill(container)
        self.assertEqual(self.host.wait(container), 137)
279 280


Philip ABBET's avatar
Philip ABBET committed
281 282 283 284
    @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)
285 286


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



class HostTest(unittest.TestCase):

Philip ABBET's avatar
Philip ABBET committed
296 297
    def setUp(self):
        Host.images_cache = {}
298 299


Philip ABBET's avatar
Philip ABBET committed
300 301 302
    @slow
    def test_images_cache(self):
        self.assertEqual(len(Host.images_cache), 0)
303

Philip ABBET's avatar
Philip ABBET committed
304 305
        # Might take some time
        start = time.time()
306

Philip ABBET's avatar
Philip ABBET committed
307 308
        host = Host(raise_on_errors=False)
        host.teardown()
309

Philip ABBET's avatar
Philip ABBET committed
310
        stop = time.time()
311

Philip ABBET's avatar
Philip ABBET committed
312 313
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
314

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

Philip ABBET's avatar
Philip ABBET committed
317 318
        # Should be instantaneous
        start = time.time()
319

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

Philip ABBET's avatar
Philip ABBET committed
323
        stop = time.time()
324

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

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


Philip ABBET's avatar
Philip ABBET committed
330 331 332
    @slow
    def test_images_cache_file(self):
        self.assertEqual(len(Host.images_cache), 0)
333

Philip ABBET's avatar
Philip ABBET committed
334 335
        # Might take some time
        start = time.time()
336

Philip ABBET's avatar
Philip ABBET committed
337 338 339
        host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                    raise_on_errors=False)
        host.teardown()
340

Philip ABBET's avatar
Philip ABBET committed
341
        stop = time.time()
342

Philip ABBET's avatar
Philip ABBET committed
343 344
        nb_images = len(Host.images_cache)
        self.assertTrue(nb_images > 0)
345

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

Philip ABBET's avatar
Philip ABBET committed
348
        Host.images_cache = {}
349

Philip ABBET's avatar
Philip ABBET committed
350 351
        # Should be instantaneous
        start = time.time()
352

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

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

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

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