Project

General

Profile

Download (9.31 KB) Statistics
| Branch: | Tag: | Revision:
require 'foreman/exception'
require 'uri'

module Foreman::Model
class Ovirt < ComputeResource

validates :url, :format => { :with => URI.regexp }
validates :user, :password, :presence => true
before_create :update_public_key

alias_attribute :datacenter, :uuid

def self.model_name
ComputeResource.model_name
end

def capabilities
[:build, :image]
end

def supports_update?
true
end

def provided_attributes
super.merge({:mac => :mac})
end

#FIXME
def max_cpu_count
8
end

def max_memory
16*1024*1024*1024
end

def quotas
client.quotas
end

def ovirt_quota=(ovirt_quota_id)
self.attrs[:ovirt_quota_id] = ovirt_quota_id
end

def ovirt_quota
self.attrs[:ovirt_quota_id]
end

def templates(opts={})
client.templates
end

def available_images
templates
end

def template(id)
compute = client.templates.get(id) || raise(ActiveRecord::RecordNotFound)
compute.interfaces
compute.volumes
compute
end

def clusters
client.clusters
end

# Check if HTTPS is mandatory, since rest_client will fail with a POST
def test_https_required
RestClient.post url, {} if URI(url).scheme == 'http'
true
rescue => e
case e.message
when /406/
true
else
raise e
end
end
private :test_https_required

def test_connection options = {}
super
if errors[:url].empty? and errors[:username].empty? and errors[:password].empty?
update_public_key options
datacenters && test_https_required
end
rescue => e
case e.message
when /404/
errors[:url] << e.message
when /302/
errors[:url] << 'HTTPS URL is required for API access'
when /401/
errors[:user] << e.message
else
errors[:base] << e.message
end
end

def datacenters(options={})
client.datacenters(options).map { |dc| [dc[:name], dc[:id]] }
end

def networks(opts ={})
if opts[:cluster_id]
client.clusters.get(opts[:cluster_id]).networks
else
[]
end
end

def available_clusters
clusters
end

def available_networks(cluster_id=nil)
raise ::Foreman::Exception.new(N_('Cluster ID is required to list available networks')) if cluster_id.nil?
cluster_networks = networks({:cluster_id => cluster_id})
end

def available_storage_domains
storage_domains
end

def storage_domains(opts ={})
client.storage_domains({:role => 'data'}.merge(opts))
end

def start_vm(uuid)
find_vm_by_uuid(uuid).start(:blocking => true)
end

def create_vm(args = {})
#ovirt doesn't accept '.' in vm name.
args[:name] = args[:name].parameterize
if (image_id = args[:image_id])
args.merge!({:template => image_id})
end
vm = super({ :first_boot_dev => 'network', :quota => ovirt_quota }.merge(args))
begin
create_interfaces(vm, args[:interfaces_attributes])
create_volumes(vm, args[:volumes_attributes])
rescue => e
destroy_vm vm.id
raise e
end
vm
end

def new_vm(attr={})
vm = super
interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
interfaces.map{ |i| vm.interfaces << new_interface(i)}
volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
volumes.map{ |v| vm.volumes << new_volume(v)}
vm
end

def new_interface(attr={})
Fog::Compute::Ovirt::Interface.new(attr)
end

def new_volume(attr={})
Fog::Compute::Ovirt::Volume.new(attr)
end

def save_vm(uuid, attr)
vm = find_vm_by_uuid(uuid)
vm.attributes.merge!(attr.symbolize_keys)
update_interfaces(vm, attr[:interfaces_attributes])
update_volumes(vm, attr[:volumes_attributes])
vm.interfaces
vm.volumes
vm.save
end

def destroy_vm(uuid)
begin
find_vm_by_uuid(uuid).destroy
rescue OVIRT::OvirtException => e
#404 error are ignored on delete.
raise e unless e.message =~ /404/
end
true
end

