2011-06-28 09:47:16 -04:00
require 'fog/core/model'
require 'fog/compute/models/libvirt/util'
2011-08-03 06:48:44 -04:00
require 'net/ssh/proxy/command'
require 'rexml/document'
require 'erb'
require 'securerandom'
2011-06-28 09:47:16 -04:00
module Fog
module Compute
class Libvirt
class Server < Fog :: Model
include Fog :: Compute :: LibvirtUtil
2011-08-03 08:28:40 -04:00
identity :uuid
2011-08-08 17:22:55 -04:00
2011-08-03 08:28:40 -04:00
attribute :cpus
attribute :os_type
attribute :memory_size
attribute :name
2011-06-28 09:47:16 -04:00
2011-08-03 06:48:44 -04:00
attribute :poolname
attribute :xml
attribute :create_persistent
attribute :template_options
attribute :template_erb
attr_accessor :password
attr_writer :private_key , :private_key_path , :public_key , :public_key_path , :username
2011-08-08 17:22:55 -04:00
# Can be created by passing in :xml => "<xml to create domain/server>"
# or by providing :template_options => {
2011-08-03 06:48:44 -04:00
# :name => "", :cpus => 1, :memory_size => 256 , :volume_template
# :}
#
# @returns server/domain created
def initialize ( attributes = { } )
self . xml || = nil unless attributes [ :xml ]
self . create_persistent || = true unless attributes [ :create_persistent ]
self . template_options || = nil unless attributes [ :template_options ]
super
2011-08-08 17:22:55 -04:00
end
2011-08-03 06:48:44 -04:00
def save
2011-08-08 17:22:55 -04:00
raise Fog :: Errors :: Error . new ( 'Resaving an existing object may create a duplicate' ) if uuid
2011-08-03 06:48:44 -04:00
# first check if we have either xml or template_options
if xml . nil? && template_options . nil?
2011-08-08 17:22:55 -04:00
raise Fog :: Errors :: Error . new ( 'Creating a new domain/server requires either xml or passing template_options' )
2011-08-03 06:48:44 -04:00
end
if ! xml . nil? && ! template_options . nil?
2011-08-08 17:22:55 -04:00
raise Fog :: Errors :: Error . new ( 'Creating a new domain/server requires either xml or passing template_options,not both' )
2011-08-03 06:48:44 -04:00
end
2011-08-08 17:22:55 -04:00
# We have a template, let's generate some xml for it
2011-08-03 06:48:44 -04:00
if ! template_options . nil?
template_defaults = {
2011-08-08 17:22:55 -04:00
:cpus = > 1 ,
:memory_size = > 256 ,
:arch = > " x86_64 " ,
2011-08-03 06:48:44 -04:00
:os = > " hvm " ,
:domain_type = > " kvm " ,
:name = > " fog- #{ SecureRandom . random_number * 10 E14 . to_i . round } " ,
# Network options
:interface_type = > " nat " , #or "bridge"
2011-08-08 17:30:09 -04:00
:nat_network_name = > " default " ,
:bridge_name = > " br0 " ,
# Disk options
:disk_type = > " raw " ,
:disk_size = > 10 ,
:disk_size_unit = > " G " ,
:disk_allocate = > 1 ,
:disk_allocate_unit = > " G " ,
:disk_extension = > " img " ,
:disk_template_name = > nil ,
:poolname = > nil ,
# DVD options
:iso_file = > nil ,
:iso_dir = > " /var/lib/libvirt/images/ "
2011-08-03 06:48:44 -04:00
}
template_options2 = template_defaults . merge ( template_options )
2011-08-08 17:22:55 -04:00
template_options = {
2011-08-08 17:30:09 -04:00
:disk_name = > " #{ template_options2 [ :name ] } . #{ template_options2 [ :disk_extension ] } "
2011-08-03 08:28:40 -04:00
} . merge ( template_options2 )
validate_template_options ( template_options )
2011-08-03 06:48:44 -04:00
if ! template_options [ :disk_template_name ] . nil?
2011-08-08 17:22:55 -04:00
# Clone the volume
2011-08-05 11:36:31 -04:00
volume = connection . volumes . all ( :name = > template_options [ :disk_template_name ] ) . first . clone ( " #{ template_options [ :disk_name ] } " )
2011-08-03 08:28:40 -04:00
# This gets passed to the domain to know the path of the disk
2011-08-03 06:48:44 -04:00
template_options [ :disk_path ] = volume . path
else
# If no template volume was given, let's create our own volume
2011-08-08 17:22:55 -04:00
volume = connection . volumes . create ( :template_options = > {
:name = > " #{ template_options [ :disk_name ] } " ,
:extension = > " #{ template_options [ :disk_extension ] } " ,
2011-08-03 06:48:44 -04:00
:type = > " #{ template_options [ :disk_type ] } " ,
:size = > " #{ template_options [ :disk_size ] } " ,
:size_unit = > " #{ template_options [ :disk_size_unit ] } " ,
:allocate = > " #{ template_options [ :disk_allocate ] } " ,
2011-08-05 11:36:31 -04:00
:allocate_unit = > " #{ template_options [ :disk_allocate_unit ] } " } )
2011-08-03 06:48:44 -04:00
2011-08-03 08:28:40 -04:00
# This gets passed to the domain to know the path of the disk
2011-08-03 06:48:44 -04:00
template_options [ :disk_path ] = volume . path
2011-06-28 09:47:16 -04:00
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
xml = xml_from_template ( template_options )
2011-06-28 09:47:16 -04:00
end
2011-08-08 17:30:09 -04:00
# We either now have xml provided by the user or generated by the template
if ! xml . nil?
domain = nil
if create_persistent
domain = connection . define_domain_xml ( xml )
else
domain = connection . create_domain_xml ( xml )
2011-08-03 08:28:40 -04:00
end
2011-08-08 17:30:09 -04:00
self . raw = domain
end
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
def validate_template_options ( template_options )
#if template_options[:disk_template_name].nil?
# raise Fog::Errors::Error.new('In order to make the disk boot, we require a template volume we can clone')
#end
unless template_options [ :interface_type ] . nil?
raise Fog :: Errors :: Error . new ( " #{ template_options [ :interface_type ] } is not a supported interface type " ) unless [ " nat " , " bridge " ] . include? ( template_options [ :interface_type ] )
2011-08-03 06:48:44 -04:00
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
def xml_from_template ( template_options )
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
# We only want specific variables for ERB
vars = ErbBinding . new ( template_options )
template_path = File . join ( File . dirname ( __FILE__ ) , " templates " , " server.xml.erb " )
template = File . open ( template_path ) . readlines . join
erb = ERB . new ( template )
vars_binding = vars . send ( :get_binding )
result = erb . result ( vars_binding )
return result
end
def username
@username || = 'root'
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def start
requires :raw
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
unless @raw . active?
begin
@raw . create
true
rescue
false
2011-08-03 06:48:44 -04:00
end
2011-08-08 17:22:55 -04:00
end
2011-08-08 17:30:09 -04:00
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def destroy ( options = { :destroy_volumes = > false } )
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
#connection.volumes(name).destroy
requires :raw
if @raw . active?
@raw . destroy
2011-08-03 06:48:44 -04:00
end
2011-08-08 17:30:09 -04:00
@raw . undefine
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def reboot
requires :raw
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
@raw . reboot
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def halt
requires :raw
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
@raw . halt
end
2011-08-05 11:36:31 -04:00
2011-08-08 17:30:09 -04:00
def poweroff
requires :raw
@raw . destroy
end
2011-08-05 11:36:31 -04:00
2011-08-08 17:30:09 -04:00
def shutdown
requires :raw
2011-08-05 11:36:31 -04:00
2011-08-08 17:30:09 -04:00
@raw . shutdown
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def resume
requires :raw
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
@raw . resume
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def suspend
requires :raw
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
@raw . suspend
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def state
state = case @raw . info . state
when 0 then :nostate
when 1 then :running
when 2 then :paused
when 3 then :shuttingdown
when 4 then :shutoff
when 5 then :crashed
end
2011-06-28 09:47:16 -04:00
return state
end
2011-08-03 06:48:44 -04:00
def ready?
state == :running
2011-06-28 09:47:16 -04:00
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def stop
requires :raw
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
@raw . shutdown
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def xml_desc
requires :raw
raw . xml_desc
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
##Note this requires arpwatch to be running
##and chmod o+x /var/lib/arpwatch
2011-08-08 19:31:12 -04:00
# This retrieves the ip address of the mac address
# It returns an array of public and private ip addresses
# Currently only one ip address is returned, but in the future this could be multiple
# if the server has multiple network interface
#
def addresses ( options = { } )
2011-08-08 17:30:09 -04:00
mac = self . mac
2011-08-08 19:31:12 -04:00
# Check if another ip_command string was provided
ip_command = options [ :ip_command ] . nil? ? " grep #{ mac } /var/log/arpwatch.log |cut -d ':' -f 4-| cut -d ' ' -f 4 " : options [ :ip_command ]
2011-08-08 17:30:09 -04:00
ipaddress = nil
2011-08-08 19:31:12 -04:00
if @connection . uri . ssh_enabled?
2011-08-08 17:30:09 -04:00
2011-08-08 19:31:12 -04:00
# Retrieve the parts we need from the connection to setup our ssh options
2011-08-08 17:30:09 -04:00
user = connection . uri . user #could be nil
host = connection . uri . host
keyfile = connection . uri . keyfile
port = connection . uri . port
2011-08-08 19:31:12 -04:00
# Setup the options
ssh_options = { }
ssh_options [ :keys ] = [ keyfile ] unless keyfile . nil?
ssh_options [ :port ] = port unless keyfile . nil?
ssh_options [ :paranoid ] = true if connection . uri . no_verify?
# TODO: we need to take the time into account, when IP's are re-allocated, we might be executing
# On the wrong host
begin
result = Fog :: SSH . new ( host , user , ssh_options ) . run ( ip_command )
rescue Errno :: ECONNREFUSED
raise Fog :: Errors :: Error . new ( " Connection was refused to host #{ host } to retrieve the ipaddress for #{ mac } " )
rescue Net :: SSH :: AuthenticationFailed
raise Fog :: Errors :: Error . new ( " Error authenticating over ssh to host #{ host } and user #{ user } " )
end
2011-08-08 17:30:09 -04:00
2011-08-08 19:31:12 -04:00
#TODO: We currently just retrieve the ip address through the ip_command
#TODO: We need to check if that Ipaddress is still valid for that mac-address
# Check for a clean exit code
2011-08-08 17:30:09 -04:00
if result . first . status == 0
ipaddress = result . first . stdout . strip
2011-08-08 17:22:55 -04:00
else
2011-08-08 19:31:12 -04:00
# We got a failure executing the command
raise Fog :: Errors :: Error . new ( " The command #{ ip_command } failed to execute with a clean exit code " )
2011-06-28 09:47:16 -04:00
end
2011-08-08 19:31:12 -04:00
2011-08-08 17:30:09 -04:00
else
2011-08-08 19:31:12 -04:00
# It's not ssh enabled, so we assume it is
if @connection . uri . transport == " tls "
raise Fog :: Errors :: Error . new ( " TlS remote transport is not currently supported, only ssh " )
end
IO . popen ( " #{ ip_command } " ) do | p |
p . each_line do | l |
ipaddress = + l
end
status = Process . waitpid2 ( p . pid ) [ 1 ] . exitstatus
if status! = 0
raise Fog :: Errors :: Error . new ( " The command #{ ip_command } failed to execute with a clean exit code " )
end
end
2011-08-08 17:30:09 -04:00
# TODO for locat execute
#No ssh just do it locally
#cat /var/log/daemon.log|grep "52:54:00:52:f6:22"|
# or local execute arp -an to get the ip (as a last resort)
2011-06-28 09:47:16 -04:00
2011-08-03 06:48:44 -04:00
end
2011-08-08 19:31:12 -04:00
if ipaddress == " "
#The grep didn't find an ip address result"
ipaddress = nil
else
# To be sure that the command didn't return another random string
# We check if the result is an actual ip-address
# otherwise we return nil
unless ipaddress =~ / ^( \ d{1,3} \ .){3} \ d{1,3}$ /
raise Fog :: Errors :: Error . new (
" The command #{ ip_command } failed to execute with a clean exit code \n " +
" Result was: #{ ipaddress } \n "
)
end
end
2011-08-08 17:30:09 -04:00
return { :public = > [ ipaddress ] , :private = > [ ipaddress ] }
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
def private_ip_address
ip_address ( :private )
end
2011-08-08 17:22:55 -04:00
2011-08-08 17:30:09 -04:00
def public_ip_address
ip_address ( :public )
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
def ip_address ( key )
ips = addresses [ key ]
unless ips . nil?
return ips . first
else
return nil
2011-08-03 06:48:44 -04:00
end
2011-08-08 17:30:09 -04:00
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
def private_key_path
@private_key_path || = Fog . credentials [ :private_key_path ]
@private_key_path && = File . expand_path ( @private_key_path )
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
def private_key
@private_key || = private_key_path && File . read ( private_key_path )
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def public_key_path
@public_key_path || = Fog . credentials [ :public_key_path ]
@public_key_path && = File . expand_path ( @public_key_path )
end
2011-08-08 17:22:55 -04:00
2011-08-08 17:30:09 -04:00
def public_key
@public_key || = public_key_path && File . read ( public_key_path )
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
def ssh ( commands )
requires :public_ip_address , :username
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
#requires :password, :private_key
ssh_options = { }
ssh_options [ :password ] = password unless password . nil?
ssh_options [ :key_data ] = [ private_key ] if private_key
ssh_options [ :proxy ] = ssh_proxy unless ssh_proxy . nil?
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
Fog :: SSH . new ( public_ip_address , @username , ssh_options ) . run ( commands )
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def ssh_proxy
proxy = nil
if @connection . uri . ssh_enabled?
relay = connection . uri . host
user_string = " "
user_string = " -l #{ connection . uri . user } " unless connection . uri . user . nil?
proxy = Net :: SSH :: Proxy :: Command . new ( " ssh #{ user_string } " + relay + " nc %h %p " )
return proxy
else
return nil
# This is a direct connection, so we don't need a proxy to be set
2011-08-03 06:48:44 -04:00
end
2011-08-08 17:30:09 -04:00
end
2011-08-08 17:22:55 -04:00
2011-08-08 17:30:09 -04:00
# Transfers a file
def scp ( local_path , remote_path , upload_options = { } )
requires :public_ip_address , :username
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
scp_options = { }
scp_options [ :password ] = password unless self . password . nil?
scp_options [ :key_data ] = [ private_key ] if self . private_key
scp_options [ :proxy ] = ssh_proxy unless self . ssh_proxy . nil?
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
Fog :: SCP . new ( public_ip_address , username , scp_options ) . upload ( local_path , remote_path , upload_options )
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
# Sets up a new key
def setup ( credentials = { } )
requires :public_key , :public_ip_address , :username
require 'multi_json'
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
credentials [ :proxy ] = ssh_proxy unless ssh_proxy . nil?
credentials [ :password ] = password unless self . password . nil?
credentails [ :key_data ] = [ private_key ] if self . private_key
2011-08-08 17:22:55 -04:00
2011-08-08 17:30:09 -04:00
commands = [
%{ mkdir .ssh } ,
# %{passwd -l #{username}}, #Not sure if we need this here
# %{echo "#{MultiJson.encode(attributes)}" >> ~/attributes.json}
]
if public_key
commands << %{ echo " #{ public_key } " >> ~/.ssh/authorized_keys }
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
# wait for domain to be ready
Timeout :: timeout ( 360 ) do
begin
Timeout :: timeout ( 8 ) do
Fog :: SSH . new ( public_ip_address , username , credentials . merge ( :timeout = > 4 ) ) . run ( 'pwd' )
2011-08-03 06:48:44 -04:00
end
2011-08-08 17:30:09 -04:00
rescue Errno :: ECONNREFUSED
sleep ( 2 )
retry
rescue Net :: SSH :: AuthenticationFailed , Timeout :: Error
retry
2011-08-03 06:48:44 -04:00
end
end
2011-08-08 17:30:09 -04:00
Fog :: SSH . new ( public_ip_address , username , credentials ) . run ( commands )
end
2011-08-03 06:48:44 -04:00
2011-08-08 19:31:12 -04:00
# Retrieves the mac address from parsing the XML of the domain
2011-08-08 17:30:09 -04:00
def mac
mac = document ( " domain/devices/interface/mac " , " address " )
return mac
end
2011-08-03 08:28:40 -04:00
2011-08-08 17:30:09 -04:00
def vnc_port
2011-08-08 17:22:55 -04:00
2011-08-08 17:30:09 -04:00
port = document ( " domain/devices/graphics[@type='vnc'] " , " port " )
return port
end
2011-08-08 17:22:55 -04:00
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
private
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def raw
@raw
end
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
def raw = ( new_raw )
@raw = new_raw
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
raw_attributes = {
:uuid = > new_raw . uuid ,
:name = > new_raw . name ,
:memory_size = > new_raw . info . max_mem ,
:cpus = > new_raw . info . nr_virt_cpu ,
:os_type = > new_raw . os_type
}
2011-08-03 06:48:44 -04:00
2011-08-08 17:30:09 -04:00
merge_attributes ( raw_attributes )
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
# finds a value from xml
def document path , attribute = nil
xml = REXML :: Document . new ( xml_desc )
attribute . nil? ? xml . elements [ path ] . text : xml . elements [ path ] . attributes [ attribute ]
2011-06-28 09:47:16 -04:00
end
2011-08-08 17:30:09 -04:00
2011-08-03 06:48:44 -04:00
end
2011-06-28 09:47:16 -04:00
2011-08-08 17:30:09 -04:00
end
2011-08-03 06:48:44 -04:00
end
2011-08-08 17:30:09 -04:00
end