1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-02-03 13:47:01 +03:00

Merge branch 'feature-1098'

This commit is contained in:
Ruben S. Montero 2012-08-31 19:51:28 +02:00
commit ef8e73becc
22 changed files with 1421 additions and 571 deletions

View File

@ -1194,7 +1194,8 @@ ONE_CLI_LIB_FILES="src/cli/one_helper/onegroup_helper.rb \
src/cli/one_helper/onevnet_helper.rb \
src/cli/one_helper/oneacl_helper.rb \
src/cli/one_helper/onedatastore_helper.rb \
src/cli/one_helper/onecluster_helper.rb"
src/cli/one_helper/onecluster_helper.rb \
src/cli/one_helper/oneacct_helper.rb"
CLI_BIN_FILES="src/cli/onevm \
src/cli/onehost \
@ -1205,7 +1206,8 @@ CLI_BIN_FILES="src/cli/onevm \
src/cli/onegroup \
src/cli/oneacl \
src/cli/onedatastore \
src/cli/onecluster"
src/cli/onecluster \
src/cli/oneacct"
CLI_CONF_FILES="src/cli/etc/onegroup.yaml \
src/cli/etc/onehost.yaml \

View File

@ -54,61 +54,206 @@ module CommandParser
attr_reader :options, :args
def initialize(args=[], &block)
@opts = Array.new
@available_options = Array.new
@commands = Hash.new
@formats = Hash.new
@script = nil
@main = nil
@exit_code = nil
@args = args
@options = Hash.new
set :format, :file, "Path to a file" do |arg|
format_file(arg)
end
set :format, :range, "List of id's in the form 1,8..15" do |arg|
format_range(arg)
end
set :format, :text, "String" do |arg|
format_text(arg)
end
define_default_formats
instance_eval(&block)
self.run
end
# Defines the usage information of the command
# @param [String] str
def usage(str)
@usage=<<EOT
## SYNOPSIS
#{str}
EOT
@usage=str
end
# Defines the version the command
# @param [String] str
def version(str)
@version = str
end
# Defines the additional information of the command
# @param [String] str
def description(str)
@description = str
end
def set(e, *args, &block)
case e
when :option
add_option(args[0])
when :format
add_format(args[0], args[1], block)
# Defines a block that will be used to parse the arguments
# of the command. Formats defined using this method con be used
# in the arguments section of the command method, when defining a new
# action
#
# @param [Symbol] format name of the format
# @param [String] description
#
# @yieldreturn [Array[Integer, String]] the block must return an Array
# containing the result (0:success, 1:failure) and the
# new value for the argument.
def format(format, description, &block)
@formats[format] = {
:desc => description,
:proc => block
}
end
# Defines a global option for the command that will be used for all the
# actions
# @param [Hash, Array<Hash>] options the option to be included. An
# array of options can be also provided
# @option options [String] :name
# @option options [String] :short
# @option options [String] :large
# @option options [String] :description
# @option options [Class] :format
# @option options [Block] :proc The block receives the value of the
# option and the hash of options. The block must return an Array
# containing the result (0:success, 1:failure) and the
# new value for the argument or nil. More than one option can be
# specified in the block using the options hash. This hash will be
# available inside the command block.
#
# @example
# This example will define the following options:
# options[:type] = type
#
# TYPE={
# :name => "type",
# :short => "-t type",
# :large => "--type type",
# :format => String,
# :description => "Type of the new Image"
# }
#
# This example will define the following options:
# options[:check] = true
# options[:datastore] = id
#
# DATASTORE = {
# :name => "datastore",
# :short => "-d id|name",
# :large => "--datastore id|name" ,
# :description => "Selects the datastore",
# :format => String,
# :proc => lambda { |o, options|
# options[:check] = true
# [0, OpenNebulaHelper.dname_to_id(o)]
# }
# }
#
def option(options)
if options.instance_of?(Array)
options.each { |o| @available_options << o }
elsif options.instance_of?(Hash)
@available_options << options
end
end
# Defines the exit code to be returned by the command
# @param [Integer] code
def exit_code(code)
@exit_code = code
end
def exit_with_code(code, output=nil)
puts output if output
exit code
end
# Defines a new action for the command, several actions can be defined
# for a command. For example: create, delete, list.
# The options and args variables can be used inside the block, and
# they contain the parsedarguments and options.
#
# @param [Symbol] name Name of the action (i.e: :create, :list)
# @param [String] desc Description of the action
# @param [Array<Symbol, Array<Symbol, nil>>, Hash] args_format arguments
# or specific options for this actiion
# Note that the first argument of the command is the
# action and should not be defined using this parameter. The rest of
# the argument must be defined using this parameter.
# This parameter can use formats previously defined with the format
# method
# Options are specified using a hash :options => ... containing
# the hashes representing the options. The option method doc contains
# the hash that has to be used to specify an option
# @yieldreturn [Integer, Array[Integer, String]] the block must
# return the exit_code and if a String is returned it will be printed
#
# @example
# Definining two arguments:
# $ onetest test a1 a2
#
# CommandParser::CmdParser.new(ARGV) do
# description "Test"
# usage "onetest <command> <args> [options]"
# version "1.0"
#
# options VERBOSE, HELP
#
# command :test, "Test", :test1, :test2, :options => XML do
# puts options[:xml]
# puts options[:verbose]
# puts args[0]
# puts args[1]
# [0, "It works"]
# end
# end
#
#
# Defining optional arguments: test1 is mandatory, test2 optional
# $ onetest test a1 | $ onetest test a1 a2
#
# CommandParser::CmdParser.new(ARGV) do
# description "Test"
# usage "onetest <command> <args> [options]"
# version "1.0"
#
# options VERBOSE, HELP
#
# command :test, "Test", :test1, [:test2, nil], :options => XML do
# puts options[:xml]
# puts options[:verbose]
# puts args[0]
# puts "It works"
# 0
# end
# end
#
#
# Defining an argument with different formats:
# $ onetest test a1 a2 | $ onetest test a1 123
#
# CommandParser::CmdParser.new(ARGV) do
# description "Test"
# usage "onetest <command> <args> [options]"
# version "1.0"
#
# options VERBOSE, HELP
#
# format :format1, "String to Integer" do
# [0, arg.to_i]
# end
#
# command :test, "Test", :test1, [:format1, format2], :options => XML do
# puts options[:xml]
# puts options[:verbose]
# puts args[0]
# 0
# end
# end
#
def command(name, desc, *args_format, &block)
cmd = Hash.new
cmd[:desc] = desc
@ -130,28 +275,119 @@ EOT
@commands[name.to_sym] = cmd
end
def script(*args_format, &block)
@script=Hash.new
@script[:args_format] = Array.new
# Defines a new action for the command, several actions can be defined
# for a command. For example: create, delete, list.
# The options and args variables can be used inside the block, and
# they contain the parsedarguments and options.
#
# @param [Array<Symbol, Array<Symbol, nil>>] args_format arguments
# or specific options for this actiion
# Note that the first argument of the command is the
# action and should not be defined using this parameter. The rest of
# the argument must be defined using this parameter.
# This parameter can use formats previously defined with the format
# method
# @yieldreturn [Integer, Array[Integer, String]] the block must
# return the exit_code and if a String is returned it will be printed
#
# @example
# Definining two arguments:
# $ onetest a1 a2
#
# CommandParser::CmdParser.new(ARGV) do
# description "Test"
# usage "onetest <args> [options]"
# version "1.0"
#
# options XML, VERBOSE, HELP
#
# main :test1, :test2 do
# puts options[:xml]
# puts options[:verbose]
# puts args[0]
# puts args[1]
# [0, "It works"]
# end
# end
#
#
# Defining optional arguments: test1 is mandatory, test2 optional
# $ onetest a1 | $ onetest a1 a2
#
# CommandParser::CmdParser.new(ARGV) do
# description "Test"
# usage "onetest <args> [<options>]"
# version "1.0"
#
# options XML, VERBOSE, HELP
#
# main :test1, [:test2, nil] do
# puts options[:xml]
# puts options[:verbose]
# puts args[0]
# puts "It works"
# 0
# end
# end
#
#
# Defining an argument with different formats:
# $ onetest a1 a2 | $ onetest a1 123
#
# CommandParser::CmdParser.new(ARGV) do
# description "Test"
# usage "onetest <args> [<options>]"
# version "1.0"
#
# options XML, VERBOSE, HELP
#
# format :format1, "String to Integer" do
# [0, arg.to_i]
# end
#
# main :test1, [:format1, :format2] do
# puts options[:xml]
# puts options[:verbose]
# puts args[0]
# puts args[1]
# 0
# end
# end
#
def main(*args_format, &block)
@main=Hash.new
@main[:arity] = 0
@main[:args_format] = Array.new
args_format.collect {|args|
if args.instance_of?(Array)
@script[:arity]+=1 unless args.include?(nil)
@script[:args_format] << args
@main[:arity]+=1 unless args.include?(nil)
@main[:args_format] << args
elsif args.instance_of?(Hash) && args[:options]
@opts << args[:options]
@available_options << args[:options]
else
@script[:arity]+=1
@script[:args_format] << [args]
@main[:arity]+=1
@main[:args_format] << [args]
end
}
@script[:proc] = block
@main[:proc] = block
end
# DEPRECATED, use format and options instead
def set(e, *args, &block)
case e
when :option
option(args[0])
when :format
format(args[0], args[1], &block)
end
end
def run
comm_name=""
if @script
comm=@script
if @main
comm=@main
elsif
if @args[0] && !@args[0].match(/^-/)
comm_name=@args.shift.to_sym
@ -160,7 +396,7 @@ EOT
end
if comm.nil?
help
print_help
exit -1
end
@ -175,133 +411,17 @@ EOT
puts rc[1]
exit rc.first
else
exit rc
exit(@exit_code || rc)
end
end
end
def help
puts @usage if @usage
puts
puts @description if @description
puts
print_options
puts
print_commands
puts
print_formatters
puts
if @version
puts "## LICENSE"
puts @version
end
end
private
def print_options
puts "## OPTIONS"
shown_opts = Array.new
opt_format = "#{' '*5}%-25s %s"
@commands.each{ |key,value|
value[:options].flatten.each { |o|
if shown_opts.include?(o[:name])
next
else
shown_opts << o[:name]
str = ""
str << o[:short].split(' ').first << ', ' if o[:short]
str << o[:large]
printf opt_format, str, o[:description]
puts
end
}
}
@opts.each{ |o|
str = ""
str << o[:short].split(' ').first << ', ' if o[:short]
str << o[:large]
printf opt_format, str, o[:description]
puts
}
end
def print_commands
puts "## COMMANDS"
cmd_format5 = "#{' '*3}%s"
cmd_format10 = "#{' '*8}%s"
@commands.each{ |key,value|
printf cmd_format5, "* #{key} "
args_str=value[:args_format].collect{ |a|
if a.include?(nil)
"[<#{a.compact.join("|")}>]"
else
"<#{a.join("|")}>"
end
}.join(' ')
printf "#{args_str}"
puts
value[:desc].split("\n").each { |l|
printf cmd_format10, l
puts
}
unless value[:options].empty?
opts_str=value[:options].flatten.collect{|o|
o[:name]
}.join(', ')
printf cmd_format10, "valid options: #{opts_str}"
puts
end
puts
}
end
def print_formatters
puts "## ARGUMENT FORMATS"
cmd_format5 = "#{' '*3}%s"
cmd_format10 = "#{' '*8}%s"
@formats.each{ |key,value|
printf cmd_format5, "* #{key}"
puts
value[:desc].split("\n").each { |l|
printf cmd_format10, l
puts
}
puts
}
end
def add_option(option)
if option.instance_of?(Array)
option.each { |o| @opts << o }
elsif option.instance_of?(Hash)
@opts << option
end
end
def add_format(format, description, block)
@formats[format] = {
:desc => description,
:proc => block
}
end
def parse(extra_options)
@cmdparse=OptionParser.new do |opts|
merge = @opts
merge = @opts + extra_options if extra_options
merge = @available_options
merge = @available_options + extra_options if extra_options
merge.flatten.each do |e|
args = []
args << e[:short] if e[:short]
@ -311,9 +431,16 @@ EOT
opts.on(*args) do |o|
if e[:proc]
e[:proc].call(o, @options)
rc = e[:proc].call(o, @options)
if rc.instance_of?(Array) && rc[0] == 0
options[e[:name].to_sym] = rc[1]
else
puts rc[1]
puts "option #{e[:name]}: Parsing error"
exit -1
end
elsif e[:name]=="help"
help
print_help
exit
elsif e[:name]=="version"
puts @version
@ -399,7 +526,126 @@ EOT
end
########################################################################
# Formatters for arguments
# Printers
########################################################################
def print_help
if @usage
puts "## SYNOPSIS"
puts
puts @usage
puts
end
puts @description if @description
puts
print_options
puts
print_commands
puts
print_formatters
puts
if @version
puts "## LICENSE"
puts @version
end
end
def print_options
puts "## OPTIONS"
shown_opts = Array.new
opt_format = "#{' '*5}%-25s %s"
@commands.each{ |key,value|
value[:options].flatten.each { |o|
if shown_opts.include?(o[:name])
next
else
shown_opts << o[:name]
str = ""
str << o[:short].split(' ').first << ', ' if o[:short]
str << o[:large]
printf opt_format, str, o[:description]
puts
end
}
}
@available_options.each{ |o|
str = ""
str << o[:short].split(' ').first << ', ' if o[:short]
str << o[:large]
printf opt_format, str, o[:description]
puts
}
end
def print_commands
cmd_format5 = "#{' '*3}%s"
cmd_format10 = "#{' '*8}%s"
if @main
print_command(@main)
else
puts "## COMMANDS"
@commands.each{ |key,value|
printf cmd_format5, "* #{key} "
print_command(value)
}
end
end
def print_command(command)
args_str=command[:args_format].collect{ |a|
if a.include?(nil)
"[<#{a.compact.join("|")}>]"
else
"<#{a.join("|")}>"
end
}.join(' ')
printf "#{args_str}"
puts
command[:desc].split("\n").each { |l|
printf cmd_format10, l
puts
}
unless command[:options].empty?
opts_str=command[:options].flatten.collect{|o|
o[:name]
}.join(', ')
printf cmd_format10, "valid options: #{opts_str}"
puts
end
puts
end
def print_formatters
puts "## ARGUMENT FORMATS"
cmd_format5 = "#{' '*3}%s"
cmd_format10 = "#{' '*8}%s"
@formats.each{ |key,value|
printf cmd_format5, "* #{key}"
puts
value[:desc].split("\n").each { |l|
printf cmd_format10, l
puts
}
puts
}
end
########################################################################
# Default Formatters for arguments
########################################################################
def format_text(arg)
arg.instance_of?(String) ? [0,arg] : [-1]
@ -432,6 +678,20 @@ EOT
return 0,ids.uniq
end
def define_default_formats
format :file, "Path to a file" do |arg|
format_file(arg)
end
format :range, "List of id's in the form 1,8..15" do |arg|
format_range(arg)
end
format :text, "String" do |arg|
format_text(arg)
end
end
end
end

