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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,22 @@ StatefulSet pod address is stable. The module writes all supplied roots into
one non-advertised local-root group and sets its valency to the number of
access points. Omitting `topology.local_roots` preserves the prior instance
shape.

Peers outside the fleet, such as another operator's relay, go in
`topology.external_local_root_groups`. Each entry renders as its own
non-advertised local-root group after the fleet group, with a valency equal to
its number of access points, so it can be added or removed without touching the
fleet mesh. External groups do not create a `nodes-<salt>` Service.

```hcl
topology = {
local_roots = [ /* fleet peers */ ]
external_local_root_groups = [
{
access_points = [
{ address = "relay.peer-operator.example", port = 3001 },
]
},
]
}
```
44 changes: 30 additions & 14 deletions bootstrap/configs/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,43 @@ variable "local_roots" {
default = []
}

variable "external_local_root_groups" {
description = "local-root groups for peers outside the fleet, each rendered as its own non-advertised group after local_roots"
type = list(object({
access_points = list(object({
address = string
port = number
}))
}))
default = []
}

locals {
baseline_topology_json = file("${path.module}/${var.network}/topology.json")
baseline_topology = jsondecode(local.baseline_topology_json)

local_root_groups = [
for group in concat(
length(var.local_roots) == 0 ? [] : [var.local_roots],
[for group in var.external_local_root_groups : group.access_points],
) : {
accessPoints = [
for root in group : {
address = root.address
port = root.port
}
]
advertise = false
trustable = false
valency = length(group)
}
]

rendered_topology = merge(local.baseline_topology, {
localRoots = length(var.local_roots) == 0 ? local.baseline_topology.localRoots : [
{
accessPoints = [
for root in var.local_roots : {
address = root.address
port = root.port
}
]
advertise = false
trustable = false
valency = length(var.local_roots)
}
]
localRoots = length(local.local_root_groups) == 0 ? local.baseline_topology.localRoots : local.local_root_groups
})

topology_json = length(var.local_roots) == 0 ? local.baseline_topology_json : jsonencode(local.rendered_topology)
topology_json = length(local.local_root_groups) == 0 ? local.baseline_topology_json : jsonencode(local.rendered_topology)
}

