Ejemplo n.º 1
0
def main():

    tornado.options.define('base_path', default='/',
            help="Base path for the server (e.g. /singlecell)"
    )
    tornado.options.parse_command_line()
    opts = tornado.options.options

    kernel_manager = MultiKernelManager()
    
	
    # we are only using one kernel:
    kernel_id = '1'
    kernel_manager.start_kernel(kernel_id=kernel_id)
    
    logging.basicConfig(level=logging.INFO)
    app = WebApp(kernel_manager, opts.base_path)

    server = httpserver.HTTPServer(app)
    server.listen(8888, '0.0.0.0')

    try:
        ioloop.IOLoop.instance().start()
    except KeyboardInterrupt:
        app_log.info("Interrupted...")
    finally:
        kernel_manager.shutdown_all()
Ejemplo n.º 2
0
 def on_close(self):
     app_log.info(f'close socket {self}')
     if self in self.sockets:
         self.sockets.remove(self)
     for i in self.subscriptions:
         self.subscriptions[i].dispose()
     self.subscriptions = {}
Ejemplo n.º 3
0
 def get(self):
     """
     用户使用QQ第三方登录,获取登录地址并跳转
     """
     request_url = self.__build_auth_url()
     app_log.info("tencent auth_code: {0}".format(request_url))
     self.redirect(request_url)
Ejemplo n.º 4
0
def main():
    handlers = [
        (r"/", RootHandler),
        (r"/login", GitHubLoginHandler),
        (r"/oauth", GitHubOAuthHandler)
    ]

    settings = dict(
        cookie_secret=str(base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), 'utf-8'),
        login_url="/login",
        xsrf_cookies=True,
        github_client_id=os.environ["GITHUB_CLIENT_ID"],
        github_client_secret=os.environ["GITHUB_CLIENT_SECRET"],
	    github_redirect_uri=os.environ["GITHUB_REDIRECT_URI"],
        github_scope="",
        debug=True,
        autoescape=None
    )
    
    port = 8088
    
    app_log.info("Listening on {}".format(port))

    application = tornado.web.Application(handlers, **settings)
    application.listen(port)
    tornado.ioloop.IOLoop().instance().start()
Ejemplo n.º 5
0
    def get_data(cls, account, source_filter, limit=100, skip=0):
        """
        Gathers card information from Google Sheets
        GET https://spreadsheets.google.com/feeds/list/[spreadsheet]/[worksheet]/private/full
        """
        if not account or not account.enabled:
            raise ValueError('cannot gather information without an account')
        client = AsyncHTTPClient()

        if source_filter.spreadsheet is None:
            raise ValueError('required parameter spreadsheet missing')
        if source_filter.worksheet is None:
            raise ValueError('required parameter worksheet missing')
        uri = "https://docs.google.com/spreadsheets/d/{}/export?format=csv&gid={}".format(
            source_filter.spreadsheet, source_filter.worksheet
        )

        app_log.info(
            "Start retrieval of worksheet {}/{} for {}".format(source_filter.spreadsheet, source_filter.worksheet,
                                                               account._id))

        lock = Condition()
        oauth_client = account.get_client()
        uri, headers, body = oauth_client.add_token(uri)
        req = HTTPRequest(uri, headers=headers, body=body, streaming_callback=lambda c: cls.write(c))

        client.fetch(req, callback=lambda r: lock.notify())
        yield lock.wait(timeout=timedelta(seconds=MAXIMUM_REQ_TIME))

        app_log.info(
            "Finished retrieving worksheet for {}".format(account._id))
Ejemplo n.º 6
0
    def get_places(self, ll, q):
        url = FacebookComm.BASE_URL.format(endpoint=FacebookComm.SEARCH_ENDPOINT)
        place = None

        try:
            url += '&type=place&center={ll}&distance=100&q={q}'.format(ll=ll, q=q)

            log.info('Fetching Facebook places from [{0}]'.format(url))
            request = HTTPRequest(url=url, connect_timeout=options.http_request_timeout, request_timeout=options.http_request_timeout)
            response = yield self.client.fetch(request)

            if response.code != 200:
                raise FacebookError(response.code)

            body = json.loads(response.body)

            places = body['data']
            if len(places) > 0:
                place = places[0]

        except HTTPError as e:
            log.error('Facebook error [{0}] while calling [{1}]!'.format(e, url))
            raise Return(None)

        raise Return(place)
Ejemplo n.º 7
0
    def get_unknown_venues_events(self, venues):
        ''' Fetch all events for a list of foursquare venue names '''
        search_url = FacebookComm.BASE_URL.format(endpoint=FacebookComm.SEARCH_ENDPOINT)

        #try matching a foursquare venue to a facebook place
        fb_venues = {}
        tasks = {}

        for vname, venue in venues.iteritems():
            ll = venue['ll']
            tasks[vname] = Task(self.get_places, ll=ll, q=friendly_str(vname))

        log.info('Fetching {0} places from Facebook...'.format(len(tasks.keys())))

        places = yield tasks

        for vname, place in places.iteritems():
            if place is None:
                log.info('Venue {0} not found on Facebook!'.format(friendly_str(vname)))
                continue

            venue = venues[vname]
            fb_venues[place['id']] = venue

        if len(fb_venues.keys()) == 0:
            raise Return({})

        # we have the facebook id, fetch the events
        fb_venues_events = yield self.get_venues_events(fb_venues)
        raise Return(fb_venues_events)
Ejemplo n.º 8
0
 def _start_control(self):
     app_log.info("Starting EE Control on port {port}".format(port=config.Control.Rest.PORT))
     self._control_process = _start_remote_rest_server(config.Control.Rest.BIN, config.Control.Rest.PORT,
                                                       config.Control.Rest.DEBUG)
     if self._control_process.is_running() and self._rest_server_listening(config.Control.Rest.BASE_URI):
         app_log.info("EEControl REST Server running")
     else:
         app_log.error("EEControl REST Server not running")
         exit(1)
     if self._is_engine_supported(
             _get_full_uri(config.Control.Rest.BASE_URI, config.Control.Rest.Endpoints.ENGINES)):
         app_log.info("{engine} supported by EE Control".format(engine=config.Engine.NAME))
     else:
         app_log.error("{engine} is not supported by EE Control".format(engine=config.Engine.NAME))
         exit(1)
     if self._set_engine(_get_full_uri(config.Control.Rest.BASE_URI, config.Control.Rest.Endpoints.ENGINES)):
         app_log.info("{engine} set by EE Control".format(engine=config.Engine.NAME))
     else:
         app_log.error("{engine} not set by EE Control".format(engine=config.Engine.NAME))
         exit(1)
     if self._connect_control():
         app_log.info("EE Control client connected to engine")
     else:
         app_log.error("EE Control client couldn't connect to engine")
         exit(1)
Ejemplo n.º 9
0
    def get(self, file_name, *args, **kwargs):

        if not file_name:
            return self.send_error(status_code=404)

        root_dir = os.path.dirname(os.path.dirname(__file__))
        file_path = os.path.join(root_dir, "../static/download/" + file_name)

        buf_size = 4096
        self.set_header("Content-Type", "application/octet-stream")
        self.set_header("Content-Disposition", "attachment; filename=" + file_name)

        try:
            download_file = open(file_path, "r")

            while True:
                data = download_file.read(buf_size)
                if not data:
                    break
                self.write(data)

            download_file.close()

            app_log.info("[{ip}] download file success: {name}".format(
                ip=self.request.remote_ip, name=file_path
            ))
        except IOError:
            app_log.info("[{ip}] download file fail: {name}".format(
                ip=self.request.remote_ip, name=file_path
            ))
            return self.send_error(status_code=404)

        self.finish()
Ejemplo n.º 10
0
    def start(self):
        # reset all connections
        try:
            app_log.info("Starting Connection Manager!")
            if self.idl is not None:
                self.idl.close()

            # set up the schema and register all tables
            self.schema_helper = SchemaHelper(self.schema)
            self.schema_helper.register_all()
            self.idl = Idl(self.remote, self.schema_helper)
            self.curr_seqno = self.idl.change_seqno

            #  we do not reset transactions when the DB connection goes down
            if self.transactions is None:
                self.transactions = OvsdbTransactionList()

            self.idl_run()
            self.monitor_connection()

        except Exception as e:
            # TODO: log this exception
            # attempt again in the next IOLoop iteration
            app_log.info("Connection Manager failed! Reason: %s" % e)
            IOLoop.current().add_timeout(time.time() + self.timeout,
                                         self.start)
Ejemplo n.º 11
0
    def prepare(self):
        request = self.request
        if request.headers and 'Content-Encoding' in request.headers:
            del request.headers['Content-Encoding']
        request.body_arguments = {}
        request.files = {}
        tornado.httputil.parse_body_arguments(
            request.headers.get("Content-Type", ""), request.body, request.body_arguments, request.files)
        for k, v in request.body_arguments.items():
            request.arguments.setdefault(k, []).extend(v)

        # cookie
        cookieId = self.get_cookie('cookie_id')
        if not cookieId or not Session.getSession(cookieId):
            # create cookie id
            ua = self.request.headers.get("User-Agent", '')
            ip = self.request.remote_ip
            time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            md5 = hashlib.md5()
            md5.update((ua + ip + time).encode())
            cookieId = md5.hexdigest()
            Session.createSession(cookieId)
            app_log.info('set cookie id ' + cookieId)
            self.set_cookie('cookie_id', cookieId, path="/", expires_days=30)

        self.cookieId = cookieId
        self.db = self.settings['db']
Ejemplo n.º 12
0
def render_notebook(exporter, json_as_string, url=None, forced_theme=None):
    app_log.info("rendering %d B notebook from %s", len(json_as_string), url)

    try:
        share_as_object = json.loads(json_as_string)
    except ValueError:
        raise SyntaxError("Notebook does not appear to be JSON: %r" % json_as_string[:16])

    template = None
    if share_as_object.get('notebookModel') and share_as_object.get('cellModel'):
        template = 'old/beaker_section.html'
    elif share_as_object.get('notebookModel'):
        template = 'old/beaker_notebook.html'
    elif share_as_object.get('cellModel'):
        template = 'old/beaker_cell.html'
    elif share_as_object.get('beaker'):
        template = 'beaker_notebook.html'

    name = 'Beaker Notebook'

    config = {
            'download_name': name,
            'css_theme': None,
            'template': template
            }
    return json_as_string, config
Ejemplo n.º 13
0
    def test_put_user(self):
        client = yield self.auth_client()
        user = yield self.create_user(client)

        cases = [
            ("login", "foofoofoo"),
            ("email", "*****@*****.**"),
            ("is_admin", False),
            ("disabled", False),
            ("password", str(uuid.uuid4()))
        ]

        for i in range(1, len(cases)):
            for case in itertools.combinations(cases, i):
                body = dict(case)

                if 'disabled' in body:
                    log.info("Deleting user: %r", user['id'])
                    yield client.fetch(
                        self.get_url("/api/v1/user/{0}".format(user['id'])),
                        "DELETE"
                    )

                log.info("Send body: %r", body)
                response = yield client.fetch(
                    self.get_url("/api/v1/user/{0}".format(user['id'])),
                    "PUT", body
                )

                for k, v in body.items():
                    if k == 'password':
                        continue

                    self.assertIn(k, response.body)
                    self.assertEqual(v, response.body[k])