View File

@ -0,0 +1,179 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2012, 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_helper'
class AcctHelper < OpenNebulaHelper::OneHelper
START_TIME = {
:name => "start_time",
:short => "-s TIME",
:large => "--start TIME" ,
:description => "Start date and time to take into account",
:format => String # TODO Time
}
END_TIME = {
:name => "end_time",
:short => "-e TIME",
:large => "--end TIME" ,
:description => "End date and time",
:format => String # TODO Time
}
USER = {
:name => "user",
:short => "-u user",
:large => "--user user" ,
:description => "User name or id to filter the results",
:format => String,
:proc => lambda { |o, options|
OpenNebulaHelper.rname_to_id(o, "USER")
}
}
GROUP = {
:name => "group",
:short => "-g group",
:large => "--group group" ,
:description => "Group name or id to filter the results",
:format => String,
:proc => lambda { |o, options|
puts o
OpenNebulaHelper.rname_to_id(o, "GROUP")
}
}
HOST = {
:name => "host",
:short => "-H HOST",
:large => "--host HOST" ,
:description => "Host name or id to filter the results",
:format => String,
:proc => lambda { |o, options|
OpenNebulaHelper.rname_to_id(o, "HOST")
}
}
XPATH = {
:name => "xpath",
:large => "--xpath XPATH_EXPRESSION" ,
:description => "Xpath expression to filter the results. \
For example: oneacct --xpath 'HISTORY[ETIME>0]'",
:format => String
}
XML = {
:name => "xml",
:short => "-x",
:large => "--xml",
:description => "Show the resource in xml format"
}
JSON = {
:name => "json",
:short => "-j",
:large => "--json",
:description => "Show the resource in xml format"
}
SPLIT={
:name => "split",
:large => "--split",
:description => "Split the output in a table for each VM"
}
ACCT_OPTIONS = [START_TIME, END_TIME, USER, GROUP, HOST, XPATH, XML, JSON, SPLIT]
ACCT_TABLE = CLIHelper::ShowTable.new("oneacct.yaml", nil) do
column :VID, "Virtual Machine ID", :size=>4 do |d|
d["OID"]
end
column :SEQ, "History record sequence number", :size=>3 do |d|
d["SEQ"]
end
column :HOSTNAME, "Host name", :left, :size=>15 do |d|
d["HOSTNAME"]
end
column :REASON, "VM state change reason", :left, :size=>4 do |d|
VirtualMachine.get_reason d["REASON"]
end
column :START_TIME, "Start time", :size=>14 do |d|
OpenNebulaHelper.time_to_str(d['STIME'])
end
column :END_TIME, "End time", :size=>14 do |d|
OpenNebulaHelper.time_to_str(d['ETIME'])
end
column :MEMORY, "Assigned memory", :size=>6 do |d|
OpenNebulaHelper.unit_to_str(d["VM"]["TEMPLATE"]["MEMORY"].to_i, {}, 'M')
end
column :CPU, "Number of CPUs", :size=>3 do |d|
d["VM"]["TEMPLATE"]["CPU"]
end
column :NET_RX, "Data received from the network", :size=>6 do |d|
# NET is measured in bytes, unit_to_str expects KBytes
OpenNebulaHelper.unit_to_str(d["VM"]["NET_RX"].to_i / 1024.0, {})
end
column :NET_TX, "Data sent to the network", :size=>6 do |d|
# NET is measured in bytes, unit_to_str expects KBytes
OpenNebulaHelper.unit_to_str(d["VM"]["NET_TX"].to_i / 1024.0, {})
end
default :VID, :HOSTNAME, :REASON, :START_TIME, :END_TIME, :MEMORY, :CPU, :NET_RX, :NET_TX
end
def self.print_start_enc_time_header(start_time, end_time)
print "Showing active history records from "
CLIHelper.scr_bold
if ( start_time != -1 )
print Time.at(start_time).to_s
else
print "-"
end
CLIHelper.scr_restore
print " to "
CLIHelper.scr_bold
if ( end_time != -1 )
print Time.at(end_time).to_s
else
print "-"
end
CLIHelper.scr_restore
puts
puts
end
def self.print_user_header(user_id)
CLIHelper.scr_bold
CLIHelper.scr_underline
puts "# User #{user_id}".ljust(80)
CLIHelper.scr_restore
puts
end
end

