parent
d36d2cb890
commit
42e56c9246
@ -0,0 +1,563 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require 'rubygems'
|
||||
require 'uri'
|
||||
require 'net/https'
|
||||
require 'json'
|
||||
require 'pp'
|
||||
|
||||
###############################################################################
|
||||
# The CloudClient module contains general functionality to implement a
|
||||
# Cloud Client
|
||||
###############################################################################
|
||||
module CloudClient
|
||||
|
||||
# OpenNebula version
|
||||
VERSION = '4.8.0'
|
||||
|
||||
# #########################################################################
|
||||
# Default location for the authentication file
|
||||
# #########################################################################
|
||||
DEFAULT_AUTH_FILE = ENV["HOME"]+"/.one/one_auth"
|
||||
|
||||
# #########################################################################
|
||||
# Gets authorization credentials from ONE_AUTH or default
|
||||
# auth file.
|
||||
#
|
||||
# Raises an error if authorization is not found
|
||||
# #########################################################################
|
||||
def self.get_one_auth
|
||||
if ENV["ONE_AUTH"] and !ENV["ONE_AUTH"].empty? and
|
||||
File.file?(ENV["ONE_AUTH"])
|
||||
one_auth=File.read(ENV["ONE_AUTH"]).strip.split(':')
|
||||
elsif File.file?(DEFAULT_AUTH_FILE)
|
||||
one_auth=File.read(DEFAULT_AUTH_FILE).strip.split(':')
|
||||
else
|
||||
raise "No authorization data present"
|
||||
end
|
||||
|
||||
raise "Authorization data malformed" if one_auth.length < 2
|
||||
|
||||
one_auth
|
||||
end
|
||||
|
||||
# #########################################################################
|
||||
# Starts an http connection and calls the block provided. SSL flag
|
||||
# is set if needed.
|
||||
# #########################################################################
|
||||
def self.http_start(url, timeout, &block)
|
||||
host = nil
|
||||
port = nil
|
||||
|
||||
if ENV['http_proxy']
|
||||
uri_proxy = URI.parse(ENV['http_proxy'])
|
||||
host = uri_proxy.host
|
||||
port = uri_proxy.port
|
||||
end
|
||||
|
||||
http = Net::HTTP::Proxy(host, port).new(url.host, url.port)
|
||||
|
||||
if timeout
|
||||
http.read_timeout = timeout.to_i
|
||||
end
|
||||
|
||||
if url.scheme=='https'
|
||||
http.use_ssl = true
|
||||
http.verify_mode=OpenSSL::SSL::VERIFY_NONE
|
||||
end
|
||||
|
||||
begin
|
||||
res = http.start do |connection|
|
||||
block.call(connection)
|
||||
end
|
||||
rescue Errno::ECONNREFUSED => e
|
||||
str = "Error connecting to server (#{e.to_s}).\n"
|
||||
str << "Server: #{url.host}:#{url.port}"
|
||||
|
||||
return CloudClient::Error.new(str,"503")
|
||||
rescue Errno::ETIMEDOUT => e
|
||||
str = "Error timeout connecting to server (#{e.to_s}).\n"
|
||||
str << "Server: #{url.host}:#{url.port}"
|
||||
|
||||
return CloudClient::Error.new(str,"504")
|
||||
rescue Timeout::Error => e
|
||||
str = "Error timeout while connected to server (#{e.to_s}).\n"
|
||||
str << "Server: #{url.host}:#{url.port}"
|
||||
|
||||
return CloudClient::Error.new(str,"504")
|
||||
rescue SocketError => e
|
||||
str = "Error timeout while connected to server (#{e.to_s}).\n"
|
||||
|
||||
return CloudClient::Error.new(str,"503")
|
||||
rescue
|
||||
return CloudClient::Error.new($!.to_s,"503")
|
||||
end
|
||||
|
||||
if res.is_a?(Net::HTTPSuccess)
|
||||
res
|
||||
else
|
||||
CloudClient::Error.new(res.body, res.code)
|
||||
end
|
||||
end
|
||||
|
||||
# #########################################################################
|
||||
# The Error Class represents a generic error in the Cloud Client
|
||||
# library. It contains a readable representation of the error.
|
||||
# #########################################################################
|
||||
class Error
|
||||
attr_reader :message
|
||||
attr_reader :code
|
||||
|
||||
# +message+ a description of the error
|
||||
def initialize(message=nil, code="500")
|
||||
@message=message
|
||||
@code=code
|
||||
end
|
||||
|
||||
def to_s()
|
||||
@message
|
||||
end
|
||||
end
|
||||
|
||||
# #########################################################################
|
||||
# Returns true if the object returned by a method of the OpenNebula
|
||||
# library is an Error
|
||||
# #########################################################################
|
||||
def self.is_error?(value)
|
||||
value.class==CloudClient::Error
|
||||
end
|
||||
end
|
||||
|
||||
module OneGate
|
||||
module VirtualMachine
|
||||
VM_STATE=%w{INIT PENDING HOLD ACTIVE STOPPED SUSPENDED DONE FAILED
|
||||
POWEROFF UNDEPLOYED}
|
||||
|
||||
LCM_STATE=%w{LCM_INIT PROLOG BOOT RUNNING MIGRATE SAVE_STOP SAVE_SUSPEND
|
||||
SAVE_MIGRATE PROLOG_MIGRATE PROLOG_RESUME EPILOG_STOP EPILOG
|
||||
SHUTDOWN CANCEL FAILURE CLEANUP_RESUBMIT UNKNOWN HOTPLUG SHUTDOWN_POWEROFF
|
||||
BOOT_UNKNOWN BOOT_POWEROFF BOOT_SUSPENDED BOOT_STOPPED CLEANUP_DELETE
|
||||
HOTPLUG_SNAPSHOT HOTPLUG_NIC HOTPLUG_SAVEAS HOTPLUG_SAVEAS_POWEROFF
|
||||
HOTPLUG_SAVEAS_SUSPENDED SHUTDOWN_UNDEPLOY EPILOG_UNDEPLOY
|
||||
PROLOG_UNDEPLOY BOOT_UNDEPLOY}
|
||||
|
||||
SHORT_VM_STATES={
|
||||
"INIT" => "init",
|
||||
"PENDING" => "pend",
|
||||
"HOLD" => "hold",
|
||||
"ACTIVE" => "actv",
|
||||
"STOPPED" => "stop",
|
||||
"SUSPENDED" => "susp",
|
||||
"DONE" => "done",
|
||||
"FAILED" => "fail",
|
||||
"POWEROFF" => "poff",
|
||||
"UNDEPLOYED"=> "unde"
|
||||
}
|
||||
|
||||
SHORT_LCM_STATES={
|
||||
"PROLOG" => "prol",
|
||||
"BOOT" => "boot",
|
||||
"RUNNING" => "runn",
|
||||
"MIGRATE" => "migr",
|
||||
"SAVE_STOP" => "save",
|
||||
"SAVE_SUSPEND" => "save",
|
||||
"SAVE_MIGRATE" => "save",
|
||||
"PROLOG_MIGRATE" => "migr",
|
||||
"PROLOG_RESUME" => "prol",
|
||||
"EPILOG_STOP" => "epil",
|
||||
"EPILOG" => "epil",
|
||||
"SHUTDOWN" => "shut",
|
||||
"CANCEL" => "shut",
|
||||
"FAILURE" => "fail",
|
||||
"CLEANUP_RESUBMIT" => "clea",
|
||||
"UNKNOWN" => "unkn",
|
||||
"HOTPLUG" => "hotp",
|
||||
"SHUTDOWN_POWEROFF" => "shut",
|
||||
"BOOT_UNKNOWN" => "boot",
|
||||
"BOOT_POWEROFF" => "boot",
|
||||
"BOOT_SUSPENDED" => "boot",
|
||||
"BOOT_STOPPED" => "boot",
|
||||
"CLEANUP_DELETE" => "clea",
|
||||
"HOTPLUG_SNAPSHOT" => "snap",
|
||||
"HOTPLUG_NIC" => "hotp",
|
||||
"HOTPLUG_SAVEAS" => "hotp",
|
||||
"HOTPLUG_SAVEAS_POWEROFF" => "hotp",
|
||||
"HOTPLUG_SAVEAS_SUSPENDED" => "hotp",
|
||||
"SHUTDOWN_UNDEPLOY" => "shut",
|
||||
"EPILOG_UNDEPLOY" => "epil",
|
||||
"PROLOG_UNDEPLOY" => "prol",
|
||||
"BOOT_UNDEPLOY" => "boot"
|
||||
}
|
||||
|
||||
def self.state_to_str(id, lcm_id)
|
||||
id = id.to_i
|
||||
state_str = VM_STATE[id]
|
||||
|
||||
if state_str=="ACTIVE"
|
||||
lcm_id = lcm_id.to_i
|
||||
return LCM_STATE[lcm_id]
|
||||
end
|
||||
|
||||
return state_str
|
||||
end
|
||||
|
||||
def self.print(json_hash)
|
||||
OneGate.print_header("VM " + json_hash["VM"]["ID"])
|
||||
OneGate.print_key_value("NAME", json_hash["VM"]["NAME"])
|
||||
OneGate.print_key_value(
|
||||
"STATE",
|
||||
self.state_to_str(
|
||||
json_hash["VM"]["STATE"],
|
||||
json_hash["VM"]["LCM_STATE"]))
|
||||
|
||||
vm_nics = [json_hash['VM']['TEMPLATE']['NIC']].flatten
|
||||
vm_nics.each { |nic|
|
||||
# TODO: IPv6
|
||||
OneGate.print_key_value("IP", nic["IP"])
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module Service
|
||||
STATE = {
|
||||
'PENDING' => 0,
|
||||
'DEPLOYING' => 1,
|
||||
'RUNNING' => 2,
|
||||
'UNDEPLOYING' => 3,
|
||||
'WARNING' => 4,
|
||||
'DONE' => 5,
|
||||
'FAILED_UNDEPLOYING' => 6,
|
||||
'FAILED_DEPLOYING' => 7,
|
||||
'SCALING' => 8,
|
||||
'FAILED_SCALING' => 9,
|
||||
'COOLDOWN' => 10
|
||||
}
|
||||
|
||||
STATE_STR = [
|
||||
'PENDING',
|
||||
'DEPLOYING',
|
||||
'RUNNING',
|
||||
'UNDEPLOYING',
|
||||
'WARNING',
|
||||
'DONE',
|
||||
'FAILED_UNDEPLOYING',
|
||||
'FAILED_DEPLOYING',
|
||||
'SCALING',
|
||||
'FAILED_SCALING',
|
||||
'COOLDOWN'
|
||||
]
|
||||
|
||||
# Returns the string representation of the service state
|
||||
# @param [String] state String number representing the state
|
||||
# @return the state string
|
||||
def self.state_str(state_number)
|
||||
return STATE_STR[state_number.to_i]
|
||||
end
|
||||
|
||||
def self.print(json_hash)
|
||||
OneGate.print_header("SERVICE " + json_hash["SERVICE"]["id"])
|
||||
OneGate.print_key_value("NAME", json_hash["SERVICE"]["name"])
|
||||
OneGate.print_key_value("STATE", Service.state_str(json_hash["SERVICE"]['state']))
|
||||
puts
|
||||
|
||||
roles = [json_hash['SERVICE']['roles']].flatten
|
||||
roles.each { |role|
|
||||
OneGate.print_header("ROLE " + role["name"], false)
|
||||
|
||||
if role["nodes"]
|
||||
role["nodes"].each{ |node|
|
||||
OneGate::VirtualMachine.print(node["vm_info"])
|
||||
}
|
||||
end
|
||||
|
||||
puts
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class Client
|
||||
def initialize(opts={})
|
||||
@vmid = ENV["VMID"]
|
||||
@token = ENV["TOKENTXT"]
|
||||
|
||||
url = opts[:url] || ENV['ONEGATE_ENDPOINT']
|
||||
@uri = URI.parse(url)
|
||||
|
||||
@user_agent = "OpenNebula #{CloudClient::VERSION} " <<
|
||||
"(#{opts[:user_agent]||"Ruby"})"
|
||||
|
||||
@host = nil
|
||||
@port = nil
|
||||
|
||||
if ENV['http_proxy']
|
||||
uri_proxy = URI.parse(ENV['http_proxy'])
|
||||
@host = uri_proxy.host
|
||||
@port = uri_proxy.port
|
||||
end
|
||||
end
|
||||
|
||||
def get(path)
|
||||
req = Net::HTTP::Proxy(@host, @port)::Get.new(path)
|
||||
|
||||
do_request(req)
|
||||
end
|
||||
|
||||
def delete(path)
|
||||
req =Net::HTTP::Proxy(@host, @port)::Delete.new(path)
|
||||
|
||||
do_request(req)
|
||||
end
|
||||
|
||||
def post(path, body)
|
||||
req = Net::HTTP::Proxy(@host, @port)::Post.new(path)
|
||||
req.body = body
|
||||
|
||||
do_request(req)
|
||||
end
|
||||
|
||||
def put(path, body)
|
||||
req = Net::HTTP::Proxy(@host, @port)::Put.new(path)
|
||||
req.body = body
|
||||
|
||||
do_request(req)
|
||||
end
|
||||
|
||||
def login
|
||||
req = Net::HTTP::Proxy(@host, @port)::Post.new('/login')
|
||||
|
||||
do_request(req)
|
||||
end
|
||||
|
||||
def logout
|
||||
req = Net::HTTP::Proxy(@host, @port)::Post.new('/logout')
|
||||
|
||||
do_request(req)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def do_request(req)
|
||||
req.basic_auth @username, @password
|
||||
|
||||
req['User-Agent'] = @user_agent
|
||||
req['X-ONEGATE-TOKEN'] = @token
|
||||
req['X-ONEGATE-VMID'] = @vmid
|
||||
|
||||
res = CloudClient::http_start(@uri, @timeout) do |http|
|
||||
http.request(req)
|
||||
end
|
||||
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_json(response)
|
||||
if CloudClient::is_error?(response)
|
||||
puts "ERROR: "
|
||||
puts response.message
|
||||
exit -1
|
||||
else
|
||||
return JSON.parse(response.body)
|
||||
end
|
||||
end
|
||||
|
||||
# Sets bold font
|
||||
def self.scr_bold
|
||||
print "\33[1m"
|
||||
end
|
||||
|
||||
# Sets underline
|
||||
def self.scr_underline
|
||||
print "\33[4m"
|
||||
end
|
||||
|
||||
# Restore normal font
|
||||
def self.scr_restore
|
||||
print "\33[0m"
|
||||
end
|
||||
|
||||
# Print header
|
||||
def self.print_header(str, underline=true)
|
||||
if $stdout.tty?
|
||||
scr_bold
|
||||
scr_underline if underline
|
||||
print "%-80s" % str
|
||||
scr_restore
|
||||
else
|
||||
print str
|
||||
end
|
||||
puts
|
||||
end
|
||||
|
||||
def self.print_key_value(key, value)
|
||||
puts "%-20s: %-20s" % [key, value]
|
||||
end
|
||||
|
||||
def self.help_str
|
||||
return <<-EOT
|
||||
Available commands
|
||||
$ onegate vm show [VMID] [--json]
|
||||
|
||||
$ onegate vm update [VMID] --data KEY=VALUE[\\nKEY2=VALUE2]
|
||||
|
||||
$ onegate vm ACTION VMID
|
||||
$ onegate resume [VMID]
|
||||
$ onegate stop [VMID]
|
||||
$ onegate suspend [VMID]
|
||||
$ onegate delete [VMID] [--hard]
|
||||
$ onegate shutdown [VMID] [--hard]
|
||||
$ onegate reboot [VMID] [--hard]
|
||||
$ onegate poweroff [VMID] [--hard]
|
||||
$ onegate resubmit [VMID]
|
||||
$ onegate resched [VMID]
|
||||
$ onegate unresched [VMID]
|
||||
$ onegate hold [VMID]
|
||||
$ onegate release [VMID]
|
||||
|
||||
$ onegate service show [--json]
|
||||
|
||||
$ onegate service scale --role ROLE --cardinality CARDINALITY
|
||||
EOT
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
require 'optparse'
|
||||
|
||||
options = {}
|
||||
OptionParser.new do |opts|
|
||||
opts.on("-d", "--data DATA", "Data to be included in the VM") do |data|
|
||||
options[:data] = data
|
||||
end
|
||||
|
||||
opts.on("-r", "--role ROLE", "Service role") do |role|
|
||||
options[:role] = role
|
||||
end
|
||||
|
||||
opts.on("-c", "--cardinality CARD", "Service cardinality") do |cardinality|
|
||||
options[:cardinality] = cardinality
|
||||
end
|
||||
|
||||
opts.on("-j", "--json", "Print resource information in JSON") do |json|
|
||||
options[:json] = json
|
||||
end
|
||||
|
||||
opts.on("-f", "--hard", "Hard option for power off operations") do |hard|
|
||||
options[:hard] = hard
|
||||
end
|
||||
|
||||
opts.on("-h", "--help", "Show this message") do
|
||||
puts OneGate.help_str
|
||||
exit
|
||||
end
|
||||
end.parse!
|
||||
|
||||
client = OneGate::Client.new()
|
||||
|
||||
case ARGV[0]
|
||||
when "vm"
|
||||
case ARGV[1]
|
||||
when "show"
|
||||
if ARGV[2]
|
||||
response = client.get("/vms/"+ARGV[2])
|
||||
else
|
||||
response = client.get("/vm")
|
||||
end
|
||||
|
||||
json_hash = OneGate.parse_json(response)
|
||||
if options[:json]
|
||||
puts JSON.pretty_generate(json_hash)
|
||||
else
|
||||
OneGate::VirtualMachine.print(json_hash)
|
||||
end
|
||||
when "update"
|
||||
if !options[:data]
|
||||
puts "You have to provide the data as a param (--data)"
|
||||
exit -1
|
||||
end
|
||||
|
||||
if ARGV[2]
|
||||
response = client.put("/vms/"+ARGV[2], options[:data])
|
||||
else
|
||||
response = client.put("/vm", options[:data])
|
||||
end
|
||||
|
||||
if CloudClient::is_error?(response)
|
||||
puts "ERROR: "
|
||||
puts response.message
|
||||
exit -1
|
||||
end
|
||||
when "resume",
|
||||
"stop",
|
||||
"suspend",
|
||||
"delete",
|
||||
"shutdown",
|
||||
"reboot",
|
||||
"poweroff",
|
||||
"resubmit",
|
||||
"resched",
|
||||
"unresched",
|
||||
"hold",
|
||||
"release"
|
||||
if ARGV[2]
|
||||
action_hash = {
|
||||
"action" => {
|
||||
"perform" => ARGV[1]
|
||||
}
|
||||
}
|
||||
|
||||
if options[:hard]
|
||||
action_hash["action"]["params"] = true
|
||||
end
|
||||
|
||||
response = client.post("/vms/"+ARGV[2]+"/action", action_hash.to_json)
|
||||
|
||||
if CloudClient::is_error?(response)
|
||||
puts "ERROR: "
|
||||
puts response.message
|
||||
exit -1
|
||||
end
|
||||
else
|
||||
puts "You have to provide a VM ID"
|
||||
exit -1
|
||||
end
|
||||
else
|
||||
puts OneGate.help_str
|
||||
puts
|
||||
puts "Action #{ARGV[1]} not supported"
|
||||
exit -1
|
||||
end
|
||||
when "service"
|
||||
case ARGV[1]
|
||||
when "show"
|
||||
response = client.get("/service")
|
||||
json_hash = OneGate.parse_json(response)
|
||||
#pp json_hash
|
||||
if options[:json]
|
||||
puts JSON.pretty_generate(json_hash)
|
||||
else
|
||||
OneGate::Service.print(json_hash)
|
||||
end
|
||||
when "scale"
|
||||
response = client.put(
|
||||
"/service/role/" + options[:role],
|
||||
{
|
||||
:cardinality => options[:cardinality]
|
||||
}.to_json)
|
||||
|
||||
if CloudClient::is_error?(response)
|
||||
puts "ERROR: "
|
||||
puts response.message
|
||||
exit -1
|
||||
end
|
||||
else
|
||||
puts OneGate.help_str
|
||||
puts
|
||||
puts "Action #{ARGV[1]} not supported"
|
||||
exit -1
|
||||
end
|
||||
else
|
||||
puts OneGate.help_str
|
||||
exit -1
|
||||
end
|
||||
|
Loading…
Reference in new issue