diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py index c6d62082..2360af04 100644 --- a/jupyterhub/spawner.py +++ b/jupyterhub/spawner.py @@ -52,6 +52,14 @@ class Spawner(LoggingConfigurable): authenticator = Any() api_token = Unicode() + will_resume = Bool(False, + help="""Whether the Spawner will resume on next start + + If a Spawner will resume instead of starting anew (e.g. resuming a Docker container), + API tokens in use when the Spawner stops will not be deleted. + """ + ) + ip = Unicode('127.0.0.1', help=""" The IP address (or hostname) the single-user server should listen on. diff --git a/jupyterhub/user.py b/jupyterhub/user.py index eed8216f..3079c12f 100644 --- a/jupyterhub/user.py +++ b/jupyterhub/user.py @@ -234,6 +234,12 @@ class User(HasTraits): # prior to 0.7, spawners had to store this info in user.server themselves. # Handle < 0.7 behavior with a warning, assuming info was stored in db by the Spawner. self.log.warning("DEPRECATION: Spawner.start should return (ip, port) in JupyterHub >= 0.7") + if spawner.api_token != api_token: + # Spawner re-used an API token, discard the one we just created + orm_token = orm.APIToken.find(self.db, api_token) + if orm_token is not None: + self.db.delete(orm_token) + self.db.commit() except Exception as e: if isinstance(e, gen.TimeoutError): self.log.warning("{user}'s server failed to start in {s} seconds, giving up".format( @@ -313,10 +319,11 @@ class User(HasTraits): if self.server: # cleanup server entry from db self.db.delete(self.server) - orm_token = orm.APIToken.find(self.db, api_token) - if orm_token: - self.db.delete(orm_token) self.server = None + if not spawner.will_resume: + orm_token = orm.APIToken.find(self.db, api_token) + if orm_token: + self.db.delete(orm_token) self.db.commit() finally: self.stop_pending = False