Files
vagrant-libvirt/lib/vagrant-libvirt/action/create_domain.rb
Darragh Bailey f51192e80b Provide uid/gid for additional volumes qemu:///session (#1170)
When using qemu:///session, it's necessary to ensure the correct
user/group is passed in when creating additional volume storage as
otherwise the default is to attempt to chown/chgrp it to 0:0 which will
fail.

With this in place and recent changes around uri/qemu_use_session,
remove the checks guarding retrieving the storage pool as it is also
possible for it to be created as expected for the session.

Update create domain tests to check for the correct settings such as
storage path and user/group id's passed to the volume create call for
the additional disks.

Fixes: #986
2020-12-05 15:49:25 +00:00

389 lines
15 KiB
Ruby

require 'log4r'
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 _disk_name(name, disk)
"#{name}-#{disk[:device]}.#{disk[:type]}" # disk name
end
def _disks_print(disks)
disks.collect do |x|
"#{x[:device]}(#{x[:type]},#{x[:size]})"
end.join(', ')
end
def _cdroms_print(cdroms)
cdroms.collect { |x| x[:dev] }.join(', ')
end
def call(env)
# Get config.
config = env[:machine].provider_config
# Gather some info about domain
@name = env[:domain_name]
@title = config.title
@description = config.description
@uuid = config.uuid
@cpus = config.cpus.to_i
@cpuset = config.cpuset
@cpu_features = config.cpu_features
@cpu_topology = config.cpu_topology
@nodeset = config.nodeset
@features = config.features
@features_hyperv = config.features_hyperv
@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_bus = config.disk_bus
@disk_device = config.disk_device
@nested = config.nested
@memory_size = config.memory.to_i * 1024
@memory_backing = config.memory_backing
@management_network_mac = config.management_network_mac
@domain_volume_cache = config.volume_cache
@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 = if config.graphics_passwd.to_s.empty?
''
else
"passwd='#{config.graphics_passwd}'"
end
@video_type = config.video_type
@sound_type = config.sound_type
@video_vram = config.video_vram
@keymap = config.keymap
@kvm_hidden = config.kvm_hidden
@tpm_model = config.tpm_model
@tpm_type = config.tpm_type
@tpm_path = config.tpm_path
# Boot order
@boot_order = config.boot_order
# Storage
@storage_pool_name = config.storage_pool_name
@snapshot_pool_name = config.snapshot_pool_name
@disks = config.disks
@cdroms = config.cdroms
# 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
config = env[:machine].provider_config
@domain_type = config.driver
@os_type = 'hvm'
# Get path to domain image from the storage pool selected if we have a box.
if env[:machine].config.vm.box
if @snapshot_pool_name != @storage_pool_name
pool_name = @snapshot_pool_name
else
pool_name = @storage_pool_name
end
@logger.debug "Search for volume in pool: #{pool_name}"
domain_volume = env[:machine].provider.driver.connection.volumes.all(
name: "#{@name}.img"
).find { |x| x.pool_name == pool_name }
raise Errors::DomainVolumeExists if domain_volume.nil?
@domain_volume_path = domain_volume.path
end
# If we have a box, take the path from the domain volume and set our storage_prefix.
# If not, we dump the storage pool xml to get its defined path.
# the default storage prefix is typically: /var/lib/libvirt/images/
if env[:machine].config.vm.box
storage_prefix = File.dirname(@domain_volume_path) + '/' # steal
else
storage_prefix = get_disk_storage_prefix(env, @storage_pool_name)
end
@disks.each do |disk|
disk[:path] ||= _disk_name(@name, disk)
# On volume creation, the <path> element inside <target>
# is oddly ignored; instead the path is taken from the
# <name> element:
# http://www.redhat.com/archives/libvir-list/2008-August/msg00329.html
disk[:name] = disk[:path]
disk[:absolute_path] = storage_prefix + disk[:path]
if not disk[:pool].nil?
disk_pool_name = disk[:pool]
@logger.debug "Overriding pool name with: #{disk_pool_name}"
disk_storage_prefix = get_disk_storage_prefix(env, disk_pool_name)
disk[:absolute_path] = disk_storage_prefix + disk[:path]
@logger.debug "Overriding disk path with: #{disk[:absolute_path]}"
else
disk_pool_name = @storage_pool_name
end
# 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_uid(env),
#:allocation => ?,
pool_name: disk_pool_name
)
rescue Libvirt::Error => e
# It is hard to believe that e contains just a string
# and no useful error code!
msg = "Call to virStorageVolCreateXML failed: " +
"storage volume '#{disk[:path]}' exists already"
if e.message == msg and disk[:allow_existing]
disk[:preexisting] = true
else
raise Errors::FogCreateDomainVolumeError,
error_message: e.message
end
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_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|
env[:ui].info(" -- Feature (HyperV): name=#{feature[:name]}, state=#{feature[:state]}")
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
unless @shares.nil?
env[:ui].info(" -- Shares: #{@shares}")
end
env[:ui].info(" -- Management MAC: #{@management_network_mac}")
env[:ui].info(" -- Loader: #{@loader}")
env[:ui].info(" -- Nvram: #{@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}")
env[:ui].info(" -- Image: #{@domain_volume_path} (#{env[:box_virtual_size]}G)")
env[:ui].info(" -- Volume Cache: #{@domain_volume_cache}")
env[:ui].info(" -- Kernel: #{@kernel}")
env[:ui].info(" -- Initrd: #{@initrd}")
env[:ui].info(" -- Graphics Type: #{@graphics_type}")
env[:ui].info(" -- Graphics Port: #{@graphics_port}")
env[:ui].info(" -- Graphics IP: #{@graphics_ip}")
env[:ui].info(" -- Graphics Password: #{@graphics_passwd.empty? ? 'Not defined' : 'Defined'}")
env[:ui].info(" -- Video Type: #{@video_type}")
env[:ui].info(" -- Video VRAM: #{@video_vram}")
env[:ui].info(" -- Sound Type: #{@sound_type}")
env[:ui].info(" -- Keymap: #{@keymap}")
env[:ui].info(" -- TPM Path: #{@tpm_path}")
@boot_order.each do |device|
env[:ui].info(" -- Boot device: #{device}")
end
unless @disks.empty?
env[:ui].info(" -- Disks: #{_disks_print(@disks)}")
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
@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
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..
begin
server = env[:machine].provider.driver.connection.servers.create(
xml: to_xml('domain')
)
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 get_disk_storage_prefix(env, disk_pool_name)
disk_storage_pool = env[:machine].provider.driver.connection.client.lookup_storage_pool_by_name(disk_pool_name)
raise Errors::NoStoragePool if disk_storage_pool.nil?
xml = Nokogiri::XML(disk_storage_pool.xml_desc)
disk_storage_prefix = xml.xpath('/pool/target/path').inner_text.to_s + '/'
end
end
end
end
end