Ejemplo n.º 14
0
    def test_put_errors(self):
        client = yield self.auth_client()
        user = yield self.create_user(client)

        cases = [
            ("login", False),
            ("login", [2, 3]),
            ("email", "@bbb.com"),
            ("email", {1: 2}),
            ("password", "123"),
            ("password", "1"),
            ("password", [1, '2']),
            ("password", {1: '2'}),
        ]

        for i in range(1, len(cases)):
            for case in itertools.combinations(cases, i):
                body = dict(case)

                with self.assertRaises(HTTPError) as err:
                    log.info("Body: %s", body)
                    yield client.fetch(
                        self.get_url("/api/v1/user/{0}".format(user['id'])),
                        "PUT", body
                    )

                self.assertEqual(err.exception.code, 400)
Ejemplo n.º 15
0
def render_notebook(exporter, nbfile, nb, url=None, config=None):
    if not isinstance(exporter, Exporter):
        exporter_cls = exporter
        if exporter_cls not in exporters:
            app_log.info("instantiating %s" % exporter_cls.__name__)
            exporters[exporter_cls] = exporter_cls(config=config, log=app_log)
        exporter = exporters[exporter_cls]

    css_theme = nb.get('metadata', {}).get('_nbviewer', {}).get('css', None)

    if not css_theme or not css_theme.strip():
        # whitespace
        css_theme = None

    # get the notebook title, if any
    try:
        name = nb.metadata.name
    except AttributeError:
        name = ''

    if not name and url is not None:
        name = url.rsplit('/')[-1]

    if not name.endswith(".ipynb"):
        name = name + ".ipynb"

    html, resources = exporter.from_filename(nbfile)

    config = {
        'download_name': name,
        'css_theme': css_theme,
    }
    return html, config
Ejemplo n.º 16
0
    def get(self, gist_id, file=''):
        new_url = '/gist/%s' % gist_id
        if file:
            new_url = "%s/%s" % (new_url, file)

        app_log.info("Redirecting %s to %s", self.request.uri, new_url)
        self.redirect(new_url)
Ejemplo n.º 17
0
def dealer_process():
    app_log.info('start dealer process')
    log_fds('start')
    log_mem('start')
    s = Storage()
    q = Q()

    while True:
        log_fds('start loop')
        log_mem('start loop')
        domains = yield s.fetch_domains_for_update(options.dealer_domains_per_task)
        if domains and len(domains) < options.dealer_domains_per_task:
            time.sleep(options.dealer_fetch_task_sleep_period_sec)
            domains = yield s.fetch_domains_for_update(options.dealer_domains_per_task)

        if not domains:
            app_log.info("not found domains")
            time.sleep(options.dealer_sleep_period_sec)
            continue

        app_log.info("fetch %d domains for new task" % len(domains))
        res = q.add_crawler_task(domains)
        yield s.update_domains_after_fetch(domains)
        app_log.info("add task %s" % res)
        del domains

    app_log.info('end dealer process')
Ejemplo n.º 18
0
    def _on_pika_message(self, channel, method, props, body):
        log.debug('PikaCient: Message received, delivery tag #%i : %r' % (method.delivery_tag, len(body)))

        correlation_id = getattr(props, 'correlation_id', None)
        if not self.callbacks_hash.has_key(correlation_id) and method.exchange != 'DLX':
            log.info('Got result for task "{0}", but no has callback'.format(correlation_id))
            return

        cb = self.callbacks_hash.pop(correlation_id)
        content_type = getattr(props, 'content_type', 'text/plain')

        if method.exchange == 'DLX':
            dl = props.headers['x-death'][0]
            body = ExpirationError("Dead letter received. Reason: {0}".format(dl.get('reason')))
            body.reason = dl.get('reason')
            body.time = dl.get('time')
            body.expiration = int(dl.get('original-expiration')) / 1000
        else:
            if props.content_encoding == 'gzip':
                body = zlib.decompress(body)

            if 'application/json' in content_type:
                body = json.loads(body)
            elif 'application/python-pickle' in content_type:
                body = pickle.loads(body)

        if isinstance(cb, Future):
            if isinstance(body, Exception):
                cb.set_exception(body)
            else:
                cb.set_result(body)
        else:
            out = cb(body, headers=props.headers)
            return out
Ejemplo n.º 19
0
    def get(self, gist_id, file=''):
        new_url = '%s/gist/%s' % (self.format_prefix, gist_id)
        if file:
            new_url = "%s/%s" % (new_url, file)

        app_log.info("Redirecting %s to %s", self.request.uri, new_url)
        self.redirect(new_url)
Ejemplo n.º 20
0
    def get(self, path=None):
        '''Spawns a brand new server'''

        try:
            if path is None:
                # No path. Assign a prelaunched container from the pool and redirect to it.
                # Append self.redirect_uri to the redirect target.
                container_path = self.pool.acquire().path
                app_log.info("Allocated [%s] from the pool.", container_path)

                url = "/{}/{}".format(container_path, self.redirect_uri)
            else:
                path_parts = path.lstrip('/').split('/', 1)
                container_path = path_parts[0]

                # Scrap a container from the pool and replace it with an ad-hoc replacement.
                # This takes longer, but is necessary to support ad-hoc containers
                yield self.pool.adhoc(container_path)

                app_log.info("Allocated ad-hoc container at [%s].", container_path)
                url = path

            app_log.debug("Redirecting [%s] -> [%s].", self.request.path, url)
            self.redirect(url, permanent=False)
        except spawnpool.EmptyPoolError:
            app_log.warning("The container pool is empty!")
            self.render("full.html", cull_period=self.cull_period)
Ejemplo n.º 21
0
    def release(self, container, replace_if_room=True):
        '''Shut down a container and delete its proxy entry.

        Destroy the container in an orderly fashion. If requested and capacity is remaining, create
        a new one to take its place.'''

        try:
            app_log.info("Releasing container [%s].", container)
            yield [
                self.spawner.shutdown_notebook_server(container.id),
                self._proxy_remove(container.path)
            ]
            app_log.debug("Container [%s] has been released.", container)
        except Exception as e:
            app_log.error("Unable to release container [%s]: %s", container, e)
            return

        if replace_if_room:
            running = yield self.spawner.list_notebook_servers(self.container_config,
                                                                    all=False)
            if len(running) + 1 <= self.capacity:
                app_log.debug("Launching a replacement container.")
                yield self._launch_container()
            else:
                app_log.info("Declining to launch a new container because [%i] containers are" +
                             " already running, and the capacity is [%i].",
                             len(running), self.capacity)
Ejemplo n.º 22
0
    def get(self):
        code = self.get_argument("code", False)
        if not code:
            raise HTTPError(400, "oauth callback made without a token")

        # validate OAuth state
        arg_state = self.get_argument("state", None)
        cookie_state = self.get_secure_cookie(self.hub_auth.state_cookie_name)
        next_url = None
        if arg_state or cookie_state:
            # clear cookie state now that we've consumed it
            self.clear_cookie(self.hub_auth.state_cookie_name)
            if isinstance(cookie_state, bytes):
                cookie_state = cookie_state.decode('ascii', 'replace')
            # check that state matches
            if arg_state != cookie_state:
                app_log.debug("oauth state %r != %r", arg_state, cookie_state)
                raise HTTPError(403, "oauth state does not match")
            next_url = self.hub_auth.get_next_url(cookie_state)
        # TODO: make async (in a Thread?)
        token = self.hub_auth.token_for_code(code)
        user_model = self.hub_auth.user_for_token(token)
        if user_model is None:
            raise HTTPError(500, "oauth callback failed to identify a user")
        app_log.info("Logged-in user %s", user_model)
        self.hub_auth.set_cookie(self, token)
        self.redirect(next_url or self.hub_auth.base_url)
Ejemplo n.º 23
0
    def delete(self, uid, *args, **kwargs):
        # dismiss a user
        try:
            admin = self.session.query(User).filter(
                User.id == self.current_user,
                User.is_admin == 1
            ).one()
        except sqlalchemy.orm.exc.NoResultFound:
            return self.send_error(status_code=404)
        except sqlalchemy.orm.exc.MultipleResultsFound:
            return self.finish("multiple uid %s found" % self.current_user)

        if not uid:
            self.finish("uid needed")

        try:
            user = self.session.query(User).filter(
                User.id == uid,
                User.is_present == 1
            ).one()
            user.is_present = 0
            self.session.commit()

            app_log.info("[{ip}] User Dismiss: {admin} - {user}".format(ip=self.request.remote_ip,
                                                                        admin=admin.email,
                                                                        user=user.email))

            self.finish("ok")
        except sqlalchemy.orm.exc.NoResultFound:
            self.finish("uid %s not found" % self.current_user)
        except sqlalchemy.orm.exc.MultipleResultsFound:
            self.finish("multiple uid %s found" % self.current_user)
Ejemplo n.º 24
0
def cull_idle(url, api_token, timeout):
    """cull idle single-user servers"""
    auth_header = {
            'Authorization': 'token %s' % api_token
        }
    req = HTTPRequest(url=url + '/api/users',
        headers=auth_header,
    )
    now = datetime.datetime.utcnow()
    cull_limit = now - datetime.timedelta(seconds=timeout)
    client = AsyncHTTPClient()
    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)
            req = HTTPRequest(url=url + '/api/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:
            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)
Ejemplo n.º 25
0
def main():
    tornado.options.parse_command_line()
    WebSocket.init_pool()
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(options.port, address=options.listen)
    log.info('Server started {host}:{port}'.format(host=options.listen, port=options.port))
    tornado.ioloop.IOLoop.instance().start()
Ejemplo n.º 26
0
def main():
    options.define('port', default=9005,
        help="Port for the REST API"
    )
    options.define('ip', default='127.0.0.1',
        help="IP address for the REST API"
    )
    options.define('host_mount', default='',
        help='Path where the host root is mounted in this container'
    )
    options.define('pool_prefix', default='',
        help='Prefix assigned by tmpnb to its pooled containers'
    )
    options.define('registration_key', default='',
        help='Registration key required to create new volumes'
    )

    options.parse_command_line()
    opts = options.options

    # regex from docker volume create
    api_handlers = [
        (r'/api/mounts(/([a-zA-Z0-9][a-zA-Z0-9_.-])+)?', MountsHandler),
        (r'/api/volumes', VolumesHander),
    ]

    api_app = web.Application(api_handlers)
    api_app.listen(opts.port, opts.ip, xheaders=True)
    app_log.info("Listening on {}:{}".format(opts.ip, opts.port))

    ioloop.IOLoop.instance().start()
Ejemplo n.º 27
0
 def get(self, user, repo, ref, path):
     raw_url = u"https://raw.github.com/{user}/{repo}/{ref}/{path}".format(
         user=user, repo=repo, ref=ref, path=quote(path)
     )
     blob_url = u"https://github.com/{user}/{repo}/blob/{ref}/{path}".format(
         user=user, repo=repo, ref=ref, path=quote(path),
     )
     with self.catch_client_error():
         response = yield self.client.fetch(raw_url)
     
     if response.effective_url.startswith("https://github.com/{user}/{repo}/tree".format(
         user=user, repo=repo
     )):
         tree_url = "/github/{user}/{repo}/tree/{ref}/{path}/".format(
             user=user, repo=repo, ref=ref, path=quote(path),
         )
         app_log.info("%s is a directory, redirecting to %s", raw_url, tree_url)
         self.redirect(tree_url)
         return
     
     filedata = response.body
     
     if path.endswith('.ipynb'):
         try:
             nbjson = response_text(response)
         except Exception as e:
             app_log.error("Failed to decode notebook: %s", raw_url, exc_info=True)
             raise web.HTTPError(400)
         yield self.finish_notebook(nbjson, raw_url,
             home_url=blob_url,
             msg="file from GitHub: %s" % raw_url,
         )
     else:
         self.set_header("Content-Type", "text/plain")
         self.cache_and_finish(filedata)
