David Lutterkort
2008-Aug-14  23:43 UTC
[Ovirt-devel] OVirt API for hosts and hardware/storage pools
[ Resent. The previous postings didn't have the actual patches in them ] This is a revamp of the API I posted previously. The most important change is that the REST specific controllers have been folded into the existing controllers, so that the WUI and the API share the same business logic, and in particular perform the same permission checks. Once Steve's patches to allow HTTP authentication have been committed, the API can be accessed through the URL http://USER:PASSWORD at SERVER/ovirt. Until then, you either have to hack the config of your OVirt server to allow unauthenticated access and assume that's user 'ovirtadmin', or direct API requests directly at the Mongrel port at SERVER:3000 Patch 6/6 contains a little supporting client code and the example script examples/script.rb that shows what you can do with the API (and how) [1] https://www.redhat.com/archives/ovirt-devel/2008-August/msg00045.html
David Lutterkort
2008-Aug-14  23:43 UTC
[Ovirt-devel] [PATCH 1/6] Select a host in the grid using GET instead of POST
The url_for call must set 'id' to nil to keep the controller from
appending
the ID of the current hardware pool, since we want a URL of the form
/hosts/show/ID where ID is dynamically determined in JavaScript.
Signed-off-by: David Lutterkort <dlutter at redhat.com>
---
 wui/src/app/views/hardware/show_hosts.rhtml |    3 +--
 1 files changed, 1 insertions(+), 2 deletions(-)
diff --git a/wui/src/app/views/hardware/show_hosts.rhtml
b/wui/src/app/views/hardware/show_hosts.rhtml
index f2962cb..38f942d 100644
--- a/wui/src/app/views/hardware/show_hosts.rhtml
+++ b/wui/src/app/views/hardware/show_hosts.rhtml
@@ -49,8 +49,7 @@
     }
     if (selected_ids.length == 1)
     {
-      $('#hosts_selection').load('<%= url_for :controller =>
"host", :action => "show" %>',
-                { id: parseInt(selected_ids[0].substring(3))})
+      $('#hosts_selection').load('<%= url_for :controller =>
"host", :action => "show", :id => nil %>/' +
parseInt(selected_ids[0].substring(3)))
     }
   }
 </script>
-- 
1.5.5.1
- List hosts and filter the list by state, arch, hostname, uuid and
  hardware_pool_id
- Retrieve an individual host
The routing maps this to /ovirt/hosts and /ovirt/hosts/#{id}
Signed-off-by: David Lutterkort <dlutter at redhat.com>
---
 wui/src/app/controllers/host_controller.rb |   28 ++++++++++++++++++++++++----
 wui/src/config/routes.rb                   |    7 +++++++
 2 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/wui/src/app/controllers/host_controller.rb
b/wui/src/app/controllers/host_controller.rb
index 4e4375a..5174c88 100644
--- a/wui/src/app/controllers/host_controller.rb
+++ b/wui/src/app/controllers/host_controller.rb
@@ -18,19 +18,36 @@
 # also available at http://www.gnu.org/copyleft/gpl.html.
 
 class HostController < ApplicationController
+
+  EQ_ATTRIBUTES = [ :state, :arch, :hostname, :uuid,
+                    :hardware_pool_id ]
+
   def index
     list
-    render :action => 'list'
+    respond_to do |format|
+      format.html { render :action => 'list' }
+      format.xml  { render :xml => @hosts.to_xml }
+    end
   end
 
   before_filter :pre_action, :only => [:host_action, :enable, :disable,
:clear_vms]
 
   # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
-  verify :method => :post, :only => [ :destroy, :create, :update ],
+  verify :method => [:post, :put], :only => [ :create, :update ],
+         :redirect_to => { :action => :list }
+  verify :method => [:post, :delete], :only => :destroy,
          :redirect_to => { :action => :list }
 
    def list
-       @hosts = Host.find(:all) if @hosts.nil? 
+     conditions = []
+     EQ_ATTRIBUTES.each do |attr|
+       if params[attr]
+         conditions << "#{attr} = :#{attr}"
+       end
+     end
+     @hosts = Host.find(:all,
+                        :conditions => [conditions.join(" and "),
params],
+                        :order => "id")
    end
 
 
@@ -41,7 +58,10 @@ class HostController < ApplicationController
       #perm errors for ajax should be done differently
       redirect_to :controller => 'dashboard', :action =>
