class Replicator

Check differences between files and copy them

Constants

FED_ATTRS
FILES
FOLDERS
ONE_AUTH
SSH_OPTIONS

Public Class Methods

new(ssh_key, server) click to toggle source

Class constructor

@param ssh_key [String] SSH key file path @param server [String] OpenNebula server IP address

# File lib/one_helper/onezone_helper.rb, line 67
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 }

    if OpenNebula.is_error?(@l_config)
        STDERR.puts 'Unable to read OpenNebula configuration. ' \
                    'Is OpenNebula running?'
        exit(-1)
    end

    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 }
end

Public Instance Methods

process_files(sync_database) click to toggle source

Process files and folders

@param sync_database [Boolean] True to sync database

# File lib/one_helper/onezone_helper.rb, line 111
def process_files(sync_database)
    # 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 if sync_database
end

Private Instance Methods

copy_and_check(file, service) click to toggle source

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

# File lib/one_helper/onezone_helper.rb, line 166
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_folder(folder, service) click to toggle source

Copy folders

@param folder [String] Folder to copy @param service [String] Service to restart

# File lib/one_helper/onezone_helper.rb, line 188
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
copy_onedconf() click to toggle source

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

# File lib/one_helper/onezone_helper.rb, line 226
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
fetch_db_config(configs) click to toggle source

Get database configuration

@param configs [Object] Configuration

# File lib/one_helper/onezone_helper.rb, line 135
def fetch_db_config(configs)
    configs.store(:backend, configs[:raw]['/OPENNEBULA_CONFIGURATION/DB/BACKEND'])

    if configs[:backend] == 'mysql' || configs[:backend] == 'postgresql'
        configs.store(:server, configs[:raw]['/OPENNEBULA_CONFIGURATION/DB/SERVER'])
        configs.store(:user, configs[:raw]['/OPENNEBULA_CONFIGURATION/DB/USER'])
        configs.store(:password, configs[:raw]['/OPENNEBULA_CONFIGURATION/DB/PASSWD'])
        configs.store(:dbname, configs[:raw]['/OPENNEBULA_CONFIGURATION/DB/DB_NAME'])
        configs.store(:port, configs[:raw]['/OPENNEBULA_CONFIGURATION/DB/PORT'])
        configs[:port] = '3306' if configs[:port] == '0'
    else
        STDERR.puts 'No mysql or postgresql backend configuration found'
        exit(-1)
    end
end
fetch_fed_config(configs) click to toggle source

Get federation configuration

@param configs [Object] Configuration

# File lib/one_helper/onezone_helper.rb, line 154
def fetch_fed_config(configs)
    configs.store(:server_id,
                  configs[:raw]['/OPENNEBULA_CONFIGURATION/FEDERATION/SERVER_ID'])
    configs.store(:zone_id,
                  configs[:raw]['/OPENNEBULA_CONFIGURATION/FEDERATION/ZONE_ID'])
end
restart_services() click to toggle source

Restart OpenNebula services

# File lib/one_helper/onezone_helper.rb, line 419
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
run_command(cmd, print_output = false) click to toggle source

Run local command

@param cmd [String] Command to run @param print_output [Boolean] True to show output

# File lib/one_helper/onezone_helper.rb, line 347
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
scp(src, dest) click to toggle source

Execute SCP command

@param src [String] Source path @param dest [String] Destination path

# File lib/one_helper/onezone_helper.rb, line 396
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
service_action(service, action = 'try-restart') click to toggle source

Service action

@param service [String] Service to restart @param action [String] Action to execute (start, stop, restart)

# File lib/one_helper/onezone_helper.rb, line 439
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
ssh(cmd) click to toggle source

Execute SSH command

@param cmd [String] Command to execute

# File lib/one_helper/onezone_helper.rb, line 366
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
sync_db() click to toggle source

Sync database

# File lib/one_helper/onezone_helper.rb, line 309
def sync_db
    puts "Dumping and fetching database from #{@remote_server}, " \
         'this could take a while'

    ssh(
        "onedb backup -f -t #{@r_config_elements[:backend]} " \
        "-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 -t #{@l_config_elements[:backend]}" \
        "-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