Ejemplo n.º 28
0
    def get(self):
        error = self.get_argument("error", False)
        if error:
            msg = self.get_argument("error_description", error)
            raise HTTPError(400, "Error in oauth: %s" % msg)

        code = self.get_argument("code", False)
        if not code:
            raise HTTPError(400, "oauth callback made without a token")

        # validate OAuth state
        arg_state = self.get_argument("state", None)
        if arg_state is None:
            raise HTTPError("oauth state is missing. Try logging in again.")
        cookie_name = self.hub_auth.get_state_cookie_name(arg_state)
        cookie_state = self.get_secure_cookie(cookie_name)
        # clear cookie state now that we've consumed it
        self.clear_cookie(cookie_name, path=self.hub_auth.base_url)
        if isinstance(cookie_state, bytes):
            cookie_state = cookie_state.decode('ascii', 'replace')
        # check that state matches
        if arg_state != cookie_state:
            app_log.warning("oauth state %r != %r", arg_state, cookie_state)
            raise HTTPError(403, "oauth state does not match. Try logging in again.")
        next_url = self.hub_auth.get_next_url(cookie_state)
        # TODO: make async (in a Thread?)
        token = self.hub_auth.token_for_code(code)
        session_id = self.hub_auth.get_session_id(self)
        user_model = self.hub_auth.user_for_token(token, session_id=session_id)
        if user_model is None:
            raise HTTPError(500, "oauth callback failed to identify a user")
        app_log.info("Logged-in user %s", user_model)
        self.hub_auth.set_cookie(self, token)
        self.redirect(next_url or self.hub_auth.base_url)
Ejemplo n.º 29
0
def show(handlers, url_width, handler_width):
    app_log.info("=" * int(url_width + handler_width))
    app_log.info("%-*s%-*s" % (url_width, "URL", handler_width, "HANDLER"))
    app_log.info("=" * int(url_width + handler_width))
    for url, _ in handlers:
        app_log.info("%-*s%-*s" % (url_width, url, handler_width, _))
    app_log.info("=" * int(url_width + handler_width))
Ejemplo n.º 30
0
    def post(self):
        '''
        Creates a new docker volume for the user protected by the given password
        if the proper registration key is provided. Errors if the key is 
        incorrect/missing, if the volume already exists, or if the volume cannot
        be created for any other reason.
        '''
        body = json.loads(self.request.body)
        registration_key = body['registration_key']
        username = body['username']
        password = body['password']

        required_key = options.options.registration_key
        if not required_key or required_key == registration_key:
            # Hash the username as the volume prefix
            volume_prefix = username_to_volume_prefix(username)
            exists = yield find_volume(volume_prefix)
            if exists:
                # Error if the volume already exists
                raise web.HTTPError(409, 'volume %s exists' % volume_prefix)
            volume_suffix = password_to_volume_suffix(password)
            created = yield create_volume(volume_prefix, volume_suffix)
            if not created:
                # Error if volume creation failed
                raise web.HTTPError(500, 'unable to create volume') 
        else:
            raise web.HTTPError(401, 'invalid registration key %s', registration_key)

        app_log.info('created volume prefix %s', volume_prefix)

        # All good if we get here
        self.set_status(201)
        self.finish()
Ejemplo n.º 31
0
def main():
    tornado.options.define('cull_period',
                           default=600,
                           help="Interval (s) for culling idle containers.")
    tornado.options.define('cull_timeout',
                           default=3600,
                           help="Timeout (s) for culling idle containers.")
    tornado.options.define('cull_max',
                           default=14400,
                           help=dedent("""
        Maximum age of a container (s), regardless of activity.

        Default: 14400 (4 hours)

        A container that has been running for this long will be culled,
        even if it is not idle.
        """))
    tornado.options.define(
        'container_ip',
        default='127.0.0.1',
        help="""Host IP address for containers to bind to. If host_network=True,
the host IP address for notebook servers to bind to.""")
    tornado.options.define(
        'container_port',
        default='8888',
        help="""Within container port for notebook servers to bind to.
If host_network=True, the starting port assigned to notebook servers on the host."""
    )
    tornado.options.define(
        'use_tokens',
        default=False,
        help="""Enable token-authentication of notebook servers.
If host_network=True, the starting port assigned to notebook servers on the host."""
    )

    command_default = ('jupyter notebook --no-browser'
                       ' --port {port} --ip=0.0.0.0 --allow-root'
                       ' --NotebookApp.base_url={base_path}'
                       ' --NotebookApp.port_retries=0'
                       ' --NotebookApp.token="{token}"')

    tornado.options.define(
        'command',
        default=command_default,
        help="""Command to run when booting the image. A placeholder for
{base_path} should be provided. A placeholder for {port} and {ip} can be provided."""
    )
    tornado.options.define('port',
                           default=9999,
                           help="port for the main server to listen on")
    tornado.options.define(
        'ip',
        default=None,
        help="ip for the main server to listen on [default: all interfaces]")
    tornado.options.define('admin_port',
                           default=10000,
                           help="port for the admin server to listen on")
    tornado.options.define(
        'admin_ip',
        default='127.0.0.1',
        help="ip for the admin server to listen on [default: 127.0.0.1]")
    tornado.options.define('max_dock_workers',
                           default=2,
                           help="Maximum number of docker workers")
    tornado.options.define('mem_limit',
                           default="512m",
                           help="Limit on Memory, per container")
    tornado.options.define('cpu_shares',
                           default=None,
                           type=int,
                           help="Limit CPU shares, per container")
    tornado.options.define('cpu_quota',
                           default=None,
                           type=int,
                           help=dedent("""
        Limit CPU quota, per container.

        Units are CPU-µs per 100ms, so 1 CPU/container would be:

            --cpu-quota=100000

        """))
    tornado.options.define(
        'image',
        default="wodeai/tensorflow",
        help=
        "Docker container to spawn for new users. Must be on the system already"
    )
    tornado.options.define('docker_version',
                           default="auto",
                           help="Version of the Docker API to use")
    tornado.options.define(
        'redirect_uri',
        default="/tree",
        help="URI to redirect users to upon initial notebook launch")
    tornado.options.define(
        'pool_size',
        default=10,
        help=
        "Capacity for containers on this system. Will be prelaunched at startup."
    )
    tornado.options.define(
        'pool_name',
        default=None,
        help=
        "Container name fragment used to identity containers that belong to this instance."
    )
    tornado.options.define(
        'static_files',
        default=None,
        help="Static files to extract from the initial container launch")
    tornado.options.define(
        'allow_origin',
        default=None,
        help=
        "Set the Access-Control-Allow-Origin header. Use '*' to allow any origin to access."
    )
    tornado.options.define(
        'expose_headers',
        default=None,
        help="Sets the Access-Control-Expose-Headers header.")
    tornado.options.define('max_age',
                           default=None,
                           help="Sets the Access-Control-Max-Age header.")
    tornado.options.define(
        'allow_credentials',
        default=None,
        help="Sets the Access-Control-Allow-Credentials header.")
    tornado.options.define(
        'allow_methods',
        default=None,
        help="Sets the Access-Control-Allow-Methods header.")
    tornado.options.define(
        'allow_headers',
        default=None,
        help="Sets the Access-Control-Allow-Headers header.")
    tornado.options.define('assert_hostname',
                           default=False,
                           help="Verify hostname of Docker daemon.")
    tornado.options.define('container_user',
                           default=None,
                           help="User to run container command as")
    tornado.options.define(
        'host_network',
        default=False,
        help="""Attaches the containers to the host networking instead of the
default docker bridge. Affects the semantics of container_port and container_ip."""
    )
    tornado.options.define(
        'docker_network',
        default=None,
        help="""Attaches the containers to the specified docker network.
        For use when the proxy, tmpnb, and containers are all in docker.""")
    tornado.options.define('host_directories',
                           default=None,
                           help=dedent("""
        Mount the specified directory as a data volume, multiple
        directories can be specified by using a comma-delimited string, directory
        path must provided in full (eg: /home/steve/data/:r), permissions default to
        rw"""))
    tornado.options.define(
        'user_length',
        default=12,
        help="Length of the unique /user/:id path generated per container")
    tornado.options.define('extra_hosts',
                           default=[],
                           multiple=True,
                           help=dedent("""
        Extra hosts for the containers, multiple hosts can be specified
        by using a comma-delimited string, specified in the form hostname:IP"""
                                       ))

    tornado.options.parse_command_line()
    opts = tornado.options.options

    api_token = os.getenv('API_AUTH_TOKEN')
    admin_token = os.getenv('ADMIN_AUTH_TOKEN')
    proxy_token = os.environ['CONFIGPROXY_AUTH_TOKEN']
    proxy_endpoint = os.environ.get('CONFIGPROXY_ENDPOINT',
                                    "http://127.0.0.1:8001")
    docker_host = os.environ.get('DOCKER_HOST', 'unix://var/run/docker.sock')

    handlers = [
        (r"/api/spawn/?", APISpawnHandler),
        (r"/api/stats/?", APIStatsHandler),
        (r"/stats/?", RedirectHandler, {
            "url": "/api/stats"
        }),
    ]

    # Only add human-facing handlers if there's no spawn API key set
    if api_token is None:
        handlers.extend([
            (r"/", LoadingHandler),
            (r"/spawn/?(/user/\w+(?:/.*)?)?", SpawnHandler),
            (r"/spawn/((?:notebooks|tree|files)(?:/.*)?)", SpawnHandler),
            (r"/(user/\w+)(?:/.*)?", LoadingHandler),
            (r"/((?:notebooks|tree|files)(?:/.*)?)", LoadingHandler),
            (r"/info/?", InfoHandler),
        ])

    admin_handlers = [(r"/api/pool/?", APIPoolHandler)]

    max_idle = datetime.timedelta(seconds=opts.cull_timeout)
    max_age = datetime.timedelta(seconds=opts.cull_max)
    pool_name = opts.pool_name
    if pool_name is None:
        # Derive a valid container name from the image name by default.
        pool_name = re.sub('[^a-zA-Z0_.-]+', '', opts.image.split(':')[0])

    container_config = dockworker.ContainerConfig(
        image=opts.image,
        command=opts.command,
        use_tokens=opts.use_tokens,
        mem_limit=opts.mem_limit,
        cpu_quota=opts.cpu_quota,
        cpu_shares=opts.cpu_shares,
        container_ip=opts.container_ip,
        container_port=opts.container_port,
        container_user=opts.container_user,
        host_network=opts.host_network,
        docker_network=opts.docker_network,
        host_directories=opts.host_directories,
        extra_hosts=opts.extra_hosts,
    )

    spawner = dockworker.DockerSpawner(
        docker_host,
        timeout=30,
        version=opts.docker_version,
        max_workers=opts.max_dock_workers,
        assert_hostname=opts.assert_hostname,
    )

    static_path = os.path.join(os.path.dirname(__file__), "static")

    pool = spawnpool.SpawnPool(proxy_endpoint=proxy_endpoint,
                               proxy_token=proxy_token,
                               spawner=spawner,
                               container_config=container_config,
                               capacity=opts.pool_size,
                               max_idle=max_idle,
                               max_age=max_age,
                               static_files=opts.static_files,
                               static_dump_path=static_path,
                               pool_name=pool_name,
                               user_length=opts.user_length)

    ioloop = tornado.ioloop.IOLoop().current()

    settings = dict(
        default_handler_class=BaseHandler,
        static_path=static_path,
        cookie_secret=uuid.uuid4(),
        xsrf_cookies=False,
        debug=True,
        cull_period=opts.cull_period,
        allow_origin=opts.allow_origin,
        expose_headers=opts.expose_headers,
        max_age=opts.max_age,
        allow_credentials=opts.allow_credentials,
        allow_methods=opts.allow_methods,
        allow_headers=opts.allow_headers,
        spawner=spawner,
        pool=pool,
        autoescape=None,
        proxy_token=proxy_token,
        api_token=api_token,
        template_path=os.path.join(os.path.dirname(__file__), 'templates'),
        proxy_endpoint=proxy_endpoint,
        redirect_uri=opts.redirect_uri.lstrip('/'),
    )

    admin_settings = dict(pool=pool, admin_token=admin_token)

    # Cleanup on a fresh state (likely a restart)
    ioloop.run_sync(pool.cleanout)

    # Synchronously cull any existing, inactive containers, and pre-launch a set number of
    # containers, ready to serve.
    ioloop.run_sync(pool.heartbeat)

    if (opts.static_files):
        ioloop.run_sync(pool.copy_static)

    # Periodically execute a heartbeat function to cull used containers and regenerated failed
    # ones, self-healing the cluster.
    cull_ms = opts.cull_period * 1e3
    app_log.info("Culling containers unused for %i seconds every %i seconds.",
                 opts.cull_timeout, opts.cull_period)
    culler = tornado.ioloop.PeriodicCallback(pool.heartbeat, cull_ms)
    culler.start()

    app_log.info("Listening on {}:{}".format(opts.ip or '*', opts.port))
    application = tornado.web.Application(handlers, **settings)
    application.listen(opts.port, opts.ip)

    app_log.info("Admin listening on {}:{}".format(opts.admin_ip or '*',
                                                   opts.admin_port))
    admin_application = tornado.web.Application(admin_handlers,
                                                **admin_settings)
    admin_application.listen(opts.admin_port, opts.admin_ip)

    ioloop.start()
