opennebula-node-kvm deploy

This commit is contained in:
santic-zombie
2024-07-07 18:33:52 +03:00
parent 533b86cbbe
commit edc73d3159
13 changed files with 773 additions and 61 deletions

View File

@@ -0,0 +1,120 @@
#!/bin/bash
# -------------------------------------------------------------------------- #
# Copyright 2002-2023, 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. #
#--------------------------------------------------------------------------- #
if [ -z "$ONE_LOCATION" ]; then
ONEGATE_PROXY_PID=/var/run/one/onegate-proxy.pid
ONEGATE_PROXY_SERVER=/usr/lib/one/onegate-proxy/onegate-proxy.rb
ONEGATE_PROXY_LOCK_FILE=/var/lock/one/.onegate-proxy.lock
ONEGATE_PROXY_LOG=/var/log/one/onegate-proxy.log
ONEGATE_PROXY_LOG_ERROR=/var/log/one/onegate-proxy.error
else
ONEGATE_PROXY_PID=$ONE_LOCATION/var/onegate-proxy.pid
ONEGATE_PROXY_SERVER=$ONE_LOCATION/lib/onegate-proxy/onegate-proxy.rb
ONEGATE_PROXY_LOCK_FILE=$ONE_LOCATION/var/.onegate-proxy.lock
ONEGATE_PROXY_LOG=$ONE_LOCATION/var/onegate-proxy.log
ONEGATE_PROXY_LOG_ERROR=$ONE_LOCATION/var/onegate-proxy.error
fi
setup()
{
if [ -f $ONEGATE_PROXY_LOCK_FILE ]; then
if [ -f $ONEGATE_PROXY_PID ]; then
ONEGATEPID=`cat $ONEGATE_PROXY_PID`
ps $ONEGATEPID &> /dev/null
if [ $? -eq 0 ]; then
echo -n "OneGate Server is still running (PID:$ONEGATEPID). Please "
echo "try 'onegate-proxy stop' first."
exit 1
fi
fi
echo "Stale .lock detected. Erasing it."
rm $ONEGATE_PROXY_LOCK_FILE
fi
}
start()
{
if [ ! -f "$ONEGATE_PROXY_SERVER" ]; then
echo "Cannot find $ONEGATE_PROXY_SERVER."
exit 1
fi
# Start the onegate daemon
touch $ONEGATE_PROXY_LOCK_FILE
ruby $ONEGATE_PROXY_SERVER > $ONEGATE_PROXY_LOG 2>$ONEGATE_PROXY_LOG_ERROR &
LASTPID=$!
if [ $? -ne 0 ]; then
echo "Error executing onegate-proxy."
echo "Check $ONEGATE_PROXY_LOG_ERROR and $ONEGATE_PROXY_LOG for more information"
exit 1
else
echo $LASTPID > $ONEGATE_PROXY_PID
fi
sleep 1
ps $LASTPID &> /dev/null
if [ $? -ne 0 ]; then
echo "Error executing onegate-proxy."
echo "Check $ONEGATE_PROXY_LOG_ERROR and $ONEGATE_PROXY_LOG for more information"
exit 1
fi
echo "onegate-proxy started"
}
#
# Function that stops the daemon/service
#
stop()
{
if [ ! -f $ONEGATE_PROXY_PID ]; then
echo "Couldn't find onegate-proxy process pid."
exit 1
fi
# Kill the onegate daemon
kill -INT `cat $ONEGATE_PROXY_PID` &> /dev/null
# Remove pid files
rm -f $ONEGATE_PROXY_LOCK_FILE &> /dev/null
rm -f $ONEGATE_PROXY_PID &> /dev/null
echo "onegate-proxy stopped"
}
case "$1" in
start)
setup
start
;;
stop)
stop
;;
restart)
stop
setup
start
;;
*)
echo "Usage: onegate-proxy {start|stop|restart}" >&2
exit 3
;;
esac

View File

