Skip to content

Commit 0aec26a

Browse files
authored
Merge pull request #1149 from rvadim/default-resource-quota-support
Add basic support of resource quotas for applications
2 parents 7bfd858 + e332ace commit 0aec26a

6 files changed

Lines changed: 98 additions & 1 deletion

File tree

rootfs/api/models/app.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ def create(self, *args, **kwargs): # noqa
203203
# create required minimum resources in k8s for the application
204204
namespace = self.id
205205
service = self.id
206+
quota_name = '{}-quota'.format(self.id)
206207
try:
207208
self.log('creating Namespace {} and services'.format(namespace), level=logging.DEBUG)
208209
# Create essential resources
@@ -211,6 +212,15 @@ def create(self, *args, **kwargs): # noqa
211212
except KubeException:
212213
self._scheduler.ns.create(namespace)
213214

215+
if settings.KUBERNETES_NAMESPACE_DEFAULT_QUOTA_SPEC != '':
216+
quota_spec = json.loads(settings.KUBERNETES_NAMESPACE_DEFAULT_QUOTA_SPEC)
217+
self.log('creating Quota {} for namespace {}'.format(quota_name, namespace),
218+
level=logging.DEBUG)
219+
try:
220+
self._scheduler.quota.get(namespace, quota_name)
221+
except KubeException:
222+
self._scheduler.quota.create(namespace, quota_name, data=quota_spec)
223+
214224
try:
215225
self._scheduler.svc.get(namespace, service)
216226
except KubeException:

rootfs/api/settings/production.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,9 @@
299299
# How long k8s waits for a pod to finish work after a SIGTERM before sending SIGKILL
300300
KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS = int(os.environ.get('KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS', 30)) # noqa
301301

302+
# Default quota spec for application namespace
303+
KUBERNETES_NAMESPACE_DEFAULT_QUOTA_SPEC = os.environ.get('KUBERNETES_NAMESPACE_DEFAULT_QUOTA_SPEC', '') # noqa
304+
302305
# registry settings
303306
REGISTRY_HOST = os.environ.get('DEIS_REGISTRY_SERVICE_HOST', '127.0.0.1')
304307
REGISTRY_PORT = os.environ.get('DEIS_REGISTRY_SERVICE_PORT', 5000)

rootfs/api/settings/testing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@
3636

3737
# How long k8s waits for a pod to finish work after a SIGTERM before sending SIGKILL
3838
KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS = int(os.environ.get('KUBERNETES_POD_TERMINATION_GRACE_PERIOD_SECONDS', 2)) # noqa
39+
KUBERNETES_NAMESPACE_DEFAULT_QUOTA_SPEC = '{"spec":{"hard":{"pods":"10"}}}'

rootfs/scheduler/mock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _acquire(self):
8080
resources = [
8181
'namespaces', 'nodes', 'pods', 'replicationcontrollers',
8282
'secrets', 'services', 'events', 'deployments', 'replicasets',
83-
'horizontalpodautoscalers', 'scale',
83+
'horizontalpodautoscalers', 'scale', 'resourcequotas'
8484
]
8585

8686

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from scheduler.exceptions import KubeHTTPException
2+
from scheduler.resources import Resource
3+
from scheduler.utils import dict_merge
4+
5+
6+
class Quota(Resource):
7+
short_name = 'quota'
8+
9+
def get(self, namespace_name, name):
10+
"""
11+
Fetch a single quota
12+
"""
13+
url = '/namespaces/{}/resourcequotas/{}'.format(namespace_name, name)
14+
message = 'get quota {} for namespace {}'.format(name, namespace_name)
15+
url = self.api(url)
16+
response = self.http_get(url)
17+
if self.unhealthy(response.status_code):
18+
raise KubeHTTPException(response, message)
19+
20+
return response
21+
22+
def create(self, namespace_name, name, **kwargs):
23+
"""
24+
Create resource quota for namespace
25+
"""
26+
url = self.api("/namespaces/{}/resourcequotas".format(namespace_name))
27+
manifest = {
28+
"kind": "ResourceQuota",
29+
"apiVersion": "v1",
30+
"metadata": {
31+
"namespace": namespace_name,
32+
"name": name,
33+
'labels': {
34+
'app': namespace_name,
35+
'heritage': 'deis'
36+
},
37+
},
38+
'spec': {}
39+
}
40+
41+
data = dict_merge(manifest, kwargs.get('data', {}))
42+
response = self.http_post(url, json=data)
43+
if not response.status_code == 201:
44+
raise KubeHTTPException(response,
45+
"create quota {} for namespace {}".format(
46+
name, namespace_name))
47+
48+
return response
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
Unit tests for the Deis scheduler module.
3+
4+
Run the tests with './manage.py test scheduler'
5+
"""
6+
from scheduler.tests import TestCase
7+
from scheduler import KubeHTTPException
8+
9+
10+
class QuotaTest(TestCase):
11+
12+
def test_create_quota(self):
13+
namespace_name = self.create_namespace()
14+
quota = {
15+
'spec': {
16+
'hard': {
17+
'cpu': '3',
18+
'pods': '10',
19+
'secrets': '5'
20+
}
21+
}
22+
}
23+
self.scheduler.quota.create(namespace_name, 'test1', data=quota)
24+
25+
response = self.scheduler.quota.get(namespace_name, 'test1')
26+
data = response.json()
27+
self.assertEqual(data.get('spec', {}), quota['spec'])
28+
self.assertEqual(data['metadata']['namespace'], namespace_name)
29+
30+
def test_create_with_nonexistent_namespace(self):
31+
with self.assertRaises(
32+
KubeHTTPException,
33+
msg='failed to create quota test1 for namespace ghost-namespace: 404 Not Found'
34+
):
35+
self.scheduler.quota.create('ghost-namespace', 'test1', data={})

0 commit comments

Comments
 (0)