Skip to content
Open
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
141 changes: 141 additions & 0 deletions .github/workflows/phpunit-kvstore.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT

name: PHPUnit key-value store

on:
pull_request:

permissions:
contents: read

concurrency:
group: phpunit-kvstore-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
VALKEY_IMAGE: valkey/valkey:8

jobs:
changes:
runs-on: ubuntu-latest-low

outputs:
src: ${{ steps.changes.outputs.src}}

steps:
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: changes
continue-on-error: true
with:
filters: |
src:
- '.github/workflows/phpunit-kvstore.yml'
- '3rdparty/**'
- '**/appinfo/**'
- '**.php'

phpunit-kvstore:
runs-on: ubuntu-latest

needs: changes
if: needs.changes.outputs.src != 'false'

strategy:
fail-fast: false
matrix:
php-versions: ["8.3", "8.5"]
# The three supported topologies for the key-value store cache
topology: ["single", "cluster", "sentinel"]

name: KV store ${{ matrix.topology }} (PHP ${{ matrix.php-versions }})

steps:
- name: Checkout server
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
submodules: true

- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 #v2.37.2
timeout-minutes: 5
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, pdo_sqlite, posix, session, simplexml, sqlite, xmlreader, xmlwriter, zip, zlib
coverage: none
ini-file: development
ini-values: disable_functions=""
env:
fail-fast: true
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Start Valkey (single server)
if: matrix.topology == 'single'
run: |
docker run -d --name kv-single -p 6379:6379 "${VALKEY_IMAGE}"
timeout 60 sh -c 'until docker exec kv-single valkey-cli ping | grep -q PONG; do sleep 1; done'

- name: Start Valkey (cluster)
if: matrix.topology == 'cluster'
run: |
for port in 7000 7001 7002 7003 7004 7005; do
docker run -d --name "kv-cluster-$port" --network host "${VALKEY_IMAGE}" \
valkey-server --port "$port" --cluster-enabled yes \
--cluster-config-file "nodes-$port.conf" --cluster-node-timeout 5000 \
--appendonly no --save ""
done
# Wait for every node to answer before forming the cluster
for port in 7000 7001 7002 7003 7004 7005; do
timeout 60 sh -c "until docker exec kv-cluster-$port valkey-cli -p $port ping | grep -q PONG; do sleep 1; done"
done
docker exec kv-cluster-7000 valkey-cli --cluster create \
127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 \
127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1 --cluster-yes

- name: Start Valkey (sentinel)
if: matrix.topology == 'sentinel'
run: |
docker run -d --name kv-master --network host "${VALKEY_IMAGE}" \
valkey-server --port 6379
docker run -d --name kv-replica --network host "${VALKEY_IMAGE}" \
valkey-server --port 6380 --replicaof 127.0.0.1 6379
printf 'port 26379\nsentinel monitor mymaster 127.0.0.1 6379 1\nsentinel down-after-milliseconds mymaster 5000\nsentinel failover-timeout mymaster 10000\n' > sentinel.conf
docker run -d --name kv-sentinel --network host -v "$PWD/sentinel.conf:/etc/valkey/sentinel.conf" \
"${VALKEY_IMAGE}" valkey-sentinel /etc/valkey/sentinel.conf
timeout 60 sh -c 'until docker exec kv-sentinel valkey-cli -p 26379 ping | grep -q PONG; do sleep 1; done'

- name: Set up dependencies
run: composer i

- name: Set up Nextcloud
run: |
mkdir data
cp tests/kvstore-${{ matrix.topology }}.config.php config/
cp tests/preseed-config.php config/config.php
./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
php -f tests/enable_all.php

- name: PHPUnit key-value store tests
run: composer run test -- --group KeyValueCache --log-junit junit.xml

- name: Print logs
if: always()
run: |
cat data/nextcloud.log

summary:
permissions:
contents: none
runs-on: ubuntu-latest-low
needs: [changes, phpunit-kvstore]

if: always()

name: phpunit-kvstore-summary

