Skip to content

Commit 0184d26

Browse files
committed
chore(api): check cpu/memory range for api
1 parent e10c89d commit 0184d26

5 files changed

Lines changed: 89 additions & 42 deletions

File tree

rootfs/api/models/app.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,15 +1096,20 @@ def _get_private_registry_config(self, image, registry=None):
10961096
@staticmethod
10971097
def _get_cpu_allocation(size):
10981098
cpu_allocation_ratio = settings.KUBERNETES_CPU_ALLOCATION_RATIO
1099-
num, unit = (
1100-
''.join(item[1]) for item in groupby(
1101-
size, key=lambda x: x.isdigit()
1099+
if size.isdigit():
1100+
unit = 'm'
1101+
num = (int(size) * 1000) / cpu_allocation_ratio
1102+
else:
1103+
num, unit = (
1104+
''.join(item[1]) for item in groupby(
1105+
size, key=lambda x: x.isdigit()
1106+
)
11021107
)
1103-
)
1104-
return "{num}{unit}".format(
1105-
num=math.floor(int(num) / cpu_allocation_ratio),
1106-
unit=unit
1107-
)
1108+
if unit not in ["m", "M"]:
1109+
raise DryccException("Units are represented in the number or milli of CPUs")
1110+
else:
1111+
num = int(num) / cpu_allocation_ratio
1112+
return "{num}{unit}".format(num=math.ceil(num), unit=unit)
11081113

11091114
@staticmethod
11101115
def _get_ram_allocation(size):
@@ -1114,10 +1119,14 @@ def _get_ram_allocation(size):
11141119
size, key=lambda x: x.isdigit()
11151120
)
11161121
)
1117-
return "{num}{unit}".format(
1118-
num=math.floor(int(num) / ram_allocation_ratio),
1119-
unit=unit
1120-
)
1122+
if unit in ['G', 'g']:
1123+
unit = 'M'
1124+
num = (int(num) * 1024) / ram_allocation_ratio
1125+
elif unit in ['M', 'm']:
1126+
num = int(num) / ram_allocation_ratio
1127+
else:
1128+
raise DryccException('Units are represented in Megabytes(M), or Gigabytes (G)')
1129+
return "{num}{unit}".format(num=math.ceil(num), unit=unit)
11211130