Ejemplo n.º 32
0
 def delete(self):
     '''Drains available containers from the pool.'''
     n = yield self.pool.drain()
     app_log.info('Drained pool of %d containers', n)
     self.finish(dict(drained=n))
Ejemplo n.º 33
0
    def show_dir(self, fullpath, path, **namespace):
        """Render the directory view template for a given filesystem path.

        Parameters
        ==========
        fullpath: string
            Absolute path on disk to show
        path: string
            URL path equating to the path on disk

        Returns
        =======
        str
            Rendered HTML
        """
        entries = []
        dirs = []
        ipynbs = []

        try:
            contents = os.listdir(fullpath)
        except IOError as ex:
            if ex.errno == errno.EACCES:
                # py2/3: can't access the dir, so don't give away its presence
                app_log.info(
                    "contents of path: '%s' cannot be listed from within nbviewer",
                    fullpath)
                raise web.HTTPError(404)

        for f in contents:
            absf = os.path.join(fullpath, f)

            if not self.can_show(absf):
                continue

            entry = {}
            entry['name'] = f

            # We need to make UTC timestamps conform to true ISO-8601 by
            # appending Z(ulu). Without a timezone, the spec says it should be
            # treated as local time which is not what we want and causes
            # moment.js on the frontend to show times in the past or future
            # depending on the user's timezone.
            # https://en.wikipedia.org/wiki/ISO_8601#Time_zone_designators
            if os.path.isdir(absf):
                st = os.stat(absf)
                dt = datetime.utcfromtimestamp(st.st_mtime)
                entry['modtime'] = dt.isoformat() + 'Z'
                entry['url'] = url_path_join(self._localfile_path, path, f)
                entry['class'] = 'fa fa-folder-open'
                dirs.append(entry)
            elif f.endswith('.ipynb'):
                st = os.stat(absf)
                dt = datetime.utcfromtimestamp(st.st_mtime)
                entry['modtime'] = dt.isoformat() + 'Z'
                entry['url'] = url_path_join(self._localfile_path, path, f)
                entry['class'] = 'fa fa-book'
                ipynbs.append(entry)

        dirs.sort(key=lambda e: e['name'])
        ipynbs.sort(key=lambda e: e['name'])

        entries.extend(dirs)
        entries.extend(ipynbs)

        html = self.render_dirview_template(entries=entries,
                                            breadcrumbs=self.breadcrumbs(path),
                                            title=url_path_join(path, '/'),
                                            **namespace)
        return html
Ejemplo n.º 34
0
 def on_close(self):
     if hasattr(self, 'messager'):
         self.messager.destroy()
     Statistics.CONNECTIONS -= 1
     app_log.info('online connections %d', Statistics.CONNECTIONS)
Ejemplo n.º 35
0
 def on_close(self):
     app_log.info('close socket %s', self)
     self.sockets.remove(self)
     for i in self.subscriptions:
         self.subscriptions[i].dispose()
     self.subscriptions = {}
Ejemplo n.º 36
0
    def handle_server(user, server_name, server):
        """Handle (maybe) culling a single server
        Returns True if server is now stopped (user removable),
        False otherwise.
        """
        log_name = user['name']
        if server_name:
            log_name = '%s/%s' % (user['name'], server_name)
        if server.get('pending'):
            app_log.warning("Not culling server %s with pending %s", log_name,
                            server['pending'])
            return False

        # jupyterhub < 0.9 defined 'server.url' once the server was ready
        # as an *implicit* signal that the server was ready.
        # 0.9 adds a dedicated, explicit 'ready' field.
        # By current (0.9) definitions, servers that have no pending
        # events and are not ready shouldn't be in the model,
        # but let's check just to be safe.

        if not server.get('ready', bool(server['url'])):
            app_log.warning("Not culling not-ready not-pending server %s: %s",
                            log_name, server)
            return False

        if server.get('started'):
            age = now - parse_date(server['started'])
        else:
            # started may be undefined on jupyterhub < 0.9
            age = None

        # check last activity
        # last_activity can be None in 0.9
        if server['last_activity']:
            inactive = now - parse_date(server['last_activity'])
        else:
            # no activity yet, use start date
            # last_activity may be None with jupyterhub 0.9,
            # which introduces the 'started' field which is never None
            # for running servers
            inactive = age

        should_cull = (inactive is not None
                       and inactive.total_seconds() >= inactive_limit)
        if should_cull:
            app_log.info("Culling server %s (inactive for %s)", log_name,
                         format_td(inactive))

        if max_age and not should_cull:
            # only check started if max_age is specified
            # so that we can still be compatible with jupyterhub 0.8
            # which doesn't define the 'started' field
            if age is not None and age.total_seconds() >= max_age:
                app_log.info(
                    "Culling server %s (age: %s, inactive for %s)",
                    log_name,
                    format_td(age),
                    format_td(inactive),
                )
                should_cull = True

        if not should_cull:
            app_log.debug(
                "Not culling server %s (age: %s, inactive for %s)",
                log_name,
                format_td(age),
                format_td(inactive),
            )
            return False

        if server_name:
            # culling a named server
            delete_url = url + "/users/%s/servers/%s" % (
                quote(user['name']),
                quote(server['name']),
            )
        else:
            delete_url = url + '/users/%s/server' % quote(user['name'])

        req = HTTPRequest(url=delete_url, method='DELETE', headers=auth_header)
        resp = yield fetch(req)
        if resp.code == 202:
            app_log.warning("Server %s is slow to stop", log_name)
            # return False to prevent culling user with pending shutdowns
            return False
        return True
Ejemplo n.º 37
0
            health="https://ovh.mybinder.org/health",
            # health="https://httpbin.org/status/404",
            versions="https://ovh.mybinder.org/versions",
        ),
        "gesis": dict(
            url="https://notebooks.gesis.org/binder",
            weight=1,
            health="https://notebooks.gesis.org/binder/health",
            versions="https://notebooks.gesis.org/binder/versions",
        ),
    },
}

config_path = "/etc/federation-redirect/config.json"
if os.path.exists(config_path):
    app_log.info("Using config from '{}'.".format(config_path))
    with open(config_path) as f:
        CONFIG = json.load(f)
else:
    app_log.warning("Using default config!")

for h in list(CONFIG["hosts"].keys()):
    # Remove empty entries from CONFIG["hosts"], these can happen because we
    # can't remove keys in our helm templates/config files. All we can do is
    # set them to Null/None. We need to turn the keys into a list so that we
    # can modify the dict while iterating over it
    if CONFIG["hosts"][h] is None:
        CONFIG["hosts"].pop(h)
    # remove trailing slashes in host urls
    # these can cause 404 after redirection (RedirectHandler) and we don't realize it
    else:
Ejemplo n.º 38
0
    async def launch(self, kube, provider):
        """Ask JupyterHub to launch the image."""
        # Load the spec-specific configuration if it has been overridden
        repo_config = provider.repo_config(self.settings)

        # the image name (without tag) is unique per repo
        # use this to count the number of pods running with a given repo
        # if we added annotations/labels with the repo name via KubeSpawner
        # we could do this better
        image_no_tag = self.image_name.rsplit(':', 1)[0]
        matching_pods = 0
        total_pods = 0

        # TODO: run a watch to keep this up to date in the background
        pool = self.settings['executor']
        f = pool.submit(
            kube.list_namespaced_pod,
            self.settings["build_namespace"],
            label_selector='app=jupyterhub,component=singleuser-server',
        )
        # concurrent.futures.Future isn't awaitable
        # wrap in tornado Future
        # tornado 5 will have `.run_in_executor`
        tf = Future()
        chain_future(f, tf)
        pods = await tf
        for pod in pods.items:
            total_pods += 1
            for container in pod.spec.containers:
                # is the container running the same image as us?
                # if so, count one for the current repo.
                image = container.image.rsplit(':', 1)[0]
                if image == image_no_tag:
                    matching_pods += 1
                    break

        # TODO: put busy users in a queue rather than fail?
        # That would be hard to do without in-memory state.
        quota = repo_config.get('quota')
        if quota and matching_pods >= quota:
            app_log.error("%s has exceeded quota: %s/%s (%s total)",
                          self.repo_url, matching_pods, quota, total_pods)
            await self.fail("Too many users running %s! Try again soon." %
                            self.repo_url)
            return

        if quota and matching_pods >= 0.5 * quota:
            log = app_log.warning
        else:
            log = app_log.info
        log("Launching pod for %s: %s other pods running this repo (%s total)",
            self.repo_url, matching_pods, total_pods)

        await self.emit({
            'phase': 'launching',
            'message': 'Launching server...\n',
        })

        launcher = self.settings['launcher']
        retry_delay = launcher.retry_delay
        for i in range(launcher.retries):
            launch_starttime = time.perf_counter()
            if self.settings['auth_enabled']:
                # get logged in user's name
                user_model = self.hub_auth.get_user(self)
                username = user_model['name']
                if launcher.allow_named_servers:
                    # user can launch multiple servers, so create a unique server name
                    server_name = launcher.unique_name_from_repo(self.repo_url)
                else:
                    server_name = ''
            else:
                # create a name for temporary user
                username = launcher.unique_name_from_repo(self.repo_url)
                server_name = ''
            try:
                extra_args = {
                    'binder_ref_url': self.ref_url,
                    'binder_launch_host': self.binder_launch_host,
                    'binder_request': self.binder_request,
                    'binder_persistent_request':
                    self.binder_persistent_request,
                }
                server_info = await launcher.launch(image=self.image_name,
                                                    username=username,
                                                    server_name=server_name,
                                                    repo_url=self.repo_url,
                                                    extra_args=extra_args)
            except Exception as e:
                duration = time.perf_counter() - launch_starttime
                if i + 1 == launcher.retries:
                    status = 'failure'
                else:
                    status = 'retry'
                # don't count retries in failure/retry
                # retry count is only interesting in success
                LAUNCH_TIME.labels(
                    status=status,
                    retries=-1,
                ).observe(time.perf_counter() - launch_starttime)
                if status == 'failure':
                    # don't count retries per repo
                    LAUNCH_COUNT.labels(
                        status=status,
                        **self.repo_metric_labels,
                    ).inc()

                if i + 1 == launcher.retries:
                    # last attempt failed, let it raise
                    raise

                # not the last attempt, try again
                app_log.error(
                    "Retrying launch of %s after error (duration=%.0fs, attempt=%s): %r",
                    self.repo_url,
                    duration,
                    i + 1,
                    e,
                )
                await self.emit({
                    "phase":
                    "launching",
                    "message":
                    "Launch attempt {} failed, retrying...\n".format(i + 1),
                })
                await gen.sleep(retry_delay)
                # exponential backoff for consecutive failures
                retry_delay *= 2
                continue
            else:
                # success
                duration = time.perf_counter() - launch_starttime
                LAUNCH_TIME.labels(status="success",
                                   retries=i).observe(duration)
                LAUNCH_COUNT.labels(
                    status='success',
                    **self.repo_metric_labels,
                ).inc()
                app_log.info("Launched %s in %.0fs", self.repo_url, duration)
                break
        event = {
            'phase': 'ready',
            'message': 'server running at %s\n' % server_info['url'],
        }
        event.update(server_info)
        await self.emit(event)
