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