Skip to content
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
65 changes: 40 additions & 25 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,35 @@ jobs:
run: ./vendor/bin/phpunit --testsuite=Unit --testdox

e2e-tests:
name: E2E Tests
name: E2E Tests (5-node FDB cluster)
needs: [check-actor, lint]
if: needs.check-actor.outputs.allowed == 'true'
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Start FoundationDB container
- name: Setup Docker Compose
run: |
docker run -d --name fdb \
-e FDB_NETWORKING_MODE=host \
-p 4500:4500 \
--entrypoint /usr/bin/tini \
foundationdb/foundationdb:7.3.75 \
-g -- /var/fdb/scripts/fdb_single.bash
sudo apt-get update
sudo apt-get install -y docker-compose

- name: Start 5-node FDB cluster
run: |
docker-compose up -d fdb-coord-1 fdb-coord-2 fdb-coord-3 fdb-server-1 fdb-server-2 fdb-config

# Wait for cluster to be ready
echo "Waiting for FDB cluster..."
for i in $(seq 1 60); do
if docker-compose exec -T fdb-config fdbcli --exec "status minimal" 2>/dev/null | grep -q "available"; then
echo "FDB cluster is ready after ${i}s"
break
fi
sleep 2
done

# Show cluster status
docker-compose exec -T fdb-config fdbcli --exec "status"

- name: Setup PHP
uses: shivammathur/setup-php@v2
Expand All @@ -135,29 +148,31 @@ jobs:
run: composer install --no-interaction --prefer-dist

- name: Create cluster file
run: echo "docker:docker@127.0.0.1:4500" > fdb.cluster

- name: Wait for FDB to be ready
run: |
for i in $(seq 1 60); do
STATUS=$(fdbcli -C fdb.cluster --exec "status minimal" 2>&1 || true)
echo " [$i] $STATUS"
if echo "$STATUS" | grep -q "available"; then
echo "FDB is ready after ${i}s"
exit 0
fi
sleep 1
done
echo "FDB failed to become ready"
docker logs fdb || true
fdbcli -C fdb.cluster --exec "status" || true
exit 1
echo "docker:docker@127.0.0.1:4500,127.0.0.1:4501,127.0.0.1:4502" > fdb.cluster

# Get IP of fdb-server-1 for reboot tests
# Container name may vary, so we use docker-compose to find it
SERVER1_CONTAINER=$(docker-compose ps -q fdb-server-1)
if [ -n "$SERVER1_CONTAINER" ]; then
SERVER1_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $SERVER1_CONTAINER)
echo "FDB_REBOOT_TEST_IP=${SERVER1_IP}:4510" >> $GITHUB_ENV
echo "fdb-server-1 IP: ${SERVER1_IP}:4510"
else
echo "Warning: Could not find fdb-server-1 container"
echo "FDB_REBOOT_TEST_IP=172.18.0.5:4510" >> $GITHUB_ENV
fi

- name: Configure tenant mode
run: |
fdbcli -C fdb.cluster --exec "configure tenant_mode=optional_experimental"
docker-compose exec -T fdb-config fdbcli --exec "configure tenant_mode=optional_experimental"

- name: Run E2E tests
env:
FDB_CLUSTER_FILE: fdb.cluster
FDB_REBOOT_TEST_IP: ${{ env.FDB_REBOOT_TEST_IP }}
run: ./vendor/bin/phpunit --testsuite=Integration --testdox