Ejemplo n.º 39
0
def get_deleter():
    """Get cached global deletion thread"""
    app_log.info("Creating global deletion thread")
    return Deleter()
Ejemplo n.º 40
0
def find_inactive_devices(db, cutoff=IDLE_CUTOFF):
    """Check for device ids that need deleting"""
    app_log.info(f"Checking for devices inactive since {cutoff}")
    cur = db.execute(r"{CALL getLastActivityBefore(?)}", (cutoff,))
    return cur.fetchall()
Ejemplo n.º 41
0
    async def process_one(uuid_activity):
        uuid, last_activity = uuid_activity
        group = await graph.get_group(uuid)
        if group is None:
            app_log.info(f"No group for inactive device {uuid}")
            counts["no_group"] += 1
            return
        if group.get(consent_revoked):
            app_log.info(f"Already marked for deletion: {uuid}")
            counts["deleted"] += 1
            return
        user = await graph.user_for_device(group)
        if user is None:
            app_log.info(f"No user for inactive device {uuid}")
            counts["no_user"] += 1
            # FIXME: something went wrong. Mark device id group for deletion?
            return

        # check other devices on the same user
        # in case of new device registrations,
        # don't delete data from a user's old phone
        other_device_activity = None
        device_ids = [uuid]
        async for group in graph.device_groups_for_user(user):
            device_id = group["displayName"]
            if device_id != uuid:
                device_ids.append(device_id)
            # First check for recent registration (cheap)
            if parse_date(group["createdDateTime"]) >= IDLE_CUTOFF:
                app_log.info(f"Recently registered device {device_id}")
                counts["new"] += 1
                if device_id == uuid:
                    app_log.warning(
                        f"WRONG activity: recently registered {device_id} not idle"
                    )
                    counts["wrong"] += 1
                other_device_activity = True
                break
            try:
                device = await devices.get_device(device_id)
            except Exception as e:
                app_log.warning(f"Failed to get device {device_id}: ({e})")
                counts["iot_err"] += 1
                pass
            else:
                if parse_date(device["lastActivityTime"]) >= IDLE_CUTOFF:
                    counts["iot"] += 1
                    app_log.info(f"Activity on {device_id} in IoTHub")
                    if device_id == uuid:
                        app_log.warning(
                            f"WRONG activity: iothub active {device_id} not idle"
                        )
                        counts["wrong"] += 1
                    other_device_activity = True
                    break
            if device_id != uuid:
                # if not registered since cutoff, check for activity in SQL
                if await check_sql_data(device_id, activity_cutoff=IDLE_CUTOFF):
                    counts["sql"] += 1
                    app_log.info(f"Activity on {device_id} in SQL")
                    other_device_activity = True
                    break
        if other_device_activity:
            app_log.info(f"{uuid} is associated with other more recent device activity")
            counts["active"] += 1
        else:
            app_log.info(f"User {user['logName']} is inactive since {last_activity}")
            app_log.info(f"Inactive devices: {','.join(device_ids)}")
            counts["idle"] += 1
            return user
Ejemplo n.º 42
0
async def find_users_to_delete(limit=None):
    """Find users whose devices should be deleted"""
    async for user in graph.list_users(filter=f"{consent_revoked} eq true"):
        if user.get(consent_revoked):
            app_log.warning(f"User {user['logName']} with consent revoked not removed!")
            yield user
        else:
            app_log.warning(
                f"User {user['logName']} without consent revoked shouldn't have been returned by query."
            )

    inactive = await find_inactive_devices()
    app_log.info(f"Found {len(inactive)} inactive devices")
    counts = defaultdict(int)

    async def process_one(uuid_activity):
        uuid, last_activity = uuid_activity
        group = await graph.get_group(uuid)
        if group is None:
            app_log.info(f"No group for inactive device {uuid}")
            counts["no_group"] += 1
            return
        if group.get(consent_revoked):
            app_log.info(f"Already marked for deletion: {uuid}")
            counts["deleted"] += 1
            return
        user = await graph.user_for_device(group)
        if user is None:
            app_log.info(f"No user for inactive device {uuid}")
            counts["no_user"] += 1
            # FIXME: something went wrong. Mark device id group for deletion?
            return

        # check other devices on the same user
        # in case of new device registrations,
        # don't delete data from a user's old phone
        other_device_activity = None
        device_ids = [uuid]
        async for group in graph.device_groups_for_user(user):
            device_id = group["displayName"]
            if device_id != uuid:
                device_ids.append(device_id)
            # First check for recent registration (cheap)
            if parse_date(group["createdDateTime"]) >= IDLE_CUTOFF:
                app_log.info(f"Recently registered device {device_id}")
                counts["new"] += 1
                if device_id == uuid:
                    app_log.warning(
                        f"WRONG activity: recently registered {device_id} not idle"
                    )
                    counts["wrong"] += 1
                other_device_activity = True
                break
            try:
                device = await devices.get_device(device_id)
            except Exception as e:
                app_log.warning(f"Failed to get device {device_id}: ({e})")
                counts["iot_err"] += 1
                pass
            else:
                if parse_date(device["lastActivityTime"]) >= IDLE_CUTOFF:
                    counts["iot"] += 1
                    app_log.info(f"Activity on {device_id} in IoTHub")
                    if device_id == uuid:
                        app_log.warning(
                            f"WRONG activity: iothub active {device_id} not idle"
                        )
                        counts["wrong"] += 1
                    other_device_activity = True
                    break
            if device_id != uuid:
                # if not registered since cutoff, check for activity in SQL
                if await check_sql_data(device_id, activity_cutoff=IDLE_CUTOFF):
                    counts["sql"] += 1
                    app_log.info(f"Activity on {device_id} in SQL")
                    other_device_activity = True
                    break
        if other_device_activity:
            app_log.info(f"{uuid} is associated with other more recent device activity")
            counts["active"] += 1
        else:
            app_log.info(f"User {user['logName']} is inactive since {last_activity}")
            app_log.info(f"Inactive devices: {','.join(device_ids)}")
            counts["idle"] += 1
            return user

    yielded = 0
    async for user in consume_concurrently(
        inactive, process_one, counts=counts, label="Inactive users"
    ):
        if user:
            yield user
            yielded += 1
            if limit and yielded >= limit:
                app_log.info(f"Reached idle user limit={limit}")
                return
Ejemplo n.º 43
0
    async def launch(self, provider):
        """Ask JupyterHub to launch the image."""
        # Load the spec-specific configuration if it has been overridden
        repo_config = provider.repo_config(self.settings)

        # the image name (without tag) is unique per repo
        # use this to count the number of pods running with a given repo
        # if we added annotations/labels with the repo name via KubeSpawner
        # we could do this better
        image_no_tag = self.image_name.rsplit(':', 1)[0]

        # TODO: put busy users in a queue rather than fail?
        # That would be hard to do without in-memory state.
        quota = repo_config.get('quota')
        if quota:
            # Fetch info on currently running users *only* if quotas are set
            matching_pods = 0
            total_pods = 0

            # TODO: run a watch to keep this up to date in the background
            f = self.settings['executor'].submit(
                self.settings['kubernetes_client'].list_namespaced_pod,
                self.settings['build_namespace'],
                label_selector='app=jupyterhub,component=singleuser-server',
                _request_timeout=KUBE_REQUEST_TIMEOUT,
                _preload_content=False,
            )
            resp = await asyncio.wrap_future(f)
            pods = json.loads(resp.read())
            for pod in pods["items"]:
                total_pods += 1
                for container in pod["spec"]["containers"]:
                    # is the container running the same image as us?
                    # if so, count one for the current repo.
                    image = container["image"].rsplit(":", 1)[0]
                    if image == image_no_tag:
                        matching_pods += 1
                        break
            if matching_pods >= quota:
                app_log.error(
                    f"{self.repo_url} has exceeded quota: {matching_pods}/{quota} ({total_pods} total)"
                )
                await self.fail(
                    f"Too many users running {self.repo_url}! Try again soon.")
                return

            if matching_pods >= 0.5 * quota:
                log = app_log.warning
            else:
                log = app_log.info
            log(
                "Launching pod for %s: %s other pods running this repo (%s total)",
                self.repo_url, matching_pods, total_pods)

        await self.emit({
            'phase': 'launching',
            'message': 'Launching server...\n',
        })

        launcher = self.settings['launcher']
        retry_delay = launcher.retry_delay
        for i in range(launcher.retries):
            launch_starttime = time.perf_counter()
            if self.settings['auth_enabled']:
                # get logged in user's name
                user_model = self.hub_auth.get_user(self)
                username = user_model['name']
                if launcher.allow_named_servers:
                    # user can launch multiple servers, so create a unique server name
                    server_name = launcher.unique_name_from_repo(self.repo_url)
                else:
                    server_name = ''
            else:
                # create a name for temporary user
                username = launcher.unique_name_from_repo(self.repo_url)
                server_name = ''
            try:
                extra_args = {
                    'binder_ref_url': self.ref_url,
                    'binder_launch_host': self.binder_launch_host,
                    'binder_request': self.binder_request,
                    'binder_persistent_request':
                    self.binder_persistent_request,
                }
                server_info = await launcher.launch(image=self.image_name,
                                                    username=username,
                                                    server_name=server_name,
                                                    repo_url=self.repo_url,
                                                    extra_args=extra_args)
            except Exception as e:
                duration = time.perf_counter() - launch_starttime
                if i + 1 == launcher.retries:
                    status = 'failure'
                else:
                    status = 'retry'
                # don't count retries in failure/retry
                # retry count is only interesting in success
                LAUNCH_TIME.labels(
                    status=status,
                    retries=-1,
                ).observe(time.perf_counter() - launch_starttime)
                if status == 'failure':
                    # don't count retries per repo
                    LAUNCH_COUNT.labels(
                        status=status,
                        **self.repo_metric_labels,
                    ).inc()

                if i + 1 == launcher.retries:
                    # last attempt failed, let it raise
                    raise

                # not the last attempt, try again
                app_log.error(
                    "Retrying launch of %s after error (duration=%.0fs, attempt=%s): %r",
                    self.repo_url,
                    duration,
                    i + 1,
                    e,
                )
                await self.emit({
                    "phase":
                    "launching",
                    "message":
                    f"Launch attempt {i + 1} failed, retrying...\n",
                })
                await gen.sleep(retry_delay)
                # exponential backoff for consecutive failures
                retry_delay *= 2
                continue
            else:
                # success
                duration = time.perf_counter() - launch_starttime
                LAUNCH_TIME.labels(status="success",
                                   retries=i).observe(duration)
                LAUNCH_COUNT.labels(
                    status='success',
                    **self.repo_metric_labels,
                ).inc()
                app_log.info("Launched %s in %.0fs", self.repo_url, duration)
                break
        event = {
            'phase': 'ready',
            'message': f"server running at {server_info['url']}\n",
        }
        event.update(server_info)
        await self.emit(event)
