Skip to content

Commit a7cff7e

Browse files
author
Matthew Fisher
authored
feat(models): add routable flag to Config (#934)
If an app is considered non-routable, it is removed from the router mesh.
1 parent 544f492 commit a7cff7e

8 files changed

Lines changed: 74 additions & 7 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.9.8 on 2016-07-29 21:59
3+
from __future__ import unicode_literals
4+
5+
import api.models.key
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('api', '0010_config_healthcheck'),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name='config',
18+
name='routable',
19+
field=models.BooleanField(default=True),
20+
),
21+
]

rootfs/api/models/app.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def _scale_pods(self, scale_types):
403403
for scale_type, replicas in scale_types.items():
404404
# only web / cmd are routable
405405
# http://docs.deis.io/en/latest/using_deis/process-types/#web-vs-cmd-process-types
406-
routable = True if scale_type in ['web', 'cmd'] else False
406+
routable = True if scale_type in ['web', 'cmd'] and release.config.routable else False
407407
# fetch application port and inject into ENV Vars as needed
408408
port = release.get_port()
409409
if port:
@@ -481,7 +481,7 @@ def deploy(self, release, force_deploy=False):
481481
for scale_type, replicas in self.structure.items():
482482
# only web / cmd are routable
483483
# http://docs.deis.io/en/latest/using_deis/process-types/#web-vs-cmd-process-types
484-
routable = True if scale_type in ['web', 'cmd'] else False
484+
routable = True if scale_type in ['web', 'cmd'] and release.config.routable else False
485485
# fetch application port and inject into ENV vars as needed
486486
port = release.get_port()
487487
if port:
@@ -580,7 +580,8 @@ def verify_application_health(self, **kwargs):
580580
only run after kubernetes has reported all pods as healthy
581581
"""
582582
# Bail out early if the application is not routable
583-
if not kwargs.get('routable', False):
583+
release = self.release_set.latest()
584+
if not kwargs.get('routable', False) and release.config.routable:
584585
return
585586

586587
app_type = kwargs.get('app_type')

rootfs/api/models/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Config(UuidAuditedModel):
2121
tags = JSONField(default={}, blank=True)
2222
registry = JSONField(default={}, blank=True)
2323
healthcheck = JSONField(default={}, blank=True)
24+
routable = models.BooleanField(default=True)
2425

2526
class Meta:
2627
get_latest_by = 'created'

rootfs/api/models/release.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,17 @@ def save(self, *args, **kwargs): # noqa
481481
self.summary += ' and '
482482
self.summary += "{} {}".format(self.config.owner, changes)
483483

484+
# if the routable flag changed, log that too
485+
changes = []
486+
old_routable = old_config.routable if old_config else True
487+
enabled = "enabled routing" if self.config.routable and not old_routable else ''
488+
disabled = "disabled routing" if not self.config.routable and old_routable else ''
489+
changes = ', '.join(i for i in (enabled, disabled) if i)
490+
if changes:
491+
if self.summary:
492+
self.summary += ' and '
493+
self.summary += "{} {}".format(self.config.owner, changes)
494+
484495
if not self.summary:
485496
if self.version == 1:
486497
self.summary = "{} created the initial release".format(self.owner)

rootfs/api/serializers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ class ConfigSerializer(serializers.ModelSerializer):
200200
tags = JSONFieldSerializer(required=False, binary=True)
201201
registry = JSONFieldSerializer(required=False, binary=True)
202202
healthcheck = JSONFieldSerializer(convert_to_str=False, required=False, binary=True)
203+
routable = serializers.BooleanField(required=False)
203204

204205
class Meta:
205206
"""Metadata options for a :class:`ConfigSerializer`."""

rootfs/api/tests/test_app.py

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

501+
501502
FAKE_LOG_DATA = """
502503
2013-08-15 12:41:25 [33454] [INFO] Starting gunicorn 17.5
503504
2013-08-15 12:41:25 [33454] [INFO] Listening at: http://0.0.0.0:5000 (33454)

rootfs/api/tests/test_config.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def test_response_data(self, mock_requests):
131131
response = self.client.post(url, body)
132132
for key in response.data:
133133
self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
134-
'cpu', 'tags', 'registry', 'healthcheck'])
134+
'cpu', 'tags', 'registry', 'healthcheck', 'routable'])
135135
expected = {
136136
'owner': self.user.username,
137137
'app': app_id,
@@ -154,7 +154,7 @@ def test_response_data_types_converted(self, mock_requests):
154154
self.assertEqual(response.status_code, 201, response.data)
155155
for key in response.data:
156156
self.assertIn(key, ['uuid', 'owner', 'created', 'updated', 'app', 'values', 'memory',
157-
'cpu', 'tags', 'registry', 'healthcheck'])
157+
'cpu', 'tags', 'registry', 'healthcheck', 'routable'])
158158
expected = {
159159
'owner': self.user.username,
160160
'app': app_id,
@@ -325,3 +325,21 @@ def test_unauthorized_user_cannot_modify_config(self, mock_requests):
325325
body = {'values': {'FOO': 'bar'}}
326326
response = self.client.post(url, body)
327327
self.assertEqual(response.status_code, 403)
328+
329+
def test_config_routable(self, mock_requests):
330+
"""
331+
Create an application with the routable flag turned on or off
332+
"""
333+
# create app, expecting routable to be true
334+
body = {'id': 'myid'}
335+
response = self.client.post('/v2/apps', body)
336+
self.assertEqual(response.status_code, 201, response.data)
337+
app = App.objects.get(id='myid')
338+
self.assertTrue(app.config_set.latest().routable)
339+
# Set routable to false
340+
response = self.client.post(
341+
'/v2/apps/{app.id}/config'.format(**locals()),
342+
{'routable': False}
343+
)
344+
self.assertEqual(response.status_code, 201, response.data)
345+
self.assertFalse(app.config_set.latest().routable)

rootfs/api/tests/test_release.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,23 @@ def test_release_str(self, mock_requests):
194194

195195
def test_release_summary(self, mock_requests):
196196
"""Test the text summary of a release."""
197-
release3 = self.test_release()
198-
release = Release.objects.get(uuid=release3['uuid'])
197+
release = self.test_release()
198+
app = App.objects.get(id=release['app'])
199+
release = app.release_set.latest()
199200
# check that the release has push and env change messages
200201
self.assertIn('autotest deployed ', release.summary)
202+
# add config and routable flags, confirm that routable
203+
# and config objects are in the summary
204+
url = '/v2/apps/{app.id}/config'.format(**locals())
205+
body = {
206+
'values': json.dumps({'FOO': 'bar'}),
207+
'routable': False,
208+
}
209+
response = self.client.post(url, body)
210+
self.assertEqual(response.status_code, 201, response.data)
211+
self.assertEqual(
212+
'autotest added FOO and autotest disabled routing',
213+
app.release_set.latest().summary)
201214

202215
def test_admin_can_create_release(self, mock_requests):
203216
"""If a non-user creates an app, an admin should be able to create releases."""

0 commit comments

Comments
 (0)