'list'
     end
-    render :layout => 'selection'    
+    respond_to do |format|
+      format.html { render :layout => 'selection' }
+      format.xml { render :xml => @host.to_xml }
+    end
   end
 
   def quick_summary
diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb
index a93ba4b..241cac6 100644
--- a/wui/src/config/routes.rb
+++ b/wui/src/config/routes.rb
@@ -43,4 +43,11 @@ ActionController::Routing::Routes.draw do |map|
   # Install the default route as the lowest priority.
   map.connect ':controller/:action/:id.:format'
   map.connect ':controller/:action/:id'
+
+  # We put routes for the REST API _after_ the default routes so that we
+  # don't disturb existing routes for the WUI
+  # FIXME: Eventually, we want to rename the controllers in a way that makes
+  # REST work out of the box, and use these as the default routes
+  map.resources :hosts, :controller => 'host'
+
 end
-- 
1.5.5.1
- List and filter storage pools by ipaddr, path, target and hardware_pool_id
- Show an individual storage pool
- Create a new storage pool
- Destroy a storage pool
Signed-off-by: David Lutterkort <dlutter at redhat.com>
---
 wui/src/app/controllers/storage_controller.rb |   64 ++++++++++++++++++++----
 wui/src/config/routes.rb                      |    1 +
 2 files changed, 54 insertions(+), 11 deletions(-)
diff --git a/wui/src/app/controllers/storage_controller.rb
b/wui/src/app/controllers/storage_controller.rb
index ffa42bd..7e97764 100644
--- a/wui/src/app/controllers/storage_controller.rb
+++ b/wui/src/app/controllers/storage_controller.rb
@@ -19,17 +19,25 @@
 
 class StorageController < ApplicationController
 
+  EQ_ATTRIBUTES = [ :ip_addr, :export_path, :target,
+                    :hardware_pool_id ]
+
   before_filter :pre_pool_admin, :only => [:refresh]
   before_filter :pre_new2, :only => [:new2]
   before_filter :pre_json, :only => [:storage_volumes_json]
 
   def index
     list
-    render :action => 'list'
+    respond_to do |format|
+      format.html { render :action => 'list' }
+      format.xml { render :xml => @storage_pools.to_xml }
+    end
   end
 
   # GETs should be safe (see http://www.w3.org/2001/tag/doc/whenToUseGet.html)
-  verify :method => :post, :only => [ :destroy, :create, :update ],
+  verify :method => [:post, :put], :only => [ :create, :update ],
+         :redirect_to => { :action => :list }
+  verify :method => [:post, :delete], :only => :destroy,
          :redirect_to => { :action => :list }
 
   def list
@@ -47,7 +55,14 @@ class StorageController < ApplicationController
       end
     else
       #no permissions here yet -- do we disable raw volume list
-      @storage_pools = StoragePool.find(:all)
+      conditions = []
+      EQ_ATTRIBUTES.each { |attr|
+        conditions << "#{attr} = :#{attr}" if params[attr]
+      }
+
+      @storage_pools = StoragePool.find(:all,
+              :conditions => [conditions.join(" and "), params],
+              :order => "id")
     end
   end
 
@@ -56,9 +71,16 @@ class StorageController < ApplicationController
     set_perms(@storage_pool.hardware_pool)
     unless @can_view
       flash[:notice] = 'You do not have permission to view this storage
pool: redirecting to top level'
-      redirect_to :controller => 'dashboard'
+      respond_to do |format|
+        format.html { redirect_to :controller => 'dashboard' }
+        format.xml { head :forbidden }
+      end
+    else
+      respond_to do |format|
+        format.html { render :layout => 'selection' }
+        format.xml { render :xml => @storage_pool.to_xml }
+      end
     end
-    render :layout => 'selection'    
   end
 
   def storage_volumes_json
@@ -113,12 +135,24 @@ class StorageController < ApplicationController
         @storage_pool.save!
         insert_refresh_task
       end
