Skip to content

Commit

Permalink
F OpenNebula/one#6280: Add the VRouter service
Browse files Browse the repository at this point in the history
This is a refactor of the previously used VNF service (to be obsolete).

All the main features have been included:

- DHCP4 server (kea + onelease)
- DNS forwarder (unbound)
- Router4
- NAT4
- SDNAT4
- LVS (keepalived's built-in, static + onegate)
- HAProxy (static + onegate)
- Keepalived with failover capability (via one-failover service)

Also works:

- It's *mostly* compatible with the VNF's interface.
- Each feature is presented as a simple OpenRC service.
- Failover is now implemented via unix pipes instead of script hooks.
- Load-balancers (LVS and HAProxy) work now in both VROUTER and OneFlow
  modes.
- Re-contextualization is fully supported.
- Lots of unit tests (rspec).
  • Loading branch information
sk4zuzu committed Dec 10, 2023
1 parent aabc942 commit e59ac9b
Show file tree
Hide file tree
Showing 38 changed files with 5,345 additions and 1 deletion.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ packer-service_vnf: packer-alpine318 ${DIR_EXPORT}/service_vnf.qcow2
packer-service_wordpress: packer-alma8 ${DIR_EXPORT}/service_wordpress.qcow2
@${INFO} "Packer service_wordpress done"

packer-service_VRouter: packer-alpine318 ${DIR_EXPORT}/service_VRouter.qcow2
@${INFO} "Packer service_VRouter done"

packer-service_OneKE: packer-ubuntu2204 ${DIR_EXPORT}/service_OneKE.qcow2
@${INFO} "Packer service_OneKE done"

Expand Down
2 changes: 1 addition & 1 deletion Makefile.config
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ DISTROS := alma8 alma9 \
rocky8 rocky9 \
ubuntu2004 ubuntu2004min ubuntu2204 ubuntu2204min

SERVICES := service_vnf service_wordpress service_OneKE
SERVICES := service_vnf service_wordpress service_VRouter service_OneKE

.DEFAULT_GOAL := help

Expand Down
Binary file not shown.
180 changes: 180 additions & 0 deletions appliances/VRouter/DHCP4/main.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# frozen_string_literal: true

require_relative '../vrouter.rb'

module Service
module DHCP4
extend self

DEPENDS_ON = %w[Service::Failover]

ONEAPP_VNF_DHCP4_ENABLED = env :ONEAPP_VNF_DHCP4_ENABLED, 'NO'

ONEAPP_VNF_DHCP4_AUTHORITATIVE = env :ONEAPP_VNF_DHCP4_AUTHORITATIVE, 'YES'

ONEAPP_VNF_DHCP4_MAC2IP_ENABLED = env :ONEAPP_VNF_DHCP4_MAC2IP_ENABLED, 'YES'
ONEAPP_VNF_DHCP4_MAC2IP_MACPREFIX = env :ONEAPP_VNF_DHCP4_MAC2IP_MACPREFIX, '02:00'

ONEAPP_VNF_DHCP4_LEASE_TIME = env :ONEAPP_VNF_DHCP4_LEASE_TIME, '3600'

ONEAPP_VNF_DHCP4_GATEWAY = env :ONEAPP_VNF_DHCP4_GATEWAY, nil
ONEAPP_VNF_DHCP4_DNS = env :ONEAPP_VNF_DHCP4_DNS, nil

ONEAPP_VNF_DHCP4_INTERFACES = env :ONEAPP_VNF_DHCP4_INTERFACES, '' # nil -> none, empty -> all

attr_reader :interfaces, :mgmt

@interfaces = parse_interfaces ONEAPP_VNF_DHCP4_INTERFACES
@mgmt = detect_mgmt_interfaces

def parse_env
interfaces = @interfaces.keys - @mgmt

n2a = addrs_to_nics(interfaces, family: %w[inet]).to_h do |a, n|
[n.first, a]
end

a2s = addrs_to_subnets(interfaces, family: %w[inet]).to_h do |a, s|
[a.split('/').first, s]
end

s2r = subnets_to_ranges(a2s.values)

interfaces.each_with_object({}) do |nic, vars|
p = env("ONEAPP_VNF_DHCP4_#{nic.upcase}", nil)&.split(':')&.map(&:strip)

vars[nic] = {
address: n2a[nic],
subnet: if p.nil? then a2s[n2a[nic]] else p[0] end,
range: if p.nil? then s2r[a2s[n2a[nic]]] else p[1] end,
gateway: env("ONEAPP_VNF_DHCP4_#{nic.upcase}_GATEWAY", ONEAPP_VNF_DHCP4_GATEWAY),
dns: env("ONEAPP_VNF_DHCP4_#{nic.upcase}_DNS", ONEAPP_VNF_DHCP4_DNS),
mtu: env("ONEAPP_VNF_DHCP4_#{nic.upcase}_MTU", ip_link_show(nic)['mtu']),
}
end
end

def install(initdir: '/etc/init.d')
msg :info, 'DHCP4::install'

onelease4_apk = File.join File.dirname(__FILE__), 'kea-hook-onelease4-1.1.1-r0.apk'

puts bash <<~SCRIPT
apk --no-cache add ruby kea-dhcp4
apk --no-cache --allow-untrusted add '#{onelease4_apk}'
SCRIPT

file "#{initdir}/one-dhcp4", <<~SERVICE, mode: 'u=rwx,g=rx,o='
#!/sbin/openrc-run
source /run/one-context/one_env
command="/usr/bin/ruby"
command_args="-r /etc/one-appliance/lib/helpers.rb -r #{__FILE__}"
output_log="/var/log/one-appliance/one-dhcp4.log"
error_log="/var/log/one-appliance/one-dhcp4.log"
depend() {
after net firewall keepalived
}
start_pre() {
rc-service kea-dhcp4 start --nodeps
}
start() { :; }
stop() { :; }
stop_post() {
rc-service kea-dhcp4 stop --nodeps
}
SERVICE

toggle [:update]
end

def configure(basedir: '/etc/kea')
msg :info, 'DHCP4::configure'

if ONEAPP_VNF_DHCP4_ENABLED
dhcp4_vars = parse_env

config = { 'Dhcp4' => {
'interfaces-config' => { 'interfaces' => dhcp4_vars.keys },
'authoritative' => ONEAPP_VNF_DHCP4_AUTHORITATIVE,
'option-data' => [],
'subnet4' => dhcp4_vars.map do |nic, vars|
data = []
data << { 'name' => 'routers', 'data' => vars[:gateway] } unless vars[:gateway].nil?
data << { 'name' => 'domain-name-servers', 'data' => vars[:dns] } unless vars[:dns].nil?
data << { 'name' => 'interface-mtu', 'data' => vars[:mtu].to_s } unless vars[:mtu].nil? || nic == 'lo'
{ 'subnet' => vars[:subnet],
'pools' => [ { 'pool' => vars[:range] } ],
'option-data' => data,
'reservations' => [
{ 'flex-id' => "'DO-NOT-LEASE-#{vars[:address]}'",
'ip-address' => vars[:address] } ],
'reservation-mode' => 'all' }
end,
'lease-database' => {
'type' => 'memfile',
'persist' => true,
'lfc-interval' => 2 * ONEAPP_VNF_DHCP4_LEASE_TIME.to_i
},
'sanity-checks' => { 'lease-checks' => 'fix-del' },
'valid-lifetime' => ONEAPP_VNF_DHCP4_LEASE_TIME.to_i,
'calculate-tee-times' => true,
'loggers' => [
{ 'name' => 'kea-dhcp4',
'output_options' => [ { 'output' => '/var/log/kea/kea-dhcp4.log' } ],
'severity' => 'INFO',
'debuglevel' => 0 }
],
'hooks-libraries' => if ONEAPP_VNF_DHCP4_MAC2IP_ENABLED then
[ { 'library' => '/usr/lib/kea/hooks/libkea-onelease-dhcp4.so',
'parameters' => {
'enabled' => true,
'byte-prefix' => ONEAPP_VNF_DHCP4_MAC2IP_MACPREFIX,
'logger-name' => 'onelease-dhcp4',
'debug' => false,
'debug-logfile' => '/var/log/kea/onelease-dhcp4-debug.log' } } ]
else [] end
} }

file "#{basedir}/kea-dhcp4.conf", JSON.pretty_generate(config), owner: 'kea',
group: 'kea',
mode: 'u=rw,g=r,o=',
overwrite: true
toggle [:enable]
else
toggle [:stop, :disable]
end
end

def toggle(operations)
operations.each do |op|
msg :debug, "DHCP4::toggle([:#{op}])"
case op
when :reload
puts bash 'rc-service kea-dhcp4 reload'
when :enable
puts bash 'rc-update add kea-dhcp4 default'
puts bash 'rc-update add one-dhcp4 default'
when :disable
puts bash 'rc-update del kea-dhcp4 default ||:'
puts bash 'rc-update del one-dhcp4 default ||:'
when :update
puts bash 'rc-update -u'
else
puts bash "rc-service one-dhcp4 #{op.to_s}"
end
end
end

def bootstrap
msg :info, 'DHCP4::bootstrap'
end
end
end
105 changes: 105 additions & 0 deletions appliances/VRouter/DHCP4/tests.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# frozen_string_literal: true

require 'rspec'

def clear_env
ENV.delete_if { |name| name.include?('VNF') || name.include?('VROUTER') }
end

RSpec.describe self do
it 'should provide and parse all env vars' do
clear_env

ENV['ONEAPP_VNF_DHCP4_ENABLED'] = 'YES'
ENV['ONEAPP_VNF_DHCP4_AUTHORITATIVE'] = 'YES'

ENV['ONEAPP_VNF_DHCP4_MAC2IP_ENABLED'] = 'YES'
ENV['ONEAPP_VNF_DHCP4_MAC2IP_MACPREFIX'] = '02:00'

ENV['ONEAPP_VNF_DHCP4_LEASE_TIME'] = '3600'

ENV['ONEAPP_VNF_DHCP4_GATEWAY'] = '1.2.3.4'
ENV['ONEAPP_VNF_DHCP4_DNS'] = '1.1.1.1'

ENV['ONEAPP_VNF_DHCP4_INTERFACES'] = 'lo/127.0.0.1 eth0 eth1 eth2 eth3'
ENV['ETH0_VROUTER_MANAGEMENT'] = 'YES'

ENV['ONEAPP_VNF_DHCP4_ETH2'] = '30.0.0.0/8:30.40.50.64-30.40.50.68'
ENV['ONEAPP_VNF_DHCP4_ETH2_GATEWAY'] = '30.40.50.1'
ENV['ONEAPP_VNF_DHCP4_ETH2_DNS'] = '8.8.8.8'

ENV['ONEAPP_VNF_DHCP4_ETH3_GATEWAY'] = '40.50.60.1'
ENV['ONEAPP_VNF_DHCP4_ETH3_DNS'] = '8.8.4.4'

load './main.rb'; include Service::DHCP4

allow(Service::DHCP4).to receive(:ip_addr_list).and_return([
{ 'ifname' => 'lo',
'addr_info' => [ { 'family' => 'inet',
'local' => '127.0.0.1',
'prefixlen' => 8 } ] },

{ 'ifname' => 'eth0',
'addr_info' => [ { 'family' => 'inet',
'local' => '10.20.30.40',
'prefixlen' => 24 } ] },

{ 'ifname' => 'eth1',
'addr_info' => [ { 'family' => 'inet',
'local' => '20.30.40.50',
'prefixlen' => 16 } ] },

{ 'ifname' => 'eth2',
'addr_info' => [ { 'family' => 'inet',
'local' => '30.40.50.60',
'prefixlen' => 8 } ] },

{ 'ifname' => 'eth3',
'addr_info' => [ { 'family' => 'inet',
'local' => '40.50.60.70',
'prefixlen' => 24 } ] },
])

allow(Service::DHCP4).to receive(:ip_link_show).and_return(
{ 'mtu' => 1111 },
{ 'mtu' => 2222 },
{ 'mtu' => 3333 },
{ 'mtu' => 4444 },
)

expect(Service::DHCP4.parse_env).to eq ({
'lo' => {
address: '127.0.0.1',
dns: '1.1.1.1',
gateway: '1.2.3.4',
mtu: 1111,
range: '127.0.0.2-127.255.255.254',
subnet: '127.0.0.0/8',
},
'eth1' => {
address: '20.30.40.50',
dns: '1.1.1.1',
gateway: '1.2.3.4',
mtu: 2222,
range: '20.30.0.2-20.30.255.254',
subnet: '20.30.0.0/16',
},
'eth2' => {
address: '30.40.50.60',
dns: '8.8.8.8',
gateway: '30.40.50.1',
mtu: 3333,
range: '30.40.50.64-30.40.50.68',
subnet: '30.0.0.0/8',
},
'eth3' => {
address: '40.50.60.70',
dns: '8.8.4.4',
gateway: '40.50.60.1',
mtu: 4444,
range: '40.50.60.2-40.50.60.254',
subnet: '40.50.60.0/24',
},
})
end
end
Loading

0 comments on commit e59ac9b

Please sign in to comment.