From 32e106858c3aac85e8b53387d6d57fe17762b3aa Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Sat, 17 Jul 2010 03:43:34 +0200 Subject: [PATCH] feature #200: Better XML parsing for ImageOCCI --- src/cloud/common/CloudServer.rb | 22 +-- src/cloud/occi/lib/ImageOCCI.rb | 69 ++++---- src/cloud/occi/lib/OCCIClient.rb | 160 ++++++++++--------- src/cloud/occi/lib/OCCIServer.rb | 261 ++++++++++++++++--------------- src/oca/ruby/OpenNebula/Image.rb | 6 +- 5 files changed, 267 insertions(+), 251 deletions(-) diff --git a/src/cloud/common/CloudServer.rb b/src/cloud/common/CloudServer.rb index 528654d424..11c11d1c33 100755 --- a/src/cloud/common/CloudServer.rb +++ b/src/cloud/common/CloudServer.rb @@ -156,25 +156,24 @@ class CloudServer end end - template = image.to_one_template + # ---------- Allocate the Image file ------------ - rc = image.allocate(template) + rc = image.allocate(image.to_one_template) if OpenNebula.is_error?(rc) return rc end - # Copy the Image file + # ---------- Copy the Image file ------------ + image.info - template=image.to_hash - template=template['IMAGE']['TEMPLATE'] if file_path rc = image.copy(file_path, image['SOURCE']) file[:tempfile].unlink - elsif template['SIZE'] and template['FSTYPE'] + elsif image['TEMPLATE/SIZE'] and image['TEMPLATE/FSTYPE'] rc = image.mk_datablock( - template['SIZE'], - template['FSTYPE'], + image['TEMPLATE/SIZE'], + image['TEMPLATE/FSTYPE'], image['SOURCE']) end @@ -185,12 +184,5 @@ class CloudServer return nil end - - # Gets an image from the repository - # image_id:: _Integer_ Image identifier - # [return] _Image_ Image object - def get_image(image_id) - return nil - end end diff --git a/src/cloud/occi/lib/ImageOCCI.rb b/src/cloud/occi/lib/ImageOCCI.rb index 175743d8b9..c680f8a0b4 100755 --- a/src/cloud/occi/lib/ImageOCCI.rb +++ b/src/cloud/occi/lib/ImageOCCI.rb @@ -23,13 +23,13 @@ class ImageOCCI < Image <%= self.id.to_s %> <%= self.name %> - <% if template['TYPE'] %> - <%= template['TYPE'] %> + <% if self['TEMPLATE/TYPE'] != nil %> + <%= self['TEMPLATE/TYPE'] %> <% end %> - <% if template['DESCRIPTION'] %> - <%= template['DESCRIPTION'] %> + <% if self['TEMPLATE/DESCRIPTION'] != nil %> + <%= self['TEMPLATE/DESCRIPTION'] %> <% end %> - <% if size %> + <% if size != nil %> <%= size %> <% end %> @@ -37,59 +37,62 @@ class ImageOCCI < Image ONE_IMAGE = %q{ - NAME = "<%= image_info['NAME'] %>" - <% if image_info['DESCRIPTION'] %> - DESCRIPTION = "<%= image_info['DESCRIPTION'] %>" + NAME = "<%= @image_info.elements['NAME'].text %>" + <% if @image_info.elements['DESCRIPTION'] != nil %> + DESCRIPTION = "<%= @image_info.elements['DESCRIPTION'].text %>" <% end %> - <% if image_info['TYPE'] %> - TYPE = <%= image_info['TYPE'] %> + <% if @image_info.elements['TYPE'] != nil %> + TYPE = <%= @image_info.elements['TYPE'].text %> <% end %> - <% if image_info['FSTYPE'] %> - FSTYPE = <%= image_info['FSTYPE'] %> + <% if @image_info.elements['FSTYPE'] != nil %> + FSTYPE = <%= @image_info.elements['FSTYPE'].text %> <% end %> - <% if image_info['SIZE'] %> - SIZE = <%= image_info['SIZE'] %> + <% if @image_info.elements['SIZE'] != nil %> + SIZE = <%= @image_info.elements['SIZE'].text %> <% end %> }.gsub(/^ /, '') # Class constructor - def initialize(image_info, xml, client) + def initialize(xml, client, xml_info=nil) super(xml, client) - @image_info = image_info + if xml_info != nil + @image_info = REXML::Document.new(xml_info).root + else + @image_info = nil + end end - + # Creates the OCCI representation of an Image def to_occi(base_url) - image_hash = self.to_hash - return image_hash, 500 if OpenNebula.is_error?(image_hash) - - template = image_hash['IMAGE']['TEMPLATE'] + size = nil + begin - size = File.stat(template['SOURCE']).size if template['SOURCE'] + if self['TEMPLATE/SOURCE'] != nil + size = File.stat(self['TEMPLATE/SOURCE']).size + end rescue Exception => e error = OpenNebula::Error.new(e.message) return error end - + occi = ERB.new(OCCI_IMAGE) return occi.result(binding).gsub(/\n\s*/,'') end - + def to_one_template() - if @image_info['STORAGE'] - image_info = @image_info['STORAGE'] - if !image_info['NAME'] - error_msg = "Missing Image NAME in the XML DISK section" - error = OpenNebula::Error.new(error_msg) - return error - end - else + if @image_info.name != 'STORAGE' error_msg = "Missing STORAGE section in the XML body" error = OpenNebula::Error.new(error_msg) return error end - + + if @image_info.elements['NAME'] == nil + error_msg = "Missing Image NAME in the XML DISK section" + error = OpenNebula::Error.new(error_msg) + return error + end + one = ERB.new(ONE_IMAGE) return one.result(binding) end diff --git a/src/cloud/occi/lib/OCCIClient.rb b/src/cloud/occi/lib/OCCIClient.rb index 25e29edc74..244ef2ccad 100755 --- a/src/cloud/occi/lib/OCCIClient.rb +++ b/src/cloud/occi/lib/OCCIClient.rb @@ -17,27 +17,27 @@ #--------------------------------------------------------------------------- # require 'rubygems' -require 'crack' +require 'rexml/document' require 'uri' require 'CloudClient' module OCCIClient - + ##################################################################### # Client Library to interface with the OpenNebula OCCI Service ##################################################################### - class Client - + class Client + ###################################################################### # Initialize client library ###################################################################### - def initialize(endpoint_str=nil, user=nil, pass=nil, + 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 @@ -46,25 +46,25 @@ module OCCIClient 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 # ################################# - + ###################################################################### # Post a new VM to the VM Pool # :instance_type @@ -72,14 +72,14 @@ module OCCIClient ###################################################################### def post_vms(xmlfile) xml=File.read(xmlfile) - + url = URI.parse(@endpoint+"/compute") - + req = Net::HTTP::Post.new(url.path) req.body=xml - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) do |http| http.request(req) end @@ -90,88 +90,92 @@ module OCCIClient return res.body end end - + ###################################################################### # Retieves the pool of Virtual Machines ###################################################################### def get_vms url = URI.parse(@endpoint+"/compute") req = Net::HTTP::Get.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|http| http.request(req) } - + if CloudClient::is_error?(res) return res else return res.body end end - + ###################################################################### # Post a new Network to the VN Pool # :xmlfile xml description of the Virtual Network ###################################################################### def post_network(xmlfile) xml=File.read(xmlfile) - + url = URI.parse(@endpoint+"/network") - + req = Net::HTTP::Post.new(url.path) req.body=xml - + 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 - + ###################################################################### # Retieves the pool of Virtual Networks ###################################################################### def get_networks url = URI.parse(@endpoint+"/network") req = Net::HTTP::Get.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|http| http.request(req) } - + 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) - image_info=Crack::XML.parse(xml) - - file_path = image_info['STORAGE']['URL'] - - m=file_path.match(/^\w+:\/\/(.*)$/) - - if m + xml = File.read(xmlfile) + image_info = REXML::Document.new(xml).root + + if image_info.elements['URL'] == nil + return CloudClient::Error.new("Can not find URL") + end + + file_path = image_info.elements['URL'].text + + m = file_path.match(/^\w+:\/\/(.*)$/) + + if m file_path="/"+m[1] end - + if curb and CURL_LOADED curl=Curl::Easy.new(@endpoint+"/storage") @@ -179,7 +183,7 @@ module OCCIClient curl.userpwd = "#{@occiauth[0]}:#{@occiauth[1]}" curl.verbose = true if @debug curl.multipart_form_post = true - + begin curl.http_post( Curl::PostField.content('occixml', xml), @@ -188,23 +192,23 @@ module OCCIClient rescue Exception => e return CloudClient::Error.new(e.message) end - + return curl.body_str else file=File.open(file_path) - + params=Hash.new params["file"]=UploadIO.new(file, 'application/octet-stream', file_path) - + params['occixml'] = xml - + 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 @@ -216,53 +220,53 @@ module OCCIClient else return res.body end - end + end end - + ###################################################################### # Retieves the pool of Images owned by the user ###################################################################### def get_images url = URI.parse(@endpoint+"/storage") req = Net::HTTP::Get.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|http| http.request(req) } - + if CloudClient::is_error?(res) return res else return res.body end end - + #################################### # Entity Resource Request Methods # #################################### - + ###################################################################### # :id VM identifier ###################################################################### def get_vm(id) url = URI.parse(@endpoint+"/compute/" + id.to_s) req = Net::HTTP::Get.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|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 @@ -270,34 +274,34 @@ module OCCIClient def put_vm(xmlfile) xml=File.read(xmlfile) vm_info=Crack::XML.parse(xml) - + url = URI.parse(@endpoint+'/compute/' + vm_info['COMPUTE']['ID']) - + req = Net::HTTP::Put.new(url.path) req.body = xml - + 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 - + #################################################################### # :id Compute identifier #################################################################### def delete_vm(id) url = URI.parse(@endpoint+"/compute/" + id.to_s) req = Net::HTTP::Delete.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|http| http.request(req) } @@ -308,7 +312,7 @@ module OCCIClient return res.body end end - + ###################################################################### # Retrieves a Virtual Network # :id Virtual Network identifier @@ -316,9 +320,9 @@ module OCCIClient def get_network(id) url = URI.parse(@endpoint+"/network/" + id.to_s) req = Net::HTTP::Get.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|http| http.request(req) } @@ -329,16 +333,16 @@ module OCCIClient return res.body end end - + ###################################################################### # :id VM identifier ###################################################################### def delete_network(id) url = URI.parse(@endpoint+"/network/" + id.to_s) req = Net::HTTP::Delete.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|http| http.request(req) } @@ -349,7 +353,7 @@ module OCCIClient return res.body end end - + ####################################################################### # Retieves an Image # :image_uuid Image identifier @@ -357,9 +361,9 @@ module OCCIClient 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, @timeout) {|http| http.request(req) } @@ -370,16 +374,16 @@ module OCCIClient return res.body end end - + ###################################################################### # :id VM identifier ###################################################################### def delete_image(id) url = URI.parse(@endpoint+"/storage/" + id.to_s) req = Net::HTTP::Delete.new(url.path) - + req.basic_auth @occiauth[0], @occiauth[1] - + res = CloudClient::http_start(url, @timeout) {|http| http.request(req) } diff --git a/src/cloud/occi/lib/OCCIServer.rb b/src/cloud/occi/lib/OCCIServer.rb index fceae8e458..e5d85ac827 100755 --- a/src/cloud/occi/lib/OCCIServer.rb +++ b/src/cloud/occi/lib/OCCIServer.rb @@ -115,9 +115,78 @@ class OCCIServer < CloudServer end end - ################################################### - # Pool Resources methods - ################################################### + ############################################################################ + ############################################################################ + # POOL RESOURCE METHODS + ############################################################################ + ############################################################################ + + # Gets the pool representation of COMPUTES + # request:: _Hash_ hash containing the data of the request + # [return] _String_,_Integer_ Pool Representation or error, status code + def get_computes(request) + # --- Get client with user credentials --- + client = get_client(request.env) + + # --- Get User's VMs --- + user_flag = -1 + vmpool = VirtualMachinePoolOCCI.new(client, user_flag) + + # --- Prepare XML Response --- + rc = vmpool.info + return rc, 404 if OpenNebula.is_error?(rc) + + return to_occi_xml(vmpool) + end + + + # Gets the pool representation of NETWORKS + # request:: _Hash_ hash containing the data of the request + # [return] _String_,_Integer_ Network pool representation or error, + # => status code + def get_networks(request) + # --- Get client with user credentials --- + client = get_client(request.env) + + # --- Get User's VNETs --- + user_flag = -1 + network_pool = VirtualNetworkPoolOCCI.new(client, user_flag) + + rc = network_pool.info + return rc, 404 if OpenNebula.is_error?(rc) + + # --- Prepare XML Response --- + return to_occi_xml(network_pool) + end + + # Gets the pool representation of STORAGES + # request:: _Hash_ hash containing the data of the request + # [return] _String_,_Integer_ Image pool representation or error, + # status code + def get_storages(request) + # --- Get client with user credentials --- + client = get_client(request.env) + + # --- Get User's Images --- + user_flag = -1 + image_pool = ImagePoolOCCI.new(client, user_flag) + + result = image_pool.info + return result, 404 if OpenNebula.is_error?(result) + + # --- Prepare XML Response --- + return to_occi_xml(image_pool) + end + + ############################################################################ + ############################################################################ + # ENTITY RESOURCE METHODS + ############################################################################ + ############################################################################ + + ############################################################################ + # COMPUTE Methods + ############################################################################ # Post a new compute to the COMPUTE pool # request:: _Hash_ hash containing the data of the request @@ -156,124 +225,6 @@ class OCCIServer < CloudServer return to_occi_xml(vm) end - # Gets the pool representation of COMPUTES - # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Pool Representation or error, status code - def get_computes(request) - # --- Get client with user credentials --- - client = get_client(request.env) - - # --- Get User's VMs --- - user_flag = -1 - vmpool = VirtualMachinePoolOCCI.new(client, user_flag) - - # --- Prepare XML Response --- - rc = vmpool.info - return rc, 404 if OpenNebula.is_error?(rc) - - return to_occi_xml(vmpool) - end - - # Post a new network to the NETWORK pool - # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Network Representation or error, status code - def post_network(request) - # --- Get client with user credentials --- - client = get_client(request.env) - - # --- Create the new Instance --- - network = VirtualNetworkOCCI.new( - VirtualNetwork.build_xml, - client, - request.body, - @config[:bridge]) - - # --- Generate the template and Allocate the new Instance --- - template = network.to_one_template - return template, 500 if OpenNebula.is_error?(template) - - rc = network.allocate(template) - return rc, 500 if OpenNebula.is_error?(rc) - - # --- Prepare XML Response --- - network.info - return to_occi_xml(network) - end - - # Gets the pool representation of NETWORKS - # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Network pool representation or error, - # => status code - def get_networks(request) - # --- Get client with user credentials --- - client = get_client(request.env) - - # --- Get User's VNETs --- - user_flag = -1 - network_pool = VirtualNetworkPoolOCCI.new(client, user_flag) - - rc = network_pool.info - return rc, 404 if OpenNebula.is_error?(rc) - - # --- Prepare XML Response --- - return to_occi_xml(network_pool) - end - - # Post a new image to the STORAGE pool - # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Image representation or error, status code - def post_storage(request) - # Get client with user credentials - client = get_client(request.env) - - # --- Check OCCI XML from POST --- - if request.params['occixml'] - image_info = XMLUtilsElement::xml_to_hash(request.params['occixml']) - return image_info, 400 if OpenNebula.is_error?(image_info) - else - error_msg = "OCCI XML representation of Image" + - " not present in the request" - error = OpenNebula::Error.new(error_msg) - return error, 400 - end - - # --- Create and Add the new Image --- - image = ImageOCCI.new(image_info, Image.build_xml, client) - - rc = add_image(image, request.params['file']) - return rc, 500 if OpenNebula.is_error?(rc) - - # --- Enable the new Image --- - rc = image.enable - return rc, 500 if OpenNebula.is_error?(rc) - - # --- Prepare XML Response --- - return to_occi_xml(image) - end - - # Gets the pool representation of STORAGES - # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Image pool representation or error, - # status code - def get_storages(request) - # --- Get client with user credentials --- - client = get_client(request.env) - - # --- Get User's Images --- - user_flag = -1 - image_pool = ImagePoolOCCI.new(client, user_flag) - - result = image_pool.info - return result, 404 if OpenNebula.is_error?(result) - - # --- Prepare XML Response --- - return to_occi_xml(image_pool) - end - - ################################################### - # Entity Resources methods - ################################################### - # Get the representation of a COMPUTE resource # request:: _Hash_ hash containing the data of the request # [return] _String_,_Integer_ COMPUTE representation or error, @@ -295,6 +246,7 @@ class OCCIServer < CloudServer return to_occi_xml(vm) end + # Deletes a COMPUTE resource # request:: _Hash_ hash containing the data of the request # [return] _String_,_Integer_ Delete confirmation msg or error, @@ -347,6 +299,36 @@ class OCCIServer < CloudServer return to_occi_xml(vm) end + ############################################################################ + # NETWORK Methods + ############################################################################ + + # Post a new network to the NETWORK pool + # request:: _Hash_ hash containing the data of the request + # [return] _String_,_Integer_ Network Representation or error, status code + def post_network(request) + # --- Get client with user credentials --- + client = get_client(request.env) + + # --- Create the new Instance --- + network = VirtualNetworkOCCI.new( + VirtualNetwork.build_xml, + client, + request.body, + @config[:bridge]) + + # --- Generate the template and Allocate the new Instance --- + template = network.to_one_template + return template, 500 if OpenNebula.is_error?(template) + + rc = network.allocate(template) + return rc, 500 if OpenNebula.is_error?(rc) + + # --- Prepare XML Response --- + network.info + return to_occi_xml(network) + end + # Retrieves a NETWORK resource # request:: _Hash_ hash containing the data of the request # [return] _String_,_Integer_ NETWORK occi representation or error, @@ -386,6 +368,39 @@ class OCCIServer < CloudServer return "", 204 end + ############################################################################ + # STORAGE Methods + ############################################################################ + + # Post a new image to the STORAGE pool + # request:: _Hash_ hash containing the data of the request + # [return] _String_,_Integer_ Image representation or error, status code + def post_storage(request) + # Get client with user credentials + client = get_client(request.env) + + # --- Check OCCI XML from POST --- + if request.params['occixml'] == nil + error_msg = "OCCI XML representation of Image" + + " not present in the request" + error = OpenNebula::Error.new(error_msg) + return error, 400 + end + + # --- Create and Add the new Image --- + image = ImageOCCI.new(Image.build_xml,client, request.params['occixml']) + + rc = add_image(image, request.params['file']) + return rc, 500 if OpenNebula.is_error?(rc) + + # --- Enable the new Image --- + rc = image.enable + return rc, 500 if OpenNebula.is_error?(rc) + + # --- Prepare XML Response --- + return to_occi_xml(image) + end + # Get a STORAGE resource # request:: _Hash_ hash containing the data of the request # [return] _String_,_Integer_ STORAGE occi representation or error, @@ -395,7 +410,7 @@ class OCCIServer < CloudServer client = get_client(request.env) # --- Get the Image --- - image = ImageOCCI.new(nil, Image.build_xml(params[:id]), client) + image = ImageOCCI.new(Image.build_xml(params[:id]), client) result = image.info return result, 404 if OpenNebula::is_error?(result) @@ -412,7 +427,7 @@ class OCCIServer < CloudServer # --- Get client with user credentials --- client = get_client(request.env) - image = ImageOCCI.new(nil, Image.build_xml(params[:id]), client) + image = ImageOCCI.new(Image.build_xml(params[:id]), client) # --- Delete the Image --- result = image.delete diff --git a/src/oca/ruby/OpenNebula/Image.rb b/src/oca/ruby/OpenNebula/Image.rb index f5b7d31b42..70da993ec7 100644 --- a/src/oca/ruby/OpenNebula/Image.rb +++ b/src/oca/ruby/OpenNebula/Image.rb @@ -190,8 +190,10 @@ module OpenNebula return OpenNebula::Error.new("copy Image: missing parameters.") end - if !FileUtils.copy(path, source) - return OpenNebula::Error.new("copy Image: in File.copy") + begin + FileUtils.copy(path, source) + rescue Exception => e + return OpenNebula::Error.new(e.message) end return nil