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