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

45

André Anjos's avatar
André Anjos committed
46 47 48 49 50 51 52 53 54 55 56
# 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)


57

58 59
class AsyncTest(unittest.TestCase):

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


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


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


76 77 78 79
  def test_echo(self):

    string = "hello, world"

80 81 82 83 84 85 86
    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), '')
87 88 89 90


  def test_non_existing(self):

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

93
    self.assertRaises(docker.errors.NotFound, self.host.start, container)
94

95
    assert not self.host.containers # All containers are gone
96 97 98 99 100 101


  def test_timeout(self):

    sleep_for = 100 # seconds

102 103
    container = self.host.create_container('debian:8.4', ["sleep", str(sleep_for)])
    self.host.start(container)
104

105 106 107 108 109
    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')
110

111
    self.host.kill(container)
112

113
    status = self.host.wait(container)
114

115 116 117 118
    self.assertEqual(self.host.status(container), 'exited')
    self.assertEqual(status, 137)
    self.assertEqual(self.host.stdout(container), '')
    self.assertEqual(self.host.stderr(container), '')
119 120


121
  def test_does_not_timeout(self):
122

123
    sleep_for = 0.5 # seconds
124

125 126
    container = self.host.create_container('debian:8.4', ["sleep", str(sleep_for)])
    self.host.start(container)
127

128
    status = self.host.wait(container, timeout=5) # Should not timeout
129

130 131 132 133
    self.assertEqual(self.host.status(container), 'exited')
    self.assertEqual(status, 0)
    self.assertEqual(self.host.stdout(container), '')
    self.assertEqual(self.host.stderr(container), '')
134 135


136
  def test_memory_limit(self):
137

138 139 140 141 142 143 144 145
    cmd = ['python', '-c', '; '.join([
        "print('Before')",
        "import sys; sys.stdout.flush()",
        "d = '0' * (10 * 1024 * 1024)",
        "import time; time.sleep(5)",
        "print('After')",
      ])
    ]
146

147 148
    container = self.host.create_container(self.test_environment, cmd)
    self.host.start(container, virtual_memory_in_megabytes=4)
149

150
    time.sleep(2)
151

152
    stats = self.host.statistics(container)
153

154
    status = self.host.wait(container)
155

156 157 158 159
    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), '')
160 161


162
  def test_memory_limit2(self):
163

164 165 166 167 168 169 170 171
    cmd = ['python', '-c', '; '.join([
        "print('Before')",
        "import sys; sys.stdout.flush()",
        "d = '0' * (10 * 1024 * 1024)",
        "import time; time.sleep(5)",
        "print('After')",
      ])
    ]
172

173 174
    container = self.host.create_container(self.test_environment, cmd)
    self.host.start(container, virtual_memory_in_megabytes=100)
175

176
    time.sleep(2)
177

178
    stats = self.host.statistics(container)
179

180
    status = self.host.wait(container)
181

182 183
    assert stats['memory']['percent'] > 10, 'Memory check failed, ' \
        '%d%% <= 10%%' % stats['memory']['percent']
184

185 186
    assert stats['memory']['percent'] < 15, 'Memory check failed, ' \
        '%d%% >= 15%%' % stats['memory']['percent']
187

188 189 190 191
    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), '')
192 193


194
  def _run_cpulimit(self, processes, max_cpu_percent, sleep_time):
195

196 197
    program = pkg_resources.resource_filename(__name__, 'cpu_stress.py')
    dst_name = os.path.join('/tmp', os.path.basename(program))
198

199 200
    container = self.host.create_container(self.test_environment,
                                           ['python', dst_name, str(processes)])
201

202
    container.copy_path(program, '/tmp')
203

204
    self.host.start(container, max_cpu_percent=max_cpu_percent)
205

206
    time.sleep(sleep_time)
207

208
    stats = self.host.statistics(container)
209

210
    self.assertEqual(self.host.status(container), 'running')
211

212 213 214
    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)
215

216 217 218
    # make sure nothing is there anymore
    self.host.kill(container)
    self.assertEqual(self.host.wait(container), 137)
219 220 221


  def test_cpulimit_at_20percent(self):
222
    # runs 1 process that should consume at most 20% of the host CPU
223 224 225 226
    self._run_cpulimit(1, 20, 3)


  def test_cpulimit_at_100percent(self):
227
    # runs 4 processes that should consume 50% of the host CPU
228
    self._run_cpulimit(4, 100, 3)
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243



class HostTest(unittest.TestCase):

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


  def test_images_cache(self):
    self.assertEqual(len(Host.images_cache), 0)

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

244
    host = Host(raise_on_errors=False)
245 246 247 248 249 250 251 252 253 254 255 256
    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()

257
    host = Host(raise_on_errors=False)
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
    host.teardown()

    stop = time.time()

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

    self.assertTrue(stop - start < 1.0)


  def test_images_cache_file(self):
    self.assertEqual(len(Host.images_cache), 0)

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

273 274
    host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                raise_on_errors=False)
275 276 277 278 279 280 281 282 283 284 285 286 287 288
    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()

289 290
    host = Host(images_cache=os.path.join(tmp_prefix, 'images_cache.json'),
                raise_on_errors=False)
291 292 293 294 295 296 297
    host.teardown()

    stop = time.time()

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

    self.assertTrue(stop - start < 1.0)