test_docker.py 8.76 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
André Anjos's avatar
André Anjos committed
41

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

46

André Anjos's avatar
André Anjos committed
47 48 49 50 51 52 53 54 55 56 57
# in case you want to see the printouts dynamically, set to ``True``
if False:
  import logging
  logger = logging.getLogger() #root logger
  logger.setLevel(logging.DEBUG)
  ch = logging.StreamHandler()
  ch.setLevel(logging.DEBUG)
  ch.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
  logger.addHandler(ch)


58

59 60
class AsyncTest(unittest.TestCase):

61 62
  @classmethod
  def setUpClass(cls):
63 64
    cls.host = Host(raise_on_errors=False)
    cls.test_environment = cls.host.full_environment_name('Python 2.7')
65 66


67 68 69
  @classmethod
  def tearDownClass(cls):
    cls.host.teardown()
70 71


72 73 74 75 76
  def tearDown(self):
    self.host.teardown()
    assert not self.host.containers # All containers are gone


77
  @slow
78 79 80 81
  def test_echo(self):

    string = "hello, world"

82 83 84 85 86 87 88
    container = self.host.create_container('debian:8.4', ["echo", string])
    self.host.start(container)
    status = self.host.wait(container)

    self.assertEqual(status, 0)
    self.assertEqual(self.host.stdout(container), string + '\n')
    self.assertEqual(self.host.stderr(container), '')
89 90


91
  @slow
92 93
  def test_non_existing(self):

94
    container = self.host.create_container('debian:8.4', ["sdfsdfdsf329909092"])
95

96
    self.assertRaises(docker.errors.NotFound, self.host.start, container)
97

98
    assert not self.host.containers # All containers are gone
99 100


101
  @slow
102 103 104 105
  def test_timeout(self):

    sleep_for = 100 # seconds

106 107
    container = self.host.create_container('debian:8.4', ["sleep", str(sleep_for)])
    self.host.start(container)
108

109 110 111 112 113
    try:
      retval = self.host.wait(container, timeout=0.5)
      assert False, "timeout never occurred after %d seconds" % sleep_for
    except requests.exceptions.ReadTimeout as e:
      self.assertEqual(self.host.status(container), 'running')
114

115
    self.host.kill(container)
116

117
    status = self.host.wait(container)
118

119 120 121 122
    self.assertEqual(self.host.status(container), 'exited')
    self.assertEqual(status, 137)
    self.assertEqual(self.host.stdout(container), '')
    self.assertEqual(self.host.stderr(container), '')
123 124


125
  @slow
126
  def test_does_not_timeout(self):
127

128
    sleep_for = 0.5 # seconds
129

130 131
    container = self.host.create_container('debian:8.4', ["sleep", str(sleep_for)])
    self.host.start(container)
132

133
    status = self.host.wait(container, timeout=5) # Should not timeout
134

135 136 137 138
    self.assertEqual(self.host.status(container), 'exited')
    self.assertEqual(status, 0)
    self.assertEqual(self.host.stdout(container), '')
    self.assertEqual(self.host.stderr(container), '')
139 140


141
  @slow
142
  def test_memory_limit(self):
143

144 145 146 147 148 149 150 151
    cmd = ['python', '-c', '; '.join([
        "print('Before')",
        "import sys; sys.stdout.flush()",
        "d = '0' * (10 * 1024 * 1024)",
        "import time; time.sleep(5)",
        "print('After')",
      ])
    ]
152

153 154
    container = self.host.create_container(self.test_environment, cmd)
    self.host.start(container, virtual_memory_in_megabytes=4)
155

156
    time.sleep(2)
157

158
    stats = self.host.statistics(container)
159

160
    status = self.host.wait(container)
161

162 163 164 165
    self.assertEqual(self.host.status(container), 'exited')
    self.assertEqual(status, 137)
    self.assertEqual(self.host.stdout(container).strip(), 'Before')
    self.assertEqual(self.host.stderr(container), '')
166 167


168
  @slow
