#!/usr/bin/env ruby 

# -------------------------------------------------------------------------- #
# Copyright 2002-2009, Distributed Systems Architecture Group, Universidad   #
# Complutense de Madrid (dsa-research.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.                                             #
#--------------------------------------------------------------------------- #

ONE_LOCATION=ENV["ONE_LOCATION"]

if !ONE_LOCATION
    RUBY_LIB_LOCATION="/usr/lib/one/ruby"
else
    RUBY_LIB_LOCATION=ONE_LOCATION+"/lib/ruby"
end

$: << RUBY_LIB_LOCATION

require 'one'
require 'client_utilities'
require 'command_parse'

ShowTableVM={
    :id => {
        :name => "ID",
        :desc => "ONE identifier for the VM",
        :size => 4,
        :proc => lambda {|d,e| d["oid"] }
    },
    :name => {
        :name => "NAME",
        :desc => "Name of the domain",
        :size => 8,
        :proc => lambda {|d,e| 
            tid=d["template_id"]
            template=e[:vm].get_template(tid)
            template["NAME"]
        }
    },
    :stat => {
        :name => "STAT",
        :desc => "Actual status of the VM",
        :size => 4,
        :proc => lambda {|d,e| e[:vm].get_state(d) }
    },
    :cpu => {
        :name => "CPU",
        :desc => "CPU percentage used by the VM",
        :size => 3,
        :proc => lambda {|d,e| d["cpu"] }
    },
    :mem => {
        :name => "MEM",
        :desc => "Memory used by the VM",
        :size => 7,
        :proc => lambda {|d,e| d["memory"] }
    },
    :hostname => {
        :name => "HOSTNAME",
        :desc => "Machine where the VM is running",
        :size => 15,
        :proc => lambda {|d,e| e[:vm].get_history_host(d["oid"]) }
    },
    :time => {
        :name => "TIME",
        :desc => "Time since the VM was submitted",
        :size => 11,
        :proc => lambda {|d,e| e[:vm].str_running_time(d) }
    },
    
    :default => [:id, :name, :stat, :cpu, :mem, :hostname, :time]
}

ShowTableHistory={
    :id => {
        :name => "ID",
        :desc => "ONE identifier for the VM",
        :size => 4,
        :proc => lambda {|d,e| d["oid"] }
    },
    :seq => {
        :name => "SEQ",
        :desc => "Sequence number",
        :size => 3,
        :proc => lambda {|d,e| d["seq"] }
    },
    :hostname => {
        :name => "HOSTNAME",
        :desc => "Name of the host where the VM was submited",
        :size => 15,
        :proc => lambda {|d,e| d["host_name"] }
    },
    :stime => {
        :name => "STIME",
        :desc => "Start time",
        :size => 14,
        :proc => lambda {|d,e|
            t=Time.at(d["stime"].to_i)
            t.strftime("%m/%d %H:%M:%S")
        }
    },
    :etime => {
        :name => "ETIME",
        :desc => "End time",
        :size => 14,
        :proc => lambda {|d,e|
            if d["etime"].to_i==0
                "--"
            else
                t=Time.at(d["etime"].to_i)
                t.strftime("%m/%d %H:%M:%S")
            end
        }
    },
    :time => {
        :name => "TIME",
        :desc => "Total time",
        :size => 11,
        :proc => lambda {|d,e| e[:vm].str_running_time(d) }
    },
    :reason => {
        :name => "REASON",
        :desc => "Reason for state change",
        :size => "6",
        :proc => lambda {|d,e| e[:vm].get_reason(d) }
    },
    
    :default => [:id, :seq, :hostname, :stime, :etime, :time, :reason]
}

