From e3b9eb63c00e93d34d87b1a795bb44dcdb369f1a Mon Sep 17 00:00:00 2001 From: "Ruben S. Montero" Date: Sat, 17 Jul 2010 02:05:06 +0200 Subject: [PATCH] feature #200: Better XML parsing --- src/cloud/common/CloudServer.rb | 58 +++---- src/cloud/occi/lib/OCCIServer.rb | 187 +++++++++++------------ src/cloud/occi/lib/VirtualNetworkOCCI.rb | 52 ++++--- src/oca/ruby/OpenNebula/XMLUtils.rb | 53 ++++--- 4 files changed, 170 insertions(+), 180 deletions(-) diff --git a/src/cloud/common/CloudServer.rb b/src/cloud/common/CloudServer.rb index 74b2d62985..528654d424 100755 --- a/src/cloud/common/CloudServer.rb +++ b/src/cloud/common/CloudServer.rb @@ -31,7 +31,7 @@ class CloudServer attr_reader :one_client # Initializes the Cloud server based on a config file - # config_file:: _String_ for the server. MUST include the following + # config_file:: _String_ for the server. MUST include the following # variables: # USER # PASSWORD @@ -43,7 +43,7 @@ class CloudServer # --- Load the Cloud Server configuration file --- @config = Configuration.new(config_file) - + @instance_types = Hash.new if @config[:vm_type].kind_of?(Array) @@ -55,7 +55,7 @@ class CloudServer end # --- Start an OpenNebula Session --- - + @one_client = Client.new() @user_pool = UserPool.new(@one_client) end @@ -72,7 +72,7 @@ class CloudServer puts "--------------------------------------" puts " Registered Instance Types " puts "--------------------------------------" - pp @instance_types + pp @instance_types end ########################################################################### @@ -80,19 +80,19 @@ class CloudServer ########################################################################### # Generates an OpenNebula Session for the given user - # user:: _Hash_ the user information - # [return] an OpenNebula client session + # user:: _Hash_ the user information + # [return] an OpenNebula client session def one_client_user(user) client = Client.new("dummy:dummy") client.one_auth = "#{user[:name]}:#{user[:password]}" - + return client end # Authenticates a user # name:: _String_ of the user # password:: _String_ of the user - # [return] true if authenticated + # [return] true if authenticated def authenticate?(name, password) user = get_user(name) @@ -104,7 +104,7 @@ class CloudServer # [return] _Hash_ with the user data def get_user(name) user = nil - + @user_pool.info @user_pool.each{ |u| if u.name==name @@ -117,31 +117,17 @@ class CloudServer } return user end - - def xml_to_hash(xml) - begin - hash = Crack::XML.parse(xml) - rescue Exception => e - error = OpenNebula::Error.new(e.message) - return error - end - - return hash - end - + def get_template_path(instance_type_name) - if instance_type_name.nil? - instance_type=@instance_types.first - end - + instance_type=@instance_types[instance_type_name] - + if !instance_type error = OpenNebula::Error.new("Bad instance type") - return error + return error end - - return @config[:template_location]+"/#{instance_type['TEMPLATE']}" + + return @config[:template_location]+"/#{instance_type['TEMPLATE']}" end ########################################################################### @@ -162,14 +148,14 @@ class CloudServer error = OpenNebula::Error.new(error_msg) return error end - + if !File.exists?(file_path) error_msg = "Image file could not be found, aborting." error = OpenNebula::Error.new(error_msg) return error end end - + template = image.to_one_template rc = image.allocate(template) @@ -181,22 +167,22 @@ class CloudServer 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'] rc = image.mk_datablock( - template['SIZE'], - template['FSTYPE'], + template['SIZE'], + template['FSTYPE'], image['SOURCE']) end - + if OpenNebula.is_error?(rc) image.delete return rc end - + return nil end diff --git a/src/cloud/occi/lib/OCCIServer.rb b/src/cloud/occi/lib/OCCIServer.rb index 36e813b76d..fceae8e458 100755 --- a/src/cloud/occi/lib/OCCIServer.rb +++ b/src/cloud/occi/lib/OCCIServer.rb @@ -35,37 +35,37 @@ require 'pp' ############################################################################## -# The OCCI Server provides an OCCI implementation based on the +# The OCCI Server provides an OCCI implementation based on the # OpenNebula Engine ############################################################################## class OCCIServer < CloudServer - + # Server initializer # config_file:: _String_ path of the config file - # template:: _String_ path to the location of the templates + # template:: _String_ path to the location of the templates def initialize(config_file,template) super(config_file) - + @config.add_configuration_value("TEMPLATE_LOCATION",template) - + if @config[:ssl_server] @base_url=@config[:ssl_server] else @base_url="http://#{@config[:server]}:#{@config[:port]}" end - + print_configuration end - + # Authorization function # requestenv:: _Hash_ Hash containing the environment of the request - # [return] _Boolean_ Whether the user is authorized or not + # [return] _Boolean_ Whether the user is authorized or not def authenticate?(requestenv) auth ||= Rack::Auth::Basic::Request.new(requestenv) if !(auth.provided? && auth.basic? && auth.credentials) return false - end + end user = get_user(requestenv, auth) @@ -85,7 +85,7 @@ class OCCIServer < CloudServer auth = Rack::Auth::Basic::Request.new(requestenv) if !auth super(auth.credentials.first) end - + # Retrieve a client with the user credentials # requestenv:: _Hash_ Hash containing the environment of the request # [return] _Client_ client with the user credentials @@ -93,20 +93,20 @@ class OCCIServer < CloudServer user = get_user(requestenv) return one_client_user(user) end - + # Prepare the OCCI XML Response # resource:: _Pool_ or _PoolElement_ that represents a OCCI resource # [return] _String_,_Integer_ Resource Representation or error, status code def to_occi_xml(resource) xml_response = resource.to_occi(@base_url) return xml_response, 500 if OpenNebula.is_error?(xml_response) - - return xml_response, 201 + + return xml_response, 201 end - + def get_info_hash(body) if body - info = xml_to_hash(body.read) + info = XMLUtilsElement::xml_to_hash(body.read) return info else error_msg = "OCCI XML representation not present" @@ -114,18 +114,18 @@ class OCCIServer < CloudServer return error end end - + ################################################### # Pool Resources methods ################################################### - + # Post a new compute to the COMPUTE pool # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ COMPUTE Representation or error, status code + # [return] _String_,_Integer_ COMPUTE Representation or error, status code def post_compute(request) # --- Get client with user credentials --- client = get_client(request.env) - + # --- Check OCCI XML from POST --- vm_info = get_info_hash(request.body) return vm_info, 400 if OpenNebula.is_error?(vm_info) @@ -134,16 +134,16 @@ class OCCIServer < CloudServer if vm_info['COMPUTE'] path = get_template_path(vm_info['COMPUTE']['INSTANCE_TYPE']) return path, 500 if OpenNebula.is_error?(path) - + vm_info['TEMPLATE_PATH'] = path end # --- Create the new Instance --- vm = VirtualMachineOCCI.new( vm_info, - VirtualMachine.build_xml, + VirtualMachine.build_xml, client) - + # --- Generate the template and Allocate the new Instance --- template = vm.to_one_template return template, 500 if OpenNebula.is_error?(template) @@ -155,7 +155,7 @@ class OCCIServer < CloudServer vm.info 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 @@ -164,36 +164,29 @@ class OCCIServer < CloudServer client = get_client(request.env) # --- Get User's VMs --- - user_flag = -1 + user_flag = -1 vmpool = VirtualMachinePoolOCCI.new(client, user_flag) - + # --- Prepare XML Response --- - rc = vmpool.info + 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) - - # --- Check OCCI XML from POST --- - network_info = get_info_hash(request.body) - return network_info, 400 if OpenNebula.is_error?(network_info) - - if network_info['NETWORK'] - network_info['NETWORK']['BRIDGE'] = @config[:bridge] - end - + # --- Create the new Instance --- network = VirtualNetworkOCCI.new( - network_info, VirtualNetwork.build_xml, - client) + client, + request.body, + @config[:bridge]) # --- Generate the template and Allocate the new Instance --- template = network.to_one_template @@ -203,39 +196,39 @@ class OCCIServer < CloudServer return rc, 500 if OpenNebula.is_error?(rc) # --- Prepare XML Response --- - network.info + 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 + # [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 + user_flag = -1 network_pool = VirtualNetworkPoolOCCI.new(client, user_flag) - - rc = network_pool.info + + 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 + # [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 = xml_to_hash(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" + @@ -243,53 +236,53 @@ class OCCIServer < CloudServer 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 + # [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 + user_flag = -1 image_pool = ImagePoolOCCI.new(client, user_flag) - - result = image_pool.info + + 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, + # [return] _String_,_Integer_ COMPUTE representation or error, # status code def get_compute(request, params) # --- Get client with user credentials --- client = get_client(request.env) - # --- Get the VM --- + # --- Get the VM --- vm = VirtualMachineOCCI.new( nil, VirtualMachine.build_xml(params[:id]), @@ -301,31 +294,31 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- 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, + # [return] _String_,_Integer_ Delete confirmation msg or error, # status code def delete_compute(request, params) # --- Get client with user credentials --- client = get_client(request.env) - + vm = VirtualMachineOCCI.new( nil, VirtualMachine.build_xml(params[:id]), client) - + # --- Finalize the VM --- result = vm.finalize return result, 500 if OpenNebula::is_error?(result) return "", 204 end - + # Updates a COMPUTE resource # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Update confirmation msg or error, - # status code + # [return] _String_,_Integer_ Update confirmation msg or error, + # status code def put_compute(request, params) # --- Get client with user credentials --- client = get_client(request.env) @@ -338,33 +331,32 @@ class OCCIServer < CloudServer if vm_info['COMPUTE'] && vm_info['COMPUTE']['STATE'] vm = VirtualMachineOCCI.new( vm_info, - VirtualMachine.build_xml(params[:id]), + VirtualMachine.build_xml(params[:id]), client) - + rc = vm.mk_action(vm_info['COMPUTE']['STATE']) return rc, 400 if OpenNebula.is_error?(rc) else - error_msg = "State not defined in the OCCI XML" + error_msg = "State not defined in the OCCI XML" error = OpenNebula::Error.new(error_msg) - return error, 400 + return error, 400 end - + # --- Prepare XML Response --- - vm.info + vm.info return to_occi_xml(vm) end - + # Retrieves a NETWORK resource # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ NETWORK occi representation or error, - # status code + # [return] _String_,_Integer_ NETWORK occi representation or error, + # status code def get_network(request, params) # --- Get client with user credentials --- client = get_client(request.env) - # --- Get the VM --- + # --- Get the VM --- network = VirtualNetworkOCCI.new( - nil, VirtualNetwork.build_xml(params[:id]), client) @@ -374,36 +366,35 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- return to_occi_xml(network) end - + # Deletes a NETWORK resource # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Delete confirmation msg or error, - # status code + # [return] _String_,_Integer_ Delete confirmation msg or error, + # status code def delete_network(request, params) # --- Get client with user credentials --- client = get_client(request.env) - + vn = VirtualNetworkOCCI.new( - nil, VirtualNetwork.build_xml(params[:id]), client) - + # --- Delete the VNET --- result = vn.delete return result, 500 if OpenNebula::is_error?(result) return "", 204 end - + # Get a STORAGE resource # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ STORAGE occi representation or error, - # status code + # [return] _String_,_Integer_ STORAGE occi representation or error, + # status code def get_storage(request, params) # --- Get client with user credentials --- client = get_client(request.env) - # --- Get the Image --- + # --- Get the Image --- image = ImageOCCI.new(nil, Image.build_xml(params[:id]), client) result = image.info @@ -412,17 +403,17 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- return to_occi_xml(image) end - + # Deletes a STORAGE resource (Not yet implemented) # request:: _Hash_ hash containing the data of the request - # [return] _String_,_Integer_ Delete confirmation msg or error, - # status code + # [return] _String_,_Integer_ Delete confirmation msg or error, + # status code def delete_storage(request, params) # --- Get client with user credentials --- client = get_client(request.env) - + image = ImageOCCI.new(nil, Image.build_xml(params[:id]), client) - + # --- Delete the Image --- result = image.delete return result, 500 if OpenNebula::is_error?(result) diff --git a/src/cloud/occi/lib/VirtualNetworkOCCI.rb b/src/cloud/occi/lib/VirtualNetworkOCCI.rb index 1ab7baa1a5..85222223f9 100755 --- a/src/cloud/occi/lib/VirtualNetworkOCCI.rb +++ b/src/cloud/occi/lib/VirtualNetworkOCCI.rb @@ -23,55 +23,57 @@ class VirtualNetworkOCCI < VirtualNetwork <%= self.id.to_s %> <%= self.name %> -
<%= template['NETWORK_ADDRESS'] %>
- <% if template['NETWORK_SIZE'] %> - <%= template['NETWORK_SIZE'] %> +
<%= self['TEMPLATE/NETWORK_ADDRESS'] %>
+ <% if self['TEMPLATE/NETWORK_SIZE'] %> + <%= self['TEMPLATE/NETWORK_SIZE'] %> <% end %>
} ONE_NETWORK = %q{ - NAME = <%= vnet_info['NAME'] %> + NAME = <%= @vnet_info.elements['NAME'].text %> TYPE = RANGED - BRIDGE = <%= vnet_info['BRIDGE'] %> - NETWORK_ADDRESS = <%= vnet_info['ADDRESS'] %> - NETWORK_SIZE = <%= vnet_info['SIZE'] %> + BRIDGE = <%= @vnet_info.elements['BRIDGE'].text %> + NETWORK_ADDRESS = <%= @vnet_info.elements['ADDRESS'].text %> + NETWORK_SIZE = <%= @vnet_info.elements['SIZE'].text %> }.gsub(/^ /, '') # Class constructor - def initialize(vnet_info, xml, client) + def initialize(xml, client, xml_info=nil, bridge=nil) super(xml, client) - @vnet_info = vnet_info - end - - # Creates the OCCI representation of a Virtual Network - def to_occi(base_url) - vn_hash = self.to_hash - return vn_hash, 500 if OpenNebula.is_error?(vn_hash) - - template = vn_hash['VNET']['TEMPLATE'] + if xml_info != nil and bridge != nil + @vnet_info = REXML::Document.new(xml_info).root + bridge_element = REXML::Element.new("BRIDGE") + bridge_element.add_text(bridge) + + @vnet_info.add(bridge_element) + else + @vnet_info = nil + end + end + + # Creates the OCCI representation of a Virtual Network + def to_occi(base_url) begin occi = ERB.new(OCCI_NETWORK) - occi_text = occi.result(binding) + occi_text = occi.result(binding) rescue Exception => e error = OpenNebula::Error.new(e.message) return error - end + end return occi_text.gsub(/\n\s*/,'') end - + def to_one_template() - if @vnet_info['NETWORK'] - vnet_info = @vnet_info['NETWORK'] - else - error_msg = "Missing STORAGE section in the XML body" + if @vnet_info.name != 'NETWORK' + error_msg = "Missing NETWORK section in the XML body" error = OpenNebula::Error.new(error_msg) return error end - + one = ERB.new(ONE_NETWORK) return one.result(binding) end diff --git a/src/oca/ruby/OpenNebula/XMLUtils.rb b/src/oca/ruby/OpenNebula/XMLUtils.rb index a0dfe43539..713cc121ed 100644 --- a/src/oca/ruby/OpenNebula/XMLUtils.rb +++ b/src/oca/ruby/OpenNebula/XMLUtils.rb @@ -7,7 +7,7 @@ module OpenNebula rescue LoadError NOKOGIRI=false end - + # Require crack library if present, otherwise don't bother # This is just for OCCI use begin @@ -17,7 +17,7 @@ module OpenNebula ########################################################################### # The XMLUtilsElement module provides an abstraction of the underlying - # XML parser engine. It provides XML-related methods for the Pool Elements + # XML parser engine. It provides XML-related methods for the Pool Elements ########################################################################### module XMLUtilsElement # Initialize a XML document for the element @@ -34,7 +34,7 @@ module OpenNebula # Extract an element from the XML description of the PoolElement. # key::_String_ The name of the element - # [return] _String_ the value of the element + # [return] _String_ the value of the element # Examples: # ['VID'] # gets VM id # ['HISTORY/HOSTNAME'] # get the hostname from the history @@ -47,16 +47,16 @@ module OpenNebula else element=@xml.elements[key.to_s.upcase] end - - if element + + if element element.text end end - + def template_str(indent=true) template_like_str('TEMPLATE', indent) end - + def template_like_str(root_element, indent=true) if NOKOGIRI xml_template=@xml.xpath(root_element).to_s @@ -64,7 +64,7 @@ module OpenNebula else rexml=@xml.elements[root_element] end - + if indent ind_enter="\n" ind_tab=' ' @@ -72,7 +72,7 @@ module OpenNebula ind_enter='' ind_tab=' ' end - + str=rexml.collect {|n| if n.class==REXML::Element str_line="" @@ -93,11 +93,11 @@ module OpenNebula str_line end }.compact.join("\n") - + str end - - def to_hash + + def to_hash if !@hash && @xml begin @hash = Crack::XML.parse(to_xml) @@ -116,18 +116,29 @@ module OpenNebula str = "" if pretty REXML::Formatters::Pretty.new(1).write(@xml,str) - else + else REXML::Formatters::Default.new.write(@xml,str) end str end end - + + def XMLUtilsElement.xml_to_hash(xml) + begin + hash = Crack::XML.parse(xml) + rescue Exception => e + error = OpenNebula::Error.new(e.message) + return error + end + + return hash + end + end - + ########################################################################### # The XMLUtilsPool module provides an abstraction of the underlying - # XML parser engine. It provides XML-related methods for the Pools + # XML parser engine. It provides XML-related methods for the Pools ########################################################################### module XMLUtilsPool @@ -141,9 +152,9 @@ module OpenNebula xml=REXML::Document.new(xml).root end end - + #Executes the given block for each element of the Pool - #block:: _Block_ + #block:: _Block_ def each_element(block) if NOKOGIRI @xml.xpath( @@ -165,14 +176,14 @@ module OpenNebula str = "" if pretty REXML::Formatters::Pretty.new(1).write(@xml,str) - else + else REXML::Formatters::Default.new.write(@xml,str) end str end end - - def to_hash + + def to_hash if !@hash && @xml @hash=Crack::XML.parse(to_xml) end