Skip to content

Commit e1ccc73

Browse files
committed
fix(proctypes): update service after all the proctypes are deployed
1 parent 9ced97c commit e1ccc73

4 files changed

Lines changed: 87 additions & 58 deletions

File tree

rootfs/api/models/app.py

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ def deploy(self, release, force_deploy=False):
469469
raise DeisException('No build associated with this release')
470470

471471
app_settings = self.appsettings_set.latest()
472+
service_annotations = {'maintenance': app_settings.maintenance}
472473

473474
# use create to make sure minimum resources are created
474475
self.create()
@@ -518,7 +519,6 @@ def deploy(self, release, force_deploy=False):
518519
'build_type': release.build.type,
519520
'healthcheck': healthcheck,
520521
'routable': routable,
521-
'service_annotations': {'maintenance': app_settings.maintenance},
522522
'deploy_batches': batches,
523523
'deploy_timeout': deploy_timeout,
524524
'deployment_history_limit': deployment_history,
@@ -529,18 +529,7 @@ def deploy(self, release, force_deploy=False):
529529
deploys = OrderedDict(sorted(deploys.items(), key=lambda d: d[1].get('routable')))
530530

531531
# Check if any proc type has a Deployment in progress
532-
for scale_type, kwargs in deploys.items():
533-
if force_deploy:
534-
continue
535-
536-
# Is there an existing deployment in progress?
537-
name = self._get_job_id(scale_type)
538-
in_progress, deploy_okay = self._scheduler.deployment_in_progress(
539-
self.id, name, deploy_timeout, batches, replicas, tags
540-
)
541-
# throw a 409 if things are in progress but we do not want to let through the deploy
542-
if in_progress and not deploy_okay:
543-
raise AlreadyExists('Deployment for {} is already in progress'.format(name))
532+
self._check_deployment_in_progress(deploys, force_deploy)
544533

545534
try:
546535
# create the application config in k8s (secret in this case) for all deploy objects
@@ -565,15 +554,37 @@ def deploy(self, release, force_deploy=False):
565554
self.log(err, logging.ERROR)
566555
raise ServiceUnavailable(err) from e
567556

568-
# Wait until application is available in the router
569-
# Only run when there is no previous build / release
570-
old = release.previous()
571-
if old is None or old.build is None:
572-
self.verify_application_health(**kwargs)
557+
app_type = 'web' if 'web' in deploys else 'cmd' if 'cmd' in deploys else None
558+
# Make sure the application is routable and uses the correct port Done after the fact to
559+
# let initial deploy settle before routing traffic to the application
560+
if deploys and app_type:
561+
routable = deploys[app_type].get('routable')
562+
port = deploys[app_type].get('envs', {}).get('PORT', None)
563+
self._update_application_service(self.id, app_type, port, routable, service_annotations) # noqa
564+
565+
# Wait until application is available in the router
566+
# Only run when there is no previous build / release
567+
old = release.previous()
568+
if old is None or old.build is None:
569+
self.verify_application_health(**deploys[app_type])
573570

574571
# cleanup old release objects from kubernetes
575572
release.cleanup_old()
576573

574+
def _check_deployment_in_progress(self, deploys, force_deploy=False):
575+
if force_deploy:
576+
return
577+
for scale_type, kwargs in deploys.items():
578+
# Is there an existing deployment in progress?
579+
name = self._get_job_id(scale_type)
580+
in_progress, deploy_okay = self._scheduler.deployment_in_progress(
581+
self.id, name, kwargs.get("deploy_timeout"), kwargs.get("deploy_batches"),
582+
kwargs.get("replicas"), kwargs.get("tags")
583+
)
584+
# throw a 409 if things are in progress but we do not want to let through the deploy
585+
if in_progress and not deploy_okay:
586+
raise AlreadyExists('Deployment for {} is already in progress'.format(name))
587+
577588
def _default_structure(self, release):
578589
"""Scale to default structure based on release type"""
579590
# if there is no SHA, assume a docker image is being promoted
@@ -869,3 +880,35 @@ def routable(self, routable):
869880
except KubeException as e:
870881
self._scheduler.update_service(self.id, self.id, data=old_service)
871882
raise ServiceUnavailable(str(e)) from e
883+
884+
def _update_application_service(self, namespace, app_type, port, routable=False, annotations={}): # noqa
885+
"""Update application service with all the various required information"""
886+
service = self._fetch_service_config(namespace)
887+
old_service = service.copy() # in case anything fails for rollback
888+
889+
try:
890+
# Update service information
891+
for key, value in annotations.items():
892+
service['metadata']['annotations']['router.deis.io/%s' % key] = str(value)
893+
if routable:
894+
service['metadata']['labels']['router.deis.io/routable'] = 'true'
895+
else:
896+
# delete the annotation
897+
service['metadata']['labels'].pop('router.deis.io/routable', None)
898+
899+
# Set app type if there is not one available
900+
if 'type' not in service['spec']['selector']:
901+
service['spec']['selector']['type'] = app_type
902+
903+
# Find if target port exists already, update / create as required
904+
if routable:
905+
for pos, item in enumerate(service['spec']['ports']):
906+
if item['port'] == 80 and port != item['targetPort']:
907+
# port 80 is the only one we care about right now
908+
service['spec']['ports'][pos]['targetPort'] = int(port)
909+
910+
self._scheduler.update_service(namespace, namespace, data=service)
911+
except Exception as e:
912+
# Fix service to old port and app type
913+
self._scheduler.update_service(namespace, namespace, data=old_service)
914+
raise KubeException(str(e)) from e