class VmShow
    
    def initialize(vm)
        @vm=vm
        @table=ShowTable.new(ShowTableVM, :vm => @vm)
        @table_history=ShowTable.new(ShowTableHistory, :vm => @vm)
    end
    
    def close
    end

    def header_vm_small
        scr_bold
        scr_underline
        puts @table.header_str
        scr_restore
    end

    def header_history_small
        scr_bold
        scr_underline
        puts @table_history.header_str
        scr_restore
    end

    def print_vm_long(data)
        str="%-10s: %-20s"
        puts str % ["ID", data["oid"]]
        puts str % ["NAME", data["deploy_id"]]
        puts str % ["STATE", ONE::VM_STATE[data["state"].to_i]]
        puts str % ["LCM_STATE", ONE::LCM_STATE[data["lcm_state"].to_i]]
        puts str % ["MEMORY", data["memory"]]
        puts str % ["CPU", data["cpu"]]
        puts
        puts "Template"
        str="    %-20s: %-20s"
        data.each {|key,value|
            puts str % [key, value] if !%w{oid deploy_id state lcm_state memory cpu template_id}.include? key
        }
    end
    
    def list_short(options=nil)
        res=@vm.get_vms(:where => "state <> 6")
        if options
            @table.columns=options[:columns] if options[:columns]
        end

        if res[0]
            result=[true, ""]
            header_vm_small
            
            if options
                puts @table.data_str(res[1], options)
            else
                puts @table.data_str(res[1])
            end
            
            result
        else
            result=res
        end
    end
    
    def list_long(id)
        res=@vm.get(:where => "oid="+id)

        if res && res[0] && res[1] && res[1].length>0
            result=[true, ""]
            res[1].each {|row|
                print_vm_long(row)
            }
            result
        else
            if res[0]
                [false, "VM not found"]
            else
                result=res
            end
        end
    end
    
    def top(options=nil)
        delay=1
        delay=options[:delay] if options && options[:delay]
        
        result=nil
        
        begin
            while true
                scr_cls
                scr_move(0,0)
                result=list_short(options)
                sleep delay
            end
        rescue Exception
        end
        result
    end
    
    def list_vm_history(id, options=nil)
        res=@vm.get_history(id)
        if options
            @table_history.columns=options[:columns] if options[:columns]
        end

        if res[0]
            result=[true, ""]
            header_history_small
            
            if options
                puts @table_history.data_str(res[1], options)
            else
                puts @table_history.data_str(res[1])
            end
            
            result
        else
            result=res
        end
    end
    
    def list_vm_history_array(ids, options=nil)
        res=[false, "No VMs found"]
        ids.each {|id|
            vm_id=get_vm_id(@vm, id)
            puts "History for VM #{id}"
            puts
            res=list_vm_history(vm_id, options)
            puts
        }
        res
    end
    
    def list_vm_history_all(options=nil)
        res=@vm.get
        res[1].each {|vm|
            list_vm_history_array(vm["oid"], options)
        }
        res
    end
end


##########################
## COMMAND LINE PARSING ##
##########################

class OnevmParse < CommandParse

    COMMANDS_HELP=<<-EOT
Commands:

* create (Submits a new virtual machine, adding it to the ONE VM pool)
    onevm create <template>
    
    template is a file name where the VM description is located
    
* deploy (Start a previously submitted VM in an specific host)
    onevm deploy <vm_id> <host_id>
    
* shutdown (Shutdown an already deployed VM)
    onevm shutdown <vm_id>
    
* livemigrate (Migrates a running VM to another host without downtime)
    onevm livemigrate <vm_id> <host_id>
    
* migrate (Saves a running VM and starts it again in the specified host)
    onevm migrate <vm_id> <host_id>

* hold (Sets a VM to hold state, scheduler will not deploy it)
    onevm hold <vm_id>

* release (Releases a VM from hold state)
    onevm release <vm_id>

* stop (Stops a running VM)
    onevm stop <vm_id>
    
* cancel (Cancels a running VM)
    onevm cancel <vm_id>

* suspend (Saves a running VM)
    onevm suspend <vm_id>
    
* resume (Resumes the execution of a saved VM)
    onevm resume <vm_id>
    
* delete (Deletes a VM from the pool and DB)
    onevm delete <vm_id>
    
* list (Shows VMs in the pool)
    onevm list
    
* show (Gets information about a specific VM)
    onevm show <vm_id>

* top (Lists VMs continuously)
    onevm top

* history (Gets history from VMs)
    onevm history [<vm_id> <vm_id> ...]
    
    if no vm_id is provided it will list history for all known VMs
    
EOT

    def text_commands
        COMMANDS_HELP
    end

    def text_command_name
        "onevm"
    end
    
    def list_options
        table=ShowTable.new(ShowTableVM)
        table.print_help
    end
    
end


server=ONE::Server.new
$vm=vm=ONE::VM.new(server)
host=ONE::Host.new(server)

def command_exit(code)
    $vm.close_db
    exit(code)
end
    

onevm_opts=OnevmParse.new
onevm_opts.parse(ARGV)
ops=onevm_opts.options

result=[false, "Unknown error"]

command=ARGV.shift

case command
when "submit", "create"
    check_parameters("create", 1)
    result=vm.allocate(*ARGV)
    if result[0]
        puts "ID: " + result[1].to_s if ops[:verbose]
        command_exit 0
    end
    
when "deploy"
    check_parameters("deploy", 2)
    vm_id=get_vm_id(vm, ARGV[0])
    host_id=get_host_id(host, ARGV[1])
    result=vm.deploy(vm_id, host_id)
    if result[0]
        puts "Deploying VM" if ops[:verbose]
        command_exit 0
    end

when "shutdown"
    check_parameters("shutdown", 1)
    vm_id=get_vm_id(vm, ARGV[0])
    result=vm.shutdown(vm_id)
    if result[0]
        puts "Shutting down VM" if ops[:verbose]
        command_exit 0
    end

