Skip to content

Commit a546ab9

Browse files
committed
Summary: Support selecion session/otk data store. Add redis as data store.
Allow admin to select the backend data store. Compatibility matrix: main\/ session>| anydbm | sqlite | redis | mysql | postgresql | anydbm | D | | X | | | sqlite | X | D | X | | | mysql | | | | D | | postgresql | | | | | D | --------------------------------------------------------------+ D - default if unconfigured, X - compatible choice DETAILS roundup/configuration.py: add config.ini section sessiondb with settings: backend and redis_url. CHANGES.txt, doc/admin_guide.txt, doc/installation.txt, doc/upgrading.txt: doc on config of session db and redis. Plus some other fixes: admin - clarified why we do not drop __words and __testids table in native-fts conversion. TYpo fix. upgrading - doc how you can keep using anydbm for session data with sqlite. Fix dupe sentence in an upgrading config.ini section. roundup/backends/back_anydbm.py, roundup/backends/back_sqlite.py: code to support redis, redis/anydbm backends respectively. roundup/backends/sessions_redis.py new storage backend for redis. roundup/rest.py, roundup/cgi/actions.py, roundup/cgi/templating.py redis uses a different way of calculating lifetime/timestamp. Since expiration of an item occurred if its timestamp was more than 1 week old, code would calculate: now - 1 week + lifetime. But this results in faster expiration in redis if used for lifetime/timestamp. Convert code to use the lifetime() method in BasicDatabase that generates the right timestamp for each backend. test/session_common.py: added tests for more cases, get without default, getall non-existing key etc. timestamp test changed to use new self.get_ts which is overridden in other tests. Test that datatypes survive storage. test/test_redis_session.py: test redis session store with sqlite and anydbm primary databases test/test_anydbm.py, test/test_sqlite.py add test to make sure the databases are properly set up sqlite - add test cases where anydbm is used as datastore anydbm - remove updateTimestamp override add get_ts(). test/test_config.py tests on redis_url and compatibility on choice of sessiondb backend .travis.yml: add redis db and redis-py
1 parent 30367b7 commit a546ab9

17 files changed

+826
-63
lines changed

.travis.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ python:
3131
services:
3232
- mysql
3333
- postgresql
34+
- redis-server
3435

3536
jobs:
3637
allow_failures: # releases not ready for prime time yet.
@@ -113,6 +114,7 @@ install:
113114
- if [[ $TRAVIS_PYTHON_VERSION == "3.4"* ]]; then pip install mysqlclient==1.3.14; fi
114115
- if [[ $TRAVIS_PYTHON_VERSION != "3.4"* ]]; then pip install mysqlclient; fi
115116
- pip install psycopg2
117+
- pip install redis
116118
- pip install gpg pytz whoosh pyjwt requests
117119
- pip install jinja2
118120
- pip install pytest-cov codecov

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ Features:
3535

3636
- Dockerfile build allows adding additional python packages via
3737
pip, setting UID tracker is run under. (John Rouillard)
38+
- issue2551140 - Added redis as a session and otk database for use
39+
with anydbm and sqlite primary databases. (John Rouillard)
3840

3941
2022-07-13 2.2.0
4042

doc/admin_guide.txt

Lines changed: 89 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,8 +443,94 @@ the following SQL commands::
443443
delete from __words;
444444
delete from __textids;
445445

