feat: extend server
This commit is contained in:
1 parent
5a74f25cfa
commit
3a8c4c0e77
1 file changed
+159
+159
@@ -5964,3 +5964,162 @@ test "server metrics register into an external Prometheus registry" {
|
||||
try std.testing.expect(std.mem.indexOf(u8, buf.items, "cobalt_server_ttl_swept_total 0\n") != null);
|
||||
}
|
||||
|
||||
test "client.metricsText fetches the server's own counters and db stats over the wire in one payload" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var client = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
defer client.close();
|
||||
|
||||
try client.set("a", "1");
|
||||
try client.set("b", "2");
|
||||
try client.ping();
|
||||
|
||||
const text = try client.metricsText(allocator);
|
||||
defer allocator.free(text);
|
||||
|
||||
// Server's own request counter: 2 sets + 1 ping + this very metrics
|
||||
// request itself, all counted in `requestsTotal` before dispatch even
|
||||
// looks at the opcode.
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_server_requests_total 4\n") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_server_rate_limited_total 0\n") != null);
|
||||
// Db stats rendered by the same `formatDbStats` `stats --prom` uses
|
||||
// locally, appended after the server's own counters in one response.
|
||||
try std.testing.expect(std.mem.indexOf(u8, text, "cobalt_memtable_entries 2\n") != null);
|
||||
}
|
||||
|
||||
test "server gates the metrics opcode behind readonly role like every other read op" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
|
||||
const store = authmod.Store.init(&db);
|
||||
try store.createUser(allocator, "reader", "pw", .readonly, 16);
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
server.enableAuth(store);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var unauthed = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
defer unauthed.close();
|
||||
try std.testing.expectError(error.ServerUnauthorized, unauthed.metricsText(allocator));
|
||||
|
||||
var reader = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
defer reader.close();
|
||||
try reader.authenticate("reader", "pw");
|
||||
const text = try reader.metricsText(allocator);
|
||||
allocator.free(text);
|
||||
}
|
||||
|
||||
test "enableSessionSweep periodically drops expired sessions nobody ever presents again" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
const store = authmod.Store.init(&db);
|
||||
try store.createUser(allocator, "alice", "hunter2", .readwrite, 16);
|
||||
|
||||
var sessions = sessionmod.SessionStore.init(allocator);
|
||||
defer sessions.deinit();
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
server.enableAuth(store);
|
||||
// A short-lived session that nobody will ever present again: without
|
||||
// the sweeper, this sits in `sessions` forever since `validate()` only
|
||||
// cleans up a token someone actually tries to use.
|
||||
server.enableSessions(&sessions, 100 * std.time.ns_per_ms);
|
||||
server.enableSessionSweep(50 * std.time.ns_per_ms);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var client = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
try client.authenticate("alice", "hunter2");
|
||||
client.close();
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 1), sessions.count());
|
||||
|
||||
// Long enough for the session to expire (100ms) and for at least one
|
||||
// sweep tick (every 50ms) to have run past that point.
|
||||
std.time.sleep(400 * std.time.ns_per_ms);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 0), sessions.count());
|
||||
try std.testing.expect(server.sessionsSweptTotal.get() >= 1);
|
||||
}
|
||||
|
||||
test "a server with sessions enabled but no sweep interval never sweeps" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
const store = authmod.Store.init(&db);
|
||||
try store.createUser(allocator, "alice", "hunter2", .readwrite, 16);
|
||||
|
||||
var sessions = sessionmod.SessionStore.init(allocator);
|
||||
defer sessions.deinit();
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
server.enableAuth(store);
|
||||
server.enableSessions(&sessions, 50 * std.time.ns_per_ms);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
const client_mod = @import("client.zig");
|
||||
var client = try client_mod.Client.connect(allocator, server.boundAddress());
|
||||
try client.authenticate("alice", "hunter2");
|
||||
client.close();
|
||||
|
||||
std.time.sleep(200 * std.time.ns_per_ms);
|
||||
|
||||
// The session expired a while ago, but with no sweeper running it is
|
||||
// still sitting in the store's map until something calls `validate` on
|
||||
// it - `count()` still sees it, unlike the sweeper test above.
|
||||
try std.testing.expectEqual(@as(usize, 1), sessions.count());
|
||||
try std.testing.expectEqual(@as(u64, 0), server.sessionsSweptTotal.get());
|
||||
}
|
||||
|
||||
test "ttl_op over the wire: expire, query, persist, and sweep round trip against a real server" {
|
||||
const allocator = std.testing.allocator;
|
||||
var tmp = std.testing.tmpDir(.{});
|
||||
defer tmp.cleanup();
|
||||
const path = try tmp.dir.realpathAlloc(allocator, ".");
|
||||
defer allocator.free(path);
|
||||
|
||||
var db = try testDb(allocator, path);
|
||||
defer db.close();
|
||||
|
||||
const address = try std.net.Address.parseIp("127.0.0.1", 0);
|
||||
var server = try Server.init(allocator, &db, address);
|
||||
try server.start();
|
||||
defer server.stop();
|
||||
|
||||
Reference in new issue
Block a user