1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-11 05:17:41 +03:00

feature #661: Update and refactor onedb

This commit is contained in:
Daniel Molina 2011-06-16 19:40:21 +02:00
parent 021587d453
commit 331f8e8c88
7 changed files with 636 additions and 624 deletions

View File

@ -263,7 +263,7 @@ INSTALL_FILES=(
MAD_RUBY_LIB_FILES:$VAR_LOCATION/remotes
MAD_SH_LIB_FILES:$LIB_LOCATION/sh
MAD_SH_LIB_FILES:$VAR_LOCATION/remotes
ONEDB_MIGRATOR_FILES:$LIB_LOCATION/onedb
ONEDB_MIGRATOR_FILES:$LIB_LOCATION/ruby/onedb
MADS_LIB_FILES:$LIB_LOCATION/mads
IM_PROBES_FILES:$VAR_LOCATION/remotes/im
IM_PROBES_KVM_FILES:$VAR_LOCATION/remotes/im/kvm.d
@ -366,7 +366,7 @@ BIN_FILES="src/nebula/oned \
src/cli/oneimage \
src/cli/onegroup \
src/cli/onetemplate \
src/cli/onedb \
src/onedb/onedb \
share/scripts/one \
src/authm_mad/oneauth"
@ -532,7 +532,9 @@ IMAGE_DRIVER_FS_SCRIPTS="src/image_mad/remotes/fs/cp \
#-------------------------------------------------------------------------------
# Migration scripts for onedb command, to be installed under $LIB_LOCATION
#-------------------------------------------------------------------------------
ONEDB_MIGRATOR_FILES="src/onedb/1.rb"
ONEDB_MIGRATOR_FILES="src/onedb/1.rb \
src/onedb/onedb.rb \
src/onedb/onedb_backend.rb"
#-------------------------------------------------------------------------------
# Configuration files for OpenNebula, to be installed under $ETC_LOCATION

View File

@ -288,7 +288,7 @@ module CommandParser
merge.flatten.each do |e|
opts.on(e[:short],e[:large], e[:format],e[:description]) do |o|
if e[:proc]
e[:proc].call
e[:proc].call(o, @options)
elsif e[:name]=="help"
help
exit

View File

@ -1,614 +0,0 @@
#!/usr/bin/env ruby
# -------------------------------------------------------------------------- */
# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) #
# 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 up the environment
# ----------------------------------------------------------------------------
ONE_LOCATION = ENV["ONE_LOCATION"]
if !ONE_LOCATION
LIB_LOCATION = "/usr/lib/one"
RUBY_LIB_LOCATION = LIB_LOCATION + "/ruby"
VAR_LOCATION = "/var/lib/one"
ETC_LOCATION = "/etc/one"
LOCK_FILE = "/var/lock/one/one"
else
LIB_LOCATION = ONE_LOCATION + "/lib"
RUBY_LIB_LOCATION = LIB_LOCATION + "/ruby"
VAR_LOCATION = ONE_LOCATION + "/var"
ETC_LOCATION = ONE_LOCATION + "/etc"
LOCK_FILE = VAR_LOCATION + "/.lock"
end
$: << RUBY_LIB_LOCATION
require 'OpenNebula'
require 'command_parse'
# TODO: Move the Configuration file to OpenNebula ruby lib?
require "#{RUBY_LIB_LOCATION}/cloud/Configuration"
require 'rubygems'
require 'sequel'
class MigratorBase
attr_reader :db_version
attr_reader :one_version
@verbose
def initialize(db, verbose)
@db = db
@verbose = verbose
end
def up
puts "Method up not implemented for version #{@version}"
return false
end
end
class OneDBParse < CommandParse
COMMANDS_HELP=<<-EOT
DB Connection options:
By default, onedb reads the connection data from oned.conf
If any of these options is set, oned.conf is ignored (i.e. if you set MySQL's
port onedb won't look for the rest of the options in oned.conf)
Description:
This command enables the user to manage the OpenNebula database. It provides
information about the DB version, means to upgrade it to the latest version, and
backup tools.
Commands:
* upgrade (Upgrades the DB to the latest version)
onedb upgrade [<version>]
where <version> : DB version (e.g. 1, 3) to upgrade. By default the DB is
upgraded to the latest version
* version (Prints the current DB version. Use -v flag to see also OpenNebula version)
onedb version
* history (Prints the upgrades history)
onedb history
* backup (Dumps the DB to a file)
onedb backup [<output_file>]
where <output_file> : Same as --backup
* restore (Restores the DB from a backup file. Only restores backups generated
from the same backend (SQLite or MySQL))
onedb restore [<backup_file>]
where <backup_file> : Same as --backup
EOT
def text_commands
COMMANDS_HELP
end
def text_command_name
"onedb"
end
def special_options(opts, options)
opts.on_tail("-f", "--force", "Forces the backup even if the DB exists") do |o|
options[:force] = true
end
opts.on_tail("--backup file", "Use this file to store/read SQL dump", String) do |o|
options[:backup] = o
end
opts.on_tail("-s file", "--sqlite file", "SQLite DB file", String) do |o|
options[:backend] = :sqlite
options[:sqlite] = o
end
opts.on_tail("--server host", "MySQL server hostname or IP. Defaults "<<
"to localhost", String) do |o|
options[:backend] = :mysql
options[:server] = o
end
opts.on_tail("--port port", "MySQL server port. Defaults to 3306", Integer) do |o|
options[:backend] = :mysql
options[:port] = o
end
opts.on_tail("--user username", "MySQL username", String) do |o|
options[:backend] = :mysql
options[:user] = o
end
opts.on_tail("--passwd password", "MySQL password. Leave unset to be "<<
"prompted for it", String) do |o|
options[:backend] = :mysql
options[:passwd] = o
end
opts.on_tail("--dbname name", "MySQL DB name for OpenNebula", String) do |o|
options[:backend] = :mysql
options[:dbname] = o
end
end
end
################################################################################
# Helpers
################################################################################
def connection_params()
if( @ops[:backend] == nil )
read_onedconf()
else
@backend = @ops[:backend]
if( @backend == :sqlite )
@sqlite_file = @ops[:sqlite]
else
@server = @ops[:server]
@port = @ops[:port]
@user = @ops[:user]
@passwd = @ops[:passwd]
@db_name = @ops[:dbname]
# Check for errors:
error = false
missing = ""
(error = true; missing = "--user" ) if @user == nil
(error = true; missing = "--dbname") if @db_name == nil
if error
puts "MySQL option #{missing} is needed"
exit -1
end
# Check for defaults:
@server = "localhost" if @server == nil
@port = 0 if @port == nil
if @passwd == nil
# Hide input characters
`stty -echo`
print "MySQL Password: "
@passwd = STDIN.gets.strip
`stty echo`
puts ""
end
end
end
end
def read_onedconf()
config = Configuration.new("#{ETC_LOCATION}/oned.conf")
if config[:db] == nil
puts "No DB defined."
exit -1
end
if config[:db]["BACKEND"].upcase.include? "SQLITE"
@backend = :sqlite
@sqlite_file = "#{VAR_LOCATION}/one.db"
elsif config[:db]["BACKEND"].upcase.include? "MYSQL"
@backend = :mysql
@server = config[:db]["SERVER"]
@port = config[:db]["PORT"]
@user = config[:db]["USER"]
@passwd = config[:db]["PASSWD"]
@db_name = config[:db]["DB_NAME"]
# In OpenNebula 2.0 PORT wasn't present in oned.conf, set default
@port = "0" if @port == nil
# Check for errors:
error = false
missing = ""
(error = true; missing = "SERVER" ) if @server == nil
(error = true; missing = "USER" ) if @user == nil
(error = true; missing = "PASSWD" ) if @passwd == nil
(error = true; missing = "DB_NAME") if @db_name == nil
if error
puts "MySQL attribute #{missing} not found in " +
"#{ETC_LOCATION}/oned.conf"
exit -1
end
# Clean leading and trailing quotes, if any
@server = @server [1..-2] if @server [0] == ?"
@port = @port [1..-2] if @port [0] == ?"
@user = @user [1..-2] if @user [0] == ?"
@passwd = @passwd [1..-2] if @passwd [0] == ?"
@db_name = @db_name[1..-2] if @db_name[0] == ?"
else
puts "Could not load DB configuration from #{ETC_LOCATION}/oned.conf"
exit -1
end
end
def get_bck_file()
bck_file = ""
if( @ops[:backup] != nil )
bck_file = @ops[:backup]
elsif @backend == :sqlite
bck_file = "#{VAR_LOCATION}/one.db.bck"
elsif @backend == :mysql
bck_file = "#{VAR_LOCATION}/mysql_#{@server}_#{@db_name}.sql"
end
return bck_file
end
def backup_db()
bck_file = get_bck_file()
if( !@ops[:force] && File.exists?(bck_file) )
puts "File #{bck_file} exists, backup aborted. Use -f to overwrite."
exit -1
end
case @backend
when :sqlite
if( ! File.exists?(@sqlite_file) )
puts "File #{@sqlite_file} doesn't exist, backup aborted."
exit -1
end
FileUtils.cp(@sqlite_file, "#{bck_file}")
puts "Sqlite database backup stored in #{bck_file}"
puts "Use 'onedb restore' or copy the file back to restore the DB."
when :mysql
cmd = "mysqldump -u #{@user} -p#{@passwd} -h #{@server} " +
"-P #{@port} #{@db_name} > #{bck_file}"
rc = system(cmd)
if( !rc )
puts "Unknown error running '#{cmd}'"
exit -1
end
puts "MySQL dump stored in #{bck_file}"
puts "Use 'onedb restore' or restore the DB using the mysql command:"
puts "mysql -u user -h server -P port db_name < backup_file"
else
puts "Unknown DB #{@backend}"
exit -1
end
puts ""
end
def connect_db()
case @backend
when :sqlite
if( ! File.exists?(@sqlite_file) )
puts "File #{@sqlite_file} doesn't exist."
exit -1
end
@db = Sequel.sqlite(@sqlite_file)
when :mysql
@db = Sequel.connect(
"mysql://#{@user}:#{@passwd}@#{@server}:#{@port}/#{@db_name}")
else
puts "Unknown DB #{@backend}"
exit -1
end
end
def read_db_version()
version = 0
timestamp = 0
comment = ""
@db.fetch("SELECT version, timestamp, comment FROM db_versioning " +
"WHERE oid=(SELECT MAX(oid) FROM db_versioning)") do |row|
version = row[:version]
timestamp = row[:timestamp]
comment = row[:comment]
end
return [version.to_i, timestamp, comment]
rescue
# If the DB doesn't have db_versioning table, it means it is empty or a 2.x
# OpenNebula DB
begin
# User with ID 0 (oneadmin) always exists
@db.fetch("SELECT * FROM user_pool WHERE oid=0") do |row|
end
rescue
puts "Database schema does not look to be created by OpenNebula:"
puts "table user_pool is missing or empty."
exit -1
end
begin
# Table image_pool is present only in 2.X DBs
@db.fetch("SELECT * FROM image_pool") do |row|
end
rescue
puts "Database schema looks to be created by OpenNebula 1.X."
puts "This tool only works with databases created by 2.X versions."
exit -1
end
comment = "Could not read any previous db_versioning data, assuming it is "+
"an OpenNebula 2.0 or 2.2 DB."
return [0, 0, comment]
end
def one_not_running()
if File.exists?(LOCK_FILE)
puts "First stop OpenNebula. Lock file found: #{LOCK_FILE}"
exit -1
end
end
################################################################################
################################################################################
onedb_opts = OneDBParse.new([])
onedb_opts.parse(ARGV)
@ops = onedb_opts.options
@verbose = @ops[:verbose]
command = ARGV.shift
case command
when "upgrade"
# Check opennebula is not running
one_not_running()
# Get DB connection parameters, from oned.conf or command arguments
connection_params()
# Connect to DB
connect_db()
# Read DB's version
version, timestamp, comment = read_db_version()
if( @verbose )
puts "Version read:"
puts "#{version} : #{comment}"
puts ""
end
# Upgrade, using the scripts in $LIB_LOCATION/onedb/xx.rb
max_version = nil
if( ARGV[0] )
max_version = ARGV[0].to_i
end
migrator_version = version + 1
migrator = nil
file = "#{LIB_LOCATION}/onedb/#{migrator_version}.rb"
if( File.exists?(file) &&
(max_version == nil || migrator_version <= max_version) )
# At least one upgrade will be executed, make DB backup
backup_db()
end
while( File.exists?(file) &&
(max_version == nil || migrator_version <= max_version) )
puts " > Running migrator #{file}" if @verbose
load(file)
migrator = Migrator.new(@db, @verbose)
result = migrator.up
if( !result )
puts "Error while upgrading from #{migrator_version-1} to #{migrator.db_version}"
return -1
end
puts " > Done" if @verbose
puts "" if @verbose
migrator_version += 1
file = "#{LIB_LOCATION}/onedb/#{migrator_version}.rb"
end
# Modify db_versioning table
if( migrator != nil )
comment = "Database migrated from #{version} to #{migrator.db_version}"+
" (#{migrator.one_version}) by onedb command."
max_oid = nil
@db.fetch("SELECT MAX(oid) FROM db_versioning") do |row|
max_oid = row[:"MAX(oid)"].to_i
end
max_oid = 0 if max_oid == nil
@db.run "INSERT INTO db_versioning (oid, version, timestamp, comment) "+
"VALUES (" +
"#{max_oid+1}, " +
"'#{migrator.db_version}', " +
"#{Time.new.to_i}, " +
"'#{comment}')"
puts comment
else
puts "Database already uses version #{version}"
end
when "version"
connection_params()
connect_db()
version, timestamp, comment = read_db_version()
if(@verbose)
puts "Version: #{version}"
time = version == 0 ? Time.now : Time.at(timestamp)
# TODO: UTC or Local time?
puts "Timestamp: #{time.getgm.strftime("%b %d, %Y %H:%M")}"
puts "Comment: #{comment}"
else
puts version
end
when "history"
connection_params()
connect_db()
begin
@db.fetch("SELECT version, timestamp, comment FROM db_versioning") do |row|
puts "Version: #{row[:version]}"
time = version == 0 ? Time.now : Time.at(row[:timestamp])
# TODO: UTC or Local time?
puts "Timestamp: #{time.getgm.strftime("%b %d, %Y %H:%M")}"
puts "Comment: #{row[:comment]}"
puts ""
end
rescue Exception => e
puts "No version records found. Error message:"
puts e.message
end
when "backup"
if( ARGV[0] != nil )
@ops[:backup] = ARGV[0]
end
connection_params()
backup_db()
when "restore"
if( ARGV[0] != nil )
@ops[:backup] = ARGV[0]
end
connection_params()
# Source sql dump file
bck_file = get_bck_file()
if( ! File.exists?(bck_file) )
puts "File #{bck_file} doesn't exist, backup restoration aborted."
exit -1
end
one_not_running()
case @backend
when :sqlite
if( !@ops[:force] && File.exists?(@sqlite_file) )
puts "File #{@sqlite_file} exists, use -f to overwrite."
exit -1
end
FileUtils.cp(bck_file, @sqlite_file)
puts "Sqlite database backup restored in #{@sqlite_file}"
when :mysql
connect_db()
# Check if target database exists
exists = false
begin
# User with ID 0 (oneadmin) always exists
@db.fetch("SELECT * FROM user_pool WHERE oid=0") do |row|
end
exists = true
rescue
end
if( !@ops[:force] && exists )
puts "MySQL database #{@db_name} at #{@server} exists, use -f to overwrite."
exit -1
end
mysql_cmd = "mysql -u #{@user} -p#{@passwd} -h #{@server} " +
"-P #{@port} "
rc = system( mysql_cmd + "-e 'DROP DATABASE IF EXISTS #{@db_name};'")
if( !rc )
puts "Error dropping MySQL DB #{@db_name} at #{@server}."
exit -1
end
rc = system( mysql_cmd + "-e 'CREATE DATABASE IF NOT EXISTS #{@db_name};'")
if( !rc )
puts "Error creating MySQL DB #{@db_name} at #{@server}."
exit -1
end
rc = system( mysql_cmd + "#{@db_name} < #{bck_file}")
if( !rc )
puts "Error while restoring MySQL DB #{@db_name} at #{@server}."
exit -1
end
puts "MySQL DB #{@db_name} at #{@server} restored."
else
puts "Unknown DB #{@backend}"
exit -1
end
else
onedb_opts.print_help
exit -1
end
exit 0

View File

@ -13,16 +13,16 @@
# limitations under the License. */
# -------------------------------------------------------------------------- */
class Migrator < MigratorBase
module Migrator
def db_version
1
end
def initialize(db, verbose)
super(db, verbose)
@db_version = 1
@one_version = "OpenNebula 2.3.0"
def one_version
"OpenNebula 2.3.0"
end
def up
########################################################################
# Users
########################################################################

206
src/onedb/onedb Executable file
View File

@ -0,0 +1,206 @@
#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) #
# #
# 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. #
#--------------------------------------------------------------------------- #
ONE_LOCATION=ENV["ONE_LOCATION"]
if !ONE_LOCATION
RUBY_LIB_LOCATION="/usr/lib/one/ruby"
ETC_LOCATION="/etc/one/"
VAR_LOCATION="/var/lib/one"
else
RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby"
ETC_LOCATION=ONE_LOCATION+"/etc/"
VAR_LOCATION="#{ONE_LOCATION}/var"
end
$: << RUBY_LIB_LOCATION
$: << RUBY_LIB_LOCATION+'/onedb'
require 'cli/command_parser'
require 'onedb'
FORCE={
:name => "force",
:short => "-f",
:large => "--force",
:description => "Forces the backup even if the DB exists"
}
###############################################################################
# SQLite options
###############################################################################
SQLITE={
:name => "sqlite",
:short => "-s file",
:large => "--sqlite file",
:format => String,
:description => "SQLite DB file",
:proc => lambda { |o, options|
options[:backend] = :sqlite
options[:sqlite] = o
}
}
###############################################################################
# MySQL options
###############################################################################
SERVER={
:name => "server",
:short => "-S host",
:large => "--server host",
:format => String,
:description => "MySQL server hostname or IP. Defaults to localhost",
:proc => lambda { |o, options|
options[:backend] = :mysql
options[:server] = o
}
}
PORT={
:name => "port",
:short => "-P port",
:large => "--port port",
:format => String,
:description => "MySQL server port. Defaults to 3306",
:proc => lambda { |o, options|
options[:backend] = :mysql
options[:port] = o
}
}
USERNAME={
:name => "username",
:short => "-u user",
:large => "--username user",
:format => String,
:description => "MySQL username",
:proc => lambda { |o, options|
options[:backend] = :mysql
options[:username] = o
}
}
PASSWORD={
:name => "password",
:short => "-p pass",
:large => "--password pass",
:format => String,
:description => "MySQL password. Leave unset to be prompted for it",
:proc => lambda { |o, options|
options[:backend] = :mysql
options[:password] = o
}
}
DBNAME={
:name => "dbname",
:short => "-d dbname",
:large => "--dbname dbname",
:format => String,
:description => "MySQL DB name for OpenNebula",
:proc => lambda { |o, options|
options[:backend] = :mysql
options[:dbname] = o
}
}
cmd=CommandParser::CmdParser.new(ARGV) do
description <<-EOT.unindent
DB Connection options:
By default, onedb reads the connection data from oned.conf
If any of these options is set, oned.conf is ignored (i.e. if you set
MySQL's port onedb won't look for the rest of the options in oned.conf)
Description:
This command enables the user to manage the OpenNebula database. It
provides information about the DB version, means to upgrade it to the
latest version, and backup tools.
EOT
###########################################################################
# Global options
###########################################################################
set :option, CommandParser::OPTIONS
set :option, [SQLITE, SERVER, PORT, USERNAME, PASSWORD, DBNAME]
###########################################################################
# Backup
###########################################################################
backup_desc = <<-EOT.unindent
Dumps the DB to a file specified in the argument
EOT
command :backup, backup_desc, [:output_file, nil], :options=>FORCE do
helper = OneDB.new(options)
helper.backup(args[0], options)
end
###########################################################################
# Version
###########################################################################
version_desc = <<-EOT.unindent
Prints the current DB version.
Use -v flag to see also OpenNebula version
EOT
command :version , version_desc do
helper = OneDB.new(options)
helper.version(options)
end
###########################################################################
# History
###########################################################################
history_desc = <<-EOT.unindent
Prints the upgrades history
EOT
command :history , history_desc do
helper = OneDB.new(options)
helper.history
end
###########################################################################
# Restore
###########################################################################
restore_desc = <<-EOT.unindent
Restores the DB from a backup file. Only restores backups generated
from the same backend (SQLite or MySQL)
EOT
command :restore , restore_desc, [:backup_file, nil], :options=>FORCE do
helper = OneDB.new(options)
helper.restore(args[0], options)
end
###########################################################################
# Upgrade
###########################################################################
upgrade_desc = <<-EOT.unindent
Upgrades the DB to the latest version
where <version> : DB version (e.g. 1, 3) to upgrade.
By default the DB is upgraded to the latest version
EOT
command :upgrade , upgrade_desc, [:version, nil] do
helper = OneDB.new(options)
helper.upgrade(args[0], options)
end
end

181
src/onedb/onedb.rb Normal file
View File

@ -0,0 +1,181 @@
ONE_LOCATION = ENV["ONE_LOCATION"]
if !ONE_LOCATION
LIB_LOCATION = "/usr/lib/one"
RUBY_LIB_LOCATION = LIB_LOCATION + "/ruby"
VAR_LOCATION = "/var/lib/one"
ETC_LOCATION = "/etc/one"
LOCK_FILE = "/var/lock/one/one"
else
LIB_LOCATION = ONE_LOCATION + "/lib"
RUBY_LIB_LOCATION = LIB_LOCATION + "/ruby"
VAR_LOCATION = ONE_LOCATION + "/var"
ETC_LOCATION = ONE_LOCATION + "/etc"
LOCK_FILE = VAR_LOCATION + "/.lock"
end
require 'cloud/Configuration'
require 'onedb_backend'
class OneDB
def initialize(ops)
if ops[:backend]==nil
@backend = from_onedconf
else
if ops[:backend] == :sqlite
@backend = BackEndSQLite.new(ops[:sqlite])
else
passwd = ops[:passwd]
if !passwd
# Hide input characters
`stty -echo`
print "MySQL Password: "
passwd = STDIN.gets.strip
`stty echo`
puts ""
end
@backend = BackEndMySQL.new(
:server => ops[:server],
:port => ops[:port],
:user => ops[:user],
:passwd => passwd,
:db_name => ops[:db_name]
)
end
end
end
def backup(bck_file, ops)
bck_file = @backend.bck_file if bck_file.nil?
if !ops[:force] && File.exists?(bck_file)
puts "File #{bck_file} exists, backup aborted. Use -f to overwrite."
exit -1
end
@backend.backup(bck_file)
end
def restore(bck_file, ops)
bck_file = @backend.bck_file if bck_file.nil?
if !File.exists?(bck_file)
puts "File #{bck_file} doesn't exist, backup restoration aborted."
exit -1
end
one_not_running
@backend.restore(bck_file, ops[:force])
end
def version(ops)
version, timestamp, comment = @backend.read_db_version
if(ops[:verbose])
puts "Version: #{version}"
time = version == 0 ? Time.now : Time.at(timestamp)
# TODO: UTC or Local time?
puts "Timestamp: #{time.getgm.strftime("%b %d, %Y %H:%M")}"
puts "Comment: #{comment}"
else
puts version
end
end
def history
@backend.history
end
def upgrade(max_version, ops)
version, timestamp, comment = @backend.read_db_version
if ops[:verbose]
puts "Version read:"
puts "#{version} : #{comment}"
puts ""
end
migrator_version = version + 1
result = nil
file = "#{LIB_LOCATION}/onedb/#{migrator_version}.rb"
if File.exists?(file) &&
(max_version == nil || migrator_version <= max_version)
# At least one upgrade will be executed, make DB backup
if ops[:backup]
bck_file = ops[:backup]
else
bck_file = @backend.bck_file(VAR_LOCATION)
end
@backend.backup(bck_file)
end
while File.exists?(file) &&
(max_version == nil || migrator_version <= max_version)
puts " > Running migrator #{file}" if ops[:verbose]
load(file)
@backend.extend Migrator
result = @backend.up
if !result
puts "Error while upgrading from #{migrator_version-1} to " <<
" #{@backend.db_version}"
return -1
end
puts " > Done" if ops[:verbose]
puts "" if ops[:verbose]
migrator_version += 1
file = "#{LIB_LOCATION}/onedb/#{migrator_version}.rb"
end
# Modify db_versioning table
if result != nil
@backend.update_db_version(version)
else
puts "Database already uses version #{version}"
end
end
private
def from_onedconf()
config = Configuration.new("#{ETC_LOCATION}/oned.conf")
if config[:db] == nil
puts "No DB defined."
exit -1
end
if config[:db]["BACKEND"].upcase.include? "SQLITE"
sqlite_file = "#{VAR_LOCATION}/one.db"
@backend = BackEndSQLite.new(sqlite_file)
elsif config[:db]["BACKEND"].upcase.include? "MYSQL"
@backend = BackEndMySQL.new(
:server => config[:db]["SERVER"],
:port => config[:db]["PORT"],
:user => config[:db]["USER"],
:passwd => config[:db]["PASSWD"],
:db_name => config[:db]["DB_NAME"]
)
else
puts "Could not load DB configuration from " <<
"#{ETC_LOCATION}/oned.conf"
exit -1
end
end
def one_not_running()
if File.exists?(LOCK_FILE)
puts "First stop OpenNebula. Lock file found: #{LOCK_FILE}"
exit -1
end
end
end

237
src/onedb/onedb_backend.rb Normal file
View File

@ -0,0 +1,237 @@
require 'rubygems'
require 'sequel'
class OneDBBacKEnd
def read_db_version
connect_db
version = 0
timestamp = 0
comment = ""
@db.fetch("SELECT version, timestamp, comment FROM db_versioning " +
"WHERE oid=(SELECT MAX(oid) FROM db_versioning)") do |row|
version = row[:version]
timestamp = row[:timestamp]
comment = row[:comment]
end
return [version.to_i, timestamp, comment]
rescue
# If the DB doesn't have db_version table, it means it is empty or a 2.x
if !db_exists?
puts "Database schema does not look to be created by OpenNebula:"
puts "table user_pool is missing or empty."
exit -1
end
begin
# Table image_pool is present only in 2.X DBs
@db.fetch("SELECT * FROM image_pool") { |row| }
rescue
puts "Database schema looks to be created by OpenNebula 1.X."
puts "This tool only works with databases created by 2.X versions."
exit -1
end
comment = "Could not read any previous db_versioning data, " <<
"assuming it is an OpenNebula 2.0 or 2.2 DB."
return [0, 0, comment]
end
def history
connect_db
begin
query = "SELECT version, timestamp, comment FROM db_versioning"
@db.fetch(query) do |row|
puts "Version: #{row[:version]}"
time = Time.at(row[:timestamp])
# TODO: UTC or Local time?
puts "Timestamp: #{time.getgm.strftime("%b %d, %Y %H:%M")}"
puts "Comment: #{row[:comment]}"
puts ""
end
rescue Exception => e
puts "No version records found. Error message:"
puts e.message
end
end
def update_db_version(version)
comment = "Database migrated from #{version} to #{db_version}"+
" (#{one_version}) by onedb command."
max_oid = nil
@db.fetch("SELECT MAX(oid) FROM db_versioning") do |row|
max_oid = row[:"MAX(oid)"].to_i
end
max_oid = 0 if max_oid.nil?
query =
@db.run(
"INSERT INTO db_versioning (oid, version, timestamp, comment) "<<
"VALUES (" <<
"#{max_oid+1}, " <<
"'#{db_version}', " <<
"#{Time.new.to_i}, " <<
"'#{comment}')"
)
puts comment
end
private
def db_exists?
begin
# User with ID 0 (oneadmin) always exists
@db.fetch("SELECT * FROM user_pool WHERE oid=0") { |row| }
return true
rescue
return false
end
end
end
class OneDBBackEndMySQL < OneDBBacKEnd
def initialize(opts={})
@server = ops[:server]
@port = ops[:port]
@user = ops[:user]
@passwd = ops[:passwd]
@db_name = ops[:db_name]
# Check for errors:
error = false
(error = true; missing = "USER" ) if @user == nil
(error = true; missing = "DBNAME") if @db_name == nil
if error
puts "MySQL option #{missing} is needed"
exit -1
end
# Check for defaults:
@server = "localhost" if @server.nil?
@port = 0 if @port.nil?
# Clean leading and trailing quotes, if any
@server = @server [1..-2] if @server [0] == ?"
@port = @port [1..-2] if @port [0] == ?"
@user = @user [1..-2] if @user [0] == ?"
@passwd = @passwd [1..-2] if @passwd [0] == ?"
@db_name = @db_name[1..-2] if @db_name[0] == ?"
end
def bck_file(var_location)
"#{var_location}/mysql_#{@server}_#{@db_name}.sql"
end
def backup(bck_file)
cmd = "mysqldump -u #{@user} -p#{@passwd} -h #{@server} " +
"-P #{@port} #{@db_name} > #{bck_file}"
rc = system(cmd)
if !rc
puts "Unknown error running '#{cmd}'"
exit -1
end
puts "MySQL dump stored in #{bck_file}"
puts "Use 'onedb restore' or restore the DB using the mysql command:"
puts "mysql -u user -h server -P port db_name < backup_file"
end
def restore(bck_file, force=nil)
connect_db
if !force && db_exists?
puts "MySQL database #{@db_name} at #{@server} exists," <<
" use -f to overwrite."
exit -1
end
mysql_cmd = "mysql -u #{@user} -p#{@passwd} -h #{@server} -P #{@port} "
drop_cmd = mysql_cmd + "-e 'DROP DATABASE IF EXISTS #{@db_name};'"
rc = system(drop_cmd)
if !rc
puts "Error dropping MySQL DB #{@db_name} at #{@server}."
exit -1
end
create_cmd = mysql_cmd+"-e 'CREATE DATABASE IF NOT EXISTS #{@db_name};'"
rc = system(create_cnd)
if !rc
puts "Error creating MySQL DB #{@db_name} at #{@server}."
exit -1
end
restore_cmd = mysql_cmd + "#{@db_name} < #{bck_file}"
rc = system(restore_cmd)
if !rc
puts "Error while restoring MySQL DB #{@db_name} at #{@server}."
exit -1
end
puts "MySQL DB #{@db_name} at #{@server} restored."
end
private
def connect_db
endpoint = "mysql://#{@user}:#{@passwd}@#{@server}:#{@port}/#{@db_name}"
@db = Sequel.connect(endpoint)
end
end
class BackEndSQLite < OneDBBacKEnd
def initialize(file)
@sqlite_file = file
pp file
end
def bck_file(var_location)
"#{var_location}/one.db.bck"
end
def backup(bck_file)
if !File.exists?(@sqlite_file)
puts "File #{@sqlite_file} doesn't exist, backup aborted."
exit -1
end
FileUtils.cp(@sqlite_file, "#{bck_file}")
puts "Sqlite database backup stored in #{bck_file}"
puts "Use 'onedb restore' or copy the file back to restore the DB."
end
def restore(bck_file, force=nil)
if !force && File.exists?(@sqlite_file)
puts "File #{@sqlite_file} exists, use -f to overwrite."
exit -1
end
FileUtils.cp(bck_file, @sqlite_file)
puts "Sqlite database backup restored in #{@sqlite_file}"
end
private
def connect_db
if !File.exists?(@sqlite_file)
puts "File #{@sqlite_file} doesn't exist."
exit -1
end
@db = Sequel.sqlite(@sqlite_file)
end
end