rootfs/api/tests/test_app.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,16 @@ def test_list_ordering(self, mock_requests):
498498
self.assertEqual(apps[2]['id'], 'tango')
499499
self.assertEqual(apps[3]['id'], 'zulu')
500500

501+
def test_app_service_metadata(self, mock_requests):
502+
"""
503+
Test that application service has annotations and labels in the metadata
504+
"""
505+
app_id = self.create_app()
506+
app = App.objects.get(id=app_id)
507+
svc = app._fetch_service_config(app_id)
508+
self.assertIn('labels', svc['metadata'])
509+
self.assertIn('annotations', svc['metadata'])
510+
501511

502512
FAKE_LOG_DATA = """
503513
2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5

rootfs/api/tests/test_build.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,22 @@ def test_build_default_containers(self, mock_requests):
196196
# pod name is auto generated so use regex
197197
self.assertRegex(container['name'], app_id + '-web-[0-9]{8,10}-[a-z0-9]{5}')
198198

199+
# start with a new app
200+
app_id = self.create_app()
201+
# post a new build with procfile and no routable type
202+
203+
url = "/v2/apps/{app_id}/builds".format(**locals())
204+
body = {
205+
'image': 'autotest/example',
206+
'sha': 'a'*40,
207+
'procfile': json.dumps({
208+
'rake': 'node server.js',
209+
'worker': 'node worker.js'
210+
})
211+
}
212+
response = self.client.post(url, body)
213+
self.assertEqual(response.status_code, 201, response.data)
214+
199215
def test_build_str(self, mock_requests):
200216
"""Test the text representation of a build."""
201217
app_id = self.create_app()

rootfs/scheduler/__init__.py

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,6 @@ def deploy(self, namespace, name, image, entrypoint, command, **kwargs): # noqa
8888
self.deploy_timeout = kwargs.get('deploy_timeout')
8989
app_type = kwargs.get('app_type')
9090
version = kwargs.get('version')
91-
routable = kwargs.get('routable', False)
92-
service_annotations = kwargs.get('service_annotations', {})
93-
port = kwargs.get('envs', {}).get('PORT', None)
9491

9592
# If an RC already exists then stop processing of the deploy
9693
try:
@@ -141,11 +138,6 @@ def deploy(self, namespace, name, image, entrypoint, command, **kwargs): # noqa
141138
"Additional information:\n{}".format(version, namespace, app_type, str(e))
142139
) from e
143140

144-
# Make sure the application is routable and uses the correct port
145-
# Done after the fact to let initial deploy settle before routing
146-
# traffic to the application
147-
self._update_application_service(namespace, app_type, port, routable, service_annotations) # noqa
148-
149141
def cleanup_release(self, namespace, controller, timeout):
150142
"""
151143
Cleans up resources related to an application deployment
@@ -186,38 +178,6 @@ def _get_deploy_batches(self, steps, desired):
186178

187179
return batches
188180

189-
def _update_application_service(self, namespace, app_type, port, routable=False, annotations={}): # noqa
190-
"""Update application service with all the various required information"""
191-
service = self.get_service(namespace, namespace).json()
192-
old_service = service.copy() # in case anything fails for rollback
193-
194-
try:
195-
# Update service information
196-
for key, value in annotations.items():
197-
service['metadata']['annotations']['router.deis.io/%s' % key] = str(value)
198-
if routable:
199-
service['metadata']['labels']['router.deis.io/routable'] = 'true'
200-
else:
201-
# delete the annotation
202-
service['metadata']['labels'].pop('router.deis.io/routable', None)
203-
204-
# Set app type if there is not one available
205-
if 'type' not in service['spec']['selector']:
206-
service['spec']['selector']['type'] = app_type
207-
208-
# Find if target port exists already, update / create as required
209-
if routable:
210-
for pos, item in enumerate(service['spec']['ports']):
211-
if item['port'] == 80 and port != item['targetPort']:
212-
# port 80 is the only one we care about right now
213-
service['spec']['ports'][pos]['targetPort'] = int(port)
214-
215-
self.update_service(namespace, namespace, data=service)
216-
except Exception as e:
217-
# Fix service to old port and app type
218-
self.update_service(namespace, namespace, data=old_service)
219-
raise KubeException(str(e)) from e
220-
221181
def scale(self, namespace, name, image, entrypoint, command, **kwargs):
222182
"""Scale Deployment"""
223183
self.deploy_timeout = kwargs.get('deploy_timeout')

0 commit comments

Comments
 (0)