github.com/misfo/deis@v1.0.1-0.20141111224634-e0eee0392b8a/controller/api/tests/test_container.py (about) 1 """ 2 Unit tests for the Deis api app. 3 4 Run the tests with "./manage.py test api" 5 """ 6 7 from __future__ import unicode_literals 8 9 import json 10 import mock 11 import requests 12 13 from django.contrib.auth.models import User 14 from django.test import TransactionTestCase 15 from django_fsm 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 body = {'web': 4, 'worker': 2} 111 response = self.client.post(url, json.dumps(body), content_type='application/json', 112 HTTP_AUTHORIZATION='token {}'.format(self.token)) 113 self.assertEqual(response.status_code, 204) 114 url = "/v1/apps/{app_id}/containers".format(**locals()) 115 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 116 self.assertEqual(response.status_code, 200) 117 self.assertEqual(len(response.data['results']), 6) 118 url = "/v1/apps/{app_id}".format(**locals()) 119 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 120 self.assertEqual(response.status_code, 200) 121 # test listing/retrieving container info 122 url = "/v1/apps/{app_id}/containers/web".format(**locals()) 123 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 124 self.assertEqual(response.status_code, 200) 125 self.assertEqual(len(response.data['results']), 4) 126 num = response.data['results'][0]['num'] 127 url = "/v1/apps/{app_id}/containers/web/{num}".format(**locals()) 128 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 129 self.assertEqual(response.status_code, 200) 130 self.assertEqual(response.data['num'], num) 131 # scale down 132 url = "/v1/apps/{app_id}/scale".format(**locals()) 133 body = {'web': 2, 'worker': 1} 134 response = self.client.post(url, json.dumps(body), content_type='application/json', 135 HTTP_AUTHORIZATION='token {}'.format(self.token)) 136 self.assertEqual(response.status_code, 204) 137 url = "/v1/apps/{app_id}/containers".format(**locals()) 138 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 139 self.assertEqual(response.status_code, 200) 140 self.assertEqual(len(response.data['results']), 3) 141 self.assertEqual(max(c['num'] for c in response.data['results']), 2) 142 url = "/v1/apps/{app_id}".format(**locals()) 143 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 144 self.assertEqual(response.status_code, 200) 145 # scale down to 0 146 url = "/v1/apps/{app_id}/scale".format(**locals()) 147 body = {'web': 0, 'worker': 0} 148 response = self.client.post(url, json.dumps(body), content_type='application/json', 149 HTTP_AUTHORIZATION='token {}'.format(self.token)) 150 self.assertEqual(response.status_code, 204) 151 url = "/v1/apps/{app_id}/containers".format(**locals()) 152 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 153 self.assertEqual(response.status_code, 200) 154 self.assertEqual(len(response.data['results']), 0) 155 url = "/v1/apps/{app_id}".format(**locals()) 156 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 157 self.assertEqual(response.status_code, 200) 158 159 @mock.patch('requests.post', mock_import_repository_task) 160 def test_container_api_docker(self): 161 url = '/v1/apps' 162 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 163 self.assertEqual(response.status_code, 201) 164 app_id = response.data['id'] 165 # should start with zero 166 url = "/v1/apps/{app_id}/containers".format(**locals()) 167 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 168 self.assertEqual(response.status_code, 200) 169 self.assertEqual(len(response.data['results']), 0) 170 # post a new build 171 url = "/v1/apps/{app_id}/builds".format(**locals()) 172 body = {'image': 'autotest/example', 'dockerfile': "FROM busybox\nCMD /bin/true"} 173 response = self.client.post(url, json.dumps(body), content_type='application/json', 174 HTTP_AUTHORIZATION='token {}'.format(self.token)) 175 self.assertEqual(response.status_code, 201) 176 # scale up 177 url = "/v1/apps/{app_id}/scale".format(**locals()) 178 body = {'cmd': 6} 179 response = self.client.post(url, json.dumps(body), content_type='application/json', 180 HTTP_AUTHORIZATION='token {}'.format(self.token)) 181 self.assertEqual(response.status_code, 204) 182 url = "/v1/apps/{app_id}/containers".format(**locals()) 183 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 184 self.assertEqual(response.status_code, 200) 185 self.assertEqual(len(response.data['results']), 6) 186 url = "/v1/apps/{app_id}".format(**locals()) 187 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 188 self.assertEqual(response.status_code, 200) 189 # test listing/retrieving container info 190 url = "/v1/apps/{app_id}/containers/cmd".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 # scale down 195 url = "/v1/apps/{app_id}/scale".format(**locals()) 196 body = {'cmd': 3} 197 response = self.client.post(url, json.dumps(body), content_type='application/json', 198 HTTP_AUTHORIZATION='token {}'.format(self.token)) 199 self.assertEqual(response.status_code, 204) 200 url = "/v1/apps/{app_id}/containers".format(**locals()) 201 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 202 self.assertEqual(response.status_code, 200) 203 self.assertEqual(len(response.data['results']), 3) 204 self.assertEqual(max(c['num'] for c in response.data['results']), 3) 205 url = "/v1/apps/{app_id}".format(**locals()) 206 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 207 self.assertEqual(response.status_code, 200) 208 # scale down to 0 209 url = "/v1/apps/{app_id}/scale".format(**locals()) 210 body = {'cmd': 0} 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']), 0) 218 url = "/v1/apps/{app_id}".format(**locals()) 219 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 220 self.assertEqual(response.status_code, 200) 221 222 @mock.patch('requests.post', mock_import_repository_task) 223 def test_container_release(self): 224 url = '/v1/apps' 225 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 226 self.assertEqual(response.status_code, 201) 227 app_id = response.data['id'] 228 # should start with zero 229 url = "/v1/apps/{app_id}/containers".format(**locals()) 230 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 231 self.assertEqual(response.status_code, 200) 232 self.assertEqual(len(response.data['results']), 0) 233 # post a new build 234 url = "/v1/apps/{app_id}/builds".format(**locals()) 235 body = {'image': 'autotest/example', 'sha': 'a'*40, 236 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 237 response = self.client.post(url, json.dumps(body), content_type='application/json', 238 HTTP_AUTHORIZATION='token {}'.format(self.token)) 239 self.assertEqual(response.status_code, 201) 240 # scale up 241 url = "/v1/apps/{app_id}/scale".format(**locals()) 242 body = {'web': 1} 243 response = self.client.post(url, json.dumps(body), content_type='application/json', 244 HTTP_AUTHORIZATION='token {}'.format(self.token)) 245 self.assertEqual(response.status_code, 204) 246 url = "/v1/apps/{app_id}/containers".format(**locals()) 247 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 248 self.assertEqual(response.status_code, 200) 249 self.assertEqual(len(response.data['results']), 1) 250 self.assertEqual(response.data['results'][0]['release'], 'v2') 251 # post a new build 252 url = "/v1/apps/{app_id}/builds".format(**locals()) 253 body = {'image': 'autotest/example'} 254 response = self.client.post(url, json.dumps(body), content_type='application/json', 255 HTTP_AUTHORIZATION='token {}'.format(self.token)) 256 self.assertEqual(response.status_code, 201) 257 self.assertEqual(response.data['image'], body['image']) 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'], 'v3') 263 # post new config 264 url = "/v1/apps/{app_id}/config".format(**locals()) 265 body = {'values': json.dumps({'KEY': 'value'})} 266 response = self.client.post(url, json.dumps(body), content_type='application/json', 267 HTTP_AUTHORIZATION='token {}'.format(self.token)) 268 self.assertEqual(response.status_code, 201) 269 url = "/v1/apps/{app_id}/containers".format(**locals()) 270 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 271 self.assertEqual(response.status_code, 200) 272 self.assertEqual(len(response.data['results']), 1) 273 self.assertEqual(response.data['results'][0]['release'], 'v4') 274 275 def test_container_errors(self): 276 url = '/v1/apps' 277 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 278 self.assertEqual(response.status_code, 201) 279 app_id = response.data['id'] 280 # create a release so we can scale 281 app = App.objects.get(id=app_id) 282 user = User.objects.get(username='autotest') 283 build = Build.objects.create(owner=user, app=app, image="qwerty") 284 # create an initial release 285 Release.objects.create(version=2, 286 owner=user, 287 app=app, 288 config=app.config_set.latest(), 289 build=build) 290 url = "/v1/apps/{app_id}/scale".format(**locals()) 291 body = {'web': 'not_an_int'} 292 response = self.client.post(url, json.dumps(body), content_type='application/json', 293 HTTP_AUTHORIZATION='token {}'.format(self.token)) 294 self.assertContains(response, 'Invalid scaling format', status_code=400) 295 body = {'invalid': 1} 296 response = self.client.post(url, json.dumps(body), content_type='application/json', 297 HTTP_AUTHORIZATION='token {}'.format(self.token)) 298 self.assertContains(response, 'Container type invalid', status_code=400) 299 300 def test_container_str(self): 301 """Test the text representation of a container.""" 302 url = '/v1/apps' 303 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 304 self.assertEqual(response.status_code, 201) 305 app_id = response.data['id'] 306 # post a new build 307 url = "/v1/apps/{app_id}/builds".format(**locals()) 308 body = {'image': 'autotest/example', 'sha': 'a'*40, 309 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 310 response = self.client.post(url, json.dumps(body), content_type='application/json', 311 HTTP_AUTHORIZATION='token {}'.format(self.token)) 312 self.assertEqual(response.status_code, 201) 313 # scale up 314 url = "/v1/apps/{app_id}/scale".format(**locals()) 315 body = {'web': 4, 'worker': 2} 316 response = self.client.post(url, json.dumps(body), content_type='application/json', 317 HTTP_AUTHORIZATION='token {}'.format(self.token)) 318 self.assertEqual(response.status_code, 204) 319 # should start with zero 320 url = "/v1/apps/{app_id}/containers".format(**locals()) 321 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 322 self.assertEqual(response.status_code, 200) 323 self.assertEqual(len(response.data['results']), 6) 324 uuid = response.data['results'][0]['uuid'] 325 container = Container.objects.get(uuid=uuid) 326 self.assertEqual(container.short_name(), 327 "{}.{}.{}".format(container.app, container.type, container.num)) 328 self.assertEqual(str(container), 329 "{}.{}.{}".format(container.app, container.type, container.num)) 330 331 def test_container_command_format(self): 332 # regression test for https://github.com/deis/deis/pull/1285 333 url = '/v1/apps' 334 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 335 self.assertEqual(response.status_code, 201) 336 app_id = response.data['id'] 337 # post a new build 338 url = "/v1/apps/{app_id}/builds".format(**locals()) 339 body = {'image': 'autotest/example', 'sha': 'a'*40, 340 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 341 response = self.client.post(url, json.dumps(body), content_type='application/json', 342 HTTP_AUTHORIZATION='token {}'.format(self.token)) 343 self.assertEqual(response.status_code, 201) 344 # scale up 345 url = "/v1/apps/{app_id}/scale".format(**locals()) 346 body = {'web': 1} 347 response = self.client.post(url, json.dumps(body), content_type='application/json', 348 HTTP_AUTHORIZATION='token {}'.format(self.token)) 349 self.assertEqual(response.status_code, 204) 350 url = "/v1/apps/{app_id}/containers".format(**locals()) 351 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 352 # verify that the container._command property got formatted 353 self.assertEqual(response.status_code, 200) 354 self.assertEqual(len(response.data['results']), 1) 355 uuid = response.data['results'][0]['uuid'] 356 container = Container.objects.get(uuid=uuid) 357 self.assertNotIn('{c_type}', container._command) 358 359 def test_container_scale_errors(self): 360 url = '/v1/apps' 361 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 362 self.assertEqual(response.status_code, 201) 363 app_id = response.data['id'] 364 # should start with zero 365 url = "/v1/apps/{app_id}/containers".format(**locals()) 366 response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token)) 367 self.assertEqual(response.status_code, 200) 368 self.assertEqual(len(response.data['results']), 0) 369 # post a new build 370 url = "/v1/apps/{app_id}/builds".format(**locals()) 371 body = {'image': 'autotest/example', 'sha': 'a'*40, 372 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 373 response = self.client.post(url, json.dumps(body), content_type='application/json', 374 HTTP_AUTHORIZATION='token {}'.format(self.token)) 375 self.assertEqual(response.status_code, 201) 376 # scale to a negative number 377 url = "/v1/apps/{app_id}/scale".format(**locals()) 378 body = {'web': -1} 379 response = self.client.post(url, json.dumps(body), content_type='application/json', 380 HTTP_AUTHORIZATION='token {}'.format(self.token)) 381 self.assertEqual(response.status_code, 400) 382 # scale to something other than a number 383 url = "/v1/apps/{app_id}/scale".format(**locals()) 384 body = {'web': 'one'} 385 response = self.client.post(url, json.dumps(body), content_type='application/json', 386 HTTP_AUTHORIZATION='token {}'.format(self.token)) 387 self.assertEqual(response.status_code, 400) 388 # scale to something other than a number 389 url = "/v1/apps/{app_id}/scale".format(**locals()) 390 body = {'web': [1]} 391 response = self.client.post(url, json.dumps(body), content_type='application/json', 392 HTTP_AUTHORIZATION='token {}'.format(self.token)) 393 self.assertEqual(response.status_code, 400) 394 # scale up to an integer as a sanity check 395 url = "/v1/apps/{app_id}/scale".format(**locals()) 396 body = {'web': 1} 397 response = self.client.post(url, json.dumps(body), content_type='application/json', 398 HTTP_AUTHORIZATION='token {}'.format(self.token)) 399 self.assertEqual(response.status_code, 204) 400 401 def test_admin_can_manage_other_containers(self): 402 """If a non-admin user creates a container, an administrator should be able to 403 manage it. 404 """ 405 user = User.objects.get(username='autotest2') 406 token = Token.objects.get(user=user).key 407 url = '/v1/apps' 408 response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(token)) 409 self.assertEqual(response.status_code, 201) 410 app_id = response.data['id'] 411 # post a new build 412 url = "/v1/apps/{app_id}/builds".format(**locals()) 413 body = {'image': 'autotest/example', 'sha': 'a'*40, 414 'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})} 415 response = self.client.post(url, json.dumps(body), content_type='application/json', 416 HTTP_AUTHORIZATION='token {}'.format(token)) 417 self.assertEqual(response.status_code, 201) 418 # login as admin, scale up 419 url = "/v1/apps/{app_id}/scale".format(**locals()) 420 body = {'web': 4, 'worker': 2} 421 response = self.client.post(url, json.dumps(body), content_type='application/json', 422 HTTP_AUTHORIZATION='token {}'.format(self.token)) 423 self.assertEqual(response.status_code, 204) 424 425 def test_scale_without_build_should_error(self): 426 """A user should not be able to scale processes unless a build is present.""" 427 app_id = 'autotest' 428 url = '/v1/apps' 429 body = {'cluster': 'autotest', 'id': app_id} 430 response = self.client.post(url, json.dumps(body), content_type='application/json', 431 HTTP_AUTHORIZATION='token {}'.format(self.token)) 432 url = '/v1/apps/{app_id}/scale'.format(**locals()) 433 body = {'web': '1'} 434 response = self.client.post(url, json.dumps(body), content_type='application/json', 435 HTTP_AUTHORIZATION='token {}'.format(self.token)) 436 self.assertEqual(response.status_code, 400) 437 self.assertEqual(response.data, "No build associated with this release")