View File

@ -25,18 +25,10 @@ class OneClusterHelper < OpenNebulaHelper::OneHelper
:description => "Selects the cluster",
:format => String,
:proc => lambda { |o, options|
ch = OneClusterHelper.new
rc, cid = ch.to_id(o)
if rc == 0
options[:cluster] = cid
else
puts cid
puts "option cluster: Parsing error"
exit -1
end
OpenNebulaHelper.rname_to_id(o, "CLUSTER")
}
}
def self.rname
"CLUSTER"
end

View File

@ -24,15 +24,7 @@ class OneDatastoreHelper < OpenNebulaHelper::OneHelper
:description => "Selects the datastore",
:format => String,
:proc => lambda { |o, options|
ch = OneDatastoreHelper.new
rc, dsid = ch.to_id(o)
if rc == 0
options[:datastore] = dsid
else
puts dsid
puts "option datastore: Parsing error"
exit -1
end
OpenNebulaHelper.rname_to_id(o, "DATASTORE")
}
}

View File

@ -32,14 +32,7 @@ class OneVMHelper < OpenNebulaHelper::OneHelper
:description => "Selects the image",
:format => String,
:proc => lambda { |o, options|
rc, imid = OpenNebulaHelper.rname_to_id(o, "IMAGE")
if rc == 0
options[:image] = imid
else
puts imid
puts "option image: Parsing error"
exit -1
end
OpenNebulaHelper.rname_to_id(o, "IMAGE")
}
}
@ -188,13 +181,13 @@ class OneVMHelper < OpenNebulaHelper::OneHelper
"USED CPU" => "CPU",
"NET_TX" => "NET_TX",
"NET_RX" => "NET_RX"
}
}
poll_attrs.each { |k,v|
poll_attrs.each { |k,v|
if k == "USED CPU"
puts str % [k,vm[v]]
puts str % [k,vm[v]]
elsif k == "USED MEMORY"
puts str % [k, OpenNebulaHelper.unit_to_str(vm[v].to_i, {})]
puts str % [k, OpenNebulaHelper.unit_to_str(vm[v].to_i, {})]
else
puts str % [k, OpenNebulaHelper.unit_to_str(vm[v].to_i/1024, {})]
end

View File

@ -16,7 +16,6 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
ONE_LOCATION=ENV["ONE_LOCATION"]
if !ONE_LOCATION
@ -28,356 +27,87 @@ end
$: << RUBY_LIB_LOCATION
$: << RUBY_LIB_LOCATION+"/cli"
require 'rubygems'
require 'command_parser'
require 'one_helper/oneacct_helper'
require 'cli/one_helper'
require 'cli/command_parser'
require 'json'
require 'optparse'
require 'optparse/time'
cmd = CommandParser::CmdParser.new(ARGV) do
usage "`oneacct` [<options>]"
description ""
version OpenNebulaHelper::ONE_VERSION
################################################################################
# CLI Helper
################################################################################
option [AcctHelper::ACCT_OPTIONS + CommandParser::OPTIONS]
class AcctHelper < OpenNebulaHelper::OneHelper
main do
filter_flag = (options[:user] || VirtualMachinePool::INFO_ALL)
start_time = options[:start] ? options[:start].to_i : -1
end_time = options[:end] ? options[:end].to_i : -1
def self.conf_file
"oneacct.yaml"
end
common_opts = {
:start_time => start_time,
:end_time => end_time,
:host => options[:host],
:group => options[:group],
:xpath => options[:xpath]
}
=begin
List of <HISTORY> child elements
client = OpenNebula::Client.new
pool = OpenNebula::VirtualMachinePool.new(client)
OID
SEQ
HOSTNAME
HID
STIME
ETIME
VMMMAD
VNMMAD
PSTIME
PETIME
RSTIME
RETIME
ESTIME
EETIME
REASON
=end
def list_history(data)
table = CLIHelper::ShowTable.new(self.class.table_conf, self) do
column :VID, "Virtual Machine ID", :size=>4 do |d|
d["OID"]
if options[:json] || options[:xml]
xml_str = pool.accounting_xml(filter_flag, common_opts)
if OpenNebula.is_error?(xml_str)
puts acct_hash.message
exit -1
end
column :SEQ, "History record sequence number", :size=>3 do |d|
d["SEQ"]
if options[:json]
xmldoc = XMLElement.new
xmldoc.initialize_xml(xml_str, 'HISTORY_RECORDS')
puts JSON.pretty_generate(xmldoc.to_hash)
elsif options[:xml]
puts xml_str
end
column :HOSTNAME, "Host name", :left, :size=>15 do |d|
d["HOSTNAME"]
exit_code 0
else
order_by = Hash.new
order_by[:order_by_1] = 'VM/UID'
order_by[:order_by_2] = 'VM/ID' if options[:split]
acct_hash = pool.accounting(filter_flag, common_opts.merge(order_by))
if OpenNebula.is_error?(acct_hash)
puts acct_hash.message
exit -1
end
column :REASON, "VM state change reason", :left, :size=>4 do |d|
VirtualMachine.get_reason d["REASON"]
if ( start_time != -1 or end_time != -1 )
AcctHelper.print_start_end_time_header(start_time, end_time)
end
column :START_TIME, "Start time", :size=>14 do |d|
OpenNebulaHelper.time_to_str(d['STIME'])
end
acct_hash.each { |user_id, value|
AcctHelper.print_user_header(user_id)
column :END_TIME, "End time", :size=>14 do |d|
OpenNebulaHelper.time_to_str(d['ETIME'])
end
column :MEMORY, "Assigned memory", :size=>6 do |d|
OpenNebulaHelper.unit_to_str(d["VM/TEMPLATE/MEMORY"].to_i, {}, 'M')
end
column :CPU, "Number of CPUs", :size=>3 do |d|
d["VM/TEMPLATE/CPU"]
end
column :NET_RX, "Data received from the network", :size=>6 do |d|
# NET is measured in bytes, unit_to_str expects KBytes
OpenNebulaHelper.unit_to_str(d["VM/NET_RX"].to_i / 1024.0, {})
end
column :NET_TX, "Data sent to the network", :size=>6 do |d|
# NET is measured in bytes, unit_to_str expects KBytes
OpenNebulaHelper.unit_to_str(d["VM/NET_TX"].to_i / 1024.0, {})
end
default :VID, :HOSTNAME, :REASON, :START_TIME, :END_TIME, :MEMORY, :CPU, :NET_RX, :NET_TX
end
table.show(data)
end
public
def list_users(xmldoc, options=nil)
uids = xmldoc.retrieve_elements('HISTORY/VM/UID')
if uids.nil?
puts "No records found."
exit 0
end
uids.uniq!
history_elems = []
uids.each do |uid|
CLIHelper.scr_bold
CLIHelper.scr_underline
username_elems =
xmldoc.retrieve_elements("HISTORY/VM[UID=#{uid}]/UNAME")
username = username_elems.nil? ? "" : username_elems.uniq
puts "# User #{uid} #{username}".ljust(80)
CLIHelper.scr_restore
puts
history_elems.clear
vm_ids = xmldoc.retrieve_elements("HISTORY/VM[UID=#{uid}]/ID")
vm_ids = [] if vm_ids.nil?
vm_ids.uniq!
vm_ids.each do |vid|
if ( options[:split] )
history_elems.clear
end
xmldoc.each("HISTORY[OID=#{vid}]") do |history|
history_elems << history
end
if ( options[:split] )
list_history(history_elems)
if options[:split]
# Use one table for each VM
value.each { |vm_id, history_array|
array = history_array['HISTORY_RECORDS']['HISTORY']
AcctHelper::ACCT_TABLE.show(array)
puts
}
else
# Use the same table for all the VMs
array = value['HISTORY_RECORDS']['HISTORY']
AcctHelper::ACCT_TABLE.show(array)
puts
end
end
}
if ( !options[:split] )
list_history(history_elems)
puts
end
exit_code 0
end
end
end
################################################################################
# Helper methods
################################################################################
def redo_xmldoc(xmldoc, xpath_str)
xml_str = "<HISTORY_RECORDS>"
xmldoc.each(xpath_str) do |history|
xml_str << history.to_xml
end
xml_str << "</HISTORY_RECORDS>"
xmldoc = XMLElement.new
xmldoc.initialize_xml(xml_str, 'HISTORY_RECORDS')
return xmldoc
end
################################################################################
# Main command
################################################################################
options = Hash.new
options[:format] = :table
opts = OptionParser.new do |opts|
opts.on('-s', '--start TIME', Time,
'Start date and time to take into account') do |ext|
options[:start]=ext
end
opts.on("-e", "--end TIME", Time,
"End date and time" ) do |ext|
options[:end]=ext
end
opts.on("-u", "--user user", String,
"User id to filter the results" ) do |ext|
options[:user]=ext
end
opts.on("-g", "--group group", String,
"Group id to filter the results" ) do |ext|
options[:group]=ext
end
opts.on("-H", "--host hostname", String,
"Host id to filter the results" ) do |ext|
options[:host]=ext
end
opts.on("--xpath expression", String,
"Xpath expression to filter the results. For example: oneacct --xpath 'HISTORY[ETIME>0]'" ) do |ext|
options[:xpath]=ext
end
opts.on("-j", "--json",
"Output in json format" ) do |ext|
options[:format]=:json
end
opts.on("-x", "--xml",
"Output in xml format" ) do |ext|
options[:format]=:xml
end
opts.on("--split",
"Split the output in a table for each VM" ) do |ext|
options[:split]=ext
end
opts.on("-h", "--help", "Show this message" ) do
puts opts
exit
end
opts.on()
end
begin
opts.parse!(ARGV)
rescue OptionParser::ParseError => e
STDERR.puts "Error: " << e.message
exit(-1)
end
client = OpenNebula::Client.new
acct_helper = AcctHelper.new()
time_start = -1
time_end = -1
filter_flag = VirtualMachinePool::INFO_ALL
time_start = options[:start].to_i if options[:start]
time_end = options[:end].to_i if options[:end]
if options[:user]
rc = OpenNebulaHelper.rname_to_id(options[:user], "USER")
if rc[0] == 0
filter_flag = rc[1]
else
puts rc[1]
exit -1
end
end
xml_str = client.call("vmpool.accounting",
filter_flag,
time_start,
time_end)
if OpenNebula.is_error?(xml_str)
puts xml_str.message
exit -1
end
xmldoc = XMLElement.new
xmldoc.initialize_xml(xml_str, 'HISTORY_RECORDS')
xpath = nil
hid = nil
gid = nil
if options[:host]
rc = OpenNebulaHelper.rname_to_id(options[:host], "HOST")
if rc[0] == 0
hid = rc[1]
else
puts rc[1]
exit -1
end
end
if options[:group]
rc = OpenNebulaHelper.rname_to_id(options[:group], "GROUP")
if rc[0] == 0
gid = rc[1]
else
puts rc[1]
exit -1
end
end
if options[:host] && options[:group]
xpath = "HISTORY[VM/GID=#{gid} and HID=#{hid}]"
elsif options[:host]
xpath = "HISTORY[HID=#{hid}]"
elsif options[:group]
xpath = "HISTORY[VM/GID=#{gid}]"
end
xmldoc = redo_xmldoc(xmldoc, xpath) if !xpath.nil?
if options[:xpath]
xmldoc = redo_xmldoc(xmldoc, options[:xpath])
end
case options[:format]
when :table
if ( time_start != -1 or time_end != -1 )
print "Showing active history records from "
CLIHelper.scr_bold
if ( time_start != -1 )
print Time.at(time_start).to_s
else
print "-"
end
CLIHelper.scr_restore
print " to "
CLIHelper.scr_bold
if ( time_end != -1 )
print Time.at(time_end).to_s
else
print "-"
end
CLIHelper.scr_restore
puts
puts
end
acct_helper.list_users(xmldoc, options)
when :xml
puts xmldoc.to_xml
when :json
puts xmldoc.to_hash.to_json
end
end

View File

@ -570,4 +570,59 @@ class OCCIServer < CloudServer
return vnc.proxy(vm)
end
##########################################################################
# Accounting
##########################################################################
def get_user_accounting(options)
tstart = options[:start].to_i
tend = options[:end].to_i
interval = options[:interval].to_i
meters = options[:monitor_resources]
acct_options = {:start_time => tstart,
:end_time => tend}
info_flag = Pool::INFO_ALL
result = {}
meters_a = meters.split(',')
meters_a.each do | meter |
result[meter] = []
end
pool = VirtualMachinePool.new(@client)
acct_xml = pool.accounting_xml(info_flag, acct_options)
if OpenNebula.is_error?(acct_xml)
error = Error.new(acct_xml.message)
return [500, error.to_json]
end
xml = XMLElement.new
xml.initialize_xml(acct_xml, 'HISTORY_RECORDS')
while tstart < tend
tstep = tstart + interval
count = Hash.new
xml.each("HISTORY[STIME<=#{tstep} and ETIME=0 or STIME<=#{tstep} and ETIME>=#{tstart}]") do |hr|
meters_a.each do | meter |
count[meter] ||= 0
count[meter] += hr["VM/#{meter}"].to_i if hr["VM/#{meter}"]
end
end
count.each do | mname, mcount |
result[mname] << [tstart, mcount]
end
tstart = tstep
end
return [200, {:monitoring => result}.to_json]
end
end

View File

@ -443,4 +443,8 @@ post '/ui/startvnc/:id' do
@occi_server.startvnc(vm_id, settings.vnc)
end
get '/ui/accounting' do
@occi_server.get_user_accounting(params)
end
Sinatra::Application.run!

View File

@ -133,7 +133,7 @@ $(document).ready(function () {
applyDefaultStyles: false
, center__paneSelector: ".outer-center"
, west__paneSelector: ".outer-west"
, west__size: 181
, west__size: 200
, north__size: 26
, south__size: 26
, spacing_open: 0 // ALL panes

View File

@ -255,22 +255,18 @@ var OCCI = {
callback_error(request, OCCI.Error(response)) : null;
}
});
}
/*
"monitor": function(params,resource,all){
},
"accounting": function(params, resource){
var callback = params.success;
var callback_error = params.error;
var data = params.data;
var method = "monitor";
var action = OpenNebula.Helper.action(method);
var request = OpenNebula.Helper.request(resource,method, data);
var url = resource.toLowerCase();
url = all ? url + "/monitor" : url + "/" + params.data.id + "/monitor";
var method = "accounting";
var request = OCCI.Helper.request(resource, method, data);
$.ajax({
url: url,
url: 'ui/accounting',
type: "GET",
data: data['monitor'],
dataType: "json",
@ -279,11 +275,10 @@ var OCCI = {
},
error: function(response){
return callback_error ?
callback_error(request, OpenNebula.Error(response)) : null;
callback_error(request, OCCI.Error(response)) : null;
}
});
}
*/
},
"Auth": {
@ -600,7 +595,9 @@ var OCCI = {
},
"show" : function(params){
OCCI.Action.show(params,OCCI.User.resource);
},
"accounting" : function(params){
OCCI.Action.accounting(params, OCCI.User.resource);
}
}
}

