Compare commits

...

10 Commits
1.5.0 ... 1.5.1

Author SHA1 Message Date
Min RK
f7903a78d1 release 1.5.1 2022-12-05 13:27:57 +01:00
Erik Sundell
edddacec1d Merge pull request #4234 from minrk/1.x
changelog for 1.5.1
2022-12-01 16:43:44 +01:00
Min RK
c5a33f227f changelog for 1.5.1 2022-11-30 10:56:00 +01:00
Min RK
aaa20e5787 fix flake8 url in pre-commit
repo moved
2022-11-30 10:55:52 +01:00
Min RK
c8ebf1ec90 Merge pull request #4076 from mriedem/1.x-db-fix
Backport PR #3566 to 1.x series
2022-10-12 12:37:22 +02:00
pre-commit-ci[bot]
89745c002b [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-10-10 15:44:36 +00:00
Matt Riedemann
3e0ee49ce8 Upgrade black to 22.3.0
Partial cherry-pick of f124f06c2 needed because of
https://github.com/psf/black/issues/2964.
2022-10-10 10:43:36 -05:00
pre-commit-ci[bot]
bbbeffb443 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-10-10 13:49:36 +00:00
Min RK
c29a5ca4ce finish up db rollback checks
- move catch_db_error to utils
- tidy catch/propagate errors in prepare, get_current_user

(cherry picked from commit 3bcc542e27)

Conflicts:
  jupyterhub/handlers/base.py

NOTE(mriedem): The conflict is due to e6845a68f not being
in 1.5.0.
2022-10-10 08:47:00 -05:00
SHAHN3
f5182fe349 add explicit db rollback
add context manager/decorator for db rollback

add db rollback in top level prepare method

Co-authored-by: Sarath Babu <sbreached@gmail.com>
(cherry picked from commit 044fb23a70)
2022-10-10 08:42:42 -05:00
12 changed files with 64 additions and 25 deletions

View File

@@ -4,14 +4,14 @@ repos:
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 20.8b1 rev: 22.3.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.2.1 rev: v2.2.1
hooks: hooks:
- id: prettier - id: prettier
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.com/pycqa/flake8
rev: "3.8.4" rev: "3.8.4"
hooks: hooks:
- id: flake8 - id: flake8

View File

@@ -3,7 +3,7 @@ swagger: "2.0"
info: info:
title: JupyterHub title: JupyterHub
description: The REST API for JupyterHub description: The REST API for JupyterHub
version: 1.5.0 version: 1.5.1
license: license:
name: BSD-3-Clause name: BSD-3-Clause
schemes: [http, https] schemes: [http, https]

View File

@@ -17,6 +17,22 @@ A few fully backward-compatible features have been backported from 2.0.
[ghsa-cw7p-q79f-m2v7]: https://github.com/jupyterhub/jupyterhub/security/advisories/GHSA-cw7p-q79f-m2v7 [ghsa-cw7p-q79f-m2v7]: https://github.com/jupyterhub/jupyterhub/security/advisories/GHSA-cw7p-q79f-m2v7
### 1.5.1 2022-12-01
This is a patch release, improving db resiliency when certain errors occur, without requiring a jupyterhub restart.
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.5.0...1.5.1))
#### Merged PRs
- Backport db rollback fixes to 1.x [#4076](https://github.com/jupyterhub/jupyterhub/pull/4076) ([@mriedem](https://github.com/mriedem), [@minrk](https://github.com/minrk)), [@nsshah1288](https://github.com/nsshah1288)
#### Contributors to this release
([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2022-09-09&to=2022-11-30&type=c))
[@mriedem](https://github.com/mriedem) | [@minrk](https://github.com/minrk) | [@nsshah1288](https://github.com/nsshah1288)
### [1.5.0] 2021-11-04 ### [1.5.0] 2021-11-04
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.4.2...1.5.0)) ([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.4.2...1.5.0))

View File

@@ -5,7 +5,7 @@
version_info = ( version_info = (
1, 1,
5, 5,
0, 1,
"", # release (b1, rc1, or "" for final or dev) "", # release (b1, rc1, or "" for final or dev)
# "dev", # dev or nothing for beta/rc/stable releases # "dev", # dev or nothing for beta/rc/stable releases
) )

View File