Ejemplo n.º 44
0
Archivo: hub.py Proyecto: zhu327/tormq
 def recv(self, msg):
     app_log.info('Message: {}'.format(msg))
     self.local_pub.send_multipart(msg)
Ejemplo n.º 45
0
    def create_notebook_server(self, base_path, container_name,
                               container_config):
        '''Creates a notebook_server running off of `base_path`.

        Returns the (container_id, ip, port) tuple in a Future.'''

        if container_config.host_network:
            # Start with specified container port
            if self.port == 0:
                self.port = int(container_config.container_port)
            port = self.port
            self.port += 1
            # No bindings when using the host network
            port_bindings = None
        else:
            # Bind the specified within-container port to a random port
            # on the container-host IP address
            port = container_config.container_port
            port_bindings = {
                container_config.container_port:
                (container_config.container_ip, )
            }

        app_log.debug(container_config)

        # Assumes that the container_config.command is of a format like:
        #
        #  ipython notebook --no-browser --port {port} --ip=0.0.0.0
        #    --NotebookApp.base_path=/{base_path}
        #    --NotebookApp.tornado_settings=\"{ \"template_path\": [ \"/srv/ga\",
        #    \"/srv/ipython/IPython/html\",
        #    \"/srv/ipython/IPython/html/templates\" ] }\""
        #
        # Important piece here is the parametrized base_path to let the
        # underlying process know where the proxy is routing it.
        rendered_command = container_config.command.format(
            base_path=base_path, port=port, ip=container_config.container_ip)

        command = ["/bin/sh", "-c", rendered_command]

        volume_bindings = {}
        volumes = []
        if container_config.host_directories:
            directories = container_config.host_directories.split(",")
            for index, item in enumerate(directories):
                directory = item.split(":")[0]
                try:
                    permissions = item.split(":")[1]
                except IndexError:
                    permissions = 'rw'

                volumes.append('/mnt/vol' + str(index))
                volume_bindings[directory] = {
                    'bind': '/mnt/vol' + str(index),
                    'mode': permissions
                }

        host_config = dict(
            mem_limit=container_config.mem_limit,
            network_mode='host' if container_config.host_network else 'bridge',
            binds=volume_bindings,
            port_bindings=port_bindings)

        host_config = create_host_config(**host_config)

        cpu_shares = None

        if container_config.cpu_shares:
            # Some versions of Docker and docker-py won't cast from string to int
            cpu_shares = int(container_config.cpu_shares)

        resp = yield self._with_retries(self.docker_client.create_container,
                                        image=container_config.image,
                                        user=container_config.container_user,
                                        command=command,
                                        volumes=volumes,
                                        host_config=host_config,
                                        cpu_shares=cpu_shares,
                                        name=container_name)

        docker_warnings = resp.get('Warnings')
        if docker_warnings is not None:
            app_log.warn(docker_warnings)

        container_id = resp['Id']
        app_log.info("Created container {}".format(container_id))

        yield self._with_retries(self.docker_client.start, container_id)

        if container_config.host_network:
            host_port = port
            host_ip = container_config.container_ip
        else:
            container_network = yield self._with_retries(
                self.docker_client.port, container_id,
                container_config.container_port)

            host_port = container_network[0]['HostPort']
            host_ip = container_network[0]['HostIp']

        raise gen.Return((container_id, host_ip, int(host_port)))
Ejemplo n.º 46
0
def main():
    tornado.options.define('cull_period',
                           default=600,
                           help="Interval (s) for culling idle containers.")
    tornado.options.define('cull_timeout',
                           default=3600,
                           help="Timeout (s) for culling idle containers.")
    tornado.options.define('container_ip',
                           default='127.0.0.1',
                           help="IP address for containers to bind to")
    tornado.options.define('container_port',
                           default='8888',
                           help="Port for containers to bind to")

    command_default = ('ipython3 notebook --no-browser'
                       ' --port {port} --ip=0.0.0.0'
                       ' --NotebookApp.base_url=/{base_path}')

    tornado.options.define(
        'command',
        default=command_default,
        help=
        "command to run when booting the image. A placeholder for base_path should be provided."
    )
    tornado.options.define('port',
                           default=9999,
                           help="port for the main server to listen on")
    tornado.options.define(
        'ip',
        default=None,
        help="ip for the main server to listen on [default: all interfaces]")
    tornado.options.define('max_dock_workers',
                           default=2,
                           help="Maximum number of docker workers")
    tornado.options.define('mem_limit',
                           default="512m",
                           help="Limit on Memory, per container")
    tornado.options.define('cpu_shares',
                           default=None,
                           help="Limit CPU shares, per container")
    tornado.options.define(
        'image',
        default="jupyter/minimal",
        help=
        "Docker container to spawn for new users. Must be on the system already"
    )
    tornado.options.define('docker_version',
                           default="1.13",
                           help="Version of the Docker API to use")
    tornado.options.define(
        'redirect_uri',
        default="/tree",
        help="URI to redirect users to upon initial notebook launch")
    tornado.options.define(
        'pool_size',
        default=10,
        help=
        "Capacity for containers on this system. Will be prelaunched at startup."
    )
    tornado.options.define(
        'pool_name',
        default=None,
        help=
        "Container name fragment used to identity containers that belong to this instance."
    )
    tornado.options.define(
        'static_files',
        default=None,
        help="Static files to extract from the initial container launch")

    tornado.options.parse_command_line()
    opts = tornado.options.options

    handlers = [
        (r"/", LoadingHandler),
        (r"/spawn/?(/user/\w+(?:/.*)?)?", SpawnHandler),
        (r"/(user/\w+)(?:/.*)?", LoadingHandler),
        (r"/stats", StatsHandler),
    ]

    proxy_token = os.environ['CONFIGPROXY_AUTH_TOKEN']
    proxy_endpoint = os.environ.get('CONFIGPROXY_ENDPOINT',
                                    "http://127.0.0.1:8001")
    docker_host = os.environ.get('DOCKER_HOST', 'unix://var/run/docker.sock')

    max_age = datetime.timedelta(seconds=opts.cull_timeout)
    pool_name = opts.pool_name
    if pool_name is None:
        # Derive a valid container name from the image name by default.
        pool_name = re.sub('[^a-zA-Z0_.-]+', '', opts.image.split(':')[0])

    container_config = dockworker.ContainerConfig(
        image=opts.image,
        command=opts.command,
        mem_limit=opts.mem_limit,
        cpu_shares=opts.cpu_shares,
        container_ip=opts.container_ip,
        container_port=opts.container_port,
    )

    spawner = dockworker.DockerSpawner(
        docker_host,
        version=opts.docker_version,
        timeout=30,
        max_workers=opts.max_dock_workers,
    )

    static_path = os.path.join(os.path.dirname(__file__), "static")

    pool = spawnpool.SpawnPool(
        proxy_endpoint=proxy_endpoint,
        proxy_token=proxy_token,
        spawner=spawner,
        container_config=container_config,
        capacity=opts.pool_size,
        max_age=max_age,
        static_files=opts.static_files,
        static_dump_path=static_path,
        pool_name=pool_name,
    )

    ioloop = tornado.ioloop.IOLoop().instance()

    settings = dict(
        static_path=static_path,
        cookie_secret=uuid.uuid4(),
        xsrf_cookies=True,
        debug=True,
        cull_period=opts.cull_period,
        spawner=spawner,
        pool=pool,
        autoescape=None,
        proxy_token=proxy_token,
        template_path=os.path.join(os.path.dirname(__file__), 'templates'),
        proxy_endpoint=proxy_endpoint,
        redirect_uri=opts.redirect_uri.lstrip('/'),
    )

    # Synchronously cull any existing, inactive containers, and pre-launch a set number of
    # containers, ready to serve.
    ioloop.run_sync(pool.heartbeat)

    if (opts.static_files):
        ioloop.run_sync(pool.copy_static)

    # Periodically execute a heartbeat function to cull used containers and regenerated failed
    # ones, self-healing the cluster.
    cull_ms = opts.cull_period * 1e3
    app_log.info("Culling containers unused for %i seconds every %i seconds.",
                 opts.cull_timeout, opts.cull_period)
    culler = tornado.ioloop.PeriodicCallback(pool.heartbeat, cull_ms)
    culler.start()

    app_log.info("Listening on {}:{}".format(opts.ip or '*', opts.port))
    application = tornado.web.Application(handlers, **settings)
    application.listen(opts.port, opts.ip)
    ioloop.start()
Ejemplo n.º 47
0
async def health_check(host, active_hosts):
    check_config = CONFIG["check"]
    all_hosts = CONFIG["hosts"]
    app_log.info("Checking health of {}".format(host))

    client = AsyncHTTPClient()
    try:
        for n in range(check_config["retries"]):
            try:
                # TODO we could use `asyncio.gather()` and fetch health and versions in parallel
                # raises an `HTTPError` if the request returned a non-200 response code
                # health url returns 503 if a (hard check) service is unhealthy
                response = await client.fetch(
                    all_hosts[host]["health"], request_timeout=check_config["timeout"]
                )
                health = json.loads(response.body)
                for check in health["checks"]:
                    # pod quota is a soft check
                    if check["service"] == "Pod quota":
                        if not check["ok"]:
                            raise Exception("{} is over its quota: {}".format(host, check))
                        break
                # check versions
                response = await client.fetch(
                    all_hosts[host]["versions"], request_timeout=check_config["timeout"]
                )
                versions = json.loads(response.body)
                # if this is the prime host store the versions so we can compare to them later
                if all_hosts[host].get("prime", False):
                    all_hosts["versions"] = versions
                # check if this cluster is on the same versions as the prime
                # w/o information about the prime's version we allow each
                # cluster to be on its own versions
                if versions != all_hosts.get("versions", versions):
                    raise Exception("{} has different versions ({}) than prime ({})".
                                    format(host, versions, all_hosts["versions"]))
            except Exception as e:
                app_log.warning(
                    "{} failed, attempt {} of {}".format(
                        host, n + 1, check_config["retries"]
                    )
                )
                # raise the exception on the last attempt
                if n == check_config["retries"] - 1:
                    raise
                else:
                    await sleep(1)

    # any kind of exception means the host is unhealthy
    except Exception as e:
        app_log.warning("{} is unhealthy".format(host))
        if host in active_hosts:
            # prime hosts are never removed, they always get traffic and users
            # will see what ever healthy or unhealthy state they are in
            # this protects us from the federation ending up with zero active
            # hosts because of a glitch somewhere in the health checks
            if all_hosts[host].get("prime", False):
                app_log.warning(
                    "{} has NOT been removed because it is a prime ({})".format(
                        host, str(e)
                    )
                )

            else:
                # remove the host from the rotation for a while
                active_hosts.pop(host)
                app_log.warning(
                    "{} has been removed from the rotation ({})".format(host, str(e))
                )

        # wait longer than usual to check unhealthy host again
        jitter = check_config["jitter"] * (0.5 - random.random())
        IOLoop.current().call_later(
            30 * (1 + jitter) * check_config["period"], health_check, host, active_hosts
        )

    else:
        if host not in active_hosts:
            active_hosts[host] = all_hosts[host]
            app_log.warning("{} has been added to the rotation".format(host))

        # schedule ourselves to check again later
        jitter = check_config["jitter"] * (0.5 - random.random())
        IOLoop.current().call_later(
            (1 + jitter) * check_config["period"], health_check, host, active_hosts
        )
