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