From bb6ee69f292c9f7d9c10e79c696e81eceb0120b5 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Fri, 14 Oct 2011 15:50:54 +0200 Subject: [PATCH 1/8] feature #873: Add errno value to the OpenNebula Error class --- src/oca/ruby/OpenNebula.rb | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/oca/ruby/OpenNebula.rb b/src/oca/ruby/OpenNebula.rb index 9dc67bb237..4bf84d51db 100644 --- a/src/oca/ruby/OpenNebula.rb +++ b/src/oca/ruby/OpenNebula.rb @@ -50,11 +50,22 @@ module OpenNebula # Any function in the OpenNebula module will return an Error # object in case of error. class Error - attr_reader :message + ESUCCESS = 0x0000 + EAUTHENTICATION = 0x0100 + EAUTHORIZATION = 0x0200 + ENO_EXISTS = 0x0400 + EACTION = 0x0800 + EXML_RPC_API = 0x1000 + EINTERNAL = 0x2000 + ENOTDEFINED = 0x1111 - # +message+ a description of the error - def initialize(message=nil) - @message=message + attr_reader :message, :errno + + # +message+ Description of the error + # +errno+ OpenNebula code error + def initialize(message=nil, errno=0x1111) + @message = message + @errno = errno end def to_str() @@ -127,7 +138,7 @@ module OpenNebula response = @server.call_async("one."+action, @one_auth, *args) if response[0] == false - Error.new(response[1]) + Error.new(response[1], response[2]) else response[1] #response[1..-1] end From 20a0a78184f56b98d15c5d5734c20c372e615402 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Fri, 14 Oct 2011 15:51:52 +0200 Subject: [PATCH 2/8] feature #873: Add mapping for errno to HTTP codes --- src/cloud/common/CloudServer.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/cloud/common/CloudServer.rb b/src/cloud/common/CloudServer.rb index 91c5335701..59ad085e7f 100755 --- a/src/cloud/common/CloudServer.rb +++ b/src/cloud/common/CloudServer.rb @@ -22,6 +22,18 @@ require 'CloudAuth' # API (OCA). Any cloud implementation should derive from this class ############################################################################## class CloudServer + ########################################################################## + # Class Constants. Define the OpenNebula Error and HTTP codes mapping + ########################################################################## + HTTP_ERROR_CODE = { + OpenNebula::Error::EAUTHENTICATION => 401, + OpenNebula::Error::EAUTHORIZATION => 403, + OpenNebula::Error::ENO_EXISTS => 404, + OpenNebula::Error::EACTION => 500, + OpenNebula::Error::EXML_RPC_API => 500, + OpenNebula::Error::EINTERNAL => 500, + OpenNebula::Error::ENOTDEFINED => 500 + } ########################################################################## # Public attributes From 591182404514fff95ca9b207d99404e39925cab3 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Fri, 14 Oct 2011 15:52:54 +0200 Subject: [PATCH 3/8] feature #873: Add quota information to OCCI --- src/authm_mad/remotes/quota/quota.rb | 2 +- src/cloud/occi/lib/OCCIServer.rb | 81 ++++++++++++++++++++-------- src/cloud/occi/lib/occi-server.rb | 10 ++++ 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/authm_mad/remotes/quota/quota.rb b/src/authm_mad/remotes/quota/quota.rb index 1f13234e45..466cfe5013 100644 --- a/src/authm_mad/remotes/quota/quota.rb +++ b/src/authm_mad/remotes/quota/quota.rb @@ -242,9 +242,9 @@ class Quota pool = get_pool(res, user_id) base_xpath = "/#{res}_POOL/#{resource}" Quota.const_get("#{res}_USAGE".to_sym).each { |key, params| + usage[key] ||= 0 pool.each_xpath("#{base_xpath}/#{params[:xpath]}") { |elem| if elem - usage[key] ||= 0 if params[:count] usage[key] += 1 else diff --git a/src/cloud/occi/lib/OCCIServer.rb b/src/cloud/occi/lib/OCCIServer.rb index 85a8f4432a..e666651546 100755 --- a/src/cloud/occi/lib/OCCIServer.rb +++ b/src/cloud/occi/lib/OCCIServer.rb @@ -27,6 +27,8 @@ require 'VirtualNetworkOCCI' require 'VirtualNetworkPoolOCCI' require 'ImageOCCI' require 'ImagePoolOCCI' +require 'UserOCCI' +require 'UserPoolOCCI' require 'pp' @@ -36,7 +38,6 @@ require 'pp' # OpenNebula Engine ############################################################################## class OCCIServer < CloudServer - # Server initializer # config_file:: _String_ path of the config file # template:: _String_ path to the location of the templates @@ -72,7 +73,7 @@ class OCCIServer < CloudServer def get_computes(request) # --- Get User's VMs --- user_flag = -1 - + vmpool = VirtualMachinePoolOCCI.new( self.client, user_flag) @@ -99,14 +100,14 @@ class OCCIServer < CloudServer def get_networks(request) # --- Get User's VNETs --- user_flag = -1 - + network_pool = VirtualNetworkPoolOCCI.new( self.client, user_flag) # --- Prepare XML Response --- rc = network_pool.info - + if OpenNebula.is_error?(rc) if rc.message.match("Error getting") return rc, 404 @@ -125,14 +126,14 @@ class OCCIServer < CloudServer def get_storages(request) # --- Get User's Images --- user_flag = -1 - + image_pool = ImagePoolOCCI.new( self.client, user_flag) # --- Prepare XML Response --- rc = image_pool.info - + if OpenNebula.is_error?(rc) if rc.message.match("Error getting") return rc, 404 @@ -144,6 +145,24 @@ class OCCIServer < CloudServer return to_occi_xml(image_pool, 200) end + # Gets the pool representation of USERs + # request:: _Hash_ hash containing the data of the request + # [return] _String_,_Integer_ User pool representation or error, + # status code + def get_users(request) + # --- Get Users Pool --- + user_pool = UserPoolOCCI.new(self.client) + + # --- Prepare XML Response --- + rc = user_pool.info + + if OpenNebula.is_error?(rc) + return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] + end + + return to_occi_xml(user_pool, 200) + end + ############################################################################ ############################################################################ # ENTITY RESOURCE METHODS @@ -190,7 +209,7 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- rc = vm.info - + if OpenNebula.is_error?(rc) if rc.message.match("Error getting") return rc, 404 @@ -232,13 +251,13 @@ class OCCIServer < CloudServer vm = VirtualMachineOCCI.new( VirtualMachine.build_xml(params[:id]), self.client) - + rc = vm.info return rc, 400 if OpenNebula.is_error?(rc) - + xmldoc = XMLElement.build_xml(request.body, 'COMPUTE') vm_info = XMLElement.new(xmldoc) if xmldoc != nil - + # Check the number of changes in the request image_name = nil image_type = nil @@ -251,7 +270,7 @@ class OCCIServer < CloudServer image_type = disk.attr('.', 'type') } state = vm_info['STATE'] - + if image_name && state error_msg = "It is not allowed to change the state and save_as" << " a disk in the same request" @@ -270,12 +289,12 @@ class OCCIServer < CloudServer " suppossed to be saved" return OpenNebula::Error.new(error_msg), 400 end - + rc = vm.save_as(disk_id, image_name) if OpenNebula.is_error?(rc) image.delete return rc, 400 - end + end elsif state rc = vm.mk_action(state) return rc, 400 if OpenNebula.is_error?(rc) @@ -324,7 +343,7 @@ class OCCIServer < CloudServer # --- Prepare XML Response --- rc = network.info - + if OpenNebula.is_error?(rc) if rc.message.match("Error getting") return rc, 404 @@ -354,7 +373,7 @@ class OCCIServer < CloudServer return "", 204 end - + # Updates a NETWORK resource # request:: _Hash_ hash containing the data of the request # [return] _String_,_Integer_ Update confirmation msg or error, @@ -366,10 +385,10 @@ class OCCIServer < CloudServer vnet = VirtualNetworkOCCI.new( VirtualNetwork.build_xml(params[:id]), self.client) - + rc = vnet.info return rc, 400 if OpenNebula.is_error?(rc) - + if vnet_info['PUBLIC'] == 'YES' rc = vnet.publish return rc, 400 if OpenNebula.is_error?(rc) @@ -432,7 +451,7 @@ class OCCIServer < CloudServer self.client) rc = image.info - + if OpenNebula.is_error?(rc) if rc.message.match("Error getting") return rc, 404 @@ -464,7 +483,7 @@ class OCCIServer < CloudServer return "", 204 end - + # Updates a STORAGE resource # request:: _Hash_ hash containing the data of the request # [return] _String_,_Integer_ Update confirmation msg or error, @@ -476,10 +495,10 @@ class OCCIServer < CloudServer image = ImageOCCI.new( Image.build_xml(params[:id]), self.client) - + rc = image.info return rc, 400 if OpenNebula.is_error?(rc) - + if image_info['PERSISTENT'] && image_info['PUBLIC'] error_msg = "It is not allowed more than one change per request" return OpenNebula::Error.new(error_msg), 400 @@ -501,4 +520,24 @@ class OCCIServer < CloudServer image.info return to_occi_xml(image, 202) end + + # Get the representation of a USER + # request:: _Hash_ hash containing the data of the request + # [return] _String_,_Integer_ USER representation or error, + # status code + def get_user(request, params) + # --- Get the USER --- + user = UserOCCI.new( + User.build_xml(params[:id]), + self.client) + + # --- Prepare XML Response --- + rc = user.info + + if OpenNebula.is_error?(rc) + return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] + end + + return to_occi_xml(user, 200) + end end diff --git a/src/cloud/occi/lib/occi-server.rb b/src/cloud/occi/lib/occi-server.rb index 06ac629054..d0d355dce9 100755 --- a/src/cloud/occi/lib/occi-server.rb +++ b/src/cloud/occi/lib/occi-server.rb @@ -144,6 +144,11 @@ get '/storage' do treat_response(result,rc) end +get '/user' do + result,rc = @occi_server.get_users(request) + treat_response(result,rc) +end + ################################################### # Entity Resources Methods ################################################### @@ -192,3 +197,8 @@ put '/storage/:id' do result,rc = @occi_server.put_storage(request, params) treat_response(result,rc) end + +get '/user/:id' do + result,rc = @occi_server.get_user(request, params) + treat_response(result,rc) +end \ No newline at end of file From 409b565dc8067da25b0003dde51e6e2d427d322a Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Fri, 14 Oct 2011 15:53:32 +0200 Subject: [PATCH 4/8] feature #873: Add UserOCCI and UserPoolOCCI classes --- src/cloud/occi/lib/UserOCCI.rb | 64 ++++++++++++++++++++++++++++++ src/cloud/occi/lib/UserPoolOCCI.rb | 43 ++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/cloud/occi/lib/UserOCCI.rb create mode 100644 src/cloud/occi/lib/UserPoolOCCI.rb diff --git a/src/cloud/occi/lib/UserOCCI.rb b/src/cloud/occi/lib/UserOCCI.rb new file mode 100644 index 0000000000..e12b2cb7f7 --- /dev/null +++ b/src/cloud/occi/lib/UserOCCI.rb @@ -0,0 +1,64 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +require 'OpenNebula' + +include OpenNebula + +require 'quota' + +class UserOCCI < User + FORCE_USAGE = true + + OCCI_USER = %q{ + + <%= self.id.to_s %> + <%= self.name %> + + <% user_quota.each { |key,value| + key_s = key.to_s.upcase + value_i = value.to_i %> + <<%= key_s %>><%= value_i %>> + <% } %> + + + <% user_usage.each { |key,value| + key_s = key.to_s.upcase + value_i = value.to_i %> + <<%= key_s %>><%= value_i %>> + <% } %> + + + } + + # Class constructor + def initialize(xml, client) + super(xml, client) + end + + # Creates the OCCI representation of a User + def to_occi(base_url) + quota = Quota.new + user_usage = quota.get_usage(self.id, nil, FORCE_USAGE) + user_usage.delete(:uid) + + user_quota = quota.get_quota(self.id) + user_quota.delete(:uid) + + occi = ERB.new(OCCI_USER) + return occi.result(binding).gsub(/\n\s*/,'') + end +end diff --git a/src/cloud/occi/lib/UserPoolOCCI.rb b/src/cloud/occi/lib/UserPoolOCCI.rb new file mode 100644 index 0000000000..acb745a5f7 --- /dev/null +++ b/src/cloud/occi/lib/UserPoolOCCI.rb @@ -0,0 +1,43 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +require 'OpenNebula' + +include OpenNebula + +class UserPoolOCCI < UserPool + OCCI_USER_POOL = %q{ + + <% self.each{ |user| %> + + <% } %> + + } + + + # Creates the OCCI representation of a User Pool + def to_occi(base_url) + begin + occi = ERB.new(OCCI_USER_POOL) + occi_text = occi.result(binding) + rescue Exception => e + error = OpenNebula::Error.new(e.message) + return error + end + + return occi_text.gsub(/\n\s*/,'') + end +end \ No newline at end of file From 1040d34766de55ce42bef4f4ce83fb3179060894 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Fri, 14 Oct 2011 15:54:01 +0200 Subject: [PATCH 5/8] feature #873: Add OCCI GET /user tests --- src/cloud/occi/test/fixtures/user.xml | 1 + .../occi/test/fixtures/user_collection.xml | 1 + src/cloud/occi/test/spec/spec_helper.rb | 39 ++++++++++ src/cloud/occi/test/spec/user_spec.rb | 73 +++++++++++++++++++ src/cloud/occi/test/test.sh | 50 +++++++++++++ 5 files changed, 164 insertions(+) create mode 100644 src/cloud/occi/test/fixtures/user.xml create mode 100644 src/cloud/occi/test/fixtures/user_collection.xml create mode 100644 src/cloud/occi/test/spec/spec_helper.rb create mode 100644 src/cloud/occi/test/spec/user_spec.rb create mode 100755 src/cloud/occi/test/test.sh diff --git a/src/cloud/occi/test/fixtures/user.xml b/src/cloud/occi/test/fixtures/user.xml new file mode 100644 index 0000000000..e48812af7b --- /dev/null +++ b/src/cloud/occi/test/fixtures/user.xml @@ -0,0 +1 @@ +0oneadmin00000000 \ No newline at end of file diff --git a/src/cloud/occi/test/fixtures/user_collection.xml b/src/cloud/occi/test/fixtures/user_collection.xml new file mode 100644 index 0000000000..67f7f0baa6 --- /dev/null +++ b/src/cloud/occi/test/fixtures/user_collection.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/cloud/occi/test/spec/spec_helper.rb b/src/cloud/occi/test/spec/spec_helper.rb new file mode 100644 index 0000000000..b942548ed1 --- /dev/null +++ b/src/cloud/occi/test/spec/spec_helper.rb @@ -0,0 +1,39 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +FIXTURES_PATH = File.join(File.dirname(__FILE__),'../fixtures') +$: << File.join(File.dirname(__FILE__), '..', '..', 'lib') + +# Load the testing libraries +require 'rubygems' +require 'rspec' +require 'rack/test' + +# Load the Sinatra app +require 'occi-server' + +# Make Rack::Test available to all spec contexts +RSpec.configure do |conf| + conf.include Rack::Test::Methods +end + +# Set the Sinatra environment +set :environment, :test + +# Add an app method for RSpec +def app + Sinatra::Application +end diff --git a/src/cloud/occi/test/spec/user_spec.rb b/src/cloud/occi/test/spec/user_spec.rb new file mode 100644 index 0000000000..1a66f454ef --- /dev/null +++ b/src/cloud/occi/test/spec/user_spec.rb @@ -0,0 +1,73 @@ +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +require File.expand_path(File.dirname(__FILE__) + '/spec_helper') + +describe 'OCCI User tests' do + before(:all) do + @username_1 = "my_first_occi_user" + @userpass_1 = "my_first_occi_pass" + `oneuser create #{@username_1} #{@userpass_1}` + end + + it "should list the user collection" do + basic_authorize('oneadmin','4478db59d30855454ece114e8ccfa5563d21c9bd') + get '/user' + + last_response.status.should eql(200) + + xml_body = last_response.body + + user_collection = File.read(FIXTURES_PATH + '/user_collection.xml') + + xml_body.strip.should eql(user_collection.strip) + end + + + it "should check the error if the user collection is retrieved by a non oneadmin user" do + basic_authorize('my_first_occi_user','c08c5a6c535b6060b7b2af34e0d2f0ffb7e63b28') + get '/user' + + last_response.status.should eql(403) + end + + it "should show the user information, no quotas and no usage" do + basic_authorize('oneadmin','4478db59d30855454ece114e8ccfa5563d21c9bd') + get '/user/0' + + last_response.status.should eql(200) + + xml_body = last_response.body + + user = File.read(FIXTURES_PATH + '/user.xml') + + xml_body.strip.should eql(user.strip) + end + + it "should get a 404 error when trying to get a non existing user" do + basic_authorize('oneadmin','4478db59d30855454ece114e8ccfa5563d21c9bd') + get '/user/99' + + last_response.status.should eql(404) + end + + it "should get a 403 error when trying to get a different user" do + basic_authorize('my_first_occi_user','c08c5a6c535b6060b7b2af34e0d2f0ffb7e63b28') + get '/user/0' + + last_response.status.should eql(403) + end +end diff --git a/src/cloud/occi/test/test.sh b/src/cloud/occi/test/test.sh new file mode 100755 index 0000000000..e61a10bb6f --- /dev/null +++ b/src/cloud/occi/test/test.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# -------------------------------------------------------------------------- # +# Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); you may # +# not use this file except in compliance with the License. You may obtain # +# a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +#--------------------------------------------------------------------------- # + +if [ -z $ONE_LOCATION ]; then + echo "ONE_LOCATION not defined." + exit -1 +fi + +VAR_LOCATION="$ONE_LOCATION/var" +rm -rf $VAR_LOCATION/* + +if [ "$(ls -A $VAR_LOCATION)" ]; then + echo "$VAR_LOCATION is not empty." + exit -1 +fi + +for j in `ls ./spec/*_spec.rb` ; do + PID=$$ + + oned -f & + sleep 2s; + + rspec $j -f s + CODE=$? + + pkill -P $PID oned + sleep 2s; + pkill -9 -P $PID oned + + if [ $CODE != 0 ] ; then + exit 1 + fi + + find $VAR_LOCATION -mindepth 1 -delete +done From faa3a8b8c687afee7feda3372878106352624dff Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Fri, 14 Oct 2011 16:47:56 +0200 Subject: [PATCH 6/8] feature #801: Add onequota unset --- src/authm_mad/remotes/quota/onequota | 24 ++++++++++- src/authm_mad/remotes/quota/quota.rb | 41 ++++++++++++++++--- .../remotes/quota/test/quota_spec.rb | 26 ++++++------ 3 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/authm_mad/remotes/quota/onequota b/src/authm_mad/remotes/quota/onequota index a6fd24e669..0638a299c5 100755 --- a/src/authm_mad/remotes/quota/onequota +++ b/src/authm_mad/remotes/quota/onequota @@ -99,7 +99,7 @@ cmd=CommandParser::CmdParser.new(ARGV) do arg_list.map! {|a| a.to_i } [0, arg_list] end - + set :format, :userid, OpenNebulaHelper.rname_to_id_desc("USER") do |arg| OpenNebulaHelper.rname_to_id(arg, "USER") end @@ -130,6 +130,26 @@ cmd=CommandParser::CmdParser.new(ARGV) do exit_with_code 0 end + ######################################################################## + unset_desc = <<-EOT.unindent + Unset a quota for a given user. + Examples: + onequota unset 3 cpu + onequota unset 4 cpu,memory,storage + EOT + + command :unset, unset_desc, :userid, :quota_list do + user_id, keys = args + + values_hash = Hash.new + keys.each_with_index { |k,i| + values_hash[k.to_sym] = 0 + } + + quota.set_quota(user_id, values_hash) + exit_with_code 0 + end + ######################################################################## delete_desc = "Delete the defined quotas for the given user" @@ -140,7 +160,7 @@ cmd=CommandParser::CmdParser.new(ARGV) do ######################################################################## show_desc = "Show the user's quota and usage. (usage/quota)" - + FORCE={ :name => "force", :short => "-f", diff --git a/src/authm_mad/remotes/quota/quota.rb b/src/authm_mad/remotes/quota/quota.rb index 466cfe5013..805eff1d6e 100644 --- a/src/authm_mad/remotes/quota/quota.rb +++ b/src/authm_mad/remotes/quota/quota.rb @@ -154,6 +154,19 @@ class Quota set(QUOTA_TABLE, uid, quota) end + # Retrieves quota information for a given user + # + # @param [Integer, nil] uid the user id from which get the quota + # information, if nil will retrieve the quotas for all users. + # @return [Hash] Hash containing the quota information and the user id + # + # { + # :uid => 4, + # :cpu => 8, + # :memory => 8064, + # :num_vms => 4, + # :storage => 1240019 + # } def get_quota(uid=nil) limit = get(QUOTA_TABLE, uid) limit ? limit : @conf[:defaults].merge!(:uid => uid) @@ -192,14 +205,16 @@ class Quota msg = "" separator = "" info.each { |qname, quota_requested| - unless quota[qname] + unless quota[qname] || quota[qname]==0 next end - used = send(DB_QUOTA_SCHEMA[qname].name.to_sym, total[qname]) - request = send(DB_QUOTA_SCHEMA[qname].name.to_sym, quota_requested) - limit = send(DB_QUOTA_SCHEMA[qname].name.to_sym, quota[qname]) - spent = used + request + type = DB_QUOTA_SCHEMA[qname].name.to_sym + + used = send(type, total[qname]) + request = send(type, quota_requested) + limit = send(type, quota[qname]) + spent = used + request if spent > limit msg << separator @@ -228,6 +243,22 @@ class Quota ########################################################################### # Usage ########################################################################### + # Retrieves usage information for a given user + # + # @param [Integer] uid the user id from which get the usage information. + # @param ["VM", "IMAGE"] resource kind of resource. If nil will return + # the usage for all kinds of resources + # @param [true, false] force If true will force the usage calculation + # instead of retrieving it from the cache + # @return [Hash] Hash containing the usage information and the user id + # + # { + # :uid => 4, + # :cpu => 8, + # :memory => 8064, + # :num_vms => 4, + # :storage => 1240019 + # } def get_usage(user_id, resource=nil, force=false) if force if RESOURCES.include?(resource) diff --git a/src/authm_mad/remotes/quota/test/quota_spec.rb b/src/authm_mad/remotes/quota/test/quota_spec.rb index ff80f0c6c8..fd6d656f18 100644 --- a/src/authm_mad/remotes/quota/test/quota_spec.rb +++ b/src/authm_mad/remotes/quota/test/quota_spec.rb @@ -80,10 +80,10 @@ describe "Quota testing" do it "should check default cache (force)" do usage1force = @quota.get_usage(@uid1, nil, true) usage1force[:uid].should eql(0) - usage1force[:num_vms].should eql(nil) - usage1force[:cpu].should eql(nil) - usage1force[:memory].should eql(nil) - usage1force[:storage].should eql(nil) + usage1force[:num_vms].should eql(0) + usage1force[:cpu].should eql(0) + usage1force[:memory].should eql(0) + usage1force[:storage].should eql(0) end it "should authorize the user because there is no quota defined" do @@ -106,10 +106,10 @@ describe "Quota testing" do it "should check the usage cache is not updated" do usage1cache = @quota.get_usage(@uid1) usage1cache[:uid].should eql(0) - usage1cache[:num_vms].should eql(nil) - usage1cache[:cpu].should eql(nil) - usage1cache[:memory].should eql(nil) - usage1cache[:storage].should eql(nil) + usage1cache[:num_vms].should eql(0) + usage1cache[:cpu].should eql(0.0) + usage1cache[:memory].should eql(0) + usage1cache[:storage].should eql(0) end it "should check the cache (force)" do @@ -118,7 +118,7 @@ describe "Quota testing" do usage1force[:num_vms].should eql(1) usage1force[:cpu].should eql(2.0) usage1force[:memory].should eql(128) - usage1force[:storage].should eql(nil) + usage1force[:storage].should eql(0) end it "should check the usage cache is updated and contains the last usage" do @@ -127,7 +127,7 @@ describe "Quota testing" do usage1cache[:num_vms].should eql(1) usage1cache[:cpu].should eql(2.0) usage1cache[:memory].should eql(128) - usage1cache[:storage].should eql(nil) + usage1cache[:storage].should eql(0) end it "should add a new Image" do @@ -146,7 +146,7 @@ describe "Quota testing" do usage1cache[:num_vms].should eql(1) usage1cache[:cpu].should eql(2.0) usage1cache[:memory].should eql(128) - usage1cache[:storage].should eql(nil) + usage1cache[:storage].should eql(0) end it "should check the cache (force)" do @@ -252,7 +252,7 @@ describe "Quota testing" do end it "should not authorize the user because the vm quota is spent" do - err_msg = "CPU quota exceeded (Quota: 2.4, Used: 4.0, Asked: 2.0)." + err_msg = "CPU quota exceeded (Quota: 2.4, Used: 4.0, Requested: 2.0)" @quota.authorize(@uid1, @acl_vm_create).should eql(err_msg) @quota.authorize(@uid1, @acl_template_instantiate).should eql(err_msg) @@ -320,7 +320,7 @@ describe "Quota testing" do @quota.authorize(@uid1, @acl_vm_create).should eql(false) @quota.authorize(@uid1, @acl_template_instantiate).should eql(false) - err_msg = "STORAGE quota exceeded (Quota: 0, Used: 2000, Asked: 1474)." + err_msg = "STORAGE quota exceeded (Quota: 0, Used: 2000, Requested: 271)" @quota.authorize(@uid1, @acl_image_create).should eql(err_msg) end From f1272b23d880f1581727a14b58e0ccda17019f7f Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Mon, 17 Oct 2011 18:37:45 +0200 Subject: [PATCH 7/8] feature #849: Improve OCCI PUT compute --- src/cloud/occi/lib/OCCIServer.rb | 58 ++-------- src/cloud/occi/lib/VirtualMachineOCCI.rb | 135 +++++++++++++++++------ 2 files changed, 111 insertions(+), 82 deletions(-) diff --git a/src/cloud/occi/lib/OCCIServer.rb b/src/cloud/occi/lib/OCCIServer.rb index e666651546..6edf28d972 100755 --- a/src/cloud/occi/lib/OCCIServer.rb +++ b/src/cloud/occi/lib/OCCIServer.rb @@ -253,56 +253,18 @@ class OCCIServer < CloudServer self.client) rc = vm.info - return rc, 400 if OpenNebula.is_error?(rc) - - xmldoc = XMLElement.build_xml(request.body, 'COMPUTE') - vm_info = XMLElement.new(xmldoc) if xmldoc != nil - - # Check the number of changes in the request - image_name = nil - image_type = nil - vm_info.each('DISK/SAVE_AS') { |disk| - if image_name - error_msg = "It is only allowed one save_as per request" - return OpenNebula::Error.new(error_msg), 400 - end - image_name = disk.attr('.', 'name') - image_type = disk.attr('.', 'type') - } - state = vm_info['STATE'] - - if image_name && state - error_msg = "It is not allowed to change the state and save_as" << - " a disk in the same request" - return OpenNebula::Error.new(error_msg), 400 - elsif image_name - # Get the disk id - disk_id = vm_info.attr('DISK/SAVE_AS/..', 'id') - if disk_id.nil? - error_msg = "DISK id attribute not specified" - return OpenNebula::Error.new(error_msg), 400 - end - - disk_id = disk_id.to_i - if vm["TEMPLATE/DISK[DISK_ID=\"#{disk_id}\"]/SAVE_AS"] - error_msg = "The disk #{disk_id} is already" << - " suppossed to be saved" - return OpenNebula::Error.new(error_msg), 400 - end - - rc = vm.save_as(disk_id, image_name) - if OpenNebula.is_error?(rc) - image.delete - return rc, 400 - end - elsif state - rc = vm.mk_action(state) - return rc, 400 if OpenNebula.is_error?(rc) + if OpenNebula.is_error?(rc) + return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] end - # --- Prepare XML Response --- - vm.info - return to_occi_xml(vm, 202) + result, code = vm.update_from_xml(request.body) + + if OpenNebula.is_error?(result) + return result, code + else + vm.info + return to_occi_xml(vm, code) + end end ############################################################################ diff --git a/src/cloud/occi/lib/VirtualMachineOCCI.rb b/src/cloud/occi/lib/VirtualMachineOCCI.rb index 7ed361cc00..1f00212020 100755 --- a/src/cloud/occi/lib/VirtualMachineOCCI.rb +++ b/src/cloud/occi/lib/VirtualMachineOCCI.rb @@ -61,62 +61,48 @@ class VirtualMachineOCCI < VirtualMachine <% end %> } - + + OCCI_ACTION = { + "STOPPED" => { :from => ["ACTIVE"], :action => :stop}, + "SUSPENDED" => { :from => ["ACTIVE"], :action => :suspend}, + "RESUME" => { :from => ["STOPPED", "SUSPENDED"], :action => :resume}, + "CANCEL" => { :from => ["ACTIVE"], :action => :cancel}, + "SHUTDOWN" => { :from => ["ACTIVE"], :action => :shutdown}, + "DONE" => { :from => VM_STATE, :action => :finalize}, + } + # Class constructor def initialize(xml, client, xml_info=nil, types=nil, base=nil) super(xml, client) @vm_info = nil @template = nil @common_template = base + '/common.erb' if base - + if xml_info != nil xmldoc = XMLElement.build_xml(xml_info, 'COMPUTE') @vm_info = XMLElement.new(xmldoc) if xmldoc != nil end - + if @vm_info != nil itype = @vm_info['INSTANCE_TYPE'] - + if itype != nil and types[itype.to_sym] != nil @template = base + "/#{types[itype.to_sym][:template]}" end end - + end - - def mk_action(action_str) - case action_str.downcase - when "stopped" - rc = self.stop - when "suspended" - rc = self.suspend - when "resume" - rc = self.resume - when "cancel" - rc = self.cancel - when "shutdown" - rc = self.shutdown - when "done" - rc = self.finalize - else - error_msg = "Invalid state" - error = OpenNebula::Error.new(error_msg) - return error - end - - return rc - end - + def to_one_template() if @vm_info == nil error_msg = "Missing COMPUTE section in the XML body" return OpenNebula::Error.new(error_msg), 400 end - + if @template == nil return OpenNebula::Error.new("Bad instance type"), 500 end - + begin template = ERB.new(File.read(@common_template)).result(binding) template << ERB.new(File.read(@template)).result(binding) @@ -124,10 +110,10 @@ class VirtualMachineOCCI < VirtualMachine error = OpenNebula::Error.new(e.message) return error end - + return template end - + # Creates the VMI representation of a Virtual Machine def to_occi(base_url) begin @@ -138,9 +124,90 @@ class VirtualMachineOCCI < VirtualMachine return error end - return occi_vm_text.gsub(/\n\s*/,'') end + + # Update de resource from an XML representation of the COMPUTE + # @param [String] xml_compute XML representation of the COMPUTE + # @return [[nil, OpenNebula::Error], HTTP_CODE] If there is no error + #  the first component is nil. + def update_from_xml(xml_compute) + xmldoc = XMLElement.build_xml(xml_compute, 'COMPUTE') + vm_info = XMLElement.new(xmldoc) if xmldoc != nil + + action = nil + args = [] + + # Check if a state change is required + occi_state = vm_info['STATE'] + + if occi_state + # If a state is provided + occi_state.upcase! + if OCCI_ACTION.keys.include?(occi_state) + # If the requested state is one the OCCI action states + shash = OCCI_ACTION[occi_state] + if shash[:from].include?(state_str) + # Action to be performed + action = shash[:action] + elsif occi_state != state_str + # If the requested state is different from the resource + # state but it does not belong to the "from" state array + error_msg = "The state of the resource cannot be changed" \ + " from #{state_str} to #{occi_state}." + error = OpenNebula::Error.new(error_msg) + return error, 403 + end + elsif !VM_STATE.include?(occi_state) + # The requested state is not one of the OCCI action states nor + # a resource state + error_msg = "Invalid state: \"#{occi_state}\"" + error = OpenNebula::Error.new(error_msg) + return error, 400 + end + end + + # Check if a disk image save as is required + image_name = nil + vm_info.each('DISK/SAVE_AS') { |save_as| + image_name = save_as.attr('.', 'name') + if image_name + if action + # Return erro if an action has been defined before + if action == :save_as + error_msg = "Only one disk can be saved per request" + else + error_msg = "Changig the state of the resource and" \ + " saving a disk is not allowed in the same request" + end + error = OpenNebula::Error.new(error_msg) + return error, 403 + else + # if no action is defined yet and a save_as is requested + action = :save_as + disk_id = save_as.attr('..', 'id') + + # Params for the save_as action: + # save_as(disk_id, image_name) + args << disk_id.to_i + args << image_name + end + end + } + + # Perform the requested action + if action + rc = self.send(action, *args) + if OpenNebula.is_error?(rc) + return rc, CloudServer::HTTP_ERROR_CODE[rc.errno] + else + return nil, 202 + end + else + # There is no change requested + return nil, 200 + end + end end From 612dc314891a68fd7b8ab9e91ff9c198fe32b442 Mon Sep 17 00:00:00 2001 From: Daniel Molina Date: Mon, 17 Oct 2011 18:40:19 +0200 Subject: [PATCH 8/8] feature #849: Add OCCI PUT compute tests --- .../remotes/quota/test/quota_spec.rb | 6 +- .../occi/test/fixtures/{ => user}/user.xml | 0 .../fixtures/{ => user}/user_collection.xml | 0 .../test/fixtures/vm_save_as/newcompute.xml | 1 + .../save_a_disk_and_change_state.xml | 1 + .../fixtures/vm_save_as/save_first_disk.xml | 1 + .../fixtures/vm_save_as/save_second_disk.xml | 1 + .../fixtures/vm_save_as/save_two_disks.xml | 1 + src/cloud/occi/test/spec/spec_helper.rb | 4 +- src/cloud/occi/test/spec/user_spec.rb | 4 +- src/cloud/occi/test/spec/vm_saveas_spec.rb | 77 +++++++++++++++++++ src/cloud/occi/test/templates/image1.template | 2 + src/cloud/occi/test/templates/image2.template | 3 + src/cloud/occi/test/templates/vm.template | 10 +++ src/cloud/occi/test/test.sh | 5 +- 15 files changed, 107 insertions(+), 9 deletions(-) rename src/cloud/occi/test/fixtures/{ => user}/user.xml (100%) rename src/cloud/occi/test/fixtures/{ => user}/user_collection.xml (100%) create mode 100644 src/cloud/occi/test/fixtures/vm_save_as/newcompute.xml create mode 100644 src/cloud/occi/test/fixtures/vm_save_as/save_a_disk_and_change_state.xml create mode 100644 src/cloud/occi/test/fixtures/vm_save_as/save_first_disk.xml create mode 100644 src/cloud/occi/test/fixtures/vm_save_as/save_second_disk.xml create mode 100644 src/cloud/occi/test/fixtures/vm_save_as/save_two_disks.xml create mode 100644 src/cloud/occi/test/spec/vm_saveas_spec.rb create mode 100644 src/cloud/occi/test/templates/image1.template create mode 100644 src/cloud/occi/test/templates/image2.template create mode 100644 src/cloud/occi/test/templates/vm.template diff --git a/src/authm_mad/remotes/quota/test/quota_spec.rb b/src/authm_mad/remotes/quota/test/quota_spec.rb index fd6d656f18..cf58842847 100644 --- a/src/authm_mad/remotes/quota/test/quota_spec.rb +++ b/src/authm_mad/remotes/quota/test/quota_spec.rb @@ -31,7 +31,7 @@ describe "Quota testing" do