github.com/blystad/deis@v0.11.0/controller/api/tests/test_container.py (about) 1 """ 2 Unit tests for the Deis api app. 3 4 Run the tests with "./manage.py test api" 5 """ 6 7 from __future__ import unicode_literals 8 9 import json 10 import mock 11 import requests 12 13 from django.contrib.auth.models import User 14 from django.test import TransactionTestCase 15 from django.test.utils import override_settings 16 17 from django_fsm import TransitionNotAllowed 18 19 from api.models import Container, App 20 21 22 def mock_import_repository_task(*args, **kwargs): 23 resp = requests.Response() 24 resp.status_code = 200 25 resp._content_consumed = True 26 return resp 27 28 29 @override_settings(CELERY_ALWAYS_EAGER=True) 30 class ContainerTest(TransactionTestCase): 31 """Tests creation of containers on nodes""" 32 33 fixtures = ['tests.json'] 34 35 def setUp(self): 36 self.assertTrue( 37 self.client.login(username='autotest', password='password')) 38 body = {'id': 'autotest', 'domain': 'autotest.local', 'type': 'mock', 39 'hosts': 'host1,host2', 'auth': 'base64string', 'options': {}} 40 response = self.client.post('/api/clusters', json.dumps(body), 41 content_type='application/json') 42 self.assertEqual(response.status_code, 201) 43 # create a malicious scheduler as well 44 body['id'] = 'autotest2' 45 body['type'] = 'faulty' 46 response = self.client.post('/api/clusters', json.dumps(body), 47 content_type='application/json') 48 self.assertEqual(response.status_code, 201) 49 50 def test_container_state_good(self): 51 """Test that the finite state machine transitions with a good scheduler""" 52 url = '/api/apps' 53 body = {'cluster': 'autotest'} 54 response = self.client.post(url, json.dumps(body), content_type='application/json') 55 self.assertEqual(response.status_code, 201) 56 app_id = response.data['id'] 57 # create a container 58 c = Container.objects.create(owner=User.objects.get(username='autotest'), 59 app=App.objects.get(id=app_id), 60 release=App.objects.get(id=app_id).release_set.latest(), 61 type='web', 62 num=1) 63 self.assertEqual(c.state, 'initialized') 64 # test an illegal transition 65 self.assertRaises(TransitionNotAllowed, lambda: c.start()) 66 c.create() 67 self.assertEqual(c.state, 'created') 68 c.start() 69 self.assertEqual(c.state, 'up') 70 c.deploy(App.objects.get(id=app_id).release_set.latest()) 71 self.assertEqual(c.state, 'up') 72 c.destroy() 73 self.assertEqual(c.state, 'destroyed') 74 75 def test_container_state_bad(self): 76 """Test that the finite state machine transitions with a faulty scheduler""" 77 url = '/api/apps' 78 body = {'cluster': 'autotest2'} 79 response = self.client.post(url, json.dumps(body), content_type='application/json') 80 self.assertEqual(response.status_code, 201) 81 app_id = response.data['id'] 82 # create a container 83 c = Container.objects.create(owner=User.objects.get(username='autotest'), 84 app=App.objects.get(id=app_id), 85 release=App.objects.get(id=app_id).release_set.latest(), 86 type='web', 87 num=1) 88 self.assertEqual(c.state, 'initialized') 89 self.assertRaises(Exception, lambda: c.create()) 90 self.assertEqual(c.state, 'initialized') 91 # test an illegal transition 92 self.assertRaises(TransitionNotAllowed, lambda: c.start()) 93 self.assertEqual(c.state, 'initialized') 94 self.assertRaises( 95 Exception, 96 lambda: c.deploy( 97 App.objects.get(id=app_id).release_set.latest() 98 ) 99 ) 100 self.assertEqual(c.state, 'down') 101 self.assertRaises(Exception, lambda: c.destroy()) 102 self.assertEqual(c.state, 'down') 103 self.assertRaises(Exception, lambda: c.run('echo hello world')) 104 self.assertEqual(c.state, 'down') 105 106 def test_container_state_protected(self): 107 """Test that you cannot directly modify the state""" 108 url = '/api/apps' 109 body = {'cluster': 'autotest'} 110 response = self.client.post(url, json.dumps(body), content_type='application/json') 111 self.assertEqual(response.status_code, 201) 112 app_id = response.data['id'] 113 c = Container.objects.create(owner=User.objects.get(username='autotest'), 114 app=App.objects.get(id=app_id), 115 release=App.objects.get(id=app_id).release_set.latest(), 116 type='web', 117 num=1) 118 self.assertRaises(AttributeError, lambda: setattr(c, 'state', 'up')) 119 120 def test_container_api_heroku(self): 121 url = '/api/apps' 122 body = {'cluster': 'autotest'} 123 response = self.client.post(url, json.dumps(body), content_type='application/json') 124 self.assertEqual(response.status_code, 201) 125 app_id = response.data['id'] 126 # should start with zero 127 url = "/api/apps/{app_id}/containers".format(**locals()) 128 response = self.client.get(url) 129 self.assertEqual(response.status_code, 200) 130 self.assertEqual(len(response.data['results']), 0) 131 # post a new build 132 url = "/api/apps/{app_id}/builds".format(**locals()) 133 body = {'image': 'autotest/example', 'sha': 'a'*40, 134 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 135 response = self.client.post(url, json.dumps(body), content_type='application/json') 136 self.assertEqual(response.status_code, 201) 137 # scale up 138 url = "/api/apps/{app_id}/scale".format(**locals()) 139 body = {'web': 4, 'worker': 2} 140 response = self.client.post(url, json.dumps(body), content_type='application/json') 141 self.assertEqual(response.status_code, 204) 142 url = "/api/apps/{app_id}/containers".format(**locals()) 143 response = self.client.get(url) 144 self.assertEqual(response.status_code, 200) 145 self.assertEqual(len(response.data['results']), 6) 146 url = "/api/apps/{app_id}".format(**locals()) 147 response = self.client.get(url) 148 self.assertEqual(response.status_code, 200) 149 # test listing/retrieving container info 150 url = "/api/apps/{app_id}/containers/web".format(**locals()) 151 response = self.client.get(url) 152 self.assertEqual(response.status_code, 200) 153 self.assertEqual(len(response.data['results']), 4) 154 num = response.data['results'][0]['num'] 155 url = "/api/apps/{app_id}/containers/web/{num}".format(**locals()) 156 response = self.client.get(url) 157 self.assertEqual(response.status_code, 200) 158 self.assertEqual(response.data['num'], num) 159 # scale down 160 url = "/api/apps/{app_id}/scale".format(**locals()) 161 body = {'web': 2, 'worker': 1} 162 response = self.client.post(url, json.dumps(body), content_type='application/json') 163 self.assertEqual(response.status_code, 204) 164 url = "/api/apps/{app_id}/containers".format(**locals()) 165 response = self.client.get(url) 166 self.assertEqual(response.status_code, 200) 167 self.assertEqual(len(response.data['results']), 3) 168 self.assertEqual(max(c['num'] for c in response.data['results']), 2) 169 url = "/api/apps/{app_id}".format(**locals()) 170 response = self.client.get(url) 171 self.assertEqual(response.status_code, 200) 172 # scale down to 0 173 url = "/api/apps/{app_id}/scale".format(**locals()) 174 body = {'web': 0, 'worker': 0} 175 response = self.client.post(url, json.dumps(body), content_type='application/json') 176 self.assertEqual(response.status_code, 204) 177 url = "/api/apps/{app_id}/containers".format(**locals()) 178 response = self.client.get(url) 179 self.assertEqual(response.status_code, 200) 180 self.assertEqual(len(response.data['results']), 0) 181 url = "/api/apps/{app_id}".format(**locals()) 182 response = self.client.get(url) 183 self.assertEqual(response.status_code, 200) 184 185 @mock.patch('requests.post', mock_import_repository_task) 186 def test_container_api_docker(self): 187 url = '/api/apps' 188 body = {'cluster': 'autotest'} 189 response = self.client.post(url, json.dumps(body), content_type='application/json') 190 self.assertEqual(response.status_code, 201) 191 app_id = response.data['id'] 192 # should start with zero 193 url = "/api/apps/{app_id}/containers".format(**locals()) 194 response = self.client.get(url) 195 self.assertEqual(response.status_code, 200) 196 self.assertEqual(len(response.data['results']), 0) 197 # post a new build 198 url = "/api/apps/{app_id}/builds".format(**locals()) 199 body = {'image': 'autotest/example', 'dockerfile': "FROM busybox\nCMD /bin/true"} 200 response = self.client.post(url, json.dumps(body), content_type='application/json') 201 self.assertEqual(response.status_code, 201) 202 # scale up 203 url = "/api/apps/{app_id}/scale".format(**locals()) 204 body = {'cmd': 6} 205 response = self.client.post(url, json.dumps(body), content_type='application/json') 206 self.assertEqual(response.status_code, 204) 207 url = "/api/apps/{app_id}/containers".format(**locals()) 208 response = self.client.get(url) 209 self.assertEqual(response.status_code, 200) 210 self.assertEqual(len(response.data['results']), 6) 211 url = "/api/apps/{app_id}".format(**locals()) 212 response = self.client.get(url) 213 self.assertEqual(response.status_code, 200) 214 # test listing/retrieving container info 215 url = "/api/apps/{app_id}/containers/cmd".format(**locals()) 216 response = self.client.get(url) 217 self.assertEqual(response.status_code, 200) 218 self.assertEqual(len(response.data['results']), 6) 219 # scale down 220 url = "/api/apps/{app_id}/scale".format(**locals()) 221 body = {'cmd': 3} 222 response = self.client.post(url, json.dumps(body), content_type='application/json') 223 self.assertEqual(response.status_code, 204) 224 url = "/api/apps/{app_id}/containers".format(**locals()) 225 response = self.client.get(url) 226 self.assertEqual(response.status_code, 200) 227 self.assertEqual(len(response.data['results']), 3) 228 self.assertEqual(max(c['num'] for c in response.data['results']), 3) 229 url = "/api/apps/{app_id}".format(**locals()) 230 response = self.client.get(url) 231 self.assertEqual(response.status_code, 200) 232 # scale down to 0 233 url = "/api/apps/{app_id}/scale".format(**locals()) 234 body = {'cmd': 0} 235 response = self.client.post(url, json.dumps(body), content_type='application/json') 236 self.assertEqual(response.status_code, 204) 237 url = "/api/apps/{app_id}/containers".format(**locals()) 238 response = self.client.get(url) 239 self.assertEqual(response.status_code, 200) 240 self.assertEqual(len(response.data['results']), 0) 241 url = "/api/apps/{app_id}".format(**locals()) 242 response = self.client.get(url) 243 self.assertEqual(response.status_code, 200) 244 245 @mock.patch('requests.post', mock_import_repository_task) 246 def test_container_release(self): 247 url = '/api/apps' 248 body = {'cluster': 'autotest'} 249 response = self.client.post(url, json.dumps(body), content_type='application/json') 250 self.assertEqual(response.status_code, 201) 251 app_id = response.data['id'] 252 # should start with zero 253 url = "/api/apps/{app_id}/containers".format(**locals()) 254 response = self.client.get(url) 255 self.assertEqual(response.status_code, 200) 256 self.assertEqual(len(response.data['results']), 0) 257 # post a new build 258 url = "/api/apps/{app_id}/builds".format(**locals()) 259 body = {'image': 'autotest/example', 'sha': 'a'*40, 260 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 261 response = self.client.post(url, json.dumps(body), content_type='application/json') 262 self.assertEqual(response.status_code, 201) 263 # scale up 264 url = "/api/apps/{app_id}/scale".format(**locals()) 265 body = {'web': 1} 266 response = self.client.post(url, json.dumps(body), content_type='application/json') 267 self.assertEqual(response.status_code, 204) 268 url = "/api/apps/{app_id}/containers".format(**locals()) 269 response = self.client.get(url) 270 self.assertEqual(response.status_code, 200) 271 self.assertEqual(len(response.data['results']), 1) 272 self.assertEqual(response.data['results'][0]['release'], 'v2') 273 # post a new build 274 url = "/api/apps/{app_id}/builds".format(**locals()) 275 body = {'image': 'autotest/example'} 276 response = self.client.post(url, json.dumps(body), content_type='application/json') 277 self.assertEqual(response.status_code, 201) 278 self.assertEqual(response.data['image'], body['image']) 279 url = "/api/apps/{app_id}/containers".format(**locals()) 280 response = self.client.get(url) 281 self.assertEqual(response.status_code, 200) 282 self.assertEqual(len(response.data['results']), 1) 283 self.assertEqual(response.data['results'][0]['release'], 'v3') 284 # post new config 285 url = "/api/apps/{app_id}/config".format(**locals()) 286 body = {'values': json.dumps({'KEY': 'value'})} 287 response = self.client.post(url, json.dumps(body), content_type='application/json') 288 self.assertEqual(response.status_code, 201) 289 url = "/api/apps/{app_id}/containers".format(**locals()) 290 response = self.client.get(url) 291 self.assertEqual(response.status_code, 200) 292 self.assertEqual(len(response.data['results']), 1) 293 self.assertEqual(response.data['results'][0]['release'], 'v4') 294 295 def test_container_errors(self): 296 url = '/api/apps' 297 body = {'cluster': 'autotest'} 298 response = self.client.post(url, json.dumps(body), content_type='application/json') 299 self.assertEqual(response.status_code, 201) 300 app_id = response.data['id'] 301 url = "/api/apps/{app_id}/scale".format(**locals()) 302 body = {'web': 'not_an_int'} 303 response = self.client.post(url, json.dumps(body), content_type='application/json') 304 self.assertContains(response, 'Invalid scaling format', status_code=400) 305 body = {'invalid': 1} 306 response = self.client.post(url, json.dumps(body), content_type='application/json') 307 self.assertContains(response, 'Container type invalid', status_code=400) 308 309 def test_container_str(self): 310 """Test the text representation of a container.""" 311 url = '/api/apps' 312 body = {'cluster': 'autotest'} 313 response = self.client.post(url, json.dumps(body), content_type='application/json') 314 self.assertEqual(response.status_code, 201) 315 app_id = response.data['id'] 316 # post a new build 317 url = "/api/apps/{app_id}/builds".format(**locals()) 318 body = {'image': 'autotest/example', 'sha': 'a'*40, 319 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 320 response = self.client.post(url, json.dumps(body), content_type='application/json') 321 self.assertEqual(response.status_code, 201) 322 # scale up 323 url = "/api/apps/{app_id}/scale".format(**locals()) 324 body = {'web': 4, 'worker': 2} 325 response = self.client.post(url, json.dumps(body), content_type='application/json') 326 self.assertEqual(response.status_code, 204) 327 # should start with zero 328 url = "/api/apps/{app_id}/containers".format(**locals()) 329 response = self.client.get(url) 330 self.assertEqual(response.status_code, 200) 331 self.assertEqual(len(response.data['results']), 6) 332 uuid = response.data['results'][0]['uuid'] 333 container = Container.objects.get(uuid=uuid) 334 self.assertEqual(container.short_name(), 335 "{}.{}.{}".format(container.app, container.type, container.num)) 336 self.assertEqual(str(container), 337 "{}.{}.{}".format(container.app, container.type, container.num)) 338 339 def test_container_command_format(self): 340 # regression test for https://github.com/deis/deis/pull/1285 341 url = '/api/apps' 342 body = {'cluster': 'autotest'} 343 response = self.client.post(url, json.dumps(body), content_type='application/json') 344 self.assertEqual(response.status_code, 201) 345 app_id = response.data['id'] 346 # post a new build 347 url = "/api/apps/{app_id}/builds".format(**locals()) 348 body = {'image': 'autotest/example', 'sha': 'a'*40, 349 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 350 response = self.client.post(url, json.dumps(body), content_type='application/json') 351 self.assertEqual(response.status_code, 201) 352 # scale up 353 url = "/api/apps/{app_id}/scale".format(**locals()) 354 body = {'web': 1} 355 response = self.client.post(url, json.dumps(body), content_type='application/json') 356 self.assertEqual(response.status_code, 204) 357 url = "/api/apps/{app_id}/containers".format(**locals()) 358 response = self.client.get(url) 359 # verify that the container._command property got formatted 360 self.assertEqual(response.status_code, 200) 361 self.assertEqual(len(response.data['results']), 1) 362 uuid = response.data['results'][0]['uuid'] 363 container = Container.objects.get(uuid=uuid) 364 self.assertNotIn('{c_type}', container._command) 365 366 def test_container_scale_errors(self): 367 url = '/api/apps' 368 body = {'cluster': 'autotest'} 369 response = self.client.post(url, json.dumps(body), content_type='application/json') 370 self.assertEqual(response.status_code, 201) 371 app_id = response.data['id'] 372 # should start with zero 373 url = "/api/apps/{app_id}/containers".format(**locals()) 374 response = self.client.get(url) 375 self.assertEqual(response.status_code, 200) 376 self.assertEqual(len(response.data['results']), 0) 377 # post a new build 378 url = "/api/apps/{app_id}/builds".format(**locals()) 379 body = {'image': 'autotest/example', 'sha': 'a'*40, 380 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 381 response = self.client.post(url, json.dumps(body), content_type='application/json') 382 self.assertEqual(response.status_code, 201) 383 # scale to a negative number 384 url = "/api/apps/{app_id}/scale".format(**locals()) 385 body = {'web': -1} 386 response = self.client.post(url, json.dumps(body), content_type='application/json') 387 self.assertEqual(response.status_code, 400) 388 # scale to something other than a number 389 url = "/api/apps/{app_id}/scale".format(**locals()) 390 body = {'web': 'one'} 391 response = self.client.post(url, json.dumps(body), content_type='application/json') 392 self.assertEqual(response.status_code, 400) 393 # scale to something other than a number 394 url = "/api/apps/{app_id}/scale".format(**locals()) 395 body = {'web': [1]} 396 response = self.client.post(url, json.dumps(body), content_type='application/json') 397 self.assertEqual(response.status_code, 400) 398 # scale up to an integer as a sanity check 399 url = "/api/apps/{app_id}/scale".format(**locals()) 400 body = {'web': 1} 401 response = self.client.post(url, json.dumps(body), content_type='application/json') 402 self.assertEqual(response.status_code, 204)