Ejemplo n.º 48
0
async def delete_test_user(user_id):
    app_log.info(f"Delete test user {user_id}")
    await graph.graph_request(f"/users/{user_id}", method="DELETE")
    app_log.info(f"Deleted test user {user_id}")
Ejemplo n.º 49
0
 def sync_db_tables(self):
     dumps = dict()
     db_path = os.path.join(static_path, 'syncDB', 'lasTables')
     if not os.path.isdir(db_path):
         os.makedirs(db_path)
     flag_file = os.path.join(db_path, 'TAG')
     old_date = ''
     if os.path.isfile(flag_file):
         with open(flag_file, 'r') as fp:
             old_date = fp.read()
     now_date = time.strftime('%Y%m%d')
     if old_date != now_date:
         env_list, total = yield self.project.get_projects_list(
             p_type='env', status=1, search='准生产')
         for env in env_list:
             details, total = yield self.setting.get_settings_list(
                 s_type='env', name=env.name)
             for detail in details:
                 desc = json.loads(detail.value)
                 if desc.get('type') != 'APPLICATION' and desc.get(
                         'title').upper().find('MYSQL') == -1:
                     continue
                 dumps[detail.id] = dict(
                     ip=desc.get('ip').strip(),
                     port=desc.get('port').strip(),
                     user=desc.get('user').strip(),
                     password=desc.get('password').strip(),
                     dbs=desc.get('description').split(','))
             break
         with open(flag_file, 'w') as fp:
             fp.write(time.strftime('%Y%m%d'))
         for key in dumps:
             for db in dumps[key]['dbs']:
                 tmp_path = os.path.join(db_path, '{}_tmp.txt'.format(db))
                 shell_dump = '''cd {}
                 /opt/lampp/bin/mysqldump -h{} -P{} -u{} -p{} -d {} > {}'''.format(
                     root_160, dumps[key]['ip'], dumps[key]['port'],
                     dumps[key]['user'], dumps[key]['password'], db,
                     'lasTables/{}_tmp.txt'.format(db))
                 res, msg = yield self.thread_func.exec_remote_shell(
                     shell=shell_dump,
                     host=host_160,
                     port=port_160,
                     username=user_160,
                     password=password_160)
                 if res and os.path.isfile(tmp_path):
                     with open(tmp_path, 'r', encoding='utf8') as fp:
                         lines = fp.readlines()
                     tables = list()
                     for line in lines:
                         if line.find('DROP TABLE IF EXISTS') == -1:
                             continue
                         table = re.findall(r'`(\w+)`;', line)
                         table and tables.append('{}\n'.format(table[0]))
                     with open(os.path.join(db_path, '{}.txt'.format(db)),
                               'w') as fp:
                         fp.writelines(tables)
                     os.remove(tmp_path)
                 else:
                     log.info(msg)
     old_time = time.time() - 30 * 24 * 3600
     old_path = os.path.join(static_path, 'syncDB',
                             time.strftime('%Y%m%d', time.gmtime(old_time)))
     if os.path.isdir(old_path):
         shutil.rmtree(old_path)
Ejemplo n.º 50
0
    async def launch(
        self,
        image,
        username,
        server_name="",
        repo_url="",
        extra_args=None,
        event_callback=None,
    ):
        """Launch a server for a given image

        - creates a temporary user on the Hub if authentication is not enabled
        - spawns a server for temporary/authenticated user
        - generates a token
        - returns a dict containing:
          - `url`: the URL of the server
          - `image`: image spec
          - `repo_url`: the url of the repo
          - `extra_args`: Dictionary of extra arguments passed to the server
          - `token`: the token for the server
        """
        # TODO: validate the image argument?

        # Matches the escaping that JupyterHub does https://github.com/jupyterhub/jupyterhub/blob/c00c3fa28703669b932eb84549654238ff8995dc/jupyterhub/user.py#L427
        escaped_username = quote(username, safe="@~")
        if self.create_user:
            # create a new user
            app_log.info("Creating user %s for image %s", username, image)
            try:
                await self.api_request(
                    f"users/{escaped_username}", body=b"", method="POST"
                )
            except HTTPError as e:
                if e.response:
                    body = e.response.body
                else:
                    body = ""
                app_log.error(
                    "Error creating user %s: %s\n%s",
                    username,
                    e,
                    body,
                )
                raise web.HTTPError(500, f"Failed to create temporary user for {image}")
        elif server_name == "":
            # authentication is enabled but not named servers
            # check if user has a running server ('')
            user_data = await self.get_user_data(escaped_username)
            if server_name in user_data["servers"]:
                raise web.HTTPError(
                    409, f"User {username} already has a running server."
                )
        elif self.named_server_limit_per_user > 0:
            # authentication is enabled with named servers
            # check if user has already reached to the limit of named servers
            user_data = await self.get_user_data(escaped_username)
            len_named_spawners = len([s for s in user_data["servers"] if s != ""])
            if self.named_server_limit_per_user <= len_named_spawners:
                raise web.HTTPError(
                    409,
                    "User {} already has the maximum of {} named servers."
                    "  One must be deleted before a new server can be created".format(
                        username, self.named_server_limit_per_user
                    ),
                )

        if self.pre_launch_hook:
            await maybe_future(
                self.pre_launch_hook(self, image, username, server_name, repo_url)
            )

        # data to be passed into spawner's user_options during launch
        # and also to be returned into 'ready' state
        data = {
            "image": image,
            "repo_url": repo_url,
            "token": base64.urlsafe_b64encode(uuid.uuid4().bytes)
            .decode("ascii")
            .rstrip("=\n"),
        }
        if extra_args:
            data.update(extra_args)

        # server name to be used in logs
        _server_name = f" {server_name}" if server_name else ""

        # start server
        app_log.info(
            f"Starting server{_server_name} for user {username} with image {image}"
        )
        ready_event_future = asyncio.Future()

        def _cancel_ready_event(f=None):
            if not ready_event_future.done():
                if f and f.exception():
                    ready_event_future.set_exception(f.exception())
                else:
                    ready_event_future.cancel()

        try:
            await self.api_request(
                f"users/{escaped_username}/servers/{server_name}",
                method="POST",
                body=json.dumps(data).encode("utf8"),
            )
            # listen for pending spawn (launch) events until server is ready
            # do this even if previous request finished!
            buffer_list = []

            async def handle_chunk(chunk):
                lines = b"".join(buffer_list + [chunk]).split(b"\n\n")
                # the last item in the list is usually an empty line ('')
                # but it can be the partial line after the last `\n\n`,
                # so put it back on the buffer to handle with the next chunk
                buffer_list[:] = [lines[-1]]
                for line in lines[:-1]:
                    if line:
                        line = line.decode("utf8", "replace")
                    if line and line.startswith("data:"):
                        event = json.loads(line.split(":", 1)[1])
                        if event_callback:
                            await event_callback(event)

                        # stream ends when server is ready or fails
                        if event.get("ready", False):
                            if not ready_event_future.done():
                                ready_event_future.set_result(event)
                        elif event.get("failed", False):
                            if not ready_event_future.done():
                                ready_event_future.set_exception(
                                    web.HTTPError(
                                        500, event.get("message", "unknown error")
                                    )
                                )

            url_parts = ["users", username]
            if server_name:
                url_parts.extend(["servers", server_name, "progress"])
            else:
                url_parts.extend(["server/progress"])
            progress_api_url = url_path_join(*url_parts)
            self.log.debug(f"Requesting progress for {username}: {progress_api_url}")
            resp_future = self.api_request(
                progress_api_url,
                streaming_callback=lambda chunk: asyncio.ensure_future(
                    handle_chunk(chunk)
                ),
                request_timeout=self.launch_timeout,
            )
            try:
                await gen.with_timeout(
                    timedelta(seconds=self.launch_timeout), resp_future
                )
            except (gen.TimeoutError, TimeoutError):
                _cancel_ready_event()
                raise web.HTTPError(
                    500,
                    f"Image {image} for user {username} took too long to launch",
                )

        except HTTPError as e:
            _cancel_ready_event()
            if e.response:
                body = e.response.body
            else:
                body = ""

            app_log.error(
                f"Error starting server{_server_name} for user {username}: {e}\n{body}"
            )
            raise web.HTTPError(500, f"Failed to launch image {image}")
        except Exception:
            _cancel_ready_event()
            raise

        # verify that the server is running!
        try:
            # this should already be done, but it's async so wait a finite time
            await gen.with_timeout(timedelta(seconds=5), ready_event_future)
        except (gen.TimeoutError, TimeoutError):
            raise web.HTTPError(
                500, f"Image {image} for user {username} failed to launch"
            )

        data["url"] = self.hub_url + f"user/{escaped_username}/{server_name}"
        self.log.debug(data["url"])
        return data
Ejemplo n.º 51
0
 def summarize(message, list):
     if list:
         app_log.info(message, len(list))
Ejemplo n.º 52
0
    def submit(self):
        """Submit a image spec to openshift's s2i and wait for completion """
        volume_mounts = [
            client.V1VolumeMount(mount_path="/var/run/docker.sock",
                                 name="docker-socket")
        ]
        docker_socket_path = urlparse(self.docker_host).path
        volumes = [
            client.V1Volume(name="docker-socket",
                            host_path=client.V1HostPathVolumeSource(
                                path=docker_socket_path, type='Socket'))
        ]

        if self.push_secret:
            volume_mounts.append(
                client.V1VolumeMount(mount_path="/root/.docker",
                                     name='docker-push-secret'))
            volumes.append(
                client.V1Volume(name='docker-push-secret',
                                secret=client.V1SecretVolumeSource(
                                    secret_name=self.push_secret)))

        self.pod = client.V1Pod(
            metadata=client.V1ObjectMeta(
                name=self.name,
                labels={
                    "name": self.name,
                    "component": "binderhub-build",
                },
                annotations={
                    "binder-repo": self.repo_url,
                },
            ),
            spec=client.V1PodSpec(containers=[
                client.V1Container(image=self.builder_image,
                                   name="builder",
                                   args=self.get_cmd(),
                                   image_pull_policy='Always',
                                   volume_mounts=volume_mounts,
                                   resources=client.V1ResourceRequirements(
                                       limits={'memory': self.memory_limit},
                                       requests={'memory': self.memory_limit}))
            ],
                                  node_selector=self.node_selector,
                                  volumes=volumes,
                                  restart_policy="Never"))

        try:
            ret = self.api.create_namespaced_pod(self.namespace, self.pod)
        except client.rest.ApiException as e:
            if e.status == 409:
                # Someone else created it!
                app_log.info("Build %s already running", self.name)
                pass
            else:
                raise
        else:
            app_log.info("Started build %s", self.name)

        app_log.info("Watching build pod %s", self.name)
        while not self.stop_event.is_set():
            w = watch.Watch()
            try:
                for f in w.stream(
                        self.api.list_namespaced_pod,
                        self.namespace,
                        label_selector="name={}".format(self.name),
                        timeout_seconds=30,
                ):
                    if f['type'] == 'DELETED':
                        self.progress('pod.phasechange', 'Deleted')
                        return
                    self.pod = f['object']
                    if not self.stop_event.is_set():
                        self.progress('pod.phasechange', self.pod.status.phase)
                    if self.pod.status.phase == 'Succeeded':
                        self.cleanup()
                    elif self.pod.status.phase == 'Failed':
                        self.cleanup()
            except Exception as e:
                app_log.exception("Error in watch stream for %s", self.name)
                raise
            finally:
                w.stop()
            if self.stop_event.is_set():
                app_log.info("Stopping watch of %s", self.name)
                return
Ejemplo n.º 53
0
 async def on_message(self, message):
     log.info(f"Message received: {message}")
Ejemplo n.º 54
0
 async def on_close(self):
     log.info("WebSocket closed by client.")
     await self.sub.unsubscribe()
     log.info("Unsubscribed from channel.")
Ejemplo n.º 55
0
import tornado.web
import tornado.ioloop
import tornado.httpserver
from tornado.log import app_log
from tornado.options import define, options, parse_command_line

from uri import uris
from settings.app_setting import SETTINGS
from settings.mongo_setting import MONGODB_NAME, MONGODB_CONFIG
from mongoengine import connect

define("port", default=8888, help="run on the given port", type=int)


class Application(tornado.web.Application):
    def __init__(self):
        # 连接MongoDB
        connect(MONGODB_NAME, **MONGODB_CONFIG)
        tornado.web.Application.__init__(self, uris, **SETTINGS)


application = Application()

if __name__ == "__main__":
    parse_command_line()
    server = tornado.httpserver.HTTPServer(application)
    server.listen(options.port)
    app_log.info("run on port:{}".format(options.port))
    io_loop_ = tornado.ioloop.IOLoop.current()
    io_loop_.start()
Ejemplo n.º 56
0
    async def launch(self,
                     image,
                     username,
                     server_name='',
                     repo_url='',
                     extra_args=None):
        """Launch a server for a given image

        - creates a temporary user on the Hub if authentication is not enabled
        - spawns a server for temporary/authenticated user
        - generates a token
        - returns a dict containing:
          - `url`: the URL of the server
          - `image`: image spec
          - `repo_url`: the url of the repo
          - `extra_args`: Dictionary of extra arguments passed to the server
          - `token`: the token for the server
        """
        # TODO: validate the image argument?

        if self.create_user:
            # create a new user
            app_log.info("Creating user %s for image %s", username, image)
            try:
                await self.api_request('users/%s' % username,
                                       body=b'',
                                       method='POST')
            except HTTPError as e:
                if e.response:
                    body = e.response.body
                else:
                    body = ''
                app_log.error(
                    "Error creating user %s: %s\n%s",
                    username,
                    e,
                    body,
                )
                raise web.HTTPError(
                    500, "Failed to create temporary user for %s" % image)
        elif server_name == '':
            # authentication is enabled but not named servers
            # check if user has a running server ('')
            user_data = await self.get_user_data(username)
            if server_name in user_data['servers']:
                raise web.HTTPError(
                    409, "User %s already has a running server." % username)

        if self.pre_launch_hook:
            await maybe_future(
                self.pre_launch_hook(self, image, username, server_name,
                                     repo_url))

        # data to be passed into spawner's user_options during launch
        # and also to be returned into 'ready' state
        data = {
            'image':
            image,
            'repo_url':
            repo_url,
            'token':
            base64.urlsafe_b64encode(
                uuid.uuid4().bytes).decode('ascii').rstrip('=\n')
        }
        if extra_args:
            data.update(extra_args)

        # server name to be used in logs
        _server_name = " {}".format(server_name) if server_name else ''

        # start server
        app_log.info("Starting server%s for user %s with image %s",
                     _server_name, username, image)
        try:
            resp = await self.api_request(
                'users/{}/servers/{}'.format(username, server_name),
                method='POST',
                body=json.dumps(data).encode('utf8'),
            )
            if resp.code == 202:
                # Server hasn't actually started yet
                # We wait for it!
                # NOTE: This ends up being about ten minutes
                for i in range(64):
                    user_data = await self.get_user_data(username)
                    if user_data['servers'][server_name]['ready']:
                        break
                    if not user_data['servers'][server_name]['pending']:
                        raise web.HTTPError(
                            500, "Image %s for user %s failed to launch" %
                            (image, username))
                    # FIXME: make this configurable
                    # FIXME: Measure how long it takes for servers to start
                    # and tune this appropriately
                    await gen.sleep(min(1.4**i, 10))
                else:
                    raise web.HTTPError(
                        500, "Image %s for user %s took too long to launch" %
                        (image, username))

        except HTTPError as e:
            if e.response:
                body = e.response.body
            else:
                body = ''

            app_log.error("Error starting server{} for user {}: {}\n{}".format(
                _server_name, username, e, body))
            raise web.HTTPError(500, "Failed to launch image %s" % image)

        data['url'] = self.hub_url + 'user/%s/%s' % (username, server_name)
        return data
Ejemplo n.º 57
0
    def get(self):
        xdata = self.get_arguments("xdata")
        ydata = self.get_arguments("ydata")
        sma1 = self.get_arguments("sma1")
        sma2 = self.get_arguments("sma2")
        pos = self.get_arguments("pos")
        kind = self.get_argument("kind", "")

        imagelog.info("len(xdata)={}".format(len(xdata)))
        imagelog.info("len(xdata)={}".format(len(ydata)))
        imagelog.info("mutli={} type={}".format(len(pos) > 1, len(pos)))
        mutli = False
        if len(pos) > 1:
            mutli = True
        if len(xdata) != len(ydata) and not mutli:
            return self.write(
                json.dumps({
                    "err_code": 1,
                    "msg": "x y length not the same"
                }))
        # print(f"{xdata} {ydata}")
        # print(xdata)
        # print(ydata)
        ydata = [float(i) for i in ydata]
        if mutli:
            imagelog.info("mutli data len(pos)={}".format(len(pos)))
            sma1 = [float(i) for i in sma1]
            sma2 = [float(i) for i in sma2]
            pos = [int(i) for i in pos]
            sma_data = [ydata, sma1, sma2]
            # plt.plot(xdata, *sma_data, marker='o')
            # plt.plot(xdata, *sma_data, color=["blue", "green", "orange"])
            plt.plot(xdata, ydata, color='blue')
            plt.plot(xdata, sma1, color='green')
            plt.plot(xdata, sma2, color='orange')
            plt.legend(["ori", "sma1", "sma2"])
            ax2 = plt.twinx()
            ax2.set_ylim([-1 - 0.1, 1 + 0.1])
            ax2.plot(xdata, pos, color='red')
            # https://blog.csdn.net/u010440456/article/details/90768681
            ax2.legend(['pos'], loc="upper center")  # upper right
        else:
            imagelog.info("single data len(ydata)={}".format(len(ydata)))
            if kind == "bar":
                plt.bar(xdata, ydata)
            else:
                plt.plot(xdata, ydata, marker='o')
        plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei Mono']
        plt.gcf().subplots_adjust(bottom=0.2)
        plt.xticks(rotation=75, fontsize=12)
        if len(xdata) < 5:
            plt.xlim(0, 5)
        figfile = BytesIO()
        # filename = "xy_{}.png".format(self.request.request_time())
        # print(filename)
        plt.savefig(figfile, dpi=600, format="png")
        figdata_png = base64.b64encode(figfile.getvalue()).decode()
        # figfile.close()
        plt.cla()
        plt.close()
        # print(len(figdata_png))
        imagelog.info("{} {} \nimgb64 len:{}".format("xdata", "ydata",
                                                     len(figdata_png)))
        return self.write(json.dumps({"err_code": 0, "img": figdata_png}))
Ejemplo n.º 58
0
 def open(self):
     app_log.info('open socket %s', self)
     self.sockets.append(self)
     self.subscriptions = {}
Ejemplo n.º 59
0
    def handle_user(user):
        """Handle one user.
        Create a list of their servers, and async exec them.  Wait for
        that to be done, and if all servers are stopped, possibly cull
        the user.
        """
        # shutdown servers first.
        # Hub doesn't allow deleting users with running servers.
        # jupyterhub 0.9 always provides a 'servers' model.
        # 0.8 only does this when named servers are enabled.
        if 'servers' in user:
            servers = user['servers']
        else:
            # jupyterhub < 0.9 without named servers enabled.
            # create servers dict with one entry for the default server
            # from the user model.
            # only if the server is running.
            servers = {}
            if user['server']:
                servers[''] = {
                    'last_activity': user['last_activity'],
                    'pending': user['pending'],
                    'url': user['server'],
                }
        server_futures = [
            handle_server(user, server_name, server)
            for server_name, server in servers.items()
        ]
        results = yield multi(server_futures)
        if not cull_users:
            return
        # some servers are still running, cannot cull users
        still_alive = len(results) - sum(results)
        if still_alive:
            app_log.debug(
                "Not culling user %s with %i servers still alive",
                user['name'],
                still_alive,
            )
            return False

        should_cull = False
        if user.get('created'):
            age = now - parse_date(user['created'])
        else:
            # created may be undefined on jupyterhub < 0.9
            age = None

        # check last activity
        # last_activity can be None in 0.9
        if user['last_activity']:
            inactive = now - parse_date(user['last_activity'])
        else:
            # no activity yet, use start date
            # last_activity may be None with jupyterhub 0.9,
            # which introduces the 'created' field which is never None
            inactive = age

        should_cull = (inactive is not None
                       and inactive.total_seconds() >= inactive_limit)
        if should_cull:
            app_log.info("Culling user %s (inactive for %s)", user['name'],
                         inactive)

        if max_age and not should_cull:
            # only check created if max_age is specified
            # so that we can still be compatible with jupyterhub 0.8
            # which doesn't define the 'started' field
            if age is not None and age.total_seconds() >= max_age:
                app_log.info(
                    "Culling user %s (age: %s, inactive for %s)",
                    user['name'],
                    format_td(age),
                    format_td(inactive),
                )
                should_cull = True

        if not should_cull:
            app_log.debug(
                "Not culling user %s (created: %s, last active: %s)",
                user['name'],
                format_td(age),
                format_td(inactive),
            )
            return False

        req = HTTPRequest(url=url + '/users/%s' % user['name'],
                          method='DELETE',
                          headers=auth_header)
        yield fetch(req)
        return True
Ejemplo n.º 60
0
 def post(self, message_type, **kwargs):
     app_log.info(self.request.body)