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