Skip to content

Commit a3f24df

Browse files
committed
fix(scheduler): move _get_port to use docker functions to split image info
Fixes #734
1 parent 40752a2 commit a3f24df

9 files changed

Lines changed: 76 additions & 63 deletions

File tree

rootfs/api/models/app.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -307,21 +307,26 @@ def scale(self, user, structure): # noqa
307307

308308
def _scale_pods(self, scale_types):
309309
release = self.release_set.latest()
310+
envs = release.config.values
310311
for scale_type, replicas in scale_types.items():
312+
# only web / cmd are routable
313+
# http://docs.deis.io/en/latest/using_deis/process-types/#web-vs-cmd-process-types
314+
routable = True if scale_type in ['web', 'cmd'] else False
315+
# fetch application port and inject into ENV Vars as needed
316+
envs['PORT'] = release.get_port(routable)
317+
311318
image = release.image
312-
version = "v{}".format(release.version)
313319
kwargs = {
314320
'memory': release.config.memory,
315321
'cpu': release.config.cpu,
316322
'tags': release.config.tags,
317-
'envs': release.config.values,
318-
'version': version,
323+
'envs': envs,
324+
'version': "v{}".format(release.version),
319325
'replicas': replicas,
320326
'app_type': scale_type,
321327
'build_type': release.build.type,
322328
'healthcheck': release.config.healthcheck(),
323-
# http://docs.deis.io/en/latest/using_deis/process-types/#web-vs-cmd-process-types
324-
'routable': True if scale_type in ['web', 'cmd'] else False
329+
'routable': routable
325330
}
326331

327332
command = self._get_command(scale_type)
@@ -355,20 +360,26 @@ def deploy(self, release):
355360

356361
# deploy application to k8s. Also handles initial scaling
357362
deploys = {}
363+
envs = release.config.values
358364
for scale_type, replicas in self.structure.items():
365+
# only web / cmd are routable
366+
# http://docs.deis.io/en/latest/using_deis/process-types/#web-vs-cmd-process-types
367+
routable = True if scale_type in ['web', 'cmd'] else False
368+
# fetch application port and inject into ENV Vars as needed
369+
envs['PORT'] = release.get_port(routable)
370+
359371
deploys[scale_type] = {
360372
'memory': release.config.memory,
361373
'cpu': release.config.cpu,
362374
'tags': release.config.tags,
363-
'envs': release.config.values,
375+
'envs': envs,
364376
# only used if there is no previous RC
365377
'replicas': replicas,
366378
'version': "v{}".format(release.version),
367379
'app_type': scale_type,
368380
'build_type': release.build.type,
369381
'healthcheck': release.config.healthcheck(),
370-
# http://docs.deis.io/en/latest/using_deis/process-types/#web-vs-cmd-process-types
371-
'routable': True if scale_type in ['web', 'cmd'] else False,
382+
'routable': routable,
372383
'batches': batches
373384
}
374385

rootfs/api/models/release.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from django.conf import settings
44
from django.db import models
55

6+
import docker
7+
from docker import Client
8+
from retrying import retry
9+
610
from registry import publish_release, RegistryException
711
from api.utils import dict_diff
812
from api.models import UuidAuditedModel, log_event, DeisException
@@ -104,18 +108,51 @@ def publish(self):
104108
if ':' not in source_image:
105109
source_image = "{}:{}".format(source_image, self.build.version)
106110

107-
# gather custom login information for registry if needed
111+
# if build is source based then it was pushed into the deis registry
112+
deis_registry = bool(self.build.source_based)
113+
publish_release(source_image, self.image, deis_registry, self.get_registry_auth())
114+
115+
@retry(stop_max_attempt_number=3, wait_fixed=1000)
116+
def get_port(self, routable=False):
117+
"""
118+
Get a port from a Docker image
119+
"""
120+
port = None
121+
# Only care about port for routable application
122+
if not routable:
123+
return port
124+
125+
if self.build.type == "buildpack":
126+
msg = "Using default port 5000 for build pack image {}".format(self.image)
127+
log_event(self.app, msg)
128+
return 5000
129+
130+
# get the target repository name and tag
131+
repo, tag = docker.utils.parse_repository_tag(self.image)
132+
133+
docker_cli = Client(version="auto")
134+
docker_cli.pull(repo, tag=tag, insecure_registry=True)
135+
image_info = docker_cli.inspect_image(self.image)
136+
if 'ExposedPorts' not in image_info['Config']:
137+
return None
138+
139+
port = int(list(image_info['Config']['ExposedPorts'].keys())[0].split("/")[0])
140+
return port
141+
142+
def get_registry_auth(self):
143+
"""
144+
Gather login information for private registry if needed
145+
"""
108146
auth = None
109-
if self.config.registry.get('username', None):
147+
registry = self.config.registry
148+
if registry.get('username', None):
110149
auth = {
111-
'username': self.config.registry.get('username', None),
112-
'password': self.config.registry.get('password', None),
150+
'username': registry.get('username', None),
151+
'password': registry.get('password', None),
113152
'email': self.owner.email
114153
}
115154