resource "kubernetes_config_map" "node-config" {
Expand Down
5 changes: 3 additions & 2 deletions bootstrap/configs/mainnet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ custom P2P topology. They preserve the node's bootstrap peers, ledger-peer
activation, peer snapshot, tracing, and protocol configuration.

`topology.json` supplies the P2P baseline. The module adds an instance's
configured local roots to it as one non-advertised root group, with a valency
equal to the number of access points.
configured local roots to it as one non-advertised root group, followed by one
non-advertised group per external local-root group, each with a valency equal
to its number of access points.

The genesis, checkpoint, and peer-snapshot paths are absolute because the
module mounts only `config.json` and `topology.json` at `/configuration`. The
Expand Down
13 changes: 7 additions & 6 deletions bootstrap/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ module "instances" {
availability_sla = coalesce(each.value.availability_sla, "consistent")
node_version = each.value.node_version
restore = coalesce(each.value.restore, false)
is_custom = coalesce(each.value.is_custom, false) || length(each.value.topology.local_roots) > 0
is_custom = coalesce(each.value.is_custom, false) || length(each.value.topology.local_roots) > 0 || length(each.value.topology.external_local_root_groups) > 0
has_local_roots = length(each.value.topology.local_roots) > 0
is_relay = coalesce(each.value.is_relay, false)
tolerations = coalesce(each.value.tolerations, [])
Expand All @@ -105,13 +105,14 @@ module "custom_configs" {
source = "./configs"
for_each = {
for key, instance in var.instances : key => instance
if coalesce(instance.is_custom, false) || length(instance.topology.local_roots) > 0
if coalesce(instance.is_custom, false) || length(instance.topology.local_roots) > 0 || length(instance.topology.external_local_root_groups) > 0
}

namespace = var.namespace
network = each.value.network
salt = each.value.salt
local_roots = each.value.topology.local_roots
namespace = var.namespace
network = each.value.network
salt = each.value.salt
local_roots = each.value.topology.local_roots
external_local_root_groups = each.value.topology.external_local_root_groups
}

module "services" {
Expand Down
110 changes: 110 additions & 0 deletions bootstrap/tests/mempool_mesh.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,113 @@ run "custom_instance_without_local_roots_preserves_baseline" {
error_message = "a custom instance without local roots must preserve its baseline topology bytes"
}
}

run "external_local_root_group_is_separate" {
command = plan

variables {
namespace = "test-namespace"
operator_image_tag = "test"
api_key_salt = "test"
proxy_blue_image_tag = "test"
proxy_blue_instances_namespace = "test-namespace"
proxy_blue_healthcheck_port = 31789
proxy_green_image_tag = "test"
proxy_green_instances_namespace = "test-namespace"
proxy_green_healthcheck_port = 32171
services = {}

instances = {
mainnet-mesh-a = {
node_image = "ghcr.io/blinklabs-io/cardano-node"
image_tag = "11.0.1"
network = "mainnet"
salt = "a"
release = "mesh"
magic = 764824073
node_version = "11.0.1"
replicas = 1
topology = {
local_roots = [
{ address = "node-mainnet-b-0.nodes-b.test-namespace.svc.cluster.local", port = 3000 },
{ address = "node-mainnet-c-0.nodes-c.test-namespace.svc.cluster.local", port = 3000 },
{ address = "node-mainnet-d-0.nodes-d.test-namespace.svc.cluster.local", port = 3000 },
]
external_local_root_groups = [
{ access_points = [{ address = "relay.peer-operator.example", port = 3001 }] },
]
}
}
}
}

assert {
condition = length(module.custom_configs["mainnet-mesh-a"].topology.localRoots) == 2
error_message = "an external group must render as its own local-root group"
}

assert {
condition = (
module.custom_configs["mainnet-mesh-a"].topology.localRoots[0].valency == 3 &&
length(module.custom_configs["mainnet-mesh-a"].topology.localRoots[0].accessPoints) == 3
)
error_message = "an external group must not change the fleet local-root group"
}

assert {
condition = (
module.custom_configs["mainnet-mesh-a"].topology.localRoots[1].advertise == false &&
module.custom_configs["mainnet-mesh-a"].topology.localRoots[1].trustable == false &&
module.custom_configs["mainnet-mesh-a"].topology.localRoots[1].valency == 1 &&
length(module.custom_configs["mainnet-mesh-a"].topology.localRoots[1].accessPoints) == 1 &&
module.custom_configs["mainnet-mesh-a"].topology.localRoots[1].accessPoints[0].address == "relay.peer-operator.example" &&
module.custom_configs["mainnet-mesh-a"].topology.localRoots[1].accessPoints[0].port == 3001
)
error_message = "an external group must be non-advertised, untrusted, and hot for each of its access points"
}
}

run "external_local_root_group_without_fleet_roots" {
command = plan

variables {
namespace = "test-namespace"
operator_image_tag = "test"
api_key_salt = "test"
proxy_blue_image_tag = "test"
proxy_blue_instances_namespace = "test-namespace"
proxy_blue_healthcheck_port = 31789
proxy_green_image_tag = "test"
proxy_green_instances_namespace = "test-namespace"
proxy_green_healthcheck_port = 32171
services = {}

instances = {
mainnet-external-a = {
node_image = "ghcr.io/blinklabs-io/cardano-node"
image_tag = "11.0.1"
network = "mainnet"
salt = "a"
release = "external"
magic = 764824073
node_version = "11.0.1"
replicas = 1
topology = {
external_local_root_groups = [
{ access_points = [{ address = "relay.peer-operator.example", port = 3001 }] },
]
}
}
}
}

assert {
condition = length(module.custom_configs["mainnet-external-a"].topology.localRoots) == 1
error_message = "an instance with only an external group must render exactly that group"
}

assert {
condition = module.instances["mainnet-external-a"].peer_service == null
error_message = "an external group alone must not create a headless peer Service"
}
}
6 changes: 6 additions & 0 deletions bootstrap/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ variable "instances" {
address = string
port = number
})), [])
external_local_root_groups = optional(list(object({
access_points = list(object({
address = string
port = number
}))
})), [])
}), {})
rts_opts = optional(string)
readiness_probe = optional(object({
Expand Down
Loading