@@ -5,7 +5,6 @@
import asyncio import asyncio
import atexit import atexit
import binascii import binascii
import json
import logging import logging
import os import os
import re import re
@@ -90,6 +89,7 @@ from .pagination import Pagination
from .proxy import Proxy, ConfigurableHTTPProxy from .proxy import Proxy, ConfigurableHTTPProxy
from .traitlets import URLPrefix, Command, EntryPointType, Callable from .traitlets import URLPrefix, Command, EntryPointType, Callable
from .utils import ( from .utils import (
catch_db_error,
maybe_future, maybe_future,
url_path_join, url_path_join,
print_stacks, print_stacks,
@@ -2000,6 +2000,7 @@ class JupyterHub(Application):
# purge expired tokens hourly # purge expired tokens hourly
purge_expired_tokens_interval = 3600 purge_expired_tokens_interval = 3600
@catch_db_error
def purge_expired_tokens(self): def purge_expired_tokens(self):
"""purge all expiring token objects from the database """purge all expiring token objects from the database
@@ -2015,7 +2016,7 @@ class JupyterHub(Application):
await self._add_tokens(self.service_tokens, kind='service') await self._add_tokens(self.service_tokens, kind='service')
await self._add_tokens(self.api_tokens, kind='user') await self._add_tokens(self.api_tokens, kind='user')
self.purge_expired_tokens() await self.purge_expired_tokens()
# purge expired tokens hourly # purge expired tokens hourly
# we don't need to be prompt about this # we don't need to be prompt about this
# because expired tokens cannot be used anyway # because expired tokens cannot be used anyway
@@ -2663,6 +2664,7 @@ class JupyterHub(Application):
with open(self.config_file, mode='w') as f: with open(self.config_file, mode='w') as f:
f.write(config_text) f.write(config_text)
@catch_db_error
async def update_last_activity(self): async def update_last_activity(self):
"""Update User.last_activity timestamps from the proxy""" """Update User.last_activity timestamps from the proxy"""
routes = await self.proxy.get_all_routes() routes = await self.proxy.get_all_routes()

View File

@@ -81,9 +81,14 @@ class BaseHandler(RequestHandler):
""" """
try: try:
await self.get_current_user() await self.get_current_user()
except Exception: except Exception as e:
self.log.exception("Failed to get current user") # ensure get_current_user is never called again for this handler,
# since it failed
self._jupyterhub_user = None self._jupyterhub_user = None
self.log.exception("Failed to get current user")
if isinstance(e, SQLAlchemyError):
self.log.error("Rolling back session due to database error")
self.db.rollback()
return await maybe_future(super().prepare()) return await maybe_future(super().prepare())
@@ -426,7 +431,8 @@ class BaseHandler(RequestHandler):
except Exception: except Exception:
# don't let errors here raise more than once # don't let errors here raise more than once
self._jupyterhub_user = None self._jupyterhub_user = None
self.log.exception("Error getting current user") # but still raise, which will get handled in .prepare()
raise
return self._jupyterhub_user return self._jupyterhub_user
@property @property
@@ -1499,14 +1505,10 @@ class UserUrlHandler(BaseHandler):
# if request is expecting JSON, assume it's an API request and fail with 503 # if request is expecting JSON, assume it's an API request and fail with 503
# because it won't like the redirect to the pending page # because it won't like the redirect to the pending page
if ( if get_accepted_mimetype(
get_accepted_mimetype(
self.request.headers.get('Accept', ''), self.request.headers.get('Accept', ''),
choices=['application/json', 'text/html'], choices=['application/json', 'text/html'],
) ) == 'application/json' or 'api' in user_path.split('/'):
== 'application/json'
or 'api' in user_path.split('/')
):
self._fail_api_request(user_name, server_name) self._fail_api_request(user_name, server_name)
return return

View File

@@ -4,9 +4,9 @@
import asyncio import asyncio
import concurrent.futures import concurrent.futures
import errno import errno
import functools
import hashlib import hashlib
import inspect import inspect
import os
import random import random
import secrets import secrets
import socket import socket
@@ -22,6 +22,7 @@ from hmac import compare_digest
from operator import itemgetter from operator import itemgetter
from async_generator import aclosing from async_generator import aclosing
from sqlalchemy.exc import SQLAlchemyError
from tornado import ioloop from tornado import ioloop
from tornado import web from tornado import web
from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import AsyncHTTPClient
@@ -642,3 +643,21 @@ def get_accepted_mimetype(accept_header, choices=None):
else: else:
return mime return mime
return None return None
def catch_db_error(f):
"""Catch and rollback database errors"""
@functools.wraps(f)
async def catching(self, *args, **kwargs):
try:
r = f(self, *args, **kwargs)
if inspect.isawaitable(r):
r = await r
except SQLAlchemyError:
self.log.exception("Rolling back session due to database error")
self.db.rollback()
else:
return r
return catching

View File

@@ -26,8 +26,8 @@ import os
import pipes import pipes
import shutil import shutil
from contextlib import contextmanager from contextlib import contextmanager
from distutils.version import LooseVersion as V
from distutils.version import LooseVersion as V
from invoke import run as invoke_run from invoke import run as invoke_run
from invoke import task from invoke import task