446-
Note this deletes data from the tables and does *not* delete the
447-
table.
446+
Note this deletes data from the tables and does *not* delete
447+
the table. This allows you to revert to Roundup's native
448+
full text indexing on SQLite or Postgres. If you were to
449+
delete the tables, Roundup will not recreate the
450+
tables. Under PostgreSQL, you can use the ``truncate
451+
<tablename>`` command if you wish.
452+
453+
Configuring Session Databases
454+
=============================
455+
456+
The session and OTK (one time key) databases
457+
store information about the operation of Roundup.
458+
This ephemeral data:
459+
460+
* web login session keys,
461+
* CSRF tokens,
462+
* email password recovery one time keys,
463+
* rate limiting data,
464+
* ...
465+
466+
can be a performance bottleneck. It usually happens with
467+
anydbm or SQLite backends. PostgreSQL and MySQL are
468+
sufficiently powerful that they can handle the higher
469+
transaction rates.
470+
471+
If you are using sqlite, you can choose to use the anydbm
472+
database for session data. By default it will use additional
473+
sqlite databases for storing the session and otk data.
474+
475+
The following table shows which primary databases support
476+
different session database backends::
477+
478+
479+
main\/ session>| anydbm | sqlite | redis | mysql | postgresql |
480+
anydbm | D | | X | | |
481+
sqlite | X | D | X | | |
482+
mysql | | | | D | |
483+
postgresql | | | | | D |
484+
--------------------------------------------------------------+
485+
D - default if unconfigured, X - compatible choice
486+
487+
The ``backend`` setting is in the tracker's ``config.ini``
488+
file under the ``sessiondb`` section.
489+
490+
Using Redis for Session Databases
491+
---------------------------------
492+
493+
Redis is an in memory key/value data structure store.
494+
495+
To use redis you must be using Python 3.6 or newer. You
496+
need to install the redis-py_ module from pypi. Then install
497+
Redis using your package manager or by downloading it from
498+
the Redis_ website.
499+
500+
You need to secure your redis instance. The data that
501+
Roundup stores includes session cookies and other
502+
authentication tokens. At minimum you should require a
503+
password to connect to your redis database. Set
504+
``requirepass`` in ``redis.conf``. Then change the
505+
``redis_url`` in ``config.ini`` to use the password.
506+
507+
508+
For example::
509+
510+
redis://:mypassword@localhost:7200/10
511+
512+
will connect to the redis instance running on localhost at
513+
port 7200 using the password ``mypassword`` to open database
514+
10. The ``redis_url`` setting can load a file to better
515+
secure the url. If you are using redis 6.0 or newer, you can
516+
specify a username/password and access control lists to
517+
improv the security of your data. Another good alternative
518+
is to talk to redis using a Unix domain socket.
519+
520+
If you are connecting to redis across the network rather
521+
than on localhost, you should configure ssl/tls and use the
522+
``rediss`` scheme in the url along with the query
523+
parameters::
524+
525+
ssl_cert_reqs=required&ssl_ca_certs=/path/to/custom/ca-cert
526+
527+
where you specify the file that can be used to validate the
528+
SSL certificate. `Securing Redis`_ has more details.
529+
530+
.. _Redis: https://redis.io
531+
.. _redis-py: https://pypi.org/project/redis/
532+
.. _Securing Redis: https://redis.io/docs/manual/security/
533+
448534

449535
Users and Security
450536
==================
@@ -579,7 +665,7 @@ Steps you may take:
579665
python setup.py install
580666

581667
6. Follow the steps in the `upgrading documentation`_ for all the
582-
version between your original version and th new version.
668+
versions between your original version and the new version.
583669

584670
Usually you should run `roundup_admin -i <tracker_home> migrate`
585671
on your tracker(s) before you allow users to start accessing the tracker.

doc/installation.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,20 @@ zstd, brotli
137137
compress the response from roundup as they transmit/proxy it to the
138138
client.
139139

140+
redis
141+
Storing ephemeral data: session keys, CSRF tokens etc. can be
142+
performance bottleneck. You can choose to deploy a Redis_ database
143+
using the redis-py_ pypi package. See the section on
144+
`Using Redis for Session Databases`_ in the `administration
145+
guide`_ for details.
146+
140147
Windows Service
141148
You can run Roundup as a Windows service if pywin32_ is installed.
142149
Otherwise it must be started manually.
143150

151+
.. _Using Redis for Session Databases:
152+
admin_guide.html#using-redis-for-session-databases
153+
144154
Getting Roundup
145155
===============
146156

