Compare commits

..

29 Commits

Author SHA1 Message Date
Min RK
e4d4e059bd Merge pull request #2545 from minrk/changelog-1.0
releasing 1.0
2019-05-03 16:12:51 +02:00
Min RK
2967383654 release 1.0.0 2019-05-03 13:50:45 +02:00
Min RK
85f5ae1a37 Merge pull request #2544 from minrk/autodoc-link-targets
[autodoc] move config sample to annotation
2019-05-03 13:49:31 +02:00
Min RK
ecafe4add9 [autodoc] move config sample to annotation
preserves link targets, which mangling the directive header does not
2019-05-03 13:00:25 +02:00
Min RK
9462511aa5 Merge pull request #2543 from minrk/pin-mysql-connector
pin mysql-connector-python on travis
2019-05-03 10:55:34 +02:00
Min RK
31736eea9a pin mysql-connector-python on travis
avoids bug in latest connector decoding: https://bugs.mysql.com/bug.php?id=94944
2019-05-03 10:33:28 +02:00
Min RK
f97ef7eaac Merge pull request #2534 from distortedsignal/refactor-logout-handler
Refactor Logout Handler
2019-05-03 10:11:43 +02:00
Min RK
2065099338 Merge pull request #2524 from minrk/fix-db-redact
test postgres with url-encoded password
2019-05-03 09:48:57 +02:00
Carol Willing
d4df579fa6 Merge pull request #2538 from labarba/patch-1
Update gallery-jhub-deployments.md
2019-05-02 12:14:58 -04:00
Lorena A. Barba
4378603e83 Update gallery-jhub-deployments.md 2019-04-26 07:32:35 -04:00
Min RK
40db4edc6d remove todo
order should be preserved between multiple steps
2019-04-25 13:51:27 +02:00
Min RK
ccf13979e9 Merge pull request #2536 from jcrist/add-jhub-hadoop-links
Add a few links for JupyterHub on Hadoop
2019-04-25 10:23:01 +02:00
Jim Crist
76f134c393 Add a few links for JupyterHub on Hadoop [skip ci]
- JupyterHub on Hadoop Guide: https://github.com/jcrist/jupyterhub-on-hadoop
- YarnSpawner: https://github.com/jcrist/yarnspawner
- KerberosAuthenticator: https://github.com/jcrist/kerberosauthenticator
2019-04-24 14:40:50 -05:00
Tom Kelley
77d4c1f23d Changes after CR Comments
Big thanks to Erik, Tim, and Min for the great comments!

Change names to be more clear, add function doc comments,
change scoping on some functions, add handle_logout to let
people take custom logout actions, extract
render_logout_page from get method, add TODO.
2019-04-23 09:59:56 -07:00
Min RK
5856f46e1d Merge pull request #2519 from dfeich/asyncio_signal_cleanup
asyncio signal handling and loop cleanup by task cancelling
2019-04-23 15:13:08 +02:00
Min RK
edfd1eb6cf Merge pull request #2526 from minrk/debug-entrypoints
demote entrypoint-loading warning to debug-level
2019-04-23 15:12:21 +02:00
Tom Kelley
1ae6678360 Refactor Logout Handler
AS A developer of a Logout handler
I WANT to be able to call a function to kill spawners and
do other backend logout stuff and a separate function to
forward the user along the logout chain.

