github.com/jiasir/deis@v1.12.2/controller/api/tests/test_container.py (about)

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