when "livemigrate"
    check_parameters("livemigrate", 2)
    vm_id=get_vm_id(vm, ARGV[0])
    host_id=get_host_id(host, ARGV[1])
    result=vm.livemigrate(vm_id, host_id)
    if result[0]
        puts "Migrating VM" if ops[:verbose]
        command_exit 0
    end

when "migrate"
    check_parameters("migrate", 2)
    vm_id=get_vm_id(vm, ARGV[0])
    host_id=get_host_id(host, ARGV[1])
    result=vm.migrate(vm_id, host_id)
    if result[0]
        puts "Migrating VM" if ops[:verbose]
        command_exit 0
    end

when "hold"
    check_parameters("hold", 1)
    vm_id=get_vm_id(vm, ARGV[0])
    result=vm.hold(vm_id)
    if result[0]
        puts "Setting VM to hold state" if ops[:verbose]
        command_exit 0
    end

when "release"
    check_parameters("release", 1)
    vm_id=get_vm_id(vm, ARGV[0])
    result=vm.release(vm_id)
    if result[0]
        puts "Releasing VM" if ops[:verbose]
        command_exit 0
    end
    
when "stop"
    check_parameters("stop", 1)
    vm_id=get_vm_id(vm, ARGV[0])
    result=vm.stop(vm_id)
    if result[0]
        puts "Stopping VM" if ops[:verbose]
        command_exit 0
    end
    
when "cancel"
    check_parameters("cancel", 1)
    result=vm.cancel(*ARGV)
    if result[0]
        puts "Cancelling VM" if ops[:verbose]
        command_exit 0
    end

when "suspend"
    check_parameters("suspend", 1)
    vm_id=get_vm_id(vm, ARGV[0])
    result=vm.suspend(vm_id)
    if result[0]
        puts "Suspending VM" if ops[:verbose]
        command_exit 0
    end
    
when "resume"
    check_parameters("resume", 1)
    vm_id=get_vm_id(vm, ARGV[0])
    result=vm.resume(vm_id)
    if result[0]
        puts "Resuming VM" if ops[:verbose]
        command_exit 0
    end

when "list"
    vmlist=VmShow.new(vm)
    ops[:columns]=ops[:list] if ops[:list]
    result=vmlist.list_short(ops)
    vmlist.close
    
when "top"
    vmlist=VmShow.new(vm)
    ops[:columns]=ops[:list] if ops[:list]
    result=vmlist.top(ops)
    vmlist.close
    
when "show_db"
    check_parameters("show", 1)
    vmlist=VmShow.new(vm)
    
    if ARGV[0]
        vm_id=get_vm_id(vm, ARGV[0])
        result=vmlist.list_long(vm_id)
        
        vmlist.close
    else
        result=[false, "You must provide a VM id."]
    end
    
when "history"
    vmlist=VmShow.new(vm)
    ops[:columns]=ops[:list] if ops[:list]
    if ARGV[0]
        ids=ARGV.collect {|arg| get_vm_id(vm, arg)}
        ids=ids.flatten.compact
        result=vmlist.list_vm_history_array(ids)
    else
        result=vmlist.list_vm_history_all(ops)
    end
    vmlist.close
    
when "delete"
    check_parameters("delete", 1)
    vm_id=get_vm_id(vm, ARGV[0])
    result=vm.delete(vm_id)
    if result[0]
        puts "VM correctly deleted"  if ops[:verbose]
        command_exit 0
    end

when "show"
     check_parameters("get_info", 1)
     vm_id=get_vm_id(vm, ARGV[0])
     res = vm.get_info(vm_id)
     if res[0]
         res[1].gsub!(/^\n/, '')
         (normal, template)=res[1].split("Template")
         
         str="%-15s: %-20s"
         normal.each{|line|
             (key, value)=line.split(":").collect{|v| v.strip }
             case key 
             when "STATE"
                 value = ONE::VM_STATE[value.to_i]
             when "LCM STATE"
                 value = ONE::LCM_STATE[value.to_i]   
             when "START TIME","STOP TIME"
                 value=Time.at(value.to_i).strftime("%m/%d %H:%M:%S")
             end
             puts str % [key, value] 
         }
         
         puts
         puts "....: Template :...."
                           
         str="    %-16s: %-20s"
         
         template.gsub!(/\@\^\_\^\@/,',')
         
         template.scan(/^\t*(.*?)=(.*)$/).each{|line|
             (key, value)=line
             puts str % [key, value] 
         }              
         
         command_exit 0
     end
        
else
    onevm_opts.print_help
    command_exit -1
end



if !result[0]
    puts "Error: " + result[1].to_s
    command_exit -1
end

command_exit 0

