1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-21 14:50:08 +03:00

feature #754: Add quota functionality

This commit is contained in:
Daniel Molina 2011-08-26 19:08:15 +02:00
parent e38d385dc6
commit c6e8767f3b
3 changed files with 311 additions and 122 deletions

165
src/authm_mad/remotes/quota/authorize Normal file → Executable file
View File

@ -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

View File

@ -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

View File

@ -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