github.com/inflatablewoman/deis@v1.0.1-0.20141111034523-a4511c46a6ce/controller/api/tests/test_container.py (about)

     1  """
     2  Unit tests for the Deis api app.
     3  
     4  Run the tests with "./manage.py test api"
     5  """
     6  
     7  from __future__ import unicode_literals
     8  
     9  import json
    10  import mock
    11  import requests
    12  
    13  from django.contrib.auth.models import User
    14  from django.test import TransactionTestCase
    15  from django_fsm import TransitionNotAllowed
    16  from rest_framework.authtoken.models import Token
    17  
    18  from api.models import App, Build, Container, Release
    19  
    20  
    21  def mock_import_repository_task(*args, **kwargs):
    22      resp = requests.Response()
    23      resp.status_code = 200
    24      resp._content_consumed = True
    25      return resp
    26  
    27  
    28  class ContainerTest(TransactionTestCase):
    29      """Tests creation of containers on nodes"""
    30  
    31      fixtures = ['tests.json']
    32  
    33      def setUp(self):
    34          self.user = User.objects.get(username='autotest')
    35          self.token = Token.objects.get(user=self.user).key
    36  
    37      def test_container_state_good(self):
    38          """Test that the finite state machine transitions with a good scheduler"""
    39          url = '/v1/apps'
    40          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
    41          self.assertEqual(response.status_code, 201)
    42          app_id = response.data['id']
    43          app = App.objects.get(id=app_id)
    44          user = User.objects.get(username='autotest')
    45          build = Build.objects.create(owner=user, app=app, image="qwerty")
    46          # create an initial release
    47          release = Release.objects.create(version=2,
    48                                           owner=user,
    49                                           app=app,
    50                                           config=app.config_set.latest(),
    51                                           build=build)
    52          # create a container
    53          c = Container.objects.create(owner=user,
    54                                       app=app,
    55                                       release=release,
    56                                       type='web',
    57                                       num=1)
    58          self.assertEqual(c.state, 'initialized')
    59          # test an illegal transition
    60          self.assertRaises(TransitionNotAllowed, lambda: c.start())
    61          c.create()
    62          self.assertEqual(c.state, 'created')
    63          c.start()
    64          self.assertEqual(c.state, 'up')
    65          c.destroy()
    66          self.assertEqual(c.state, 'destroyed')
    67  
    68      def test_container_state_protected(self):
    69          """Test that you cannot directly modify the state"""
    70          url = '/v1/apps'
    71          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
    72          self.assertEqual(response.status_code, 201)
    73          app_id = response.data['id']
    74          app = App.objects.get(id=app_id)
    75          user = User.objects.get(username='autotest')
    76          build = Build.objects.create(owner=user, app=app, image="qwerty")
    77          # create an initial release
    78          release = Release.objects.create(version=2,
    79                                           owner=user,
    80                                           app=app,
    81                                           config=app.config_set.latest(),
    82                                           build=build)
    83          # create a container
    84          c = Container.objects.create(owner=user,
    85                                       app=app,
    86                                       release=release,
    87                                       type='web',
    88                                       num=1)
    89          self.assertRaises(AttributeError, lambda: setattr(c, 'state', 'up'))
    90  
    91      def test_container_api_heroku(self):
    92          url = '/v1/apps'
    93          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
    94          self.assertEqual(response.status_code, 201)
    95          app_id = response.data['id']
    96          # should start with zero
    97          url = "/v1/apps/{app_id}/containers".format(**locals())
    98          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
    99          self.assertEqual(response.status_code, 200)
   100          self.assertEqual(len(response.data['results']), 0)
   101          # post a new build
   102          url = "/v1/apps/{app_id}/builds".format(**locals())
   103          body = {'image': 'autotest/example', 'sha': 'a'*40,
   104                  'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})}
   105          response = self.client.post(url, json.dumps(body), content_type='application/json',
   106                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   107          self.assertEqual(response.status_code, 201)
   108          # scale up
   109          url = "/v1/apps/{app_id}/scale".format(**locals())
   110          body = {'web': 4, 'worker': 2}
   111          response = self.client.post(url, json.dumps(body), content_type='application/json',
   112                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   113          self.assertEqual(response.status_code, 204)
   114          url = "/v1/apps/{app_id}/containers".format(**locals())
   115          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   116          self.assertEqual(response.status_code, 200)
   117          self.assertEqual(len(response.data['results']), 6)
   118          url = "/v1/apps/{app_id}".format(**locals())
   119          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   120          self.assertEqual(response.status_code, 200)
   121          # test listing/retrieving container info
   122          url = "/v1/apps/{app_id}/containers/web".format(**locals())
   123          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   124          self.assertEqual(response.status_code, 200)
   125          self.assertEqual(len(response.data['results']), 4)
   126          num = response.data['results'][0]['num']
   127          url = "/v1/apps/{app_id}/containers/web/{num}".format(**locals())
   128          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   129          self.assertEqual(response.status_code, 200)
   130          self.assertEqual(response.data['num'], num)
   131          # scale down
   132          url = "/v1/apps/{app_id}/scale".format(**locals())
   133          body = {'web': 2, 'worker': 1}
   134          response = self.client.post(url, json.dumps(body), content_type='application/json',
   135                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   136          self.assertEqual(response.status_code, 204)
   137          url = "/v1/apps/{app_id}/containers".format(**locals())
   138          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   139          self.assertEqual(response.status_code, 200)
   140          self.assertEqual(len(response.data['results']), 3)
   141          self.assertEqual(max(c['num'] for c in response.data['results']), 2)
   142          url = "/v1/apps/{app_id}".format(**locals())
   143          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   144          self.assertEqual(response.status_code, 200)
   145          # scale down to 0
   146          url = "/v1/apps/{app_id}/scale".format(**locals())
   147          body = {'web': 0, 'worker': 0}
   148          response = self.client.post(url, json.dumps(body), content_type='application/json',
   149                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   150          self.assertEqual(response.status_code, 204)
   151          url = "/v1/apps/{app_id}/containers".format(**locals())
   152          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   153          self.assertEqual(response.status_code, 200)
   154          self.assertEqual(len(response.data['results']), 0)
   155          url = "/v1/apps/{app_id}".format(**locals())
   156          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   157          self.assertEqual(response.status_code, 200)
   158  
   159      @mock.patch('requests.post', mock_import_repository_task)
   160      def test_container_api_docker(self):
   161          url = '/v1/apps'
   162          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   163          self.assertEqual(response.status_code, 201)
   164          app_id = response.data['id']
   165          # should start with zero
   166          url = "/v1/apps/{app_id}/containers".format(**locals())
   167          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   168          self.assertEqual(response.status_code, 200)
   169          self.assertEqual(len(response.data['results']), 0)
   170          # post a new build
   171          url = "/v1/apps/{app_id}/builds".format(**locals())
   172          body = {'image': 'autotest/example', 'dockerfile': "FROM busybox\nCMD /bin/true"}
   173          response = self.client.post(url, json.dumps(body), content_type='application/json',
   174                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   175          self.assertEqual(response.status_code, 201)
   176          # scale up
   177          url = "/v1/apps/{app_id}/scale".format(**locals())
   178          body = {'cmd': 6}
   179          response = self.client.post(url, json.dumps(body), content_type='application/json',
   180                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   181          self.assertEqual(response.status_code, 204)
   182          url = "/v1/apps/{app_id}/containers".format(**locals())
   183          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   184          self.assertEqual(response.status_code, 200)
   185          self.assertEqual(len(response.data['results']), 6)
   186          url = "/v1/apps/{app_id}".format(**locals())
   187          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   188          self.assertEqual(response.status_code, 200)
   189          # test listing/retrieving container info
   190          url = "/v1/apps/{app_id}/containers/cmd".format(**locals())
   191          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   192          self.assertEqual(response.status_code, 200)
   193          self.assertEqual(len(response.data['results']), 6)
   194          # scale down
   195          url = "/v1/apps/{app_id}/scale".format(**locals())
   196          body = {'cmd': 3}
   197          response = self.client.post(url, json.dumps(body), content_type='application/json',
   198                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   199          self.assertEqual(response.status_code, 204)
   200          url = "/v1/apps/{app_id}/containers".format(**locals())
   201          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   202          self.assertEqual(response.status_code, 200)
   203          self.assertEqual(len(response.data['results']), 3)
   204          self.assertEqual(max(c['num'] for c in response.data['results']), 3)
   205          url = "/v1/apps/{app_id}".format(**locals())
   206          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   207          self.assertEqual(response.status_code, 200)
   208          # scale down to 0
   209          url = "/v1/apps/{app_id}/scale".format(**locals())
   210          body = {'cmd': 0}
   211          response = self.client.post(url, json.dumps(body), content_type='application/json',
   212                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   213          self.assertEqual(response.status_code, 204)
   214          url = "/v1/apps/{app_id}/containers".format(**locals())
   215          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   216          self.assertEqual(response.status_code, 200)
   217          self.assertEqual(len(response.data['results']), 0)
   218          url = "/v1/apps/{app_id}".format(**locals())
   219          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   220          self.assertEqual(response.status_code, 200)
   221  
   222      @mock.patch('requests.post', mock_import_repository_task)
   223      def test_container_release(self):
   224          url = '/v1/apps'
   225          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   226          self.assertEqual(response.status_code, 201)
   227          app_id = response.data['id']
   228          # should start with zero
   229          url = "/v1/apps/{app_id}/containers".format(**locals())
   230          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   231          self.assertEqual(response.status_code, 200)
   232          self.assertEqual(len(response.data['results']), 0)
   233          # post a new build
   234          url = "/v1/apps/{app_id}/builds".format(**locals())
   235          body = {'image': 'autotest/example', 'sha': 'a'*40,
   236                  'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})}
   237          response = self.client.post(url, json.dumps(body), content_type='application/json',
   238                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   239          self.assertEqual(response.status_code, 201)
   240          # scale up
   241          url = "/v1/apps/{app_id}/scale".format(**locals())
   242          body = {'web': 1}
   243          response = self.client.post(url, json.dumps(body), content_type='application/json',
   244                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   245          self.assertEqual(response.status_code, 204)
   246          url = "/v1/apps/{app_id}/containers".format(**locals())
   247          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   248          self.assertEqual(response.status_code, 200)
   249          self.assertEqual(len(response.data['results']), 1)
   250          self.assertEqual(response.data['results'][0]['release'], 'v2')
   251          # post a new build
   252          url = "/v1/apps/{app_id}/builds".format(**locals())
   253          body = {'image': 'autotest/example'}
   254          response = self.client.post(url, json.dumps(body), content_type='application/json',
   255                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   256          self.assertEqual(response.status_code, 201)
   257          self.assertEqual(response.data['image'], body['image'])
   258          url = "/v1/apps/{app_id}/containers".format(**locals())
   259          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   260          self.assertEqual(response.status_code, 200)
   261          self.assertEqual(len(response.data['results']), 1)
   262          self.assertEqual(response.data['results'][0]['release'], 'v3')
   263          # post new config
   264          url = "/v1/apps/{app_id}/config".format(**locals())
   265          body = {'values': json.dumps({'KEY': 'value'})}
   266          response = self.client.post(url, json.dumps(body), content_type='application/json',
   267                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   268          self.assertEqual(response.status_code, 201)
   269          url = "/v1/apps/{app_id}/containers".format(**locals())
   270          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   271          self.assertEqual(response.status_code, 200)
   272          self.assertEqual(len(response.data['results']), 1)
   273          self.assertEqual(response.data['results'][0]['release'], 'v4')
   274  
   275      def test_container_errors(self):
   276          url = '/v1/apps'
   277          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   278          self.assertEqual(response.status_code, 201)
   279          app_id = response.data['id']
   280          # create a release so we can scale
   281          app = App.objects.get(id=app_id)
   282          user = User.objects.get(username='autotest')
   283          build = Build.objects.create(owner=user, app=app, image="qwerty")
   284          # create an initial release
   285          Release.objects.create(version=2,
   286                                 owner=user,
   287                                 app=app,
   288                                 config=app.config_set.latest(),
   289                                 build=build)
   290          url = "/v1/apps/{app_id}/scale".format(**locals())
   291          body = {'web': 'not_an_int'}
   292          response = self.client.post(url, json.dumps(body), content_type='application/json',
   293                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   294          self.assertContains(response, 'Invalid scaling format', status_code=400)
   295          body = {'invalid': 1}
   296          response = self.client.post(url, json.dumps(body), content_type='application/json',
   297                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   298          self.assertContains(response, 'Container type invalid', status_code=400)
   299  
   300      def test_container_str(self):
   301          """Test the text representation of a container."""
   302          url = '/v1/apps'
   303          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   304          self.assertEqual(response.status_code, 201)
   305          app_id = response.data['id']
   306          # post a new build
   307          url = "/v1/apps/{app_id}/builds".format(**locals())
   308          body = {'image': 'autotest/example', 'sha': 'a'*40,
   309                  'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})}
   310          response = self.client.post(url, json.dumps(body), content_type='application/json',
   311                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   312          self.assertEqual(response.status_code, 201)
   313          # scale up
   314          url = "/v1/apps/{app_id}/scale".format(**locals())
   315          body = {'web': 4, 'worker': 2}
   316          response = self.client.post(url, json.dumps(body), content_type='application/json',
   317                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   318          self.assertEqual(response.status_code, 204)
   319          # should start with zero
   320          url = "/v1/apps/{app_id}/containers".format(**locals())
   321          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   322          self.assertEqual(response.status_code, 200)
   323          self.assertEqual(len(response.data['results']), 6)
   324          uuid = response.data['results'][0]['uuid']
   325          container = Container.objects.get(uuid=uuid)
   326          self.assertEqual(container.short_name(),
   327                           "{}.{}.{}".format(container.app, container.type, container.num))
   328          self.assertEqual(str(container),
   329                           "{}.{}.{}".format(container.app, container.type, container.num))
   330  
   331      def test_container_command_format(self):
   332          # regression test for https://github.com/deis/deis/pull/1285
   333          url = '/v1/apps'
   334          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   335          self.assertEqual(response.status_code, 201)
   336          app_id = response.data['id']
   337          # post a new build
   338          url = "/v1/apps/{app_id}/builds".format(**locals())
   339          body = {'image': 'autotest/example', 'sha': 'a'*40,
   340                  'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})}
   341          response = self.client.post(url, json.dumps(body), content_type='application/json',
   342                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   343          self.assertEqual(response.status_code, 201)
   344          # scale up
   345          url = "/v1/apps/{app_id}/scale".format(**locals())
   346          body = {'web': 1}
   347          response = self.client.post(url, json.dumps(body), content_type='application/json',
   348                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   349          self.assertEqual(response.status_code, 204)
   350          url = "/v1/apps/{app_id}/containers".format(**locals())
   351          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   352          # verify that the container._command property got formatted
   353          self.assertEqual(response.status_code, 200)
   354          self.assertEqual(len(response.data['results']), 1)
   355          uuid = response.data['results'][0]['uuid']
   356          container = Container.objects.get(uuid=uuid)
   357          self.assertNotIn('{c_type}', container._command)
   358  
   359      def test_container_scale_errors(self):
   360          url = '/v1/apps'
   361          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   362          self.assertEqual(response.status_code, 201)
   363          app_id = response.data['id']
   364          # should start with zero
   365          url = "/v1/apps/{app_id}/containers".format(**locals())
   366          response = self.client.get(url, HTTP_AUTHORIZATION='token {}'.format(self.token))
   367          self.assertEqual(response.status_code, 200)
   368          self.assertEqual(len(response.data['results']), 0)
   369          # post a new build
   370          url = "/v1/apps/{app_id}/builds".format(**locals())
   371          body = {'image': 'autotest/example', 'sha': 'a'*40,
   372                  'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})}
   373          response = self.client.post(url, json.dumps(body), content_type='application/json',
   374                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   375          self.assertEqual(response.status_code, 201)
   376          # scale to a negative number
   377          url = "/v1/apps/{app_id}/scale".format(**locals())
   378          body = {'web': -1}
   379          response = self.client.post(url, json.dumps(body), content_type='application/json',
   380                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   381          self.assertEqual(response.status_code, 400)
   382          # scale to something other than a number
   383          url = "/v1/apps/{app_id}/scale".format(**locals())
   384          body = {'web': 'one'}
   385          response = self.client.post(url, json.dumps(body), content_type='application/json',
   386                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   387          self.assertEqual(response.status_code, 400)
   388          # scale to something other than a number
   389          url = "/v1/apps/{app_id}/scale".format(**locals())
   390          body = {'web': [1]}
   391          response = self.client.post(url, json.dumps(body), content_type='application/json',
   392                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   393          self.assertEqual(response.status_code, 400)
   394          # scale up to an integer as a sanity check
   395          url = "/v1/apps/{app_id}/scale".format(**locals())
   396          body = {'web': 1}
   397          response = self.client.post(url, json.dumps(body), content_type='application/json',
   398                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   399          self.assertEqual(response.status_code, 204)
   400  
   401      def test_admin_can_manage_other_containers(self):
   402          """If a non-admin user creates a container, an administrator should be able to
   403          manage it.
   404          """
   405          user = User.objects.get(username='autotest2')
   406          token = Token.objects.get(user=user).key
   407          url = '/v1/apps'
   408          response = self.client.post(url, HTTP_AUTHORIZATION='token {}'.format(token))
   409          self.assertEqual(response.status_code, 201)
   410          app_id = response.data['id']
   411          # post a new build
   412          url = "/v1/apps/{app_id}/builds".format(**locals())
   413          body = {'image': 'autotest/example', 'sha': 'a'*40,
   414                  'procfile': json.dumps({'web': 'node server.js', 'worker': 'node worker.js'})}
   415          response = self.client.post(url, json.dumps(body), content_type='application/json',
   416                                      HTTP_AUTHORIZATION='token {}'.format(token))
   417          self.assertEqual(response.status_code, 201)
   418          # login as admin, scale up
   419          url = "/v1/apps/{app_id}/scale".format(**locals())
   420          body = {'web': 4, 'worker': 2}
   421          response = self.client.post(url, json.dumps(body), content_type='application/json',
   422                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   423          self.assertEqual(response.status_code, 204)
   424  
   425      def test_scale_without_build_should_error(self):
   426          """A user should not be able to scale processes unless a build is present."""
   427          app_id = 'autotest'
   428          url = '/v1/apps'
   429          body = {'cluster': 'autotest', 'id': app_id}
   430          response = self.client.post(url, json.dumps(body), content_type='application/json',
   431                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   432          url = '/v1/apps/{app_id}/scale'.format(**locals())
   433          body = {'web': '1'}
   434          response = self.client.post(url, json.dumps(body), content_type='application/json',
   435                                      HTTP_AUTHORIZATION='token {}'.format(self.token))
   436          self.assertEqual(response.status_code, 400)
   437          self.assertEqual(response.data, "No build associated with this release")