async def pipe_spawner_progress(dashboard_user, new_server_name, builder): while True: await sleep(0.01) if builder._build_future.done(): break if new_server_name in dashboard_user.spawners and dashboard_user.spawners[new_server_name].pending \ and dashboard_user.spawners[new_server_name]._spawn_future: spawner = dashboard_user.spawners[new_server_name] app_log.debug('Found spawner for progress') async with aclosing( iterate_until( builder._build_future, spawner._generate_progress())) as spawnevents: try: async for event in spawnevents: if 'message' in event: builder.add_progress_event({ 'progress': 95, 'message': 'Spawner progress: {}'.format( event['message']) }) except CancelledError: pass break
async def get(self, dashboard_urlname=''): self.set_header('Cache-Control', 'no-cache') current_user = await self.get_current_user() dashboard = self.db.query(Dashboard).filter( Dashboard.urlname == dashboard_urlname).one_or_none() if dashboard is None: raise HTTPError(404, 'No such dashboard or user') if not dashboard.is_orm_user_allowed(current_user.orm_user): raise HTTPError( 403, 'User {} not authorized to access dashboard {}'.format( current_user.name, dashboard.urlname)) # start sending keepalive to avoid proxies closing the connection asyncio.ensure_future(self.keepalive()) # cases: # - spawner already started and ready # - spawner not running at all # - spawner failed # - spawner pending start (what we expect) def ready_event(dashboard): url = url_path_join(self.settings['base_url'], "user", dashboard.user.name, dashboard.final_spawner.name) return { 'progress': 100, 'ready': True, 'message': "Redirecting to server at {}".format(url), 'html_message': 'Redirecting to server at <a href="{0}" target="_blank">{0}</a>' .format(url), 'url': url, } failed_event = { 'progress': 100, 'failed': True, 'message': "Build failed or unable to get progress", 'url': url_path_join(self.settings['base_url'], "hub", "dashboards", dashboard.urlname, 'clear-error') } builders_store = BuildersStore.get_instance(self.settings['config']) builder = builders_store[dashboard] if builder.ready: # spawner already ready. Trigger progress-completion immediately self.log.info("Server %s is already started", builder._log_name) await self.send_event(ready_event(dashboard)) return build_future = builder._build_future if not builder._build_pending: # not pending, no progress to fetch # check if spawner has just failed f = build_future if f and f.done() and f.exception(): failed_event['message'] = "Build failed: %s" % f.exception() await self.send_event(failed_event) return else: raise HTTPError(400, "%s is not starting...", builder._log_name) if build_future: # Just in case build_future is None so iterate_until's asyncio.wait doesn't fail; # builder._generate_progress should return an iterator because _build_pending was non-None # just above, with no awaits since # Retrieve progress events from the Builder async with aclosing( iterate_until(build_future, builder._generate_progress())) as events: try: async for event in events: # don't allow events to sneakily set the 'ready' flag if 'ready' in event: event.pop('ready', None) await self.send_event(event) except asyncio.CancelledError: pass # progress finished, wait for spawn to actually resolve, # in case progress finished early # (ignore errors, which will be logged elsewhere) await build_future # progress and spawn finished, check if spawn succeeded if builder.ready: # spawner is ready, signal completion and redirect self.log.info("Server %s is ready", builder._log_name) await self.send_event(ready_event(dashboard)) else: # what happened? Maybe spawn failed? f = build_future if f and f.done() and f.exception(): failed_event['message'] = "Build failed: %s" % f.exception() else: self.log.warning("Server %s didn't start for unknown reason", builder._log_name) await self.send_event(failed_event)