diff --git a/src/authm_mad/remotes/quota/authorize b/src/authm_mad/remotes/quota/authorize old mode 100644 new mode 100755 index ee6047ebbc..19c0ca02f4 --- a/src/authm_mad/remotes/quota/authorize +++ b/src/authm_mad/remotes/quota/authorize @@ -1,3 +1,5 @@ +#!/usr/bin/env ruby + # -------------------------------------------------------------------------- # # Copyright 2002-2011, OpenNebula Project Leads (OpenNebula.org) # # # @@ -14,128 +16,47 @@ # limitations under the License. # #--------------------------------------------------------------------------- # -require 'one_usage' +ONE_LOCATION=ENV["ONE_LOCATION"] -# Quota functionality for auth driver. Stores in database limits for each -# user and using OneUsage is able to retrieve resource usage from -# OpenNebula daemon and check if it is below limits -class Quota - attr_accessor :defaults - - TABLE_NAME=:quotas - - # 'db' is a Sequel database where to store user limits and client - # is OpenNebula::Client used to connect to OpenNebula daemon - def initialize(db, client, conf={}) - @db=db - @client=client - @conf={ - :defaults => { - :cpu => nil, - :memory => nil, - :num_vms => nil - } - }.merge(conf) - - @defaults=@conf[:defaults] - - @usage=OneUsage.new(@client) - - create_table - @table=@db[TABLE_NAME] - end - - # Creates database quota table if it does not exist - def create_table - @db.create_table?(TABLE_NAME) do - primary_key :id - Integer :uid - Float :cpu - Integer :memory - Integer :num_vms - index :uid - end - end - - # Adds new user limits - def set(uid, cpu, memory, num_vms) - data={ - :cpu => cpu, - :memory => memory, - :num_vms => num_vms - } - - quotas=@table.filter(:uid => uid) - - if quotas.first - #STDERR.puts "updating" - quotas.update(data) - else - #STDERR.puts "inserting" - @table.insert(data.merge!(:uid => uid)) - end - end - - # Gets user limits - def get(uid) - limit=@table.filter(:uid => uid).first - if limit - limit - else - @defaults - end - end - - # Checks if the user is below resource limits. If new_vm is defined - # checks if its requirements fit in limits - def check(user, new_vm=nil) - use=@usage.total(user) - use_after=use.clone - user_quota=get(user) - if new_vm - use_after.cpu+=new_vm.cpu.to_f - use_after.memory+=new_vm.memory.to_i - use_after.num_vms+=1 - end - - STDERR.puts [user_quota, use_after, new_vm].inspect - - error_message="" - - if !(!user_quota[:cpu] || use_after.cpu<=user_quota[:cpu]) - error_message<<"Cpu quota exceeded (Quota: #{user_quota[:cpu]}, "+ - "Used: #{use.cpu}" - error_message<<", asked: #{new_vm.cpu.to_f}" if new_vm - error_message<<")." - end - - if !(!user_quota[:memory] || use_after.memory<=user_quota[:memory]) - error_message<<" Memory quota exceeded (Quota: "+ - "#{user_quota[:memory]}, Used: #{use.memory}" - error_message<<", asked: #{new_vm.memory.to_i}" if new_vm - error_message<<")." - end - - if !(!user_quota[:num_vms] || use_after.num_vms<=user_quota[:num_vms]) - error_message<<" Num VMS quota exceeded (Quota: "+ - "#{user_quota[:memory]}, Used: #{use.num_vms})." - end - - if error_message=="" - false - else - error_message.strip - end - end - - # Updates user resource consuption - def update(user) - @usage.update_user(user) - end - - # Get cache for the user - def get_user(user) - @usage.vms(user) - end +if !ONE_LOCATION + RUBY_LIB_LOCATION="/usr/lib/one/ruby" + ETC_LOCATION="/etc/one/" +else + RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby" + ETC_LOCATION=ONE_LOCATION+"/etc/" end +$: << RUBY_LIB_LOCATION + +require 'scripts_common' +require 'quota' + +user_id = ARGV.shift + +overall_evalutation = ARGV.pop +exit -1 if overall_evalutation == 0 + +quota = Quota.new + +#q = { +# :cpu => 10, +# :memory => 2048, +# :storage => 100000, +# :num_vms => 5 +#} +# +#quota.set(1, q) +#OpenNebula.log_debug("quotas: #{quota.get(1)}") + +ARGV.each {|request| + rc = quota.check_request(user_id, request) + + if rc + OpenNebula.error_message rc + exit -1 + end +} + +#OpenNebula.log_debug("AUTHORIZE ARGS: #{ARGV.join(' ')}") + +exit 0 \ No newline at end of file diff --git a/src/authm_mad/remotes/quota/one_usage.rb b/src/authm_mad/remotes/quota/one_usage.rb new file mode 100644 index 0000000000..8a52a84121 --- /dev/null +++ b/src/authm_mad/remotes/quota/one_usage.rb @@ -0,0 +1,113 @@ +# -------------------------------------------------------------------------- # +# 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' + +# This class retrieves and caches vms and its consuption grouped +# by users. 'update_user' method should be called to fill data for +# a user before any calculation is made +class OneUsage + VM_USAGE = { + :cpu => { + :proc_info => lambda {|template| template['CPU']}, + :proc_total => lambda {|resource| resource['TEMPLATE']['CPU']} + }, + :memory => { + :proc_info => lambda {|template| template['MEMORY']}, + :proc_total => lambda {|resource| resource['TEMPLATE']['MEMORY']} + }, + :num_vms => { + :proc_info => lambda {|template| 1 }, + :proc_total => lambda {|resource| 1 } + } + } + + IMAGE_USAGE = { + :storage => { + :proc_info => lambda {|template| File.size(template['PATH']) }, + :proc_total => lambda {|resource| File.size(resource['TEMPLATE']['SOURCE']) } + } + } + + def initialize() + @client = OpenNebula::Client.new + + @usage = Hash.new + + keys = VM_USAGE.keys + IMAGE_USAGE.keys + keys.each { |key| + @usage[key] = 0 + } + + end + + + def total(resource, user_id) + pool_array = pool_to_array(resource, user_id) + + usage = Hash.new + pool_array.each { |elem| + OneUsage.const_get("#{resource}_USAGE".to_sym).each { |key, params| + usage[key] ||= 0 + usage[key] += params[:proc_total].call(elem).to_i + } + } + + usage + end + + # Retrieve the useful information of the template for the specified + # kind of resource + def get_info(resource, xml_template) + template = OpenNebula::XMLElement.new + template.initialize_xml(xml_template, 'TEMPLATE') + + info = Hash.new + + self.class.const_get("#{resource}_USAGE").each { |key, params| + info[key] = params[:proc_info].call(template).to_i + } + + info + end + + private + + # Returns a an Array than contains the elements of the resource Pool + def pool_to_array(resource, user_id) + pool = case resource + when "VM" then OpenNebula::VirtualMachinePool.new(@client, user_id) + when "IMAGE" then OpenNebula::ImagePool.new(@client, user_id) + end + + rc = pool.info + + phash = pool.to_hash + + if phash["#{resource}_POOL"] && + phash["#{resource}_POOL"]["#{resource}"] + if phash["#{resource}_POOL"]["#{resource}"].instance_of?(Array) + parray = phash["#{resource}_POOL"]["#{resource}"] + else + parray = [phash["#{resource}_POOL"]["#{resource}"]] + end + else + parray = Array.new + end + + return parray + end +end diff --git a/src/authm_mad/remotes/quota/quota.rb b/src/authm_mad/remotes/quota/quota.rb new file mode 100644 index 0000000000..69977c60b4 --- /dev/null +++ b/src/authm_mad/remotes/quota/quota.rb @@ -0,0 +1,155 @@ +# -------------------------------------------------------------------------- # +# 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 'one_usage' +require 'sequel' +require 'base64' + +# Quota functionality for auth driver. Stores in database limits for each +# user and using OneUsage is able to retrieve resource usage from +# OpenNebula daemon and check if it is below limits +class Quota + attr_accessor :defaults + + TABLE_NAME = :quotas + + DB_QUOTA_SCHEMA = { + :cpu => Float, + :memory => Integer, + :num_vms => Integer, + :storage => Integer + } + + CONF = { + :db => "sqlite:///tmp/onequota.db", + :defaults => { + :cpu => nil, + :memory => nil, + :num_vms => nil, + :storage => nil + } + } + + # 'db' is a Sequel database where to store user limits and client + # is OpenNebula::Client used to connect to OpenNebula daemon + def initialize(conf={}) + # TBD merge with the conf file + @conf=CONF + + @defaults=@conf[:defaults] + + @db=Sequel.connect(@conf[:db]) + + create_table + @table=@db[TABLE_NAME] + + @one_usage=OneUsage.new + end + + ########################################################################### + # DB handling + ########################################################################### + + # Creates database quota table if it does not exist + def create_table + @db.create_table?(TABLE_NAME) do + Integer :uid + + DB_QUOTA_SCHEMA.each { |key,value| + column key, value + } + + primary_key :uid + index :uid + end + end + + # Adds new user limits + def set(uid, quota={}) + data=quota.delete_if{|key,value| !DB_QUOTA_SCHEMA.keys.include?(key)} + + quotas=@table.filter(:uid => uid) + + if quotas.first + quotas.update(data) + else + @table.insert(data.merge!(:uid => uid)) + end + end + + # Gets user limits + def get(uid) + limit=@table.filter(:uid => uid).first + if limit + limit + else + @conf[:defaults] + end + end + + + ########################################################################### + # Authorization + ########################################################################### + + def check_request(user_id, request) + obj, template_or_id, op, owner, pub, acl_eval = request.split(':') + + if acl_eval == 0 + return "ACL evaluation denied" + end + + # Check if this op needs to check the quota + return false unless with_quota?(obj, op) + + # If the object is a template the info should be retrived from the + # VM pool. + obj = "VM" if obj == "TEMPLATE" + template = Base64::decode64(template_or_id) + + check_quotas(user_id.to_i, obj, template) + end + + def check_quotas(user_id, obj, template) + info = @one_usage.get_info(obj, template) + total = @one_usage.total(obj, user_id) + quota = get(user_id) + + msg = "" + info.each { |quota_name, quota_requested| + spent = total[quota_name].to_i + quota_requested.to_i + if spent > quota[quota_name].to_i + msg << " #{quota_name.to_s.upcase} quota exceeded " + msg << "(Quota: #{quota[quota_name].to_i}, " + msg << "Used: #{spent.to_i}, " + msg << "Asked: #{quota_requested.to_i})." + end + } + + if msg=="" + return false + else + return msg.strip + end + end + + def with_quota?(obj, op) + return (obj == "VM" && op == "CREATE") || + (obj == "IMAGE" && op == "CREATE") || + (obj == "TEMPLATE" && op == "INSTANTIATE") + end +end +