11221131
def _gather_app_settings(self, release, app_settings, process_type, replicas, volumes=None):
11231132
"""

rootfs/api/serializers.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ def validate_values(data):
279279

280280
@staticmethod
281281
def validate_memory(data):
282+
max_memory = settings.KUBERNETES_RAM_MAX_ALLOCATION
283+
min_memory = settings.KUBERNETES_RAM_MIN_ALLOCATION * settings.KUBERNETES_RAM_ALLOCATION_RATIO # noqa
282284
for key, value in data.items():
283285
if value is None: # use NoneType to unset an item
284286
continue
@@ -290,11 +292,20 @@ def validate_memory(data):
290292
raise serializers.ValidationError(
291293
"Memory limit format: <number><unit>, "
292294
"where unit = M or G")
293-
295+
range_error = "Memory setting is not in allowed range: %sM~%sM" % (
296+
min_memory, max_memory)
297+
if str(value).endswith("G"):
298+
if int(value[:-1]) * 1024 < min_memory or int(value[:-1]) * 1024 > max_memory:
299+
raise serializers.ValidationError(range_error)
300+
else:
301+
if int(value[:-1]) < min_memory or int(value[:-1]) > max_memory:
302+
raise serializers.ValidationError(range_error)
294303
return data
295304

296305
@staticmethod
297306
def validate_cpu(data):
307+
max_cpu = settings.KUBERNETES_CPU_MAX_ALLOCATION
308+
min_cpu = settings.KUBERNETES_CPU_MIN_ALLOCATION * settings.KUBERNETES_CPU_ALLOCATION_RATIO # noqa
298309
for key, value in data.items():
299310
if value is None: # use NoneType to unset an item
300311
continue
@@ -306,7 +317,14 @@ def validate_cpu(data):
306317
if not shares:
307318
raise serializers.ValidationError(
308319
"CPU limit format: <value>, where value must be a numeric")
309-
320+
range_error = "CPU setting is not in allowed range: %sm~%sm" % (
321+
min_cpu, max_cpu)
322+
if str(value).isdigit():
323+
if int(value) * 1000 < min_cpu or int(value) * 1000 > max_cpu:
324+
raise serializers.ValidationError(range_error)
325+
else:
326+
if int(value[:-1]) < min_cpu or int(value[:-1]) > max_cpu:
327+
raise serializers.ValidationError(range_error)
310328
return data
311329

312330
@staticmethod

rootfs/api/settings/production.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -335,22 +335,28 @@
335335
# How long k8s waits for a pod to finish work after a SIGTERM before sending SIGKILL
336336
KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS = int(os.environ.get('KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS', 30)) # noqa
337337

338+
# Minimum allocation cpu, units are represented in the millicpu of CPUs
339+
KUBERNETES_CPU_MIN_ALLOCATION = int(os.environ.get('KUBERNETES_RAM_MIN_ALLOCATION', '100'))
340+
# Minimum allocation memory, units are represented in Megabytes(M)
341+
KUBERNETES_RAM_MIN_ALLOCATION = int(os.environ.get('KUBERNETES_RAM_MIN_ALLOCATION', '128'))
342+
# Maximum allocation cpu, units are represented in the millicpu of CPUs
343+
KUBERNETES_CPU_MAX_ALLOCATION = int(os.environ.get('KUBERNETES_RAM_MIN_ALLOCATION', '32000'))
344+
# Maximum allocation memory, units are represented in Megabytes(M)
345+
KUBERNETES_RAM_MAX_ALLOCATION = int(os.environ.get('KUBERNETES_RAM_MIN_ALLOCATION', '131072'))
346+
# CPU allocation ratio
338347
KUBERNETES_CPU_ALLOCATION_RATIO = int(os.environ.get('KUBERNETES_CPU_ALLOCATION_RATIO', '10'))
348+
# RAM allocation ratio
339349
KUBERNETES_RAM_ALLOCATION_RATIO = int(os.environ.get('KUBERNETES_RAM_ALLOCATION_RATIO', '2'))
340350

341351
# Default pod spec for application.
342352
KUBERNETES_POD_DEFAULT_RESOURCES = os.environ.get(
343353
'KUBERNETES_POD_DEFAULT_RESOURCES',
344354
json.dumps({
345355
"requests": {
346-
"cpu": "100m",
347-
"memory": "128M",
348-
"ephemeral-storage": "256Mi",
356+
"ephemeral-storage": "1Gi",
349357
},
350358
"limits": {
351-
"cpu": "1",
352-
"memory": "256M",
353-
"ephemeral-storage": "512Mi",
359+
"ephemeral-storage": "2Gi",
354360
}
355361
})
356362
)
@@ -365,20 +371,20 @@
365371
"limits": [
366372
{
367373
"default": {
368-
"cpu": "100m",
369-
"memory": "128Mi"
374+
"cpu": "%sm" % KUBERNETES_CPU_MIN_ALLOCATION * KUBERNETES_CPU_ALLOCATION_RATIO, # noqa
375+
"memory": "%sMi" % KUBERNETES_RAM_MIN_ALLOCATION * KUBERNETES_RAM_ALLOCATION_RATIO # noqa
370376
},
371377
"defaultRequest": {
372-
"cpu": "100m",
373-
"memory": "128Mi"
378+
"cpu": "%sm" % KUBERNETES_CPU_MIN_ALLOCATION,
379+
"memory": "%sMi" % KUBERNETES_RAM_MIN_ALLOCATION
374380
},
375381
"max": {
376-
"cpu": "32",
377-
"memory": "128Gi"
382+
"cpu": "%sm" % KUBERNETES_CPU_MAX_ALLOCATION,
383+
"memory": "%sMi" % KUBERNETES_RAM_MAX_ALLOCATION
378384
},
379385
"min": {
380-
"cpu": "100m",
381-
"memory": "128Mi"
386+
"cpu": "%sm" % (KUBERNETES_CPU_MIN_ALLOCATION - 1),
387+
"memory": "%sMi" % (KUBERNETES_RAM_MIN_ALLOCATION - 1)
382388
},
383389
"type": "Container"
384390
},

rootfs/api/tests/test_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def test_response_data_types_converted(self, mock_requests):
182182

183183
url = "/v2/apps/{app_id}/config".format(**locals())
184184

185-
body = {'values': json.dumps({'PORT': 5000}), 'cpu': json.dumps({'web': '1024'})}
185+
body = {'values': json.dumps({'PORT': 5000}), 'cpu': json.dumps({'web': '1000m'})}
186186
response = self.client.post(url, body)
187187
self.assertEqual(response.status_code, 201, response.data)
188188
for key in response.data:
@@ -194,7 +194,7 @@ def test_response_data_types_converted(self, mock_requests):
194194
'app': app_id,
195195
'values': {'PORT': '5000'},
196196
'memory': {},
197-
'cpu': {'web': "1024"},
197+
'cpu': {'web': "1000m"},
198198
'tags': {},
199199
'registry': {}
200200
}

rootfs/api/tests/test_limits.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,13 @@ def test_request_limit_memory(self, mock_requests):
171171
body = {'memory': json.dumps(mem)}
172172
response = self.client.post(url, body)
173173
self.assertEqual(response.status_code, 400, response.data)
174+
# out of range
175+
body = {'memory': json.dumps({'web': '64M'})}
176+
response = self.client.post(url, body)
177+
self.assertEqual(response.status_code, 400, response.data)
178+
body = {'memory': json.dumps({'web': '1024G'})}
179+
response = self.client.post(url, body)
180+
self.assertEqual(response.status_code, 400, response.data)
174181

175182
mem = {'w3&b': '1G'}
176183
body = {'memory': json.dumps(mem)}
@@ -202,7 +209,7 @@ def test_request_limit_cpu(self, mock_requests):
202209
self.assertNotIn('"', response.data['cpu'])
203210

204211
# set an initial limit
205-
body = {'cpu': json.dumps({'web': '1024'})}
212+
body = {'cpu': json.dumps({'web': '1000m'})}
206213
response = self.client.post(url, body)
207214
self.assertEqual(response.status_code, 201, response.data)
208215
limit1 = response.data
@@ -213,19 +220,19 @@ def test_request_limit_cpu(self, mock_requests):
213220
self.assertIn('cpu', response.data)
214221
cpu = response.data['cpu']
215222
self.assertIn('web', cpu)
216-
self.assertEqual(cpu['web'], '1024')
223+
self.assertEqual(cpu['web'], '1000m')
217224

218225
# set an additional value
219-
body = {'cpu': json.dumps({'worker': '512m'})}
226+
body = {'cpu': json.dumps({'worker': '2000m'})}
220227
response = self.client.post(url, body)
221228
self.assertEqual(response.status_code, 201, response.data)
222229
limit2 = response.data
223230
self.assertNotEqual(limit1['uuid'], limit2['uuid'])
224231
cpu = response.data['cpu']
225232
self.assertIn('worker', cpu)
226-
self.assertEqual(cpu['worker'], '512m')
233+
self.assertEqual(cpu['worker'], '2000m')
227234
self.assertIn('web', cpu)
228-
self.assertEqual(cpu['web'], '1024')
235+
self.assertEqual(cpu['web'], '1000m')
229236

230237
# read the limit again
231238
response = self.client.get(url)
@@ -234,9 +241,9 @@ def test_request_limit_cpu(self, mock_requests):
234241
self.assertEqual(limit2, limit3)
235242
cpu = response.data['cpu']
236243
self.assertIn('worker', cpu)
237-
self.assertEqual(cpu['worker'], '512m')
244+
self.assertEqual(cpu['worker'], '2000m')
238245
self.assertIn('web', cpu)
239-
self.assertEqual(cpu['web'], '1024')
246+
self.assertEqual(cpu['web'], '1000m')
240247

241248
# add with requests/limits
242249
body = {'cpu': json.dumps({'db': '1'})}
@@ -248,14 +255,14 @@ def test_request_limit_cpu(self, mock_requests):
248255
self.assertEqual(response.status_code, 200, response.data)
249256
cpu = response.data['cpu']
250257
self.assertIn('worker', cpu)
251-
self.assertEqual(cpu['worker'], '512m')
258+
self.assertEqual(cpu['worker'], '2000m')
252259
self.assertIn('web', cpu)
253-
self.assertEqual(cpu['web'], '1024')
260+
self.assertEqual(cpu['web'], '1000m')
254261
self.assertIn('db', cpu)
255262
self.assertEqual(cpu['db'], '1')
256263

257264
# replace one with requests/limits
258-
body = {'cpu': json.dumps({'web': '300m'})}
265+
body = {'cpu': json.dumps({'web': '3000m'})}
259266
response = self.client.post(url, body)
260267
self.assertEqual(response.status_code, 201, response.data)
261268

@@ -264,9 +271,9 @@ def test_request_limit_cpu(self, mock_requests):
264271
self.assertEqual(response.status_code, 200, response.data)
265272
cpu = response.data['cpu']
266273
self.assertIn('worker', cpu)
267-
self.assertEqual(cpu['worker'], '512m')
274+
self.assertEqual(cpu['worker'], '2000m')
268275
self.assertIn('web', cpu)
269-
self.assertEqual(cpu['web'], '300m')
276+
self.assertEqual(cpu['web'], '3000m')
270277
self.assertIn('db', cpu)
271278
self.assertEqual(cpu['db'], '1')
272279

@@ -283,6 +290,13 @@ def test_request_limit_cpu(self, mock_requests):
283290
body = {'cpu': json.dumps(mem)}
284291
response = self.client.post(url, body)
285292
self.assertEqual(response.status_code, 400, response.data)
293+
# out of range
294+
body = {'cpu': json.dumps({'web': '1000'})}
295+
response = self.client.post(url, body)
296+
self.assertEqual(response.status_code, 400, response.data)
297+
body = {'cpu': json.dumps({'web': '100m'})}
298+
response = self.client.post(url, body)
299+
self.assertEqual(response.status_code, 400, response.data)
286300

287301
mem = {'w3&b': '1G'}
288302
body = {'cpu': json.dumps(mem)}

0 commit comments

Comments
 (0)