-      render :json => { :object => "storage_pool", :success
=> true,
-                        :alert => "Storage Pool was successfully
created." }
+      respond_to do |format|
+        format.json { render :json => { :object =>
"storage_pool",
+            :success => true,
+            :alert => "Storage Pool was successfully created." } }
+        format.xml { render :xml => @storage_pool,
+            :status => :created,
+            :location => storage_pool_url(@storage_pool)
+        }
+      end
     rescue
       # FIXME: need to distinguish pool vs. task save errors (but should mostly
be pool)
-      render :json => { :object => "storage_pool", :success
=> false,
-                        :errors =>
@storage_pool.errors.localize_error_messages.to_a  }
+      respond_to do |format|
+        format.json {
+          render :json => { :object => "storage_pool", :success
=> false,
+            :errors => @storage_pool.errors.localize_error_messages.to_a  }
}
+        format.xml { render :xml => @storage_pool.errors,
+          :status => :unprocessable_entity }
+      end
     end
   end
 
@@ -213,7 +247,11 @@ class StorageController < ApplicationController
       alert="Failed to delete storage pool."
       success=false
     end
-    render :json => { :object => "storage_pool", :success =>
success, :alert => alert }
+    respond_to do |format|
+      format.json { render :json => { :object =>
"storage_pool",
+          :success => success, :alert => alert } }
+      format.xml { head (success ? :ok : :method_not_allowed) }
+    end
   end
 
   def pre_new
@@ -233,7 +271,11 @@ class StorageController < ApplicationController
     authorize_admin
   end
   def pre_create
-    @storage_pool = StoragePool.factory(params[:storage_type],
params[:storage_pool])
+    pool = params[:storage_pool]
+    unless type = params[:storage_type]
+      type = pool.delete(:storage_type)
+    end
+    @storage_pool = StoragePool.factory(type, pool)
     @perm_obj = @storage_pool.hardware_pool
     @redir_controller = @storage_pool.hardware_pool.get_controller
   end
diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb
index 241cac6..c5e7f90 100644
--- a/wui/src/config/routes.rb
+++ b/wui/src/config/routes.rb
@@ -49,5 +49,6 @@ ActionController::Routing::Routes.draw do |map|
   # FIXME: Eventually, we want to rename the controllers in a way that makes
   # REST work out of the box, and use these as the default routes
   map.resources :hosts, :controller => 'host'
+  map.resources :storage_pools, :controller => 'storage'
 
 end
-- 
1.5.5.1
David Lutterkort
2008-Aug-14  23:43 UTC
[Ovirt-devel] [PATCH 4/6] hardware_pool: search by path
Signed-off-by: David Lutterkort <dlutter at redhat.com>
---
 wui/src/app/models/hardware_pool.rb |   12 ++++++++++++
 1 files changed, 12 insertions(+), 0 deletions(-)
diff --git a/wui/src/app/models/hardware_pool.rb
b/wui/src/app/models/hardware_pool.rb
index 276779f..249d744 100644
--- a/wui/src/app/models/hardware_pool.rb
+++ b/wui/src/app/models/hardware_pool.rb
@@ -97,4 +97,16 @@ class HardwarePool < Pool
     return {:total => total, :labels => labels}
   end
 
+  def self.find_by_path(path)
+      segs = path.split("/")
+      unless segs.shift.empty?
+          raise "Path must be absolute, but is #{path}"
+      end
+      if segs.shift == "default"
+          segs.inject(get_default_pool) do |pool, seg|
+              pool.sub_hardware_pools.find { |p| p.name == seg } if pool
+          end
+      end
+  end
+
 end
-- 
1.5.5.1
- List and filter HW pools by name
- CRUD of individual HW pool
- Hosts and storage pools for a HW pool are accessible as nested
  resources, e.g. /ovirt/hardware_pools/1/hosts?hostname=myhost.example.com
Signed-off-by: David Lutterkort <dlutter at redhat.com>
---
 wui/src/app/controllers/hardware_controller.rb |  145 ++++++++++++++++++++----
 wui/src/config/routes.rb                       |    4 +
 2 files changed, 128 insertions(+), 21 deletions(-)
diff --git a/wui/src/app/controllers/hardware_controller.rb
b/wui/src/app/controllers/hardware_controller.rb
index 019fdd8..ad12d34 100644
--- a/wui/src/app/controllers/hardware_controller.rb
+++ b/wui/src/app/controllers/hardware_controller.rb
@@ -20,7 +20,15 @@
 
 class HardwareController < ApplicationController
 
