Skip to content

Commit 78b0f04

Browse files
committed
fix(controller): don't encode empty JSON objects or arrays as strings
The django-json-field 0.5.5 package has a bug encoding empty JSON objects. Instead use a subclass which fixes this as a special case.
1 parent 72f5419 commit 78b0f04

4 files changed

Lines changed: 35 additions & 9 deletions

File tree

controller/api/fields.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,22 @@
88
from django import forms
99
from django.db import models
1010

11+
import json_field
12+
13+
14+
class JSONField(json_field.JSONField):
15+
16+
"""
17+
A subclass of json_field.JSONField that fixes empty JSON object
18+
encoding behavior.
19+
"""
20+
21+
def get_db_prep_value(self, value, *args, **kwargs):
22+
# if it's one of these values, it's already encoded
23+
if value in ['{}', '[]']:
24+
return value
25+
return super(JSONField, self).get_db_prep_value(value, *args, **kwargs)
26+
1127

1228
class UuidField(models.CharField):
1329
"""A univerally unique ID field."""

controller/api/models.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from django.utils.encoding import python_2_unicode_compatible
2222
from django_fsm import FSMField, transition
2323
from django_fsm.signals import post_transition
24-
from json_field.fields import JSONField
2524

2625
from api import fields, tasks
2726
from registry import publish_release
@@ -88,7 +87,7 @@ class Cluster(UuidAuditedModel):
8887
domain = models.CharField(max_length=128)
8988
hosts = models.CharField(max_length=256)
9089
auth = models.TextField()
91-
options = JSONField(default='{}', blank=True)
90+
options = fields.JSONField(default='{}', blank=True)
9291

9392
def __str__(self):
9493
return self.id
@@ -123,7 +122,7 @@ class App(UuidAuditedModel):
123122
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
124123
id = models.SlugField(max_length=64, unique=True)
125124
cluster = models.ForeignKey('Cluster')
126-
structure = JSONField(default='{}', blank=True)
125+
structure = fields.JSONField(default='{}', blank=True)
127126

128127
class Meta:
129128
permissions = (('use_app', 'Can use app'),)
@@ -412,7 +411,7 @@ class Build(UuidAuditedModel):
412411

413412
# optional fields populated by builder
414413
sha = models.CharField(max_length=40, blank=True)
415-
procfile = JSONField(default='{}', blank=True)
414+
procfile = fields.JSONField(default='{}', blank=True)
416415
dockerfile = models.TextField(blank=True)
417416

418417
class Meta:
@@ -433,7 +432,7 @@ class Config(UuidAuditedModel):
433432

434433
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
435434
app = models.ForeignKey('App')
436-
values = JSONField(default='{}', blank=True)
435+
values = fields.JSONField(default='{}', blank=True)
437436
limit = models.ForeignKey('Limit', null=True)
438437

439438
class Meta:
@@ -454,8 +453,8 @@ class Limit(UuidAuditedModel):
454453

455454
owner = models.ForeignKey(settings.AUTH_USER_MODEL)
456455
app = models.ForeignKey('App')
457-
memory = JSONField(default='{}', blank=True)
458-
cpu = JSONField(default='{}', blank=True)
456+
memory = fields.JSONField(default='{}', blank=True)
457+
cpu = fields.JSONField(default='{}', blank=True)
459458

460459
class Meta:
461460
get_latest_by = 'created'

controller/api/tests/test_limit.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ def test_limit_memory(self):
5555
response = self.client.get(url, content_type='application/json')
5656
self.assertEqual(response.status_code, 200)
5757
self.assertIn('memory', response.data)
58-
self.assertEqual(json.loads(response.data['memory']), json.dumps({}))
58+
self.assertEqual(json.loads(response.data['memory']), {})
59+
# regression test for https://github.com/deis/deis/issues/1563
60+
self.assertNotIn('"', response.data['memory'])
5961
# set an initial limit
6062
mem = {'web': '1G'}
6163
body = {'memory': json.dumps(mem)}
@@ -119,7 +121,9 @@ def test_limit_cpu(self):
119121
response = self.client.get(url, content_type='application/json')
120122
self.assertEqual(response.status_code, 200)
121123
self.assertIn('cpu', response.data)
122-
self.assertEqual(json.loads(response.data['memory']), json.dumps({}))
124+
self.assertEqual(json.loads(response.data['cpu']), {})
125+
# regression test for https://github.com/deis/deis/issues/1563
126+
self.assertNotIn('"', response.data['cpu'])
123127
# set an initial limit
124128
body = {'cpu': json.dumps({'web': '1024'})}
125129
response = self.client.post(url, json.dumps(body), content_type='application/json')

tests/limits_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ var (
2626

2727
func limitsSetTest(t *testing.T, cfg *utils.DeisTestConfig, ver int) {
2828
cpuCmd, memCmd := limitsSetCPUCmd, limitsSetMemCmd
29+
// regression test for https://github.com/deis/deis/issues/1563
30+
// previously the client would throw a stack trace with empty limits
31+
utils.Execute(t, limitsListCmd, cfg, false, "Unlimited")
2932
if strings.Contains(cfg.ExampleApp, "dockerfile") {
3033
cpuCmd = strings.Replace(cpuCmd, "web", "cmd", 1)
3134
memCmd = strings.Replace(memCmd, "web", "cmd", 1)
@@ -35,11 +38,13 @@ func limitsSetTest(t *testing.T, cfg *utils.DeisTestConfig, ver int) {
3538
if _, err := regexp.MatchString(output1, out); err != nil {
3639
t.Fatal(err)
3740
}
41+
utils.Execute(t, limitsListCmd, cfg, false, "512")
3842
utils.Execute(t, memCmd, cfg, false, "256M")
3943
out = dockerInspect(t, cfg, ver+1)
4044
if _, err := regexp.MatchString(output2, out); err != nil {
4145
t.Fatal(err)
4246
}
47+
utils.Execute(t, limitsListCmd, cfg, false, "256M")
4348
}
4449

4550
func limitsUnsetTest(t *testing.T, cfg *utils.DeisTestConfig, ver int) {
@@ -53,11 +58,13 @@ func limitsUnsetTest(t *testing.T, cfg *utils.DeisTestConfig, ver int) {
5358
if _, err := regexp.MatchString(output3, out); err != nil {
5459
t.Fatal(err)
5560
}
61+
utils.Execute(t, limitsListCmd, cfg, false, "Unlimited")
5662
utils.Execute(t, memCmd, cfg, false, "Unlimited")
5763
out = dockerInspect(t, cfg, ver+1)
5864
if _, err := regexp.MatchString(output4, out); err != nil {
5965
t.Fatal(err)
6066
}
67+
utils.Execute(t, limitsListCmd, cfg, false, "Unlimited")
6168
}
6269

6370
// dockerInspect creates an SSH session to the Deis controller

0 commit comments

Comments
 (0)