Skip to content

Commit 75f7514

Browse files
committed
ref(scheduler): rework the way deis run is handled, timeouts and more
Use the same "is the pod ready" function as normal deployments, also give deis run 20 mins max (arbitrary but is somewhat what the gunicorn worker can do) This also the API to return exit_code instead of the cryptic rc value On failure the whole Pod manifest is no longer returned. This change affects how we do tests here but minimally Mocking was improved to help move a Pod from Pending to Running and if it is a deis run Pod (not attached to RC) then it also goes to Succeeded state. This helps with test coverage but is not the full Pod state transition route
1 parent c6ad9f6 commit 75f7514

7 files changed

Lines changed: 299 additions & 112 deletions

File tree

rootfs/api/models/app.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ def _get_command(self, container_type):
123123
# handle special case for Dockerfile deployments
124124
return '' if container_type == 'cmd' else 'start {}'.format(container_type)
125125

126+
def _get_command_run(self, command):
127+
# SECURITY: shell-escape user input
128+
command = command.replace("'", "'\\''")
129+
130+
# if this is a procfile-based app, switch the entrypoint to slugrunner's default
131+
# FIXME: remove slugrunner's hardcoded entrypoint
132+
release = self.release_set.latest()
133+
if release.build.procfile and \
134+
release.build.sha and not \
135+
release.build.dockerfile:
136+
entrypoint = '/runner/init'
137+
command = "'{}'".format(command)
138+
else:
139+
entrypoint = '/bin/bash'
140+
command = "-c '{}'".format(command)
141+
142+
return entrypoint, command
143+
126144
def log(self, message, level=logging.INFO):
127145
"""Logs a message in the context of this application.
128146
@@ -555,35 +573,22 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits):
555573
raise DeisException('No build associated with this release to run this command')
556574

557575
# TODO: add support for interactive shell
558-
# SECURITY: shell-escape user input
559-
command = command.replace("'", "'\\''")
560-
561-
# if this is a procfile-based app, switch the entrypoint to slugrunner's default
562-
# FIXME: remove slugrunner's hardcoded entrypoint
563-
if release.build.procfile and \
564-
release.build.sha and not \
565-
release.build.dockerfile:
566-
entrypoint = '/runner/init'
567-
command = "'{}'".format(command)
568-
else:
569-
entrypoint = '/bin/bash'
570-
command = "-c '{}'".format(command)
576+
entrypoint, command = self._get_command_run(command)
571577

572578
name = self._get_job_id('run') + '-' + pod_name()
573-
574-
msg = "{} on {} runs '{}'".format(user.username, name, command)
575-
log_event(self, msg)
579+
log_event(self, "{} on {} runs '{}'".format(user.username, name, command))
576580

577581
kwargs = {
578582
'memory': release.config.memory,
579583
'cpu': release.config.cpu,
580584
'tags': release.config.tags,
581585
'envs': release.config.values,
582586
'version': "v{}".format(release.version),
587+
'build_type': release.build.type,
583588
}
584589

585590
try:
586-
rc, output = self._scheduler.run(
591+
exit_code, output = self._scheduler.run(
587592
self.id,
588593
name,
589594
release.image,
@@ -592,7 +597,7 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits):
592597
**kwargs
593598
)
594599

595-
return rc, output
600+
return exit_code, output
596601
except Exception as e:
597602
err = '{} (run): {}'.format(name, e)
598603
log_event(self, err, logging.ERROR)
@@ -615,7 +620,7 @@ def list_pods(self, *args, **kwargs):
615620
if p['metadata']['labels']['type'] == 'run':
616621
continue
617622

618-
state = self._scheduler.resolve_state(p)
623+
state = self._scheduler._pod_state(p).name
619624

620625
# follows kubelete convention - these are hidden unless show-all is set
621626
if state in ['down', 'crashed']:

rootfs/api/tests/test_pods.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,8 @@ def test_run_command_good(self, mock_requests):
578578
body = {'command': 'echo hi'}
579579
response = self.client.post(url, body)
580580
self.assertEqual(response.status_code, 200, response.data)
581-
entrypoint = json.loads(response.data['output'])['spec']['containers'][0]['command'][0]
581+
data = App.objects.get(id=app_id)
582+
entrypoint, _ = data._get_command_run('echo hi')
582583
self.assertEqual(entrypoint, '/bin/bash')
583584

584585
# docker image workflow
@@ -589,7 +590,8 @@ def test_run_command_good(self, mock_requests):
589590
body = {'command': 'echo hi'}
590591
response = self.client.post(url, body)
591592
self.assertEqual(response.status_code, 200, response.data)
592-
entrypoint = json.loads(response.data['output'])['spec']['containers'][0]['command'][0]
593+
data = App.objects.get(id=app_id)
594+
entrypoint, _ = data._get_command_run('echo hi')
593595
self.assertEqual(entrypoint, '/bin/bash')
594596

595597
# procfile workflow
@@ -599,12 +601,13 @@ def test_run_command_good(self, mock_requests):
599601
body = {'command': 'echo hi'}
600602
response = self.client.post(url, body)
601603
self.assertEqual(response.status_code, 200, response.data)
602-
entrypoint = json.loads(response.data['output'])['spec']['containers'][0]['command'][0]
604+
data = App.objects.get(id=app_id)
605+
entrypoint, _ = data._get_command_run('echo hi')
603606
self.assertEqual(entrypoint, '/runner/init')
604607

605608
def test_run_not_fail_on_debug(self, mock_requests):
606609
"""
607-
do a run with DEBUG on - https://github.com/deis/controller/issues/583
610+
do a run with DEIS_DEBUG on - https://github.com/deis/controller/issues/583
608611
"""
609612
env = EnvironmentVarGuard()
610613
env['DEIS_DEBUG'] = 'true'
@@ -642,7 +645,8 @@ def test_run_not_fail_on_debug(self, mock_requests):
642645
body = {'command': 'echo hi'}
643646
response = self.client.post(url, body)
644647
self.assertEqual(response.status_code, 200, response.data)
645-
entrypoint = json.loads(response.data['output'])['spec']['containers'][0]['command'][0]
648+
data = App.objects.get(id=app_id)
649+
entrypoint, _ = data._get_command_run('echo hi')
646650
self.assertEqual(entrypoint, '/bin/bash')
647651

648652
def test_scaling_does_not_add_run_proctypes_to_structure(self, mock_requests):
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import unittest
2+
from scheduler.states import PodState
3+
4+
5+
class TestSchedulerStates(unittest.TestCase):
6+
"""Test Scheduler States OrderedEnum"""
7+
8+
def test_gt_comparison(self):
9+
self.assertTrue(PodState.up > PodState.starting)
10+
self.assertFalse(PodState.starting > PodState.up)
11+
with self.assertRaises(TypeError):
12+
self.assertTrue(PodState.up > 'starting')
13+
14+
def test_ge_comparison(self):
15+
self.assertTrue(PodState.up >= PodState.starting)
16+
self.assertFalse(PodState.starting >= PodState.up)
17+
with self.assertRaises(TypeError):
18+
self.assertTrue(PodState.up >= 'starting')
19+
20+
def test_lt_comparison(self):
21+
self.assertFalse(PodState.up < PodState.starting)
22+
self.assertTrue(PodState.starting < PodState.up)
23+
with self.assertRaises(TypeError):
24+
self.assertTrue(PodState.up < 'crashed')
25+
26+
def test_le_comparison(self):
27+
self.assertFalse(PodState.up <= PodState.starting)
28+
self.assertTrue(PodState.starting <= PodState.up)
29+
with self.assertRaises(TypeError):
30+
self.assertTrue(PodState.up <= 'crashed')

rootfs/deis/testing.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
import string
33
from deis.settings import * # noqa
44

5+
# A boolean that turns on/off debug mode.
6+
# https://docs.djangoproject.com/en/1.9/ref/settings/#debug
7+
DEBUG = True
8+
9+
# If set to True, Django's normal exception handling of view functions
10+
# will be suppressed, and exceptions will propagate upwards
11+
# https://docs.djangoproject.com/en/1.9/ref/settings/#debug-propagate-exceptions
12+
DEBUG_PROPAGATE_EXCEPTIONS = True
13+
514
# scheduler for testing
615
SCHEDULER_MODULE = 'scheduler.mock'
716
SCHEDULER_URL = 'http://test-scheduler.example.com'

0 commit comments

Comments
 (0)