116-
# if build is source based then it was pushed into the deis registry
117-
deis_registry = bool(self.build.source_based)
118-
publish_release(source_image, self.image, deis_registry, auth)
155+
return auth
119156

120157
def previous(self):
121158
"""

rootfs/api/tests/test_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def _mock_run(*args, **kwargs):
3131

3232
@requests_mock.Mocker(real_http=True, adapter=adapter)
3333
@mock.patch('api.models.release.publish_release', lambda *args: None)
34-
@mock.patch('scheduler.KubeHTTPClient._get_port', mock_port)
34+
@mock.patch('api.models.release.Release.get_port', mock_port)
3535
class AppTest(APITestCase):
3636
"""Tests creation of applications"""
3737

rootfs/api/tests/test_build.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
@requests_mock.Mocker(real_http=True, adapter=adapter)
2727
@mock.patch('api.models.release.publish_release', lambda *args: None)
28-
@mock.patch('scheduler.KubeHTTPClient._get_port', mock_port)
28+
@mock.patch('api.models.release.Release.get_port', mock_port)
2929
class BuildTest(APITransactionTestCase):
3030

3131
"""Tests build notification from build system"""

rootfs/api/tests/test_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
@requests_mock.Mocker(real_http=True, adapter=adapter)
2323
@mock.patch('api.models.release.publish_release', lambda *args: None)
24-
@mock.patch('scheduler.KubeHTTPClient._get_port', mock_port)
24+
@mock.patch('api.models.release.Release.get_port', mock_port)
2525
class ConfigTest(APITransactionTestCase):
2626

2727
"""Tests setting and updating config values"""

rootfs/api/tests/test_hooks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
@requests_mock.Mocker(real_http=True, adapter=adapter)
3737
@mock.patch('api.models.release.publish_release', lambda *args: None)
38-
@mock.patch('scheduler.KubeHTTPClient._get_port', mock_port)
38+
@mock.patch('api.models.release.Release.get_port', mock_port)
3939
class HookTest(APITransactionTestCase):
4040

4141
"""Tests API hooks used to trigger actions from external components"""

rootfs/api/tests/test_pods.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
@requests_mock.Mocker(real_http=True, adapter=adapter)
2626
@mock.patch('api.models.release.publish_release', lambda *args: None)
27-
@mock.patch('scheduler.KubeHTTPClient._get_port', mock_port)
27+
@mock.patch('api.models.release.Release.get_port', mock_port)
2828
class PodTest(APITransactionTestCase):
2929
"""Tests creation of pods on nodes"""
3030

rootfs/api/tests/test_release.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
@requests_mock.Mocker(real_http=True, adapter=adapter)
2525
@mock.patch('api.models.release.publish_release', lambda *args: None)
26-
@mock.patch('scheduler.KubeHTTPClient._get_port', mock_port)
26+
@mock.patch('api.models.release.Release.get_port', mock_port)
2727
class ReleaseTest(APITransactionTestCase):
2828

2929
"""Tests push notification from build system"""

rootfs/scheduler/__init__.py

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,10 @@
88
import base64
99

1010
from django.conf import settings
11-
from docker import Client
1211
from .states import JobState
1312
import requests
1413
from requests_toolbelt import user_agent
1514
from .utils import dict_merge
16-
from retrying import retry
1715

1816
from deis import __version__ as deis_version
1917

@@ -343,22 +341,9 @@ def __init__(self):
343341
def deploy(self, namespace, name, image, command, **kwargs): # noqa
344342
logger.debug('deploy {}, img {}, params {}, cmd "{}"'.format(name, image, kwargs, command))
345343
app_type = kwargs.get('app_type')
346-
build_type = kwargs.get('build_type')
347344
routable = kwargs.get('routable', False)
348-
port = None
349-
350-
try:
351-
if routable:
352-
if build_type == "buildpack":
353-
logger.debug("Using default port 5000 for build pack app {}".format(name))
354-
port = 5000
355-
else:
356-
port = self._get_port(image)
357-
if port is None:
358-
raise Exception("Expose a port or make the app non routable by changing"
359-
" the process type")
360-
except Exception as e:
361-
raise KubeException('{} (scheduler::deploy): {}'.format(name, e))
345+
envs = kwargs.get('envs', {})
346+
port = envs.get('PORT', None)
362347

363348
# Fetch old RC and create the new one for a release
364349
old_rc = self._get_old_rc(namespace, app_type)
@@ -686,7 +671,7 @@ def _set_container(self, namespace, data, **kwargs): # noqa
686671
if kwargs.get('healthcheck', None):
687672
self._healthcheck(namespace, data, kwargs.get('routable'), **kwargs['healthcheck'])
688673
else:
689-
self._default_readiness_probe(data, kwargs.get('build_type'), kwargs.get('image'))
674+
self._default_readiness_probe(data, kwargs.get('build_type'), kwargs.get('port', None))
690675

691676
def resolve_state(self, pod):
692677
# See "Pod Phase" at http://kubernetes.io/v1.1/docs/user-guide/pod-states.html
@@ -722,19 +707,6 @@ def resolve_state(self, pod):
722707

723708
return states.get(pod_state, pod_state)
724709

725-
@retry(stop_max_attempt_number=3, wait_fixed=1000)
726-
def _get_port(self, image):
727-
# try thrice to find the port before raising exception as docker-py is flaky
728-
repo = image.split(":")
729-
# image already includes the tag, so we split it out here
730-
docker_cli = Client(version="auto")
731-
docker_cli.pull(repo[0]+":"+repo[1], tag=repo[2], insecure_registry=True)
732-
image_info = docker_cli.inspect_image(image)
733-
if 'ExposedPorts' not in image_info['Config']:
734-
return None
735-
port = int(list(image_info['Config']['ExposedPorts'].keys())[0].split("/")[0])
736-
return port
737-
738710
def _api(self, tmpl, *args):
739711
"""Return a fully-qualified Kubernetes API URL from a string template with args."""
740712
url = "/api/{}".format(self.apiversion) + tmpl.format(*args)
@@ -1165,12 +1137,12 @@ def _healthcheck(self, namespace, container, routable=False, path='/', port=5000
11651137
# Update only the application container with the health check
11661138
container.update(healthcheck)
11671139

1168-
def _default_readiness_probe(self, container, build_type, image):
1140+
def _default_readiness_probe(self, container, build_type, port=None):
11691141
# Update only the application container with the health check
11701142
if build_type == "buildpack":
11711143
container.update(self._default_buildpack_readiness_probe())
1172-
else:
1173-
container.update(self._default_dockerapp_readiness_probe(image))
1144+
elif port:
1145+
container.update(self._default_dockerapp_readiness_probe(port))
11741146

11751147
'''
11761148
Applies exec readiness probe to the slugrunner container.
@@ -1213,15 +1185,8 @@ def _default_buildpack_readiness_probe(self, delay=30, timeout=5, period_seconds
12131185
Applies tcp socket readiness probe to the docker app container only if some port is exposed
12141186
by the docker image.
12151187
'''
1216-
def _default_dockerapp_readiness_probe(self, image, delay=5, timeout=5, period_seconds=5,
1188+
def _default_dockerapp_readiness_probe(self, port, delay=5, timeout=5, period_seconds=5,
12171189
success_threshold=1, failure_threshold=1):
1218-
try:
1219-
port = self._get_port(image)
1220-
if port is None:
1221-
return None
1222-
except Exception:
1223-
return None
1224-
12251190
readinessprobe = {
12261191
'readinessProbe': {
12271192
# an exec probe

0 commit comments

Comments
 (0)