Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat redis: add standalone configuration #125

Merged
merged 9 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion docs/redis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,19 @@ TESTSUITE_REDIS_CLUSTER_REPLICAS

Use to override number of replicas per master in redis cluster. Default is ``1``.

TESTSUITE_REDIS_STANDALONE_PORT
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Use to override standalone server port. Default is ``7000``.


Fixtures
--------

redis_store
~~~~~~~~~~~

Provide access to non cluster redis via same interface as redis.StrictRedis().
Provide access to non cluster redis with sentinels via same interface as redis.StrictRedis().

.. code-block:: python

Expand Down Expand Up @@ -76,6 +81,27 @@ Provide access to cluster redis via same interface as redis.RedisCluster().
assert redis_cluster_store.get('somekey') == b'somedata'


redis_standalone_store
~~~~~~~~~~~~~~~~~~~~~~

Provide access to standalone redis (no slaves or sentinels, only single master)
via same interface as redis.StrictRedis().

.. code-block:: python

import pytest

pytest_plugins = [
'testsuite.pytest_plugin',
'testsuite.databases.redis.pytest_plugin',
]


def test_redis_basic(redis_standalone_store):
redis_standalone_store.set('somekey', 'somedata')
assert redis_standalone_store.get('somekey') == b'somedata'


redis_cluster_nodes
~~~~~~~~~~~~~~~~~~~

Expand All @@ -88,6 +114,12 @@ redis_cluster_replicas
Gives the number of replicas per primary node in redis cluster.


redis_standalone_node
~~~~~~~~~~~~~~~~~~~~~~

Returns a dictionary with 'host' and 'port' values of the standalone redis.


Marks
-----

Expand Down Expand Up @@ -143,3 +175,11 @@ Specify custom per-test data for ``redis_cluster_store`` fixture
@pytest.mark.redis_cluster_store(file='use_redis_store_file')
def test_redis_store_file(redis_cluster_store):
assert redis_cluster_store.get('foo') == b'store'



pytest.mark.redis_standalone_store
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Specify custom per-test data for ``redis_standalone_store`` fixture. See above
for a usage example.
9 changes: 9 additions & 0 deletions tests/databases/redis/test_standalone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import pytest
import redis

from testsuite.databases.redis import service


def test_standalone_rw(redis_standalone_store: redis.RedisCluster):
assert redis_standalone_store.set('foo_standalone', b'bar')
assert redis_standalone_store.get('foo_standalone') == b'bar'
25 changes: 25 additions & 0 deletions testsuite/databases/redis/genredis.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,31 @@ def generate_cluster_redis_configs(
)


def generate_standalone_redis_config(
output_path: pathlib.Path,
host: str,
port: int,
) -> None:
protected_mode_no = ''
if redis_version() >= (3, 2, 0):
protected_mode_no = 'protected-mode no'

input_file = _redis_config_directory() / MASTER_TPL_FILENAME
output_file = _construct_output_filename(
output_path,
MASTER_TPL_FILENAME,
0,
)

_generate_redis_config(
input_file,
output_file,
protected_mode_no,
host,
port,
)


def generate_redis_configs(
output_path: pathlib.Path,
host: str,
Expand Down
53 changes: 50 additions & 3 deletions testsuite/databases/redis/pytest_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,18 @@ def pytest_configure(config):
'markers',
'redis_cluster_store: per-test redis cluster initialization',
)
config.addinivalue_line(
'markers',
'redis_standalone_store: per-test standalone redis initialization',
)


def pytest_service_register(register_service):
register_service('redis', service.create_redis_service)
register_service('redis-cluster', service.create_cluster_redis_service)
register_service(
'redis-standalone', service.create_standalone_redis_service
)