steps:
- name: Summary status
run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-kvstore.result != 'success' }}; then exit 1; fi
2 changes: 1 addition & 1 deletion 3rdparty
Submodule 3rdparty updated 631 files
1 change: 1 addition & 0 deletions apps/settings/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
'OCA\\Settings\\SetupChecks\\LoggingLevel' => $baseDir . '/../lib/SetupChecks/LoggingLevel.php',
'OCA\\Settings\\SetupChecks\\MaintenanceWindowStart' => $baseDir . '/../lib/SetupChecks/MaintenanceWindowStart.php',
'OCA\\Settings\\SetupChecks\\MemcacheConfigured' => $baseDir . '/../lib/SetupChecks/MemcacheConfigured.php',
'OCA\\Settings\\SetupChecks\\MemcacheLegacy' => $baseDir . '/../lib/SetupChecks/MemcacheLegacy.php',
'OCA\\Settings\\SetupChecks\\MimeTypeMigrationAvailable' => $baseDir . '/../lib/SetupChecks/MimeTypeMigrationAvailable.php',
'OCA\\Settings\\SetupChecks\\MysqlRowFormat' => $baseDir . '/../lib/SetupChecks/MysqlRowFormat.php',
'OCA\\Settings\\SetupChecks\\MysqlUnicodeSupport' => $baseDir . '/../lib/SetupChecks/MysqlUnicodeSupport.php',
Expand Down
1 change: 1 addition & 0 deletions apps/settings/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ class ComposerStaticInitSettings
'OCA\\Settings\\SetupChecks\\LoggingLevel' => __DIR__ . '/..' . '/../lib/SetupChecks/LoggingLevel.php',
'OCA\\Settings\\SetupChecks\\MaintenanceWindowStart' => __DIR__ . '/..' . '/../lib/SetupChecks/MaintenanceWindowStart.php',
'OCA\\Settings\\SetupChecks\\MemcacheConfigured' => __DIR__ . '/..' . '/../lib/SetupChecks/MemcacheConfigured.php',
'OCA\\Settings\\SetupChecks\\MemcacheLegacy' => __DIR__ . '/..' . '/../lib/SetupChecks/MemcacheLegacy.php',
'OCA\\Settings\\SetupChecks\\MimeTypeMigrationAvailable' => __DIR__ . '/..' . '/../lib/SetupChecks/MimeTypeMigrationAvailable.php',
'OCA\\Settings\\SetupChecks\\MysqlRowFormat' => __DIR__ . '/..' . '/../lib/SetupChecks/MysqlRowFormat.php',
'OCA\\Settings\\SetupChecks\\MysqlUnicodeSupport' => __DIR__ . '/..' . '/../lib/SetupChecks/MysqlUnicodeSupport.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/settings/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use OCA\Settings\SetupChecks\LegacySSEKeyFormat;
use OCA\Settings\SetupChecks\MaintenanceWindowStart;
use OCA\Settings\SetupChecks\MemcacheConfigured;
use OCA\Settings\SetupChecks\MemcacheLegacy;
use OCA\Settings\SetupChecks\MimeTypeMigrationAvailable;
use OCA\Settings\SetupChecks\MysqlRowFormat;
use OCA\Settings\SetupChecks\MysqlUnicodeSupport;
Expand Down Expand Up @@ -193,6 +194,7 @@ public function register(IRegistrationContext $context): void {
$context->registerSetupCheck(LegacySSEKeyFormat::class);
$context->registerSetupCheck(MaintenanceWindowStart::class);
$context->registerSetupCheck(MemcacheConfigured::class);
$context->registerSetupCheck(MemcacheLegacy::class);
$context->registerSetupCheck(MimeTypeMigrationAvailable::class);
$context->registerSetupCheck(MysqlRowFormat::class);
$context->registerSetupCheck(MysqlUnicodeSupport::class);
Expand Down
59 changes: 59 additions & 0 deletions apps/settings/lib/SetupChecks/MemcacheLegacy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Settings\SetupChecks;

use OC\Memcache\Redis;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\SetupCheck\ISetupCheck;
use OCP\SetupCheck\SetupResult;

class MemcacheLegacy implements ISetupCheck {
public function __construct(
private readonly IL10N $l10n,
private readonly IConfig $config,
private readonly IURLGenerator $urlGenerator,
) {
}

#[\Override]
public function getName(): string {
return $this->l10n->t('Redis cache');
}

#[\Override]
public function getCategory(): string {
return 'system';
}

#[\Override]
public function run(): SetupResult {
if ($this->isLegacyRedisUsed()) {
return SetupResult::info(
$this->l10n->t('You are still using the old Redis cache backend. For full support of latest Valkey and Redis features, like clustering and sentinel, please switch to the new KeyValueCache backend.'),
$this->urlGenerator->linkToDocs('admin-cache')
);
} else {
return SetupResult::success($this->l10n->t('No legacy Redis cache detected'));
}
}

protected function isLegacyRedisUsed(): bool {
$memcacheDistributedClass = $this->config->getSystemValue('memcache.distributed', null);
$memcacheLockingClass = $this->config->getSystemValue('memcache.locking', null);

/** @psalm-suppress DeprecatedClass */
if ($memcacheDistributedClass === Redis::class || $memcacheLockingClass === Redis::class) {
return true;
}
return false;
}
}
10 changes: 10 additions & 0 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3060,6 +3060,11 @@
<code><![CDATA[$this->timeFactory->getTime()]]></code>
</InvalidScalarArgument>
</file>
<file src="core/Command/Memcache/RedisCommand.php">
<DeprecatedClass>
<code><![CDATA[protected]]></code>
</DeprecatedClass>
</file>
<file src="core/Command/Security/BruteforceAttempts.php">
<DeprecatedMethod>
<code><![CDATA[getAttempts]]></code>
Expand Down Expand Up @@ -4285,6 +4290,11 @@
<code><![CDATA[array{X-Request-Id: string, Cache-Control: string, Content-Security-Policy: string, Feature-Policy: string, X-Robots-Tag: string, Last-Modified?: string, ETag?: string, ...H}]]></code>
</MoreSpecificReturnType>
</file>
<file src="lib/public/ICache.php">
<AmbiguousConstantInheritance>
<code><![CDATA[DEFAULT_TTL]]></code>
</AmbiguousConstantInheritance>
</file>
<file src="public.php">
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
Expand Down
85 changes: 85 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,87 @@
*/
'memcache_customprefix' => 'mycustomprefix',

/**
* Connection details for the Key-Value store used for in-memory caching,
* for example when using Valkey or Redis.
*
* This is the brand-independent successor of the ``redis`` and
* ``redis.cluster`` options below and supports the latest Valkey and Redis
* releases. Three topologies are supported:
* a single server, a Sentinel managed replication set, and a
* server cluster. Configure exactly one of ``server``, ``sentinel`` or
* ``seeds``.
*
* For enhanced security, it is recommended to configure ACLs in
* the cache server and configure the ``user`` and ``password`` (or a TLS
* client certificate). Alternatively, you can also configure the cache
* server to just use a ``password``.
* See https://valkey.io/topics/security/ for more information when using Valkey.
*/
'memcache.kvstore' => [
/**
* Single server setup.
*
* Also used as the connection template for the ``sentinel`` managed
* primary / replica connections.
*/
'server' => [
'host' => 'localhost', // can also be a Unix domain socket: '/tmp/cache.sock'
'port' => 6379, // ignored for Unix domain sockets
// Protocol used to connect. One of 'tcp', 'tls' or 'unix'.
// When omitted it is derived from the host (a leading '/' means 'unix').
'protocol' => 'tcp',
],

/**
* Sentinel managed replication setup.
*
* Provide the name of the monitored service and one entry per Sentinel
* node. Each seed uses the same format as the ``server`` entry above.
* Uncomment to enable and remove the ``server`` / ``seeds`` entries.
*/
//'sentinel' => [
// 'service' => 'mymaster',
// 'seeds' => [
// ['host' => 'localhost', 'port' => 26379],
// ['host' => 'localhost', 'port' => 26380],
// ],
//],

/**
* Cluster setup.
*
* Provide some or all of the cluster nodes to bootstrap discovery.
* Each seed uses the same format as the ``server`` entry above.
* Uncomment to enable and remove the ``server`` / ``sentinel`` entries.
*/
//'seeds' => [
// ['host' => 'localhost', 'port' => 7000],
// ['host' => 'localhost', 'port' => 7001],
//],

// Optional: username, only sent when the cache server uses ACLs.
'user' => '',
// Optional: if not defined, no password will be used.
'password' => '',
// Optional: select a numbered database. Only supported for single
// servers and Sentinel setups, clusters always use database 0.
'dbindex' => 0,
// Optional: connection timeout in seconds (float). 0 means no timeout.
'timeout' => 0.0,
// Optional: read/write timeout in seconds (float). 0 means no timeout.
'read_timeout' => 0.0,
// Optional: keep the connection open across requests. Defaults to false.
'persistent' => false,
// Optional: when the 'tls' protocol is used, provide the SSL context.
// SSL context options, see https://www.php.net/manual/en/context.ssl.php
//'ssl_context' => [
// 'local_cert' => '/certs/cache.crt',
// 'local_pk' => '/certs/cache.key',
// 'cafile' => '/certs/ca.crt',
//],
],

/**
* Connection details for Redis to use for memory caching in a single server configuration.
*
Expand All @@ -1800,6 +1881,8 @@
*
* We also support Redis SSL/TLS encryption as of version 6.
* See https://redis.io/topics/encryption for more information.
*
* @deprecated 34.0.0 use `memcache.kvstore` instead which supports also Valkey.
*/
'redis' => [
'host' => 'localhost', // can also be a Unix domain socket: '/tmp/redis.sock'
Expand Down Expand Up @@ -1839,6 +1922,8 @@
*
* Authentication works with phpredis version 4.2.1+. See
* https://github.com/phpredis/phpredis/commit/c5994f2a42b8a348af92d3acb4edff1328ad8ce1
*
* @deprecated 34.0.0 use `memcache.kvstore` instead which supports also Valkey.
*/
'redis.cluster' => [
'seeds' => [ // provide some or all of the cluster servers to bootstrap discovery, port required
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,8 @@
'OC\\Memcache\\CASTrait' => $baseDir . '/lib/private/Memcache/CASTrait.php',
'OC\\Memcache\\Cache' => $baseDir . '/lib/private/Memcache/Cache.php',
'OC\\Memcache\\Factory' => $baseDir . '/lib/private/Memcache/Factory.php',
'OC\\Memcache\\KeyValueCache' => $baseDir . '/lib/private/Memcache/KeyValueCache.php',
'OC\\Memcache\\KeyValueCacheFactory' => $baseDir . '/lib/private/Memcache/KeyValueCacheFactory.php',
'OC\\Memcache\\LoggerWrapperCache' => $baseDir . '/lib/private/Memcache/LoggerWrapperCache.php',
'OC\\Memcache\\Memcached' => $baseDir . '/lib/private/Memcache/Memcached.php',
'OC\\Memcache\\MemcachedFactory' => $baseDir . '/lib/private/Memcache/MemcachedFactory.php',
Expand Down
2 changes: 2 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Memcache\\CASTrait' => __DIR__ . '/../../..' . '/lib/private/Memcache/CASTrait.php',
'OC\\Memcache\\Cache' => __DIR__ . '/../../..' . '/lib/private/Memcache/Cache.php',
'OC\\Memcache\\Factory' => __DIR__ . '/../../..' . '/lib/private/Memcache/Factory.php',
'OC\\Memcache\\KeyValueCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/KeyValueCache.php',
'OC\\Memcache\\KeyValueCacheFactory' => __DIR__ . '/../../..' . '/lib/private/Memcache/KeyValueCacheFactory.php',
'OC\\Memcache\\LoggerWrapperCache' => __DIR__ . '/../../..' . '/lib/private/Memcache/LoggerWrapperCache.php',
'OC\\Memcache\\Memcached' => __DIR__ . '/../../..' . '/lib/private/Memcache/Memcached.php',
'OC\\Memcache\\MemcachedFactory' => __DIR__ . '/../../..' . '/lib/private/Memcache/MemcachedFactory.php',
Expand Down
Loading
Loading