Files
ubicloud/prog/vnet/update_load_balancer_node.rb
Jeremy Evans e6b7e5e879 Change rubocop TargetRubyVersion to 3.4
Disable Style/RedundantLineContinuation, as it incorrectly removes
line continutations in rhizome/host/lib/vm_setup.rb that are not
redundant.

All code changes are for _1 => it in blocks.
2025-04-26 06:51:19 +09:00

182 lines
6.6 KiB
Ruby

# frozen_string_literal: true
class Prog::Vnet::UpdateLoadBalancerNode < Prog::Base
subject_is :vm
def load_balancer
@load_balancer ||= LoadBalancer[frame.fetch("load_balancer_id")]
end
def before_run
pop "VM is destroyed" unless vm
end
label def update_load_balancer
vm.load_balancer_vm_ports.select { |lvp| lvp.state == "detaching" }.each do |load_balancer_vm_port|
load_balancer.remove_vm_port(load_balancer_vm_port)
end
# if there is literally no up resources to balance for, we simply not do
# load balancing.
hop_remove_load_balancer if load_balancer.active_vm_ports.count == 0
vm.vm_host.sshable.cmd("sudo ip netns exec #{vm.inhost_name} nft --file -", stdin: generate_lb_based_nat_rules)
pop "load balancer is updated"
end
label def remove_load_balancer
vm.vm_host.sshable.cmd("sudo ip netns exec #{vm.inhost_name} nft --file -", stdin: generate_nat_rules(vm.ephemeral_net4.to_s, vm.private_ipv4.to_s))
pop "load balancer is removed"
end
def generate_lb_based_nat_rules
active_vm_ports_uniq_by_port = load_balancer.active_vm_ports { |ds| ds.eager(:load_balancer_port, load_balancer_vm: {vm: :nics}) }
.uniq(&:load_balancer_port_id)
.sort_by { |vm_port| vm_port.load_balancer_port.src_port }
public_ipv4 = vm.ephemeral_net4.to_s
public_ipv6 = vm.ephemeral_net6.nth(2).to_s
private_ipv4 = vm.private_ipv4
private_ipv6 = vm.private_ipv6
neighbor_vms = load_balancer.active_vm_ports.reject { it.load_balancer_vm.vm_id == vm.id }.uniq { |row| row.load_balancer_vm.id }.map(&:vm)
neighbor_ips_v4_set, neighbor_ips_v6_set = generate_lb_ip_set_definition(neighbor_vms)
balance_mode_ip4, balance_mode_ip6 = if load_balancer.algorithm == "round_robin"
["numgen inc", "numgen inc"]
elsif load_balancer.algorithm == "hash_based"
["jhash ip saddr . tcp sport . ip daddr . tcp dport", "jhash ip6 saddr . tcp sport . ip6 daddr . tcp dport"]
else
fail ArgumentError, "Unsupported load balancer algorithm: #{load_balancer.algorithm}"
end
ipv4_prerouting = if load_balancer.ipv4_enabled?
active_vm_ports_uniq_by_port.map do |vm_port|
port = vm_port.load_balancer_port
ipv4_map_def = generate_lb_map_defs_ipv4(port)
modulo = ipv4_map_def.count
<<-IPV4_PREROUTING
ip daddr #{public_ipv4} tcp dport #{port.src_port} meta mark set 0x00B1C100D
ip daddr #{public_ipv4} tcp dport #{port.src_port} ct state established,related,new counter dnat to #{balance_mode_ip4} mod #{modulo} map { #{ipv4_map_def.join(", ")} }
ip daddr #{private_ipv4} tcp dport #{port.src_port} ct state established,related,new counter dnat to #{private_ipv4}:#{port.dst_port}
IPV4_PREROUTING
end.join("\n")
end
ipv6_prerouting = if load_balancer.ipv6_enabled?
active_vm_ports_uniq_by_port.map do |vm_port|
port = vm_port.load_balancer_port
ipv6_map_def = generate_lb_map_defs_ipv6(port)
modulo = ipv6_map_def.count
<<-IPV6_PREROUTING
ip6 daddr #{public_ipv6} tcp dport #{port.src_port} meta mark set 0x00B1C100D
ip6 daddr #{public_ipv6} tcp dport #{port.src_port} ct state established,related,new counter dnat to #{balance_mode_ip6} mod #{modulo} map { #{ipv6_map_def.join(", ")} }
ip6 daddr #{private_ipv6} tcp dport #{port.src_port} ct state established,related,new counter dnat to [#{public_ipv6}]:#{port.dst_port}
IPV6_PREROUTING
end.join("\n")
end
sorted_ports = load_balancer.ports.sort_by { |port| port.src_port }
ipv4_postrouting_rule = sorted_ports.map do |port|
if load_balancer.ipv4_enabled?
"ip daddr @neighbor_ips_v4 tcp dport #{port.src_port} ct state established,related,new counter snat to #{private_ipv4}"
end
end.join("\n")
ipv6_postrouting_rule = sorted_ports.map do |port|
if load_balancer.ipv6_enabled?
"ip6 daddr @neighbor_ips_v6 tcp dport #{port.src_port} ct state established,related,new counter snat to #{private_ipv6}"
end
end.join("\n")
<<TEMPLATE
table ip nat;
delete table ip nat;
table inet nat;
delete table inet nat;
table inet nat {
set neighbor_ips_v4 {
type ipv4_addr;
#{neighbor_ips_v4_set}
}
set neighbor_ips_v6 {
type ipv6_addr;
#{neighbor_ips_v6_set}
}
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
#{ipv4_prerouting}
#{ipv6_prerouting}
# Basic NAT for public IPv4 to private IPv4
ip daddr #{public_ipv4} dnat to #{private_ipv4}
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
#{ipv4_postrouting_rule}
#{ipv6_postrouting_rule}
# Basic NAT for private IPv4 to public IPv4
ip saddr #{private_ipv4} ip daddr != { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } snat to #{public_ipv4}
ip saddr #{private_ipv4} ip daddr #{private_ipv4} snat to #{public_ipv4}
}
}
TEMPLATE
end
def generate_lb_ip_set_definition(neighbor_vms)
return ["", ""] if neighbor_vms.empty?
["elements = {#{neighbor_vms.map(&:private_ipv4).join(", ")}}",
"elements = {#{neighbor_vms.map(&:private_ipv6).join(", ")}}"]
end
def generate_lb_map_defs(current_port)
items = load_balancer.active_vm_ports
.select { |vm_port| vm_port.load_balancer_port.dst_port == current_port.dst_port }
.map do |vm_port|
address = yield vm_port
port = (vm_port.load_balancer_vm.vm_id == vm.id) ? vm_port.load_balancer_port.dst_port : vm_port.load_balancer_port.src_port
[address.to_s, port.to_i]
end
items.sort!.map!.with_index do |(address, port), index|
"#{index} : #{address} . #{port}"
end
end
def generate_lb_map_defs_ipv4(current_port)
generate_lb_map_defs(current_port) do |vm_port|
vm_port.load_balancer_vm.vm.private_ipv4
end
end
def generate_lb_map_defs_ipv6(current_port)
generate_lb_map_defs(current_port) do |vm_port|
(vm_port.load_balancer_vm.vm_id == vm.id) ? vm.ephemeral_net6.nth(2) : vm_port.load_balancer_vm.vm.private_ipv6
end
end
def generate_nat_rules(current_public_ipv4, current_private_ipv4)
<<NAT
table ip nat;
delete table ip nat;
table inet nat;
delete table inet nat;
table ip nat {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
ip daddr #{current_public_ipv4} dnat to #{current_private_ipv4}
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr #{current_private_ipv4} ip daddr != { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } snat to #{current_public_ipv4}
ip saddr #{current_private_ipv4} ip daddr #{current_private_ipv4} snat to #{current_public_ipv4}
}
}
NAT
end
end