169
  def test_memory_limit2(self):
170

171 172 173 174 175 176 177 178
    cmd = ['python', '-c', '; '.join([
        "print('Before')",
        "import sys; sys.stdout.flush()",
        "d = '0' * (10 * 1024 * 1024)",
        "import time; time.sleep(5)",
        "print('After')",
      ])
    ]
179

180 181
    container = self.host.create_container(self.test_environment, cmd)
    self.host.start(container, virtual_memory_in_megabytes=100)
182

183
    time.sleep(2)
184

185
    stats = self.host.statistics(container)
186

187
    status = self.host.wait(container)
188

189 190
    assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
        '%d%% <= 10%%' % stats['memory']['percent']
191

192 193
    assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
        '%d%% >= 15%%' % stats['memory']['percent']
194

195 196 197 198
    self.assertEqual(self.host.status(container), 'exited')
    self.assertEqual(status, 0)
    self.assertEqual(self.host.stdout(container).strip(), 'Before\nAfter')
    self.assertEqual(self.host.stderr(container), '')
199 200


201
  def _run_cpulimit(self, processes, max_cpu_percent, sleep_time):
202

203 204
    program = pkg_resources.resource_filename(__name__, 'cpu_stress.py')
    dst_name = os.path.join('/tmp', os.path.basename(program))
205

206 207
    container = self.host.create_container(self.test_environment,
                                           ['python', dst_name, str(processes)])
208

209
    container.copy_path(program, '/tmp')
210

211
    self.host.start(container, max_cpu_percent=max_cpu_percent)
212

213
    time.sleep(sleep_time)
214

215
    stats = self.host.statistics(container)
216

217
    self.assertEqual(self.host.status(container), 'running')
218

219 220 221
    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)
222

223 224 225
    # make sure nothing is there anymore
    self.host.kill(container)
    self.assertEqual(self.host.wait(container), 137)
226 227


228
  @slow
229
  def test_cpulimit_at_20percent(self):
230
    # runs 1 process that should consume at most 20% of the host CPU
231 232 233
    self._run_cpulimit(1, 20, 3)


234
  @slow
235
  def test_cpulimit_at_100percent(self):
236
    # runs 4 processes that should consume 50% of the host CPU
237
    self._run_cpulimit(4, 100, 3)
238 239 240 241 242 243 244 245 246



class HostTest(unittest.TestCase):

  def setUp(self):
    Host.images_cache = {}


247
  @slow
248 249 250 251 252 253
  def test_images_cache(self):
    self.assertEqual(len(Host.images_cache), 0)

    # Might take some time
    start = time.time()

254
    host = Host(raise_on_errors=False)
255 256 257 258 259 260 261 262 263 264 265 266
    host.teardown()

    stop = time.time()

    nb_images = len(Host.images_cache)
    self.assertTrue(nb_images > 0)

    self.assertTrue(stop - start > 2.0)

    # Should be instantaneous
    start = time.time()

267
    host = Host(raise_on_errors=False)
268 269 270 271 272 273 274 275 276
    host.teardown()

    stop = time.time()

    self.assertEqual(len(Host.images_cache), nb_images)

    self.assertTrue(stop - start < 1.0)


277
  @slow
278 279 280 281 282 283
  def test_images_cache_file(self):
    self.assertEqual(len(Host.images_cache), 0)

    # Might take some time
    start = time.time()

284 285
    host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                raise_on_errors=False)
286 287 288 289 290 291 292 293 294 295 296 297 298 299
    host.teardown()

    stop = time.time()

    nb_images = len(Host.images_cache)
    self.assertTrue(nb_images > 0)

    self.assertTrue(stop - start > 2.0)

    Host.images_cache = {}

    # Should be instantaneous
    start = time.time()

300 301
    host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                raise_on_errors=False)
302 303 304 305 306 307 308
    host.teardown()

    stop = time.time()

    self.assertEqual(len(Host.images_cache), nb_images)

    self.assertTrue(stop - start < 1.0)