@@ -1770,6 +1780,8 @@ there are no errors. If there are errors, please let us know!
17701780
.. _pysqlite: https://pysqlite.org/
17711781
.. _pytz: https://pypi.org/project/pytz/
17721782
.. _pywin32: https://pypi.org/project/pywin32/
1783+
.. _Redis: https://redis.io
1784+
.. _redis-py: https://pypi.org/project/redis/
17731785
.. _Whoosh: https://whoosh.readthedocs.org/en/latest
17741786
.. _Xapian: https://xapian.org/
17751787
.. _zstd: https://pypi.org/project/zstd/

doc/upgrading.txt

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,18 @@ Contents:
3535
Migrating from 2.2.0 to 2.3.0
3636
=============================
3737

38+
Update your ``config.ini`` (required)
39+
-------------------------------------
40+
41+
Upgrade tracker's config.ini file. Use::
42+
43+
roundup-admin -i /path/to/tracker updateconfig newconfig.ini
44+
45+
to generate a new ini file preserving all your settings.
46+
You can then merge any local comments from the tracker's
47+
``config.ini`` to ``newconfig.ini`` and replace
48+
``config.ini`` with ``newconfig.ini``.
49+
3850
Rdbms version change from 7 to 8 (required)
3951
-------------------------------------------
4052

@@ -67,8 +79,8 @@ RDBMS backend) or "No migration action required" (if you
6779
have run it, or have used another interface to the tracker,
6880
or are using anydbm).
6981

70-
Session/OTK data storage for SQLite backend changed
71-
---------------------------------------------------
82+
Session/OTK data storage for SQLite backend changed (required)
83+
--------------------------------------------------------------
7284

7385
Roundup stores a lot of ephemeral data:
7486

@@ -83,17 +95,35 @@ is stored in a SQLite db. Using both dbm and sqlite style
8395
files is surprising and due to how we lock dbm files can be
8496
a performance issue.
8597

86-
In this release two sqlite databases called ``db-otk`` and
87-
``db-session`` replace the dbm databases. Once you make the
88-
change the old ``otks`` and ``sessions`` dbm databases can
89-
be removed.
98+
However you can continue to use the dbm files by setting the
99+
``backend`` option in the ``[sessiondb]`` section of
100+
``config.ini`` to ``anydbm``.
101+
102+
If you do not change the setting, two sqlite databases
103+
called ``db-otk`` and ``db-session`` replace the dbm
104+
databases. Once you make the change the old ``otks`` and
105+
``sessions`` dbm databases can be removed.
90106

91107
Note this replacement will require users to log in again and
92-
refresh web pages to save data. It is best is people save
108+
refresh web pages to save data. It is best if people save
93109
all their changes and log out of Roundup before the upgrade
94110
is done to minimize confusion. Because the data is
95111
ephemeral, there is no plan to migrate this data to the new
96-
SQLite databases.
112+
SQLite databases. If you want to keep using the data set the
113+
``sessiondb`` ``backend`` option as described above.
114+
115+
Session/OTK data storage using Redis (optional)
116+
-----------------------------------------------
117+
118+
If you are running Roundup with Python 3.6 or newer, you can
119+
store your ephemeral data in a Redis database. This provides
120+
significantly better performance for ephemeral data than
121+
SQLite or dbm files. See the section `Using Redis for Session
122+
Databases`_ in the `administration guide`_
123+
124+
125+
.. _Using Redis for Session Databases:
126+
admin_guide.html#using-redis-for-session-databases
97127

98128
.. index:: Upgrading; 2.1.0 to 2.2.0
99129

@@ -107,11 +137,10 @@ Upgrade tracker's config.ini file. Use::
107137

108138
roundup-admin -i /path/to/tracker updateconfig newconfig.ini
109139

110-
to generate a new ini file preserving all your settings. You
111-
can then merge any local comments from the tracker's
112-
``config.ini`` into ``newconfig.ini``. You can then merge
113-
comments from ``config.ini`` to ``newconfig.ini`` and
114-
replace ``config.ini`` with ``newconfig.ini``.
140+
to generate a new ini file preserving all your settings.
141+
You can then merge any local comments from the tracker's
142+
``config.ini`` to ``newconfig.ini`` and replace
143+
``config.ini`` with ``newconfig.ini``.
115144

