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:
commit
ef8e73becc
@ -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 \
|
||||
|
@ -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
|
||||
|
||||
|
179
src/cli/one_helper/oneacct_helper.rb
Normal file
179
src/cli/one_helper/oneacct_helper.rb
Normal 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
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
390
src/cli/oneacct
390
src/cli/oneacct
@ -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
|
@ -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
|
||||
|
@ -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!
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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")+': <input type="text" style="font-size:12px;width: 80px;" id="user_acct_from" name="from"/> \
|
||||
'+tr("To")+': <input type="text" style="font-size:12px;width: 80px;" id="user_acct_to" name="to"/>\
|
||||
<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);
|
||||
});
|
@ -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-->
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
|
@ -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
|
||||
|
||||
############################################################################
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -633,6 +633,10 @@ var vm_info_panel = {
|
||||
"vm_history_tab" : {
|
||||
title: tr("History information"),
|
||||
content: ""
|
||||
},
|
||||
"vm_monitoring_tab" : {
|
||||
title: tr("Monitoring information"),
|
||||
content: ""
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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;// + " " + 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);
|
||||
};
|
||||
}
|
@ -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],
|
||||
|
Loading…
x
Reference in New Issue
Block a user