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())