- name: Cleanup
if: always()
run: docker-compose down -v
153 changes: 139 additions & 14 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,159 @@
services:
fdb:
fdb-coord-1:
image: foundationdb/foundationdb:7.3.75
entrypoint: ["/usr/bin/tini", "-g", "--", "/var/fdb/scripts/fdb_single.bash"]
hostname: fdb-coord-1
environment:
FDB_NETWORKING_MODE: container
FDB_PORT: 4500
FDB_CLUSTER_FILE_CONTENTS: |
docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502
ports:
- "4500:4500"
volumes:
- fdb-data:/var/fdb/data
healthcheck:
test: ["CMD", "fdbcli", "--exec", "status minimal"]
interval: 5s
timeout: 10s
retries: 30
start_period: 30s
- fdb-coord-1-data:/var/fdb/data
entrypoint:
- /bin/sh
- -c
- |
rm -rf /var/fdb/logs/* 2>/dev/null || true
exec /usr/bin/tini -g -- /var/fdb/scripts/fdb.bash

fdb-coord-2:
image: foundationdb/foundationdb:7.3.75
hostname: fdb-coord-2
environment:
FDB_PORT: 4501
FDB_CLUSTER_FILE_CONTENTS: |
docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502
ports:
- "4501:4501"
volumes:
- fdb-coord-2-data:/var/fdb/data
entrypoint:
- /bin/sh
- -c
- |
rm -rf /var/fdb/logs/* 2>/dev/null || true
exec /usr/bin/tini -g -- /var/fdb/scripts/fdb.bash

fdb-coord-3:
image: foundationdb/foundationdb:7.3.75
hostname: fdb-coord-3
environment:
FDB_PORT: 4502
FDB_CLUSTER_FILE_CONTENTS: |
docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502
ports:
- "4502:4502"
volumes:
- fdb-coord-3-data:/var/fdb/data
entrypoint:
- /bin/sh
- -c
- |
rm -rf /var/fdb/logs/* 2>/dev/null || true
exec /usr/bin/tini -g -- /var/fdb/scripts/fdb.bash

fdb-server-1:
image: foundationdb/foundationdb:7.3.75
hostname: fdb-server-1
depends_on:
- fdb-coord-1
- fdb-coord-2
- fdb-coord-3
environment:
FDB_PORT: 4510
FDB_CLUSTER_FILE_CONTENTS: |
docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502
ports:
- "4510:4510"
volumes:
- fdb-server-1-data:/var/fdb/data
entrypoint:
- /bin/sh
- -c
- |
rm -rf /var/fdb/logs/* 2>/dev/null || true
exec /usr/bin/tini -g -- /var/fdb/scripts/fdb.bash

fdb-server-2:
image: foundationdb/foundationdb:7.3.75
hostname: fdb-server-2
depends_on:
- fdb-coord-1
- fdb-coord-2
- fdb-coord-3
environment:
FDB_PORT: 4511
FDB_CLUSTER_FILE_CONTENTS: |
docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502
ports:
- "4511:4511"
volumes:
- fdb-server-2-data:/var/fdb/data
entrypoint:
- /bin/sh
- -c
- |
rm -rf /var/fdb/logs/* 2>/dev/null || true
exec /usr/bin/tini -g -- /var/fdb/scripts/fdb.bash

fdb-config:
image: foundationdb/foundationdb:7.3.75
depends_on:
- fdb-coord-1
- fdb-coord-2
- fdb-coord-3
- fdb-server-1
- fdb-server-2
environment:
FDB_PORT: 4500
FDB_CLUSTER_FILE_CONTENTS: |
docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502
entrypoint:
- /bin/sh
- -c
- |
set -e
rm -rf /var/fdb/logs/* 2>/dev/null || true
mkdir -p /var/fdb
echo "docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502" > /var/fdb/fdb.cluster

echo "Waiting for FDB cluster..."
for i in $$(seq 1 60); do
if fdbcli --exec "status" 2>/dev/null | grep -q "processes"; then
echo "Cluster is ready!"
break
fi
sleep 2
done

echo "Configuring FDB cluster..."
fdbcli --exec "configure new double ssd" || echo "Already configured"
fdbcli --exec "status"
exec tail -f /dev/null

php:
build:
context: ./docker/php
depends_on:
fdb:
condition: service_healthy
- fdb-config
volumes:
- .:/app
environment:
FDB_CLUSTER_FILE: /app/fdb.cluster
working_dir: /app
stdin_open: true
tty: true
entrypoint: ["/bin/bash", "-c", "echo \"docker:docker@$(getent hosts fdb | awk '{print $1}'):4500\" > /app/fdb.cluster && exec tail -f /dev/null"]
entrypoint:
- /bin/bash
- -c
- |
echo "docker:docker@fdb-coord-1:4500,fdb-coord-2:4501,fdb-coord-3:4502" > /app/fdb.cluster
exec tail -f /dev/null

volumes:
fdb-data:
fdb-coord-1-data:
fdb-coord-2-data:
fdb-coord-3-data:
fdb-server-1-data:
fdb-server-2-data:
48 changes: 48 additions & 0 deletions src/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,20 @@ public function clearRangeStartsWith(string $prefix): void
});
}

/**
* Clear all data from the database.
*
* WARNING: This is a destructive operation that removes ALL keys from the database.
* Use with caution, primarily intended for testing and administrative operations.
*/
public function clearAll(): void
{
$this->transact(function (Transaction $tr): void {
// Clear entire keyspace from \x00 to \xFF
$tr->clearRange("\x00", "\xFF");
});
}

/**
* @return list<KeyValue>
*/
Expand Down Expand Up @@ -338,6 +352,40 @@ public function getClientStatus(): string
return $future->await();
}

/**
* Reboots a FoundationDB worker process.
*
* @param string $address The network address of the worker to reboot (e.g., "127.0.0.1:4500")
* @param bool $checkFile If true, checks that a file exists at the specified path before rebooting
* @param int $suspendDuration Duration in seconds to suspend the process (0 for immediate restart)
*
* @throws RebootWorkerException If the reboot operation fails
*
* @warning Do not close the Database immediately after calling this method, as the operation
* may still be in progress. Allow sufficient time for the operation to complete.
*/
public function rebootWorker(string $address, bool $checkFile = false, int $suspendDuration = 0): void
{
$this->ensureOpen();

$future = new Future\FutureInt64(
$this->client->fdb->fdb_database_reboot_worker(
$this->dpointer,
$address,
strlen($address),
$checkFile ? 1 : 0,
$suspendDuration,
),
$this->client,
);

$result = $future->await();

if ($result === 0) {
throw new RebootWorkerException($address);
}
}

public function options(): DatabaseOptions
{
return new DatabaseOptions($this);
Expand Down
3 changes: 3 additions & 0 deletions src/NativeClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ final class NativeClient
FDBFuture* fdb_database_get_client_status(FDBDatabase* d);
fdb_error_t fdb_database_set_option(FDBDatabase* d, int option, const void* value, int value_length);
fdb_error_t fdb_database_create_transaction(FDBDatabase* d, FDBTransaction** out_transaction);
FDBFuture* fdb_database_reboot_worker(
FDBDatabase* d, const char* address, int address_length, fdb_bool_t check, int duration
);
fdb_error_t fdb_database_open_tenant(
FDBDatabase* d, const char* tenant_name, int tenant_name_length, FDBTenant** out_tenant
);
Expand Down
15 changes: 15 additions & 0 deletions src/RebootWorkerException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace CrazyGoat\FoundationDB;

final class RebootWorkerException extends \RuntimeException
{
public function __construct(
public readonly string $address,
string $message = 'Failed to reboot worker',
) {
parent::__construct($message);
}
}
Loading
Loading