@@ -0,0 +1,120 @@
#!/bin/sh
# -------------------------------------------------------------------------- #
# Copyright 2002-2023, 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. #
#--------------------------------------------------------------------------- #
set -e
CMD=$(basename ${0})
# file path to the new qemu-kvm symlink
QEMU_ONE="/usr/bin/qemu-kvm-one"
#
# functions
#
print_usage()
{
cat <<EOF
NAME:
${CMD} - System agnostic QEMU/KVM symlink generator
It will try to find a qemu-kvm binary in the system from a list of known
paths and if successful - it will create a proper symlink:
'${QEMU_ONE}'
USAGE:
${CMD} [-f|--force]
Find the system QEMU binary and create the symlink
-f|--force: This option will overwrite existing symlink or file
${CMD} -h|--help
Print this help
EOF
}
#
# main
#
FORCE_CREATE=
case "$1" in
'')
:
;;
-h|--help)
print_usage
exit 0
;;
-f|--force)
FORCE_CREATE=yes
;;
*)
echo "ERROR: ${CMD}: Unknown option '${1}' !" >&2
print_usage >&2
exit 1
;;
esac
# find cpu arch or default to x86_64
if command -v arch >/dev/null 2>&1 ; then
ARCH=$(arch)
else
ARCH="x86_64"
fi
# verify that symlink is not already created
if [ -L "${QEMU_ONE}" ] ; then
# symlink already exists
qemu_target=$(readlink "${QEMU_ONE}")
if [ -e "${qemu_target}" ] && [ -z "${FORCE_CREATE}" ] ; then
# symlink is valid
exit 0
fi
elif [ -e "${QEMU_ONE}" ] ; then
# there is a file of the same name and it is not a symlink
if [ -z "${FORCE_CREATE}" ] ; then
echo "ERROR: ${CMD}: File '${QEMU_ONE}' already exists but it is not a symlink !" >&2
exit 1
else
# --force is used
rm -f "${QEMU_ONE}"
fi
fi
# search the known paths for qemu binary
#
# NOTE: you can add new supported paths here in the future
for QEMU_BIN in \
/usr/libexec/qemu-kvm \
/usr/bin/qemu-kvm \
/usr/bin/qemu-system-${ARCH} \
;
do
if [ -e "${QEMU_BIN}" ] ; then
ln -s ${FORCE_CREATE:+-f} "${QEMU_BIN}" "${QEMU_ONE}"
exit 0
fi
done
# no qemu binary found -> we signal error and exit
echo "ERROR: ${CMD}: No qemu kvm binary found !" >&2
exit 1

View File

