Skip to content

Commit 3cea166

Browse files
committed
fix(healthcheck): delete outdated code
1 parent b29c14f commit 3cea166

6 files changed

Lines changed: 6 additions & 234 deletions

File tree

rootfs/api/models/app.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,9 +1118,7 @@ def _gather_app_settings(self, release, app_settings, process_type, replicas, vo
11181118
# https://www.drycc.cc/applications/managing-app-processes/#default-process-types
11191119
routable = True if process_type == 'web' and app_settings.routable else False
11201120

1121-
healthcheck = config.get_healthcheck().get(process_type, {})
1122-
if not healthcheck and process_type == 'web':
1123-
healthcheck = config.get_healthcheck().get('web', {})
1121+
healthcheck = config.healthcheck.get(process_type, {})
11241122
volumes, volume_mounts = self._get_volumes_and_mounts(process_type, volumes)
11251123
return {
11261124
'memory': memory,

rootfs/api/models/config.py

Lines changed: 0 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -40,55 +40,6 @@ class Meta:
4040
def __str__(self):
4141
return "{}-{}".format(self.app.id, str(self.uuid)[:7])
4242

43-
def _migrate_legacy_healthcheck(self):
44-
"""
45-
Get all healthchecks options together for use in scheduler
46-
"""
47-
# return if no legacy healthcheck is found
48-
if 'HEALTHCHECK_URL' not in self.values.keys():
49-
return
50-
51-
path = self.values.get('HEALTHCHECK_URL', '/')
52-
timeout = int(self.values.get('HEALTHCHECK_TIMEOUT', 50))
53-
delay = int(self.values.get('HEALTHCHECK_INITIAL_DELAY', 50))
54-
period_seconds = int(self.values.get('HEALTHCHECK_PERIOD_SECONDS', 10))
55-
success_threshold = int(self.values.get('HEALTHCHECK_SUCCESS_THRESHOLD', 1))
56-
failure_threshold = int(self.values.get('HEALTHCHECK_FAILURE_THRESHOLD', 3))
57-
58-
self.healthcheck['web'] = {}
59-
self.healthcheck['web']['livenessProbe'] = {
60-
'initialDelaySeconds': delay,
61-
'timeoutSeconds': timeout,
62-
'periodSeconds': period_seconds,
63-
'successThreshold': success_threshold,
64-
'failureThreshold': failure_threshold,
65-
'httpGet': {
66-
'path': path,
67-
}
68-
}
69-
70-
self.healthcheck['web']['readinessProbe'] = {
71-
'initialDelaySeconds': delay,
72-
'timeoutSeconds': timeout,
73-
'periodSeconds': period_seconds,
74-
'successThreshold': success_threshold,
75-
'failureThreshold': failure_threshold,
76-
'httpGet': {
77-
'path': path,
78-
}
79-
}
80-
81-
# Unset all the old values
82-
self.values = {k: v for k, v in self.values.items() if not k.startswith('HEALTHCHECK_')}
83-
84-
def get_healthcheck(self):
85-
if (
86-
'livenessProbe' in self.healthcheck.keys() or
87-
'readinessProbe' in self.healthcheck.keys()
88-
):
89-
return {'web': self.healthcheck}
90-
return self.healthcheck
91-
9243
def _set_cpu_memory(self):
9344
"""
9445
According to settings.KUBERNETES_CPU_MEMORY_RATIO corrects cpu and memory
@@ -200,15 +151,6 @@ def set_tags(self, previous_config):
200151
def set_healthcheck(self, previous_config):
201152
data = getattr(previous_config, 'healthcheck', {}).copy()
202153
new_data = getattr(self, 'healthcheck', {}).copy()
203-
# update the config data for healthcheck if they are not
204-
# present for per proctype
205-
# TODO: This is required for backward compatibility and can be
206-
# removed in next major version change.
207-
if 'livenessProbe' in data.keys() or 'readinessProbe' in data.keys():
208-
data = {'web': data.copy()}
209-
if 'livenessProbe' in new_data.keys() or 'readinessProbe' in new_data.keys(): # noqa
210-
new_data = {'web': new_data.copy()}
211-
212154
# remove config keys if a null value is provided
213155
for key, value in new_data.items():
214156
if value is None:
@@ -258,7 +200,6 @@ def save(self, **kwargs):
258200
setattr(self, attr, data)
259201
self._set_cpu_memory()
260202
self.set_healthcheck(previous_config)
261-
self._migrate_legacy_healthcheck()
262203
self.set_registry()
263204
self.set_tags(previous_config)
264205
except Config.DoesNotExist:

rootfs/api/serializers.py

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
import time
55
import json
66
import logging
7-
import jmespath
87
import re
98
import jsonschema
109
import idna
11-
from urllib.parse import urlparse
1210

1311
from django.conf import settings
1412
from django.contrib.auth import get_user_model
@@ -62,6 +60,8 @@
6260
) % (settings.KUBERNETES_LIMITS_MAX_VOLUME, settings.KUBERNETES_LIMITS_MIN_VOLUME)
6361
VOLUME_PATH_MATCH = re.compile(r'^\/(\w+\/?)+$', re.IGNORECASE)
6462
METRIC_EVERY_MATCH = re.compile(r'^[1-9][0-9]*m$')
63+
HEALTHCHECK_MATCH = re.compile(r'^(livenessProbe|readinessProbe|startupProbe)$')
64+
HEALTHCHECK_MISMATCH_MSG = "Healthcheck pattern: %s" % HEALTHCHECK_MATCH.pattern
6565

6666

6767
class JSONFieldSerializer(serializers.JSONField):
@@ -211,31 +211,6 @@ def validate_values(data):
211211
# check if hte port is between 1 and 65535. One extra added for range()
212212
# http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_serviceport
213213
raise serializers.ValidationError('PORT needs to be between 1 and 65535')
214-
215-
# Validate HEALTHCHECK_*
216-
if key == 'HEALTHCHECK_URL':
217-
# Only Path information is supported, not query / anchor or anything else
218-
# Path is the only thing Kubernetes supports right now
219-
# See https://github.com/drycc/controller/issues/774
220-
uri = urlparse(value)
221-
222-
if not uri.path:
223-
raise serializers.ValidationError(
224-
'{} is missing a URI path (such as /healthz). '
225-
'Without it no health check can be done'.format(key)
226-
)
227-
228-
# Disallow everything but path
229-
# https://docs.python.org/3/library/urllib.parse.html
230-
if uri.query or uri.fragment or uri.netloc:
231-
raise serializers.ValidationError(
232-
'{} can only be a URI path (such as /healthz) that does not contain '
233-
'other things such as query params'.format(key)
234-
)
235-
elif key.startswith('HEALTHCHECK_') and not str(value).isnumeric():
236-
# all other healthchecks are integers
237-
raise serializers.ValidationError('{} can only be a numeric value'.format(key))
238-
239214
return data
240215

241216
@staticmethod
@@ -345,30 +320,19 @@ def validate_registry(data):
345320

346321
@staticmethod
347322
def validate_healthcheck(data):
348-
for procType, healthcheck in data.items():
323+
for _, healthcheck in data.items():
349324
if healthcheck is None:
350325
continue
351326
for key, value in healthcheck.items():
352327
if value is None:
353328
continue
354-
if key not in ['livenessProbe', 'readinessProbe']:
355-
raise serializers.ValidationError(
356-
"Healthcheck keys must be either livenessProbe or readinessProbe")
329+
if not re.match(HEALTHCHECK_MATCH, key):
330+
raise serializers.ValidationError(HEALTHCHECK_MISMATCH_MSG)
357331
try:
358332
jsonschema.validate(value, HEALTHCHECK_SCHEMA)
359333
except jsonschema.ValidationError as e:
360334
raise serializers.ValidationError(
361335
"could not validate {}: {}".format(value, e.message))
362-
363-
# http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_probe
364-
# liveness only supports successThreshold=1, no other value
365-
# This is not in the schema since readiness supports other values
366-
threshold = jmespath.search('livenessProbe.successThreshold', healthcheck)
367-
if threshold is not None and threshold != 1:
368-
raise serializers.ValidationError(
369-
'livenessProbe successThreshold can only be 1'
370-
)
371-
372336
return data
373337

374338

rootfs/api/tests/test_healthchecks.py

Lines changed: 0 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from django.contrib.auth import get_user_model
66
from rest_framework.authtoken.models import Token
77

8-
from api.models.app import App
98
from api.tests import adapter, DryccTransactionTestCase
109

1110
User = get_user_model()
@@ -26,50 +25,6 @@ def tearDown(self):
2625
# make sure every test has a clean slate for k8s mocking
2726
cache.clear()
2827

29-
def test_healthchecks_validations(self, mock_requests):
30-
"""
31-
Test that healthchecks validations work
32-
"""
33-
app_id = self.create_app()
34-
35-
# Set one of the values that require a numeric value to a string
36-
response = self.client.post(
37-
'/v2/apps/{app_id}/config'.format(**locals()),
38-
{'values': json.dumps({'HEALTHCHECK_INITIAL_DELAY': 'horse'})}
39-
)
40-
self.assertEqual(response.status_code, 400, response.data)
41-
42-
# test URL - Path is the only allowed thing
43-
# Try setting various things such as query param
44-
45-
# query param
46-
response = self.client.post(
47-
'/v2/apps/{app_id}/config'.format(**locals()),
48-
{'values': json.dumps({'HEALTHCHECK_URL': '/health?testing=0'})}
49-
)
50-
self.assertEqual(response.status_code, 400, response.data)
51-
52-
# fragment
53-
response = self.client.post(
54-
'/v2/apps/{app_id}/config'.format(**locals()),
55-
{'values': json.dumps({'HEALTHCHECK_URL': '/health#db'})}
56-
)
57-
self.assertEqual(response.status_code, 400, response.data)
58-
59-
# netloc
60-
response = self.client.post(
61-
'/v2/apps/{app_id}/config'.format(**locals()),
62-
{'values': json.dumps({'HEALTHCHECK_URL': 'http://someurl.com/health/'})}
63-
)
64-
self.assertEqual(response.status_code, 400, response.data)
65-
66-
# no path
67-
response = self.client.post(
68-
'/v2/apps/{app_id}/config'.format(**locals()),
69-
{'values': json.dumps({'HEALTHCHECK_URL': 'http://someurl.com'})}
70-
)
71-
self.assertEqual(response.status_code, 400, response.data)
72-
7328
def test_config_healthchecks(self, mock_requests):
7429
"""
7530
Test that healthchecks can be applied
@@ -171,85 +126,3 @@ def test_config_healthchecks_validations(self, mock_requests):
171126
{'httpGet': {'path': '/'}, 'initialDelaySeconds': 1}}})}
172127
)
173128
self.assertEqual(response.status_code, 400, response.data)
174-
175-
# set liveness success threshold to a non-1 value
176-
# Don't set one of the mandatory value
177-
response = self.client.post(
178-
'/v2/apps/{app_id}/config'.format(**locals()),
179-
{'healthcheck': {'web': {'livenessProbe':
180-
{'httpGet': {'path': '/', 'port': 5000},
181-
'successThreshold': 5}}}}
182-
)
183-
self.assertEqual(response.status_code, 400, response.data)
184-
185-
def test_config_healthchecks_legacy(self, mock_requests):
186-
"""
187-
Test that when a user uses `drycc config:set HEALTHCHECK_URL=/`, the config
188-
object is rolled over to the `healthcheck` field.
189-
"""
190-
app_id = self.create_app()
191-
app = App.objects.get(id=app_id)
192-
193-
# Set healthcheck URL to get defaults set
194-
response = self.client.post(
195-
'/v2/apps/{app.id}/config'.format(**locals()),
196-
{'values': json.dumps({'HEALTHCHECK_URL': '/health'})}
197-
)
198-
self.assertEqual(response.status_code, 201, response.data)
199-
# this gets migrated to the new healtcheck format
200-
self.assertNotIn('HEALTHCHECK_URL', response.data['values'])
201-
# legacy defaults
202-
expected = {'web': {
203-
'livenessProbe': {
204-
'initialDelaySeconds': 50,
205-
'timeoutSeconds': 50,
206-
'periodSeconds': 10,
207-
'successThreshold': 1,
208-
'failureThreshold': 3,
209-
'httpGet': {
210-
'path': '/health'
211-
}
212-
},
213-
'readinessProbe': {
214-
'initialDelaySeconds': 50,
215-
'timeoutSeconds': 50,
216-
'periodSeconds': 10,
217-
'successThreshold': 1,
218-
'failureThreshold': 3,
219-
'httpGet': {
220-
'path': '/health'
221-
}
222-
}
223-
}
224-
}
225-
actual = app.config_set.latest().healthcheck
226-
self.assertEqual(actual, expected)
227-
# Now set all the envvars and check to make sure they are written properly
228-
response = self.client.post(
229-
'/v2/apps/{app.id}/config'.format(**locals()),
230-
{
231-
'values': json.dumps({
232-
'HEALTHCHECK_URL': '/health',
233-
'HEALTHCHECK_INITIAL_DELAY': '25',
234-
'HEALTHCHECK_TIMEOUT': '10',
235-
'HEALTHCHECK_PERIOD_SECONDS': '5',
236-
'HEALTHCHECK_SUCCESS_THRESHOLD': '2',
237-
'HEALTHCHECK_FAILURE_THRESHOLD': '2'})
238-
}
239-
)
240-
self.assertEqual(response.status_code, 201, response.data)
241-
# this gets migrated to the new healtcheck format
242-
self.assertNotIn('HEALTHCHECK_INITIAL_DELAY', response.data['values'])
243-
expected['web']['livenessProbe'] = {
244-
'initialDelaySeconds': 25,
245-
'timeoutSeconds': 10,
246-
'periodSeconds': 5,
247-
'successThreshold': 2,
248-
'failureThreshold': 2,
249-
'httpGet': {
250-
'path': '/health'
251-
}
252-
}
253-
expected['web']['readinessProbe'] = expected['web']['livenessProbe']
254-
actual = app.config_set.latest().healthcheck
255-
self.assertEqual(expected, actual)

rootfs/requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ gunicorn==20.1.0
1111
uvicorn[standard]==0.22.0
1212
asgiref==3.7.2
1313
idna==3.4
14-
jmespath==1.0.1
1514
jsonschema==4.18.0
1615
morph==0.1.5
1716
packaging==23.1

rootfs/scheduler/resources/pod.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,6 @@ def _get_memory(size):
295295
def _set_health_checks(self, container, env, **kwargs):
296296
healthchecks = kwargs.get('healthcheck', None)
297297
if healthchecks:
298-
# check if a port is present. if not, auto-populate it
299-
# TODO: rip this out when we stop supporting drycc config:set HEALTHCHECK_URL
300298
if (
301299
healthchecks.get('livenessProbe') is not None and
302300
healthchecks['livenessProbe'].get('httpGet') is not None and
@@ -348,7 +346,6 @@ def _default_container_readiness_probe(port, delay=5, timeout=5, period_seconds=
348346
"""
349347
readinessprobe = {
350348
'readinessProbe': {
351-
# an exec probe
352349
'tcpSocket': {
353350
"port": int(port)
354351
},

0 commit comments

Comments
 (0)