@@ -226,51 +226,12 @@ jupyterhub:
226
226
'admin': True,
227
227
'command': ['python3', '-m', 'jupyterhub_idle_culler',
228
228
'--url=http://127.0.0.1:8081/hub/hub/api',
229
- '--timeout=604800 ',
229
+ '--timeout=2500000 ',
230
230
'--cull-every=86400',
231
231
'--concurrency=4',
232
232
'--cull-users'
233
233
]})
234
234
235
- delete_user_pvc : |
236
- import string
237
- import escapism
238
- import oauthenticator
239
- import kubernetes.client
240
- import tornado.concurrent
241
- import concurrent.futures
242
-
243
- pvc_name_template = get_config('singleuser.storage.dynamic.pvcNameTemplate')
244
- pvc_namespace = os.environ.get('POD_NAMESPACE', 'default')
245
-
246
- class BinderAuthenticator(oauthenticator.GoogleOAuthenticator):
247
- executor = concurrent.futures.ThreadPoolExecutor(1)
248
-
249
- @tornado.concurrent.run_on_executor
250
- def delete_pvc(self, user):
251
- safe_chars = set(string.ascii_lowercase + string.digits)
252
- legacy_escaped_username = ''.join([s if s in safe_chars else '-' for s in user.name.lower()])
253
- safe_username = escapism.escape(user.name, safe=safe_chars, escape_char='-').lower()
254
- name = pvc_name_template.format(
255
- userid=user.id,
256
- username=safe_username,
257
- unescaped_username=user.name,
258
- legacy_escape_username=legacy_escaped_username,
259
- servername='',
260
- unescaped_servername=''
261
- )
262
- try:
263
- kubernetes.client.CoreV1Api().delete_namespaced_persistent_volume_claim(name, pvc_namespace, body=kubernetes.client.V1DeleteOptions())
264
- except kubernetes.client.rest.ApiException as e:
265
- if e.status != 404:
266
- self.log.warn("Error deleting user PVC %s: %s", name, e)
267
-
268
- def delete_user(self, user):
269
- self.delete_pvc(user)
270
- return super().delete_user(user)
271
-
272
- c.JupyterHub.authenticator_class = BinderAuthenticator
273
-
274
235
monitor : |
275
236
from jupyterhub.handlers import BaseHandler
276
237
import prometheus_client
@@ -279,10 +240,73 @@ jupyterhub:
279
240
import datetime
280
241
from kubespawner.clients import shared_client
281
242
from kubespawner import KubeSpawner
282
- from kubernetes.dynamic import DynamicClient
283
- from kubernetes.dynamic.exceptions import ResourceNotFoundError
284
- from kubernetes.client.exceptions import ApiException
285
- from kubernetes.utils import parse_quantity
243
+ #from kubernetes_asyncio.dynamic import DynamicClient
244
+ #from kubernetes_asyncio.dynamic.exceptions import ResourceNotFoundError
245
+ from kubernetes_asyncio.client.exceptions import ApiException
246
+ #from kubernetes_asyncio.utils import parse_quantity
247
+
248
+ # https://github.com/kubernetes-client/python/blob/master/kubernetes/utils/quantity.py
249
+ from decimal import Decimal, InvalidOperation
250
+
251
+ def parse_quantity(quantity):
252
+ """
253
+ Parse kubernetes canonical form quantity like 200Mi to a decimal number.
254
+ Supported SI suffixes:
255
+ base1024: Ki | Mi | Gi | Ti | Pi | Ei
256
+ base1000: n | u | m | "" | k | M | G | T | P | E
257
+
258
+ See https://github.com/kubernetes/apimachinery/blob/master/pkg/api/resource/quantity.go
259
+
260
+ Input:
261
+ quantity: string. kubernetes canonical form quantity
262
+
263
+ Returns:
264
+ Decimal
265
+
266
+ Raises:
267
+ ValueError on invalid or unknown input
268
+ """
269
+ if isinstance(quantity, (int, float, Decimal)):
270
+ return Decimal(quantity)
271
+
272
+ exponents = {"n": -3, "u": -2, "m": -1, "K": 1, "k": 1, "M": 2,
273
+ "G": 3, "T": 4, "P": 5, "E": 6}
274
+
275
+ quantity = str(quantity)
276
+ number = quantity
277
+ suffix = None
278
+ if len(quantity) >= 2 and quantity[-1] == "i":
279
+ if quantity[-2] in exponents:
280
+ number = quantity[:-2]
281
+ suffix = quantity[-2:]
282
+ elif len(quantity) >= 1 and quantity[-1] in exponents:
283
+ number = quantity[:-1]
284
+ suffix = quantity[-1:]
285
+
286
+ try:
287
+ number = Decimal(number)
288
+ except InvalidOperation:
289
+ raise ValueError("Invalid number format: {}".format(number))
290
+
291
+ if suffix is None:
292
+ return number
293
+
294
+ if suffix.endswith("i"):
295
+ base = 1024
296
+ elif len(suffix) == 1:
297
+ base = 1000
298
+ else:
299
+ raise ValueError("{} has unknown suffix".format(quantity))
300
+
301
+ # handle SI inconsistency
302
+ if suffix == "ki":
303
+ raise ValueError("{} has unknown suffix".format(quantity))
304
+
305
+ if suffix[0] not in exponents:
306
+ raise ValueError("{} has unknown suffix".format(quantity))
307
+
308
+ exponent = Decimal(exponents[suffix[0]])
309
+ return number * (base ** exponent)
286
310
287
311
import z2jh
288
312
allowed = list(map(ipaddress.ip_network, z2jh.get_config('config.Monitor.allowed_monitor_ips', [])))
@@ -291,10 +315,11 @@ jupyterhub:
291
315
def collect(self):
292
316
pods = KubeSpawner.reflectors['pods']
293
317
if not pods: return []
294
- try:
295
- api = DynamicClient(shared_client('ApiClient')).resources.get(api_version='metrics.k8s.io/v1beta1', kind='PodMetrics')
296
- except ResourceNotFoundError:
297
- api = None
318
+ #try:
319
+ # api = DynamicClient(shared_client('ApiClient')).resources.get(api_version='metrics.k8s.io/v1beta1', kind='PodMetrics')
320
+ #except ResourceNotFoundError:
321
+ # api = None
322
+ api = None
298
323
299
324
l = ['user','specuser','specproj']
300
325
run = prometheus_client.metrics_core.GaugeMetricFamily('binder_running_servers', 'Running binderhub jupyter servers', labels=l)
0 commit comments