Skip to content

Commit d496e3d

Browse files
committed
Merge pull request #738 from helgi/deis_run
ref(scheduler): rework the way deis run is handled, timeouts and more
2 parents c6ad9f6 + 75f7514 commit d496e3d

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)