mirror of
https://github.com/vagrant-libvirt/vagrant-libvirt.git
synced 2025-02-25 18:55:27 -06:00
This patch allows to specify the virtio-mmio address type, which is needed for debian guests on virt machines that don't support the PCI address type. See also https://translatedcode.wordpress.com/2016/11/03/installing-debian-on-qemus-32-bit-arm-virt-board/ where it is explicitly said to use `virtio-blk-device` and `virtio-net-device` instead of `virtio-blk-pci` and `virtio-net-pci`, for that reason. Apparently, libvirt will use the `virtio-blk-pci` and `virtio-net-pci` by default. By setting address type to `virtio-mmio`, it uses `virtio-blk-device` instead. It seems not necessary to do the same for the network controller, since libvirt will also use `virtio-net-device` if the disk address type is set to `virtio-mmio`. While this should help with ARM machines, it won't solve all issues as some machines will boot perfectly with the existing defaults provided the correct loader binary is used. Relates-to: #1608
475 lines
18 KiB
Ruby
475 lines
18 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'log4r'
|
|
|
|
require 'vagrant-libvirt/util/erb_template'
|
|
require 'vagrant-libvirt/util/resolvers'
|
|
require 'vagrant-libvirt/util/storage_util'
|
|
|
|
module VagrantPlugins
|
|
module ProviderLibvirt
|
|
module Action
|
|
class CreateDomain
|
|
include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate
|
|
include VagrantPlugins::ProviderLibvirt::Util::StorageUtil
|
|
|
|
def initialize(app, _env)
|
|
@logger = Log4r::Logger.new('vagrant_libvirt::action::create_domain')
|
|
@app = app
|
|
end
|
|
|
|
def call(env)
|
|
# Get config.
|
|
config = env[:machine].provider_config
|
|
|
|
# Gather some info about domain
|
|
@name = env[:domain_name]
|
|
@title = config.title
|
|
vagrantfile = File.join(env[:root_path], (env[:vagrantfile_name] || "Vagrantfile"))
|
|
@description = !config.description.empty? ? config.description : "Source: #{vagrantfile}"
|
|
@uuid = config.uuid
|
|
@cpus = config.cpus.to_i
|
|
@cpuset = config.cpuset
|
|
@cpu_features = config.cpu_features
|
|
@cpu_topology = config.cpu_topology
|
|
@cpu_affinity = config.cpu_affinity
|
|
@nodeset = config.nodeset
|
|
@features = config.features
|
|
@features_hyperv = config.features_hyperv
|
|
@clock_offset = config.clock_offset
|
|
@clock_timers = config.clock_timers
|
|
@launchsecurity_data = config.launchsecurity_data
|
|
@shares = config.shares
|
|
@cpu_mode = config.cpu_mode
|
|
@cpu_model = config.cpu_model
|
|
@cpu_fallback = config.cpu_fallback
|
|
@numa_nodes = config.numa_nodes
|
|
@loader = config.loader
|
|
@nvram = config.nvram
|
|
@machine_type = config.machine_type
|
|
@machine_arch = config.machine_arch
|
|
@disk_controller_model = config.disk_controller_model
|
|
@disk_driver_opts = config.disk_driver_opts
|
|
@disk_address_type = config.disk_address_type
|
|
@nested = config.nested
|
|
@memory_size = config.memory.to_i * 1024
|
|
@memory_backing = config.memory_backing
|
|
@memtunes = config.memtunes
|
|
|
|
@management_network_mac = config.management_network_mac
|
|
@domain_volume_cache = config.volume_cache || 'default'
|
|
@kernel = config.kernel
|
|
@cmd_line = config.cmd_line
|
|
@emulator_path = config.emulator_path
|
|
@initrd = config.initrd
|
|
@dtb = config.dtb
|
|
@graphics_type = config.graphics_type
|
|
@graphics_autoport = config.graphics_autoport
|
|
@graphics_port = config.graphics_port
|
|
@graphics_ip = config.graphics_ip
|
|
@graphics_passwd = config.graphics_passwd
|
|
@graphics_gl = config.graphics_gl
|
|
@video_type = config.video_type
|
|
@sound_type = config.sound_type
|
|
@video_vram = config.video_vram
|
|
@video_accel3d = config.video_accel3d
|
|
@keymap = config.keymap
|
|
@kvm_hidden = config.kvm_hidden
|
|
|
|
@tpm_model = config.tpm_model
|
|
@tpm_type = config.tpm_type
|
|
@tpm_path = config.tpm_path
|
|
@tpm_version = config.tpm_version
|
|
|
|
@sysinfo = config.sysinfo.dup
|
|
@sysinfo.each do |section, _v|
|
|
if @sysinfo[section].respond_to?(:each_pair)
|
|
@sysinfo[section].delete_if { |_k, v| v.to_s.empty? }
|
|
else
|
|
@sysinfo[section].reject! { |e| e.to_s.empty? }
|
|
end
|
|
end.reject! { |_k, v| v.empty? }
|
|
@sysinfo_blocks = {
|
|
'bios' => {:ui => "BIOS", :xml => "bios"},
|
|
'system' => {:ui => "System", :xml => "system"},
|
|
'base board' => {:ui => "Base Board", :xml => "baseBoard"},
|
|
'chassis' => {:ui => "Chassis", :xml => "chassis"},
|
|
'oem strings' => {:ui => "OEM Strings", :xml => "oemStrings"},
|
|
}
|
|
|
|
# Boot order
|
|
@boot_order = config.boot_order
|
|
|
|
# Storage
|
|
@storage_pool_name = config.storage_pool_name
|
|
@domain_volumes = env[:domain_volumes] || []
|
|
@disks = env[:disks] || []
|
|
@cdroms = config.cdroms
|
|
@floppies = config.floppies
|
|
|
|
# Input
|
|
@inputs = config.inputs
|
|
|
|
# Channels
|
|
@channels = config.channels
|
|
|
|
# PCI device passthrough
|
|
@pcis = config.pcis
|
|
|
|
# Watchdog device
|
|
@watchdog_dev = config.watchdog_dev
|
|
|
|
# USB controller
|
|
@usbctl_dev = config.usbctl_dev
|
|
|
|
# USB device passthrough
|
|
@usbs = config.usbs
|
|
|
|
# Redirected devices
|
|
@redirdevs = config.redirdevs
|
|
@redirfilters = config.redirfilters
|
|
|
|
# Additional QEMU commandline arguments
|
|
@qemu_args = config.qemu_args
|
|
|
|
# Additional QEMU commandline environment variables
|
|
@qemu_env = config.qemu_env
|
|
|
|
# smartcard device
|
|
@smartcard_dev = config.smartcard_dev
|
|
|
|
# RNG device passthrough
|
|
@rng = config.rng
|
|
|
|
# Memballoon
|
|
@memballoon_enabled = config.memballoon_enabled
|
|
@memballoon_model = config.memballoon_model
|
|
@memballoon_pci_bus = config.memballoon_pci_bus
|
|
@memballoon_pci_slot = config.memballoon_pci_slot
|
|
|
|
@domain_type = config.driver
|
|
|
|
@os_type = 'hvm'
|
|
|
|
env[:domain_volumes].each_with_index do |vol, index|
|
|
suffix_index = index > 0 ? "_#{index}" : ''
|
|
domain_volume = env[:machine].provider.driver.connection.volumes.all(
|
|
name: "#{@name}#{suffix_index}.img"
|
|
).find { |x| x.pool_name == vol[:pool] }
|
|
raise Errors::NoDomainVolume if domain_volume.nil?
|
|
end
|
|
|
|
@disks.each do |disk|
|
|
# make the disk. equivalent to:
|
|
# qemu-img create -f qcow2 <path> 5g
|
|
begin
|
|
env[:machine].provider.driver.connection.volumes.create(
|
|
name: disk[:name],
|
|
format_type: disk[:type],
|
|
path: disk[:absolute_path],
|
|
capacity: disk[:size],
|
|
owner: storage_uid(env),
|
|
group: storage_gid(env),
|
|
#:allocation => ?,
|
|
pool_name: disk[:pool],
|
|
)
|
|
rescue Libvirt::Error => e
|
|
# It is hard to believe that e contains just a string
|
|
# and no useful error code!
|
|
msgs = [disk[:name], disk[:absolute_path]].map do |name|
|
|
"Call to virStorageVolCreateXML failed: " +
|
|
"storage volume '#{name}' exists already"
|
|
end
|
|
if msgs.include?(e.message) and disk[:allow_existing]
|
|
disk[:preexisting] = true
|
|
else
|
|
raise Errors::FogCreateDomainVolumeError,
|
|
error_message: e.message
|
|
end
|
|
end
|
|
end
|
|
|
|
@serials = config.serials
|
|
|
|
@serials.each do |serial|
|
|
next unless serial[:source] && serial[:source][:path]
|
|
|
|
dir = File.dirname(serial[:source][:path])
|
|
begin
|
|
FileUtils.mkdir_p(dir)
|
|
rescue ::Errno::EACCES
|
|
raise Errors::SerialCannotCreatePathError,
|
|
path: dir
|
|
end
|
|
end
|
|
|
|
# Output the settings we're going to use to the user
|
|
env[:ui].info(I18n.t('vagrant_libvirt.creating_domain'))
|
|
env[:ui].info(" -- Name: #{@name}")
|
|
env[:ui].info(" -- Title: #{@title}") if @title != ''
|
|
env[:ui].info(" -- Description: #{@description}") if @description != ''
|
|
env[:ui].info(" -- Forced UUID: #{@uuid}") if @uuid != ''
|
|
env[:ui].info(" -- Domain type: #{@domain_type}")
|
|
env[:ui].info(" -- Cpus: #{@cpus}")
|
|
unless @cpuset.nil?
|
|
env[:ui].info(" -- Cpuset: #{@cpuset}")
|
|
end
|
|
if not @cpu_topology.empty?
|
|
env[:ui].info(" -- CPU topology: sockets=#{@cpu_topology[:sockets]}, cores=#{@cpu_topology[:cores]}, threads=#{@cpu_topology[:threads]}")
|
|
end
|
|
@cpu_affinity.each do |vcpu, cpuset|
|
|
env[:ui].info(" -- CPU affinity: vcpu #{vcpu} => cpuset #{cpuset}")
|
|
end
|
|
@cpu_features.each do |cpu_feature|
|
|
env[:ui].info(" -- CPU feature: name=#{cpu_feature[:name]}, policy=#{cpu_feature[:policy]}")
|
|
end
|
|
@features.each do |feature|
|
|
env[:ui].info(" -- Feature: #{feature}")
|
|
end
|
|
@features_hyperv.each do |feature|
|
|
if feature[:name] == 'spinlocks'
|
|
env[:ui].info(" -- Feature (HyperV): name=#{feature[:name]}, state=#{feature[:state]}, retries=#{feature[:retries]}")
|
|
else
|
|
env[:ui].info(" -- Feature (HyperV): name=#{feature[:name]}, state=#{feature[:state]}")
|
|
end
|
|
end
|
|
env[:ui].info(" -- Clock offset: #{@clock_offset}")
|
|
@clock_timers.each do |timer|
|
|
env[:ui].info(" -- Clock timer: #{timer.map { |k,v| "#{k}=#{v}"}.join(', ')}")
|
|
end
|
|
env[:ui].info(" -- Memory: #{@memory_size / 1024}M")
|
|
unless @nodeset.nil?
|
|
env[:ui].info(" -- Nodeset: #{@nodeset}")
|
|
end
|
|
@memory_backing.each do |backing|
|
|
env[:ui].info(" -- Memory Backing: #{backing[:name]}: #{backing[:config].map { |k,v| "#{k}='#{v}'"}.join(' ')}")
|
|
end
|
|
|
|
@memtunes.each do |type, options|
|
|
env[:ui].info(" -- Memory Tuning: #{type}: #{options[:config].map { |k,v| "#{k}='#{v}'"}.join(' ')}, value: #{options[:value]}")
|
|
end
|
|
unless @shares.nil?
|
|
env[:ui].info(" -- Shares: #{@shares}")
|
|
end
|
|
env[:ui].info(" -- Management MAC: #{@management_network_mac}") if @management_network_mac
|
|
env[:ui].info(" -- Kernel: #{@kernel}") if @kernel
|
|
env[:ui].info(" -- Initrd: #{@initrd}") if @initrd
|
|
env[:ui].info(" -- Loader: #{@loader}") if @loader
|
|
env[:ui].info(" -- Nvram: #{@nvram}") if @nvram
|
|
if env[:machine].config.vm.box
|
|
env[:ui].info(" -- Base box: #{env[:machine].box.name}")
|
|
end
|
|
env[:ui].info(" -- Storage pool: #{@storage_pool_name}")
|
|
@domain_volumes.each do |volume|
|
|
env[:ui].info(" -- Image(#{volume[:device]}): #{volume[:absolute_path]}, #{volume[:bus]}, #{volume[:virtual_size].to_GB}G")
|
|
end
|
|
|
|
if not @disk_driver_opts.empty?
|
|
env[:ui].info(" -- Disk driver opts: #{@disk_driver_opts.reject { |k,v| v.nil? }.map { |k,v| "#{k}='#{v}'"}.join(' ')}")
|
|
else
|
|
env[:ui].info(" -- Disk driver opts: cache='#{@domain_volume_cache}'")
|
|
end
|
|
|
|
env[:ui].info(" -- Graphics Type: #{@graphics_type}")
|
|
if !@graphics_autoport
|
|
env[:ui].info(" -- Graphics Port: #{@graphics_port}")
|
|
env[:ui].info(" -- Graphics IP: #{@graphics_ip}")
|
|
env[:ui].info(" -- Graphics Password: #{@graphics_passwd.nil? ? 'Not defined' : 'Defined'}")
|
|
end
|
|
env[:ui].info(" -- Video Type: #{@video_type}")
|
|
env[:ui].info(" -- Video VRAM: #{@video_vram}")
|
|
env[:ui].info(" -- Video 3D accel: #{@video_accel3d}")
|
|
env[:ui].info(" -- Sound Type: #{@sound_type}") if @sound_type
|
|
env[:ui].info(" -- Keymap: #{@keymap}")
|
|
env[:ui].info(" -- TPM Backend: #{@tpm_type}")
|
|
if @tpm_type == 'emulator'
|
|
env[:ui].info(" -- TPM Model: #{@tpm_model}")
|
|
env[:ui].info(" -- TPM Version: #{@tpm_version}")
|
|
else
|
|
env[:ui].info(" -- TPM Path: #{@tpm_path}") if @tpm_path
|
|
end
|
|
|
|
unless @sysinfo.empty?
|
|
env[:ui].info(" -- Sysinfo:")
|
|
@sysinfo.each_pair do |block, values|
|
|
env[:ui].info(" -- #{@sysinfo_blocks[block.to_s][:ui]}:")
|
|
if values.respond_to?(:each_pair)
|
|
values.each_pair do |name, value|
|
|
env[:ui].info(" -> #{name}: #{value}")
|
|
end
|
|
else
|
|
values.each do |value|
|
|
env[:ui].info(" -> #{value}")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if @memballoon_enabled
|
|
env[:ui].info(" -- Memballoon model: #{@memballoon_model}")
|
|
env[:ui].info(" -- Memballoon bus: #{@memballoon_pci_bus}")
|
|
env[:ui].info(" -- Memballoon slot: #{@memballoon_pci_slot}")
|
|
end
|
|
|
|
@boot_order.each do |device|
|
|
env[:ui].info(" -- Boot device: #{device}")
|
|
end
|
|
|
|
unless @disks.empty?
|
|
env[:ui].info(" -- Disks: #{_disks_print(@disks)}")
|
|
end
|
|
|
|
if not @launchsecurity_data.nil?
|
|
env[:ui].info(" -- Launch security: #{@launchsecurity_data.map { |k, v| "#{k.to_s}=#{v}" }.join(", ")}")
|
|
end
|
|
|
|
@disks.each do |disk|
|
|
msg = " -- Disk(#{disk[:device]}): #{disk[:absolute_path]}"
|
|
msg += ' Shared' if disk[:shareable]
|
|
msg += ' (Remove only manually)' if disk[:allow_existing]
|
|
msg += ' Not created - using existed.' if disk[:preexisting]
|
|
env[:ui].info(msg)
|
|
end
|
|
|
|
unless @cdroms.empty?
|
|
env[:ui].info(" -- CDROMS: #{_cdroms_print(@cdroms)}")
|
|
end
|
|
|
|
@cdroms.each do |cdrom|
|
|
env[:ui].info(" -- CDROM(#{cdrom[:dev]}): #{cdrom[:path]}")
|
|
end
|
|
|
|
unless @floppies.empty?
|
|
env[:ui].info(" -- Floppies: #{_floppies_print(@floppies)}")
|
|
end
|
|
|
|
@floppies.each do |floppy|
|
|
env[:ui].info(" -- Floppy(#{floppy[:dev]}): #{floppy[:path]}")
|
|
end
|
|
|
|
@inputs.each do |input|
|
|
env[:ui].info(" -- INPUT: type=#{input[:type]}, bus=#{input[:bus]}")
|
|
end
|
|
|
|
@channels.each do |channel|
|
|
env[:ui].info(" -- CHANNEL: type=#{channel[:type]}, mode=#{channel[:source_mode]}")
|
|
env[:ui].info(" -- CHANNEL: target_type=#{channel[:target_type]}, target_name=#{channel[:target_name]}")
|
|
end
|
|
|
|
@pcis.each do |pci|
|
|
env[:ui].info(" -- PCI passthrough: #{pci[:domain]}:#{pci[:bus]}:#{pci[:slot]}.#{pci[:function]}")
|
|
end
|
|
|
|
unless @rng[:model].nil?
|
|
env[:ui].info(" -- RNG device model: #{@rng[:model]}")
|
|
end
|
|
|
|
if not @watchdog_dev.empty?
|
|
env[:ui].info(" -- Watchdog device: model=#{@watchdog_dev[:model]}, action=#{@watchdog_dev[:action]}")
|
|
end
|
|
|
|
if not @usbctl_dev.empty?
|
|
msg = " -- USB controller: model=#{@usbctl_dev[:model]}"
|
|
msg += ", ports=#{@usbctl_dev[:ports]}" if @usbctl_dev[:ports]
|
|
env[:ui].info(msg)
|
|
end
|
|
|
|
@usbs.each do |usb|
|
|
usb_dev = []
|
|
usb_dev.push("bus=#{usb[:bus]}") if usb[:bus]
|
|
usb_dev.push("device=#{usb[:device]}") if usb[:device]
|
|
usb_dev.push("vendor=#{usb[:vendor]}") if usb[:vendor]
|
|
usb_dev.push("product=#{usb[:product]}") if usb[:product]
|
|
env[:ui].info(" -- USB passthrough: #{usb_dev.join(', ')}")
|
|
end
|
|
|
|
unless @redirdevs.empty?
|
|
env[:ui].info(' -- Redirected Devices: ')
|
|
@redirdevs.each do |redirdev|
|
|
msg = " -> bus=usb, type=#{redirdev[:type]}"
|
|
env[:ui].info(msg)
|
|
end
|
|
end
|
|
|
|
unless @redirfilters.empty?
|
|
env[:ui].info(' -- USB Device filter for Redirected Devices: ')
|
|
@redirfilters.each do |redirfilter|
|
|
msg = " -> class=#{redirfilter[:class]}, "
|
|
msg += "vendor=#{redirfilter[:vendor]}, "
|
|
msg += "product=#{redirfilter[:product]}, "
|
|
msg += "version=#{redirfilter[:version]}, "
|
|
msg += "allow=#{redirfilter[:allow]}"
|
|
env[:ui].info(msg)
|
|
end
|
|
end
|
|
|
|
if not @smartcard_dev.empty?
|
|
env[:ui].info(" -- smartcard device: mode=#{@smartcard_dev[:mode]}, type=#{@smartcard_dev[:type]}")
|
|
end
|
|
|
|
@serials.each_with_index do |serial, port|
|
|
if serial[:source]
|
|
env[:ui].info(" -- SERIAL(COM#{port}: redirect to #{serial[:source][:path]}")
|
|
env[:ui].warn(I18n.t('vagrant_libvirt.warnings.creating_domain_console_access_disabled'))
|
|
end
|
|
end
|
|
|
|
unless @qemu_args.empty?
|
|
env[:ui].info(' -- Command line args: ')
|
|
@qemu_args.each do |arg|
|
|
msg = " -> value=#{arg[:value]}, "
|
|
env[:ui].info(msg)
|
|
end
|
|
end
|
|
|
|
unless @qemu_env.empty?
|
|
env[:ui].info(' -- Command line environment variables: ')
|
|
@qemu_env.each do |env_var, env_value|
|
|
msg = " -> #{env_var}=#{env_value}, "
|
|
env[:ui].info(msg)
|
|
end
|
|
end
|
|
|
|
env[:ui].info(" -- Command line : #{@cmd_line}") unless @cmd_line.empty?
|
|
|
|
# Create Libvirt domain.
|
|
# Is there a way to tell fog to create new domain with already
|
|
# existing volume? Use domain creation from template..
|
|
xml = to_xml('domain')
|
|
@logger.debug {
|
|
"Creating Domain with XML:\n#{xml}"
|
|
}
|
|
|
|
begin
|
|
server = env[:machine].provider.driver.connection.servers.create(
|
|
xml: xml
|
|
)
|
|
rescue Fog::Errors::Error => e
|
|
raise Errors::FogCreateServerError, error_message: e.message
|
|
end
|
|
|
|
# Immediately save the ID since it is created at this point.
|
|
env[:machine].id = server.id
|
|
|
|
@app.call(env)
|
|
end
|
|
|
|
private
|
|
def _disks_print(disks)
|
|
disks.collect do |x|
|
|
"#{x[:device]}(#{x[:type]}, #{x[:bus]}, #{x[:size]})"
|
|
end.join(', ')
|
|
end
|
|
|
|
def _cdroms_print(cdroms)
|
|
cdroms.collect { |x| x[:dev] }.join(', ')
|
|
end
|
|
|
|
def _floppies_print(floppies)
|
|
floppies.collect { |x| x[:dev] }.join(', ')
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|