1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-10 01:17:40 +03:00
one/share/start-scripts/map_vnets_start_script
Ricardo Diaz e239bfc0d6
M #-: Redesign of map vnets mapping script (#4461)
New design of vnets mapping script based on 'open3' instead of directly
command execution.
Added option to select which private and public networks are going to be
used to perform the mapping.

Signed-off-by: Ricardo Diaz <rdiaz@opennebula.systems>
2020-04-01 13:31:49 +02:00

205 lines
6.6 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# Copyright 2002-2020, OpenNebula Project, OpenNebula Systems #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'getoptlong'
require 'ipaddr'
require 'json'
require 'logger'
require 'open3'
require 'tempfile'
require 'yaml'
CHAINS = {
'PREROUTING' => 'chain-onevnetmap-dnat',
'POSTROUTING' => 'chain-onevnetmap-snat'
}
# Read the configuration file
def read_configuration_file(cfile)
begin
YAML.safe_load(File.read(cfile))
rescue StandardError => e
puts "Error: #{e}"
exit(-1)
end
end
# Get a list of NIC/NIC_ALIAS hashes as long as NIC_ALIAS belongs to
# public network and NIC belongs to private network
def do_ip_mapping(service, private_ip = '0.0.0.0/0', public_ip = '0.0.0.0/0')
private_network = IPAddr.new private_ip
public_network = IPAddr.new public_ip
retval = []
roles = service['SERVICE']['roles'].flatten
roles.each do |role|
next unless role['nodes']
role['nodes'].each do |node|
nic_aliases =
[node['vm_info']['VM']['TEMPLATE']['NIC_ALIAS']].flatten
next unless nic_aliases.any?
nics = [node['vm_info']['VM']['TEMPLATE']['NIC']].flatten
nic_aliases.each do |nic_alias|
next unless public_network.include?(nic_alias['IP'])
nic = nics.detect {|n| n['NAME'] == nic_alias['PARENT'] }
next unless private_network.include?(nic['IP'])
retval << { 'NIC' => nic['IP'], 'NIC_ALIAS' => nic_alias['IP'] }
end
end
end
retval
end
# Get iptables rules to apply for NAT table if no NIC/NIC_ALIAS detected
def iptables_tnat_apply_init
rules = ''
CHAINS.each do |nat_chain, custom_chain|
o, _, rc = Open3.capture3("iptables -tnat -S #{custom_chain}")
if rc.exitstatus != 0
# The chain does not exist, add rules to create it and add it to the
# corresponding table NAT chain
rules += "-N #{custom_chain}\n"
rules += "-A #{nat_chain} -j #{custom_chain}\n"
else
o.each_line do |r|
next if r.include?("-N #{custom_chain}")
# The chain does exist, add all rules belonging to the chain and
# mark them to be deleted initially
rules += r.gsub(/-A (.*)/, '-D \1')
end
end
end
rules
end
# Merge intial iptables rules to apply for NAT with the ones needed by
# NIC/NIC_ALIAS mapping
def iptables_tnat_apply_merge(rules, nics_maps)
nics_maps.each do |nat|
# DNAT rule
jdnat = "#{CHAINS['PREROUTING']} -d #{nat['NIC_ALIAS']}/32 -j DNAT \
--to-destination #{nat['NIC']}"
# Try to delete -D DNAT rule which means previously NIC_ALIAS still
# attached
if !rules.gsub!(/-D #{jdnat}\n/, '')
# Add -A rule if not DNAT rule found which means new NIC_ALIAS
# has been attached
rules += "-A #{jdnat}\n"
end
# DNAT rule
jsnat = "#{CHAINS['POSTROUTING']} -s #{nat['NIC']}/32 -j SNAT \
--to-source #{nat['NIC_ALIAS']}"
# Try to delete -D SNAT rule which means previously NIC_ALIAS still
# attached
next if rules.gsub!(/-D #{jsnat}\n/, '')
# Add -A rule if not SNAT rule found which means new NIC_ALIAS
# has been attached
rules += "-A #{jsnat}\n"
end
rules
end
def usage
puts <<~EOT
#{File.basename($PROGRAM_NAME)} [-f|--configuration-file CONFIG_FILE] [-h|--help]
-f, --configuration-file=CONFIG_FILE use CONFIG_FILE in YAML format like this:
networks:
public: IP/MASK
private: IP/MASK
Only NIC_ALIASes and NICs belonging to
public and private networks respectively
will be used.
EOT
STDOUT.flush
end
# Parse arguments
opts = GetoptLong.new(
['--configuration-file', '-f', GetoptLong::REQUIRED_ARGUMENT],
['--help', '-h', GetoptLong::NO_ARGUMENT]
)
# Configuration file path
cfile = ''
begin
opts.each do |opt, arg|
case opt
when '--configuration-file'
cfile = arg
when '--help'
usage
exit(0)
end
end
rescue StandardError
exit(-1)
end
# Read configuration
config = read_configuration_file(cfile) unless cfile.empty?
# Get service info through OneGate
o, e, rc = Open3.capture3('onegate service show --json --extended')
if rc.exitstatus != 0
STDERR.puts "Error: #{o}"
exit(-1)
end
service = JSON.parse(o)
# Get IP network addresses of private and public networks
# If no network configuration passed each NIC_ALIAS found will be mapped to the
# parent NIC
private_ip = '0.0.0.0/0'
public_ip = '0.0.0.0/0'
if !config.nil?
private_ip = config['networks']['private']
public_ip = config['networks']['public']
end
# Perform the NIC_ALIAS/NIC mapping
nics_maps = do_ip_mapping(service, private_ip, public_ip)
# Get initial iptables rules required as if there no NIC_ALIAS/NIC mappings
rules_pre = iptables_tnat_apply_init
# Modify initial iptables rules with NIC_ALIAS/NIC mappings
rules_post = iptables_tnat_apply_merge(rules_pre, nics_maps)
# Apply the inferred iptables rules
rules_post.each_line do |rule|
o, e, rc = Open3.capture3("iptables -tnat #{rule}")
if rc.exitstatus != 0
STDERR.puts e
end
end