db-mode config changes apply live in-process
settings and upstream writes now follow a prepare, commit, publish, retire contract: candidates are built and validated before the database transaction, published as infallible pointer swaps, and old generations retire after their readers drain. per-query policy values snapshot once per query; upstream pool, cache, rate limiter, sessions, api limiter, log sink, blocklist scheduler and the query-log queue each gained one named live operation. restart_required shrinks from every scalar key to the bind keys and web.enabled; the admin ui drops its restart notices for everything else. file mode is unchanged.
This commit is contained in:
+103
-9
@@ -28,6 +28,7 @@ const handler = @import("handler.zig");
|
||||
const listener = @import("listener.zig");
|
||||
const tls_server = @import("../platform/tls_server.zig");
|
||||
const transport = @import("../upstream/transport.zig");
|
||||
const upstream_owner = @import("../upstream/owner.zig");
|
||||
|
||||
/// Plaintext staging for `ServerStream`: the framing bytes and the decrypted
|
||||
/// record tail pass through here, while whole messages go straight to
|
||||
@@ -312,11 +313,10 @@ const forward_timeout: std.Io.Clock.Duration = .{
|
||||
/// An upstream and nothing else optional: no filtering, no cache, no log. The
|
||||
/// listener is what these tests exercise, so the handler is the same bare one
|
||||
/// its own tests use.
|
||||
fn bareHandler(client: transport.Client) handler.Handler {
|
||||
fn bareHandler(up: *upstream_owner.Owner) handler.Handler {
|
||||
return .{
|
||||
.upstream = client,
|
||||
.blocking = blocking,
|
||||
.forward_read_timeout = forward_timeout,
|
||||
.upstream = up,
|
||||
.policy = .{ .blocking = blocking, .forward_read_timeout = forward_timeout },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@ test "dot: two framed queries share one TLS connection" {
|
||||
defer env.deinit(io);
|
||||
|
||||
var fake: FakeUpstream = .{ .reply = response_bytes };
|
||||
var h = bareHandler(fake.client());
|
||||
var h_owner: upstream_owner.Borrowed = .{};
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: std.Io.net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try DotServer.listen(gpa, io, listen_address, &h, &env.store, .{ .max_connections = 2 });
|
||||
@@ -607,7 +608,8 @@ test "dot: a transport EOF without close_notify is a connection error, not a cra
|
||||
defer env.deinit(io);
|
||||
|
||||
var fake: FakeUpstream = .{ .reply = response_bytes };
|
||||
var h = bareHandler(fake.client());
|
||||
var h_owner: upstream_owner.Borrowed = .{};
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: std.Io.net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try DotServer.listen(gpa, io, listen_address, &h, &env.store, .{ .max_connections = 2 });
|
||||
@@ -645,7 +647,8 @@ test "dot: plain TCP bytes fail the handshake and are counted" {
|
||||
defer env.deinit(io);
|
||||
|
||||
var fake: FakeUpstream = .{ .reply = response_bytes };
|
||||
var h = bareHandler(fake.client());
|
||||
var h_owner: upstream_owner.Borrowed = .{};
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: std.Io.net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try DotServer.listen(gpa, io, listen_address, &h, &env.store, .{ .max_connections = 2 });
|
||||
@@ -731,7 +734,8 @@ test "dot: a reload serves new handshakes without breaking the old connection" {
|
||||
defer env.deinit(io);
|
||||
|
||||
var fake: FakeUpstream = .{ .reply = response_bytes };
|
||||
var h = bareHandler(fake.client());
|
||||
var h_owner: upstream_owner.Borrowed = .{};
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: std.Io.net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try DotServer.listen(gpa, io, listen_address, &h, &env.store, .{ .max_connections = 2 });
|
||||
@@ -759,6 +763,95 @@ test "dot: a reload serves new handshakes without breaking the old connection" {
|
||||
};
|
||||
}
|
||||
|
||||
/// S3.4 across a live listener: a cert PATH change — new files, not rewritten
|
||||
/// ones — serves the new certificate on the next handshake while the
|
||||
/// connection pinned to the old generation finishes on it.
|
||||
fn dotPathChangeServesNewCert(io: std.Io, address_: std.Io.net.IpAddress, env: *CertEnv) anyerror!void {
|
||||
var first: TestTls = undefined;
|
||||
try first.connect(io, address_);
|
||||
defer first.close(io);
|
||||
|
||||
try first.sendQuery();
|
||||
try expectAnswersQuery(try first.readReply());
|
||||
|
||||
const old_entry = env.store.acquire(io);
|
||||
defer env.store.release(io, old_entry);
|
||||
|
||||
try env.tmp.dir.writeFile(io, .{ .sub_path = "cert2.pem", .data = fixtures.cert2_pem });
|
||||
try env.tmp.dir.writeFile(io, .{ .sub_path = "key2.pem", .data = fixtures.key2_pem });
|
||||
var cert_buf: [128]u8 = undefined;
|
||||
var key_buf: [128]u8 = undefined;
|
||||
const next_cert = try std.fmt.bufPrint(&cert_buf, ".zig-cache/tmp/{s}/cert2.pem", .{env.tmp.sub_path});
|
||||
const next_key = try std.fmt.bufPrint(&key_buf, ".zig-cache/tmp/{s}/key2.pem", .{env.tmp.sub_path});
|
||||
|
||||
const prepared = try env.store.preparePathChange(io, next_cert, next_key);
|
||||
env.store.publishPathChange(io, prepared);
|
||||
|
||||
const new_entry = env.store.acquire(io);
|
||||
defer env.store.release(io, new_entry);
|
||||
try testing.expect(old_entry != new_entry);
|
||||
|
||||
var second: TestTls = undefined;
|
||||
try second.connect(io, address_);
|
||||
defer second.close(io);
|
||||
try second.sendQuery();
|
||||
try expectAnswersQuery(try second.readReply());
|
||||
|
||||
try first.sendQuery();
|
||||
try expectAnswersQuery(try first.readReply());
|
||||
|
||||
try second.client.end();
|
||||
try second.net_writer.interface.flush();
|
||||
var second_tail: [1]u8 = undefined;
|
||||
try testing.expectError(error.EndOfStream, second.client.reader.readSliceAll(&second_tail));
|
||||
|
||||
try first.client.end();
|
||||
try first.net_writer.interface.flush();
|
||||
var first_tail: [1]u8 = undefined;
|
||||
try testing.expectError(error.EndOfStream, first.client.reader.readSliceAll(&first_tail));
|
||||
}
|
||||
|
||||
test "dot: a cert path change serves the new certificate on the next handshake" {
|
||||
const build_options = @import("build_options");
|
||||
if (!build_options.integration) return error.SkipZigTest;
|
||||
|
||||
const gpa = testing.allocator;
|
||||
var threaded: std.Io.Threaded = .init(gpa, .{});
|
||||
defer threaded.deinit();
|
||||
const io = threaded.io();
|
||||
|
||||
var env: CertEnv = undefined;
|
||||
try env.init(io);
|
||||
defer env.deinit(io);
|
||||
|
||||
var fake: FakeUpstream = .{ .reply = response_bytes };
|
||||
var h_owner: upstream_owner.Borrowed = .{};
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: std.Io.net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try DotServer.listen(gpa, io, listen_address, &h, &env.store, .{ .max_connections = 2 });
|
||||
const server_address = server.boundAddress();
|
||||
|
||||
var group: std.Io.Group = .init;
|
||||
try group.concurrent(io, DotServer.serve, .{ &server, io });
|
||||
|
||||
try bounded(io, dotPathChangeServesNewCert, .{ io, server_address, &env });
|
||||
|
||||
const stats = server.snapshotStats();
|
||||
try testing.expectEqual(@as(u64, 2), stats.connections);
|
||||
try testing.expectEqual(@as(u64, 0), stats.tls_handshake_failures);
|
||||
try testing.expectEqual(@as(u64, 0), stats.connection_errors);
|
||||
|
||||
const store_stats = env.store.snapshotStats();
|
||||
try testing.expectEqual(@as(u64, 1), store_stats.reloads);
|
||||
try testing.expectEqual(@as(u64, 0), store_stats.reload_failures);
|
||||
|
||||
server.deinit(io);
|
||||
group.await(io) catch |err| switch (err) {
|
||||
error.Canceled => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
test "dot: an idle connection is closed with close_notify and counted" {
|
||||
const build_options = @import("build_options");
|
||||
if (!build_options.integration) return error.SkipZigTest;
|
||||
@@ -773,7 +866,8 @@ test "dot: an idle connection is closed with close_notify and counted" {
|
||||
defer env.deinit(io);
|
||||
|
||||
var fake: FakeUpstream = .{ .reply = response_bytes };
|
||||
var h = bareHandler(fake.client());
|
||||
var h_owner: upstream_owner.Borrowed = .{};
|
||||
var h = bareHandler(h_owner.client(fake.client()));
|
||||
|
||||
const listen_address: std.Io.net.IpAddress = try .parse("127.0.0.1", 0);
|
||||
var server = try DotServer.listen(gpa, io, listen_address, &h, &env.store, .{
|
||||
|
||||
Reference in New Issue
Block a user