Repository: jupyterhub/jupyterhub

Issue:
'Stop server' raises an alert if it gets back a 202 Accepted
If stopping a server is slow, the JupyterHub API responds with a '202 Accepted'. The UI should do something with this - right now it just throws up an error box.


Discussion:

Resolving patch:
diff --git a/jupyterhub/apihandlers/users.py b/jupyterhub/apihandlers/users.py
--- a/jupyterhub/apihandlers/users.py
+++ b/jupyterhub/apihandlers/users.py
@@ -156,9 +156,9 @@ def delete(self, name):
         yield gen.maybe_future(self.authenticator.delete_user(user))
         # remove from registry
         del self.users[user]
-        
+
         self.set_status(204)
-    
+
     @admin_only
     def patch(self, name):
         user = self.find_user(name)
@@ -174,88 +174,55 @@ def patch(self, name):
             setattr(user, key, value)
         self.db.commit()
         self.write(json.dumps(self.user_model(user)))
-        
+
 
 class UserServerAPIHandler(APIHandler):
-    """Create and delete single-user servers
-    
-    This handler should be used when c.JupyterHub.allow_named_servers = False
-    """
+    """Start and stop single-user servers"""
     @gen.coroutine
     @admin_or_self
-    def post(self, name):
+    def post(self, name, server_name=''):
         user = self.find_user(name)
-        if user.running:
+        if server_name:
+            if not self.allow_named_servers:
+                raise web.HTTPError(400, "Named servers are not enabled.")
+        spawner = user.spawners[server_name]
+        if spawner.ready:
             # include notify, so that a server that died is noticed immediately
-            state = yield user.spawner.poll_and_notify()
+            state = yield spawner.poll_and_notify()
             if state is None:
-                raise web.HTTPError(400, "%s's server is already running" % name)
+                raise web.HTTPError(400, "%s's server %s is already running" % (name, server_name))
 
         options = self.get_json_body()
-        yield self.spawn_single_user(user, options=options)
-        status = 202 if user.spawner._spawn_pending else 201
+        yield self.spawn_single_user(user, server_name, options=options)
+        status = 202 if spawner.pending == 'spawn' else 201
+        self.set_header('Content-Type', 'text/plain')
         self.set_status(status)
 
     @gen.coroutine
     @admin_or_self
-    def delete(self, name):
+    def delete(self, name, server_name=''):
         user = self.find_user(name)
-        if user.spawner._stop_pending:
-            self.set_status(202)
-            return
-        if not user.running:
-            raise web.HTTPError(400, "%s's server is not running" % name)
-        # include notify, so that a server that died is noticed immediately
-        status = yield user.spawner.poll_and_notify()
-        if status is not None:
-            raise web.HTTPError(400, "%s's server is not running" % name)
-        yield self.stop_single_user(user)
-        status = 202 if user.spawner._stop_pending else 204
-        self.set_status(status)
-
+        if server_name:
+            if not self.allow_named_servers:
+                raise web.HTTPError(400, "Named servers are not enabled.")
+            if server_name not in user.spawners:
+                raise web.HTTPError(404, "%s has no server named '%s'" % (name, server_name))
 
-class UserNamedServerAPIHandler(APIHandler):
-    """Manage named single-user servers
-    
-    This handler should be used when c.JupyterHub.allow_named_servers = True
-    """
-    @gen.coroutine
-    @admin_or_self
-    def post(self, name, server_name):
-        if not self.allow_named_servers:
-            raise web.HTTPError(400, "Named servers are not enabled.")
-        user = self.find_user(name)
-        if user is None:
-            raise web.HTTPError(404, "No such user '%s'" % name)
-        
-        options = self.get_json_body()
-        yield self.spawn_single_user(user, server_name, options=options)
         spawner = user.spawners[server_name]
-        status = 202 if spawner._spawn_pending else 201
-        self.set_status(status)
 
-    @gen.coroutine
-    @admin_or_self
-    def delete(self, name, server_name):
-        if not self.allow_named_servers:
-            raise web.HTTPError(400, "Named servers are not enabled.")
-        user = self.find_user(name)
-        if user is None:
-            raise web.HTTPError(404, "No such user '%s'" % name)
-        if server_name not in user.spawners:
-            raise web.HTTPError(404, "%s has no server named '%s'" % (name, server_name))
-        spawner = user.spawners[server_name]
         if spawner._stop_pending:
+            self.set_header('Content-Type', 'text/plain')
             self.set_status(202)
             return
         if not spawner.ready:
-           raise web.HTTPError(400, "%s's server %s is not running" % (name, server_name))
+            raise web.HTTPError(400, "%s's server %s is not running" % (name, server_name))
         # include notify, so that a server that died is noticed immediately
         status = yield spawner.poll_and_notify()
         if status is not None:
             raise web.HTTPError(400, "%s's server %s is not running" % (name, server_name))
         yield self.stop_single_user(user, server_name)
         status = 202 if spawner._stop_pending else 204
+        self.set_header('Content-Type', 'text/plain')
         self.set_status(status)
 
 
@@ -284,6 +251,6 @@ def post(self, name):
     (r"/api/users", UserListAPIHandler),
     (r"/api/users/([^/]+)", UserAPIHandler),
     (r"/api/users/([^/]+)/server", UserServerAPIHandler),
-    (r"/api/users/([^/]+)/servers/([^/]*)", UserNamedServerAPIHandler),
+    (r"/api/users/([^/]+)/servers/([^/]*)", UserServerAPIHandler),
     (r"/api/users/([^/]+)/admin-access", UserAdminAccessAPIHandler),
 ]


Test patch: