Project

General

Profile

Download (18.5 KB) Statistics
| Branch: | Tag: | Revision:
5f45920e Amos Benari
require 'foreman/exception'
85ed3c3f Dominic Cleal
require 'uri'

334d0359 Amos Benari
module Foreman::Model
class Ovirt < ComputeResource
27e31470 Michael Moll
validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true,
18439248 Swapnil Abnave
:url_schema => ['http', 'https']
f2c78d4a Joseph Magen
validates :user, :password, :presence => true
223383f5 Ori Rabin
after_validation :connect, :update_available_operating_systems unless Rails.env.test?
7e031001 Ohad Levy
8a0ffcfa Joseph Magen
alias_attribute :datacenter, :uuid

9034d78e Baptiste
delegate :clusters, :quotas, :templates, :instance_types, :to => :client
9a9ec5b1 Daniel Lobato
bd95cda6 Dominic Cleal
def self.available?
Fog::Compute.providers.include?(:ovirt)
end

334d0359 Amos Benari
def self.model_name
ComputeResource.model_name
end

5dec3a52 karimb
def user_data_supported?
true
end

5e74b082 Ivan Nečas
def host_compute_attrs(host)
super.tap do |attrs|
attrs[:os] = { :type => determine_os_type(host) } if supports_operating_systems?
end
end

dd42df0a Ohad Levy
def capabilities
3cf9d65f Marek Hulán
[:build, :image, :new_volume]
dd42df0a Ohad Levy
end

4269abbd Tomas Strachota
def find_vm_by_uuid(uuid)
super
6fb097f9 Ori Rabin
rescue Fog::Ovirt::Errors::OvirtEngineError
4269abbd Tomas Strachota
raise(ActiveRecord::RecordNotFound)
end

a6a6b703 David Swift
def supports_update?
true
end

5e74b082 Ivan Nečas
def supports_operating_systems?
62548768 Ori Rabin
if client.respond_to?(:operating_systems)
f97a2fb8 Ivan Nečas
unless self.attrs.key?(:available_operating_systems)
update_available_operating_systems
save
end
self.attrs[:available_operating_systems] != :unsupported
else
false
end
edb83962 Marek Hulan
rescue Foreman::FingerprintException
logger.info "Unable to verify OS capabilities, SSL certificate verification failed"
false
5e74b082 Ivan Nečas
end

def determine_os_type(host)
return nil unless host
ret = "other_linux"
return ret unless host.operatingsystem
os_name = os_name_mapping(host)
arch_name = arch_name_mapping(host)

f97a2fb8 Ivan Nečas
best_match = available_operating_systems.select { |os| os[:name].present? }.max_by do |os|
5e74b082 Ivan Nečas
rating = 0.0
f97a2fb8 Ivan Nečas
if os[:name].include?(os_name)
5e74b082 Ivan Nečas
rating += 100
9d43fc71 Michael Moll
rating += (1.0 / os[:name].length) # prefer the shorter names a bit in case we have not found more important some specifics
f97a2fb8 Ivan Nečas
rating += 10 if os[:name].include?("#{os_name}_#{host.operatingsystem.major}")
rating += 10 if arch_name && os[:name].include?(arch_name)
5e74b082 Ivan Nečas
end
rating
end

f97a2fb8 Ivan Nečas
best_match[:name] if best_match
5e74b082 Ivan Nečas
end

def available_operating_systems
f97a2fb8 Ivan Nečas
if attrs.key?(:available_operating_systems)
attrs[:available_operating_systems]
5e74b082 Ivan Nečas
else
raise Foreman::Exception.new("Listing operating systems is not supported by the current version")
end
end

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

334d0359 Amos Benari
def max_cpu_count
04ae1269 Shira Maximov
16
end

def max_socket_count
16
334d0359 Amos Benari
end

def max_memory
eabbbeb4 Marek Hulan
16.gigabytes
334d0359 Amos Benari
end

dc2efa60 Ivan Nečas
def use_v4=(value)
value = case value
when true, '1'
true
else
false
end
self.attrs[:ovirt_use_v4] = value
end

def use_v4
self.attrs[:ovirt_use_v4] || false
end
alias_method :use_v4?, :use_v4

180d7f43 Jason Montleon
def ovirt_quota=(ovirt_quota_id)
self.attrs[:ovirt_quota_id] = ovirt_quota_id
end

def ovirt_quota
20b2ec5b Michael Moll
self.attrs[:ovirt_quota_id].presence
180d7f43 Jason Montleon
end

d6026572 Amos Benari
def available_images
templates
end

c6e02bd3 Joseph Magen
def template(id)
99527500 Jimmi Dyson
compute = client.templates.get(id) || raise(ActiveRecord::RecordNotFound)
compute.interfaces
compute.volumes
compute
334d0359 Amos Benari
end

9034d78e Baptiste
def instance_type(id)
client.instance_types.get(id) || raise(ActiveRecord::RecordNotFound)
end

85ed3c3f Dominic Cleal
# 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

5f029ed6 Daniel Lobato
def test_connection(options = {})
334d0359 Amos Benari
super
223383f5 Ori Rabin
connect(options)
end

def connect(options = {})
return unless connection_properties_valid?

update_public_key options
datacenters && test_https_required
334d0359 Amos Benari
rescue => e
case e.message
when /404/
errors[:url] << e.message
85ed3c3f Dominic Cleal
when /302/
113ec278 Ido Kanner
errors[:url] << _('HTTPS URL is required for API access')
334d0359 Amos Benari
when /401/
errors[:user] << e.message
else
errors[:base] << e.message
end
end

223383f5 Ori Rabin
def connection_properties_valid?
errors[:url].empty? && errors[:username].empty? && errors[:password].empty?
end

4f7a4d0b David Davis
def datacenters(options = {})
334d0359 Amos Benari
client.datacenters(options).map { |dc| [dc[:name], dc[:id]] }
end

99069f53 Ivan Nečas
def editable_network_interfaces?
# we can't decide whether the networks are available when we
# don't know the cluster_id, assuming it's possible
true
end

4f7a4d0b David Davis
def networks(opts = {})
334d0359 Amos Benari
if opts[:cluster_id]
client.clusters.get(opts[:cluster_id]).networks
else
[]
end
end

ea49a05e James Netherton
def available_clusters
clusters
end

4f7a4d0b David Davis
def available_networks(cluster_id = nil)
e4d88172 Greg Petras
raise ::Foreman::Exception.new(N_('Cluster ID is required to list available networks')) if cluster_id.nil?
2312cccf Daniel Lobato
networks({:cluster_id => cluster_id})
ea49a05e James Netherton
end

4f7a4d0b David Davis
def available_storage_domains(storage_domain = nil)
ea49a05e James Netherton
storage_domains
end

4f7a4d0b David Davis
def storage_domains(opts = {})
96ede451 Amos Benari
client.storage_domains({:role => 'data'}.merge(opts))
end

def start_vm(uuid)
5dec3a52 karimb
vm = find_vm_by_uuid(uuid)
if vm.comment.include? "cloud-config"
d3cd2536 Ivan Nečas
vm.start_with_cloudinit(:blocking => true, :user_data => vm.comment, :use_custom_script => true)
5dec3a52 karimb
vm.comment = ''
vm.save
else
vm.start(:blocking => true)
end
end

def start_with_cloudinit(uuid, user_data = nil)
d3cd2536 Ivan Nečas
find_vm_by_uuid(uuid).start_with_cloudinit(:blocking => true, :user_data => user_data, :use_custom_script => true)
334d0359 Amos Benari
end

9034d78e Baptiste
def sanitize_inherited_vm_attributes(args)
# Cleanup memory an cores values if template and/or instance type provided when VM values are
# * Blank values for these attributes, because oVirt will fail if empty values are present in VM definition
# * Provided but identical to values present in the template or instance type
# Instance type values take precedence on templates values
85021506 Michael Moll
if args[:template].present?
9034d78e Baptiste
template = template(args[:template])
85021506 Michael Moll
cores = template.cores.to_i if template.cores.present?
memory = template.memory.to_i if template.memory.present?
9034d78e Baptiste
end
85021506 Michael Moll
if args[:instance_type].present?
9034d78e Baptiste
instance_type = instance_type(args[:instance_type])
85021506 Michael Moll
cores = instance_type.cores.to_i if instance_type.cores.present?
memory = instance_type.memory.to_i if instance_type.memory.present?
9034d78e Baptiste
end
args.delete(:cores) if (args[:cores].blank? && cores) || (args[:cores].to_i == cores)
args.delete(:memory) if (args[:memory].blank? && memory) || (args[:memory].to_i == memory)
end

96ede451 Amos Benari
def create_vm(args = {})
5dec3a52 karimb
args[:comment] = args[:user_data] if args[:user_data]
9034d78e Baptiste
args[:template] = args[:image_id] if args[:image_id]
sanitize_inherited_vm_attributes(args)
7d6f25b5 Ori Rabin
preallocate_disks(args) if args[:volumes_attributes].present?

180d7f43 Jason Montleon
vm = super({ :first_boot_dev => 'network', :quota => ovirt_quota }.merge(args))
7d6f25b5 Ori Rabin
334d0359 Amos Benari
begin
96ede451 Amos Benari
create_interfaces(vm, args[:interfaces_attributes])
create_volumes(vm, args[:volumes_attributes])
334d0359 Amos Benari
rescue => e
destroy_vm vm.id
raise e
end
vm
end

7d6f25b5 Ori Rabin
def preallocate_disks(args)
change_allocation_volumes = args[:volumes_attributes].values.select{ |x| x[:preallocate] == '1' }
if args[:template].present? && change_allocation_volumes.present?
disks = change_allocation_volumes.map do |volume|
{ :id => volume[:id], :sparse => 'false', :format => 'raw', :storagedomain => volume[:storage_domain] }
end
args.merge!(:clone => true, :disks => disks)
end
end

54c83f7a Shira Maximov
def vm_instance_defaults
super.merge(
:memory => 1024.megabytes,
:cores => '1',
:sockets => '1'
)
end

4f7a4d0b David Davis
def new_vm(attr = {})
95be0963 Amos Benari
vm = super
96ede451 Amos Benari
interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
interfaces.map{ |i| vm.interfaces << new_interface(i)}
volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
545716c4 Marek Hulan
volumes.map { |v| vm.volumes << new_volume(v) }
334d0359 Amos Benari
vm
end

4f7a4d0b David Davis
def new_interface(attr = {})
334d0359 Amos Benari
Fog::Compute::Ovirt::Interface.new(attr)
end

4f7a4d0b David Davis
def new_volume(attr = {})
545716c4 Marek Hulan
set_preallocated_attributes!(attr, attr[:preallocate])
96ede451 Amos Benari
Fog::Compute::Ovirt::Volume.new(attr)
end

def save_vm(uuid, attr)
334d0359 Amos Benari
vm = find_vm_by_uuid(uuid)
3059cea1 Tom Caspy
vm.attributes.merge!(attr.symbolize_keys).deep_symbolize_keys
334d0359 Amos Benari
update_interfaces(vm, attr[:interfaces_attributes])
96ede451 Amos Benari
update_volumes(vm, attr[:volumes_attributes])
334d0359 Amos Benari
vm.interfaces
96ede451 Amos Benari
vm.volumes
334d0359 Amos Benari
vm.save
end

96ede451 Amos Benari
def destroy_vm(uuid)
736cb75f Ivan Necas
find_vm_by_uuid(uuid).destroy
rescue ActiveRecord::RecordNotFound
334d0359 Amos Benari
true
end

fbd765f1 Tomer Brisker
def supports_vms_pagination?
true
end

def parse_vms_list_params(params)
77e12c85 Tomer Brisker
max = (params['length'] || 10).to_i
fbd765f1 Tomer Brisker
{
77e12c85 Tomer Brisker
:search => params['search']['value'] || '',
fbd765f1 Tomer Brisker
:max => max,
9d43fc71 Michael Moll
:page => (params['start'].to_i / max) + 1,
fbd765f1 Tomer Brisker
:without_details => true
}
end

b43fa642 Ohad Levy
def console(uuid)
vm = find_vm_by_uuid(uuid)
raise "VM is not running!" if vm.status == "down"
057d4974 Amos Benari
if vm.display[:type] =~ /spice/i
5f45920e Amos Benari
xpi_opts = {:name => vm.name, :address => vm.display[:address], :secure_port => vm.display[:secure_port], :ca_cert => public_key, :subject => vm.display[:subject] }
8ffd9aee Ohad Levy
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')
057d4974 Amos Benari
else
8ffd9aee Ohad Levy
WsProxy.start(:host => vm.display[:address], :host_port => vm.display[:port], :password => vm.ticket).merge(:name => vm.name, :type => 'vnc')
057d4974 Amos Benari
end
b43fa642 Ohad Levy
end

0e5696d3 Amos Benari
def update_required?(old_attrs, new_attrs)
return true if super(old_attrs, new_attrs)

b03dcd1b Michael Moll
new_attrs[:interfaces_attributes]&.each do |key, interface|
68388bc2 Michael Moll
return true if (interface[:id].blank? || interface[:_delete] == '1') && key != 'new_interfaces' # ignore the template
b03dcd1b Michael Moll
end
0e5696d3 Amos Benari
b03dcd1b Michael Moll
new_attrs[:volumes_attributes]&.each do |key, volume|
68388bc2 Michael Moll
return true if (volume[:id].blank? || volume[:_delete] == '1') && key != 'new_volumes' # ignore the template
b03dcd1b Michael Moll
end
0e5696d3 Amos Benari
false
end

805358df Jason Montleon
def associated_host(vm)
81a02cde Tom Caspy
associate_by("mac", vm.interfaces.map(&:mac))
805358df Jason Montleon
end

ddce3dc1 Amos Benari
def self.provider_friendly_name
2ebd2f22 Joseph Magen
"oVirt"
end

5f45920e Amos Benari
def public_key
attrs[:public_key]
end

5f029ed6 Daniel Lobato
def public_key=(key)
5f45920e Amos Benari
attrs[:public_key] = key
end

56de025f Tomas Strachota
def normalize_vm_attrs(vm_attrs)
normalized = slice_vm_attributes(vm_attrs, ['cores', 'interfaces_attributes', 'memory'])
normalized['cluster_id'] = vm_attrs['cluster']
normalized['cluster_name'] = self.clusters.detect { |c| c.id == normalized['cluster_id'] }.try(:name)

normalized['template_id'] = vm_attrs['template']
normalized['template_name'] = self.templates.detect { |t| t.id == normalized['template_id'] }.try(:name)

cluster_networks = self.networks(:cluster_id => normalized['cluster_id'])

interface_attrs = vm_attrs['interfaces_attributes'] || {}
normalized['interfaces_attributes'] = interface_attrs.inject({}) do |interfaces, (key, nic)|
interfaces.update(key => { 'name' => nic['name'],
'network_id' => nic['network'],
'network_name' => cluster_networks.detect { |n| n.id == nic['network'] }.try(:name)
})
end

volume_attrs = vm_attrs['volumes_attributes'] || {}
normalized['volumes_attributes'] = volume_attrs.inject({}) do |volumes, (key, vol)|
volumes.update(key => { 'size' => memory_gb_to_bytes(vol['size_gb']).to_s,
'storage_domain_id' => vol['storage_domain'],
'storage_domain_name' => storage_domains.detect { |d| d.id == vol['storage_domain'] }.try(:name),
'preallocate' => to_bool(vol['preallocate']),
'bootable' => to_bool(vol['bootable'])
})
end

normalized
end

