---
 client/lib/dcloud/base_model.rb       |   80 ----
 client/lib/dcloud/hardware_profile.rb |  124 -------
 client/lib/dcloud/image.rb            |   56 ---
 client/lib/dcloud/instance.rb         |  142 -------
 client/lib/dcloud/realm.rb            |   57 ---
 client/lib/dcloud/state.rb            |   30 --
 client/lib/dcloud/storage_snapshot.rb |   59 ---
 client/lib/dcloud/storage_volume.rb   |   63 ----
 client/lib/dcloud/transition.rb       |   35 --
 client/lib/deltacloud.rb              |  647 ++++++++++++++++-----------------
 10 files changed, 321 insertions(+), 972 deletions(-)
 delete mode 100644 client/lib/dcloud/base_model.rb
 delete mode 100644 client/lib/dcloud/hardware_profile.rb
 delete mode 100644 client/lib/dcloud/image.rb
 delete mode 100644 client/lib/dcloud/instance.rb
 delete mode 100644 client/lib/dcloud/realm.rb
 delete mode 100644 client/lib/dcloud/state.rb
 delete mode 100644 client/lib/dcloud/storage_snapshot.rb
 delete mode 100644 client/lib/dcloud/storage_volume.rb
 delete mode 100644 client/lib/dcloud/transition.rb

diff --git a/client/lib/dcloud/base_model.rb b/client/lib/dcloud/base_model.rb
deleted file mode 100644
index 0f210a4..0000000
--- a/client/lib/dcloud/base_model.rb
+++ /dev/null
@@ -1,80 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-
-module DCloud
-    class BaseModel
-
-      def self.xml_tag_name(name=nil)
-        unless ( name.nil? )
-          @xml_tag_name = name
-        end
-        @xml_tag_name || self.class.name.downcase.to_sym
-      end
-
-
-      def self.attribute(attr)
-        build_reader attr
-      end
-
-      def self.build_reader(attr)
-        eval "
-          def #{attr}
-            check_load_payload
-            @#{attr}
-          end
-        "
-      end
-
-      attr_reader :uri
-
-      def initialize(client, uri=nil, xml=nil)
-        @client      = client
-        @uri         = uri
-        @loaded      = false
-        load_payload( xml )
-      end
-
-      def id()
-        check_load_payload
-        @id
-      end
-
-
-      protected
-
-      attr_reader :client
-
-      def check_load_payload()
-        return if @loaded
-        xml = @client.fetch_resource( self.class.xml_tag_name.to_sym, @uri )
-        load_payload(xml)
-      end
-
-      def load_payload(xml=nil)
-        unless ( xml.nil? )
-          @loaded = true
-          @id = xml.text( 'id' )
-        end
-      end
-
-      def unload
-        @loaded = false
-      end
-
-    end
-end
diff --git a/client/lib/dcloud/hardware_profile.rb 
b/client/lib/dcloud/hardware_profile.rb
deleted file mode 100644
index 02ee303..0000000
--- a/client/lib/dcloud/hardware_profile.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-
-require 'dcloud/base_model'
-
-module DCloud
-    class HardwareProfile < BaseModel
-
-      class Property
-        class Range
-          attr_reader :first, :last
-          def initialize(element)
-            if element
-              @first = element.attributes['first']
-              @last = element.attributes['last']
-            end
-          end
-          def present?
-            ! @first.nil?
-          end
-        end
-        class Enum
-          attr_reader :entries
-          def initialize(element)
-            @entries = []
-            if element
-              element.get_elements( 'entry' ).each do |entry|
-                @entries << entry.attributes['value']
-              end
-            end
-          end
-          def present?
-            ! @entries.empty?
-          end
-        end
-        attr_reader :name, :kind, :unit, :value, :range, :enum
-
-        def initialize(xml, name)
-          @name = name
-          p = REXML::XPath.first(xml, "proper...@name = '#{name}']")
-          if p
-            @value = p.attributes['value']
-            @unit = p.attributes['unit']
-            @kind = p.attributes['kind']
-            @range = Range.new(p.get_elements('range')[0]) if @kind=='range'
-            @enum = Enum.new(p.get_elements('enum')[0]) if @kind=='enum'
-          end
-        end
-
-        def present?
-          ! @value.nil?
-        end
-
-        # FIXME: how to  range/enum/kind bits fit into this?
-        def to_s
-          v = @value || "---"
-          u = @unit || ""
-          u = "" if ["label", "count"].include?(u)
-          "#{v} #{u}"
-        end
-      end
-
-      class FloatProperty < Property
-        def initialize(xml, name)
-          super(xml, name)
-          @value = @value.to_f if @value
-        end
-      end
-
-      class IntegerProperty < Property
-        def initialize(xml, name)
-          super(xml, name)
-          @value = @value.to_i if @value
-        end
-      end
-
-      xml_tag_name :hardware_profile
-
-      attribute :memory
-      attribute :storage
-      attribute :cpu
-      attribute :architecture
-
-      def initialize(client, uri, xml=nil)
-        super( client, uri, xml )
-      end
-
-      def load_payload(xml=nil)
-        super(xml)
-        unless xml.nil?
-          @memory = FloatProperty.new(xml, 'memory')
-          @storage = FloatProperty.new(xml, 'storage')
-          @cpu = IntegerProperty.new(xml, 'cpu')
-          @architecture = Property.new(xml, 'architecture')
-        end
-      end
-
-      def to_plain
-        sprintf("%-15s | %-6s | %10s | %10s ", id[0, 15],
-                architecture.to_s[0,6], memory.to_s[0,10], storage.to_s[0,10])
-      end
-
-      private
-      def property_value(xml, name)
-        p = REXML::XPath.first(xml, "proper...@name = '#{name}']")
-        p ? p.attributes['value'] : ""
-      end
-    end
-end
diff --git a/client/lib/dcloud/image.rb b/client/lib/dcloud/image.rb
deleted file mode 100644
index a44f873..0000000
--- a/client/lib/dcloud/image.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-
-require 'dcloud/base_model'
-
-module DCloud
-    class Image < BaseModel
-
-      xml_tag_name :image
-
-      attribute :description
-      attribute :owner_id
-      attribute :architecture
-      attribute :name
-
-      def initialize(client, uri, xml=nil)
-        super( client, uri, xml )
-      end
-
-      def load_payload(xml)
-        super( xml )
-        unless xml.nil?
-          @description = xml.text( 'description' )
-          @owner_id = xml.text( 'owner_id' )
-          @name = xml.text( 'name' )
-          @architecture = xml.text( 'architecture' )
-        end
-      end
-
-      def to_plain
-        sprintf("%-10s | %-20s | %-6s | %-20s | %15s",
-          self.id[0,10],
-          self.name ? self.name[0, 20]: 'unknown',
-          self.architecture[0,6],
-          self.description[0,20],
-          self.owner_id[0,15]
-        )
-      end
-
-    end
-end
diff --git a/client/lib/dcloud/instance.rb b/client/lib/dcloud/instance.rb
deleted file mode 100644
index 4898083..0000000
--- a/client/lib/dcloud/instance.rb
+++ /dev/null
@@ -1,142 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-
-require 'dcloud/base_model'
-
-module DCloud
-
-    class InstanceProfile
-      attr_reader :hardware_profile, :id
-
-      def initialize(client, xml)
-        @hardware_profile = HardwareProfile.new(client, xml.attributes['href'])
-        @properties = {}
-        @id = xml.text("id")
-        xml.get_elements('property').each do |prop|
-          @properties[prop.attributes['name'].to_sym] = {
-            :value => prop.attributes['value'],
-            :unit => prop.attributes['unit'],
-            :kind => prop.attributes['kind'].to_sym
-          }
-        end
-      end
-
-      def [](prop)
-        p = @properties[prop]
-        p ? p[:value] : nil
-      end
-
-      def property(prop)
-        @properties[prop]
-      end
-    end
-
-    class Instance < BaseModel
-
-      xml_tag_name :instance
-
-      attribute :name
-      attribute :owner_id
-      attribute :public_addresses
-      attribute :private_addresses
-      attribute :state
-      attribute :actions
-      attribute :image
-      attribute :realm
-      attribute :action_urls
-      attribute :instance_profile
-
-      def initialize(client, uri, xml=nil)
-        @action_urls = {}
-        super( client, uri, xml )
-      end
-
-      def to_plain
-        sprintf("%-15s | %-15s | %-15s | %10s | %32s | %32s",
-          self.id ? self.id[0,15] : '-',
-          self.name ? self.name[0,15] : 'unknown',
-          self.image.name ? self.image.name[0,15] : 'unknown',
-          self.state ? self.state.to_s[0,10] : 'unknown',
-          self.public_addresses.join(',')[0,32],
-          self.private_addresses.join(',')[0,32]
-          )
-      end
-
-      def start!()
-        url = action_urls['start']
-        throw Exception.new( "Unable to start" ) unless url
-        client.post_instance( url )
-        unload
-      end
-
-      def reboot!()
-        url = action_urls['reboot']
-        throw Exception.new( "Unable to reboot" ) unless url
-        client.post_instance( url )
-        unload
-      end
-
-      def stop!()
-        url = action_urls['stop']
-        throw Exception.new( "Unable to stop" ) unless url
-        client.post_instance( url )
-        unload
-      end
-
-      def destroy!()
-        url = action_urls['destroy']
-        throw Exception.new( "Unable to destroy" ) unless url
-        client.post_instance( url )
-        unload
-      end
-
-      def load_payload(xml=nil)
-        super(xml)
-        unless xml.nil?
-          @owner_id = xml.text('owner_id')
-          @name     = xml.text('name')
-          @public_addresses = []
-          xml.get_elements( 'public-addresses/address' ).each do |address|
-            @public_addresses << address.text
-          end
-          @private_addresses = []
-          xml.get_elements( 'private-addresses/address' ).each do |address|
-            @private_addresses << address.text
-          end
-          image_uri = xml.get_elements( 'image' )[0].attributes['href']
-          @image = Image.new( @client, image_uri )
-          # Only use realms if they are there
-          if (!xml.get_elements( 'realm' ).empty?)
-              realm_uri = xml.get_elements( 'realm' )[0].attributes['href']
-              @realm = Realm.new( @client, realm_uri )
-          end
-          instance_profile = xml.get_elements( 'hardware-profile' ).first
-          @instance_profile = InstanceProfile.new( @client, instance_profile )
-          @state = xml.text( 'state' )
-          @actions = []
-          xml.get_elements( 'actions/link' ).each do |link|
-            action_name = link.attributes['rel']
-            if ( action_name )
-              @actions << link.attributes['rel']
-              @action_urls[ link.attributes['rel'] ] = link.attributes['href']
-            end
-          end
-        end
-      end
-    end
-end
diff --git a/client/lib/dcloud/realm.rb b/client/lib/dcloud/realm.rb
deleted file mode 100644
index b5ae8ea..0000000
--- a/client/lib/dcloud/realm.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-require 'dcloud/base_model'
-
-module DCloud
-    class Realm < BaseModel
-
-      xml_tag_name :realm
-
-      attribute :name
-      attribute :state
-      attribute :limit
-
-      def initialize(client, uri, xml=nil)
-        super( client, uri, xml )
-      end
-
-      def load_payload(xml=nil)
-        super(xml)
-        unless xml.nil?
-          @name = xml.text( 'name' )
-          @state = xml.text( 'state' )
-          @limit = xml.text( 'limit' )
-          if ( @limit.nil? || @limit == '' )
-            @limit = :unlimited
-          else
-            @limit = @limit.to_f
-          end
-        end
-      end
-
-      def to_plain
-        sprintf("%-10s | %-15s | %-5s | %10s GB",
-          self.id[0, 10],
-          self.name[0, 15],
-          self.state[0,5],
-          self.limit.to_s[0,10]
-        )
-      end
-
-    end
-end
diff --git a/client/lib/dcloud/state.rb b/client/lib/dcloud/state.rb
deleted file mode 100644
index 131b6d9..0000000
--- a/client/lib/dcloud/state.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-module DCloud
-    class State
-
-      attr_accessor :name
-      attr_accessor :transitions
-
-      def initialize(name)
-        @name = name
-        @transitions = []
-      end
-
-    end
-end
diff --git a/client/lib/dcloud/storage_snapshot.rb 
b/client/lib/dcloud/storage_snapshot.rb
deleted file mode 100644
index 107f0d7..0000000
--- a/client/lib/dcloud/storage_snapshot.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-
-require 'dcloud/base_model'
-
-module DCloud
-    class StorageSnapshot < BaseModel
-
-      xml_tag_name :storage_snapshot
-
-      attribute :created
-      attribute :state
-      attribute :storage_volume
-
-      def to_plain
-        sprintf("%-10s | %-15s | %-6s | %15s",
-          self.id[0,10],
-          self.storage_volume.name ? self.storage_volume.name[0, 15] : 
'unknown',
-          self.state ? self.state[0,6] : 'unknown',
-          self.created ? self.created[0,15] : 'unknown'
-        )
-      end
-
-      def initialize(client, uri, xml=nil)
-        super( client, uri, xml )
-      end
-
-      def load_payload(xml=nil)
-        super(xml)
-        unless xml.nil?
-          @created = xml.text( 'created' )
-          @state = xml.text( 'state' )
-          storage_volumes = xml.get_elements( 'storage-volume' )
-          if ( ! storage_volumes.empty? )
-            storage_volume = storage_volumes.first
-            storage_volume_href = storage_volume.attributes['href']
-            if ( storage_volume_href )
-              @storage_volume = StorageVolume.new( @client, 
storage_volume_href )
-            end
-          end
-        end
-      end
-    end
-end
diff --git a/client/lib/dcloud/storage_volume.rb 
b/client/lib/dcloud/storage_volume.rb
deleted file mode 100644
index 7effd2d..0000000
--- a/client/lib/dcloud/storage_volume.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-require 'dcloud/base_model'
-
-module DCloud
-    class StorageVolume < BaseModel
-
-      xml_tag_name :storage_volume
-
-      attribute :created
-      attribute :state
-      attribute :capacity
-      attribute :device
-      attribute :instance
-
-      def initialize(client, uri, xml=nil)
-        super( client, uri, xml )
-      end
-
-      def to_plain
-        sprintf("%-10s | %15s GB | %-10s | %-10s | %-15s",
-          self.id[0,10],
-          self.capacity ? self.capacity.to_s[0,15] : 'unknown',
-          self.device ? self.device[0,10] : 'unknown',
-          self.state ? self.state[0,10] : 'unknown',
-          self.instance ? self.instance.name[0,15] : 'unknown'
-        )
-      end
-
-      def load_payload(xml=nil)
-        super(xml)
-        unless xml.nil?
-          @created = xml.text( 'created' )
-          @state = xml.text( 'state' )
-          @capacity = xml.text( 'capacity' ).to_f
-          @device = xml.text( 'device' )
-          instances = xml.get_elements( 'instance' )
-          if ( ! instances.empty? )
-            instance = instances.first
-            instance_href = instance.attributes['href']
-            if ( instance_href )
-              @instance = Instance.new( @client, instance_href )
-            end
-          end
-        end
-      end
-    end
-end
diff --git a/client/lib/dcloud/transition.rb b/client/lib/dcloud/transition.rb
deleted file mode 100644
index cde4598..0000000
--- a/client/lib/dcloud/transition.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# Copyright (C) 2009  Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
-
-
-module DCloud
-    class Transition
-
-      attr_accessor :to
-      attr_accessor :action
-
-      def initialize(to, action)
-        @to = to
-        @action = action
-      end
-
-      def auto?()
-        @action.nil?
-      end
-
-    end
-end
diff --git a/client/lib/deltacloud.rb b/client/lib/deltacloud.rb
index 7593a56..25da777 100644
--- a/client/lib/deltacloud.rb
+++ b/client/lib/deltacloud.rb
@@ -15,404 +15,399 @@
 # License along with this library; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  
USA
 
+require 'nokogiri'
 require 'rest_client'
-require 'rexml/document'
-require 'logger'
-require 'dcloud/hardware_profile'
-require 'dcloud/realm'
-require 'dcloud/image'
-require 'dcloud/instance'
-require 'dcloud/storage_volume'
-require 'dcloud/storage_snapshot'
-require 'dcloud/state'
-require 'dcloud/transition'
 require 'base64'
+require 'logger'
 
-class DeltaCloud
+module DeltaCloud
 
-  attr_accessor :logger
-  attr_reader   :api_uri
-  attr_reader   :entry_points
-  attr_reader   :driver_name
-  attr_reader   :last_request_xml
-  attr_reader   :features
+  def self.new(user_name, password, api_url, &block)
+    API.new(user_name, password, api_url, &block)
+  end
 
   def self.driver_name(url)
-    DeltaCloud.new( nil, nil, url) do |client|
-      return client.driver_name
+    API.new(nil, nil, url).driver_name
+  end
+
+  class API
+    attr_accessor :logger
+    attr_reader   :api_uri, :driver_name, :api_version, :features, 
:entry_points
+
+    def initialize(user_name, password, api_url, opts={}, &block)
+      @logger = opts[:verbose] ? Logger.new(STDERR) : []
+      @username, @password = user_name, password
+      @api_uri = URI.parse(api_url)
+      @entry_points = {}
+      @verbose = opts[:verbose] || false
+      @features = {}
+      discover_entry_points unless discovered?
+      yield self if block_given?
     end
-  end
-
-  def initialize(name, password, api_uri, opts={}, &block)
-    @logger       = Logger.new( STDERR )
-    @name         = name
-    @password     = password
-    @api_uri      = URI.parse( api_uri )
-    @entry_points = {}
-    @verbose      = opts[:verbose]
-    @features = {}
-    discover_entry_points
-    connect( &block )
-    self
-  end
-
-
-  def connect(&block)
-    @http = RestClient::Resource.new( api_uri.to_s , :accept => 
'application/xml' )
-    discover_entry_points
-    block.call( self ) if block
-    self
-  end
 
-  def api_host
-    @api_uri.host
-  end
-
-  def api_port
-    @api_uri.port
-  end
-
-  def api_path
-    @api_uri.path
-  end
-
-  def feature?(collection, name)
-    @features.has_key?(collection) && @features[collection].include?(name)
-  end
+    def connect(&block)
+      yield self
+    end
 
-  def hardware_profiles(opts={})
-    hardware_profiles = []
-    request(entry_points[:hardware_profiles], :get, opts) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'hardware-profiles/hardware-profile' ).each do |hwp|
-        uri = hwp.attributes['href']
-        hardware_profiles << DCloud::HardwareProfile.new( self, uri, hwp )
+    # Define API URL fragment methods
+    [:host, :port, :path].each do |item|
+      define_method :"api_#{item}" do
+        @api_uri.send(item)
       end
     end
-    hardware_profiles
-  end
 
-  def hardware_profile(id)
-    request( entry_points[:hardware_profiles], :get, {:id=>id } ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( '/hardware-profile' ).each do |hwp|
-        uri = hwp.attributes['href']
-        return DCloud::HardwareProfile.new( self, uri, hwp )
+    # Define methods based on 'rel' attribute in entry point
+    # Two methods are declared: 'images' and 'image'
+    def declare_entry_points_methods(entry_points)
+      logger = @logger
+      API.instance_eval do
+        entry_points.keys.select {|k| [:instance_states].include?(k)==false 
}.each do |model|
+          define_method model do |*args|
+            request(:get, "/#{model}", args.first) do |response|
+              # Define a new class based on model name
+              c = Kernel.define_class("#{model.to_s.classify}")
+              # Create collection from index operation
+              base_object_collection(c, model, response)
+            end
+          end
+          logger << "[API] Added method #{model}\n"
+          define_method :"#{model.to_s.singularize}" do |*args|
+            request(:get, "/#{model}/#{args[0]}") do |response|
+              # Define a new class based on model name
+              c = Kernel.define_class("#{model.to_s.classify}")
+              # Build class for returned object
+              base_object(c, model, response)
+            end
+          end
+          logger << "[API] Added method #{model.to_s.singularize}\n"
+          define_method :"fetch_#{model.to_s.singularize}" do |url|
+            id = url.grep(/\/#{model}\/(.*)$/)
+            self.send(model.to_s.singularize.to_sym, $1)
+          end
+        end
       end
     end
-  end
 
-  def fetch_hardware_profile(uri)
-    xml = fetch_resource( :hardware_profile, uri )
-    return DCloud::HardwareProfile.new( self, uri, xml ) if xml
-    nil
-  end
+    def base_object_collection(c, model, response)
+      collection = []
+      Nokogiri::XML(response).xpath("#{model}/#{model.to_s.singularize}").each 
do |item|
+        c.instance_eval do
+          attr_accessor :id
+          attr_accessor :uri
+        end
+        collection << xml_to_class(c, item)
+      end
+      return collection
+    end
 
-  def fetch_resource(type, uri)
-    request( uri ) do |response|
-      doc = REXML::Document.new( response )
-      if ( doc.root && ( doc.root.name == type.to_s.gsub( /_/, '-' ) ) )
-        return doc.root
+    # Add default attributes [id and href] to class
+    def base_object(c, model, response)
+      obj = nil
+      Nokogiri::XML(response).xpath("#{model.to_s.singularize}").each do |item|
+        c.instance_eval do
+          attr_accessor :id
+          attr_accessor :uri
+        end
+        obj = xml_to_class(c, item)
       end
+      return obj
     end
-    nil
-  end
 
-  def fetch_documentation(collection, operation=nil)
-    response = @http["docs/#{collection}#{operation ? "/#{operation}" : 
''}"].get(:accept => "application/xml")
-    doc = REXML::Document.new( response.to_s )
-    if operation.nil?
-      docs = {
-        :name => doc.get_elements('docs/collection').first.attributes['name'],
-        :description => 
doc.get_elements('docs/collection/description').first.text,
-        :operations => []
-      }
-      doc.get_elements('docs/collection/operations/operation').each do 
|operation|
-        p = {}
-        p[:name] = operation.attributes['name']
-        p[:description] = operation.get_elements('description').first.text
-        p[:parameters] = []
-        operation.get_elements('parameter').each do |param|
-          p[:parameters] << param.attributes['name']
+    # Convert XML response to defined Ruby Class
+    def xml_to_class(c, item)
+      obj = c.new
+      # Set default attributes
+      obj.id = item['id']
+      api = self
+      c.instance_eval do
+        define_method :client do
+          api
         end
-        docs[:operations] << p
       end
-    else
-      docs = {
-        :name => doc.get_elements('docs/operation').attributes['name'],
-        :description => 
doc.get_elements('docs/operation/description').first.text,
-        :parameters => []
-      }
-      doc.get_elements('docs/operation/parameter').each do |param|
-        docs[:parameters] << param.attributes['name']
+      obj.uri = item['href']
+      logger = @logger
+      logger << "[DC] Creating class #{obj.class.name}\n"
+      obj.instance_eval do
+        # Declare methods for all attributes in object
+        item.xpath('./*').each do |attribute|
+          # If attribute is a link to another object then
+          # create a method which request this object from API
+          if api.entry_points.keys.include?(:"#{attribute.name}s")
+            c.instance_eval do
+              define_method :"#{attribute.name}" do
+                client.send(:"#{attribute.name}", attribute['id'] )
+              end
+              logger << "[DC] Added #{attribute.name} to class 
#{obj.class.name}\n"
+            end
+          else
+            # Define methods for other attributes
+            c.instance_eval do
+              case attribute.name
+                # When response cointains 'link' block, declare
+                # methods to call links inside. This is used for instance
+                # to dynamicaly create .stop!, .start! methods
+                when "actions":
+                  actions = []
+                  attribute.xpath('link').each do |link|
+                    actions << [link['rel'], link[:href]]
+                    define_method :"#{link['rel']}!" do
+                      client.request(:"#{link['method']}", link['href'], {}, 
{})
+                      client.send(:"#{item.name}", item['id'])
+                    end
+                  end
+                  define_method :actions do
+                    actions.collect { |a| a.first }
+                  end
+                  define_method :actions_urls do
+                    urls = {}
+                    actions.each { |a| urls[a.first] = a.last }
+                    urls
+                  end
+                # Property attribute is handled differently
+                when "property":
+                  define_method :"#{attribute['name']}" do
+                    if attribute['value'] =~ /^(\d+)$/
+                      DeltaCloud::HWP::FloatProperty.new(attribute, 
attribute['name'])
+                    else
+                      DeltaCloud::HWP::Property.new(attribute, 
attribute['name'])
+                    end
+                  end
+                # Public and private addresses are returned as Array
+                when "public_addresses", "private_addresses":
+                  define_method :"#{attribute.name}" do
+                    attribute.xpath('address').collect { |address| 
address.text }
+                  end
+                # Value for other attributes are just returned using
+                # method with same name as attribute (eg. .owner_id, .state)
+                else
+                  define_method :"#{attribute.name}" do
+                    attribute.text.convert
+                  end
+                  logger << "[DC] Added method #{attribute.name} to 
#{obj.class.name}\n"
+              end
+            end
+          end
+        end
       end
+      return obj
     end
-    docs
-  end
 
-  def instance_states
-    states = []
-    request( entry_points[:instance_states] ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'states/state' ).each do |state_elem|
-        state = DCloud::State.new( state_elem.attributes['name'] )
-        state_elem.get_elements( 'transition' ).each do |transition_elem|
-          state.transitions << DCloud::Transition.new(
-                                 transition_elem.attributes['to'],
-                                 transition_elem.attributes['action']
-                               )
+    # Get /api and parse entry points
+    def discover_entry_points
+      return if discovered?
+      request(:get, @api_uri.to_s) do |response|
+        api_xml = Nokogiri::XML(response)
+        @driver_name = api_xml.xpath('/api').first['driver']
+        @api_version = api_xml.xpath('/api').first['version']
+        logger << "[API] Version #...@api_version}\n"
+        logger << "[API] Driver #...@driver_name}\n"
+        api_xml.css("api > link").each do |entry_point|
+          rel, href = entry_point['rel'].to_sym, entry_point['href']
+          @entry_points.store(rel, href)
+          logger << "[API] Entry point '#{rel}' added\n"
+          entry_point.css("feature").each do |feature|
+            @features[rel] ||= []
+            @features[rel] << feature['name'].to_sym
+            logger << "[API] Feature #{feature['name']} added to #{rel}\n"
+          end
         end
-        states << state
       end
+      declare_entry_points_methods(@entry_points)
     end
-    states
-  end
 
-  def instance_state(name)
-    found = instance_states.find{|e| e.name.to_s == name.to_s}
-    found
-  end
-
-  def realms(opts={})
-    realms = []
-    request( entry_points[:realms], :get, opts ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'realms/realm' ).each do |realm|
-        uri = realm.attributes['href']
-        realms << DCloud::Realm.new( self, uri, realm )
-      end
+    # Skip parsing /api when we already got entry points
+    def discovered?
+      true if @entry_points!={}
     end
-    realms
-  end
 
-  def realm(id)
-    request( entry_points[:realms], :get, {:id=>id } ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'realm' ).each do |realm|
-        uri = realm.attributes['href']
-        return DCloud::Realm.new( self, uri, realm )
+    # Create a new instance, using image +image_id+. Possible optiosn are
+    #
+    #   name  - a user-defined name for the instance
+    #   realm - a specific realm for placement of the instance
+    #   hardware_profile - either a string giving the name of the
+    #                      hardware profile or a hash. The hash must have an
+    #                      entry +id+, giving the id of the hardware profile,
+    #                      and may contain additional names of properties,
+    #                      e.g. 'storage', to override entries in the
+    #                      hardware profile
+    def create_instance(image_id, opts={}, &block)
+      name = opts[:name]
+      realm_id = opts[:realm]
+
+      params = {}
+      ( params[:realm_id] = realm_id ) if realm_id
+      ( params[:name] = name ) if name
+
+      if opts[:hardware_profile].is_a?(String)
+        params[:hwp_id] = opts[:hardware_profile]
+      elsif opts[:hardware_profile].is_a?(Hash)
+        opts[:hardware_profile].each do |k,v|
+          params[:"hwp_#{k}"] = v
+        end
       end
-    end
-    nil
-  end
 
-  def fetch_realm(uri)
-    xml = fetch_resource( :realm, uri )
-    return DCloud::Realm.new( self, uri, xml ) if xml
-    nil
-  end
+      params[:image_id] = image_id
+      instance = nil
 
-  def images(opts={})
-    images = []
-    request_path = entry_points[:images]
-    request( request_path, :get, opts ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'images/image' ).each do |image|
-        uri = image.attributes['href']
-        images << DCloud::Image.new( self, uri, image )
+      request(:post, entry_points[:instances], {}, params) do |response|
+        c = Kernel.define_class("Instance")
+        instance = base_object(c, :instance, response)
+        yield instance if block_given?
       end
+
+      return instance
     end
-    images
-  end
 
-  def image(id)
-    request( entry_points[:images], :get, {:id=>id } ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'image' ).each do |instance|
-        uri = instance.attributes['href']
-        return DCloud::Image.new( self, uri, instance )
-      end
+    def feature?(collection, name)
+      @feature.has_key?(collection) && @feature[collection].include?(name)
     end
-    nil
-  end
 
-  def instances(opts={})
-    instances = []
-    request( entry_points[:instances], :get, opts ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'instances/instance' ).each do |instance|
-        uri = instance.attributes['href']
-        instances << DCloud::Instance.new( self, uri, instance )
+    def instance_states
+      states = []
+      request(:get, entry_points[:instance_states]) do |response|
+        Nokogiri::XML(response).xpath('states/state').each do |state_el|
+          state = DeltaCloud::InstanceState::State.new(state_el['name'])
+          state_el.xpath('transition').each do |transition_el|
+            state.transitions << DeltaCloud::InstanceState::Transition.new(
+              transition_el['to'],
+              transition_el['action']
+            )
+          end
+          states << state
+        end
       end
+      states
     end
-    instances
-  end
 
-  def instance(id)
-    request( entry_points[:instances], :get, {:id=>id } ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'instance' ).each do |instance|
-        uri = instance.attributes['href']
-        return DCloud::Instance.new( self, uri, instance )
+    def instance_state(name)
+      instance_states.select { |s| s.name.to_s.eql?(name.to_s) }.first
+    end
+
+    # Basic request method
+    # Usage:
+    # => request(:get, '/api/images', { :architecture => 'i386' })
+    # => request(:get, '/api/images', { :architecture => 'i386' })
+    #
+    def request(*args, &block)
+      conf = {
+        :method => (args[0] || 'get').to_sym,
+        :path => (args[1]=~/^http/) ? args[1] : "#{api_uri.to_s}#{args[1]}",
+        :query_args => args[2] || {},
+        :form_data => args[3] || {}
+      }
+      if conf[:query_args] != {}
+        conf[:path] += '?' + URI.escape(conf[:query_args].collect{ |key, 
value| "#{key}=#{value}" }.join('&')).to_s
+      end
+      logger << "[#{conf[:method].to_s.upcase}] #{conf[:path]}\n"
+      if conf[:method].eql?(:post)
+        RestClient.send(:post, conf[:path], conf[:form_data], default_headers) 
do |response|
+          yield response.body if block_given?
+        end
+      else
+        RestClient.send(conf[:method], conf[:path], default_headers) do 
|response|
+          yield response.body if block_given?
+        end
       end
     end
-    nil
-  end
 
-  def post_instance(uri)
-    request( uri, :post ) do |response|
-      return true
+    private
+
+    def default_headers
+      {
+        :authorization => "Basic 
"+Base64.encode64("#...@username}:#...@password}"),
+        :accept => "application/xml"
+      }
     end
-    return false
-  end
 
-  def fetch_instance(uri)
-    xml = fetch_resource( :instance, uri )
-    return DCloud::Instance.new( self, uri, xml ) if xml
-    nil
   end
 
-  # Create a new instance, using image +image_id+. Possible optiosn are
-  #
-  #   name  - a user-defined name for the instance
-  #   realm - a specific realm for placement of the instance
-  #   hardware_profile - either a string giving the name of the
-  #                      hardware profile or a hash. The hash must have an
-  #                      entry +id+, giving the id of the hardware profile,
-  #                      and may contain additional names of properties,
-  #                      e.g. 'storage', to override entries in the
-  #                      hardware profile
-  def create_instance(image_id, opts={})
-    name = opts[:name]
-    realm_id = opts[:realm]
-
-    params = {}
-    ( params[:realm_id] = realm_id ) if realm_id
-    ( params[:name] = name ) if name
-
-    if opts[:hardware_profile].is_a?(String)
-      params[:hwp_id] = opts[:hardware_profile]
-    elsif opts[:hardware_profile].is_a?(Hash)
-      opts[:hardware_profile].each do |k,v|
-        params[:"hwp_#{k}"] = v
+  module InstanceState
+    class State
+      attr_reader :name
+      attr_reader :transitions
+
+      def initialize(name)
+        @name, @transitions = name, []
       end
     end
+    class Transition
+      attr_reader :to
+      attr_reader :action
 
-    params[:image_id] = image_id
-    request( entry_points[:instances], :post, {}, params ) do |response|
-      doc = REXML::Document.new( response )
-      instance = doc.root
-      uri = instance.attributes['href']
-      return DCloud::Instance.new( self, uri, instance )
-    end
-  end
+      def initialize(to, action)
+        @to = to
+        @action = action
+      end
 
-  def storage_volumes(opts={})
-    storage_volumes = []
-    request( entry_points[:storage_volumes] ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'storage-volumes/storage-volume' ).each do |instance|
-        uri = instance.attributes['href']
-        storage_volumes << DCloud::StorageVolume.new( self, uri, instance )
+      def auto?
+        @action.nil?
       end
     end
-    storage_volumes
   end
 
-  def storage_volume(id)
-    request( entry_points[:storage_volumes], :get, {:id=>id } ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'storage-volume' ).each do |storage_volume|
-        uri = storage_volume.attributes['href']
-        return DCloud::StorageVolume.new( self, uri, storage_volume )
+  module HWP
+   class Property
+      attr_reader :name, :unit, :value, :kind
+
+      def initialize(xml, name)
+        @kind, @value, @unit = xml['kind'].to_sym, xml['value'], xml['unit']
+        declare_ranges(xml)
+        self
       end
-    end
-    nil
-  end
 
-  def fetch_storage_volume(uri)
-    xml = fetch_resource( :storage_volume, uri )
-    return DCloud::StorageVolume.new( self, uri, xml ) if xml
-    nil
-  end
+      def declare_ranges(xml)
+        case xml['kind']
+          when 'range':
+            self.class.instance_eval do
+              attr_reader :range
+            end
+            @range = { :from => xml.xpath('range').first['first'], :to => 
xml.xpath('range').first['last'] }
+          when 'enum':
+            self.class.instance_eval do
+              attr_reader :options
+            end
+            @options = xml.xpath('enum/entry').collect { |e| e['value'] }
+        end
+      end
 
-  def storage_snapshots(opts={})
-    storage_snapshots = []
-    request( entry_points[:storage_snapshots] ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'storage-snapshots/storage-snapshot' ).each do 
|instance|
-        uri = instance.attributes['href']
-        storage_snapshots << DCloud::StorageSnapshot.new( self, uri, instance )
+      def present?
+        ! @value.nil?
       end
     end
-    storage_snapshots
-  end
 
-  def storage_snapshot(id)
-    request( entry_points[:storage_snapshots], :get, {:id=>id } ) do |response|
-      doc = REXML::Document.new( response )
-      doc.get_elements( 'storage-snapshot' ).each do |storage_snapshot|
-        uri = storage_snapshot.attributes['href']
-        return DCloud::StorageSnapshot.new( self, uri, storage_snapshot )
+    class FloatProperty < Property
+      def initialize(xml, name)
+        super(xml, name)
+        @value = @value.to_f if @value
       end
     end
-    nil
   end
 
-  def fetch_storage_snapshot(uri)
-    xml = fetch_resource( :storage_snapshot, uri )
-    return DCloud::StorageSnapshot.new( self, uri, xml ) if xml
-    nil
+end
+
+class String
+  def classify
+    self.singularize.camelize
   end
 
-  def fetch_image(uri)
-    xml = fetch_resource( :image, uri )
-    return DCloud::Image.new( self, uri, xml ) if xml
-    nil
+  def camelize
+    self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { 
$1.upcase }
   end
 
-  private
-
-  attr_reader :http
-
-  def discover_entry_points
-    return if @discovered
-    request(api_uri.to_s) do |response|
-      doc = REXML::Document.new( response )
-      @driver_name = doc.root.attributes['driver']
-      doc.get_elements( 'api/link' ).each do |link|
-        rel = link.attributes['rel'].to_sym
-        uri = link.attributes['href']
-        @entry_points[rel] = uri
-        @features[rel] ||= []
-        link.get_elements('feature').each do |feature|
-          @features[rel] << feature.attributes['name'].to_sym
-        end
-      end
-    end
-    @discovered = true
+  def singularize
+    self.gsub(/s$/, '')
   end
 
-  def request(path='', method=:get, query_args={}, form_data={}, &block)
-    if ( path =~ /^http/ )
-      request_path = path
-    else
-      request_path = "#{api_uri.to_s}#{path}"
-    end
-    if query_args[:id]
-      request_path += "/#{query_args[:id]}"
-      query_args.delete(:id)
-    end
-    query_string = URI.escape(query_args.collect{|k,v| "#{k}=#{v}"}.join('&'))
-    request_path += "?#{query_string}" unless query_string==''
-    headers = {
-      :authorization => "Basic "+Base64.encode64("#...@name}:#...@password}"),
-      :accept => "application/xml"
-    }
-
-    logger << "Request [#{method.to_s.upcase}] #{request_path}]\n"  if @verbose
-
-    if method.eql?(:get)
-      RestClient.send(method, request_path, headers) do |response|
-        @last_request_xml = response
-        yield response.to_s
-      end
-    else
-      RestClient.send(method, request_path, form_data, headers) do |response|
-        @last_request_xml = response
-        yield response.to_s
-      end
-    end
+  def convert
+    return self.to_f if self.strip =~ /^([\d\.]+$)/
+    self
   end
 
 end
+
+module Kernel
+  # Get defined class or declare a new one, when class was not declared before
+  def define_class(name)
+    DeltaCloud.const_get(name) rescue DeltaCloud.const_set(name, Class.new)
+  end
+end
-- 
1.7.1

_______________________________________________
deltacloud-devel mailing list
[email protected]
https://fedorahosted.org/mailman/listinfo/deltacloud-devel

Reply via email to