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)