foreman/app/models/compute_resources/foreman/model/libvirt.rb @ 630061d2
334d0359 | Amos Benari | module Foreman::Model
|
|
class Libvirt < ComputeResource
|
|||
553a0beb | Joseph Magen | include ComputeResourceConsoleCommon
|
|
e52bc50f | Adam Ruzicka | ALLOWED_DISPLAY_TYPES = %w(vnc spice)
|
|
27e31470 | Michael Moll | validates :url, :format => { :with => URI::DEFAULT_PARSER.make_regexp }, :presence => true
|
|
e52bc50f | Adam Ruzicka | validates :display_type, :inclusion => { :in => ALLOWED_DISPLAY_TYPES }
|
|
7e031001 | Ohad Levy | ||
bd95cda6 | Dominic Cleal | def self.available?
|
|
Fog::Compute.providers.include?(:libvirt)
|
|||
end
|
|||
b760d48d | Greg Sutcliffe | # Some getters/setters for the attrs Hash
|
|
def display_type
|
|||
20b2ec5b | Michael Moll | self.attrs[:display].presence || 'vnc'
|
|
b760d48d | Greg Sutcliffe | end
|
|
def display_type=(display)
|
|||
e52bc50f | Adam Ruzicka | self.attrs[:display] = display.downcase
|
|
b760d48d | Greg Sutcliffe | end
|
|
dd42df0a | Ohad Levy | def provided_attributes
|
|
super.merge({:mac => :mac})
|
|||
end
|
|||
43c4bd72 | Marek Hulan | def interfaces_attrs_name
|
|
1fa8dcfb | Daniel Lobato | :nics
|
|
43c4bd72 | Marek Hulan | end
|
|
dd42df0a | Ohad Levy | def capabilities
|
|
3cf9d65f | Marek Hulán | [:build, :image, :new_volume]
|
|
dd42df0a | Ohad Levy | end
|
|
58c48471 | Julien Pivotto | def editable_network_interfaces?
|
|
e263719a | David Davis | interfaces.any? || networks.any?
|
|
58c48471 | Julien Pivotto | end
|
|
5f029ed6 | Daniel Lobato | def find_vm_by_uuid(uuid)
|
|
4269abbd | Tomas Strachota | super
|
|
a6a6b703 | David Swift | rescue ::Libvirt::RetrieveError => e
|
|
f4459c11 | David Davis | Foreman::Logging.exception("Failed retrieving libvirt vm by uuid #{uuid}", e)
|
|
2312cccf | Daniel Lobato | raise ActiveRecord::RecordNotFound
|
|
a6a6b703 | David Swift | end
|
|
f37934af | Ohad Levy | # we default to destroy the VM's storage as well.
|
|
5f029ed6 | Daniel Lobato | def destroy_vm(uuid, args = { })
|
|
f37934af | Ohad Levy | find_vm_by_uuid(uuid).destroy({ :destroy_volumes => true }.merge(args))
|
|
dd42df0a | Ohad Levy | rescue ActiveRecord::RecordNotFound
|
|
f37934af | Ohad Levy | true
|
|
334d0359 | Amos Benari | end
|
|
def self.model_name
|
|||
ComputeResource.model_name
|
|||
end
|
|||
def max_cpu_count
|
|||
f37934af | Ohad Levy | hypervisor.cpus
|
|
334d0359 | Amos Benari | end
|
|
eabbbeb4 | Marek Hulan | # returns available memory for VM in bytes
|
|
334d0359 | Amos Benari | def max_memory
|
|
eabbbeb4 | Marek Hulan | # libvirt reports in KB
|
|
hypervisor.memory.kilobyte
|
|||
f37934af | Ohad Levy | rescue => e
|
|
logger.debug "unable to figure out free memory, guessing instead due to:#{e}"
|
|||
eabbbeb4 | Marek Hulan | 16.gigabytes
|
|
334d0359 | Amos Benari | end
|
|
5f029ed6 | Daniel Lobato | def test_connection(options = {})
|
|
f37934af | Ohad Levy | super
|
|
e263719a | David Davis | errors[:url].empty? && hypervisor
|
|
f37934af | Ohad Levy | rescue => e
|
|
disconnect rescue nil
|
|||
errors[:base] << e.message
|
|||
end
|
|||
334d0359 | Amos Benari | ||
3cf9d65f | Marek Hulán | def new_nic(attr = {})
|
|
f37934af | Ohad Levy | client.nics.new attr
|
|
end
|
|||
6d05514a | Tomas Strachota | def new_interface(attr = {})
|
|
# fog compatibility
|
|||
new_nic(attr)
|
|||
end
|
|||
3cf9d65f | Marek Hulán | def new_volume(attr = {})
|
|
return unless new_volume_errors.empty?
|
|||
583865ce | Guido Günther | client.volumes.new(attr.merge(:allocation => '0G'))
|
|
f37934af | Ohad Levy | end
|
|
3cf9d65f | Marek Hulán | def new_volume_errors
|
|
errors = []
|
|||
errors.push _('no storage pool available on hypervisor') if storage_pools.empty?
|
|||
errors
|
|||
end
|
|||
f37934af | Ohad Levy | def storage_pools
|
|
98987690 | Ohad Levy | client.pools rescue []
|
|
f37934af | Ohad Levy | end
|
|
01fde835 | Marek Hulan | def bridges
|
|
# before ruby-libvirt fixes https://bugzilla.redhat.com/show_bug.cgi?id=1317909 we have to use raw XML to get type
|
|||
bridges = client.client.list_all_interfaces.select do |libvirt_interface|
|
|||
341bb2c0 | Marek Hulan | type_match = libvirt_interface.xml_desc.match /<interface.*?type=['"]([a-z]+)['"]/
|
|
01fde835 | Marek Hulan | type_match[1] == 'bridge'
|
|
end
|
|||
bridge_names = bridges.map(&:name)
|
|||
interfaces.select { |fog_interface| fog_interface.active? && bridge_names.include?(fog_interface.name) }
|
|||
rescue => e
|
|||
Foreman::Logging.exception('No bridge interface could be found in libvirt', e)
|
|||
[]
|
|||
end
|
|||
c5c84034 | Ohad Levy | def interfaces
|
|
98987690 | Ohad Levy | client.interfaces rescue []
|
|
f37934af | Ohad Levy | end
|
|
c5c84034 | Ohad Levy | def networks
|
|
client.networks rescue []
|
|||
end
|
|||
3d03e334 | Dominic Cleal | def template(id)
|
|
template = client.volumes.get(id)
|
|||
raise Foreman::Exception.new(N_("Unable to find template %s"), id) unless template.persisted?
|
|||
template
|
|||
end
|
|||
5f029ed6 | Daniel Lobato | def new_vm(attr = { })
|
|
95be0963 | Amos Benari | test_connection
|
|
return unless errors.empty?
|
|||
408774ae | Dominic Cleal | opts = vm_instance_defaults.merge(attr.to_h).deep_symbolize_keys
|
|
f37934af | Ohad Levy | ||
# convert rails nested_attributes into a plain hash
|
|||
[:nics, :volumes].each do |collection|
|
|||
nested_attrs = opts.delete("#{collection}_attributes".to_sym)
|
|||
opts[collection] = nested_attributes_for(collection, nested_attrs) if nested_attrs
|
|||
end
|
|||
opts.reject! { |k, v| v.nil? }
|
|||
3d03e334 | Dominic Cleal | opts[:boot_order] = %w[hd]
|
|
opts[:boot_order].unshift 'network' unless attr[:image_id]
|
|||
1c81c2b9 | Ohad Levy | vm = client.servers.new opts
|
|
vm.memory = opts[:memory] if opts[:memory]
|
|||
vm
|
|||
f37934af | Ohad Levy | end
|
|
5f029ed6 | Daniel Lobato | def create_vm(args = { })
|
|
f37934af | Ohad Levy | vm = new_vm(args)
|
|
3d03e334 | Dominic Cleal | create_volumes :prefix => vm.name, :volumes => vm.volumes, :backing_id => args[:image_id]
|
|
f37934af | Ohad Levy | vm.save
|
|
rescue Fog::Errors::Error => e
|
|||
01e78260 | Ivan Nečas | Foreman::Logging.exception("Unhandled Libvirt error", e)
|
|
169ac7b3 | Shimon Shtein | begin
|
|
b03dcd1b | Michael Moll | destroy_vm vm.id if vm&.id
|
|
169ac7b3 | Shimon Shtein | rescue Fog::Errors::Error => destroy_e
|
|
Foreman::Logging.exception("Libvirt destroy failed for #{vm.id}", destroy_e)
|
|||
end
|
|||
c67f9c5e | Greg Sutcliffe | raise e
|
|
f37934af | Ohad Levy | end
|
|
5f029ed6 | Daniel Lobato | def console(uuid)
|
|
f37934af | Ohad Levy | vm = find_vm_by_uuid(uuid)
|
|
dab82c90 | Dmitri Dolguikh | raise Foreman::Exception.new(N_("VM is not running!")) unless vm.ready?
|
|
f37934af | Ohad Levy | password = random_password
|
|
710dfa86 | Ryan Davies | # Listen address cannot be updated while the guest is running
|
|
# When we update the display password, we pass the existing listen address
|
|||
b760d48d | Greg Sutcliffe | vm.update_display(:password => password, :listen => vm.display[:listen], :type => vm.display[:type])
|
|
630061d2 | Michael Moll | WsProxy.start(:host => hypervisor.hostname, :host_port => vm.display[:port], :password => password).merge(:type => vm.display[:type], :name=> vm.name)
|
|
f37934af | Ohad Levy | rescue ::Libvirt::Error => e
|
|
if e.message =~ /cannot change listen address/
|
|||
logger.warn e
|
|||
dab82c90 | Dmitri Dolguikh | Foreman::Exception.new(N_("Unable to change VM display listen address, make sure the display is not attached to localhost only"))
|
|
f37934af | Ohad Levy | else
|
|
raise e
|
|||
end
|
|||
334d0359 | Amos Benari | end
|
|
def hypervisor
|
|||
client.nodes.first
|
|||
end
|
|||
805358df | Jason Montleon | def associated_host(vm)
|
|
81a02cde | Tom Caspy | associate_by("mac", vm.mac)
|
|
805358df | Jason Montleon | end
|
|
4269abbd | Tomas Strachota | def vm_compute_attributes_for(uuid)
|
|
vm_attrs = super
|
|||
if vm_attrs[:memory_size].nil?
|
|||
vm_attrs[:memory] = nil
|
|||
logger.debug("Compute attributes for VM '#{uuid}' diddn't contain :memory_size")
|
|||
else
|
|||
vm_attrs[:memory] = vm_attrs[:memory_size]*1024 # value is returned in megabytes, we need bytes
|
|||
end
|
|||
vm_attrs
|
|||
end
|
|||
56de025f | Tomas Strachota | def normalize_vm_attrs(vm_attrs)
|
|
normalized = slice_vm_attributes(vm_attrs, ['cpus', 'memory', 'image_id'])
|
|||
normalized['image_name'] = self.images.find_by(:uuid => vm_attrs['image_id']).try(:name)
|
|||
volume_attrs = vm_attrs['volumes_attributes'] || {}
|
|||
normalized['volumes_attributes'] = volume_attrs.each_with_object({}) do |(key, vol), volumes|
|
|||
volumes[key] = {
|
|||
'capacity' => memory_gb_to_bytes(vol['capacity']).to_s,
|
|||
'allocation' => memory_gb_to_bytes(vol['allocation']).to_s,
|
|||
'format_type' => vol['format_type'],
|
|||
'pool' => vol['pool_name']
|
|||
}
|
|||
end
|
|||
interface_attrs = vm_attrs['nics_attributes'] || {}
|
|||
normalized['interfaces_attributes'] = interface_attrs.each_with_object({}) do |(key, nic), interfaces|
|
|||
interfaces[key] = {
|
|||
'type' => nic['type'],
|
|||
'model' => nic['model']
|
|||
}
|
|||
if nic['type'] == 'network'
|
|||
interfaces[key]['network'] = nic['network']
|
|||
else
|
|||
interfaces[key]['bridge'] = nic['bridge']
|
|||
end
|
|||
end
|
|||
normalized
|
|||
end
|
|||
f37934af | Ohad Levy | protected
|
|
def client
|
|||
# WARNING potential connection leak
|
|||
c5c84034 | Ohad Levy | tries ||= 3
|
|
b43fa642 | Ohad Levy | Thread.current[url] ||= ::Fog::Compute.new(:provider => "Libvirt", :libvirt_uri => url)
|
|
c5c84034 | Ohad Levy | rescue ::Libvirt::RetrieveError
|
|
Thread.current[url] = nil
|
|||
retry unless (tries -= 1).zero?
|
|||
f37934af | Ohad Levy | end
|
|
def disconnect
|
|||
client.terminate if Thread.current[url]
|
|||
Thread.current[url] = nil
|
|||
end
|
|||
def vm_instance_defaults
|
|||
557b8543 | Joseph Mitchell Magen | super.merge(
|
|
60668e77 | Stephen Benjamin | :memory => 2048.megabytes,
|
|
f37934af | Ohad Levy | :nics => [new_nic],
|
|
3cf9d65f | Marek Hulán | :volumes => [new_volume].compact,
|
|
e52bc50f | Adam Ruzicka | :display => { :type => display_type,
|
|
96144a47 | Daniel Lobato | :listen => Setting[:libvirt_default_console_address],
|
|
b760d48d | Greg Sutcliffe | :password => random_password,
|
|
96144a47 | Daniel Lobato | :port => '-1' }
|
|
557b8543 | Joseph Mitchell Magen | )
|
|
f37934af | Ohad Levy | end
|
|
5f029ed6 | Daniel Lobato | def create_volumes(args)
|
|
dab82c90 | Dmitri Dolguikh | args[:volumes].each {|vol| validate_volume_capacity(vol)}
|
|
3d03e334 | Dominic Cleal | ||
# if using image creation, the first volume needs a backing disk set
|
|||
if args[:backing_id].present?
|
|||
raise ::Foreman::Exception.new(N_('At least one volume must be specified for image-based provisioning.')) unless args[:volumes].size >= 1
|
|||
args[:volumes].first.backing_volume = template(args[:backing_id])
|
|||
end
|
|||
dab82c90 | Dmitri Dolguikh | begin
|
|
vols = []
|
|||
(volumes = args[:volumes]).each do |vol|
|
|||
vol.name = "#{args[:prefix]}-disk#{volumes.index(vol)+1}"
|
|||
vol.capacity = "#{vol.capacity}G" unless vol.capacity.to_s.end_with?('G')
|
|||
41fb208d | Lukas Zapletal | vol.allocation = "#{vol.allocation}G" unless vol.allocation.to_s.end_with?('G')
|
|
dab82c90 | Dmitri Dolguikh | vol.save
|
|
vols << vol
|
|||
end
|
|||
vols
|
|||
rescue => e
|
|||
3d85bef8 | Lukas Zapletal | logger.error "Failure detected #{e}: removing already created volumes" if vols.any?
|
|
dab82c90 | Dmitri Dolguikh | vols.each { |vol| vol.destroy }
|
|
raise e
|
|||
f37934af | Ohad Levy | end
|
|
end
|
|||
dab82c90 | Dmitri Dolguikh | def validate_volume_capacity(vol)
|
|
287082a5 | David Davis | if vol.capacity.to_s.empty? || /\A\d+G?\Z/.match(vol.capacity.to_s).nil?
|
|
dab82c90 | Dmitri Dolguikh | raise Foreman::Exception.new(N_("Please specify volume size. You may optionally use suffix 'G' to specify volume size in gigabytes."))
|
|
end
|
|||
end
|
|||
334d0359 | Amos Benari | end
|
|
end
|