Skip to content

Commit d9d4a67

Browse files
author
Matthew Fisher
authored
Merge branch 'master' into bump-version
2 parents db2e182 + c371950 commit d9d4a67

6 files changed

Lines changed: 70 additions & 14 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ build: docker-build
2828

2929
docker-build: check-docker
3030
docker build --rm -t ${IMAGE} rootfs
31-
docker tag -f ${IMAGE} ${MUTABLE_IMAGE}
31+
docker tag ${IMAGE} ${MUTABLE_IMAGE}
3232

3333
deploy: docker-build docker-push
3434
sed 's#\(image:\) .*#\1 $(IMAGE)#' /tmp/deis-$(COMPONENT) | kubectl apply --validate=true -f -

rootfs/api/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ class AlreadyExists(APIException):
1818
status_code = 409
1919

2020

21+
class UnprocessableEntity(APIException):
22+
status_code = 422
23+
24+
2125
class ServiceUnavailable(APIException):
2226
status_code = 503
2327
default_detail = 'Service temporarily unavailable, try again later.'

rootfs/api/models/config.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from jsonfield import JSONField
44

55
from api.models.release import Release
6-
from api.models import UuidAuditedModel, DeisException
6+
from api.models import UuidAuditedModel
7+
from api.exceptions import DeisException, UnprocessableEntity
78

89

910
class Config(UuidAuditedModel):
@@ -130,9 +131,16 @@ def save(self, **kwargs):
130131
data = getattr(previous_config, attr, {}).copy()
131132
new_data = getattr(self, attr, {}).copy()
132133

133-
data.update(new_data)
134-
# remove config keys if we provided a null value
135-
[data.pop(k) for k, v in new_data.items() if v is None]
134+
# remove config keys if a null value is provided
135+
for key, value in new_data.items():
136+
if value is None:
137+
# error if unsetting non-existing key
138+
if key not in data:
139+
raise UnprocessableEntity('{} does not exist under {}'.format(key, attr)) # noqa
140+
141+
data.pop(key)
142+
else:
143+
data[key] = value
136144
setattr(self, attr, data)
137145

138146
self.set_healthchecks()

rootfs/api/models/release.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
from registry import publish_release, get_port as docker_get_port, RegistryException
77
from api.utils import dict_diff
8-
from api.models import UuidAuditedModel, DeisException
8+
from api.models import UuidAuditedModel
9+
from api.exceptions import DeisException, AlreadyExists
910
from scheduler import KubeHTTPException
1011

1112
logger = logging.getLogger(__name__)
@@ -423,5 +424,7 @@ def save(self, *args, **kwargs): # noqa
423424
if self.version == 1:
424425
self.summary = "{} created the initial release".format(self.owner)
425426
else:
426-
self.summary = "{} changed nothing".format(self.owner)
427+
# There were no changes to this release
428+
raise AlreadyExists("{} changed nothing - release stopped".format(self.owner))
429+
427430
super(Release, self).save(*args, **kwargs)

rootfs/api/tests/test_release.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,43 @@ def json(self):
333333
body = {'version': 2}
334334
response = self.client.post(url, body)
335335
self.assertEqual(response.status_code, 400, response.data)
336+
337+
def test_release_unset_config(self, mock_requests):
338+
"""
339+
Test that a release is created when an app is created, a config can be
340+
set and then unset without causing a 409 (conflict)
341+
"""
342+
url = '/v2/apps'
343+
response = self.client.post(url)
344+
self.assertEqual(response.status_code, 201, response.data)
345+
app_id = response.data['id']
346+
347+
# check that updating config rolls a new release
348+
url = '/v2/apps/{app_id}/config'.format(**locals())
349+
body = {'cpu': json.dumps({'cmd': None})}
350+
response = self.client.post(url, body)
351+
self.assertEqual(response.status_code, 422, response.data)
352+
353+
def test_release_no_change(self, mock_requests):
354+
"""
355+
Test that a release is created when an app is created, and
356+
then has 2 identical config set, causing a 409 as there was
357+
no change
358+
"""
359+
url = '/v2/apps'
360+
response = self.client.post(url)
361+
self.assertEqual(response.status_code, 201, response.data)
362+
app_id = response.data['id']
363+
364+
# check that updating config rolls a new release
365+
url = '/v2/apps/{app_id}/config'.format(**locals())
366+
body = {'values': json.dumps({'NEW_URL1': 'http://localhost:8080/'})}
367+
response = self.client.post(url, body)
368+
self.assertEqual(response.status_code, 201, response.data)
369+
self.assertIn('NEW_URL1', response.data['values'])
370+
371+
# trigger identical release
372+
url = '/v2/apps/{app_id}/config'.format(**locals())
373+
body = {'values': json.dumps({'NEW_URL1': 'http://localhost:8080/'})}
374+
response = self.client.post(url, body)
375+
self.assertEqual(response.status_code, 409, response.data)

rootfs/scheduler/__init__.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,6 @@ def _wait_until_pods_are_ready(self, namespace, container, labels, desired): #
766766

767767
logger.info("waiting for {} pods in {} namespace to be in services ({} timeout)".format(desired, namespace, timeout)) # noqa
768768

769-
# has timeout been increased or not within the loop
770-
timeout_padded = False
771769
# Ensure the minimum desired number of pods are available
772770
while waited < timeout:
773771
count = 0 # ready pods
@@ -777,11 +775,7 @@ def _wait_until_pods_are_ready(self, namespace, container, labels, desired): #
777775
if pod['status']['phase'] == 'Pending':
778776
reason, message = self._pod_pending_status(pod)
779777
# If pulling an image is taking long then increase the timeout
780-
if not timeout_padded:
781-
pull = self._handle_pod_long_image_pulling(pod, reason)
782-
if pull:
783-
timeout_padded = True
784-
timeout += pull
778+
timeout += self._handle_pod_long_image_pulling(pod, reason)
785779

786780
# handle errors and bubble up if need be
787781
self._handle_pod_image_errors(pod, reason, message)
@@ -1411,6 +1405,10 @@ def _handle_pod_long_image_pulling(self, reason, pod):
14111405
14121406
Return value is an int that represents seconds
14131407
"""
1408+
# only apply once
1409+
if getattr(self, '_handle_pod_long_image_pulling_applied', False):
1410+
return 0
1411+
14141412
if reason is not 'Pulling':
14151413
return 0
14161414

@@ -1427,6 +1425,9 @@ def _handle_pod_long_image_pulling(self, reason, pod):
14271425
# add 10 minutes to timeout to allow a pull image operation to finish
14281426
logger.info('Kubernetes has been pulling the image for {} seconds'.format(seconds)) # noqa
14291427
logger.info('Increasing timeout by 10 minutes to allow a pull image operation to finish for pods in namespace {}'.format(namespace)) # noqa
1428+
1429+
# make it so function doesn't do processing again
1430+
setattr(self, '_handle_pod_long_image_pulling_applied', True)
14301431
return 600
14311432

14321433
return 0

0 commit comments

Comments
 (0)