Files
brianmcgee e76ac4e1ea pkg/config: change default network name to home
I think it's a clearer indicator of what the default network is: it's the node's home network/clan.
2026-03-30 11:48:35 +01:00

365 lines
12 KiB
Nix

{
lib,
dmModule,
testers,
}:
testers.nixosTest {
name = "data-mesher-multi-network";
nodes =
let
peerID = {
a1 = builtins.readFile ./keys/network_a/a1.peerID;
a2 = builtins.readFile ./keys/network_a/a2.peerID;
a3 = builtins.readFile ./keys/network_a/a3.peerID;
b1 = builtins.readFile ./keys/network_b/b1.peerID;
b2 = builtins.readFile ./keys/network_b/b2.peerID;
b3 = builtins.readFile ./keys/network_b/b3.peerID;
c1 = builtins.readFile ./keys/network_c/c1.peerID;
c2 = builtins.readFile ./keys/network_c/c2.peerID;
c3 = builtins.readFile ./keys/network_c/c3.peerID;
};
mkPeerAddr = name: "/dns/${name}/tcp/7946/p2p/${peerID.${name}}";
mkNode =
{
name,
networkCA,
bootstrapPeers,
homeFiles,
extraNetworks ? { },
}:
{
imports = [ dmModule ];
config = {
system.activationScripts.identity_key = {
text =
let
# Collect all network pub keys that need to be copied
extraNetworkPubs = lib.concatMapStrings (netName: ''
cp ${./keys/${netName}/network.pub} /run/${netName}.pub
chown data-mesher:data-mesher /run/${netName}.pub
chmod 600 /run/${netName}.pub
'') (builtins.attrNames extraNetworks);
in
''
# Copy home network public key
cp ${./keys/${networkCA}/network.pub} /run/${networkCA}.pub
chown data-mesher:data-mesher /run/${networkCA}.pub
chmod 600 /run/${networkCA}.pub
# Copy extra network public keys
${extraNetworkPubs}
# Copy peer identity key
cp ${./keys/${networkCA}/${name}.key} /run/identity.key
chown data-mesher:data-mesher /run/identity.key
chmod 600 /run/identity.key
# Copy peer identity certificate signed by the network
cp ${./keys/${networkCA}/${name}.cert} /run/identity.cert
chown data-mesher:data-mesher /run/identity.cert
chmod 600 /run/identity.cert
'';
};
services.data-mesher = {
enable = true;
openFirewall = true;
settings = {
log_level = "debug";
cluster = {
port = 7946;
bootstrap_peers = bootstrapPeers;
# speed up for testing
push_pull_interval = "5s";
identity_key = "/run/identity.key";
identity_cert = "/run/identity.cert";
};
network = {
id = "/run/${networkCA}.pub";
files = homeFiles;
};
extra_networks = builtins.mapAttrs (netName: netCfg: {
id = "/run/${netName}.pub";
inherit (netCfg) files;
}) extraNetworks;
};
};
# override just for testing
# when booting the interface we bind to is not ready, causing an error
systemd.services.data-mesher.serviceConfig.RestartSec = lib.mkForce "1s";
};
};
# Files configuration per network
# Network A: file_a signed by alice
filesA = {
"file_a" = [ "${./keys/signers/alice.pub}" ];
};
# Network B: file_b signed by bob
filesB = {
"file_b" = [ "${./keys/signers/bob.pub}" ];
};
# Network C: file_c signed by charlie
filesC = {
"file_c" = [ "${./keys/signers/charlie.pub}" ];
};
# Bootstrap peers per network group
# A nodes bootstrap to a1, a2, and c1 (cross-network)
bootstrapA = [
(mkPeerAddr "a1")
(mkPeerAddr "a2")
(mkPeerAddr "c1")
];
# B nodes bootstrap to b1, b2, and c1 (cross-network)
bootstrapB = [
(mkPeerAddr "b1")
(mkPeerAddr "b2")
(mkPeerAddr "c1")
];
# C nodes bootstrap to c1, c2, a1, and b1 (cross-network to both)
bootstrapC = [
(mkPeerAddr "c1")
(mkPeerAddr "c2")
(mkPeerAddr "a1")
(mkPeerAddr "b1")
];
in
{
# Network A nodes: home=netA, extra=netC
a1 = mkNode {
name = "a1";
networkCA = "network_a";
bootstrapPeers = bootstrapA;
homeFiles = filesA;
extraNetworks.network_c = {
files = filesC;
};
};
a2 = mkNode {
name = "a2";
networkCA = "network_a";
bootstrapPeers = bootstrapA;
homeFiles = filesA;
extraNetworks.network_c = {
files = filesC;
};
};
a3 = mkNode {
name = "a3";
networkCA = "network_a";
bootstrapPeers = bootstrapA;
homeFiles = filesA;
extraNetworks.network_c = {
files = filesC;
};
};
# Network B nodes: home=netB, extra=netC
b1 = mkNode {
name = "b1";
networkCA = "network_b";
bootstrapPeers = bootstrapB;
homeFiles = filesB;
extraNetworks.network_c = {
files = filesC;
};
};
b2 = mkNode {
name = "b2";
networkCA = "network_b";
bootstrapPeers = bootstrapB;
homeFiles = filesB;
extraNetworks.network_c = {
files = filesC;
};
};
b3 = mkNode {
name = "b3";
networkCA = "network_b";
bootstrapPeers = bootstrapB;
homeFiles = filesB;
extraNetworks.network_c = {
files = filesC;
};
};
# Network C nodes: home=netC, extra=netA and netB
c1 = mkNode {
name = "c1";
networkCA = "network_c";
bootstrapPeers = bootstrapC;
homeFiles = filesC;
extraNetworks = {
network_a = {
files = filesA;
};
network_b = {
files = filesB;
};
};
};
c2 = mkNode {
name = "c2";
networkCA = "network_c";
bootstrapPeers = bootstrapC;
homeFiles = filesC;
extraNetworks = {
network_a = {
files = filesA;
};
network_b = {
files = filesB;
};
};
};
c3 = mkNode {
name = "c3";
networkCA = "network_c";
bootstrapPeers = bootstrapC;
homeFiles = filesC;
extraNetworks = {
network_a = {
files = filesA;
};
network_b = {
files = filesB;
};
};
};
};
testScript = ''
# Network names: home network is always "home", extra networks use their configured names
# For network A nodes: home="home" (file_a), extra="network_c" (file_c)
# For network B nodes: home="home" (file_b), extra="network_c" (file_c)
# For network C nodes: home="home" (file_c), extra="network_a" (file_a), extra="network_b" (file_b)
def file_path(net_name, filename):
"""Return the on-disk path for a file under a given network."""
return f"/var/lib/data-mesher/files/{net_name}/{filename}"
def upload_file(node, filename, content, key_path, network_pub):
"""Upload a file to a node's home network via the CLI."""
node.succeed(f"echo -n '{content}' > /tmp/{filename}")
node.succeed(f"data-mesher file update /tmp/{filename} --url http://[::1]:7331 --key {key_path} --network-id {network_pub}")
def wait_for_file(node, net_name, filename, expected_content, timeout=60):
"""Wait until a file exists with the expected content."""
fp = file_path(net_name, filename)
node.wait_until_succeeds(
f"test -f {fp} && grep -q '{expected_content}' {fp}",
timeout
)
def file_absent(node, net_name, filename):
"""Assert that a file does NOT exist under the given network."""
fp = file_path(net_name, filename)
status = node.succeed(f"test -f {fp} && echo yes || echo no").strip()
assert status == "no", f"Expected {fp} to be absent on {node.name}, but it exists"
# Wait for all nodes to start
all_nodes = [a1, a2, a3, b1, b2, b3, c1, c2, c3]
for node in all_nodes:
node.wait_for_unit("data-mesher.service")
# ========================================
# Test 1: Intra-network propagation (A)
# ========================================
upload_file(a1, "file_a", "hello_from_a", "${./keys/signers/alice.key}", "${./keys/network_a/network.pub}")
# On A nodes, file_a is stored in "home" (their home network)
wait_for_file(a1, "home", "file_a", "hello_from_a")
wait_for_file(a2, "home", "file_a", "hello_from_a")
wait_for_file(a3, "home", "file_a", "hello_from_a")
print("Test 1 PASSED: Intra-network propagation (A)")
# ========================================
# Test 2: Cross-network A -> C
# ========================================
# On C nodes, file_a is stored in "network_a" (their extra network)
wait_for_file(c1, "network_a", "file_a", "hello_from_a")
wait_for_file(c2, "network_a", "file_a", "hello_from_a")
wait_for_file(c3, "network_a", "file_a", "hello_from_a")
print("Test 2 PASSED: Cross-network A -> C")
# ========================================
# Test 3: Intra-network propagation (B)
# ========================================
upload_file(b1, "file_b", "hello_from_b", "${./keys/signers/bob.key}", "${./keys/network_b/network.pub}")
# On B nodes, file_b is stored in "home" (their home network)
wait_for_file(b1, "home", "file_b", "hello_from_b")
wait_for_file(b2, "home", "file_b", "hello_from_b")
wait_for_file(b3, "home", "file_b", "hello_from_b")
print("Test 3 PASSED: Intra-network propagation (B)")
# ========================================
# Test 4: Cross-network B -> C
# ========================================
# On C nodes, file_b is stored in "network_b" (their extra network)
wait_for_file(c1, "network_b", "file_b", "hello_from_b")
wait_for_file(c2, "network_b", "file_b", "hello_from_b")
wait_for_file(c3, "network_b", "file_b", "hello_from_b")
print("Test 4 PASSED: Cross-network B -> C")
# ========================================
# Test 5: Cross-network C -> A and C -> B
# ========================================
upload_file(c1, "file_c", "hello_from_c", "${./keys/signers/charlie.key}", "${./keys/network_c/network.pub}")
# On C nodes, file_c is stored in "home" (their home network)
wait_for_file(c1, "home", "file_c", "hello_from_c")
wait_for_file(c2, "home", "file_c", "hello_from_c")
wait_for_file(c3, "home", "file_c", "hello_from_c")
# C -> A: On A nodes, file_c is stored in "network_c" (their extra network)
wait_for_file(a1, "network_c", "file_c", "hello_from_c")
wait_for_file(a2, "network_c", "file_c", "hello_from_c")
wait_for_file(a3, "network_c", "file_c", "hello_from_c")
# C -> B: On B nodes, file_c is stored in "network_c" (their extra network)
wait_for_file(b1, "network_c", "file_c", "hello_from_c")
wait_for_file(b2, "network_c", "file_c", "hello_from_c")
wait_for_file(b3, "network_c", "file_c", "hello_from_c")
print("Test 5 PASSED: Cross-network C -> A and C -> B")
# ========================================
# Test 6: Network isolation (A <-> B)
# ========================================
# file_a should NOT be on any B nodes (B nodes don't have network_a configured)
# Check both default (where file_b is) and network_a (which doesn't exist for B nodes)
for node in [b1, b2, b3]:
file_absent(node, "network_a", "file_a")
# file_b should NOT be on any A nodes (A nodes don't have network_b configured)
# Check both default (where file_a is) and network_b (which doesn't exist for A nodes)
for node in [a1, a2, a3]:
file_absent(node, "network_b", "file_b")
print("Test 6 PASSED: Network isolation (A <-> B)")
print("All tests PASSED!")
'';
}