mirror of
https://github.com/OpenNebula/one.git
synced 2024-12-23 17:33:56 +03:00
F #4109: add onezone serversync command
This commit is contained in:
parent
5ba128ee70
commit
23c6907d89
@ -73,3 +73,4 @@ gem 'activesupport', '~> 4.2' # packethost
|
||||
gem 'i18n', '~> 0.9' # packethost
|
||||
gem 'ffi-rzmq', '~> 2.0.7' # onehem (hooks)
|
||||
gem 'ipaddress', '~> 0.8.3' # sunstone, oneflow
|
||||
gem 'augeas', '~> 0.6' # serversync
|
||||
|
@ -47,7 +47,8 @@ GROUPS={
|
||||
:oca => 'ox',
|
||||
:market => 'aws-sdk',
|
||||
:onedb => "mysql2",
|
||||
:hooks => "zeromq"
|
||||
:hooks => "zeromq",
|
||||
:serversync => "augeas"
|
||||
}
|
||||
|
||||
PACKAGES=GROUPS.keys
|
||||
@ -69,7 +70,8 @@ DISTRIBUTIONS={
|
||||
'xmlparser' => ['gcc', 'libexpat1-dev'],
|
||||
'thin' => ['g++'],
|
||||
'json' => ['gcc'],
|
||||
'zeromq' => ['gcc', 'libzmq5', 'libzmq3-dev']
|
||||
'zeromq' => ['gcc', 'libzmq5', 'libzmq3-dev'],
|
||||
'augeas' => ['gcc', 'libaugeas-dev']
|
||||
},
|
||||
:install_command_interactive => 'apt-get install',
|
||||
:install_command => 'apt-get -y install',
|
||||
@ -89,7 +91,8 @@ DISTRIBUTIONS={
|
||||
'xmlparser' => ['gcc', 'expat-devel'],
|
||||
'thin' => ['gcc-c++'],
|
||||
'json' => ['gcc'],
|
||||
'zeromq' => ['gcc', 'zeromq', 'zeromq-devel']
|
||||
'zeromq' => ['gcc', 'zeromq', 'zeromq-devel'],
|
||||
'augeas' => ['gcc', 'augeas-devel']
|
||||
},
|
||||
:install_command_interactive => 'yum install',
|
||||
:install_command => 'yum -y install',
|
||||
|
@ -14,8 +14,434 @@
|
||||
# limitations under the License. #
|
||||
#--------------------------------------------------------------------------- #
|
||||
|
||||
require 'fileutils'
|
||||
require 'tempfile'
|
||||
require 'CommandManager'
|
||||
|
||||
require 'one_helper'
|
||||
|
||||
# Check differences between files and copy them
|
||||
class Replicator
|
||||
|
||||
SSH_OPTIONS = '-o stricthostkeychecking=no -o passwordauthentication=no'
|
||||
ONE_AUTH = '/var/lib/one/.one/one_auth'
|
||||
FED_ATTRS = %w[MODE ZONE_ID SERVER_ID MASTER_ONED]
|
||||
|
||||
FILES = [
|
||||
{ :name => 'az_driver.conf',
|
||||
:service => 'opennebula' },
|
||||
{ :name => 'az_driver.default',
|
||||
:service => 'opennebula' },
|
||||
{ :name => 'ec2_driver.conf',
|
||||
:service => 'opennebula' },
|
||||
{ :name => 'ec2_driver.default',
|
||||
:service => 'opennebula' },
|
||||
{ :name => 'econe.conf',
|
||||
:service => 'opennebula-econe' },
|
||||
{ :name => 'oneflow-server.conf',
|
||||
:service => 'opennebula-flow' },
|
||||
{ :name => 'onegate-server.conf',
|
||||
:service => 'opennebula-gate' },
|
||||
{ :name => 'sched.conf',
|
||||
:service => 'opennebula' },
|
||||
{ :name => 'sunstone-logos.yaml',
|
||||
:service => 'opennebula-sunstone' },
|
||||
{ :name => 'sunstone-server.conf',
|
||||
:service => 'opennebula-sunstone' },
|
||||
{ :name => 'vcenter_driver.default',
|
||||
:service => 'opennebula' }
|
||||
]
|
||||
|
||||
FOLDERS = [
|
||||
{ :name => 'sunstone-views', :service => 'opennebula-sunstone' },
|
||||
{ :name => 'auth', :service => 'opennebula' },
|
||||
{ :name => 'ec2query_templates', :service => 'opennebula' },
|
||||
{ :name => 'hm', :service => 'opennebula' },
|
||||
{ :name => 'sunstone-views', :service => 'opennebula' },
|
||||
{ :name => 'vmm_exec', :service => 'opennebula' }
|
||||
]
|
||||
|
||||
# Class constructor
|
||||
#
|
||||
# @param ssh_key [String] SSH key file path
|
||||
# @param server [String] OpenNebula server IP address
|
||||
def initialize(ssh_key, server)
|
||||
@oneadmin_identity_file = ssh_key
|
||||
@remote_server = server
|
||||
|
||||
# Get local configuration
|
||||
l_credentials = File.read(ONE_AUTH).gsub("\n", '')
|
||||
l_endpoint = 'http://localhost:2633/RPC2'
|
||||
local_client = Client.new(l_credentials, l_endpoint)
|
||||
|
||||
@l_config = OpenNebula::System.new(local_client).get_configuration
|
||||
@l_config_elements = { :raw => @l_config }
|
||||
@l_fed_elements = { :raw => @l_config }
|
||||
|
||||
fetch_db_config(@l_config_elements)
|
||||
fetch_fed_config(@l_fed_elements)
|
||||
|
||||
# Get remote configuration
|
||||
r_credentials = ssh("cat #{ONE_AUTH}").stdout.gsub("\n", '')
|
||||
r_endpoint = "http://#{server}:2633/RPC2"
|
||||
remote_client = Client.new(r_credentials, r_endpoint)
|
||||
|
||||
@r_config = OpenNebula::System.new(remote_client).get_configuration
|
||||
@r_config_elements = { :raw => @r_config }
|
||||
@r_fed_elements = { :raw => @r_config }
|
||||
|
||||
fetch_db_config(@r_config_elements)
|
||||
fetch_fed_config(@r_fed_elements)
|
||||
|
||||
# Set OpenNebula services to not restart
|
||||
@opennebula_services = { 'opennebula' => false,
|
||||
'opennebula-sunstone' => false,
|
||||
'opennebula-gate' => false,
|
||||
'opennebula-flow' => false,
|
||||
'opennebula-econe' => false }
|
||||
end
|
||||
|
||||
# Process files and folders
|
||||
#
|
||||
# @param sync_db [Boolean] True to sync database
|
||||
def process_files(sync_db)
|
||||
# Files to be copied
|
||||
copy_onedconf
|
||||
|
||||
FILES.each do |file|
|
||||
copy_and_check(file[:name], file[:service])
|
||||
end
|
||||
|
||||
# Folders to be copied
|
||||
FOLDERS.each do |folder|
|
||||
copy_folder(folder[:name], folder[:service])
|
||||
end
|
||||
|
||||
restart_services
|
||||
|
||||
# Sync database
|
||||
sync_db
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Get database configuration
|
||||
#
|
||||
# @param configs [Object] Configuration
|
||||
def fetch_db_config(configs)
|
||||
configs.store(:backend, configs[:raw]['/TEMPLATE/DB/BACKEND'])
|
||||
|
||||
if configs[:backend] == 'mysql'
|
||||
configs.store(:server, configs[:raw]['/TEMPLATE/DB/SERVER'])
|
||||
configs.store(:user, configs[:raw]['/TEMPLATE/DB/USER'])
|
||||
configs.store(:password, configs[:raw]['/TEMPLATE/DB/PASSWD'])
|
||||
configs.store(:dbname, configs[:raw]['/TEMPLATE/DB/DB_NAME'])
|
||||
configs.store(:port, configs[:raw]['/TEMPLATE/DB/PORT'])
|
||||
configs[:port] = '3306' if configs[:port] == '0'
|
||||
else
|
||||
STDERR.puts 'No mysql backend configuration found'
|
||||
exit(-1)
|
||||
end
|
||||
end
|
||||
|
||||
# Get federation configuration
|
||||
#
|
||||
# @param configs [Object] Configuration
|
||||
def fetch_fed_config(configs)
|
||||
configs.store(:server_id,
|
||||
configs[:raw]['/TEMPLATE/FEDERATION/SERVER_ID'])
|
||||
configs.store(:zone_id,
|
||||
configs[:raw]['/TEMPLATE/FEDERATION/ZONE_ID'])
|
||||
end
|
||||
|
||||
# Replaces a file with the version located on a remote server
|
||||
# Only replaces the file if it's different from the remote one
|
||||
#
|
||||
# @param file [String] File to check
|
||||
# @param service [String] Service to restart
|
||||
def copy_and_check(file, service)
|
||||
puts "Checking #{file}"
|
||||
|
||||
temp_file = Tempfile.new("#{file}-temp")
|
||||
|
||||
scp("/etc/one/#{file}", temp_file.path)
|
||||
|
||||
if !FileUtils.compare_file(temp_file, "/etc/one/#{file}")
|
||||
FileUtils.cp(temp_file.path, "/etc/one/#{file}")
|
||||
|
||||
puts "#{file} has been replaced by #{@remote_server}:#{file}"
|
||||
|
||||
@opennebula_services[service] = true
|
||||
end
|
||||
ensure
|
||||
temp_file.unlink
|
||||
end
|
||||
|
||||
# Copy folders
|
||||
#
|
||||
# @param folder [String] Folder to copy
|
||||
# @param service [String] Service to restart
|
||||
def copy_folder(folder, service)
|
||||
puts "Checking #{folder}"
|
||||
|
||||
rc = run_command(
|
||||
"rsync -ai\
|
||||
-e \"ssh #{SSH_OPTIONS} -i #{@oneadmin_identity_file}\" " \
|
||||
"#{@remote_server}:/etc/one/#{folder}/ " \
|
||||
"/etc/one/#{folder}/"
|
||||
)
|
||||
|
||||
unless rc
|
||||
rc = run_command(
|
||||
"rsync -ai\
|
||||
-e \"ssh #{SSH_OPTIONS} -i #{@oneadmin_identity_file}\" " \
|
||||
"oneadmin@#{@remote_server}:/etc/one/#{folder}/ " \
|
||||
"/etc/one/#{folder}/"
|
||||
)
|
||||
end
|
||||
|
||||
unless rc
|
||||
STDERR.puts 'ERROR'
|
||||
STDERR.puts "Fail to sync #{folder}"
|
||||
exit(-1)
|
||||
end
|
||||
|
||||
output = rc.stdout
|
||||
|
||||
return if output.empty?
|
||||
|
||||
puts "Folder #{folder} has been sync with #{@remote_server}:#{folder}"
|
||||
|
||||
@opennebula_services[service] = true
|
||||
end
|
||||
|
||||
# oned.conf file on distributed environments will always be different,
|
||||
# due to the federation section.
|
||||
# Replace oned.conf based on a remote server's version maintaining
|
||||
# the old FEDERATION section
|
||||
def copy_onedconf
|
||||
puts 'Checking oned.conf'
|
||||
|
||||
# Create temporarhy files
|
||||
l_oned = Tempfile.new('l_oned')
|
||||
r_oned = Tempfile.new('r_oned')
|
||||
|
||||
l_oned.close
|
||||
r_oned.close
|
||||
|
||||
# Copy remote and local oned.conf files to temporary files
|
||||
scp('/etc/one/oned.conf', r_oned.path)
|
||||
|
||||
FileUtils.cp('/etc/one/oned.conf', l_oned.path)
|
||||
|
||||
# Create augeas objects to manage oned.conf files
|
||||
l_work_file_dir = File.dirname(l_oned.path)
|
||||
l_work_file_name = File.basename(l_oned.path)
|
||||
|
||||
r_work_file_dir = File.dirname(r_oned.path)
|
||||
r_work_file_name = File.basename(r_oned.path)
|
||||
|
||||
l_aug = Augeas.create(:no_modl_autoload => true,
|
||||
:no_load => true,
|
||||
:root => l_work_file_dir,
|
||||
:loadpath => l_oned.path)
|
||||
|
||||
l_aug.clear_transforms
|
||||
l_aug.transform(:lens => 'Oned.lns', :incl => l_work_file_name)
|
||||
l_aug.context = "/files/#{l_work_file_name}"
|
||||
l_aug.load
|
||||
|
||||
r_aug = Augeas.create(:no_modl_autoload => true,
|
||||
:no_load => true,
|
||||
:root => r_work_file_dir,
|
||||
:loadpath => r_oned.path)
|
||||
|
||||
r_aug.clear_transforms
|
||||
r_aug.transform(:lens => 'Oned.lns', :incl => r_work_file_name)
|
||||
r_aug.context = "/files/#{r_work_file_name}"
|
||||
r_aug.load
|
||||
|
||||
# Get local federation information
|
||||
fed_attrs = []
|
||||
|
||||
FED_ATTRS.each do |attr|
|
||||
fed_attrs << l_aug.get("FEDERATION/#{attr}")
|
||||
end
|
||||
|
||||
# Remove federation section
|
||||
l_aug.rm('FEDERATION')
|
||||
r_aug.rm('FEDERATION')
|
||||
|
||||
# Save augeas files in temporary directories
|
||||
l_aug.save
|
||||
r_aug.save
|
||||
|
||||
return if FileUtils.compare_file(l_oned.path, r_oned.path)
|
||||
|
||||
time_based_identifier = Time.now.to_i
|
||||
|
||||
# backup oned.conf
|
||||
FileUtils.cp('/etc/one/oned.conf',
|
||||
"/etc/one/oned.conf#{time_based_identifier}")
|
||||
|
||||
FED_ATTRS.zip(fed_attrs) do |name, value|
|
||||
r_aug.set("FEDERATION/#{name}", value)
|
||||
end
|
||||
|
||||
r_aug.save
|
||||
|
||||
FileUtils.cp(r_oned.path, '/etc/one/oned.conf')
|
||||
|
||||
puts 'oned.conf has been replaced by ' \
|
||||
"#{@remote_server}:/etc/one/oned.conf"
|
||||
|
||||
puts 'A copy of your old oned.conf file is located here: ' \
|
||||
"/etc/one/oned.conf#{time_based_identifier}"
|
||||
|
||||
@opennebula_services['opennebula'] = true
|
||||
end
|
||||
|
||||
# Sync database
|
||||
def sync_db
|
||||
puts "Dumping and fetching database from #{@remote_server}, " \
|
||||
'this could take a while'
|
||||
|
||||
ssh(
|
||||
"onedb backup -f -u #{@r_config_elements[:user]} " \
|
||||
"-p #{@r_config_elements[:password]} " \
|
||||
"-d #{@r_config_elements[:dbname]} " \
|
||||
"-P #{@r_config_elements[:port]} /tmp/one_db_dump.sql"
|
||||
)
|
||||
|
||||
scp('/tmp/one_db_dump.sql', '/tmp/one_db_dump.sql')
|
||||
|
||||
puts "Local OpenNebula's database will be replaced, hang tight"
|
||||
|
||||
service_action('opennebula', 'stop')
|
||||
|
||||
puts 'Restoring database'
|
||||
|
||||
run_command(
|
||||
"onedb restore -f -u #{@l_config_elements[:user]} " \
|
||||
"-p #{@l_config_elements[:password]} " \
|
||||
"-d #{@l_config_elements[:dbname]} " \
|
||||
"-P #{@l_config_elements[:port]} " \
|
||||
"-h #{@l_config_elements[:server]}" \
|
||||
'/tmp/one_db_dump.sql',
|
||||
true
|
||||
)
|
||||
|
||||
service_action('opennebula', 'start')
|
||||
end
|
||||
|
||||
# Run local command
|
||||
#
|
||||
# @param cmd [String] Command to run
|
||||
# @param print_output [Boolean] True to show output
|
||||
def run_command(cmd, print_output = false)
|
||||
output = LocalCommand.run(cmd)
|
||||
|
||||
if output.code == 0
|
||||
output
|
||||
else
|
||||
return false unless print_output
|
||||
|
||||
STDERR.puts 'ERROR'
|
||||
STDERR.puts "Failed to run: #{cmd}"
|
||||
STDERR.puts output.stderr
|
||||
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Execute SSH command
|
||||
#
|
||||
# @param cmd [String] Command to execute
|
||||
def ssh(cmd)
|
||||
rc = run_command(
|
||||
"ssh -i #{@oneadmin_identity_file} " \
|
||||
"#{SSH_OPTIONS} #{@remote_server} " \
|
||||
"#{cmd}"
|
||||
)
|
||||
|
||||
# if default users doesn't work, try with oneadmin
|
||||
unless rc
|
||||
rc = run_command(
|
||||
"ssh -i #{@oneadmin_identity_file} " \
|
||||
"#{SSH_OPTIONS} oneadmin@#{@remote_server} " \
|
||||
"#{cmd}"
|
||||
)
|
||||
end
|
||||
|
||||
# if oneadmin doesn't work neither, fail
|
||||
unless rc
|
||||
STDERR.puts 'ERROR'
|
||||
STDERR.puts "Couldn't execute command #{cmd} on remote host"
|
||||
exit(-1)
|
||||
end
|
||||
|
||||
rc
|
||||
end
|
||||
|
||||
# Execute SCP command
|
||||
#
|
||||
# @param src [String] Source path
|
||||
# @param dest [String] Destination path
|
||||
def scp(src, dest)
|
||||
rc = run_command(
|
||||
"scp -i #{@oneadmin_identity_file} " \
|
||||
"#{SSH_OPTIONS} #{@remote_server}:/#{src} #{dest}"
|
||||
)
|
||||
|
||||
# if default users doesn't work, try with oneadmin
|
||||
unless rc
|
||||
rc = run_command(
|
||||
"scp -i #{@oneadmin_identity_file} " \
|
||||
"#{SSH_OPTIONS} oneadmin@#{@remote_server}:#{src} #{dest}"
|
||||
)
|
||||
end
|
||||
|
||||
# if oneadmin doesn't work neither, fail
|
||||
unless rc
|
||||
STDERR.puts 'ERROR'
|
||||
STDERR.puts "Couldn't execute command #{cmd} on remote host"
|
||||
exit(-1)
|
||||
end
|
||||
end
|
||||
|
||||
# Restart OpenNebula services
|
||||
def restart_services
|
||||
restarted = false
|
||||
|
||||
@opennebula_services.each do |service, status|
|
||||
next unless status
|
||||
|
||||
service_action(service)
|
||||
|
||||
restarted = true
|
||||
end
|
||||
|
||||
return if restarted
|
||||
|
||||
puts 'Everything seems synchronized, nothing was replaced.'
|
||||
end
|
||||
|
||||
# Service action
|
||||
#
|
||||
# @param service [String] Service to restart
|
||||
# @param action [String] Action to execute (start, stop, restart)
|
||||
def service_action(service, action = 'try-restart')
|
||||
if `file /sbin/init`.include? 'systemd'
|
||||
puts "#{action}ing #{service} via systemd"
|
||||
run_command("systemctl #{action} #{service}", true)
|
||||
else
|
||||
puts "#{action}ing #{service} via init"
|
||||
run_command("service #{service} #{action}", true)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class OneZoneHelper < OpenNebulaHelper::OneHelper
|
||||
|
||||
SERVER_NAME={
|
||||
|
@ -51,6 +51,12 @@ CommandParser::CmdParser.new(ARGV) do
|
||||
:format => String
|
||||
}
|
||||
|
||||
DATABASE = {
|
||||
:name => 'database',
|
||||
:large => '--db',
|
||||
:description => 'Also sync database'
|
||||
}
|
||||
|
||||
before_proc do
|
||||
helper.set_client(options)
|
||||
end
|
||||
@ -215,4 +221,33 @@ CommandParser::CmdParser.new(ARGV) do
|
||||
helper.set_zone(args[0], false)
|
||||
end
|
||||
end
|
||||
|
||||
sync_desc = <<-EOT.unindent
|
||||
Syncs configuration files and folders from another server
|
||||
|
||||
This command must be executed under root
|
||||
EOT
|
||||
|
||||
command :serversync, sync_desc, :server, :options => [DATABASE] do
|
||||
begin
|
||||
gem 'augeas', '~> 0.6'
|
||||
require 'augeas'
|
||||
rescue Gem::LoadError
|
||||
STDERR.puts(
|
||||
'Augeas gem is not installed, run `gem install ' \
|
||||
'augeas -v \'0.6\'` to install it'
|
||||
)
|
||||
exit(-1)
|
||||
end
|
||||
|
||||
if !Process.uid.zero? || !Process.gid.zero?
|
||||
STDERR.puts("'onezone serversync' must be run under root")
|
||||
exit(-1)
|
||||
end
|
||||
|
||||
server = Replicator.new('/var/lib/one/.ssh/id_rsa', args[0])
|
||||
server.process_files(options.key?(:database))
|
||||
|
||||
0
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user