334d0359 Amos Benari
protected

96ede451 Amos Benari
def bootstrap(args)
408774ae Dominic Cleal
client.servers.bootstrap vm_instance_defaults.merge(args.to_h)
334d0359 Amos Benari
rescue Fog::Errors::Error => e
01e78260 Ivan Nečas
Foreman::Logging.exception("Failed to bootstrap vm", e)
334d0359 Amos Benari
errors.add(:base, e.to_s)
false
end

def client
5f45920e Amos Benari
return @client if @client
client = ::Fog::Compute.new(
b7d400f6 Michael Moll
:provider => "ovirt",
:ovirt_username => user,
:ovirt_password => password,
:ovirt_url => url,
:ovirt_datacenter => uuid,
dc2efa60 Ivan Nečas
:ovirt_ca_cert_store => ca_cert_store(public_key),
:public_key => public_key,
:api_version => use_v4? ? 'v4' : 'v3'
334d0359 Amos Benari
)
5f45920e Amos Benari
client.datacenters
@client = client
rescue => e
dc2efa60 Ivan Nečas
if e.message =~ /SSL_connect.*certificate verify failed/ ||
e.message =~ /Peer certificate cannot be authenticated with given CA certificates/
5f45920e Amos Benari
raise Foreman::FingerprintException.new(
b7d400f6 Michael Moll
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
)
5f45920e Amos Benari
else
raise e
end
end

5f029ed6 Daniel Lobato
def update_public_key(options = {})
5f45920e Amos Benari
return unless public_key.blank? || options[:force]
client
rescue Foreman::FingerprintException => e
e944a1b2 Lukas Zapletal
self.public_key = e.fingerprint if self.public_key.blank?
334d0359 Amos Benari
end

55f8636a Amos Benari
def api_version
5f45920e Amos Benari
@api_version ||= client.api_version
end

4c351621 Ori Rabin
def ca_cert_store(certs)
return if certs.blank?
store = OpenSSL::X509::Store.new
certs.split(/(?=-----BEGIN)/).each do |cert|
store.add_cert(OpenSSL::X509::Certificate.new(cert))
end
store
5f45920e Amos Benari
rescue => e
raise _("Failed to create X509 certificate, error: %s" % e.message)
55f8636a Amos Benari
end

bf416ab2 Lukas Zapletal
def fetch_unverified(path, query = '')
057d4974 Amos Benari
ca_url = URI.parse(url)
bf416ab2 Lukas Zapletal
ca_url.path = path
ca_url.query = query
6a85948c Jimmi Dyson
http = Net::HTTP.new(ca_url.host, ca_url.port)
http.use_ssl = (ca_url.scheme == 'https')
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
bf416ab2 Lukas Zapletal
request = Net::HTTP::Get.new(ca_url)
4a9179a0 Marek Hulan
response = http.request(request)
# response might be 404 or some other normal code,
# that would not trigger any exception so we rather check what kind of response we got
response.is_a?(Net::HTTPSuccess) ? response.body : nil
bf416ab2 Lukas Zapletal
rescue => e
Foreman::Logging.exception("Unable to fetch CA certificate on path #{path}: #{e}", e)
nil
end

def ca_cert
fetch_unverified("/ovirt-engine/services/pki-resource", "resource=ca-certificate&format=X509-PEM-CA") || fetch_unverified("/ca.crt")
057d4974 Amos Benari
end

334d0359 Amos Benari
private
abd8f1d1 Daniel Lobato
f97a2fb8 Ivan Nečas
def update_available_operating_systems
223383f5 Ori Rabin
return false if errors.any?
62548768 Ori Rabin
ovirt_operating_systems = client.operating_systems if client.respond_to?(:operating_systems)

