diff --git a/examples/cull-idle/cull_idle_servers.py b/examples/cull-idle/cull_idle_servers.py index 8a4dfbf7..3ab21095 100644 --- a/examples/cull-idle/cull_idle_servers.py +++ b/examples/cull-idle/cull_idle_servers.py @@ -40,8 +40,11 @@ from tornado.options import define, options, parse_command_line @coroutine -def cull_idle(url, api_token, timeout): - """cull idle single-user servers""" +def cull_idle(url, api_token, timeout, cull_users=False): + """Shutdown idle single-user servers + + If cull_users, inactive *users* will be deleted as well. + """ auth_header = { 'Authorization': 'token %s' % api_token } @@ -54,26 +57,50 @@ def cull_idle(url, api_token, timeout): resp = yield client.fetch(req) users = json.loads(resp.body.decode('utf8', 'replace')) futures = [] - for user in users: - last_activity = parse_date(user['last_activity']) - if user['server'] and last_activity < cull_limit: - app_log.info("Culling %s (inactive since %s)", user['name'], last_activity) + + @coroutine + def cull_one(user, last_activity): + """cull one user""" + + # shutdown server first. Hub doesn't allow deleting users with running servers. + if user['server']: + app_log.info("Culling server for %s (inactive since %s)", user['name'], last_activity) req = HTTPRequest(url=url + '/users/%s/server' % user['name'], method='DELETE', headers=auth_header, ) - futures.append((user['name'], client.fetch(req))) - elif user['server'] and last_activity > cull_limit: + yield client.fetch(req) + if cull_users: + app_log.info("Culling user %s (inactive since %s)", user['name'], last_activity) + req = HTTPRequest(url=url + '/users/%s' % user['name'], + method='DELETE', + headers=auth_header, + ) + yield client.fetch(req) + + for user in users: + if not user['server'] and not cull_users: + # server not running and not culling users, nothing to do + continue + last_activity = parse_date(user['last_activity']) + if last_activity < cull_limit: + futures.append((user['name'], cull_one(user, last_activity))) + else: app_log.debug("Not culling %s (active since %s)", user['name'], last_activity) for (name, f) in futures: yield f app_log.debug("Finished culling %s", name) + if __name__ == '__main__': define('url', default=os.environ.get('JUPYTERHUB_API_URL'), help="The JupyterHub API URL") define('timeout', default=600, help="The idle timeout (in seconds)") define('cull_every', default=0, help="The interval (in seconds) for checking for idle servers to cull") + define('cull_users', default=False, + help="""Cull users in addition to servers. + This is for use in temporary-user cases such as tmpnb.""", + ) parse_command_line() if not options.cull_every: @@ -82,7 +109,7 @@ if __name__ == '__main__': api_token = os.environ['JUPYTERHUB_API_TOKEN'] loop = IOLoop.current() - cull = lambda : cull_idle(options.url, api_token, options.timeout) + cull = lambda : cull_idle(options.url, api_token, options.timeout, options.cull_users) # run once before scheduling periodic call loop.run_sync(cull) # schedule periodic cull