View File

@ -14,6 +14,21 @@
/* limitations under the License. */
/* -------------------------------------------------------------------------- */
var user_acct_graphs = [
{ title : tr("CPU"),
monitor_resources : "CPU",
humanize_figures : false
},
{ title : tr("Memory"),
monitor_resources : "MEMORY",
humanize_figures : true
},
{ title : tr("Net transfer rates"),
monitor_resources : "NETTX,NETRX",
humanize_figures : true
}
];
var dashboard_tab_content =
'<table id="dashboard_table">\
<tr>\
@ -47,6 +62,50 @@ var dashboard_tab_content =
<tr id="network_quotas"><td class="key_td">'+tr("Networks")+':</td>\
<td class="value_td">'+$network_count+'</td></tr>\
</table>\
\
</div>\
</div>\
</td>\
</tr>\
<tr>\
<td>\
<div class="panel" id="user_acct_panel">\
<h3>' + tr("Usages") + '<i class="icon-refresh user_acct_date_ok" style="float:right;cursor:pointer"></i></h3>\
<div class="panel_info">\
\
<div style="margin-left:20px;text-align:center;">\
'+tr("From")+':&nbsp;<input type="text" style="font-size:12px;width: 80px;" id="user_acct_from" name="from"/>&nbsp;&nbsp;\
'+tr("To")+':&nbsp;<input type="text" style="font-size:12px;width: 80px;" id="user_acct_to" name="to"/>\
&nbsp;&nbsp;<a href="#" class="user_acct_date_ok">'+tr('Go!')+' <i class="icon-ok"></i></a>\
</div>\
\
<table class="info_table">\
<tr><td id="legend_CPU"></td></tr>\
<tr><td style="border:0;width:800px!important;">\
<div id="user_acct_CPU" style="height:100px;position:relative;left:0px;overflow: hidden;">'+
spinner+
'</div>\
</td>\
</tr>\
</table>\
<table class="info_table">\
<tr><td id="legend_MEMORY"></td></tr>\
<tr><td style="border:0;width:800px!important;">\
<div id="user_acct_MEMORY" style="height:100px;position:relative;left:0px;overflow: hidden;">'+
spinner+
'</div>\
</td>\
</tr>\
</table>\
<table class="info_table">\
<tr><td id="legend_NETTX_NETRX"></td></tr>\
<tr><td style="border:0;width:800px!important;">\
<div id="user_acct_NETTX_NETRX" style="height:100px;position:relative;left:0px;overflow: hidden;">'+
spinner+
'</div>\
</td>\
</tr>\
</table>\
\
</div>\
</div>\
@ -136,15 +195,18 @@ Sunstone.addAction('User.refresh', {
}
});
Sunstone.addAction('User.accounting', {
type: "monitor",
call: OCCI.User.accounting,
callback: function(req, response){
var info = req.request.data[0].monitor;
plot_graph(response, '#user_acct_panel', 'user_acct_', info);
},
error: onError
});
Sunstone.addMainTab('dashboard_tab',dashboard_tab);
function quickstart_setup(){
$('#dashboard_table #quickstart_form input',main_tabs_context).click(function(){
Sunstone.runAction($(this).val());
});
};
function generateDashboardLinks(){
var links="<ul>";
for (var i=0; i<dashboard_links.length;i++){
@ -282,7 +344,7 @@ function dashboardQuotasHTML(req, response){
//puts the dashboard values into "retrieving"
function emptyDashboard(){
$("#dashboard_tab .value_td span",main_tabs_context).html(spinner);
$("#dashboard_tab .value_td > span",main_tabs_context).html(spinner);
}
@ -304,6 +366,60 @@ function updateDashboard(what,json_info){
}
}
function accountingSetup(){
var context = $('#dashboard_table', main_tabs_context);
//Enable datepicker
$("#user_acct_from", context).datepicker({
defaultDate: "-1d",
changeMonth: true,
numberOfMonths: 1,
dateFormat: "dd/mm/yy",
defaultDate: '-1',
onSelect: function( selectedDate ) {
$( "#user_acct_to", context).datepicker("option",
"minDate",
selectedDate );
}
});
$("#user_acct_from", context).datepicker('setDate', '-1');
$("#user_acct_to", context).datepicker({
defaultDate: "0",
changeMonth: true,
numberOfMonths: 1,
dateFormat: "dd/mm/yy",
maxDate: '+1',
onSelect: function( selectedDate ) {
$( "#user_acct_from", context).datepicker( "option",
"maxDate",
selectedDate );
}
});
$("#user_acct_to", context).datepicker('setDate', 'Now');
//Listen to set date button
$('.user_acct_date_ok', context).click(function(){
var from = $("#user_acct_from", context).val();
var to = $("#user_acct_to", context).val();
var start = $.datepicker.parseDate('dd/mm/yy', from)
if (start){
start = start.getTime();
start = Math.floor(start / 1000);
}
var end = $.datepicker.parseDate('dd/mm/yy', to);
if (end){
end = end.getTime();
end = Math.floor(end / 1000);
}
loadAccounting('User', null, user_acct_graphs,
{ start : start, end: end });
return false;
});
}
$(document).ready(function(){
//Dashboard link listener
@ -332,7 +448,7 @@ $(document).ready(function(){
emptyDashboard();
quickstart_setup();
accountingSetup();
$('#li_dashboard_tab').click(function(){
hideDialog();
@ -347,4 +463,5 @@ $(document).ready(function(){
}
});
Sunstone.runAction("User.show", uid);
loadAccounting('User', null, user_acct_graphs);
});

View File

@ -18,7 +18,12 @@
<script type="text/javascript" src="vendor/jQueryLayout/jquery.layout-latest.min.js"></script>
<script type="text/javascript" src="vendor/dataTables/jquery.dataTables.min.js"></script>
<script type="text/javascript" src="vendor/fileuploader/fileuploader.js"></script>
<script type="text/javascript" src="vendor/flot/jquery.flot.min.js"></script>
<!-- <script type="text/javascript" src="vendor/flot/jquery.flot.pie.min.js"></script> -->
<script type="text/javascript" src="vendor/flot/jquery.flot.resize.min.js"></script>
<!--[if lte IE 8]>
<script type="text/javascript" src="vendor/explorercanvas/excanvas.compiled.js"></script>
<![endif]-->
<!-- End Vendor Libraries -->
<!--Languages-->

View File

@ -26,7 +26,8 @@ module OpenNebula
VM_POOL_METHODS = {
:info => "vmpool.info",
:monitoring => "vmpool.monitoring"
:monitoring => "vmpool.monitoring",
:accounting => "vmpool.accounting"
}
# Constants for info queries (include/RequestManagerPoolInfoFilter.h)
@ -56,7 +57,7 @@ module OpenNebula
#######################################################################
# Retrieves all or part of the VirtualMachines in the pool.
# No arguments, returns the not-in-done VMs for the user
# No arguments, returns the not-in-done VMs for the user
# [user_id, start_id, end_id]
# [user_id, start_id, end_id, state]
def info(*args)
@ -133,7 +134,7 @@ module OpenNebula
# [["1337608271", "510"], ["1337608301", "510"], ["1337608331", "520"]],
# "TEMPLATE/CUSTOM_PROBE"=>
# []},
#
#
# "0"=>
# {"CPU"=>
# [["1337608271", "0"], ["1337608301", "0"], ["1337608331", "0"]],
@ -156,8 +157,160 @@ module OpenNebula
return @client.call(VM_POOL_METHODS[:monitoring], filter_flag)
end
# Retrieves the accounting data for all the VMs in the pool
#
# @param [Integer] filter_flag Optional filter flag to retrieve all or
# part of the Pool. Possible values: INFO_ALL, INFO_GROUP, INFO_MINE
# or user_id
# @param [Hash] options
# @option params [Integer] :start_time Start date and time to take into account,
# if no start_time is required use -1
# @option params [Integer] :end_time End date and time to take into account,
# if no end_time is required use -1
# @option params [Integer] :host Host id to filter the results
# @option params [Integer] :group Group id to filter the results
# @option params [String] :xpath Xpath expression to filter the results.
# For example: HISTORY[ETIME>0]
# @option params [String] :order_by_1 Xpath expression to group the
# @option params [String] :order_by_2 Xpath expression to group the
# returned hash. This will be the second level of the hash
#
# @return [Hash, OpenNebula::Error]
# The first level hash uses the :order_by_1 values as keys, and
# as value a Hash with the :order_by_2 values and the HISTORY_RECORDS
#
# @example
# {"HISTORY_RECORDS"=>
# {"HISTORY"=> [
# {"OID"=>"0",
# "SEQ"=>"0",
# "HOSTNAME"=>"dummy",
# ...
# },
# {"OID"=>"0",
# "SEQ"=>"0",
# "HOSTNAME"=>"dummy",
#
# @example :order_by_1 => VM/UID
# {"0"=>
# {"HISTORY_RECORDS"=>
# {"HISTORY"=> [
# {"OID"=>"0",
# "SEQ"=>"0",
# "HOSTNAME"=>"dummy",
# ...
# },
# {"OID"=>"0",
# "SEQ"=>"0",
# "HOSTNAME"=>"dummy",
#
# @example :order_by_1 => VM/UID, :order_by_2 => VM/ID
# {"0"=>
# {"0"=>
# {"HISTORY_RECORDS"=>
# {"HISTORY"=> [
# {"OID"=>"0",
# "SEQ"=>"0",
# "HOSTNAME"=>"dummy",
# ...
# },
# {"OID"=>"0",
# "SEQ"=>"0",
# "HOSTNAME"=>"dummy",
#
def accounting(filter_flag=INFO_ALL, options={})
acct_hash = Hash.new
rc = build_accounting(filter_flag, options) do |history|
hash = acct_hash
if options[:order_by_1]
id_1 = history[options[:order_by_1]]
acct_hash[id_1] ||= Hash.new
if options[:order_by_2]
id_2 = history[options[:order_by_2]]
acct_hash[id_1][id_2] ||= Hash.new
hash = acct_hash[id_1][id_2]
else
hash = acct_hash[id_1]
end
end
hash["HISTORY_RECORDS"] ||= Hash.new
hash["HISTORY_RECORDS"]["HISTORY"] ||= Array.new
hash["HISTORY_RECORDS"]["HISTORY"] << history.to_hash['HISTORY']
end
return rc if OpenNebula.is_error?(rc)
acct_hash
end
# Retrieves the accounting data for all the VMs in the pool in xml
#
# @param [Integer] filter_flag Optional filter flag to retrieve all or
# part of the Pool. Possible values: INFO_ALL, INFO_GROUP, INFO_MINE
# or user_id
# @param [Hash] options
# @option params [Integer] :start_time Start date and time to take into account,
# if no start_time is required use -1
# @option params [Integer] :end_time End date and time to take into account,
# if no end_time is required use -1
# @option params [Integer] :host Host id to filter the results
# @option params [Integer] :group Group id to filter the results
# @option params [String] :xpath Xpath expression to filter the results.
# For example: HISTORY[ETIME>0]
#
# @return [String] the xml representing the accounting data
def accounting_xml(filter_flag=INFO_ALL, options={})
acct_hash = Hash.new
xml_str = "<HISTORY_RECORDS>\n"
rc = build_accounting(filter_flag, options) do |history|
xml_str << history.to_xml
end
return rc if OpenNebula.is_error?(rc)
xml_str << "\n</HISTORY_RECORDS>"
xml_str
end
private
def build_accounting(filter_flag, options, &block)
xml_str = @client.call(VM_POOL_METHODS[:accounting],
filter_flag,
options[:start_time],
options[:end_time])
return xml_str if OpenNebula.is_error?(xml_str)
xmldoc = XMLElement.new
xmldoc.initialize_xml(xml_str, 'HISTORY_RECORDS')
xpath_array = Array.new
xpath_array << "HISTORY[HID=#{options[:host]}]" if options[:host]
xpath_array << "HISTORY[VM/GID=#{options[:group]}]" if options[:group]
xpath_array << options[:xpath] if options[:xpath]
if xpath_array.empty?
xpath_str = "HISTORY"
else
xpath_str = xpath_array.join(' | ')
end
acct_hash = Hash.new
xmldoc.each(xpath_str) do |history|
block.call(history)
end
acct_hash
end
def info_filter(xml_method, who, start_id, end_id, state)
return xmlrpc_info(xml_method, who, start_id, end_id, state)
end

View File

@ -335,12 +335,33 @@ module OpenNebula
return str
end
#
#
#
def to_hash(hash={}, element=nil)
element ||= @xml.document.root
# @return [Hash] a hash representing the resource
def to_hash
hash = {}
if NOKOGIRI
if @xml.instance_of?(Nokogiri::XML::NodeSet)
@xml.each { |c|
if c.element?
build_hash(hash, c)
end
}
else
build_hash(hash, @xml)
end
else
build_hash(hash, @xml)
end
hash
end
private
#
#
#
def build_hash(hash, element)
if NOKOGIRI
array = element.children
if array.length==1 and (array.first.text? or array.first.cdata?)
@ -349,14 +370,14 @@ module OpenNebula
r = {}
array.each { |c|
if c.element?
to_hash(r, c)
build_hash(r, c)
end
}
end
else
r = {}
if element.has_elements?
element.each_element { |c| to_hash(r, c) }
element.each_element { |c| build_hash(r, c) }
elsif element.has_text?
r = element.text
end
@ -376,8 +397,6 @@ module OpenNebula
hash
end
private
#
#
#

View File

@ -238,7 +238,7 @@ class SunstoneServer < CloudServer
end
########################################################################
#
# Accounting & Monitoring
########################################################################
def get_pool_monitoring(resource, meters)
#pool_element
@ -274,7 +274,7 @@ class SunstoneServer < CloudServer
Host.new_with_id(id, @client)
else
error = Error.new("Monitoring not supported for #{resource}")
return [200, error.to_json]
return [403, error.to_json]
end
meters_a = meters.split(',')
@ -294,6 +294,76 @@ class SunstoneServer < CloudServer
return [200, meters_h.to_json]
end
# returns a { monitoring : meter1 : [[ts1, agg_value],[ts2, agg_value]...]
# meter2 : [[ts1, agg_value],[ts2, agg_value]...]}
# with this information we can paint historical graphs of usage
def get_user_accounting(options)
uid = options[:id].to_i
tstart = options[:start].to_i
tend = options[:end].to_i
interval = options[:interval].to_i
meters = options[:monitor_resources]
gid = options[:gid].to_i
acct_options = {:start_time => tstart,
:end_time => tend}
# If we want acct per group, we ask for all VMs visible to user
# and then filter by group.
if gid
uid = Pool::INFO_ALL
acct_options[:group] = gid
end
# Init results and request accounting
result = {}
meters_a = meters.split(',')
meters_a.each do | meter |
result[meter] = []
end
pool = VirtualMachinePool.new(@client)
acct_xml = pool.accounting_xml(uid, acct_options)
if OpenNebula.is_error?(acct_xml)
error = Error.new(acct_xml.message)
return [500, error.to_json]
end
xml = XMLElement.new
xml.initialize_xml(acct_xml, 'HISTORY_RECORDS')
# We aggregate the accounting values for each interval withing
# the given timeframe
while tstart < tend
tstep = tstart + interval
count = Hash.new
# We count machines which have started before the end of
# this interval AND have not finished yet OR machines which
# have started before the end of this interval AND
# have finished anytime after the start of this interval
xml.each("HISTORY[STIME<=#{tstep} and ETIME=0 or STIME<=#{tstep} and ETIME>=#{tstart}]") do |hr|
meters_a.each do | meter |
count[meter] ||= 0
count[meter] += hr["VM/#{meter}"].to_i if hr["VM/#{meter}"]
end
end
# We have aggregated values for this interval
# Then we just add them to the results along with a timestamp
count.each do | mname, mcount |
result[mname] << [tstart, mcount]
end
tstart = tstep
end
return [200, {:monitoring => result}.to_json]
end
private
############################################################################

View File

@ -348,7 +348,6 @@ var OpenNebula = {
var data = params.data;
var method = "monitor";
var action = OpenNebula.Helper.action(method);
var request = OpenNebula.Helper.request(resource,method, data);
var url = resource.toLowerCase();
@ -711,6 +710,9 @@ var OpenNebula = {
},
"show" : function(params){
OpenNebula.Action.show(params,OpenNebula.Group.resource);
},
"accounting" : function(params){
OpenNebula.Action.monitor(params,OpenNebula.Group.resource,false);
}
},
@ -754,6 +756,9 @@ var OpenNebula = {
"fetch_template" : function(params){
OpenNebula.Action.show(params,OpenNebula.User.resource,"template");
},
"accounting" : function(params){
OpenNebula.Action.monitor(params,OpenNebula.User.resource,false);
},
"set_quota" : function(params){
var action_obj = { quotas : params.data.extra_param };
OpenNebula.Action.simple_action(params,OpenNebula.User.resource,"set_quota",action_obj);

View File

@ -19,6 +19,25 @@ var dataTable_groups;
var $create_group_dialog;
var $group_quotas_dialog;
var group_acct_graphs = [
{ title : tr("CPU"),
monitor_resources : "CPU",
humanize_figures : false
},
{ title : tr("Memory"),
monitor_resources : "MEMORY",
humanize_figures : true
},
{ title : tr("Net TX"),
monitor_resources : "NETTX",
humanize_figures : true
},
{ title : tr("Net RX"),
monitor_resources : "NETRX",
humanize_figures : true
}
];
var groups_tab_content = '\
<h2><i class="icon-group"></i> '+tr("Groups")+'</h2>\
<form id="group_form" action="" action="javascript:alert(\'js error!\');">\
@ -217,6 +236,17 @@ var group_actions = {
},
error: onError
},
"Group.accounting" : {
type: "monitor",
call: OpenNebula.Group.accounting,
callback: function(req,response) {
var info = req.request.data[0].monitor;
plot_graph(response,'#group_acct_tab','group_acct_', info);
},
error: onError
},
"Group.help" : {
type: "custom",
call: function() {
@ -265,6 +295,10 @@ var group_info_panel = {
"group_info_tab" : {
title: tr("Group information"),
content:""
},
"group_acct_tab" : {
title: tr("Historical usages"),
content: ""
}
};
@ -433,8 +467,92 @@ function updateGroupInfo(request,group){
content : info_tab_html
};
var acct_tab = {
title : tr("Historical usages"),
content : '<div><table class="info_table" style="margin-bottom:0">\
<tr>\
<td class="key_td"><label for="from">'+tr('From / to')+'</label></td>\
<td class="value_td">\
<input type="text" id="group_acct_from" name="from"/>\
<input type="text" id="group_acct_to" name="to"/>\
<button id="group_acct_date_ok"><i class="icon-ok"></i></button>\
</td>\
</tr>\
<!--\
<tr>\
<td class="key_td"><label for="from">'+tr('Meters')+'</label></td>\
<td class="value_td">\
<select style="width:173px" id="group_acct_meter1" name="meter1">\
</select>\
<select style="width:173px" id="group_acct_meter2" name="meter2">\
</select>\
</td>\
</tr>\
-->\
</table></div>' + generateMonitoringDivs(group_acct_graphs, "group_acct_")
};
Sunstone.updateInfoPanelTab("group_info_panel","group_info_tab",info_tab);
Sunstone.updateInfoPanelTab("group_info_panel","group_acct_tab",acct_tab);
Sunstone.popUpInfoPanel("group_info_panel");
//Enable datepicker
var info_dialog = $('div#group_acct_tab');
$("#group_acct_from", info_dialog).datepicker({
defaultDate: "-1d",
changeMonth: true,
numberOfMonths: 1,
dateFormat: "dd/mm/yy",
defaultDate: '-1',
onSelect: function( selectedDate ) {
$( "#group_acct_to", info_dialog).datepicker("option",
"minDate",
selectedDate );
}
});
$("#group_acct_from", info_dialog).datepicker('setDate', '-1');
$("#group_acct_to", info_dialog).datepicker({
defaultDate: "0",
changeMonth: true,
numberOfMonths: 1,
dateFormat: "dd/mm/yy",
maxDate: '+1',
onSelect: function( selectedDate ) {
$( "#group_acct_from", info_dialog).datepicker( "option",
"maxDate",
selectedDate );
}
});
$("#group_acct_to", info_dialog).datepicker('setDate', 'Now');
//Listen to set date button
$('button#group_acct_date_ok', info_dialog).click(function(){
var from = $("#group_acct_from", info_dialog).val();
var to = $("#group_acct_to", info_dialog).val();
var start = $.datepicker.parseDate('dd/mm/yy', from)
if (start){
start = start.getTime();
start = Math.floor(start / 1000);
}
var end = $.datepicker.parseDate('dd/mm/yy', to);
if (end){
end = end.getTime();
end = Math.floor(end / 1000);
}
loadAccounting('Group', info.ID, group_acct_graphs,
{ start : start, end: end });
return false;
});
//preload acct
loadAccounting('Group', info.ID, group_acct_graphs);
}

View File

@ -21,6 +21,26 @@ var $create_user_dialog;
var $user_quotas_dialog;
var $update_pw_dialog;
var user_acct_graphs = [
{ title : tr("CPU"),
monitor_resources : "CPU",
humanize_figures : false
},
{ title : tr("Memory"),
monitor_resources : "MEMORY",
humanize_figures : true
},
{ title : tr("Net TX"),
monitor_resources : "NETTX",
humanize_figures : true
},
{ title : tr("Net RX"),
monitor_resources : "NETRX",
humanize_figures : true
}
];
var users_tab_content = '\
<h2><i class="icon-user"></i> '+tr("Users")+'</h2>\
<form id="user_form" action="" action="javascript:alert(\'js error!\');">\
@ -347,6 +367,16 @@ var user_actions = {
error: onError
},
"User.accounting" : {
type: "monitor",
call: OpenNebula.User.accounting,
callback: function(req,response) {
var info = req.request.data[0].monitor;
plot_graph(response,'#user_acct_tab','user_acct_', info);
},
error: onError
},
"User.help" : {
type: "custom",
call: function() {
@ -436,6 +466,10 @@ var user_info_panel = {
"user_quotas_tab" : {
title: tr("User quotas"),
content:""
},
"user_acct_tab" : {
title: tr("Historical usages"),
content: ""
}
};
@ -640,9 +674,90 @@ function updateUserInfo(request,user){
content : quotas_tab_html
};
var acct_tab = {
title : tr("Historical usages"),
content : '<div><table class="info_table" style="margin-bottom:0">\
<tr>\
<td class="key_td"><label for="from">'+tr('From / to')+'</label></td>\
<td class="value_td">\
<input type="text" id="user_acct_from" name="from"/>\
<input type="text" id="user_acct_to" name="to"/>\
<button id="user_acct_date_ok"><i class="icon-ok"></i></button>\
</td>\
</tr>\
<!--\
<tr>\
<td class="key_td"><label for="from">'+tr('Meters')+'</label></td>\
<td class="value_td">\
<select style="width:173px" id="user_acct_meter1" name="meter1">\
</select>\
<select style="width:173px" id="user_acct_meter2" name="meter2">\
</select>\
</td>\
</tr>\
-->\
</table></div>' + generateMonitoringDivs(user_acct_graphs, "user_acct_")
};
Sunstone.updateInfoPanelTab("user_info_panel","user_info_tab",info_tab);
Sunstone.updateInfoPanelTab("user_info_panel","user_quotas_tab",quotas_tab);
Sunstone.updateInfoPanelTab("user_info_panel","user_acct_tab",acct_tab);
Sunstone.popUpInfoPanel("user_info_panel");
//Enable datepicker
var info_dialog = $('div#user_acct_tab');
$("#user_acct_from", info_dialog).datepicker({
defaultDate: "-1d",
changeMonth: true,
numberOfMonths: 1,
dateFormat: "dd/mm/yy",
defaultDate: '-1',
onSelect: function( selectedDate ) {
$( "#user_acct_to", info_dialog).datepicker("option",
"minDate",
selectedDate );
}
});
$("#user_acct_from", info_dialog).datepicker('setDate', '-1');
$("#user_acct_to", info_dialog).datepicker({
defaultDate: "0",
changeMonth: true,
numberOfMonths: 1,
dateFormat: "dd/mm/yy",
maxDate: '+1',
onSelect: function( selectedDate ) {
$( "#user_acct_from", info_dialog).datepicker( "option",
"maxDate",
selectedDate );
}
});
$("#user_acct_to", info_dialog).datepicker('setDate', 'Now');
//Listen to set date button
$('button#user_acct_date_ok', info_dialog).click(function(){
var from = $("#user_acct_from", info_dialog).val();
var to = $("#user_acct_to", info_dialog).val();
var start = $.datepicker.parseDate('dd/mm/yy', from)
if (start){
start = start.getTime();
start = Math.floor(start / 1000);
}
var end = $.datepicker.parseDate('dd/mm/yy', to);
if (end){
end = end.getTime();
end = Math.floor(end / 1000);
}
loadAccounting('User', user_info.ID, user_acct_graphs,
{ start : start, end: end });
return false;
});
//preload acct
loadAccounting('User', user_info.ID, user_acct_graphs);
};
// Prepare the user creation dialog

View File

@ -633,6 +633,10 @@ var vm_info_panel = {
"vm_history_tab" : {
title: tr("History information"),
content: ""
},
"vm_monitoring_tab" : {
title: tr("Monitoring information"),
content: ""
}
};

View File

@ -47,7 +47,8 @@ function pretty_time(time_seconds)
}
// Format time for plot axis
function pretty_time_axis(time){
// If show date, only date information is shown
function pretty_time_axis(time, show_date){
var d = new Date();
d.setTime(time*1000);
@ -58,7 +59,10 @@ function pretty_time_axis(time){
var month = pad(d.getMonth()+1,2); //getMonths returns 0-11
var year = d.getFullYear();
return hour + ":" + mins + ":" + secs;// + "&nbsp;" + month + "/" + day;
if (show_date)
return month + "/" + day;
else
return hour + ":" + mins;
}
function pretty_time_runtime(time){
@ -617,25 +621,26 @@ function escapeDoubleQuotes(string){
//will be contained. They have some elements which ids are
//determined by the graphs configuration, so when the time
//of plotting comes, we can put the data in the right place.
function generateMonitoringDivs(graphs, id_prefix){
function generateMonitoringDivs(graphs, id_prefix, options){
var str = "";
//40% of the width of the screen minus
//181px (left menu size)
//200px (left menu size)
var width = ($(window).width()-200)*39/100;
var id_suffix="";
var label="";
var id="";
var omit_title = options && options.omit_title;
$.each(graphs,function(){
label = this.monitor_resources;
id_suffix=label.replace(/,/g,'_');
id_suffix=id_suffix.replace(/\//g,'_');
id = id_prefix+id_suffix;
str+='<table class="info_table">\
<thead><tr><th colspan="1">'+this.title+'</th></tr></thead>\
<tr><td id="legend_'+id_suffix+'"></td></tr>\
<tr><td style="border:0">\
<div id="'+id+'" style="width:'+width+'px; height:150px;margin-bottom:10px;position:relative;left:0px;">'+
str+='<table class="info_table">'+
(!omit_title ? '<thead><tr><th colspan="1">'+this.title+'</th></tr></thead>' : '')
+ '<tr><td id="legend_'+id_suffix+'"></td></tr>\
<tr><td style="border:0;width:100%;">\
<div id="'+id+'" style="width:'+width+'px; height:150px;position:relative;left:0px;margin: 0 auto 10px auto">'+
spinner+
'</div>\
</td></tr></table>';
@ -660,6 +665,7 @@ function plot_graph(data,context,id_prefix,info){
var series = [];
var serie;
var mon_count = 0;
var show_date = info.show_date;
//make sure series are painted in the order of the
//labels array.
@ -683,7 +689,7 @@ function plot_graph(data,context,id_prefix,info){
},
xaxis : {
tickFormatter: function(val,axis){
return pretty_time_axis(val);
return pretty_time_axis(val, show_date);
}
},
yaxis : { labelWidth: 40,
@ -1222,4 +1228,29 @@ function progressBar(value, opts){
</div>\
</div>\
</div>';
}
function loadAccounting(resource, id, graphs, options){
var secs_in_day = 3600 * 24;
var now = Math.floor(new Date().getTime() / 1000)
var start = options && options.start ? options.start : now - secs_in_day;
var end = options && options.end ? options.end : now;
var interval;
if (options && options.interval){
interval = options.interval;
} else {
//If we are asking more than one interval is one day, otherwise 1 hour
interval = (end - start) > secs_in_day ? secs_in_day : 3600;
}
for (var i = 0; i < graphs.length; i++){
var graph_cfg = graphs[i];
graph_cfg.start = start
graph_cfg.end = end
graph_cfg.interval = interval
// If the date range is longer than 24 hours, then show only
// date, otherwise show time in the x axis
graph_cfg.show_date = (end - start) > (3600 * 24)? true : false;
Sunstone.runAction(resource+".accounting", id, graph_cfg);
};
}

View File

@ -303,7 +303,7 @@ get '/vm/:id/log' do
end
##############################################################################
# Monitoring
# Accounting & Monitoring
##############################################################################
get '/:resource/monitor' do
@ -312,6 +312,15 @@ get '/:resource/monitor' do
params[:monitor_resources])
end
get '/user/:id/monitor' do
@SunstoneServer.get_user_accounting(params)
end
get '/group/:id/monitor' do
params[:gid] = params[:id]
@SunstoneServer.get_user_accounting(params)
end
get '/:resource/:id/monitor' do
@SunstoneServer.get_resource_monitoring(
params[:id],