-  verify :method => :post, :only => [ :destroy, :create, :update ],
+  XML_OPTS  = {
+    :include => [ :storage_pools, :hosts, :quota ]
+  }
+
+  EQ_ATTRIBUTES = [ :name, :parent_id ]
+
+  verify :method => [:post, :put], :only => [ :create, :update ],
+         :redirect_to => { :action => :list }
+  verify :method => [:post, :delete], :only => :destroy,
          :redirect_to => { :action => :list }
 
   before_filter :pre_json, :only => [:vm_pools_json, :users_json, 
@@ -29,19 +37,47 @@ class HardwareController < ApplicationController
                                        :add_storage, :move_storage, 
                                        :create_storage, :delete_storage]
 
+  def index
+    if params[:path]
+      @pools = []
+      pool = HardwarePool.find_by_path(params[:path])
+      @pools << pool if pool
+    else
+      conditions = []
+      EQ_ATTRIBUTES.each do |attr|
+        if params[attr]
+          conditions << "#{attr} = :#{attr}"
+        end
+      end
+
+      @pools = HardwarePool.find(:all,
+                 :conditions => [conditions.join(" and "),
params],
+                 :order => "id")
+    end
+
+    respond_to do |format|
+      format.xml { render :xml => @pools.to_xml(XML_OPTS) }
+    end
+  end
 
   def show
     set_perms(@perm_obj)
     unless @can_view
       flash[:notice] = 'You do not have permission to view this hardware
pool: redirecting to top level'
-      redirect_to :controller => "dashboard"
+      respond_to do |format|
+        format.html { redirect_to :controller => "dashboard" }
+        format.xml { head :forbidden }
+      end
       return
     end
-    if params[:ajax]
-      render :layout => 'tabs-and-content'
-    end
-    if params[:nolayout]
-      render :layout => false
+    respond_to do |format|
+      format.html {
+        render :layout => 'tabs-and-content' if params[:ajax]
+        render :layout => false if params[:nolayout]
+      }
+      format.xml {
+        render :xml => @pool.to_xml(XML_OPTS)
+      }
     end
   end
   
@@ -199,15 +235,29 @@ class HardwareController < ApplicationController
     resource_ids = resource_ids_str.split(",").collect {|x| x.to_i}
if resource_ids_str
     begin
       @pool.create_with_resources(@parent, resource_type, resource_ids)