116145
Rdbms version change from 6 to 7 (required)
117146
-------------------------------------------

roundup/backends/back_anydbm.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@
3333
from roundup.i18n import _
3434

3535
from roundup.backends.blobfiles import FileStorage
36-
from roundup.backends.sessions_dbm import Sessions, OneTimeKeys
36+
from roundup.backends import sessions_dbm
37+
38+
try:
39+
from roundup.backends import sessions_redis
40+
except ModuleNotFoundError:
41+
sessions_redis = None
3742

3843
from roundup.backends.indexer_common import get_indexer
3944

@@ -133,12 +138,26 @@ def refresh_database(self):
133138

134139
def getSessionManager(self):
135140
if not self.Session:
136-
self.Session = Sessions(self)
141+
if self.config.SESSIONDB_BACKEND == "redis":
142+
if sessions_redis is None:
143+
self.Session = sessions_dbm.Sessions(self)
144+
raise ValueError("[redis] session is set, but "
145+
"redis is not found")
146+
self.Session = sessions_redis.Sessions(self)
147+
else:
148+
self.Session = sessions_dbm.Sessions(self)
137149
return self.Session
138150

139151
def getOTKManager(self):
140152
if not self.Otk:
141-
self.Otk = OneTimeKeys(self)
153+
if self.config.SESSIONDB_BACKEND == "redis":
154+
if sessions_redis is None:
155+
self.Session = sessions_dbm.OneTimeKeys(self)
156+
raise ValueError("[redis] session is set, but "
157+
"redis is not found")
158+
self.Otk = sessions_redis.OneTimeKeys(self)
159+
else:
160+
self.Otk = sessions_dbm.OneTimeKeys(self)
142161
return self.Otk
143162

144163
def reindex(self, classname=None, show_progress=False):

roundup/backends/back_sqlite.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@
1212

1313
from roundup import hyperdb, date, password
1414
from roundup.backends import rdbms_common
15-
from roundup.backends.sessions_sqlite import Sessions, OneTimeKeys
15+
from roundup.backends import sessions_sqlite
16+
from roundup.backends import sessions_dbm
17+
18+
try:
19+
from roundup.backends import sessions_redis
20+
except ModuleNotFoundError:
21+
sessions_redis = None
22+
1623
from roundup.anypy.strings import uany2s
1724

1825
sqlite_version = None
@@ -105,12 +112,30 @@ class Database(rdbms_common.Database):
105112
# connections to the same database which SQLite doesn't support
106113
def getSessionManager(self):
107114
if not self.Session:
108-
self.Session = Sessions(self)
115+
if self.config.SESSIONDB_BACKEND == "redis":
116+
if sessions_redis is None:
117+
self.Session = sessions_sqlite.Sessions(self)
118+
raise ValueError("[redis] session is set, but "
119+
"redis module is not found")
120+
self.Session = sessions_redis.Sessions(self)
121+
elif self.config.SESSIONDB_BACKEND == "anydbm":
122+
self.Session = sessions_dbm.Sessions(self)
123+
else:
124+
self.Session = sessions_sqlite.Sessions(self)
109125
return self.Session
110126

111127
def getOTKManager(self):
112128
if not self.Otk:
113-
self.Otk = OneTimeKeys(self)
129+
if self.config.SESSIONDB_BACKEND == "redis":
130+
if sessions_redis is None:
131+
self.Session = sessions_sqlite.OneTimeKeys(self)
132+
raise ValueError("[redis] session is set, but "
133+
"redis is not found")
134+
self.Otk = sessions_redis.OneTimeKeys(self)
135+
elif self.config.SESSIONDB_BACKEND == "anydbm":
136+
self.Otk = sessions_dbm.OneTimeKeys(self)
137+
else:
138+
self.Otk = sessions_sqlite.OneTimeKeys(self)
114139
return self.Otk
115140

116141
def sqlite_busy_handler(self, data, table, count):

0 commit comments

Comments
 (0)