I believe this PR adds (moderately private) methods to the
Logout Handler to do just that.
2019-04-22 12:14:55 -07:00
Min RK
7794eea3fb demote entrypoint-loading warning to debug-level 2019-04-16 15:30:04 +02:00
Derek Feichtinger
f51e6a1ca0 move event_loop closing to shutdown_cancel_tasks 2019-04-16 13:04:11 +02:00
Min RK
ab00a19be1 test postgres with url-encoded username+password 2019-04-16 12:57:49 +02:00
Min RK
7742bfdda5 Merge pull request #2514 from minrk/user-redirect-test
fix flaky test_user_redirect
2019-04-16 12:52:24 +02:00
Erik Sundell
f3878d8216 Merge pull request #2518 from leportella/add-new-jhub-fluxogram
Add new introduction to jhub on docs
2019-04-15 21:13:25 +02:00
Tim Head
d17cb637fe Merge pull request #2520 from parente/vul-reporting
Add vulnerability reporting info to docs
2019-04-15 16:38:58 +02:00
Peter Parente
5b63efe63c Add a newline for the linter 2019-04-15 07:52:55 -04:00
Peter Parente
54816b0a7c Add vulnerability reporting info
https://discourse.jupyter.org/t/responsible-vulnerability-reporting/655
2019-04-14 22:10:58 -04:00
Leticia Portella
41fc73db42 Add new introduction to jhub on docs 2019-04-13 20:55:36 +01:00
Derek Feichtinger
984d6be542 asyncio signal handling and loop cleanup by task cancelling
Also:
- No longer exits with 143 (128+15) on SIGTERM, but with 0
- Allows SIGUSR1 in addition to SIGINFO for dumping status
2019-04-13 20:17:53 +02:00
Min RK
74a457f6b5 guard against using get_page with full urls
since that was hard to debug
2019-03-28 17:02:41 +01:00
Min RK
137a044f96 fix flaky test_user_redirect
when re-fetching the same url, use `requests.get`, not `get_page`
2019-03-28 17:01:00 +01:00
17 changed files with 185 additions and 70 deletions

View File

