Files
herolib/lib/threefold/tfgrid3deployer/network.v
mariobassem 5256ab6f23 refactor(tfgrid3deployer): use more reliable nodes
- pick reliable nodes by pinging them before attempting deployment
2025-01-15 17:59:15 +02:00

327 lines
9.6 KiB
V

module tfgrid3deployer
import freeflowuniverse.herolib.threefold.grid.models as grid_models
import freeflowuniverse.herolib.threefold.gridproxy
import freeflowuniverse.herolib.threefold.grid
import freeflowuniverse.herolib.ui.console
import json
import rand
// NetworkInfo struct to represent network details
pub struct NetworkSpecs {
pub mut:
name string = 'net' + rand.string(5)
ip_range string = '10.10.0.0/16'
mycelium string = rand.hex(64)
}
struct NetworkHandler {
mut:
network_name string
nodes []u32
ip_range string
wg_ports map[u32]u16
wg_keys map[u32][]string
wg_subnet map[u32]string
endpoints map[u32]string
public_node u32
hidden_nodes []u32
none_accessible_ip_ranges []string
mycelium string
deployer &grid.Deployer @[skip; str: skip]
}
// TODO: maybe rename to fill_network or something similar
fn (mut self NetworkHandler) create_network(vmachines []VMachine) ! {
// Set nodes
self.nodes = []
for vmachine in vmachines {
if !self.nodes.contains(vmachine.node_id) {
self.nodes << vmachine.node_id
}
}
console.print_header('Network nodes: ${self.nodes}.')
self.setup_wireguard_data()!
self.setup_access_node()!
}
fn (mut self NetworkHandler) generate_workload(node_id u32, peers []grid_models.Peer, mycleium_hex_key string) !grid_models.Workload {
mut network_workload := grid_models.Znet{
ip_range: self.ip_range
subnet: self.wg_subnet[node_id]
wireguard_private_key: self.wg_keys[node_id][0]
wireguard_listen_port: self.wg_ports[node_id]
peers: peers
mycelium: grid_models.Mycelium{
hex_key: mycleium_hex_key
peers: []
}
}
return network_workload.to_workload(
name: self.network_name
description: 'VGridClient network workload'
)
}
fn (mut self NetworkHandler) prepare_hidden_node_peers(node_id u32) ![]grid_models.Peer {
mut peers := []grid_models.Peer{}
if self.public_node != 0 {
peers << grid_models.Peer{
subnet: self.wg_subnet[self.public_node]
wireguard_public_key: self.wg_keys[self.public_node][1]
allowed_ips: [self.ip_range, '100.64.0.0/16']
endpoint: '${self.endpoints[self.public_node]}:${self.wg_ports[self.public_node]}'
}
}
return peers
}
fn (mut self NetworkHandler) setup_access_node() ! {
// Case 1: Deployment on 28 which is hidden node
// - Setup access node
// Case 2: Deployment on 11 which is public node
// - Already have the access node
// Case 3: if the saved state has already public node.
// - Check the new deployment if its node is hidden take the saved one
// - if the access node is already set, that means we have set its values e.g. the wireguard port, keys
if self.hidden_nodes.len < 1 || self.nodes.len == 1 {
self.public_node = 0
return
}
if self.public_node != 0 {
if !self.nodes.contains(self.public_node) {
self.nodes << self.public_node
}
return
}
/*
- In this case a public node should be assigned.
- We need to store it somewhere to inform the user that the deployment has one more contract on another node,
also delete that contract when delete the full deployment.
- Assign the public node with the new node id.
*/
console.print_header('No public nodes found based on your specs.')
console.print_header('Requesting the Proxy to assign a public node.')
nodes := filter_nodes(
ipv4: true
status: 'up'
healthy: true
available_for: u64(self.deployer.twin_id)
features: [
'zmachine',
]
)!
if nodes.len == 0 {
return error('Requested the Grid Proxy and no nodes found.')
}
access_node := pick_node(mut self.deployer, nodes) or {
return error('Failed to pick valid node: ${err}')
}
self.public_node = u32(access_node.node_id)
console.print_header('Public node ${self.public_node}')
self.nodes << self.public_node
wg_port := self.deployer.assign_wg_port(self.public_node)!
keys := self.deployer.client.generate_wg_priv_key()! // The first index will be the private.
mut parts := self.ip_range.split('/')[0].split('.')
parts[2] = '${self.nodes.len + 2}'
subnet := parts.join('.') + '/24'
self.wg_ports[self.public_node] = wg_port
self.wg_keys[self.public_node] = keys
self.wg_subnet[self.public_node] = subnet
self.endpoints[self.public_node] = access_node.public_config.ipv4.split('/')[0]
}
fn (mut self NetworkHandler) setup_wireguard_data() ! {
// TODO: We need to set the extra node
console.print_header('Setting up network workload.')
self.hidden_nodes, self.none_accessible_ip_ranges = [], []
for node_id in self.nodes {
// TODO: Check if there values don't re-generate
mut public_config := self.deployer.get_node_pub_config(node_id) or {
if err.msg().contains('no public configuration') {
grid_models.PublicConfig{}
} else {
return error('Failed to get node public config: ${err}')
}
}
if _ := self.wg_ports[node_id] {
// The node already exists
if public_config.ipv4.len != 0 {
self.endpoints[node_id] = public_config.ipv4.split('/')[0]
if self.public_node == 0 {
self.public_node = node_id
}
} else if public_config.ipv6.len != 0 {
self.endpoints[node_id] = public_config.ipv6.split('/')[0]
} else {
self.hidden_nodes << node_id
self.none_accessible_ip_ranges << self.wg_subnet[node_id]
self.none_accessible_ip_ranges << wireguard_routing_ip(self.wg_subnet[node_id])
}
continue
}
self.wg_ports[node_id] = self.deployer.assign_wg_port(node_id)!
self.wg_keys[node_id] = self.deployer.client.generate_wg_priv_key()!
self.wg_subnet[node_id] = self.calculate_subnet()!
if public_config.ipv4.len != 0 {
self.endpoints[node_id] = public_config.ipv4.split('/')[0]
self.public_node = node_id
} else if public_config.ipv6.len != 0 {
self.endpoints[node_id] = public_config.ipv6.split('/')[0]
} else {
self.hidden_nodes << node_id
self.none_accessible_ip_ranges << self.wg_subnet[node_id]
self.none_accessible_ip_ranges << wireguard_routing_ip(self.wg_subnet[node_id])
}
}
}
fn (mut self NetworkHandler) prepare_public_node_peers(node_id u32) ![]grid_models.Peer {
mut peers := []grid_models.Peer{}
for peer_id in self.nodes {
if peer_id in self.hidden_nodes || peer_id == node_id {
continue
}
subnet := self.wg_subnet[peer_id]
mut allowed_ips := [subnet, wireguard_routing_ip(subnet)]
if peer_id == self.public_node {
allowed_ips << self.none_accessible_ip_ranges
}
peers << grid_models.Peer{
subnet: subnet
wireguard_public_key: self.wg_keys[peer_id][1]
allowed_ips: allowed_ips
endpoint: '${self.endpoints[peer_id]}:${self.wg_ports[peer_id]}'
}
}
if node_id == self.public_node {
for hidden_node_id in self.hidden_nodes {
subnet := self.wg_subnet[hidden_node_id]
routing_ip := wireguard_routing_ip(subnet)
peers << grid_models.Peer{
subnet: subnet
wireguard_public_key: self.wg_keys[hidden_node_id][1]
allowed_ips: [subnet, routing_ip]
endpoint: ''
}
}
}
return peers
}
fn (mut self NetworkHandler) calculate_subnet() !string {
mut parts := self.ip_range.split('/')[0].split('.')
for i := 2; i <= 255; i += 1 {
parts[2] = '${i}'
candidate := parts.join('.') + '/24'
if !self.wg_subnet.values().contains(candidate) {
return candidate
}
}
return error('failed to calcuate subnet')
}
fn (mut self NetworkHandler) load_network_state(dls map[u32]grid_models.Deployment) ! {
// load network from deployments
mut network_name := ''
mut subnet_node := map[string]u32{}
mut subnet_to_endpoint := map[string]string{}
for node_id, dl in dls {
mut znet := grid_models.Znet{}
for wl in dl.workloads {
network_name = wl.name
if wl.type_ == grid_models.workload_types.network {
znet = json.decode(grid_models.Znet, wl.data)!
break
}
}
if znet.subnet == '' {
// deployment didn't have a network workload. skip..
continue
}
self.network_name = network_name
self.nodes << node_id
self.ip_range = znet.ip_range
self.wg_ports[node_id] = znet.wireguard_listen_port
self.wg_keys[node_id] = [znet.wireguard_private_key,
self.deployer.client.generate_wg_public_key(znet.wireguard_private_key)!]
self.wg_subnet[node_id] = znet.subnet
self.mycelium = if myclelium := znet.mycelium { myclelium.hex_key } else { '' }
subnet_node[znet.subnet] = node_id
for peer in znet.peers {
subnet_to_endpoint[peer.subnet] = peer.endpoint
if peer.endpoint == '' {
// current node is the access node
self.public_node = node_id
}
}
}
for subnet, endpoint in subnet_to_endpoint {
node_id := subnet_node[subnet]
if endpoint == '' {
self.hidden_nodes << node_id
continue
}
self.endpoints[node_id] = endpoint.all_before_last(':').trim('[]')
}
for node_id in self.hidden_nodes {
self.none_accessible_ip_ranges << self.wg_subnet[node_id]
self.none_accessible_ip_ranges << wireguard_routing_ip(self.wg_subnet[node_id])
}
}
fn (mut self NetworkHandler) generate_workloads() !map[u32]grid_models.Workload {
mut workloads := map[u32]grid_models.Workload{}
for node_id in self.nodes {
if node_id in self.hidden_nodes {
mut peers := self.prepare_hidden_node_peers(node_id)!
workloads[node_id] = self.generate_workload(node_id, peers, self.mycelium)!
continue
}
mut peers := self.prepare_public_node_peers(node_id)!
workloads[node_id] = self.generate_workload(node_id, peers, self.mycelium)!
}
return workloads
}
fn (mut n NetworkHandler) remove_node(node_id u32) ! {
}
fn (mut n NetworkHandler) add_node() ! {
}