github.com/dustinrc/deis@v1.10.1-0.20150917223407-0894a5fb979e/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 scheduler.states import TransitionError 16 from rest_framework.authtoken.models import Token 17 18 from api.models import App, Build, Container, Release 19 20 21 def mock_import_repository_task(*args, **kwargs): 22 resp = requests.Response() 23 resp.status_code = 200 24 resp._content_consumed = True 25 return resp 26 27 28 class ContainerTest(TransactionTestCase): 29 """Tests creation of containers on nodes""" 30 31 fixtures = ['tests.json'] 32 33 def setUp(self): 34 self.user = User.objects.get(username='autotest') 35 self.token = Token.objects.get(user=self.user).key 36 37 def test_container_state_good(self): 38 """Test that the finite state machine transitions with a good scheduler""" 39 url = '/v1/apps' 40 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 41 self.assertEqual(response.status_code, 201) 42 app_id = response.data['id'] 43 app = App.objects.get(id=app_id) 44 user = User.objects.get(username='autotest') 45 build = Build.objects.create(owner=user, app=app, image="qwerty") 46 # create an initial release 47 release = Release.objects.create(version=2, 48 owner=user, 49 app=app, 50 config=app.config_set.latest(), 51 build=build) 52 # create a container 53 c = Container.objects.create(owner=user, 54 app=app, 55 release=release, 56 type='web', 57 num=1) 58 self.assertEqual(c.state, 'initialized') 59 # test an illegal transition 60 self.assertRaises(TransitionError, lambda: c.start()) 61 c.create() 62 self.assertEqual(c.state, 'created') 63 c.start() 64 self.assertEqual(c.state, 'up') 65 c.stop() 66 self.assertEqual(c.state, 'down') 67 c.destroy() 68 self.assertEqual(c.state, 'destroyed') 69 70 def test_container_state_protected(self): 71 """Test that you cannot directly modify the state""" 72 url = '/v1/apps' 73 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 74 self.assertEqual(response.status_code, 201) 75 app_id = response.data['id'] 76 app = App.objects.get(id=app_id) 77 user = User.objects.get(username='autotest') 78 build = Build.objects.create(owner=user, app=app, image="qwerty") 79 # create an initial release 80 release = Release.objects.create(version=2, 81 owner=user, 82 app=app, 83 config=app.config_set.latest(), 84 build=build) 85 # create a container 86 c = Container.objects.create(owner=user, 87 app=app, 88 release=release, 89 type='web', 90 num=1) 91 self.assertRaises(AttributeError, lambda: setattr(c, 'state', 'up')) 92 93 def test_container_api_heroku(self): 94 url = '/v1/apps' 95 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 96 self.assertEqual(response.status_code, 201) 97 app_id = response.data['id'] 98 # should start with zero 99 url = "/v1/apps/{app_id}/containers".format(**locals()) 100 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 101 self.assertEqual(response.status_code, 200) 102 self.assertEqual(len(response.data['results']), 0) 103 # post a new build 104 url = "/v1/apps/{app_id}/builds".format(**locals()) 105 body = {'image': 'autotest/example', 'sha': 'a'*40, 106 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 107 response = self.client.post(url, json.dumps(body), content_type='application/json', 108 HTTP_AUTHORIZATION='token {}'.format(self.token)) 109 self.assertEqual(response.status_code, 201) 110 # scale up 111 url = "/v1/apps/{app_id}/scale".format(**locals()) 112 # test setting one proc type at a time 113 body = {'web': 4} 114 response = self.client.post(url, json.dumps(body), content_type='application/json', 115 HTTP_AUTHORIZATION='token {}'.format(self.token)) 116 self.assertEqual(response.status_code, 204) 117 body = {'worker': 2} 118 response = self.client.post(url, json.dumps(body), content_type='application/json', 119 HTTP_AUTHORIZATION='token {}'.format(self.token)) 120 self.assertEqual(response.status_code, 204) 121 url = "/v1/apps/{app_id}/containers".format(**locals()) 122 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 123 self.assertEqual(response.status_code, 200) 124 self.assertEqual(len(response.data['results']), 6) 125 url = "/v1/apps/{app_id}".format(**locals()) 126 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 127 self.assertEqual(response.status_code, 200) 128 # ensure the structure field is up-to-date 129 self.assertEqual(response.data['structure']['web'], 4) 130 self.assertEqual(response.data['structure']['worker'], 2) 131 # test listing/retrieving container info 132 url = "/v1/apps/{app_id}/containers/web".format(**locals()) 133 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 134 self.assertEqual(response.status_code, 200) 135 self.assertEqual(len(response.data['results']), 4) 136 num = response.data['results'][0]['num'] 137 url = "/v1/apps/{app_id}/containers/web/{num}".format(**locals()) 138 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 139 self.assertEqual(response.status_code, 200) 140 self.assertEqual(response.data['num'], num) 141 # scale down 142 url = "/v1/apps/{app_id}/scale".format(**locals()) 143 # test setting two proc types at a time 144 body = {'web': 2, 'worker': 1} 145 response = self.client.post(url, json.dumps(body), content_type='application/json', 146 HTTP_AUTHORIZATION='token {}'.format(self.token)) 147 self.assertEqual(response.status_code, 204) 148 url = "/v1/apps/{app_id}/containers".format(**locals()) 149 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 150 self.assertEqual(response.status_code, 200) 151 self.assertEqual(len(response.data['results']), 3) 152 self.assertEqual(max(c['num'] for c in response.data['results']), 2) 153 url = "/v1/apps/{app_id}".format(**locals()) 154 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 155 self.assertEqual(response.status_code, 200) 156 # ensure the structure field is up-to-date 157 self.assertEqual(response.data['structure']['web'], 2) 158 self.assertEqual(response.data['structure']['worker'], 1) 159 # scale down to 0 160 url = "/v1/apps/{app_id}/scale".format(**locals()) 161 body = {'web': 0, 'worker': 0} 162 response = self.client.post(url, json.dumps(body), content_type='application/json', 163 HTTP_AUTHORIZATION='token {}'.format(self.token)) 164 self.assertEqual(response.status_code, 204) 165 url = "/v1/apps/{app_id}/containers".format(**locals()) 166 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 167 self.assertEqual(response.status_code, 200) 168 self.assertEqual(len(response.data['results']), 0) 169 url = "/v1/apps/{app_id}".format(**locals()) 170 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 171 self.assertEqual(response.status_code, 200) 172 173 @mock.patch('requests.post', mock_import_repository_task) 174 def test_container_api_docker(self): 175 url = '/v1/apps' 176 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 177 self.assertEqual(response.status_code, 201) 178 app_id = response.data['id'] 179 # should start with zero 180 url = "/v1/apps/{app_id}/containers".format(**locals()) 181 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 182 self.assertEqual(response.status_code, 200) 183 self.assertEqual(len(response.data['results']), 0) 184 # post a new build 185 url = "/v1/apps/{app_id}/builds".format(**locals()) 186 body = {'image': 'autotest/example', 'dockerfile': "FROM busybox\nCMD /bin/true"} 187 response = self.client.post(url, json.dumps(body), content_type='application/json', 188 HTTP_AUTHORIZATION='token {}'.format(self.token)) 189 self.assertEqual(response.status_code, 201) 190 # scale up 191 url = "/v1/apps/{app_id}/scale".format(**locals()) 192 body = {'cmd': 6} 193 response = self.client.post(url, json.dumps(body), content_type='application/json', 194 HTTP_AUTHORIZATION='token {}'.format(self.token)) 195 self.assertEqual(response.status_code, 204) 196 url = "/v1/apps/{app_id}/containers".format(**locals()) 197 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 198 self.assertEqual(response.status_code, 200) 199 self.assertEqual(len(response.data['results']), 6) 200 url = "/v1/apps/{app_id}".format(**locals()) 201 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 202 self.assertEqual(response.status_code, 200) 203 # test listing/retrieving container info 204 url = "/v1/apps/{app_id}/containers/cmd".format(**locals()) 205 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 206 self.assertEqual(response.status_code, 200) 207 self.assertEqual(len(response.data['results']), 6) 208 # scale down 209 url = "/v1/apps/{app_id}/scale".format(**locals()) 210 body = {'cmd': 3} 211 response = self.client.post(url, json.dumps(body), content_type='application/json', 212 HTTP_AUTHORIZATION='token {}'.format(self.token)) 213 self.assertEqual(response.status_code, 204) 214 url = "/v1/apps/{app_id}/containers".format(**locals()) 215 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 216 self.assertEqual(response.status_code, 200) 217 self.assertEqual(len(response.data['results']), 3) 218 self.assertEqual(max(c['num'] for c in response.data['results']), 3) 219 url = "/v1/apps/{app_id}".format(**locals()) 220 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 221 self.assertEqual(response.status_code, 200) 222 # scale down to 0 223 url = "/v1/apps/{app_id}/scale".format(**locals()) 224 body = {'cmd': 0} 225 response = self.client.post(url, json.dumps(body), content_type='application/json', 226 HTTP_AUTHORIZATION='token {}'.format(self.token)) 227 self.assertEqual(response.status_code, 204) 228 url = "/v1/apps/{app_id}/containers".format(**locals()) 229 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 230 self.assertEqual(response.status_code, 200) 231 self.assertEqual(len(response.data['results']), 0) 232 url = "/v1/apps/{app_id}".format(**locals()) 233 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 234 self.assertEqual(response.status_code, 200) 235 236 @mock.patch('requests.post', mock_import_repository_task) 237 def test_container_release(self): 238 url = '/v1/apps' 239 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 240 self.assertEqual(response.status_code, 201) 241 app_id = response.data['id'] 242 # should start with zero 243 url = "/v1/apps/{app_id}/containers".format(**locals()) 244 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 245 self.assertEqual(response.status_code, 200) 246 self.assertEqual(len(response.data['results']), 0) 247 # post a new build 248 url = "/v1/apps/{app_id}/builds".format(**locals()) 249 body = {'image': 'autotest/example', 'sha': 'a'*40, 250 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 251 response = self.client.post(url, json.dumps(body), content_type='application/json', 252 HTTP_AUTHORIZATION='token {}'.format(self.token)) 253 self.assertEqual(response.status_code, 201) 254 # scale up 255 url = "/v1/apps/{app_id}/scale".format(**locals()) 256 body = {'web': 1} 257 response = self.client.post(url, json.dumps(body), content_type='application/json', 258 HTTP_AUTHORIZATION='token {}'.format(self.token)) 259 self.assertEqual(response.status_code, 204) 260 url = "/v1/apps/{app_id}/containers".format(**locals()) 261 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 262 self.assertEqual(response.status_code, 200) 263 self.assertEqual(len(response.data['results']), 1) 264 self.assertEqual(response.data['results'][0]['release'], 'v2') 265 # post a new build 266 url = "/v1/apps/{app_id}/builds".format(**locals()) 267 # a web proctype must exist on the second build or else the container will be removed 268 body = {'image': 'autotest/example', 'procfile': {'web': 'echo hi'}} 269 response = self.client.post(url, json.dumps(body), content_type='application/json', 270 HTTP_AUTHORIZATION='token {}'.format(self.token)) 271 self.assertEqual(response.status_code, 201) 272 self.assertEqual(response.data['image'], body['image']) 273 url = "/v1/apps/{app_id}/containers".format(**locals()) 274 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 275 self.assertEqual(response.status_code, 200) 276 self.assertEqual(len(response.data['results']), 1) 277 self.assertEqual(response.data['results'][0]['release'], 'v3') 278 # post new config 279 url = "/v1/apps/{app_id}/config".format(**locals()) 280 body = {'values': json.dumps({'KEY': 'value'})} 281 response = self.client.post(url, json.dumps(body), content_type='application/json', 282 HTTP_AUTHORIZATION='token {}'.format(self.token)) 283 self.assertEqual(response.status_code, 201) 284 url = "/v1/apps/{app_id}/containers".format(**locals()) 285 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 286 self.assertEqual(response.status_code, 200) 287 self.assertEqual(len(response.data['results']), 1) 288 self.assertEqual(response.data['results'][0]['release'], 'v4') 289 290 def test_container_errors(self): 291 url = '/v1/apps' 292 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 293 self.assertEqual(response.status_code, 201) 294 app_id = response.data['id'] 295 # create a release so we can scale 296 app = App.objects.get(id=app_id) 297 user = User.objects.get(username='autotest') 298 build = Build.objects.create(owner=user, app=app, image="qwerty") 299 # create an initial release 300 Release.objects.create(version=2, 301 owner=user, 302 app=app, 303 config=app.config_set.latest(), 304 build=build) 305 url = "/v1/apps/{app_id}/scale".format(**locals()) 306 body = {'web': 'not_an_int'} 307 response = self.client.post(url, json.dumps(body), content_type='application/json', 308 HTTP_AUTHORIZATION='token {}'.format(self.token)) 309 self.assertEqual(response.status_code, 400) 310 self.assertEqual(response.data, {'detail': "Invalid scaling format: invalid literal for " 311 "int() with base 10: 'not_an_int'"}) 312 body = {'invalid': 1} 313 response = self.client.post(url, json.dumps(body), content_type='application/json', 314 HTTP_AUTHORIZATION='token {}'.format(self.token)) 315 self.assertContains(response, 'Container type invalid', status_code=400) 316 317 def test_container_str(self): 318 """Test the text representation of a container.""" 319 url = '/v1/apps' 320 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 321 self.assertEqual(response.status_code, 201) 322 app_id = response.data['id'] 323 # post a new build 324 url = "/v1/apps/{app_id}/builds".format(**locals()) 325 body = {'image': 'autotest/example', 'sha': 'a'*40, 326 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 327 response = self.client.post(url, json.dumps(body), content_type='application/json', 328 HTTP_AUTHORIZATION='token {}'.format(self.token)) 329 self.assertEqual(response.status_code, 201) 330 # scale up 331 url = "/v1/apps/{app_id}/scale".format(**locals()) 332 body = {'web': 4, 'worker': 2} 333 response = self.client.post(url, json.dumps(body), content_type='application/json', 334 HTTP_AUTHORIZATION='token {}'.format(self.token)) 335 self.assertEqual(response.status_code, 204) 336 # should start with zero 337 url = "/v1/apps/{app_id}/containers".format(**locals()) 338 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 339 self.assertEqual(response.status_code, 200) 340 self.assertEqual(len(response.data['results']), 6) 341 uuid = response.data['results'][0]['uuid'] 342 container = Container.objects.get(uuid=uuid) 343 self.assertEqual(container.short_name(), 344 "{}.{}.{}".format(container.app, container.type, container.num)) 345 self.assertEqual(str(container), 346 "{}.{}.{}".format(container.app, container.type, container.num)) 347 348 def test_container_command_format(self): 349 # regression test for https://github.com/deis/deis/pull/1285 350 url = '/v1/apps' 351 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 352 self.assertEqual(response.status_code, 201) 353 app_id = response.data['id'] 354 # post a new build 355 url = "/v1/apps/{app_id}/builds".format(**locals()) 356 body = {'image': 'autotest/example', 'sha': 'a'*40, 357 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 358 response = self.client.post(url, json.dumps(body), content_type='application/json', 359 HTTP_AUTHORIZATION='token {}'.format(self.token)) 360 self.assertEqual(response.status_code, 201) 361 # scale up 362 url = "/v1/apps/{app_id}/scale".format(**locals()) 363 body = {'web': 1} 364 response = self.client.post(url, json.dumps(body), content_type='application/json', 365 HTTP_AUTHORIZATION='token {}'.format(self.token)) 366 self.assertEqual(response.status_code, 204) 367 url = "/v1/apps/{app_id}/containers".format(**locals()) 368 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 369 # verify that the container._command property got formatted 370 self.assertEqual(response.status_code, 200) 371 self.assertEqual(len(response.data['results']), 1) 372 uuid = response.data['results'][0]['uuid'] 373 container = Container.objects.get(uuid=uuid) 374 self.assertNotIn('{c_type}', container._command) 375 376 def test_container_scale_errors(self): 377 url = '/v1/apps' 378 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 379 self.assertEqual(response.status_code, 201) 380 app_id = response.data['id'] 381 # should start with zero 382 url = "/v1/apps/{app_id}/containers".format(**locals()) 383 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 384 self.assertEqual(response.status_code, 200) 385 self.assertEqual(len(response.data['results']), 0) 386 # post a new build 387 url = "/v1/apps/{app_id}/builds".format(**locals()) 388 body = {'image': 'autotest/example', 'sha': 'a'*40, 389 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 390 response = self.client.post(url, json.dumps(body), content_type='application/json', 391 HTTP_AUTHORIZATION='token {}'.format(self.token)) 392 self.assertEqual(response.status_code, 201) 393 # scale to a negative number 394 url = "/v1/apps/{app_id}/scale".format(**locals()) 395 body = {'web': -1} 396 response = self.client.post(url, json.dumps(body), content_type='application/json', 397 HTTP_AUTHORIZATION='token {}'.format(self.token)) 398 self.assertEqual(response.status_code, 400) 399 # scale to something other than a number 400 url = "/v1/apps/{app_id}/scale".format(**locals()) 401 body = {'web': 'one'} 402 response = self.client.post(url, json.dumps(body), content_type='application/json', 403 HTTP_AUTHORIZATION='token {}'.format(self.token)) 404 self.assertEqual(response.status_code, 400) 405 # scale to something other than a number 406 url = "/v1/apps/{app_id}/scale".format(**locals()) 407 body = {'web': [1]} 408 response = self.client.post(url, json.dumps(body), content_type='application/json', 409 HTTP_AUTHORIZATION='token {}'.format(self.token)) 410 self.assertEqual(response.status_code, 400) 411 # scale up to an integer as a sanity check 412 url = "/v1/apps/{app_id}/scale".format(**locals()) 413 body = {'web': 1} 414 response = self.client.post(url, json.dumps(body), content_type='application/json', 415 HTTP_AUTHORIZATION='token {}'.format(self.token)) 416 self.assertEqual(response.status_code, 204) 417 418 def test_admin_can_manage_other_containers(self): 419 """If a non-admin user creates a container, an administrator should be able to 420 manage it. 421 """ 422 user = User.objects.get(username='autotest2') 423 token = Token.objects.get(user=user).key 424 url = '/v1/apps' 425 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(token)) 426 self.assertEqual(response.status_code, 201) 427 app_id = response.data['id'] 428 # post a new build 429 url = "/v1/apps/{app_id}/builds".format(**locals()) 430 body = {'image': 'autotest/example', 'sha': 'a'*40, 431 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 432 response = self.client.post(url, json.dumps(body), content_type='application/json', 433 HTTP_AUTHORIZATION='token {}'.format(token)) 434 self.assertEqual(response.status_code, 201) 435 # login as admin, scale up 436 url = "/v1/apps/{app_id}/scale".format(**locals()) 437 body = {'web': 4, 'worker': 2} 438 response = self.client.post(url, json.dumps(body), content_type='application/json', 439 HTTP_AUTHORIZATION='token {}'.format(self.token)) 440 self.assertEqual(response.status_code, 204) 441 442 def test_scale_without_build_should_error(self): 443 """A user should not be able to scale processes unless a build is present.""" 444 app_id = 'autotest' 445 url = '/v1/apps' 446 body = {'cluster': 'autotest', 'id': app_id} 447 response = self.client.post(url, json.dumps(body), content_type='application/json', 448 HTTP_AUTHORIZATION='token {}'.format(self.token)) 449 url = '/v1/apps/{app_id}/scale'.format(**locals()) 450 body = {'web': '1'} 451 response = self.client.post(url, json.dumps(body), content_type='application/json', 452 HTTP_AUTHORIZATION='token {}'.format(self.token)) 453 self.assertEqual(response.status_code, 400) 454 self.assertEqual(response.data, {'detail': 'No build associated with this release'}) 455 456 def test_command_good(self): 457 """Test the default command for each container workflow""" 458 url = '/v1/apps' 459 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 460 self.assertEqual(response.status_code, 201) 461 app_id = response.data['id'] 462 app = App.objects.get(id=app_id) 463 user = User.objects.get(username='autotest') 464 # Heroku Buildpack app 465 build = Build.objects.create(owner=user, 466 app=app, 467 image="qwerty", 468 procfile={'web': 'node server.js', 469 'worker': 'node worker.js'}, 470 sha='african-swallow', 471 dockerfile='') 472 # create an initial release 473 release = Release.objects.create(version=2, 474 owner=user, 475 app=app, 476 config=app.config_set.latest(), 477 build=build) 478 # create a container 479 c = Container.objects.create(owner=user, 480 app=app, 481 release=release, 482 type='web', 483 num=1) 484 # use `start web` for backwards compatibility with slugrunner 485 self.assertEqual(c._command, 'start web') 486 c.type = 'worker' 487 self.assertEqual(c._command, 'start worker') 488 # switch to docker image app 489 build.sha = None 490 c.type = 'web' 491 self.assertEqual(c._command, "bash -c 'node server.js'") 492 # switch to dockerfile app 493 build.sha = 'european-swallow' 494 build.dockerfile = 'dockerdockerdocker' 495 self.assertEqual(c._command, "bash -c 'node server.js'") 496 c.type = 'cmd' 497 self.assertEqual(c._command, '') 498 # ensure we can override the cmd process type in a Procfile 499 build.procfile['cmd'] = 'node server.js' 500 self.assertEqual(c._command, "bash -c 'node server.js'") 501 c.type = 'worker' 502 self.assertEqual(c._command, "bash -c 'node worker.js'") 503 c.release.build.procfile = None 504 # for backwards compatibility if no Procfile is supplied 505 self.assertEqual(c._command, 'start worker') 506 507 def test_run_command_good(self): 508 """Test the run command for each container workflow""" 509 url = '/v1/apps' 510 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 511 self.assertEqual(response.status_code, 201) 512 app_id = response.data['id'] 513 app = App.objects.get(id=app_id) 514 user = User.objects.get(username='autotest') 515 # dockerfile + procfile worflow 516 build = Build.objects.create(owner=user, 517 app=app, 518 image="qwerty", 519 procfile={'web': 'node server.js', 520 'worker': 'node worker.js'}, 521 dockerfile='foo', 522 sha='somereallylongsha') 523 # create an initial release 524 release = Release.objects.create(version=2, 525 owner=user, 526 app=app, 527 config=app.config_set.latest(), 528 build=build) 529 # create a container 530 c = Container.objects.create(owner=user, 531 app=app, 532 release=release, 533 type='web', 534 num=1) 535 rc, output = c.run('echo hi') 536 self.assertEqual(rc, 0) 537 self.assertEqual(json.loads(output)['entrypoint'], '/bin/bash') 538 # docker image workflow 539 build.dockerfile = None 540 build.sha = None 541 rc, output = c.run('echo hi') 542 self.assertEqual(json.loads(output)['entrypoint'], '/bin/bash') 543 # procfile workflow 544 build.sha = 'somereallylongsha' 545 rc, output = c.run('echo hi') 546 self.assertEqual(json.loads(output)['entrypoint'], '/runner/init') 547 548 def test_scaling_does_not_add_run_proctypes_to_structure(self): 549 """Test that app info doesn't show transient "run" proctypes.""" 550 url = '/v1/apps' 551 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 552 self.assertEqual(response.status_code, 201) 553 app_id = response.data['id'] 554 app = App.objects.get(id=app_id) 555 user = User.objects.get(username='autotest') 556 # dockerfile + procfile worflow 557 build = Build.objects.create(owner=user, 558 app=app, 559 image="qwerty", 560 procfile={'web': 'node server.js', 561 'worker': 'node worker.js'}, 562 dockerfile='foo', 563 sha='somereallylongsha') 564 # create an initial release 565 release = Release.objects.create(version=2, 566 owner=user, 567 app=app, 568 config=app.config_set.latest(), 569 build=build) 570 # create a run container manually to simulate how they persist 571 # when actually created by "deis apps:run". 572 c = Container.objects.create(owner=user, 573 app=app, 574 release=release, 575 type='run', 576 num=1) 577 # scale up 578 url = "/v1/apps/{app_id}/scale".format(**locals()) 579 body = {'web': 3} 580 response = self.client.post(url, json.dumps(body), content_type='application/json', 581 HTTP_AUTHORIZATION='token {}'.format(self.token)) 582 self.assertEqual(response.status_code, 204) 583 # test that "run" proctype isn't in the app info returned 584 url = "/v1/apps/{app_id}".format(**locals()) 585 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 586 self.assertEqual(response.status_code, 200) 587 self.assertNotIn('run', response.data['structure']) 588 589 def test_scale_with_unauthorized_user_returns_403(self): 590 """An unauthorized user should not be able to access an app's resources. 591 592 If an unauthorized user is trying to scale an app he or she does not have access to, it 593 should return a 403. 594 """ 595 url = '/v1/apps' 596 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 597 self.assertEqual(response.status_code, 201) 598 app_id = response.data['id'] 599 # post a new build 600 url = "/v1/apps/{app_id}/builds".format(**locals()) 601 body = {'image': 'autotest/example', 'sha': 'a'*40, 602 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 603 response = self.client.post(url, json.dumps(body), content_type='application/json', 604 HTTP_AUTHORIZATION='token {}'.format(self.token)) 605 unauthorized_user = User.objects.get(username='autotest2') 606 unauthorized_token = Token.objects.get(user=unauthorized_user).key 607 # scale up with unauthorized user 608 url = "/v1/apps/{app_id}/scale".format(**locals()) 609 body = {'web': 4} 610 response = self.client.post(url, json.dumps(body), content_type='application/json', 611 HTTP_AUTHORIZATION='token {}'.format(unauthorized_token)) 612 self.assertEqual(response.status_code, 403) 613 614 def test_modified_procfile_from_build_removes_containers(self): 615 """ 616 When a new procfile is posted which removes a certain process type, deis should stop the 617 existing containers. 618 """ 619 url = '/v1/apps' 620 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 621 self.assertEqual(response.status_code, 201) 622 app_id = response.data['id'] 623 # post a new build 624 build_url = "/v1/apps/{app_id}/builds".format(**locals()) 625 body = {'image': 'autotest/example', 'sha': 'a'*40, 626 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 627 response = self.client.post(build_url, json.dumps(body), content_type='application/json', 628 HTTP_AUTHORIZATION='token {}'.format(self.token)) 629 url = "/v1/apps/{app_id}/scale".format(**locals()) 630 body = {'web': 4} 631 response = self.client.post(url, json.dumps(body), content_type='application/json', 632 HTTP_AUTHORIZATION='token {}'.format(self.token)) 633 self.assertEqual(response.status_code, 204) 634 body = {'image': 'autotest/example', 'sha': 'a'*40, 635 'procfile': json.dumps({'worker': 'node worker.js'})} 636 response = self.client.post(build_url, json.dumps(body), content_type='application/json', 637 HTTP_AUTHORIZATION='token {}'.format(self.token)) 638 self.assertEqual(response.status_code, 201) 639 self.assertEqual(Container.objects.filter(type='web').count(), 0) 640 641 def test_restart_containers(self): 642 url = '/v1/apps' 643 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 644 self.assertEqual(response.status_code, 201) 645 app_id = response.data['id'] 646 # post a new build 647 build_url = "/v1/apps/{app_id}/builds".format(**locals()) 648 body = {'image': 'autotest/example', 'sha': 'a'*40, 649 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 650 response = self.client.post(build_url, json.dumps(body), content_type='application/json', 651 HTTP_AUTHORIZATION='token {}'.format(self.token)) 652 url = "/v1/apps/{app_id}/scale".format(**locals()) 653 body = {'web': 4, 'worker': 8} 654 response = self.client.post(url, json.dumps(body), content_type='application/json', 655 HTTP_AUTHORIZATION='token {}'.format(self.token)) 656 self.assertEqual(response.status_code, 204) 657 container_set = App.objects.get(id=app_id).container_set.all() 658 # restart all containers 659 response = self.client.post('/v1/apps/{}/containers/restart'.format(app_id), 660 content_type='application/json', 661 HTTP_AUTHORIZATION='token {}'.format(self.token)) 662 self.assertEqual(response.status_code, 200) 663 self.assertEqual(len(response.data), container_set.count()) 664 # restart only the workers 665 response = self.client.post('/v1/apps/{}/containers/worker/restart'.format(app_id), 666 content_type='application/json', 667 HTTP_AUTHORIZATION='token {}'.format(self.token)) 668 self.assertEqual(response.status_code, 200) 669 self.assertEqual(len(response.data), container_set.filter(type='worker').count()) 670 # restart only web.2 671 response = self.client.post('/v1/apps/{}/containers/web/1/restart'.format(app_id), 672 content_type='application/json', 673 HTTP_AUTHORIZATION='token {}'.format(self.token)) 674 self.assertEqual(response.status_code, 200) 675 self.assertEqual(len(response.data), container_set.filter(type='web', num=1).count())