@@ -27,8 +27,11 @@ before_install:
unset MYSQL_UNIX_PORT
DB=mysql bash ci/docker-db.sh
DB=mysql bash ci/init-db.sh
pip install 'mysql-connector-python'
# FIXME: mysql-connector-python 8.0.16 incorrectly decodes bytes to str
# ref: https://bugs.mysql.com/bug.php?id=94944
pip install 'mysql-connector-python==8.0.15'
elif [[ $JUPYTERHUB_TEST_DB_URL == postgresql* ]]; then
psql -c "CREATE USER $PGUSER WITH PASSWORD '$PGPASSWORD';" -U postgres
DB=postgres bash ci/init-db.sh
pip install psycopg2-binary
fi
@@ -87,7 +90,10 @@ matrix:
- JUPYTERHUB_TEST_DB_URL=mysql+mysqlconnector://root@127.0.0.1:$MYSQL_TCP_PORT/jupyterhub
- python: 3.6
env:
- JUPYTERHUB_TEST_DB_URL=postgresql://postgres@127.0.0.1/jupyterhub
- PGUSER=jupyterhub
- PGPASSWORD=hub[test/:?
# password in url is url-encoded (urllib.parse.quote($PGPASSWORD, safe=''))
- JUPYTERHUB_TEST_DB_URL=postgresql://jupyterhub:hub%5Btest%2F%3A%3F@127.0.0.1/jupyterhub
- python: 3.7
dist: xenial
allow_failures:

View File

@@ -150,7 +150,7 @@ To start the Hub on a specific url and port ``10.0.1.2:443`` with **https**:
| PAMAuthenticator | Default, built-in authenticator |
| [OAuthenticator](https://github.com/jupyterhub/oauthenticator) | OAuth + JupyterHub Authenticator = OAuthenticator |
| [ldapauthenticator](https://github.com/jupyterhub/ldapauthenticator) | Simple LDAP Authenticator Plugin for JupyterHub |
| [kdcAuthenticator](https://github.com/bloomberg/jupyterhub-kdcauthenticator)| Kerberos Authenticator Plugin for JupyterHub |
| [kerberosauthenticator](https://github.com/jcrist/kerberosauthenticator) | Kerberos Authenticator Plugin for JupyterHub |
### Spawners
@@ -162,6 +162,7 @@ To start the Hub on a specific url and port ``10.0.1.2:443`` with **https**:
| [sudospawner](https://github.com/jupyterhub/sudospawner) | Spawn single-user servers without being root |
| [systemdspawner](https://github.com/jupyterhub/systemdspawner) | Spawn single-user notebook servers using systemd |
| [batchspawner](https://github.com/jupyterhub/batchspawner) | Designed for clusters using batch scheduling software |
| [yarnspawner](https://github.com/jcrist/yarnspawner) | Spawn single-user notebook servers distributed on a Hadoop cluster |
| [wrapspawner](https://github.com/jupyterhub/wrapspawner) | WrapSpawner and ProfilesSpawner enabling runtime configuration of spawners |
## Docker

View File

@@ -2,7 +2,7 @@
# source this file to setup postgres and mysql
# for local testing (as similar as possible to docker)
set -e
set -eu
export MYSQL_HOST=127.0.0.1
export MYSQL_TCP_PORT=${MYSQL_TCP_PORT:-13306}
@@ -40,6 +40,15 @@ for i in {1..60}; do
done
$CHECK
case "$DB" in
"mysql")
;;
"postgres")
# create the user
psql --user postgres -c "CREATE USER $PGUSER WITH PASSWORD '$PGPASSWORD';"
;;
*)
esac
echo -e "
Set these environment variables:

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# initialize jupyterhub databases for testing
set -e
set -eu
MYSQL="mysql --user root --host $MYSQL_HOST --port $MYSQL_TCP_PORT -e "
PSQL="psql --user postgres -c "
@@ -23,5 +23,5 @@ set -x
for SUFFIX in '' _upgrade_072 _upgrade_081 _upgrade_094; do
$SQL "DROP DATABASE jupyterhub${SUFFIX};" 2>/dev/null || true
$SQL "CREATE DATABASE jupyterhub${SUFFIX} ${EXTRA_CREATE};"
$SQL "CREATE DATABASE jupyterhub${SUFFIX} ${EXTRA_CREATE:-};"
done

View File

@@ -9,7 +9,7 @@ command line for details.
## 1.0
### [1.0.0] 2019-04-XX
### [1.0.0] 2019-05-03
JupyterHub 1.0 is a major milestone for JupyterHub.
Huge thanks to the many people who have contributed to this release,
@@ -577,7 +577,7 @@ First preview release
[Unreleased]: https://github.com/jupyterhub/jupyterhub/compare/1.0.0...HEAD
[1.0.0]: https://github.com/jupyterhub/jupyterhub/compare/0.9.5...HEAD
[1.0.0]: https://github.com/jupyterhub/jupyterhub/compare/0.9.6...1.0.0
[0.9.6]: https://github.com/jupyterhub/jupyterhub/compare/0.9.4...0.9.6
[0.9.4]: https://github.com/jupyterhub/jupyterhub/compare/0.9.3...0.9.4
[0.9.3]: https://github.com/jupyterhub/jupyterhub/compare/0.9.2...0.9.3

View File

@@ -66,7 +66,7 @@ easy to do with RStudio too.
### University of Colorado Boulder
- (CU Research Computing) CURC
- (CU Research Computing) CURC
- [JupyterHub User Guide](https://www.rc.colorado.edu/support/user-guide/jupyterhub.html)
- Slurm job dispatched on Crestone compute cluster
- log troubleshooting
@@ -77,13 +77,17 @@ easy to do with RStudio too.
- Earth Lab at CU
- [Tutorial on Parallel R on JupyterHub](https://earthdatascience.org/tutorials/parallel-r-on-jupyterhub/)
### George Washington University
- [Jupyter Hub](http://go.gwu.edu/jupyter) with university single-sign-on. Deployed early 2017.
### HTCondor
- [HTCondor Python Bindings Tutorial from HTCondor Week 2017 includes information on their JupyterHub tutorials](https://research.cs.wisc.edu/htcondor/HTCondorWeek2017/presentations/TueBockelman_Python.pdf)
### University of Illinois
- https://datascience.business.illinois.edu
- https://datascience.business.illinois.edu (currently down; checked 04/26/19)
### IllustrisTNG Simulation Project
@@ -110,7 +114,11 @@ easy to do with RStudio too.
- [Data Science (DICE) group](https://dice.cs.uni-paderborn.de/)
- [nbgraderutils](https://github.com/dice-group/nbgraderutils): Use JupyterHub + nbgrader + iJava kernel for online Java exercises. Used in lecture Statistical Natural Language Processing.
### University of Rochester CIRC
### Penn State University
- [Press release](https://news.psu.edu/story/523093/2018/05/24/new-open-source-web-apps-available-students-and-faculty): "New open-source web apps available for students and faculty" (but Hub is currently down; checked 04/26/19)
### University of Rochester CIRC
- [JupyterHub Userguide](https://info.circ.rochester.edu/Web_Applications/JupyterHub.html) - Slurm, beehive
@@ -126,7 +134,7 @@ easy to do with RStudio too.
- Educational Technology Services - Paul Jamason
- [jupyterhub.ucsd.edu](https://jupyterhub.ucsd.edu)
### TACC University of Texas
### Texas A&M
@@ -160,6 +168,10 @@ easy to do with RStudio too.
- https://getcarina.com/blog/learning-how-to-whale/
- http://carolynvanslyck.com/talk/carina/jupyterhub/#/
### Hadoop
- [Deploying JupyterHub on Hadoop](https://jcrist.github.io/jupyterhub-on-hadoop/)
## Miscellaneous

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@@ -2,21 +2,37 @@
JupyterHub
==========
`JupyterHub`_, a multi-user **Hub**, spawns, manages, and proxies multiple
`JupyterHub`_ is the best way to serve `Jupyter notebook`_ for multiple users.
It can be used in a classes of students, a corporate data science group or scientific
research group. It is a multi-user **Hub** that spawns, manages, and proxies multiple
instances of the single-user `Jupyter notebook`_ server.
JupyterHub can be used to serve notebooks to a class of students, a corporate
data science group, or a scientific research group.
.. image:: images/jhub-parts.png
To make life easier, JupyterHub have distributions. Be sure to
take a look at them before continuing with the configuration of the broad
original system of `JupyterHub`_. Today, you can find two main cases:
1. If you need a simple case for a small amount of users (0-100) and single server
take a look at
`The Littlest JupyterHub <https://github.com/jupyterhub/the-littlest-jupyterhub>`__ distribution.
2. If you need to allow for even more users, a dynamic amount of servers can be used on a cloud,
take a look at the `Zero to JupyterHub with Kubernetes <https://github.com/jupyterhub/zero-to-jupyterhub-k8s>`__ .
Four subsystems make up JupyterHub:
* a **Hub** (tornado process) that is the heart of JupyterHub
* a **configurable http proxy** (node-http-proxy) that receives the requests from the client's browser
* multiple **single-user Jupyter notebook servers** (Python/IPython/tornado) that are monitored by Spawners
* an **authentication class** that manages how users can access the system
Besides these central pieces, you can add optional configurations through a `config.py` file and manage users kernels on an admin panel. A simplification of the whole system can be seen in the figure below:
.. image:: images/jhub-fluxogram.jpeg
:alt: JupyterHub subsystems
:width: 40%
:align: right
:width: 80%
:align: center
Three subsystems make up JupyterHub:
* a multi-user **Hub** (tornado process)
* a **configurable http proxy** (node-http-proxy)
* multiple **single-user Jupyter notebook servers** (Python/IPython/tornado)
JupyterHub performs the following functions:

View File

@@ -25,6 +25,8 @@ Some examples include:
run without being root, by spawning an intermediate process via `sudo`
- [BatchSpawner](https://github.com/jupyterhub/batchspawner) for spawning remote
servers using batch systems
- [YarnSpawner](https://github.com/jcrist/yarnspawner) for spawning notebook
servers in YARN containers on a Hadoop cluster
- [RemoteSpawner](https://github.com/zonca/remotespawner) to spawn notebooks
and a remote server and tunnel the port via SSH

View File

@@ -127,3 +127,11 @@ A handy website for testing your deployment is
[configurable-http-proxy]: https://github.com/jupyterhub/configurable-http-proxy
## Vulnerability reporting
If you believe youve found a security vulnerability in JupyterHub, or any
Jupyter project, please report it to
[security@ipython.org](mailto:security@iypthon.org). If you prefer to encrypt
your security reports, you can use [this PGP public
key](https://jupyter-notebook.readthedocs.io/en/stable/_downloads/ipython_security.asc).

View File

@@ -38,17 +38,18 @@ class TraitDocumenter(AttributeDocumenter):
def can_document_member(cls, member, membername, isattr, parent):
return isinstance(member, TraitType)
def format_name(self):
return 'config c.' + super().format_name()
def add_directive_header(self, sig):
default = self.object.get_default_value()
if default is Undefined:
default_s = ''
else:
default_s = repr(default)
sig = ' = {}({})'.format(self.object.__class__.__name__, default_s)
return super().add_directive_header(sig)
self.options.annotation = 'c.{name} = {trait}({default})'.format(
name=self.format_name(),
trait=self.object.__class__.__name__,
default=default_s,
)
super().add_directive_header(sig)
def setup(app):

View File

@@ -6,7 +6,7 @@ version_info = (
1,
0,
0,
"b2", # release (b1, rc1, or "" for final or dev)
# "b2", # release (b1, rc1, or "" for final or dev)
# "dev", # dev or nothing
)

View File

@@ -277,7 +277,7 @@ class JupyterHub(Application):
try:
cls = entry_point.load()
except Exception as e:
self.log.warning(
self.log.debug(
"Failed to load %s entrypoint %r: %r",
trait.entry_point_group,
key,
@@ -2184,7 +2184,6 @@ class JupyterHub(Application):
self.log.info("Cleaning up PID file %s", self.pid_file)
os.remove(self.pid_file)
# finally stop the loop once we are all cleaned up
self.log.info("...done")
def write_config_file(self):
@@ -2422,37 +2421,51 @@ class JupyterHub(Application):
self.log.info("JupyterHub is now running at %s", self.proxy.public_url)
# register cleanup on both TERM and INT
atexit.register(self.atexit)
self.init_signal()
def init_signal(self):
signal.signal(signal.SIGTERM, self.sigterm)
loop = asyncio.get_event_loop()
for s in (signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(
s, lambda s=s: asyncio.ensure_future(self.shutdown_cancel_tasks(s))
)
infosignals = [signal.SIGUSR1]
if hasattr(signal, 'SIGINFO'):
signal.signal(signal.SIGINFO, self.log_status)
infosignals.append(signal.SIGINFO)
for s in infosignals:
loop.add_signal_handler(
s, lambda s=s: asyncio.ensure_future(self.log_status(s))
)
def log_status(self, signum, frame):
async def log_status(self, sig):
"""Log current status, triggered by SIGINFO (^T in many terminals)"""
self.log.debug("Received signal %s[%s]", signum, signal.getsignal(signum))
self.log.critical("Received signal %s...", sig.name)
print_ps_info()
print_stacks()
def sigterm(self, signum, frame):
self.log.critical("Received SIGTERM, shutting down")
raise SystemExit(128 + signum)
async def shutdown_cancel_tasks(self, sig):
"""Cancel all other tasks of the event loop and initiate cleanup"""
self.log.critical("Received signal %s, initiating shutdown...", sig.name)
tasks = [
t for t in asyncio.Task.all_tasks() if t is not asyncio.Task.current_task()
]
_atexit_ran = False
if tasks:
self.log.debug("Cancelling pending tasks")
[t.cancel() for t in tasks]
def atexit(self):
"""atexit callback"""
if self._atexit_ran:
return
self._atexit_ran = True
# run the cleanup step (in a new loop, because the interrupted one is unclean)
asyncio.set_event_loop(asyncio.new_event_loop())
IOLoop.clear_current()
loop = IOLoop()
loop.make_current()
loop.run_sync(self.cleanup)
try:
await asyncio.wait(tasks)
except asyncio.CancelledError as e:
self.log.debug("Caught Task CancelledError. Ignoring")
except StopAsyncIteration as e:
self.log.error("Caught StopAsyncIteration Exception", exc_info=True)
tasks = [t for t in asyncio.Task.all_tasks()]
for t in tasks:
self.log.debug("Task status: %s", t)
await self.cleanup()
asyncio.get_event_loop().stop()
def stop(self):
if not self.io_loop:
@@ -2479,6 +2492,9 @@ class JupyterHub(Application):
loop.start()
except KeyboardInterrupt:
print("\nInterrupted")
finally:
loop.stop()
loop.close()
NewToken.classes.append(JupyterHub)

View File

@@ -18,33 +18,73 @@ class LogoutHandler(BaseHandler):
def shutdown_on_logout(self):
return self.settings.get('shutdown_on_logout', False)
async def get(self):
async def _shutdown_servers(self, user):
"""Shutdown servers for logout
Get all active servers for the provided user, stop them.
"""
active_servers = [
name
for (name, spawner) in user.spawners.items()
if spawner.active and not spawner.pending
]
if active_servers:
self.log.info("Shutting down %s's servers", user.name)
futures = []
for server_name in active_servers:
futures.append(maybe_future(self.stop_single_user(user, server_name)))
await asyncio.gather(*futures)
def _backend_logout_cleanup(self, name):
"""Default backend logout actions
Send a log message, clear some cookies, increment the logout counter.
"""
self.log.info("User logged out: %s", name)
self.clear_login_cookie()
self.statsd.incr('logout')
async def default_handle_logout(self):
"""The default logout action
Optionally cleans up servers, clears cookies, increments logout counter
Cleaning up servers can be prevented by setting shutdown_on_logout to
False.
"""
user = self.current_user
if user:
if self.shutdown_on_logout:
active_servers = [
name
for (name, spawner) in user.spawners.items()
if spawner.active and not spawner.pending
]
if active_servers:
self.log.info("Shutting down %s's servers", user.name)
futures = []
for server_name in active_servers:
futures.append(
maybe_future(self.stop_single_user(user, server_name))
)
await asyncio.gather(*futures)
await self._shutdown_servers(user)
self.log.info("User logged out: %s", user.name)
self.clear_login_cookie()
self.statsd.incr('logout')
self._backend_logout_cleanup(user.name)
async def handle_logout(self):
"""Custom user action during logout
By default a no-op, this function should be overridden in subclasses
to have JupyterHub take a custom action on logout.
"""
return
async def render_logout_page(self):
"""Render the logout page, if any
Override this function to set a custom logout page.
"""
if self.authenticator.auto_login:
html = self.render_template('logout.html')
self.finish(html)
else:
self.redirect(self.settings['login_url'], permanent=False)
async def get(self):
"""Log the user out, call the custom action, forward the user
to the logout page
"""
await self.default_handle_logout()
await self.handle_logout()
await self.render_logout_page()
class LoginHandler(BaseHandler):
"""Render the login page."""

View File

@@ -30,7 +30,7 @@ def generate_old_db(env_dir, hub_version, db_url):
if 'mysql' in db_url:
pkgs.append('mysql-connector-python')
elif 'postgres' in db_url:
pkgs.append('psycopg2')
pkgs.append('psycopg2-binary')
check_call([env_pip, 'install'] + pkgs)
check_call([env_py, populate_db, db_url])

View File

@@ -393,7 +393,7 @@ async def test_user_redirect(app, username):
path = urlparse(r.url).path
while '/spawn-pending/' in path:
await asyncio.sleep(0.1)
r = await get_page(r.url, app, cookies=cookies)
r = await async_requests.get(r.url, cookies=cookies)
path = urlparse(r.url).path
assert path == ujoin(app.base_url, '/user/%s/notebooks/test.ipynb' % name)

View File

@@ -159,6 +159,10 @@ async def api_request(
def get_page(path, app, hub=True, **kw):
if "://" in path:
raise ValueError(
"Not a hub page path: %r. Did you mean async_requests.get?" % path
)
if hub:
prefix = app.hub.base_url
else: