You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@deltacloud.apache.org by ma...@redhat.com on 2012/02/14 14:25:31 UTC

[PATCH 1/3] Updated OpenNebula driver+client (and occi_client) for OpenNebula API v.3.x Contributed by Daniel Molina dmolina@opennebula.org

From: marios <ma...@redhat.com>


Signed-off-by: marios <ma...@redhat.com>
---
 .../deltacloud/drivers/opennebula/cloud_client.rb  |  180 +++++----
 .../deltacloud/drivers/opennebula/occi_client.rb   |  433 +++++++++++++-------
 .../drivers/opennebula/opennebula_driver.rb        |  315 +++++++++------
 3 files changed, 585 insertions(+), 343 deletions(-)

diff --git a/server/lib/deltacloud/drivers/opennebula/cloud_client.rb b/server/lib/deltacloud/drivers/opennebula/cloud_client.rb
index 00be869..283efd9 100644
--- a/server/lib/deltacloud/drivers/opennebula/cloud_client.rb
+++ b/server/lib/deltacloud/drivers/opennebula/cloud_client.rb
@@ -18,11 +18,30 @@
 require 'rubygems'
 require 'uri'
 
+require 'digest/sha1'
 require 'net/https'
 
+require "rexml/document"
+
+begin
+  require 'rexml/formatters/pretty'
+  REXML_FORMATTERS=true
+rescue LoadError
+  REXML_FORMATTERS=false
+end
+
+begin
+  require 'curb'
+  CURL_LOADED=true
+rescue LoadError
+  CURL_LOADED=false
+end
+
 begin
-    require 'net/http/post/multipart'
+  require 'net/http/post/multipart'
+  MULTIPART_LOADED=true
 rescue LoadError
+  MULTIPART_LOADED=false
 end
 
 ###############################################################################
@@ -30,85 +49,100 @@ end
 # Cloud Client
 ###############################################################################
 module CloudClient
-    # #########################################################################
-    # Default location for the authentication file
-    # #########################################################################
-    DEFAULT_AUTH_FILE = ENV["HOME"]+"/.one/one_auth"
-
-    # #########################################################################
-    # Gets authorization credentials from ONE_AUTH or default
-    # auth file.
-    #
-    # Raises an error if authorization is not found
-    # #########################################################################
-    def self.get_one_auth
-        if ENV["ONE_AUTH"] and !ENV["ONE_AUTH"].empty? and
-           File.file?(ENV["ONE_AUTH"])
-            one_auth=File.read(ENV["ONE_AUTH"]).strip.split(':')
-        elsif File.file?(DEFAULT_AUTH_FILE)
-            one_auth=File.read(DEFAULT_AUTH_FILE).strip.split(':')
-        else
-            raise "No authorization data present"
-        end
-
-        raise "Authorization data malformed" if one_auth.length < 2
-
-        one_auth
+  # #########################################################################
+  # Default location for the authentication file
+  # #########################################################################
+  DEFAULT_AUTH_FILE = ENV["HOME"]+"/.one/one_auth"
+
+  # #########################################################################
+  # Gets authorization credentials from ONE_AUTH or default
+  # auth file.
+  #
+  # Raises an error if authorization is not found
+  # #########################################################################
+  def self.get_one_auth
+    if ENV["ONE_AUTH"] and !ENV["ONE_AUTH"].empty? and
+       File.file?(ENV["ONE_AUTH"])
+      one_auth=File.read(ENV["ONE_AUTH"]).strip.split(':')
+    elsif File.file?(DEFAULT_AUTH_FILE)
+      one_auth=File.read(DEFAULT_AUTH_FILE).strip.split(':')
+    else
+      raise "No authorization data present"
     end
 
-    # #########################################################################
-    # Starts an http connection and calls the block provided. SSL flag
-    # is set if needed.
-    # #########################################################################
-    def self.http_start(url, &block)
-        http = Net::HTTP.new(url.host, url.port)
-        if url.scheme=='https'
-            http.use_ssl = true
-            http.verify_mode=OpenSSL::SSL::VERIFY_NONE
-        end
-
-        begin
-            http.start do |connection|
-                block.call(connection)
-            end
-        rescue Errno::ECONNREFUSED => e
-            str =  "Error connecting to server (#{e.to_s})."
-            str << "Server: #{url.host}:#{url.port}"
-
-            return CloudClient::Error.new(str)
-        end
+    raise "Authorization data malformed" if one_auth.length < 2
+
+    one_auth
+  end
+
+  # #########################################################################
+  # Starts an http connection and calls the block provided. SSL flag
+  # is set if needed.
+  # #########################################################################
+  def self.http_start(url, timeout, &block)
+    http = Net::HTTP.new(url.host, url.port)
+
+    if timeout
+      http.read_timeout = timeout.to_i
     end
 
-    # #########################################################################
-    # The Error Class represents a generic error in the Cloud Client
-    # library. It contains a readable representation of the error.
-    # #########################################################################
-    class Error
-        attr_reader :message
-
-        # +message+ a description of the error
-        def initialize(message=nil)
-            @message=message
-        end
-
-        def to_s()
-            @message
-        end
+    if url.scheme=='https'
+      http.use_ssl = true
+      http.verify_mode=OpenSSL::SSL::VERIFY_NONE
     end
 
-    # #########################################################################
-    # Returns true if the object returned by a method of the OpenNebula
-    # library is an Error
-    # #########################################################################
-    def self.is_error?(value)
-        value.class==CloudClient::Error
+    begin
+      res = http.start do |connection|
+        block.call(connection)
+      end
+    rescue Errno::ECONNREFUSED => e
+      str =  "Error connecting to server (#{e.to_s}).\n"
+      str << "Server: #{url.host}:#{url.port}"
+
+      return CloudClient::Error.new(str,"503")
+    rescue Errno::ETIMEDOUT => e
+      str =  "Error timeout connecting to server (#{e.to_s}).\n"
+      str << "Server: #{url.host}:#{url.port}"
+
+      return CloudClient::Error.new(str,"504")
+    rescue Timeout::Error => e
+      str =  "Error timeout while connected to server (#{e.to_s}).\n"
+      str << "Server: #{url.host}:#{url.port}"
+
+      return CloudClient::Error.new(str,"504")
     end
-end
 
-# Command line help functions
-module CloudCLI
-    # Returns the command name
-    def cmd_name
-        File.basename($0)
+    if res.is_a?(Net::HTTPSuccess)
+      res
+    else
+      CloudClient::Error.new(res.body, res.code)
     end
+  end
+
+  # #########################################################################
+  # The Error Class represents a generic error in the Cloud Client
+  # library. It contains a readable representation of the error.
+  # #########################################################################
+  class Error
+    attr_reader :message
+    attr_reader :code
+
+    # +message+ a description of the error
+    def initialize(message=nil, code="500")
+      @message=message
+      @code=code
+    end
+
+    def to_s()
+      @message
+    end
+  end
+
+  # #########################################################################
+  # Returns true if the object returned by a method of the OpenNebula
+  # library is an Error
+  # #########################################################################
+  def self.is_error?(value)
+    value.class==CloudClient::Error
+  end
 end
diff --git a/server/lib/deltacloud/drivers/opennebula/occi_client.rb b/server/lib/deltacloud/drivers/opennebula/occi_client.rb
index 533eb94..3eab5d7 100644
--- a/server/lib/deltacloud/drivers/opennebula/occi_client.rb
+++ b/server/lib/deltacloud/drivers/opennebula/occi_client.rb
@@ -16,187 +16,330 @@
 #
 
 require 'rubygems'
-require 'uri'
 require 'rexml/document'
+require 'uri'
 
 require 'deltacloud/drivers/opennebula/cloud_client'
 
 
 module OCCIClient
 
-    #####################################################################
-    #  Client Library to interface with the OpenNebula OCCI Service
-    #####################################################################
-    class Client
-
-        ######################################################################
-        # Initialize client library
-        ######################################################################
-        def initialize(endpoint_str=nil, user=nil, pass=nil, debug_flag=true)
-            @debug = debug_flag
-
-            # Server location
-            if endpoint_str
-                @endpoint =  endpoint_str
-            elsif ENV["OCCI_URL"]
-                @endpoint = ENV["OCCI_URL"]
-            else
-                @endpoint = "http://localhost:4567"
-            end
-
-            # Autentication
-            if user && pass
-                @occiauth = [user, pass]
-            else
-                @occiauth = CloudClient::get_one_auth
-            end
-
-            if !@occiauth
-                raise "No authorization data present"
-            end
-
-            @occiauth[1] = Digest::SHA1.hexdigest(@occiauth[1])
-        end
+  #####################################################################
+  #  Client Library to interface with the OpenNebula OCCI Service
+  #####################################################################
+  class Client
+
+    attr_accessor :endpoint
+
+    ######################################################################
+    # Initialize client library
+    ######################################################################
+    def initialize(endpoint_str=nil, user=nil, pass=nil,
+             timeout=nil, debug_flag=true)
+      @debug   = debug_flag
+      @timeout = timeout
+
+      # Server location
+      if endpoint_str
+        @endpoint =  endpoint_str
+      elsif ENV["OCCI_URL"]
+        @endpoint = ENV["OCCI_URL"]
+      else
+        @endpoint = "http://localhost:4567"
+      end
+
+      # Autentication
+      if user && pass
+        @occiauth = [user, pass]
+      else
+        @occiauth = CloudClient::get_one_auth
+      end
+
+      if !@occiauth
+        raise "No authorization data present"
+      end
+
+      @occiauth[1] = Digest::SHA1.hexdigest(@occiauth[1])
+    end
 
-        #################################
-        # Pool Resource Request Methods #
-        #################################
+    #################################
+    # Pool Resource Request Methods #
+    #################################
 
-        ######################################################################
-        # Post a new VM to the VM Pool
-        # :instance_type
-        # :xmlfile
-        ######################################################################
-        def post_vms(xmlfile)
-            xml=File.read(xmlfile)
+    def get_root
+      get('/')
+    end
+
+    ######################################################################
+    # Retieves the available Instance types
+    ######################################################################
+    def get_instance_types
+      get('/instance_type')
+    end
 
-            url = URI.parse(@endpoint+"/compute")
+    ######################################################################
+    # Post a new VM to the VM Pool
+    # :xmlfile
+    ######################################################################
+    def post_vms(xmlfile)
+      post('/compute', xmlfile)
+    end
 
-            req = Net::HTTP::Post.new(url.path)
-            req.body=xml
+    ######################################################################
+    # Retieves the pool of Virtual Machines
+    ######################################################################
+    def get_vms
+      get('/compute')
+    end
 
-            req.basic_auth @occiauth[0], @occiauth[1]
+    ######################################################################
+    # Post a new Network to the VN Pool
+    # :xmlfile xml description of the Virtual Network
+    ######################################################################
+    def post_network(xmlfile)
+      post('/network', xmlfile)
+    end
 
-            res = CloudClient::http_start(url) do |http|
-                http.request(req)
-            end
+    ######################################################################
+    # Retieves the pool of Virtual Networks
+    ######################################################################
+    def get_networks
+      get('/network')
+    end
 
-            if CloudClient::is_error?(res)
-                return res
-            else
-                return res.body
-            end
-        end
+    ######################################################################
+    # Post a new Image to the Image Pool
+    # :xmlfile
+    ######################################################################
+    def post_image(xmlfile, curb=true)
+      xml        = File.read(xmlfile)
 
-        ######################################################################
-        # Retieves the pool of Virtual Machines
-        ######################################################################
-        def get_vms
-            url = URI.parse(@endpoint+"/compute")
-            req = Net::HTTP::Get.new(url.path)
+      begin
+        image_info = REXML::Document.new(xml).root
+      rescue Exception => e
+        error = CloudClient::Error.new(e.message)
+        return error
+      end
 
-            req.basic_auth @occiauth[0], @occiauth[1]
+      if image_info.elements['URL']
+        file_path = image_info.elements['URL'].text
 
-            res = CloudClient::http_start(url) {|http|
-                http.request(req)
-            }
+        m = file_path.match(/^\w+:\/\/(.*)$/)
 
-            if CloudClient::is_error?(res)
-                return res
-            else
-                return res.body
-            end
+        if m
+          file_path="/"+m[1]
         end
+      elsif !image_info.elements['TYPE'] == "DATABLOCK"
+        return CloudClient::Error.new("Can not find URL")
+      end
+
+      if curb
+        if !CURL_LOADED
+          error_msg = "curb gem not loaded"
+          error = CloudClient::Error.new(error_msg)
+          return error
+        end
+
+        curl=Curl::Easy.new(@endpoint+"/storage")
 
-        ######################################################################
-        # Retieves the pool of Images owned by the user
-        ######################################################################
-        def get_images
-            url = URI.parse(@endpoint+"/storage")
-            req = Net::HTTP::Get.new(url.path)
+        curl.http_auth_types     = Curl::CURLAUTH_BASIC
+        curl.userpwd             = "#{@occiauth[0]}:#{@occiauth[1]}"
+        curl.verbose             = true if @debug
+        curl.multipart_form_post = true
 
-            req.basic_auth @occiauth[0], @occiauth[1]
+        begin
+          postfields = Array.new
+          postfields << Curl::PostField.content('occixml', xml)
 
-            res = CloudClient::http_start(url) {|http|
-                http.request(req)
-            }
+          if file_path
+            postfields << Curl::PostField.file('file', file_path)
+          end
 
-            if CloudClient::is_error?(res)
-                return res
-            else
-                return res.body
-            end
+          curl.http_post(*postfields)
+        rescue Exception => e
+          return CloudClient::Error.new(e.message)
         end
 
-        ####################################
-        # Entity Resource Request Methods  #
-        ####################################
+        return curl.body_str
+      else
+        if !MULTIPART_LOADED
+          error_msg = "multipart-post gem not loaded"
+          error = CloudClient::Error.new(error_msg)
+          return error
+        end
 
-        ######################################################################
-        # :id VM identifier
-        ######################################################################
-        def get_vm(id)
-            url = URI.parse(@endpoint+"/compute/" + id.to_s)
-            req = Net::HTTP::Get.new(url.path)
+        params=Hash.new
 
-            req.basic_auth @occiauth[0], @occiauth[1]
+        if file_path
+          file=File.open(file_path)
+          params["file"]=UploadIO.new(file,
+            'application/octet-stream', file_path)
+        end
 
-            res = CloudClient::http_start(url) {|http|
-                http.request(req)
-            }
+        params['occixml'] = xml
 
-            if CloudClient::is_error?(res)
-                return res
-            else
-                return res.body
-            end
+        url = URI.parse(@endpoint+"/storage")
+
+        req = Net::HTTP::Post::Multipart.new(url.path, params)
+
+        req.basic_auth @occiauth[0], @occiauth[1]
+
+        res = CloudClient::http_start(url, @timeout) do |http|
+          http.request(req)
         end
 
-        ######################################################################
-        # Puts a new Compute representation in order to change its state
-        # :xmlfile Compute OCCI xml representation
-        ######################################################################
-        def put_vm(xmlfile)
-            xml=File.read(xmlfile)
-            vm_info=REXML::Document.new(xml).root.elements
+        file.close if file_path
 
-            url = URI.parse(@endpoint+'/compute/' + vm_info['ID'].text)
+        if CloudClient::is_error?(res)
+          return res
+        else
+          return res.body
+        end
+      end
+    end
 
-            req = Net::HTTP::Put.new(url.path)
-            req.body = xml
+    ######################################################################
+    # Retieves the pool of Images owned by the user
+    ######################################################################
+    def get_images
+      get('/storage')
+    end
 
-            req.basic_auth @occiauth[0], @occiauth[1]
 
-            res = CloudClient::http_start(url) do |http|
-                http.request(req)
-            end
+    ####################################
+    # Entity Resource Request Methods  #
+    ####################################
 
-            if CloudClient::is_error?(res)
-                return res
-            else
-                return res.body
-            end
-        end
+    ######################################################################
+    # :id VM identifier
+    ######################################################################
+    def get_vm(id)
+      get('/compute/'+id.to_s)
+    end
 
-       #######################################################################
-        # Retieves an Image
-        # :image_uuid Image identifier
-        ######################################################################
-        def get_image(image_uuid)
-            url = URI.parse(@endpoint+"/storage/"+image_uuid)
-            req = Net::HTTP::Get.new(url.path)
-
-            req.basic_auth @occiauth[0], @occiauth[1]
-
-            res = CloudClient::http_start(url) {|http|
-                http.request(req)
-            }
-
-            if CloudClient::is_error?(res)
-                return res
-            else
-                return res.body
-            end
-        end
+    ######################################################################
+    # Puts a new Compute representation in order to change its state
+    # :xmlfile Compute OCCI xml representation
+    ######################################################################
+    def put_vm(xmlfile)
+      put('/compute/', xmlfile)
+    end
+
+    ####################################################################
+    # :id Compute identifier
+    ####################################################################
+    def delete_vm(id)
+      delete('/compute/'+id.to_s)
+    end
+
+    ######################################################################
+    # Retrieves a Virtual Network
+    # :id Virtual Network identifier
+    ######################################################################
+    def get_network(id)
+      get('/network/'+id.to_s)
+    end
+
+    ######################################################################
+    # Puts a new Network representation in order to change its state
+    # :xmlfile Network OCCI xml representation
+    ######################################################################
+    def put_network(xmlfile)
+      put('/network/', xmlfile)
+    end
+
+    ######################################################################
+    # :id VM identifier
+    ######################################################################
+    def delete_network(id)
+      delete('/network/'+id.to_s)
+    end
+
+     #######################################################################
+    # Retieves an Image
+    # :image_uuid Image identifier
+    ######################################################################
+    def get_image(id)
+      get('/storage/'+id.to_s)
+    end
+
+    ######################################################################
+    # Puts a new Storage representation in order to change its state
+    # :xmlfile Storage OCCI xml representation
+    ######################################################################
+    def put_image(xmlfile)
+      put('/storage/', xmlfile)
+    end
+
+    ######################################################################
+    # :id VM identifier
+    ######################################################################
+    def delete_image(id)
+      delete('/storage/'+id.to_s)
+    end
+
+    private
+
+    def get(path)
+      url = URI.parse(@endpoint+path)
+      req = Net::HTTP::Get.new(url.path)
+
+      do_request(url, req)
+    end
+
+    def post(path, xmlfile)
+      url = URI.parse(@endpoint+path)
+      req = Net::HTTP::Post.new(url.path)
+
+      req.body=File.read(xmlfile)
+
+      do_request(url, req)
+    end
+
+    def delete(path, xmlfile)
+      url = URI.parse(@endpoint+path)
+      req = Net::HTTP::Delete.new(url.path)
+
+      do_request(url, req)
+    end
+
+    def put(path, xmlfile)
+      xml = File.read(xmlfile)
+
+      # Get ID from XML
+      begin
+        info = REXML::Document.new(xml).root
+      rescue Exception => e
+        error = CloudClient::Error.new(e.message)
+        return error
+      end
+
+      if info.elements['ID'] == nil
+        return CloudClient::Error.new("Can not find RESOURCE ID")
+      end
+
+      resource_id = info.elements['ID'].text
+
+      url = URI.parse(@endpoint+path + resource_id)
+      req = Net::HTTP::Put.new(url.path)
+
+      req.body = xml
+
+      do_request(url, req)
+    end
+
+    def do_request(url, req)
+      req.basic_auth @occiauth[0], @occiauth[1]
+
+      res = CloudClient::http_start(url, @timeout) do |http|
+        http.request(req)
+      end
+
+      if CloudClient::is_error?(res)
+        return res
+      else
+        return res.body
+      end
     end
+  end
 end
diff --git a/server/lib/deltacloud/drivers/opennebula/opennebula_driver.rb b/server/lib/deltacloud/drivers/opennebula/opennebula_driver.rb
index e4f8f4d..81a1290 100644
--- a/server/lib/deltacloud/drivers/opennebula/opennebula_driver.rb
+++ b/server/lib/deltacloud/drivers/opennebula/opennebula_driver.rb
@@ -16,229 +16,294 @@
 #
 
 require 'deltacloud/base_driver'
+require 'deltacloud/drivers/opennebula/occi_client'
 
 require 'erb'
+# TBD Nokogiri support
 require 'rexml/document'
 
-path = File.dirname(__FILE__)
-$: << path
-
-require 'occi_client'
-
 module Deltacloud
   module Drivers
     module Opennebula
 
 class OpennebulaDriver < Deltacloud::BaseDriver
 
-  feature :instances, :user_name
+  def supported_collections
+    DEFAULT_COLLECTIONS - [:storage_volumes, :storage_snapshots]
+  end
 
   ######################################################################
   # Hardware profiles
-  ######################################################################
-
-  define_hardware_profile 'small'
-
-  define_hardware_profile 'medium'
+  #####################################################################
+  def hardware_profiles(credentials, opts=nil)
+    occi_client = new_client(credentials)
+    xml = occi_client.get_instance_types
+    if CloudClient.is_error?(xml)
+      # OpenNebula 3.0 support
+      @hardware_profiles = ['small','medium','large'].map {|name|
+        ::Deltacloud::HardwareProfile.new(name)
+      }
+    else
+      # OpenNebula 3.2 support
+      @hardware_profiles = REXML::Document.new(xml).root.elements.map {|d|
+        elem = d.elements
+        ::Deltacloud::HardwareProfile.new(elem['NAME'].text) {
+          cpu          elem['CPU']
+          memory       elem['MEMORY']
+          storage      elem['STORAGE']
+          architecture elem['ARCHITECTURE']
+        }
+      }
+    end
 
-  define_hardware_profile 'large'
+    filter_hardware_profiles(@hardware_profiles, opts)
+  end
 
   ######################################################################
   # Realms
   ######################################################################
 
   (REALMS = [
-	Realm.new( {
-		:id=>'Any id',
-		:name=>'Any name',
-		:limit=>:unlimited,
-		:state=>'Any state',
-	} ),
+  Realm.new( {
+    :id=>'ONE',
+    :name=>'Opennebula',
+    :limit=>:unlimited,
+    :state=>'AVAILABLE',
+  } ),
   ] ) unless defined?( REALMS )
 
 
   def realms(credentials, opts=nil)
-	return REALMS if ( opts.nil? )
-	results = REALMS
-	results = filter_on( results, :id, opts )
-	results
+    return REALMS if ( opts.nil? )
+    results = REALMS
+    results = filter_on( results, :id, opts )
+    results
   end
 
 
   ######################################################################
   # Images
   ######################################################################
-
   def images(credentials, opts=nil)
-	occi_client = new_client(credentials)
+    occi_client = new_client(credentials)
 
-	images = []
-	imagesxml = occi_client.get_images
+    xml = treat_response(occi_client.get_images)
 
-	storage = REXML::Document.new(imagesxml)
-	storage.root.elements.each do |d|
-		id = d.attributes['href'].split("/").last
-
-		diskxml = occi_client.get_image(id)
+    # TBD Add extended info in the pool
+    images = REXML::Document.new(xml).root.elements.map do |d|
+      im_id = d.attributes['href'].split("/").last
+      storage = treat_response(occi_client.get_image(im_id))
+      convert_image(storage, credentials)
+    end
+  end
 
-		images << convert_image(diskxml.to_s(), credentials)
-	end
-	images
+  def image(credentials, opts=nil)
+    occi_client = new_client(credentials)
+    xml = treat_response(occi_client.get_image(opts[:id]))
+    convert_image(xml, credentials)
   end
 
+  def destroy_image(credentials, id)
+    occi_client = new_client(credentials)
+    treat_response(occi_client.delete_image(opts[:id]))
+  end
 
   ######################################################################
   # Instances
   ######################################################################
 
-  define_instance_states do
-	start.to( :pending )			.on( :create )
-
-	pending.to( :running )			.automatically
-
-	running.to( :stopped )			.on( :stop )
+  feature :instances, :user_name
+  # TBD Add Context to the VMs
+
+  OCCI_VM = %q{
+    <COMPUTE>
+      <% if opts[:name] %>
+      <NAME><%=opts[:name]%></NAME>
+      <% end %>
+      <INSTANCE_TYPE><%= opts[:hwp_id] || 'small' %></INSTANCE_TYPE>
+      <DISK>
+        <STORAGE href="<%= storage_href %>" />
+      </DISK>
+    </COMPUTE>
+  }
+
+  OCCI_ACTION = %q{
+    <COMPUTE>
+      <ID><%= id %></ID>
+      <STATE><%= strstate %></STATE>
+    </COMPUTE>
+  }
+
+  VM_STATES = {
+    "INIT"      => "START",
+    "PENDING"   => "PENDING",
+    "HOLD"      => "STOPPED",
+    "ACTIVE"    => "RUNNING",
+    "STOPPED"   => "STOPPED",
+    "SUSPENDED" => "STOPPED",
+    "DONE"      => "FINISHED",
+    "FAILED"    => "FINISHED"
+  }
 
-	stopped.to( :running )			.on( :start )
-	stopped.to( :finish )			.on( :destroy )
+  define_instance_states do
+    start.to(:pending)          .on( :create )
+    pending.to(:running)        .automatically
+    stopped.to(:running)        .on( :start )
+    running.to(:running)        .on( :reboot )
+    running.to(:stopping)       .on( :stop )
+    stopping.to(:stopped)       .automatically
+    running.to(:shutting_down)  .on( :destroy )
+    shutting_down.to(:finish)   .automatically
   end
 
-
   def instances(credentials, opts=nil)
-	occi_client = new_client(credentials)
-
-	instances = []
-	instancesxml = occi_client.get_vms
+    occi_client = new_client(credentials)
 
-	computes = REXML::Document.new(instancesxml)
-	computes.root.elements.each do |d|
-		vm_id = d.attributes['href'].split("/").last
+    xml = treat_response(occi_client.get_vms)
 
-		computexml = occi_client.get_vm(vm_id)
+    # TBD Add extended info in the pool
+    instances = REXML::Document.new(xml).root.elements.map do |d|
+      vm_id = d.attributes['href'].split("/").last
+      computexml = treat_response(occi_client.get_vm(vm_id))
+      convert_instance(computexml, credentials)
+    end
 
-		instances << convert_instance(computexml.to_s(), credentials)
-	end
-        instances = filter_on( instances, :id, opts )
-        instances = filter_on( instances, :state, opts )
-	instances
+    instances = filter_on( instances, :state, opts )
   end
 
+  def instance(credentials, opts=nil)
+    occi_client = new_client(credentials)
+    xml = treat_response(occi_client.get_vm(opts[:id]))
+    convert_instance(xml, credentials)
+  end
 
   def create_instance(credentials, image_id, opts=nil)
-	occi_client = new_client(credentials)
+    occi_client = new_client(credentials)
 
-	hwp_id = opts[:hwp_id] || 'small'
+    storage_href = "#{occi_client.endpoint}/storage/#{image_id}"
 
-	if not opts[:name]
-          opts[:name] = "#{Time.now.to_i.to_s}#{rand(9)}"
-        end
+    instancexml  = ERB.new(OCCI_VM).result(binding)
+    instancefile = "|echo '#{instancexml}'"
 
-	instancexml = ERB.new(OCCI_VM).result(binding)
-	instancefile = "|echo '#{instancexml}'"
+    # TBD Specify VNET in the template.
 
-	xmlvm = occi_client.post_vms(instancefile)
+    xmlvm = treat_response(occi_client.post_vms(instancefile))
 
-	convert_instance(xmlvm.to_s(), credentials)
+    convert_instance(xmlvm, credentials)
   end
 
-
   def start_instance(credentials, id)
-	occi_action(credentials, id, 'RESUME')
+    occi_action(credentials, id, 'RESUME')
   end
 
 
   def stop_instance(credentials, id)
-	occi_action(credentials, id, 'STOPPED')
+    occi_action(credentials, id, 'STOPPED')
   end
 
 
   def destroy_instance(credentials, id)
-	occi_action(credentials, id, 'DONE')
+    occi_action(credentials, id, 'DONE')
   end
 
-
+  def reboot_instance(credentials, id)
+    begin
+      occi_action(credentials, id, 'REBOOT')
+    rescue Exception => e
+      # TBD Check exception
+      # OpenNebula 3.0 support
+      raise "Reboot action not supported"
+    end
+  end
 
   private
 
   def new_client(credentials)
-	OCCIClient::Client.new(nil,	credentials.user, credentials.password, false)
+    OCCIClient::Client.new(nil, credentials.user, credentials.password, false)
   end
 
 
   def convert_image(diskxml, credentials)
-	disk = REXML::Document.new(diskxml)
-	diskhash = disk.root.elements
-
-	Image.new( {
-		:id=>diskhash['ID'].text,
-		:name=>diskhash['NAME'].text,
-		:description=>diskhash['NAME'].text,
-		:owner_id=>credentials.user,
-		:architecture=>'Any architecture',
-	} )
+    storage = REXML::Document.new(diskxml).root.elements
+
+    # TBD Add Image STATE, OWNER
+    Image.new( {
+      :id=>storage['ID'].text,
+      :name=>storage['NAME'].text,
+      :description=>storage['TYPE'].text,
+      :owner_id=>credentials.user,
+      :state=>"AVAILABLE",
+      :architecture=>storage['ARCH'],
+    } )
   end
 
 
   def convert_instance(computexml, credentials)
-	compute = REXML::Document.new(computexml)
-	computehash = compute.root.elements
-
-	imageid = computehash['STORAGE/DISK[@type="disk"]'].attributes['href'].split("/").last
+    compute = REXML::Document.new(computexml)
+    computehash = compute.root.elements
 
-	state = (computehash['STATE'].text == "ACTIVE") ? "RUNNING" : "STOPPED"
+    network = []
+    computehash.each('NIC/IP') {|ip| network<<InstanceAddress.new(ip)}
 
-	hwp_name = computehash['INSTANCE_TYPE'] || 'small'
-
-	networks = []
-	(computehash['NETWORK'].each do |n|
-		networks << InstanceAddress.new(n.attributes['ip'])
-	end) unless computehash['NETWORK'].nil?
+    image_id = nil
+    if computehash['DISK/STORAGE']
+      image_id = computehash['DISK/STORAGE'].attributes['href'].split("/").last
+    end
 
-	Instance.new( {
-		:id=>computehash['ID'].text,
-		:owner_id=>credentials.user,
-		:name=>computehash['NAME'].text,
-		:image_id=>imageid,
-		:instance_profile=>InstanceProfile.new(hwp_name),
-		:realm_id=>'Any realm',
-		:state=>state,
-		:public_addreses=>networks,
-		:private_addreses=>networks,
-		:actions=> instance_actions_for( state )
-	} )
+    Instance.new( {
+      :id=>computehash['ID'].text,
+      :owner_id=>credentials.user,
+      :name=>computehash['NAME'].text,
+      :image_id=>image_id,
+      :instance_profile=>InstanceProfile.new(computehash['INSTANCE_TYPE']||'small'),
+      :realm_id=>'ONE',
+      :state=>VM_STATES[computehash['STATE'].text],
+      :public_addreses=>network,
+      :private_addreses=>[],
+      :actions=> instance_actions_for( VM_STATES[computehash['STATE'].text] )
+    } )
   end
 
 
   def occi_action(credentials, id, strstate)
-	occi_client = new_client(credentials)
+    occi_client = new_client(credentials)
 
-	actionxml = ERB.new(OCCI_ACTION).result(binding)
-	actionfile = "|echo '#{actionxml}'"
+    actionxml = ERB.new(OCCI_ACTION).result(binding)
+    actionfile = "|echo '#{actionxml}'"
 
-	xmlvm = occi_client.put_vm(actionfile)
+    xmlvm = treat_response(occi_client.put_vm(actionfile))
 
-	convert_instance(xmlvm.to_s(), credentials)
+    convert_instance(xmlvm, credentials)
   end
 
 
-  (OCCI_VM = %q{
-	<COMPUTE>
-		<NAME><%=opts[:name]%></NAME>
-		<INSTANCE_TYPE><%= hwp_id %></INSTANCE_TYPE>
-		<STORAGE>
-			<DISK image="<%= image_id %>" dev="sda1" />
-		</STORAGE>
-	</COMPUTE>
-  }.gsub(/^        /, '') ) unless defined?( OCCI_VM )
+  def treat_response(res)
+    safely do
+      if CloudClient.is_error?(res)
+        raise case res.code
+              when "401" then "AuthenticationFailure"
+              when "404" then "ObjectNotFound"
+              else res.message
+              end
+      end
+    end
+    res
+  end
 
+  exceptions do
+    on /AuthenticationFailure/ do
+      status 401
+    end
 
-  (OCCI_ACTION = %q{
-	<COMPUTE>
-		<ID><%= id %></ID>
-		<STATE><%= strstate %></STATE>
-	</COMPUTE>
-  }.gsub(/^        /, '') ) unless defined?( OCCI_ACTION )
+    on /ObjectNotFound/ do
+      status 404
+    end
 
- end
+    on // do
+      status 502
+    end
+  end
+end
 
     end
   end
-- 
1.7.6.5