Add library build, Docker cluster, and example client
Introduce a Makefile that builds libtoastyfs.a and libtoastyfs.so from the client-side sources (client, tcp, byte_queue, message, basic) so external programs can link against the ToastyFS client API in include/toastyfs.h. Add a Dockerfile and docker-compose.yml that spin up a 3-node ToastyFS server cluster on a private bridge network with ports 8081-8083 exposed on the host. Include cluster.sh as a convenience wrapper (up/down/status/ logs/run) — the "run" sub-command builds the library, compiles a user- supplied C source against it, starts the cluster, and executes the binary. Add examples/example.c demonstrating synchronous PUT, GET, DELETE, and a NOT_FOUND verification. https://claude.ai/code/session_019DVhmc25jfzcHnmdNxzib2
This commit is contained in:
@@ -7,3 +7,7 @@ toastyfs_simulation
|
||||
*.info
|
||||
coverage_html/
|
||||
*.log
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
examples/example
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
FROM gcc:latest AS build
|
||||
WORKDIR /build
|
||||
COPY src/ src/
|
||||
COPY include/ include/
|
||||
COPY quakey/ quakey/
|
||||
COPY Makefile .
|
||||
RUN make toastyfs
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
RUN mkdir -p /data
|
||||
COPY --from=build /build/toastyfs /usr/local/bin/toastyfs
|
||||
ENTRYPOINT ["toastyfs"]
|
||||
@@ -0,0 +1,81 @@
|
||||
CC ?= gcc
|
||||
CFLAGS ?= -Wall -Wextra -ggdb -O0
|
||||
AR ?= ar
|
||||
|
||||
INCLUDES = -Iquakey/include -Iinclude -I.
|
||||
|
||||
# ---- Library (client-side only) ----
|
||||
|
||||
LIB_SRCS = src/client.c src/tcp.c src/byte_queue.c src/message.c src/basic.c
|
||||
LIB_OBJS = $(LIB_SRCS:.c=.o)
|
||||
|
||||
STATIC_LIB = libtoastyfs.a
|
||||
SHARED_LIB = libtoastyfs.so
|
||||
|
||||
# ---- Server binary ----
|
||||
|
||||
SERVER_SRCS = src/basic.c src/file_system.c src/byte_queue.c src/message.c \
|
||||
src/tcp.c src/server.c src/main.c src/log.c src/client_table.c \
|
||||
src/chunk_store.c src/metadata.c
|
||||
|
||||
# ---- Client binary (random test client) ----
|
||||
|
||||
CLIENT_SRCS = src/basic.c src/file_system.c src/byte_queue.c src/message.c \
|
||||
src/tcp.c src/server.c src/client.c src/random_client.c src/main.c \
|
||||
src/log.c src/client_table.c src/chunk_store.c src/metadata.c
|
||||
|
||||
# ---- Simulation binary ----
|
||||
|
||||
SIM_SRCS = src/basic.c src/file_system.c src/byte_queue.c src/message.c \
|
||||
src/tcp.c src/server.c src/client.c src/random_client.c src/main.c \
|
||||
src/log.c src/client_table.c src/invariant_checker.c src/chunk_store.c \
|
||||
src/metadata.c quakey/src/mockfs.c quakey/src/quakey.c
|
||||
|
||||
# ---- Default target ----
|
||||
|
||||
all: $(STATIC_LIB) $(SHARED_LIB) toastyfs toastyfs_client toastyfs_simulation
|
||||
|
||||
# ---- Library targets ----
|
||||
|
||||
lib: $(STATIC_LIB) $(SHARED_LIB)
|
||||
|
||||
$(STATIC_LIB): $(LIB_OBJS)
|
||||
$(AR) rcs $@ $^
|
||||
|
||||
$(SHARED_LIB): $(LIB_SRCS)
|
||||
$(CC) $(CFLAGS) -shared -fPIC $(INCLUDES) $^ -o $@
|
||||
|
||||
src/%.o: src/%.c
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -fPIC -c $< -o $@
|
||||
|
||||
# ---- Binary targets ----
|
||||
|
||||
toastyfs: $(SERVER_SRCS)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -DMAIN_SERVER $^ -o $@
|
||||
|
||||
toastyfs_client: $(CLIENT_SRCS)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -DMAIN_CLIENT $^ -o $@
|
||||
|
||||
toastyfs_simulation: $(SIM_SRCS)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -DMAIN_SIMULATION -DFAULT_INJECTION $^ -o $@
|
||||
|
||||
# ---- Install ----
|
||||
|
||||
PREFIX ?= /usr/local
|
||||
LIBDIR ?= $(PREFIX)/lib
|
||||
INCLUDEDIR?= $(PREFIX)/include
|
||||
|
||||
install: $(STATIC_LIB) $(SHARED_LIB)
|
||||
install -d $(DESTDIR)$(LIBDIR)
|
||||
install -d $(DESTDIR)$(INCLUDEDIR)
|
||||
install -m 644 $(STATIC_LIB) $(DESTDIR)$(LIBDIR)/
|
||||
install -m 755 $(SHARED_LIB) $(DESTDIR)$(LIBDIR)/
|
||||
install -m 644 include/toastyfs.h $(DESTDIR)$(INCLUDEDIR)/
|
||||
|
||||
# ---- Clean ----
|
||||
|
||||
clean:
|
||||
rm -f $(LIB_OBJS) $(STATIC_LIB) $(SHARED_LIB)
|
||||
rm -f toastyfs toastyfs_client toastyfs_simulation
|
||||
|
||||
.PHONY: all lib clean install
|
||||
Executable
+98
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# cluster.sh - Spin up a ToastyFS cluster and optionally build & run
|
||||
# an application that links against libtoastyfs.
|
||||
#
|
||||
# Usage:
|
||||
# ./cluster.sh up Start the 3-node cluster
|
||||
# ./cluster.sh down Stop the cluster
|
||||
# ./cluster.sh status Show cluster status
|
||||
# ./cluster.sh logs [node] Tail cluster logs (or a single node)
|
||||
# ./cluster.sh run <source.c> Build the library, compile <source.c>
|
||||
# against it, start the cluster, run the
|
||||
# binary, then tear down the cluster.
|
||||
#
|
||||
# The cluster exposes ports 8081-8083 on localhost, mapped to the three
|
||||
# server nodes. Client applications should connect to:
|
||||
# 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8083
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
usage() {
|
||||
sed -n '3,14s/^# \?//p' "$0"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cluster_up() {
|
||||
echo "==> Building server image and starting cluster..."
|
||||
docker compose up --build -d
|
||||
echo "==> Cluster is running."
|
||||
echo " Nodes: 127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8083"
|
||||
}
|
||||
|
||||
cluster_down() {
|
||||
echo "==> Stopping cluster..."
|
||||
docker compose down
|
||||
}
|
||||
|
||||
cluster_status() {
|
||||
docker compose ps
|
||||
}
|
||||
|
||||
cluster_logs() {
|
||||
if [ $# -gt 0 ]; then
|
||||
docker compose logs -f "$1"
|
||||
else
|
||||
docker compose logs -f
|
||||
fi
|
||||
}
|
||||
|
||||
cluster_run() {
|
||||
local src="$1"
|
||||
local bin
|
||||
bin="$(basename "${src%.c}")"
|
||||
|
||||
echo "==> Building libtoastyfs..."
|
||||
make -C "$SCRIPT_DIR" lib
|
||||
|
||||
echo "==> Compiling $src..."
|
||||
${CC:-gcc} -Wall -Wextra -o "$bin" "$src" \
|
||||
-I"$SCRIPT_DIR/include" \
|
||||
-L"$SCRIPT_DIR" \
|
||||
-ltoastyfs
|
||||
|
||||
echo "==> Starting cluster..."
|
||||
cluster_up
|
||||
# Give the servers a moment to elect a leader.
|
||||
sleep 2
|
||||
|
||||
echo "==> Running ./$bin"
|
||||
echo "---"
|
||||
LD_LIBRARY_PATH="$SCRIPT_DIR:${LD_LIBRARY_PATH:-}" "./$bin" || true
|
||||
echo "---"
|
||||
|
||||
echo "==> Stopping cluster..."
|
||||
cluster_down
|
||||
}
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
usage
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
up) cluster_up ;;
|
||||
down) cluster_down ;;
|
||||
status) cluster_status ;;
|
||||
logs) shift; cluster_logs "$@" ;;
|
||||
run)
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Error: 'run' requires a source file argument." >&2
|
||||
usage
|
||||
fi
|
||||
cluster_run "$2"
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
@@ -0,0 +1,52 @@
|
||||
services:
|
||||
node1:
|
||||
build: .
|
||||
command:
|
||||
- "--addr"
|
||||
- "172.20.0.11:8080"
|
||||
- "--peer"
|
||||
- "172.20.0.12:8080"
|
||||
- "--peer"
|
||||
- "172.20.0.13:8080"
|
||||
networks:
|
||||
cluster:
|
||||
ipv4_address: 172.20.0.11
|
||||
ports:
|
||||
- "8081:8080"
|
||||
|
||||
node2:
|
||||
build: .
|
||||
command:
|
||||
- "--addr"
|
||||
- "172.20.0.12:8080"
|
||||
- "--peer"
|
||||
- "172.20.0.11:8080"
|
||||
- "--peer"
|
||||
- "172.20.0.13:8080"
|
||||
networks:
|
||||
cluster:
|
||||
ipv4_address: 172.20.0.12
|
||||
ports:
|
||||
- "8082:8080"
|
||||
|
||||
node3:
|
||||
build: .
|
||||
command:
|
||||
- "--addr"
|
||||
- "172.20.0.13:8080"
|
||||
- "--peer"
|
||||
- "172.20.0.11:8080"
|
||||
- "--peer"
|
||||
- "172.20.0.12:8080"
|
||||
networks:
|
||||
cluster:
|
||||
ipv4_address: 172.20.0.13
|
||||
ports:
|
||||
- "8083:8080"
|
||||
|
||||
networks:
|
||||
cluster:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.20.0.0/24
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* example.c - Minimal ToastyFS client example.
|
||||
*
|
||||
* Connects to a running 3-node cluster (ports 8081-8083 on localhost),
|
||||
* performs a PUT, a GET, and a DELETE using the synchronous API, then
|
||||
* verifies the results.
|
||||
*
|
||||
* Build & run against a live cluster:
|
||||
* ./cluster.sh run examples/example.c
|
||||
*
|
||||
* Or manually:
|
||||
* make lib
|
||||
* gcc -Wall -Wextra -o example examples/example.c \
|
||||
* -Iinclude -L. -ltoastyfs
|
||||
* LD_LIBRARY_PATH=. ./example
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <toastyfs.h>
|
||||
|
||||
static const char *error_name(ToastyFS_Error e)
|
||||
{
|
||||
switch (e) {
|
||||
case TOASTYFS_ERROR_VOID: return "OK";
|
||||
case TOASTYFS_ERROR_OUT_OF_MEMORY: return "OUT_OF_MEMORY";
|
||||
case TOASTYFS_ERROR_UNEXPECTED_MESSAGE: return "UNEXPECTED_MESSAGE";
|
||||
case TOASTYFS_ERROR_REJECTED: return "REJECTED";
|
||||
case TOASTYFS_ERROR_FULL: return "FULL";
|
||||
case TOASTYFS_ERROR_NOT_FOUND: return "NOT_FOUND";
|
||||
case TOASTYFS_ERROR_TRANSFER_FAILED: return "TRANSFER_FAILED";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
/* Cluster addresses (mapped via docker-compose ports). */
|
||||
char *addrs[] = {
|
||||
"127.0.0.1:8081",
|
||||
"127.0.0.1:8082",
|
||||
"127.0.0.1:8083",
|
||||
};
|
||||
|
||||
printf("Connecting to cluster...\n");
|
||||
ToastyFS *tfs = toastyfs_init(1, addrs, 3);
|
||||
if (tfs == NULL) {
|
||||
fprintf(stderr, "toastyfs_init failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ToastyFS_Result res;
|
||||
int ret;
|
||||
|
||||
/* ---- PUT ---- */
|
||||
char *key = "hello";
|
||||
char *data = "world";
|
||||
printf("PUT key=\"%s\" data=\"%s\" (%d bytes)\n",
|
||||
key, data, (int)strlen(data));
|
||||
|
||||
ret = toastyfs_put(tfs, key, strlen(key), data, strlen(data), &res);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "toastyfs_put returned %d\n", ret);
|
||||
toastyfs_free(tfs);
|
||||
return 1;
|
||||
}
|
||||
printf(" result: %s\n", error_name(res.error));
|
||||
|
||||
/* ---- GET ---- */
|
||||
printf("GET key=\"%s\"\n", key);
|
||||
ret = toastyfs_get(tfs, key, strlen(key), &res);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "toastyfs_get returned %d\n", ret);
|
||||
toastyfs_free(tfs);
|
||||
return 1;
|
||||
}
|
||||
printf(" result: %s\n", error_name(res.error));
|
||||
if (res.error == TOASTYFS_ERROR_VOID && res.data) {
|
||||
printf(" data: \"%.*s\" (%d bytes)\n", res.size, res.data, res.size);
|
||||
if (res.size == (int)strlen(data) &&
|
||||
memcmp(res.data, data, res.size) == 0) {
|
||||
printf(" PASS - data matches\n");
|
||||
} else {
|
||||
printf(" FAIL - data mismatch\n");
|
||||
}
|
||||
free(res.data);
|
||||
}
|
||||
|
||||
/* ---- DELETE ---- */
|
||||
printf("DELETE key=\"%s\"\n", key);
|
||||
ret = toastyfs_delete(tfs, key, strlen(key), &res);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "toastyfs_delete returned %d\n", ret);
|
||||
toastyfs_free(tfs);
|
||||
return 1;
|
||||
}
|
||||
printf(" result: %s\n", error_name(res.error));
|
||||
|
||||
/* ---- GET after DELETE (expect NOT_FOUND) ---- */
|
||||
printf("GET key=\"%s\" (after delete)\n", key);
|
||||
ret = toastyfs_get(tfs, key, strlen(key), &res);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "toastyfs_get returned %d\n", ret);
|
||||
toastyfs_free(tfs);
|
||||
return 1;
|
||||
}
|
||||
printf(" result: %s\n", error_name(res.error));
|
||||
if (res.error == TOASTYFS_ERROR_NOT_FOUND) {
|
||||
printf(" PASS - key not found as expected\n");
|
||||
} else {
|
||||
printf(" FAIL - expected NOT_FOUND\n");
|
||||
free(res.data);
|
||||
}
|
||||
|
||||
toastyfs_free(tfs);
|
||||
printf("Done.\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user