f97a2fb8 Ivan Nečas
attrs[:available_operating_systems] = ovirt_operating_systems.map do |os|
{ :id => os.id, :name => os.name, :href => os.href }
end
edb83962 Marek Hulan
rescue Foreman::FingerprintException
logger.info "Unable to verify OS capabilities, SSL certificate verification failed"
true
6fb097f9 Ori Rabin
rescue Fog::Ovirt::Errors::OvirtEngineError => e
f97a2fb8 Ivan Nečas
if e.message =~ /404/
attrs[:available_operating_systems] ||= :unsupported
else
raise e
end
end

5e74b082 Ivan Nečas
def os_name_mapping(host)
9d43fc71 Michael Moll
(host.operatingsystem.name =~ /redhat|centos/i) ? 'rhel' : host.operatingsystem.name.downcase
5e74b082 Ivan Nečas
end

def arch_name_mapping(host)
da9865b8 Michael Moll
(host.architecture.name == 'x86_64') ? 'x64' : host.architecture.name.downcase if host.architecture
5e74b082 Ivan Nečas
end

12a32535 Baptiste
def default_iface_name(interfaces)
nic_name_num = 1
name_blacklist = interfaces.map{ |i| i[:name]}.reject{|n| n.blank?}
nic_name_num += 1 while name_blacklist.include?("nic#{nic_name_num}")
"nic#{nic_name_num}"
end

96ede451 Amos Benari
def create_interfaces(vm, attrs)
68388bc2 Michael Moll
# first remove all existing interfaces
b03dcd1b Michael Moll
vm.interfaces&.each do |interface|
68388bc2 Michael Moll
# The blocking true is a work-around for ovirt bug, it should be removed.
334d0359 Amos Benari
vm.destroy_interface(:id => interface.id, :blocking => true)
b03dcd1b Michael Moll
end
68388bc2 Michael Moll
# add interfaces
96ede451 Amos Benari
interfaces = nested_attributes_for :interfaces, attrs
12a32535 Baptiste
interfaces.map do |interface|
interface[:name] = default_iface_name(interfaces) if interface[:name].empty?
vm.add_interface(interface)
end
334d0359 Amos Benari
vm.interfaces.reload
end

96ede451 Amos Benari
def create_volumes(vm, attrs)
68388bc2 Michael Moll
# add volumes
96ede451 Amos Benari
volumes = nested_attributes_for :volumes, attrs
2bf991a8 Leon Strong
volumes.map do |vol|
545716c4 Marek Hulan
set_preallocated_attributes!(vol, vol[:preallocate])
68388bc2 Michael Moll
# The blocking true is a work-around for ovirt bug fixed in ovirt version 3.1.
2bf991a8 Leon Strong
vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 3.1}.merge(vol)) if vol[:id].blank?
end
96ede451 Amos Benari
vm.volumes.reload
end

545716c4 Marek Hulan
def set_preallocated_attributes!(volume_attributes, preallocate)
if preallocate == '1'
volume_attributes[:sparse] = 'false'
volume_attributes[:format] = 'raw'
else
volume_attributes[:sparse] = 'true'
end
end

334d0359 Amos Benari
def update_interfaces(vm, attrs)
2a08c26b Amos Benari
interfaces = nested_attributes_for :interfaces, attrs
interfaces.each do |interface|
afe02d30 Daniel Lobato
vm.destroy_interface(:id => interface[:id]) if interface[:_delete] == '1' && interface[:id]
12a32535 Baptiste
if interface[:id].blank?
interface[:name] = default_iface_name(interfaces) if interface[:name].empty?
vm.add_interface(interface)
end
2a08c26b Amos Benari
end
334d0359 Amos Benari
end

96ede451 Amos Benari
def update_volumes(vm, attrs)
2a08c26b Amos Benari
volumes = nested_attributes_for :volumes, attrs
volumes.each do |volume|
55f8636a Amos Benari
vm.destroy_volume(:id => volume[:id], :blocking => api_version.to_f < 3.1) if volume[:_delete] == '1' && volume[:id].present?
180d7f43 Jason Montleon
vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 3.1}.merge(volume)) if volume[:id].blank?
2a08c26b Amos Benari
end
96ede451 Amos Benari
end
334d0359 Amos Benari
end
end