def console(uuid)
vm = find_vm_by_uuid(uuid)
raise "VM is not running!" if vm.status == "down"
if vm.display[:type] =~ /spice/i
xpi_opts = {:name => vm.name, :address => vm.display[:address], :secure_port => vm.display[:secure_port], :ca_cert => public_key, :subject => vm.display[:subject] }
opts = if vm.display[:secure_port]
{ :host_port => vm.display[:secure_port], :ssl_target => true }
else
{ :host_port => vm.display[:port] }
end
WsProxy.start(opts.merge(:host => vm.display[:address], :password => vm.ticket)).merge(xpi_opts).merge(:type => 'spice')
else
WsProxy.start(:host => vm.display[:address], :host_port => vm.display[:port], :password => vm.ticket).merge(:name => vm.name, :type => 'vnc')
end
end

def update_required?(old_attrs, new_attrs)
return true if super(old_attrs, new_attrs)

new_attrs[:interfaces_attributes].each do |key, interface|
return true if (interface[:id].blank? || interface[:_delete] == '1') && key != 'new_interfaces' #ignore the template
end if new_attrs[:interfaces_attributes]

new_attrs[:volumes_attributes].each do |key, volume|
return true if (volume[:id].blank? || volume[:_delete] == '1') && key != 'new_volumes' #ignore the template
end if new_attrs[:volumes_attributes]

false
end

def associated_host(vm)
Host.authorized(:view_hosts, Host).where(:mac => vm.mac).first
end

def self.provider_friendly_name
"oVirt"
end

def public_key
attrs[:public_key]
end

def public_key= key
attrs[:public_key] = key
end

protected

def bootstrap(args)
client.servers.bootstrap vm_instance_defaults.merge(args.to_hash)
rescue Fog::Errors::Error => e
errors.add(:base, e.to_s)
false
end

def client
return @client if @client
client = ::Fog::Compute.new(
:provider => "ovirt",
:ovirt_username => user,
:ovirt_password => password,
:ovirt_url => url,
:ovirt_datacenter => uuid,
:ovirt_ca_cert_store => ca_cert_store(public_key)
)
client.datacenters
@client = client
rescue => e
if e.message =~ /SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed/
raise Foreman::FingerprintException.new(
N_("The remote system presented a public key signed by an unidentified certificate authority. If you are sure the remote system is authentic, go to the compute resource edit page, press the 'Test Connection' or 'Load Datacenters' button and submit"),
ca_cert)
else
raise e
end
end

def update_public_key options ={}
return unless public_key.blank? || options[:force]
client
rescue Foreman::FingerprintException => e
self.public_key = e.fingerprint
end

def api_version
@api_version ||= client.api_version
end

def ca_cert_store cert
return if cert.blank?
OpenSSL::X509::Store.new.add_cert(OpenSSL::X509::Certificate.new(cert))
rescue => e
raise _("Failed to create X509 certificate, error: %s" % e.message)
end

def ca_cert
ca_url = URI.parse(url)
ca_url.path = "/ca.crt"
http = Net::HTTP.new(ca_url.host, ca_url.port)
http.use_ssl = (ca_url.scheme == 'https')
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(ca_url.path)
http.request(request).body
end

private
def create_interfaces(vm, attrs)
#first remove all existing interfaces
vm.interfaces.each do |interface|
#The blocking true is a work-around for ovirt bug, it should be removed.
vm.destroy_interface(:id => interface.id, :blocking => true)
end if vm.interfaces
#add interfaces
interfaces = nested_attributes_for :interfaces, attrs
interfaces.map{ |i| vm.add_interface(i)}
vm.interfaces.reload
end

def create_volumes(vm, attrs)
#add volumes
volumes = nested_attributes_for :volumes, attrs
#The blocking true is a work-around for ovirt bug fixed in ovirt version 3.1.
volumes.map{ |vol| vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 3.1}.merge(vol)) if vol[:id].blank?}
vm.volumes.reload
end

def update_interfaces(vm, attrs)
interfaces = nested_attributes_for :interfaces, attrs
interfaces.each do |interface|
vm.destroy_interface(:id => interface[:id]) if interface[:_delete] == '1' && interface[:id]
vm.add_interface(interface) if interface[:id].blank?
end
end

def update_volumes(vm, attrs)
volumes = nested_attributes_for :volumes, attrs
volumes.each do |volume|
vm.destroy_volume(:id => volume[:id], :blocking => api_version.to_f < 3.1) if volume[:_delete] == '1' && volume[:id].present?
vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 3.1}.merge(volume)) if volume[:id].blank?
end
end

end
end
(5-5/7)