-      reply = { :object => "pool", :success => true, 
-                        :alert => "Hardware Pool was successfully
created." }
-      reply[:resource_type] = resource_type if resource_type
-      render :json => reply
+      respond_to do |format|
+        format.html {
+          reply = { :object => "pool", :success => true,
+            :alert => "Hardware Pool was successfully created." }
+          reply[:resource_type] = resource_type if resource_type
+          render :json => reply
+        }
+        format.xml {
+          render :xml => @pool.to_xml(XML_OPTS),
+          :status => :created,
+          :location => hardware_pool_url(@pool)
+        }
+      end
     rescue
-      render :json => { :object => "pool", :success =>
false,
-                        :errors => @pool.errors.localize_error_messages.to_a
}
+      respond_to do |format|
+        format.json {
+          render :json => { :object => "pool", :success =>
false,
+            :errors => @pool.errors.localize_error_messages.to_a  }
+        }
+        format.xml  { render :xml => @pool.errors,
+          :status => :unprocessable_entity }
+      end
     end
-
   end
 
   def edit
@@ -215,13 +265,51 @@ class HardwareController < ApplicationController
   end
 
   def update
+    if params[:hardware_pool]
+      # FIXME: For the REST API, we allow moving hosts/storage through
+      # update.  It makes that operation convenient for clients, though makes
+      # the implementation here somewhat ugly.
+      [:hosts, :storage_pools].each do |k|
+        objs = params[:hardware_pool].delete(k)
+        ids = objs.reject{ |obj| obj[:hardware_pool_id] == @pool.id}.
+          collect{ |obj| obj[:id] }
+        if ids.size > 0
+          # FIXME: use self.move_hosts/self.move_storage
+          if k == :hosts
+            @pool.move_hosts(ids, @pool.id)
+          else
+            @pool.move_storage(ids, @pool.id)
+          end
+        end
+      end
+      # FIXME: HTML views should use :hardware_pool
+      params[:pool] = params.delete(:hardware_pool)
+    end
+
     begin
       @pool.update_attributes!(params[:pool])
-      render :json => { :object => "pool", :success => true,
-                        :alert => "Hardware Pool was successfully
modified." }
+      respond_to do |format|
+        format.json {
+          render :json => { :object => "pool", :success =>
true,
+            :alert => "Hardware Pool was successfully modified." }
+        }
+        format.xml {
+          render :xml => @pool.to_xml(XML_OPTS),
+          :status => :created,
+          :location => hardware_pool_url(@pool)
+        }
+      end
     rescue
-      render :json => { :object => "pool", :success =>
false,
-                        :errors =>
@pool.errors.localize_error_messages.to_a}
+      respond_to do |format|
+        format.json {
+          render :json => { :object => "pool", :success =>
false,
+            :errors => @pool.errors.localize_error_messages.to_a}
+        }
+        format.xml {
+          render :xml => @pool.errors,
+          :status => :unprocessable_entity
+        }
+      end
     end
   end
 
@@ -313,19 +401,27 @@ class HardwareController < ApplicationController
     if not(parent)
       alert="You can't delete the top level Hardware pool."
       success=false
+      status=:method_not_allowed
     elsif not(@pool.children.empty?)
       alert = "You can't delete a Pool without first deleting its
children."
       success=false
+      status=:conflict
     else
       if @pool.move_contents_and_destroy
         alert="Hardware Pool was successfully deleted."
         success=true
+        status=:ok
       else
         alert="Failed to delete hardware pool."
         success=false
+        status=:internal_server_error
       end
     end
-    render :json => { :object => "pool", :success =>
success, :alert => alert }
+    respond_to do |format|
+      format.json { render :json => { :object => "pool",
:success => success,
+                                      :alert => alert } }
+      format.xml { head status }
+    end
   end
 
   private
@@ -337,8 +433,15 @@ class HardwareController < ApplicationController
     @current_pool_id=@parent.id
   end
   def pre_create
-    @pool = HardwarePool.new(params[:pool])
-    @parent = Pool.find(params[:parent_id])
+    # FIXME: REST and browsers send params differently. Should be fixed
+    # in the views
+    if params[:pool]
+      @pool = HardwarePool.new(params[:pool])
+      @parent = Pool.find(params[:parent_id])
+    else
+      @pool = HardwarePool.new(params[:hardware_pool])
+      @parent = Pool.find(params[:hardware_pool][:parent_id])
+    end
     @perm_obj = @parent
     @current_pool_id=@parent.id
   end
diff --git a/wui/src/config/routes.rb b/wui/src/config/routes.rb
index c5e7f90..8fd64a9 100644
--- a/wui/src/config/routes.rb
+++ b/wui/src/config/routes.rb
@@ -50,5 +50,9 @@ ActionController::Routing::Routes.draw do |map|
   # REST work out of the box, and use these as the default routes
   map.resources :hosts, :controller => 'host'
   map.resources :storage_pools, :controller => 'storage'
+  map.resources :hardware_pools, :controller => 'hardware' do
|hardware_pools|
+    hardware_pools.resources :hosts, :controller => 'host'
+    hardware_pools.resources :storage_pools, :controller =>
'storage'
+  end
 
 end
-- 
1.5.5.1
David Lutterkort
2008-Aug-14  23:43 UTC
[Ovirt-devel] [PATCH 6/6] Sample client code that shows how to use the API
Signed-off-by: David Lutterkort <dlutter at redhat.com>
---
 wui/client/README             |   14 ++++++
 wui/client/examples/script.rb |   99 +++++++++++++++++++++++++++++++++++++++++
 wui/client/lib/ovirt.rb       |   63 ++++++++++++++++++++++++++
 3 files changed, 176 insertions(+), 0 deletions(-)
 create mode 100644 wui/client/README
 create mode 100755 wui/client/examples/script.rb
 create mode 100644 wui/client/lib/ovirt.rb
diff --git a/wui/client/README b/wui/client/README
new file mode 100644
index 0000000..4bdce27
--- /dev/null
+++ b/wui/client/README
@@ -0,0 +1,14 @@
+This is a very simple client library for accessing the OVirt API from Ruby.
+
+The file examples/script.rb contains a script that shows how this is done
+in some detail.
+
+You must have ActiveResource installed, e.g. with 'yum install
+rubygem-activeresource'
+
+The server is specified with a URL of the form
+  http://USER:PASSWORD at HOST/ovirt
+
+This requires that the server is configured to allow HTTP authentication,
+since there are no mechanisms in the API to forward krb5 tickets. You can
+also try and connect to Mongrel running on HOST:3000 directly.
diff --git a/wui/client/examples/script.rb b/wui/client/examples/script.rb
new file mode 100755
index 0000000..2103ad7
--- /dev/null
+++ b/wui/client/examples/script.rb
@@ -0,0 +1,99 @@
+#! /usr/bin/ruby
+
+# Sample script that shows how to use the OVirt API
+
+require 'pp'
+require 'rubygems'
+require 'activeresource'
+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
+end
+
+def element_path(obj)
+    "[#{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
+end
+
+# Plumbing so we can find the OVirt server
+# "http://ovirt.watzmann.net:3000/ovirt/rest"
+PROGNAME=File::basename($0)
+OVirt::Base.site = ENV["OVIRT_SERVER"]
+opts = OptionParser.new("#{PROGNAME} GLOBAL_OPTS")
+opts.separator("  Run some things against an OVirt server. The server is
specified with")
+opts.separator("  the -s option as a URL of the form http://USER:PASSWORD
at SERVER/ovirt")
+opts.separator("")
+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
+end
+
+opts.order(ARGV)
+
+unless OVirt::Base.site
+    $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
+end
+
+# Get a single host by name
+host = OVirt::Host.find_by_hostname("node3.priv.ovirt.org")
+puts "#{host.uuid} has id #{host.id}"
+
+# What's in the default pool
+defpool = OVirt::HardwarePool.default_pool
+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" } )
+end
+
+# Move some hosts around
+puts
+if defpool.hosts.size > 1
+    move_random_host(defpool.hosts, mypool)
+elsif mypool.hosts.size > 0
+    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
+end
+
+storage_pool = OVirt::StoragePool.create( { :storage_type =>
"NFS",
+                                            :hardware_pool_id => mypool.id,
+                                            :ip_addr =>
"192.168.122.50",
+                                            :export_path =>
"/exports/pool1" } )
+puts "Created storage pool #{storage_pool.id}"
+
+# For some reason, mypool.reload doesn't work here
+mypool = OVirt::HardwarePool.find_by_path("/default/mypool")
+print_pool(mypool)
diff --git a/wui/client/lib/ovirt.rb b/wui/client/lib/ovirt.rb
new file mode 100644
index 0000000..48739f4
--- /dev/null
+++ b/wui/client/lib/ovirt.rb
@@ -0,0 +1,63 @@
+require 'pp'
+require 'rubygems'
+require 'activeresource'
+
+module OVirt
+    class Base < ActiveResource::Base ; 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 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
+    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
+    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 hardware_pool
+            HardwarePool.find(hardware_pool_id)
+        end
+    end
+end
-- 
1.5.5.1
Perry N. Myers
2008-Aug-27  18:35 UTC
[Ovirt-devel] OVirt API for hosts and hardware/storage pools
David Lutterkort wrote:> [ Resent. The previous postings didn't have the actual patches in them ] > > This is a revamp of the API I posted previously. The most important change > is that the REST specific controllers have been folded into the existing > controllers, so that the WUI and the API share the same business logic, and > in particular perform the same permission checks. > > Once Steve's patches to allow HTTP authentication have been committed, the > API can be accessed through the URL > http://USER:PASSWORD at SERVER/ovirt. Until then, you either have to hack the > config of your OVirt server to allow unauthenticated access and assume > that's user 'ovirtadmin', or direct API requests directly at the Mongrel > port at SERVER:3000 > > Patch 6/6 contains a little supporting client code and the example script > examples/script.rb that shows what you can do with the API (and how) > > [1] https://www.redhat.com/archives/ovirt-devel/2008-August/msg00045.htmlI didn't review the code since I'm not a Ruby hacker, but I did apply these and tested basic functionality. There were no regressions that I saw and the API code mostly worked, so I think this can be ACKed. Of course we make no committments about API stability for a while yet. All things are subject to change for now... Perry
David Lutterkort
2008-Aug-27  21:04 UTC
[Ovirt-devel] OVirt API for hosts and hardware/storage pools
On Thu, 2008-08-14 at 16:43 -0700, David Lutterkort wrote:> This is a revamp of the API I posted previously. The most important change > is that the REST specific controllers have been folded into the existing > controllers, so that the WUI and the API share the same business logic, and > in particular perform the same permission checks.I just committed this, with a small change to HardwarePool.find_by_path to avoid hardcoding the root pool's name. David