@@ -0,0 +1,309 @@
#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# Copyright 2002-2023, 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. #
#--------------------------------------------------------------------------- #
# frozen_string_literal: true
# rubocop:disable Lint/MissingCopEnableDirective
# rubocop:disable Lint/RedundantRequireStatement
# rubocop:disable Layout/FirstHashElementIndentation
# rubocop:disable Layout/HashAlignment
# rubocop:disable Layout/HeredocIndentation
# rubocop:disable Layout/IndentationWidth
# rubocop:disable Style/HashSyntax
# rubocop:disable Style/ParallelAssignment
ONE_LOCATION = ENV['ONE_LOCATION']
if !ONE_LOCATION
RUBY_LIB_LOCATION = '/usr/lib/one/ruby'
GEMS_LOCATION = '/usr/share/one/gems'
ETC_LOCATION = '/etc/one'
REMOTES_LOCATION = '/var/tmp/one'
else
RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby'
GEMS_LOCATION = ONE_LOCATION + '/share/gems'
ETC_LOCATION = ONE_LOCATION + '/etc'
REMOTES_LOCATION = ONE_LOCATION + '/var/remotes'
end
CONFIGURATION_FILE = REMOTES_LOCATION + '/etc/onegate-proxy.conf'
# %%RUBYGEMS_SETUP_BEGIN%%
if File.directory?(GEMS_LOCATION)
real_gems_path = File.realpath(GEMS_LOCATION)
if !defined?(Gem) || Gem.path != [real_gems_path]
$LOAD_PATH.reject! {|p| p =~ /vendor_ruby/ }
# Suppress warnings from Rubygems
# https://github.com/OpenNebula/one/issues/5379
begin
verb = $VERBOSE
$VERBOSE = nil
require 'rubygems'
Gem.use_paths(real_gems_path)
ensure
$VERBOSE = verb
end
end
end
# %%RUBYGEMS_SETUP_END%%
$LOAD_PATH << RUBY_LIB_LOCATION
require 'async/io'
require 'async/io/stream'
require 'async/io/trap'
require 'etc'
require 'pp'
require 'rb-inotify'
require 'socket'
require 'yaml'
$stdout.sync = true
$stderr.sync = true
DEFAULT_OPTIONS = {
:debug_level => 2, # 0 = ERROR, 1 = WARNING, 2 = INFO, 3 = DEBUG
:process_owner => 'oneadmin',
:onegate_addr => '127.0.0.1',
:onegate_port => '5030',
:service_addr => '169.254.16.9'
}.freeze
# Proxy-class for converting log levels between OpenNebula and
# the socketry/console library. It also splits specific log levels
# into separate stdout and stderr loggers.
class Logger
LOG_LEVEL_MAP = {
0 => '3', # ERROR
1 => '2', # WARN
2 => '1', # INFO
3 => '0' # DEBUG
}.freeze
def initialize(log_level = 2)
@out = Console::Logger.default_logger $stdout, {
'CONSOLE_LEVEL' => LOG_LEVEL_MAP[log_level]
}
@err = Console::Logger.default_logger $stderr, {
'CONSOLE_LEVEL' => LOG_LEVEL_MAP[log_level]
}
end
# rubocop:disable Style/ArgumentsForwarding
def error(*args, &block)
@err.error(*args, &block)
end
def warn(*args, &block)
@err.warn(*args, &block)
end
def info(*args, &block)
@out.info(*args, &block)
end
def debug(*args, &block)
@err.debug(*args, &block)
end
# rubocop:enable Style/ArgumentsForwarding
end
# Class that implements a classic two-way TCP socket proxy (async).
class OneGateProxy
def initialize(options = {})
@options = DEFAULT_OPTIONS.dup.merge! options
@options.each {|k, v| instance_variable_set("@#{k}", v) }
@logger = Logger.new options[:debug_level]
@sigint = Async::IO::Trap.new :INT
@sigint.install!
@inotify = setup_inotify
@inotify_io = Async::IO::Generic.new @inotify.to_io
@proxy_ep = Async::IO::Endpoint.socket setup_socket
end
def run
# NOTE: At this point all config should be set in stone,
# we can drop root privileges..
drop_privileges
Async do |task|
# Make CTRL-C work..
task.async do
@sigint.wait { exit 0 }
end
# Handle filesystem notifications..
task.async do
@inotify.process while @inotify_io.wait_readable
end
glue_peers task
end
end
private
def drop_privileges
new_gid, new_uid = Etc.getpwnam(@process_owner).gid,
Etc.getpwnam(@process_owner).uid
@logger.info(self) do
"Drop root privileges -> #{@process_owner}"
end
Process::Sys.setgid new_gid
Process::Sys.setuid new_uid
end
def setup_inotify
inotify = INotify::Notifier.new
inotify.watch(CONFIGURATION_FILE, :modify) do
@logger.info(self) do
"#{CONFIGURATION_FILE} has been just updated, exiting.."
end
# We assume here that the service will be restarted by
# the service manager.
exit 0
end
inotify
rescue Errno::ENOENT => e
@logger.error(self) do
e.message
end
# We assume here that the service will be restarted by
# the service manager.
exit e.class::Errno
end
def setup_service_addr
# NOTE: We need the service_addr to be defined on one of the interfaces
# inside the host, one natural choice is the loopback interface (lo).
# Effectively we set it once, subsequent restarts of the service should
# honor the idempotence.
ip_address_add_cmd = lambda do |cidr_host, nic_device|
check = "[ -n \"$(ip a s to '#{cidr_host}' dev '#{nic_device}')\" ]"
apply = "ip a a '#{cidr_host}' dev '#{nic_device}'"
"#{check.strip} >/dev/null 2>&1 || #{apply.strip}"
end
system ip_address_add_cmd.call "#{@service_addr}/32", 'lo'
end
def setup_socket(listen = Socket::SOMAXCONN)
# NOTE: Must be executed before calling bind(), otherwise it fails..
setup_service_addr
sock = Socket.new Socket::AF_INET, Socket::SOCK_STREAM, 0
sock.setsockopt Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1
@logger.info(self) do
"Bind #{Addrinfo.tcp(@service_addr, @onegate_port).inspect}"
end
sock.bind Socket.pack_sockaddr_in(@onegate_port, @service_addr)
sock.listen listen
sock
end
def glue_streams(stream1, stream2, task)
Async do
concurrent = []
concurrent << task.async do
while (chunk = stream1.read_partial)
stream2.write chunk
stream2.flush
end
end
concurrent << task.async do
while (chunk = stream2.read_partial)
stream1.write chunk
stream1.flush
end
end
concurrent.each(&:wait)
end
end
def glue_peers(task)
@proxy_ep.accept do |vm_peer|
@logger.debug(self) do
"Accept #{vm_peer.remote_address.inspect}"
end
begin
gate_ep = Async::IO::Endpoint.tcp @onegate_addr,
@onegate_port
gate_ep.connect do |gate_peer|
vm_stream, gate_stream = Async::IO::Stream.new(vm_peer),
Async::IO::Stream.new(gate_peer)
glue_streams(vm_stream, gate_stream, task).wait
@logger.debug(self) do
"Close #{gate_peer.remote_address.inspect}"
end
gate_peer.close
end
rescue Errno::ECONNREFUSED,
Errno::ECONNRESET,
Errno::EHOSTUNREACH,
Errno::ETIMEDOUT => e
@logger.error(self) do
e.message
end
end
@logger.debug(self) do
"Close #{vm_peer.remote_address.inspect}"
end
vm_peer.close
end
end
end
if caller.empty?
options = DEFAULT_OPTIONS.dup
# NOTE: The "CONFIGURATION_FILE" is updated during the host sync procedure.
begin
options.merge! YAML.load_file(CONFIGURATION_FILE)
rescue StandardError => e
warn "Error parsing config file #{CONFIGURATION_FILE}: #{e.message}"
exit 1
end
puts <<~HEADER
--------------------------------------
Proxy configuration
--------------------------------------
#{options.pretty_inspect.strip}
--------------------------------------
HEADER
service = OneGateProxy.new options
service.run
end