mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Added support for Redis sentinel
This commit is contained in:
@@ -108,8 +108,9 @@ jobs:
|
||||
run: |
|
||||
pip install -r mysql-requirements.txt
|
||||
echo "DB=mysql" >> $GITHUB_ENV
|
||||
- name: Test with pytest
|
||||
- name: Run tests
|
||||
if: ${{ matrix.python-version != '3.12' || matrix.database != 'postgres' }}
|
||||
uses: hoverkraft-tech/compose-action@v2.0.1
|
||||
run: |
|
||||
python ./tests.py
|
||||
cd test_project
|
||||
@@ -126,7 +127,26 @@ jobs:
|
||||
LDAP_SERVER_PORT: ${{ job.services.openldap.ports[389] }}
|
||||
OIDC_RSA_PRIVATE_KEY: ${{ vars.OIDC_RSA_PRIVATE_KEY }}
|
||||
|
||||
- name: Test with pytest and coverage
|
||||
- name: Start redis sentinel
|
||||
if: ${{ matrix.python-version == '3.12' || matrix.database == 'postgres' }}
|
||||
uses: hoverkraft-tech/compose-action@v2.0.1
|
||||
with:
|
||||
compose-file: "./docker/compose.redis-sentinel.yml"
|
||||
|
||||
- name: Run tests againt redis sentinel
|
||||
if: ${{ matrix.python-version == '3.12' || matrix.database == 'postgres' }}
|
||||
run: |
|
||||
cd test_project
|
||||
coverage run manage.py test modoboa.policyd
|
||||
env:
|
||||
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
|
||||
REDIS_SENTINEL: 1
|
||||
REDIS_SENTINEL_HOST: localhost
|
||||
REDIS_SENTINEL_PORT: 26379
|
||||
REDIS_MASTER: mymaster
|
||||
OIDC_RSA_PRIVATE_KEY: ${{ vars.OIDC_RSA_PRIVATE_KEY }}
|
||||
|
||||
- name: Run tests and coverage
|
||||
if: ${{ matrix.python-version == '3.12' && matrix.database == 'postgres' }}
|
||||
run: |
|
||||
python ./tests.py
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
networks:
|
||||
redis-net:
|
||||
external: true
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:5-alpine
|
||||
@@ -41,6 +45,9 @@ services:
|
||||
python3 manage.py runserver_plus 0.0.0.0:8000 --cert-file cert.crt"
|
||||
volumes:
|
||||
- .:/code
|
||||
networks:
|
||||
- default
|
||||
- redis-net
|
||||
|
||||
api-unsecured:
|
||||
container_name: modoboa_api_unsecured
|
||||
@@ -89,3 +96,28 @@ services:
|
||||
- api
|
||||
volumes:
|
||||
- ./docker/dovecot_config:/etc/dovecot/conf.d
|
||||
|
||||
policyd:
|
||||
container_name: modoboa_policyd
|
||||
restart: unless-stopped
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/Dockerfile.dev
|
||||
ports:
|
||||
- 9999:9999
|
||||
env_file:
|
||||
- test_project/oidc_key
|
||||
environment:
|
||||
DB: "SQLITE"
|
||||
DEBUG: "True"
|
||||
REDIS_HOST: redis
|
||||
PYTHONPATH: ".."
|
||||
PYTHONUNBUFFERED: 1
|
||||
command: >
|
||||
sh -c "cd test_project
|
||||
python3 manage.py policy_daemon --host 0.0.0.0 --port 9999 --debug"
|
||||
volumes:
|
||||
- .:/code
|
||||
networks:
|
||||
- default
|
||||
- redis-net
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
services:
|
||||
redis-master:
|
||||
image: redis:7-alpine
|
||||
|
||||
redis-slave:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --slaveof redis-master 6379
|
||||
depends_on:
|
||||
- redis-master
|
||||
|
||||
redis-sentinel:
|
||||
image: redis:7-alpine
|
||||
depends_on:
|
||||
- redis-master
|
||||
- redis-slave
|
||||
ports:
|
||||
- "26379:26379"
|
||||
command: >
|
||||
sh -c 'echo "bind 0.0.0.0" > /etc/sentinel.conf &&
|
||||
echo "sentinel monitor mymaster redis-master 6379 2" >> /etc/sentinel.conf &&
|
||||
echo "sentinel resolve-hostnames yes" >> /etc/sentinel.conf &&
|
||||
echo "sentinel down-after-milliseconds mymaster 10000" >> /etc/sentinel.conf &&
|
||||
echo "sentinel failover-timeout mymaster 10000" >> /etc/sentinel.conf &&
|
||||
echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
|
||||
redis-sentinel /etc/sentinel.conf'
|
||||
+19
-16
@@ -31,6 +31,15 @@ SUCCESS_ACTION = b"dunno"
|
||||
FAILURE_ACTION = b"defer_if_permit Daily limit reached, retry later"
|
||||
|
||||
|
||||
def get_redis_client():
|
||||
if not getattr(settings, "REDIS_SENTINEL", False):
|
||||
return aioredis.from_url(
|
||||
settings.REDIS_URL, encoding="utf-8", decode_responses=True
|
||||
)
|
||||
sentinel = aioredis.sentinel.Sentinel(settings.REDIS_SENTINELS, socket_timeout=0.1)
|
||||
return sentinel.master_for(settings.REDIS_MASTER, socket_timeout=0.1)
|
||||
|
||||
|
||||
def close_db_connections(func, *args, **kwargs):
|
||||
"""
|
||||
Make sure to close all connections to DB.
|
||||
@@ -144,9 +153,7 @@ async def apply_policies(attributes):
|
||||
sasl_username = attributes.get("sasl_username")
|
||||
if not sasl_username:
|
||||
return SUCCESS_ACTION
|
||||
rclient = aioredis.from_url(
|
||||
settings.REDIS_URL, encoding="utf-8", decode_responses=True
|
||||
)
|
||||
rclient = get_redis_client()
|
||||
decr_domain = False
|
||||
decr_user = False
|
||||
localpart, domain = split_mailbox(sasl_username)
|
||||
@@ -200,16 +207,14 @@ async def handle_connection(reader, writer):
|
||||
|
||||
|
||||
async def new_connection(reader, writer):
|
||||
try:
|
||||
await asyncio.wait_for(handle_connection(reader, writer), timeout=5)
|
||||
except asyncio.TimeoutError as err:
|
||||
logger.warning("Timeout received while handling connection: %s", err)
|
||||
finally:
|
||||
writer.close()
|
||||
if hasattr(writer, "wait_closed"):
|
||||
# Python 3.7+ only
|
||||
await writer.wait_closed()
|
||||
logger.info("exit")
|
||||
# try:
|
||||
await asyncio.wait_for(handle_connection(reader, writer), timeout=5)
|
||||
# except asyncio.TimeoutError as err:
|
||||
# logger.warning(f"Timeout received while handling connection: {err}")
|
||||
# finally:
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
logger.info("exit")
|
||||
|
||||
|
||||
def get_next_execution_dt():
|
||||
@@ -255,9 +260,7 @@ def get_mailboxes_to_reset():
|
||||
|
||||
async def reset_counters():
|
||||
"""Reset all counters."""
|
||||
rclient = aioredis.from_url(
|
||||
settings.REDIS_URL, encoding="utf-8", decode_responses=True
|
||||
)
|
||||
rclient = get_redis_client()
|
||||
logger.info("Resetting all counters")
|
||||
for domain in await get_domains_to_reset():
|
||||
await rclient.hset(constants.REDIS_HASHNAME, domain.name, domain.message_limit)
|
||||
|
||||
@@ -16,9 +16,16 @@ def set_message_limit(instance, key):
|
||||
old_message_limit = instance._loaded_values.get("message_limit")
|
||||
if old_message_limit == instance.message_limit:
|
||||
return
|
||||
rclient = redis.Redis(
|
||||
host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_QUOTA_DB
|
||||
)
|
||||
if not getattr(settings, "REDIS_SENTINEL", False):
|
||||
rclient = redis.Redis(
|
||||
host=settings.REDIS_HOST,
|
||||
port=settings.REDIS_PORT,
|
||||
db=settings.REDIS_QUOTA_DB,
|
||||
)
|
||||
else:
|
||||
sentinel = redis.sentinel.Sentinel(settings.REDIS_SENTINELS, socket_timeout=0.1)
|
||||
rclient = sentinel.master_for(settings.REDIS_MASTER, socket_timeout=0.1)
|
||||
|
||||
if instance.message_limit is None:
|
||||
# delete existing key
|
||||
if rclient.hexists(constants.REDIS_HASHNAME, key):
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
WEBMAIL_DEV_USERNAME=tonio@ngyn.org
|
||||
WEBMAIL_DEV_PASSWORD=Aka/12ctogael
|
||||
@@ -256,6 +256,15 @@ PID_FILE_STORAGE_PATH = "/tmp"
|
||||
|
||||
# REDIS
|
||||
|
||||
REDIS_SENTINEL = bool(os.environ.get("REDIS_SENTINEL", False))
|
||||
REDIS_SENTINELS = [
|
||||
(
|
||||
os.environ.get("REDIS_SENTINEL_HOST", "127.0.0.1"),
|
||||
os.environ.get("REDIS_SENTINEL_PORT", 26379),
|
||||
)
|
||||
]
|
||||
REDIS_MASTER = os.environ.get("REDIS_MASTER", "mymaster")
|
||||
|
||||
REDIS_HOST = os.environ.get("REDIS_HOST", "127.0.0.1")
|
||||
REDIS_PORT = os.environ.get("REDIS_PORT", 6379)
|
||||
REDIS_QUOTA_DB = 0
|
||||
|
||||
Reference in New Issue
Block a user