Skip to content

Commit 8a61e63

Browse files
committed
feat(scheduler): add support for set based requirement filtering via the kubernetes API
Makes it possible to use a notin() and in() filter. This can be achieved by using this syntax: version__notin(v3, v4, v5) or type__in(cmd, web, public)
1 parent e9cd9b2 commit 8a61e63

3 files changed

Lines changed: 63 additions & 15 deletions

File tree

rootfs/api/models/release.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,7 @@ def cleanup_old(self):
258258
)
259259

260260
# Cleanup controllers
261-
labels = {
262-
'heritage': 'deis'
263-
}
261+
labels = {'heritage': 'deis'}
264262
controller_removal = []
265263
controllers = self._scheduler.get_rcs(self.app.id, labels=labels).json()
266264
for controller in controllers['items']:
@@ -299,9 +297,7 @@ def cleanup_old(self):
299297
self._scheduler.delete_secret(self.app.id, secret['metadata']['name'])
300298

301299
# Remove stray pods
302-
labels = {
303-
'heritage': 'deis'
304-
}
300+
labels = {'heritage': 'deis'}
305301
pods = self._scheduler.get_pods(self.app.id, labels=labels).json()
306302
for pod in pods['items']:
307303
if self._scheduler.pod_deleted(pod):

rootfs/scheduler/__init__.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -605,9 +605,24 @@ def _selectors(self, **kwargs):
605605
# labels and fields are encoded slightly differently than python-requests can do
606606
labels = kwargs.get('labels', {})
607607
if labels:
608-
# http://kubernetes.io/v1.1/docs/user-guide/labels.html#list-and-watch-filtering
609-
labels = ['{}={}'.format(key, value) for key, value in labels.items()]
610-
query['labelSelector'] = ','.join(labels)
608+
selectors = []
609+
for key, value in labels.items():
610+
# http://kubernetes.io/docs/user-guide/labels/#set-based-requirement
611+
if '__notin' in key:
612+
key = key.replace('__notin', '')
613+
selectors.append('{} notin({})'.format(key, ','.join(value)))
614+
# list is automagically a in()
615+
elif '__in' in key or isinstance(value, list):
616+
key = key.replace('__in', '')
617+
selectors.append('{} in({})'.format(key, ','.join(value)))
618+
elif value is None:
619+
# allowing a check if a label exists (or not) without caring about value
620+
selectors.append(key)
621+
# http://kubernetes.io/docs/user-guide/labels/#equality-based-requirement
622+
elif isinstance(value, str):
623+
selectors.append('{}={}'.format(key, value))
624+
625+
query['labelSelector'] = ','.join(selectors)
611626

612627
fields = kwargs.get('fields', {})
613628
if fields:

rootfs/scheduler/mock.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from urllib.parse import urlparse, parse_qs
55
import string
66
import random
7+
import re
78
import time
89

910
from . import KubeHTTPClient, KubeHTTPException
@@ -275,8 +276,9 @@ def upsert_pods(controller, url):
275276

276277
def filter_data(filters, path):
277278
data = []
278-
for item in cache.get(path, []):
279-
item = cache.get(item)
279+
rows = cache.get(path, [])
280+
for row in rows:
281+
item = cache.get(row)
280282
if not item:
281283
# item is broken
282284
continue
@@ -288,7 +290,25 @@ def filter_data(filters, path):
288290
# Do extra filtering based on labelSelector
289291
add = True
290292
for label, value in filters['labels'].items():
291-
if (
293+
# set based filter
294+
if '__' in label:
295+
label, matcher = label.split('__')
296+
if matcher == 'in':
297+
if (
298+
label not in item['metadata']['labels'] or
299+
item['metadata']['labels'][label] not in value
300+
):
301+
add = False
302+
continue
303+
elif matcher == 'notin':
304+
if (
305+
label not in item['metadata']['labels'] or
306+
item['metadata']['labels'][label] in value
307+
):
308+
add = False
309+
continue
310+
311+
elif (
292312
label not in item['metadata']['labels'] or
293313
item['metadata']['labels'][label] != value
294314
):
@@ -325,12 +345,29 @@ def fetch_all(request, context):
325345
def prepare_query_filters(query):
326346
filters = {'labels': {}, 'fields': {}}
327347
if query:
348+
# set based regex - does not support only field
349+
labelRegex = re.compile('^(?P<label>.*) (?P<matcher>notin|in)\s?\((?P<values>.*)\)$')
350+
328351
queries = parse_qs(query)
329352
if 'labelSelector' in queries:
330353
for items in queries['labelSelector']:
331-
for item in items.split(','):
332-
key, value = item.split('=')
333-
filters['labels'][key] = value
354+
# split on , but not inside ()
355+
r = re.compile(r'(?:[^,(]|\([^)]*\))+')
356+
for item in r.findall(items):
357+
if '=' in item:
358+
# equal based requirement
359+
key, value = item.split('=')
360+
filters['labels'][key] = value
361+
else:
362+
# set based requirement
363+
matches = labelRegex.match(item)
364+
if matches is None:
365+
continue
366+
367+
# split and strip spaces
368+
values = [x.strip() for x in matches.group('values').split(',')]
369+
key = matches.group('label') + '__' + matches.group('matcher')
370+
filters['labels'][key] = values
334371

335372
if 'fieldSelector' in queries:
336373
for items in queries['fieldSelector']:

0 commit comments

Comments
 (0)