This series of patches makes changes to the new ovirt-client repo. The repo can be found at git://ovirt.org/ovirt-client; the same people that have commit access to the server repo have commit for this repo. Since I was so excited to establish a new repo, I already pushed all these patches to the new repo, but I would still appreciate comments/acks on these. The patch series has to be applied against the very first commit in the repo, which only copied files from the server to the client repo, though it's probably simpler to just check out ovirt-client and review the log of the next branch. Please read the README first before trying the ovirt CLI out - you need a very recent version of activeresource (>= 2.2.2) and the 'main' rubygem for the CLI to work. David
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 01/11] Indent with 2 spaces
Only whitespace changes, no functional changes --- examples/script.rb | 50 ++++++++++---------- lib/ovirt.rb | 126 ++++++++++++++++++++++++++-------------------------- 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/examples/script.rb b/examples/script.rb index 1485535..f6f6839 100755 --- a/examples/script.rb +++ b/examples/script.rb @@ -10,27 +10,27 @@ require 'optparse' require 'ovirt' def move_random_host(hosts, pool) - host = hosts[rand(hosts.size)] - puts "Move #{host.hostname} to #{pool.name}" - pool.hosts << host - pool.save + host = hosts[rand(hosts.size)] + puts "Move #{host.hostname} to #{pool.name}" + pool.hosts << host + pool.save end def element_path(obj) - "[#{obj.class.element_path(obj.id)}]" + "[#{obj.class.element_path(obj.id)}]" end def print_pool(pool) - puts "\n\nPool #{pool.name}: #{pool.hosts.size} hosts, #{pool.storage_pools.size} storage pools #{element_path(pool)} " - puts "=" * 75 - pool.hosts.each do |h| - printf "%-36s %s\n", h.hostname, element_path(h) - end - pool.storage_pools.each do |sp| - type = sp.nfs? ? "NFS" : "iSCSI" - printf "%-5s %-30s %s\n", type, sp.label, element_path(sp) - end - puts "-" * 75 + puts "\n\nPool #{pool.name}: #{pool.hosts.size} hosts, #{pool.storage_pools.size} storage pools #{element_path(pool)} " + puts "=" * 75 + pool.hosts.each do |h| + printf "%-36s %s\n", h.hostname, element_path(h) + end + pool.storage_pools.each do |sp| + type = sp.nfs? ? "NFS" : "iSCSI" + printf "%-5s %-30s %s\n", type, sp.label, element_path(sp) + end + puts "-" * 75 end # Plumbing so we can find the OVirt server @@ -45,17 +45,17 @@ opts.separator "Global options:" opts.on("-s", "--server=URL", "The OVirt server. Since there is no auth\n" + "#{" "*37}yet, must be the mongrel server port.\n" + "#{" "*37}Overrides env var OVIRT_SERVER") do |val| - OVirt::Base.site = val + OVirt::Base.site = val end opts.order(ARGV) unless OVirt::Base.site - $stderr.puts <<EOF + $stderr.puts <<EOF You must specify the OVirt server to connect to, either with the --server option or through the OVIRT_SERVER environment variable EOF - exit 1 + exit 1 end OVirt::Base.login @@ -71,23 +71,23 @@ print_pool(defpool) # Create a new hardware pool mypool = OVirt::HardwarePool.find_by_path("/default/mypool") unless mypool - puts "Create mypool" - mypool = OVirt::HardwarePool.create( { :parent_id => defpool.id, - :name => "mypool" } ) + puts "Create mypool" + mypool = OVirt::HardwarePool.create( { :parent_id => defpool.id, + :name => "mypool" } ) end # Move some hosts around puts if defpool.hosts.size > 1 - move_random_host(defpool.hosts, mypool) + move_random_host(defpool.hosts, mypool) elsif mypool.hosts.size > 0 - move_random_host(mypool.hosts, defpool) + move_random_host(mypool.hosts, defpool) end # Delete all storage pools for mypool and add a new one mypool.storage_pools.each do |sp| - puts "Delete storage pool #{sp.id}" - sp.destroy + puts "Delete storage pool #{sp.id}" + sp.destroy end storage_pool = OVirt::StoragePool.create( { :storage_type => "NFS", diff --git a/lib/ovirt.rb b/lib/ovirt.rb index 15dc467..fc3ac2a 100644 --- a/lib/ovirt.rb +++ b/lib/ovirt.rb @@ -18,85 +18,85 @@ class ActiveResource::Connection end module OVirt - class Base < ActiveResource::Base - LOGIN_PATH = "login/login" - - def self.login - response = nil - begin - response = connection.get(prefix + LOGIN_PATH) - rescue ActiveResource::Redirection => e - response = e.response - end - unless connection.session = session_cookie(response) - raise "Authentication failed" - end + class Base < ActiveResource::Base + LOGIN_PATH = "login/login" + + def self.login + response = nil + begin + response = connection.get(prefix + LOGIN_PATH) + rescue ActiveResource::Redirection => e + response = e.response end + unless connection.session = session_cookie(response) + raise "Authentication failed" + end + end - private - def self.session_cookie(response) - if cookies = response.get_fields("Set-Cookie") - cookies.find { |cookie| - cookie.split(";")[0].split("=")[0] == "_ovirt_session_id" - } - end + private + def self.session_cookie(response) + if cookies = response.get_fields("Set-Cookie") + cookies.find { |cookie| + cookie.split(";")[0].split("=")[0] == "_ovirt_session_id" + } end + end + + end + + class HardwarePool < Base + def self.find_by_path(path) + find(:first, :params => { :path => path }) + end + def self.default_pool + find(:first, :params => { :path => "/default" }) end + end - class HardwarePool < Base - def self.find_by_path(path) - find(:first, :params => { :path => path }) - end + class StoragePool < Base + def iscsi? + attributes["type"] == "IscsiStoragePool" + end - def self.default_pool - find(:first, :params => { :path => "/default" }) - end + def nfs? + attributes["type"] == "NfsStoragePool" end - class StoragePool < Base - def iscsi? - attributes["type"] == "IscsiStoragePool" - end - - def nfs? - attributes["type"] == "NfsStoragePool" - end - - def label - if iscsi? - "#{ip_addr}:#{port}:#{target}" - elsif nfs? - "#{ip_addr}:#{export_path}" - else - raise "Unknown type #{attributes["type"]}" - end - end + def label + if iscsi? + "#{ip_addr}:#{port}:#{target}" + elsif nfs? + "#{ip_addr}:#{export_path}" + else + raise "Unknown type #{attributes["type"]}" + end end + end - class IscsiStoragePool < StoragePool - def initialize(attributes = {}) - super(attributes.update( "type" => "IscsiStoragePool" )) - end + class IscsiStoragePool < StoragePool + def initialize(attributes = {}) + super(attributes.update( "type" => "IscsiStoragePool" )) end + end - class NfsStoragePool < StoragePool - def initialize(attributes = {}) - super(attributes.update( "type" => "NfsStoragePool" )) - end + class NfsStoragePool < StoragePool + def initialize(attributes = {}) + super(attributes.update( "type" => "NfsStoragePool" )) end + end - class Host < Base - def self.find_by_uuid(uuid) - find(:first, :params => { :uuid => uuid }) - end + class Host < Base + def self.find_by_uuid(uuid) + find(:first, :params => { :uuid => uuid }) + end - def self.find_by_hostname(hostname) - find(:first, :params => { :hostname => hostname }) - end + def self.find_by_hostname(hostname) + find(:first, :params => { :hostname => hostname }) + end - def hardware_pool - HardwarePool.find(hardware_pool_id) - end + def hardware_pool + HardwarePool.find(hardware_pool_id) end + end end -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 02/11] Fix paths for default pool
--- examples/script.rb | 4 ++-- lib/ovirt.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/script.rb b/examples/script.rb index f6f6839..522c883 100755 --- a/examples/script.rb +++ b/examples/script.rb @@ -69,7 +69,7 @@ defpool = OVirt::HardwarePool.default_pool print_pool(defpool) # Create a new hardware pool -mypool = OVirt::HardwarePool.find_by_path("/default/mypool") +mypool = OVirt::HardwarePool.find_by_path("/root/hardware/default/mypool") unless mypool puts "Create mypool" mypool = OVirt::HardwarePool.create( { :parent_id => defpool.id, @@ -97,5 +97,5 @@ storage_pool = OVirt::StoragePool.create( { :storage_type => "NFS", puts "Created storage pool #{storage_pool.id}" # For some reason, mypool.reload doesn't work here -mypool = OVirt::HardwarePool.find_by_path("/default/mypool") +mypool = OVirt::HardwarePool.find_by_path("/root/hardware/default/mypool") print_pool(mypool) diff --git a/lib/ovirt.rb b/lib/ovirt.rb index fc3ac2a..927efe3 100644 --- a/lib/ovirt.rb +++ b/lib/ovirt.rb @@ -50,7 +50,7 @@ module OVirt end def self.default_pool - find(:first, :params => { :path => "/default" }) + find(:first, :params => { :path => "/root/hardware/default" }) end end -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 03/11] Add StoragePool.find_by_path
--- lib/ovirt.rb | 6 ++++++ 1 files changed, 6 insertions(+), 0 deletions(-) diff --git a/lib/ovirt.rb b/lib/ovirt.rb index 927efe3..ea55feb 100644 --- a/lib/ovirt.rb +++ b/lib/ovirt.rb @@ -72,6 +72,12 @@ module OVirt raise "Unknown type #{attributes["type"]}" end end + + def self.find_by_path(path) + hw = HardwarePool.find_by_path(path) + hw.nil? ? nil : hw.storage_pools + end + end class IscsiStoragePool < StoragePool -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 04/11] Add way to format size as human readable
--- lib/ovirt.rb | 31 +++++++++++++++++++++++++++++++ 1 files changed, 31 insertions(+), 0 deletions(-) diff --git a/lib/ovirt.rb b/lib/ovirt.rb index ea55feb..ce51e66 100644 --- a/lib/ovirt.rb +++ b/lib/ovirt.rb @@ -21,6 +21,31 @@ module OVirt class Base < ActiveResource::Base LOGIN_PATH = "login/login" + def to_human_readable(size) + size = size.to_f + unit = "kB" + if size > 512 + size /= 1024 + unit = "MB" + if size > 512 + size /= 1024 + unit = "GB" + if size > 512 + size /= 1024 + unit = "TB" + end + end + end + format "%5.2f %s", size, unit + end + + def self.human_readable(attr) + attr = attr.to_s + define_method("human_readable_#{attr}") do + to_human_readable(attributes[attr]) + end + end + def self.login response = nil begin @@ -92,7 +117,13 @@ module OVirt end end + class StorageVolume < Base + human_readable :size + end + class Host < Base + human_readable :memory + def self.find_by_uuid(uuid) find(:first, :params => { :uuid => uuid }) end -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 05/11] Simple helper to format an error message
--- lib/ovirt.rb | 10 ++++++++++ 1 files changed, 10 insertions(+), 0 deletions(-) diff --git a/lib/ovirt.rb b/lib/ovirt.rb index ce51e66..5ce64d5 100644 --- a/lib/ovirt.rb +++ b/lib/ovirt.rb @@ -136,4 +136,14 @@ module OVirt HardwarePool.find(hardware_pool_id) end end + + def self.format_remote_exception(msg, e) + err = Hash.from_xml(e.response.body)["error"] + unless err.nil? + "#{msg}: #{err}" + else + msg + end + end + end -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 06/11] Command to list nodes in a hardware pool
--- lib/ovirt/command/node.rb | 32 ++++++++++++++++++++++++++++++++ 1 files changed, 32 insertions(+), 0 deletions(-) create mode 100644 lib/ovirt/command/node.rb diff --git a/lib/ovirt/command/node.rb b/lib/ovirt/command/node.rb new file mode 100644 index 0000000..0728616 --- /dev/null +++ b/lib/ovirt/command/node.rb @@ -0,0 +1,32 @@ +module OVirt::Command::Node + + module List + def execute(args) + pool = nil + begin + pool = OVirt::HardwarePool.find_by_path(args["pool"]) + rescue ActiveResource::ResourceNotFound + pool = nil + end + unless pool + puts_error "pool #{args["pool"]} not found" + return + end + puts "Pool #{args["pool"]}: #{pool.hosts.size} host(s)" + puts "=" * 75 + pool.hosts.each do |h| + printf "%-36s %s\n", h.hostname, h.state + if args["verbose"] + printf " uuid : %s\n", h.uuid + printf " enabled : %s\n", h.is_disabled == 0 ? "yes" : "no" + printf " arch : %s\n", h.arch + printf " hypervisor: %s\n", h.hypervisor_type + printf " memory : %s\n", h.human_readable_memory + printf " cpus : %d\n", h.cpus.size + puts "-" * 75 + end + end + end + end + +end -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 07/11] Command to list storage in a hardware pool
--- lib/ovirt/command/storage.rb | 31 +++++++++++++++++++++++++++++++ 1 files changed, 31 insertions(+), 0 deletions(-) create mode 100644 lib/ovirt/command/storage.rb diff --git a/lib/ovirt/command/storage.rb b/lib/ovirt/command/storage.rb new file mode 100644 index 0000000..85577e9 --- /dev/null +++ b/lib/ovirt/command/storage.rb @@ -0,0 +1,31 @@ +module OVirt::Command::Storage + + module List + def execute(args) + begin + pools = OVirt::StoragePool.find_by_path(args["pool"]) + rescue ActiveResource::ResourceNotFound => e + puts_error "pool #{args["pool"]} not found" + return + end + puts "Pool #{args["pool"]}: #{pools.size} storage pool(s)" + puts "=" * 75 + pools.each do |sp| + type = sp.nfs? ? "NFS" : "iSCSI" + printf "%3d %-5s %-30s %s\n", sp.id, type, sp.label, sp.state + if args["verbose"] + sp = OVirt::StoragePool.find(sp.id) + sp.storage_volumes.sort_by { |vol| vol.filename }.each do |vol| + if sp.nfs? + printf " %3d %-30s %s\n", vol.id, vol.filename, vol.human_readable_size + else + printf "FIXME\n" + end + end + end + end + puts "-" * 75 + end + end + +end -- 1.6.0.6
--- lib/ovirt.rb | 2 ++ lib/ovirt/command.rb | 11 +++++++++++ 2 files changed, 13 insertions(+), 0 deletions(-) create mode 100644 lib/ovirt/command.rb diff --git a/lib/ovirt.rb b/lib/ovirt.rb index 5ce64d5..07a2c3b 100644 --- a/lib/ovirt.rb +++ b/lib/ovirt.rb @@ -147,3 +147,5 @@ module OVirt end end + +require 'ovirt/command' diff --git a/lib/ovirt/command.rb b/lib/ovirt/command.rb new file mode 100644 index 0000000..bf362f0 --- /dev/null +++ b/lib/ovirt/command.rb @@ -0,0 +1,11 @@ +module OVirt::Command + module Base + def puts_error(msg) + $stderr.puts "error: " + msg + exit_status exit_failure + end + end +end + +require 'ovirt/command/node' +require 'ovirt/command/storage' -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 09/11] Command line driver
--- bin/ovirt | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 84 insertions(+), 0 deletions(-) create mode 100755 bin/ovirt diff --git a/bin/ovirt b/bin/ovirt new file mode 100755 index 0000000..42b7871 --- /dev/null +++ b/bin/ovirt @@ -0,0 +1,84 @@ +#! /usr/bin/ruby + +# FIXME: Remove this, only to ease testing +$:.unshift(File::join(File::dirname(File::dirname(__FILE__)), "lib")) + +# The main oVirt command line client. + +require 'rubygems' +require 'ovirt' +require 'main' + +require 'ruby-debug' + +# This is not really how we want the command line, but a temporary +# approximation + +Main { + mixin :command do + include OVirt::Command::Base + + def run + OVirt::Base::site = params["server"].value + OVirt::Base::login + + hash = params.to_options + ["server", "help"].each { |k| hash.delete(k) } + begin + execute(hash) + rescue ActiveResource::TimeoutError + puts_error "request timed out" + rescue ActiveResource::ServerError => e + puts_error "server error: #{e}" + rescue Errno::ECONNREFUSED => e + puts_error "connection refused" + end + end + end + + mixin :pool_argument do + argument("pool") { + description "the pool on which to perform the operation" + required + } + end + + description "Control an oVirt server from a distance" + + option("server=[SERVER]") { + validate { |val| ! val.nil? } + default ENV["OVIRT_SERVER"] + description <<DESCR +URL of the oVirt server, in the form http://USER:PASSWORD at SERVER/ovirt +DESCR + } + + mode 'node' do + + mixin :pool_argument + + mode 'list' do + option("verbose", "v") + include OVirt::Command::Node::List + mixin :command + end + end + + mode 'storage' do + + mixin :pool_argument + + mode 'list' do + option("verbose", "v") + include OVirt::Command::Storage::List + mixin :command + end + end + + def run + $stderr.puts usage.to_s + end +} +# Want: +# ovirt node /root/hardware/default mv host.example.com /root/hardware/default/subpool +# ovirt storage /root/hardware/default create-pool --type=nfs --ip=... --path=.. -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 10/11] remove individual volumes from a storage pool or an entire storage pool
--- bin/ovirt | 25 +++++++++++++++++++++++++ lib/ovirt/command/storage.rb | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 0 deletions(-) diff --git a/bin/ovirt b/bin/ovirt index 42b7871..8c0bfcf 100755 --- a/bin/ovirt +++ b/bin/ovirt @@ -73,6 +73,31 @@ DESCR include OVirt::Command::Storage::List mixin :command end + + mode 'rm' do + option("pool=POOL", "p") { + description "the ID of the pool" + required + cast :int + } + # --all, --volume and --self are mutually exclusive + + # We identify storage pools and volumes by their ID + # - it would be much nicer if we had some sort of label + # for each, but that requires we change the WUI + option("all", "a") { + description "delete all volumes from the pool" + } + option("volume=VOLUME", "v") { + description "delete the volume with the given id" + cast :int + } + option("self") { + description "delete the pool itself and all its volumes" + } + include OVirt::Command::Storage::Remove + mixin :command + end end def run diff --git a/lib/ovirt/command/storage.rb b/lib/ovirt/command/storage.rb index 85577e9..7cbf78a 100644 --- a/lib/ovirt/command/storage.rb +++ b/lib/ovirt/command/storage.rb @@ -28,4 +28,38 @@ module OVirt::Command::Storage end end + module Remove + def execute(args) + pool, volume, slf, all = args.values_at 'pool', 'volume', 'self', 'all' + + begin + pool = OVirt::StoragePool::find(pool) + rescue ActiveResource::ResourceNotFound + puts_error "error: pool #{pool} not found" + return + end + + begin + if volume + vol = pool.storage_volumes.find { |vol| vol.id == volume } + if vol + vol.destroy + else + $stderr.puts "error: no volume #{volume} in pool #{pool.label}" + exit_status exit_failure + end + elsif all + pool.storage_volumes.each { |vol| vol.destroy } + elsif slf + pool.destroy + else + $stderr.puts "error: you must specify one of --volume, --all, or --self" + exit_status exit_failure + end + rescue ActiveResource::ForbiddenAccess => e + $stderr.puts OVirt::format_remote_exception("Removal denied", e) + exit_status exit_failure + end + end + end end -- 1.6.0.6
David Lutterkort
2009-Jan-26 21:20 UTC
[Ovirt-devel] [PATCH client 11/11] Update the README
--- README | 42 ++++++++++++++++++++++++++++++++---------- 1 files changed, 32 insertions(+), 10 deletions(-) diff --git a/README b/README index ee1db15..ec874b4 100644 --- a/README +++ b/README @@ -1,17 +1,39 @@ -This is a very simple client library for accessing the OVirt API from Ruby. +ovirt-client is a simple command line interface and client library for the +OVirt Server Suite (http://ovirt.org) -The file examples/script.rb contains a script that shows how this is done -in some detail. +Prerequisites +============ -You must have ActiveResource installed, e.g. with 'yum install -rubygem-activeresource' +Before you can run the 'ovirt' command, you must have the activeresource +and main rubygems installed. The version of activeresource must be at least +2.2.2, earlier versions have bugs that make it unusable. -The server is specified with a URL of the form - http://USER:PASSWORD at HOST/ovirt +The simplest way to install these gems (if they are not packaged natively +for your distribution), is by setting up a separate gem repository: -This requires that the server is configured to allow HTTP authentication, -since there are no mechanisms in the API to forward krb5 tickets. + export GEM_HOME=~/gems # could be anywhere + export GEM_PATH=${GEM_HOME}:/usr/lib/ruby/gems/1.8 + mkdir $GEM_HOME + +and then install whatever you are missing, e.g. + + gem install activeresource + gem install main + +(this will all become much easier once we package the command line client) + +Usage +====+ +Run ./bin/ovirt --help + +API Hints +========+ +The client bits are in lib/ + +Before using any of them, you need to authenticate against the server by +calling -Before calling any other method on the API, you need to call OVirt::Base::site = "http://USER:PASSWORD at HOST/ovirt" OVirt::Base::login -- 1.6.0.6