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-08 19:54:54 -04:00
identity :id , :aliases = > 'uuid'
2011-08-08 17:22:55 -04:00
2011-08-09 17:03:40 -04:00
attribute :xml
2011-08-03 08:28:40 -04:00
attribute :cpus
attribute :os_type
attribute :memory_size
attribute :name
2011-08-09 17:03:40 -04:00
attribute :arch
attribute :persistent
attribute :domain_type
attribute :uuid
attribute :disk_format_type
attribute :disk_allocation
attribute :disk_capacity
attribute :disk_name
attribute :disk_pool_name
attribute :disk_template_name
attribute :disk_path
attribute :iso_dir
attribute :iso_file
attribute :network_interface_type
attribute :network_nat_network
attribute :network_bridge_name
2011-08-03 06:48:44 -04:00
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 = { } )
2011-08-09 17:03:40 -04:00
2011-08-03 06:48:44 -04:00
self . xml || = nil unless attributes [ :xml ]
2011-08-09 17:03:40 -04:00
self . persistent || = true unless attributes [ :persistent ]
self . cpus || = 1 unless attributes [ :cpus ]
self . memory_size || = 256 unless attributes [ :memory_size ]
self . name || = " fog- #{ SecureRandom . random_number * 10 E14 . to_i . round } " unless attributes [ :name ]
self . os_type || = " hvm " unless attributes [ :os_type ]
self . arch || = " x86_64 " unless attributes [ :arch ]
self . domain_type || = " kvm " unless attributes [ :domain_type ]
self . iso_file || = nil unless attributes [ :iso_file ]
self . iso_dir || = " /var/lib/libvirt/images " unless attributes [ :iso_dir ]
self . disk_format_type || = nil unless attributes [ :disk_format_type ]
self . disk_capacity || = nil unless attributes [ :disk_capacity ]
self . disk_allocation || = nil unless attributes [ :disk_allocation ]
self . disk_name || = nil unless attributes [ :disk_name ]
self . disk_pool_name || = nil unless attributes [ :disk_pool_name ]
self . disk_template_name || = nil unless attributes [ :disk_template_name ]
self . network_interface_type || = " nat " unless attributes [ :network_interface_type ]
self . network_nat_network || = " default " unless attributes [ :network_nat_network ]
self . network_bridge_name || = " br0 " unless attributes [ :network_bridge_name ]
2011-08-03 06:48:44 -04:00
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
2011-08-09 17:03:40 -04:00
raise Fog :: Errors :: Error . new ( 'Resaving an existing server may create a duplicate' ) if uuid
2011-08-08 17:22:55 -04:00
2011-08-09 17:03:40 -04:00
validate_template_options
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
xml = xml_from_template if xml . nil?
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
create_or_clone_volume
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
xml = xml_from_template
2011-08-08 17:30:09 -04:00
2011-08-09 17:03:40 -04:00
# We either now have xml provided by the user or generated by the template
begin
if ! xml . nil?
domain = nil
if self . persistent
domain = connection . define_domain_xml ( xml )
else
domain = connection . create_domain_xml ( xml )
end
self . raw = domain
end
rescue
raise Fog :: Errors :: Error . new ( " Error saving the server: #{ $! } " )
end
end
2011-08-08 17:30:09 -04:00
2011-08-09 17:03:40 -04:00
def create_or_clone_volume
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
volume_options = Hash . new
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
unless self . disk_name . nil?
volume_options [ :name ] = self . disk_name
else
extension = self . disk_format_type . nil? ? " img " : self . disk_format_type
volume_name = " #{ self . name } . #{ extension } "
volume_options [ :name ] = volume_name
end
2011-08-03 08:28:40 -04:00
2011-08-09 17:03:40 -04:00
# Check if a disk template was specified
unless self . disk_template_name . nil?
2011-08-03 08:28:40 -04:00
2011-08-09 17:03:40 -04:00
template_volumes = connection . volumes . all ( :name = > self . disk_template_name )
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
raise Fog :: Errors :: Error . new ( " Template #{ self . disk_template_name } not found " ) unless template_volumes . length == 1
2011-08-03 08:28:40 -04:00
2011-08-09 17:03:40 -04:00
volume = template_volumes . first . clone ( " #{ volume_options [ :name ] } " )
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
# This gets passed to the domain to know the path of the disk
self . disk_path = volume . path
2011-08-03 06:48:44 -04:00
2011-08-09 17:03:40 -04:00
else
# If no template volume was given, let's create our own volume
2011-08-08 17:30:09 -04:00
2011-08-09 17:03:40 -04:00
volume_options [ :format_type ] = self . disk_format_type unless self . disk_format_type . nil?
volume_options [ :capacity ] = self . disk_capacity unless self . disk_capacity . nil?
volume_options [ :allocation ] = self . disk_allocation unless self . disk_allocation . nil?
2011-06-28 09:47:16 -04:00
2011-08-09 17:03:40 -04:00
begin
volume = connection . volumes . create ( volume_options )
self . disk_path = volume . path
rescue
raise Fog :: Errors :: Error . new ( " Error creating the volume : #{ $! } " )
2011-08-03 08:28:40 -04:00
end
2011-08-09 17:03:40 -04:00
2011-08-08 17:30:09 -04:00
end
end
2011-06-28 09:47:16 -04:00
2011-08-09 17:03:40 -04:00
def validate_template_options
unless self . network_interface_type . nil?
raise Fog :: Errors :: Error . new ( " #{ self . network_interface_type } is not a supported interface type " ) unless [ " nat " , " bridge " ] . include? ( self . network_interface_type )
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-09 17:03:40 -04:00
def xml_from_template
template_options = {
:cpus = > self . cpus ,
:memory_size = > self . memory_size ,
:domain_type = > self . domain_type ,
:name = > self . name ,
:iso_file = > self . iso_file ,
:iso_dir = > self . iso_dir ,
:os_type = > self . os_type ,
:arch = > self . arch ,
:disk_path = > self . disk_path ,
:network_interface_type = > self . network_interface_type ,
:network_nat_network = > self . network_nat_network ,
:network_bridge_name = > self . network_bridge_name
}
2011-08-08 17:30:09 -04:00
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 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
2011-08-09 09:22:35 -04:00
ip_command_global = @connection . ip_command . nil? ? " grep #{ mac } /var/log/arpwatch.log |cut -d ':' -f 4-| cut -d ' ' -f 4 " : @connection . ip_command
ip_command = options [ :ip_command ] . nil? ? ip_command_global : options [ :ip_command ]
2011-08-08 19:31:12 -04:00
2011-08-08 19:54:54 -04:00
ip_address = nil
2011-08-08 17:30:09 -04:00
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
2011-08-08 19:54:54 -04:00
raise Fog :: Errors :: Error . new ( " Connection was refused to host #{ host } to retrieve the ip_address for #{ mac } " )
2011-08-08 19:31:12 -04:00
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
2011-08-08 19:54:54 -04:00
#TODO: We need to check if that ip_address is still valid for that mac-address
2011-08-08 19:31:12 -04:00
# Check for a clean exit code
2011-08-08 17:30:09 -04:00
if result . first . status == 0
2011-08-08 19:54:54 -04:00
ip_address = 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
2011-08-08 19:54:54 -04:00
# Execute the ip_command locally
2011-08-08 19:31:12 -04:00
IO . popen ( " #{ ip_command } " ) do | p |
p . each_line do | l |
2011-08-09 17:03:40 -04:00
ip_address = + l
2011-08-08 19:31:12 -04:00
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-06-28 09:47:16 -04:00
2011-08-03 06:48:44 -04:00
end
2011-08-08 19:31:12 -04:00
2011-08-08 19:54:54 -04:00
if ip_address == " "
2011-08-08 19:31:12 -04:00
#The grep didn't find an ip address result"
2011-08-08 19:54:54 -04:00
ip_address = nil
2011-08-08 19:31:12 -04:00
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
2011-08-08 19:54:54 -04:00
unless ip_address =~ / ^( \ d{1,3} \ .){3} \ d{1,3}$ /
2011-08-08 19:31:12 -04:00
raise Fog :: Errors :: Error . new (
" The command #{ ip_command } failed to execute with a clean exit code \n " +
2011-08-08 19:54:54 -04:00
" Result was: #{ ip_address } \n "
2011-08-09 17:03:40 -04:00
)
2011-08-08 19:31:12 -04:00
end
end
2011-08-08 19:54:54 -04:00
return { :public = > [ ip_address ] , :private = > [ ip_address ] }
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_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 = {
2011-08-08 19:54:54 -04:00
:id = > new_raw . uuid ,
2011-08-08 17:30:09 -04:00
: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