1
0
Fork 0
mirror of https://github.com/fog/fog.git synced 2022-11-09 13:51:43 -05:00
fog--fog/lib/fog/compute/models/libvirt/server.rb

499 lines
15 KiB
Ruby
Raw Normal View History

require 'fog/core/model'
require 'fog/compute/models/libvirt/util'
require 'net/ssh/proxy/command'
require 'rexml/document'
require 'erb'
require 'securerandom'
module Fog
module Compute
class Libvirt
class Server < Fog::Model
include Fog::Compute::LibvirtUtil
identity :uuid
2011-08-08 17:22:55 -04:00
attribute :cpus
attribute :os_type
attribute :memory_size
attribute :name
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 => {
# :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
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
# 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')
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')
end
2011-08-08 17:22:55 -04:00
# We have a template, let's generate some xml for it
if !template_options.nil?
template_defaults={
2011-08-08 17:22:55 -04:00
:cpus => 1,
:memory_size => 256,
:arch => "x86_64",
:os => "hvm",
:domain_type => "kvm",
:name => "fog-#{SecureRandom.random_number*10E14.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/"
}
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]}"
}.merge(template_options2)
validate_template_options(template_options)
if !template_options[:disk_template_name].nil?
2011-08-08 17:22:55 -04:00
# Clone the volume
volume=connection.volumes.all(:name => template_options[:disk_template_name]).first.clone("#{template_options[:disk_name]}")
# This gets passed to the domain to know the path of the disk
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]}",
:type => "#{template_options[:disk_type]}",
:size => "#{template_options[:disk_size]}",
:size_unit => "#{template_options[:disk_size_unit]}",
:allocate => "#{template_options[:disk_allocate]}",
:allocate_unit => "#{template_options[:disk_allocate_unit]}" })
# This gets passed to the domain to know the path of the disk
template_options[:disk_path]=volume.path
end
2011-08-08 17:30:09 -04:00
xml=xml_from_template(template_options)
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)
end
2011-08-08 17:30:09 -04:00
self.raw=domain
end
end
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])
end
2011-08-08 17:30:09 -04:00
end
2011-08-08 17:30:09 -04:00
def xml_from_template(template_options)
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-08 17:30:09 -04:00
def start
requires :raw
2011-08-08 17:30:09 -04:00
unless @raw.active?
begin
@raw.create
true
rescue
false
end
2011-08-08 17:22:55 -04:00
end
2011-08-08 17:30:09 -04:00
end
2011-08-08 17:30:09 -04:00
def destroy(options={ :destroy_volumes => false})
2011-08-08 17:30:09 -04:00
#connection.volumes(name).destroy
requires :raw
if @raw.active?
@raw.destroy
end
2011-08-08 17:30:09 -04:00
@raw.undefine
end
2011-08-08 17:30:09 -04:00
def reboot
requires :raw
2011-08-08 17:30:09 -04:00
@raw.reboot
end
2011-08-08 17:30:09 -04:00
def halt
requires :raw
2011-08-08 17:30:09 -04:00
@raw.halt
end
2011-08-08 17:30:09 -04:00
def poweroff
requires :raw
@raw.destroy
end
2011-08-08 17:30:09 -04:00
def shutdown
requires :raw
2011-08-08 17:30:09 -04:00
@raw.shutdown
end
2011-08-08 17:30:09 -04:00
def resume
requires :raw
2011-08-08 17:30:09 -04:00
@raw.resume
end
2011-08-08 17:30:09 -04:00
def suspend
requires :raw
2011-08-08 17:30:09 -04:00
@raw.suspend
end
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
return state
end
def ready?
state == :running
end
2011-08-08 17:30:09 -04:00
def stop
requires :raw
2011-08-08 17:30:09 -04:00
@raw.shutdown
end
2011-08-08 17:30:09 -04:00
def xml_desc
requires :raw
raw.xml_desc
end
2011-08-08 17:30:09 -04:00
##Note this requires arpwatch to be running
##and chmod o+x /var/lib/arpwatch
# 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
# 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
if @connection.uri.ssh_enabled?
2011-08-08 17:30:09 -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
# 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
#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
# We got a failure executing the command
raise Fog::Errors::Error.new("The command #{ip_command} failed to execute with a clean exit code")
end
2011-08-08 17:30:09 -04:00
else
# 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)
end
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-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-08-08 17:30:09 -04:00
def ip_address(key)
ips=addresses[key]
unless ips.nil?
return ips.first
else
return nil
end
2011-08-08 17:30:09 -04:00
end
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-08-08 17:30:09 -04:00
def private_key
@private_key ||= private_key_path && File.read(private_key_path)
end
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-08-08 17:30:09 -04:00
def ssh(commands)
requires :public_ip_address, :username
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-08 17:30:09 -04:00
Fog::SSH.new(public_ip_address, @username, ssh_options).run(commands)
end
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
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-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-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-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-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-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')
end
2011-08-08 17:30:09 -04:00
rescue Errno::ECONNREFUSED
sleep(2)
retry
rescue Net::SSH::AuthenticationFailed, Timeout::Error
retry
end
end
2011-08-08 17:30:09 -04:00
Fog::SSH.new(public_ip_address, username, credentials).run(commands)
end
# 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-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-08 17:30:09 -04:00
private
2011-08-08 17:30:09 -04:00
def raw
@raw
end
2011-08-08 17:30:09 -04:00
def raw=(new_raw)
@raw = new_raw
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-08 17:30:09 -04:00
merge_attributes(raw_attributes)
end
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]
end
2011-08-08 17:30:09 -04:00
end
2011-08-08 17:30:09 -04:00
end
end
2011-08-08 17:30:09 -04:00
end