@pytest.fixture(scope='session')
Expand All @@ -51,16 +58,24 @@ def redis_service(

@pytest.fixture(scope='session')
def redis_cluster_service(
pytestconfig,
ensure_service_started,
_redis_cluster_service_settings,
pytestconfig, ensure_service_started, _redis_cluster_service_settings
):
if not pytestconfig.option.no_redis and not pytestconfig.option.redis_host:
ensure_service_started(
'redis-cluster', settings=_redis_cluster_service_settings
)


@pytest.fixture(scope='session')
async def redis_standalone_service(
pytestconfig, ensure_service_started, _redis_standalone_service_settings
):
if not pytestconfig.option.no_redis and not pytestconfig.option.redis_host:
ensure_service_started(
'redis-standalone', settings=_redis_standalone_service_settings
)


@pytest.fixture
def redis_store(
pytestconfig,
Expand Down Expand Up @@ -176,6 +191,14 @@ def redis_cluster_nodes(_redis_cluster_service_settings):
]


@pytest.fixture(scope='session')
def redis_standalone_node(_redis_standalone_service_settings):
return {
'host': _redis_standalone_service_settings.host,
'port': _redis_standalone_service_settings.port,
}


@pytest.fixture(scope='session')
def redis_cluster_replicas(_redis_cluster_service_settings):
return _redis_cluster_service_settings.cluster_replicas
Expand All @@ -191,6 +214,11 @@ def _redis_cluster_service_settings():
return service.get_cluster_service_settings()


@pytest.fixture(scope='session')
def _redis_standalone_service_settings():
return service.get_standalone_service_settings()


def _json_object_hook(dct):
if '$json' in dct:
return json.dumps(dct['$json'])
Expand Down Expand Up @@ -234,6 +262,25 @@ def _redis_cluster_store(
yield redis_db


@pytest.fixture
def redis_standalone_store(
pytestconfig, redis_standalone_service, redis_standalone_node
):
if pytestconfig.option.no_redis:
yield
return

redis_db = redisdb.StrictRedis(
host=redis_standalone_node['host'],
port=redis_standalone_node['port'],
)

try:
yield redis_db
finally:
redis_db.flushall()


@pytest.fixture
def _redis_execute_commands_from_file(request, load_json):
def _execute_commands(markers, redis_db):
Expand Down
30 changes: 30 additions & 0 deletions testsuite/databases/redis/scripts/service-standalone-redis
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh

. "$TESTSUITE_LIB_UTILS"

if [ "x$REDIS_CONFIGS_DIR" = "x" ]; then
die "REDIS_CONFIGS_DIR must be set"
fi

REDIS_SERVER=$(which redis-server)
REDIS_ROLE="standalone"
if [ "x$REDIS_SERVER" = "x" ]; then
die "No redis-server binary found"
fi

start() {

echo "Starting redis ($REDIS_ROLE) with config '$REDIS_CONFIGS_DIR/redis_master0.conf'..."
pidfile="$(get_pidfile $REDIS_ROLE)"
$REDIS_SERVER $REDIS_CONFIGS_DIR/redis_master0.conf --pidfile $pidfile || {
die "Failed to start redis ($REDIS_ROLE) server"
}
}

stop() {
pidfile="$(get_pidfile $REDIS_ROLE)"
stop_daemon $REDIS_SERVER $pidfile
}

script_main "$@"

51 changes: 51 additions & 0 deletions testsuite/databases/redis/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
DEFAULT_SLAVE_PORTS = (16380, 16390, 16381)
DEFAULT_CLUSTER_PORTS = (17380, 17381, 17382, 17383, 17384, 17385)
DEFAULT_CLUSTER_REPLICAS = 1
DEFAULT_STANDALONE_PORT = 7000

SERVICE_SCRIPT_PATH = pathlib.Path(__file__).parent.joinpath(
'scripts/service-redis',
)
CLUSTER_SERVICE_SCRIPT_PATH = pathlib.Path(__file__).parent.joinpath(
'scripts/service-cluster-redis',
)
STANDALONE_SERVICE_SCRIPT_PATH = pathlib.Path(__file__).parent.joinpath(
'scripts/service-standalone-redis',
)


class BaseError(Exception):
Expand Down Expand Up @@ -65,6 +69,11 @@ def validate(self):
)


class StandaloneServiceSettings(typing.NamedTuple):
host: str
port: int


def get_service_settings():
return ServiceSettings(
host=_get_hostname(),
Expand Down Expand Up @@ -97,6 +106,16 @@ def get_cluster_service_settings():
)


def get_standalone_service_settings():
return StandaloneServiceSettings(
host=_get_hostname(),
port=utils.getenv_int(
key='TESTSUITE_REDIS_STANDALONE_PORT',
default=DEFAULT_STANDALONE_PORT,
),
)


def create_redis_service(
service_name,
working_dir,
Expand Down Expand Up @@ -183,6 +202,38 @@ def prestart_hook():
)


def create_standalone_redis_service(
service_name,
working_dir,
settings: typing.Optional[StandaloneServiceSettings] = None,
env=None,
):
if settings is None:
settings = get_standalone_service_settings()
configs_dir = pathlib.Path(working_dir).joinpath('configs')

def prestart_hook():
configs_dir.mkdir(parents=True, exist_ok=True)
genredis.generate_standalone_redis_config(
output_path=configs_dir,
host=settings.host,
port=settings.port,
)

return service.ScriptService(
service_name=service_name,
script_path=str(STANDALONE_SERVICE_SCRIPT_PATH),
working_dir=working_dir,
environment={
'REDIS_CONFIGS_DIR': str(configs_dir),
**(env or {}),
},
check_host=settings.host,
check_ports=[settings.port],
prestart_hook=prestart_hook,
)


def _get_hostname():
hostname = 'localhost'
for var in ('TESTSUITE_REDIS_HOSTNAME', 'HOSTNAME'):
Expand Down
Loading