mirror of
https://github.com/vagrant-libvirt/vagrant-libvirt.git
synced 2025-02-25 18:55:27 -06:00
Libvirt did already change the current 9216kb
to 16384kb when starting a domain so in practice this is a no-op.
The libvirt default was changed in 2014 in 81ba2298b2
1164 lines
39 KiB
Ruby
1164 lines
39 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'cgi'
|
|
|
|
require 'vagrant'
|
|
|
|
require 'vagrant-libvirt/errors'
|
|
require 'vagrant-libvirt/util/resolvers'
|
|
|
|
module VagrantPlugins
|
|
module ProviderLibvirt
|
|
class Config < Vagrant.plugin('2', :config)
|
|
# manually specify URI
|
|
# will supercede most other options if provided
|
|
attr_accessor :uri
|
|
|
|
# A hypervisor name to access via Libvirt.
|
|
attr_accessor :driver
|
|
|
|
# The name of the server, where Libvirtd is running.
|
|
attr_accessor :host
|
|
|
|
# If use ssh tunnel to connect to Libvirt.
|
|
attr_accessor :connect_via_ssh
|
|
# Path towards the Libvirt socket
|
|
attr_accessor :socket
|
|
|
|
# The username to access Libvirt.
|
|
attr_accessor :username
|
|
|
|
# Password for Libvirt connection.
|
|
attr_accessor :password
|
|
|
|
# ID SSH key file
|
|
attr_accessor :id_ssh_key_file
|
|
|
|
attr_accessor :proxy_command
|
|
|
|
# Forward port with id 'ssh'
|
|
attr_accessor :forward_ssh_port
|
|
|
|
# Libvirt storage pool name, where box image and instance snapshots will
|
|
# be stored.
|
|
attr_accessor :storage_pool_name
|
|
attr_accessor :storage_pool_path
|
|
|
|
# Libvirt storage pool where the base image snapshot shall be stored
|
|
attr_accessor :snapshot_pool_name
|
|
|
|
# Turn on to prevent hostname conflicts
|
|
attr_accessor :random_hostname
|
|
|
|
# Libvirt default network
|
|
attr_accessor :management_network_device
|
|
attr_accessor :management_network_name
|
|
attr_accessor :management_network_address
|
|
attr_accessor :management_network_mode
|
|
attr_accessor :management_network_mac
|
|
attr_accessor :management_network_guest_ipv6
|
|
attr_accessor :management_network_autostart
|
|
attr_accessor :management_network_pci_bus
|
|
attr_accessor :management_network_pci_slot
|
|
attr_accessor :management_network_domain
|
|
attr_accessor :management_network_mtu
|
|
attr_accessor :management_network_keep
|
|
|
|
# System connection information
|
|
attr_accessor :system_uri
|
|
|
|
# Default host prefix (alternative to use project folder name)
|
|
attr_accessor :default_prefix
|
|
|
|
# Domain specific settings used while creating new domain.
|
|
attr_accessor :title
|
|
attr_accessor :description
|
|
attr_accessor :uuid
|
|
attr_accessor :memory
|
|
attr_accessor :nodeset
|
|
attr_accessor :memory_backing
|
|
attr_accessor :channel
|
|
attr_accessor :cpus
|
|
attr_accessor :cpuset
|
|
attr_accessor :cpu_mode
|
|
attr_accessor :cpu_model
|
|
attr_accessor :cpu_fallback
|
|
attr_accessor :cpu_features
|
|
attr_accessor :cpu_topology
|
|
attr_accessor :shares
|
|
attr_accessor :features
|
|
attr_accessor :features_hyperv
|
|
attr_accessor :clock_offset
|
|
attr_accessor :clock_timers
|
|
attr_accessor :numa_nodes
|
|
attr_accessor :loader
|
|
attr_accessor :nvram
|
|
attr_accessor :boot_order
|
|
attr_accessor :machine_type
|
|
attr_accessor :machine_arch
|
|
attr_accessor :machine_virtual_size
|
|
attr_accessor :disk_bus
|
|
attr_accessor :disk_device
|
|
attr_accessor :disk_driver_opts
|
|
attr_accessor :nic_model_type
|
|
attr_accessor :nested
|
|
attr_accessor :volume_cache # deprecated, kept for backwards compatibility; use disk_driver
|
|
attr_accessor :kernel
|
|
attr_accessor :cmd_line
|
|
attr_accessor :initrd
|
|
attr_accessor :dtb
|
|
attr_accessor :emulator_path
|
|
attr_accessor :graphics_type
|
|
attr_accessor :graphics_autoport
|
|
attr_accessor :graphics_port
|
|
attr_accessor :graphics_passwd
|
|
attr_accessor :graphics_ip
|
|
attr_accessor :graphics_gl
|
|
attr_accessor :video_type
|
|
attr_accessor :video_vram
|
|
attr_accessor :video_accel3d
|
|
attr_accessor :keymap
|
|
attr_accessor :kvm_hidden
|
|
attr_accessor :sound_type
|
|
|
|
# Sets the information for connecting to a host TPM device
|
|
# Only supports socket-based TPMs
|
|
attr_accessor :tpm_model
|
|
attr_accessor :tpm_type
|
|
attr_accessor :tpm_path
|
|
attr_accessor :tpm_version
|
|
|
|
# Configure the memballoon
|
|
attr_accessor :memballoon_enabled
|
|
attr_accessor :memballoon_model
|
|
attr_accessor :memballoon_pci_bus
|
|
attr_accessor :memballoon_pci_slot
|
|
|
|
# Sets the max number of NICs that can be created
|
|
# Default set to 8. Don't change the default unless you know
|
|
# what are doing
|
|
attr_accessor :nic_adapter_count
|
|
|
|
# Storage
|
|
attr_accessor :disks
|
|
attr_accessor :cdroms
|
|
|
|
# Inputs
|
|
attr_accessor :inputs
|
|
|
|
# Channels
|
|
attr_accessor :channels
|
|
|
|
# PCI device passthrough
|
|
attr_accessor :pcis
|
|
|
|
# Random number device passthrough
|
|
attr_accessor :rng
|
|
|
|
# Watchdog device
|
|
attr_accessor :watchdog_dev
|
|
|
|
# USB controller
|
|
attr_accessor :usbctl_dev
|
|
|
|
# USB device passthrough
|
|
attr_accessor :usbs
|
|
|
|
# Redirected devices
|
|
attr_accessor :redirdevs
|
|
attr_accessor :redirfilters
|
|
|
|
# smartcard device
|
|
attr_accessor :smartcard_dev
|
|
|
|
# Suspend mode
|
|
attr_accessor :suspend_mode
|
|
|
|
# Autostart
|
|
attr_accessor :autostart
|
|
|
|
# Attach mgmt network
|
|
attr_accessor :mgmt_attach
|
|
|
|
# Additional qemuargs arguments
|
|
attr_accessor :qemu_args
|
|
|
|
# Additional qemuenv arguments
|
|
attr_accessor :qemu_env
|
|
|
|
# Use QEMU session instead of system
|
|
attr_accessor :qemu_use_session
|
|
|
|
# Use QEMU Agent to get ip address
|
|
attr_accessor :qemu_use_agent
|
|
|
|
# serial consoles
|
|
attr_accessor :serials
|
|
|
|
def initialize
|
|
@uri = UNSET_VALUE
|
|
@driver = UNSET_VALUE
|
|
@host = UNSET_VALUE
|
|
@port = UNSET_VALUE
|
|
@connect_via_ssh = UNSET_VALUE
|
|
@username = UNSET_VALUE
|
|
@password = UNSET_VALUE
|
|
@id_ssh_key_file = UNSET_VALUE
|
|
@socket = UNSET_VALUE
|
|
@proxy_command = UNSET_VALUE
|
|
@forward_ssh_port = UNSET_VALUE # forward port with id 'ssh'
|
|
@storage_pool_name = UNSET_VALUE
|
|
@snapshot_pool_name = UNSET_VALUE
|
|
@random_hostname = UNSET_VALUE
|
|
@management_network_device = UNSET_VALUE
|
|
@management_network_name = UNSET_VALUE
|
|
@management_network_address = UNSET_VALUE
|
|
@management_network_mode = UNSET_VALUE
|
|
@management_network_mac = UNSET_VALUE
|
|
@management_network_guest_ipv6 = UNSET_VALUE
|
|
@management_network_autostart = UNSET_VALUE
|
|
@management_network_pci_slot = UNSET_VALUE
|
|
@management_network_pci_bus = UNSET_VALUE
|
|
@management_network_domain = UNSET_VALUE
|
|
@management_network_mtu = UNSET_VALUE
|
|
@management_network_keep = UNSET_VALUE
|
|
|
|
# System connection information
|
|
@system_uri = UNSET_VALUE
|
|
|
|
# Domain specific settings.
|
|
@title = UNSET_VALUE
|
|
@description = UNSET_VALUE
|
|
@uuid = UNSET_VALUE
|
|
@memory = UNSET_VALUE
|
|
@nodeset = UNSET_VALUE
|
|
@memory_backing = UNSET_VALUE
|
|
@cpus = UNSET_VALUE
|
|
@cpuset = UNSET_VALUE
|
|
@cpu_mode = UNSET_VALUE
|
|
@cpu_model = UNSET_VALUE
|
|
@cpu_fallback = UNSET_VALUE
|
|
@cpu_features = UNSET_VALUE
|
|
@cpu_topology = UNSET_VALUE
|
|
@shares = UNSET_VALUE
|
|
@features = UNSET_VALUE
|
|
@features_hyperv = UNSET_VALUE
|
|
@clock_offset = UNSET_VALUE
|
|
@clock_timers = []
|
|
@numa_nodes = UNSET_VALUE
|
|
@loader = UNSET_VALUE
|
|
@nvram = UNSET_VALUE
|
|
@machine_type = UNSET_VALUE
|
|
@machine_arch = UNSET_VALUE
|
|
@machine_virtual_size = UNSET_VALUE
|
|
@disk_bus = UNSET_VALUE
|
|
@disk_device = UNSET_VALUE
|
|
@disk_driver_opts = {}
|
|
@nic_model_type = UNSET_VALUE
|
|
@nested = UNSET_VALUE
|
|
@volume_cache = UNSET_VALUE
|
|
@kernel = UNSET_VALUE
|
|
@initrd = UNSET_VALUE
|
|
@dtb = UNSET_VALUE
|
|
@cmd_line = UNSET_VALUE
|
|
@emulator_path = UNSET_VALUE
|
|
@graphics_type = UNSET_VALUE
|
|
@graphics_autoport = UNSET_VALUE
|
|
@graphics_port = UNSET_VALUE
|
|
@graphics_ip = UNSET_VALUE
|
|
@graphics_passwd = UNSET_VALUE
|
|
@graphics_gl = UNSET_VALUE
|
|
@video_type = UNSET_VALUE
|
|
@video_vram = UNSET_VALUE
|
|
@video_accel3d = UNSET_VALUE
|
|
@sound_type = UNSET_VALUE
|
|
@keymap = UNSET_VALUE
|
|
@kvm_hidden = UNSET_VALUE
|
|
|
|
@tpm_model = UNSET_VALUE
|
|
@tpm_type = UNSET_VALUE
|
|
@tpm_path = UNSET_VALUE
|
|
@tpm_version = UNSET_VALUE
|
|
|
|
@memballoon_enabled = UNSET_VALUE
|
|
@memballoon_model = UNSET_VALUE
|
|
@memballoon_pci_bus = UNSET_VALUE
|
|
@memballoon_pci_slot = UNSET_VALUE
|
|
|
|
@nic_adapter_count = UNSET_VALUE
|
|
|
|
# Boot order
|
|
@boot_order = []
|
|
# Storage
|
|
@disks = []
|
|
@cdroms = []
|
|
|
|
# Inputs
|
|
@inputs = UNSET_VALUE
|
|
|
|
# Channels
|
|
@channels = UNSET_VALUE
|
|
|
|
# PCI device passthrough
|
|
@pcis = UNSET_VALUE
|
|
|
|
# Random number device passthrough
|
|
@rng = UNSET_VALUE
|
|
|
|
# Watchdog device
|
|
@watchdog_dev = UNSET_VALUE
|
|
|
|
# USB controller
|
|
@usbctl_dev = UNSET_VALUE
|
|
|
|
# USB device passthrough
|
|
@usbs = UNSET_VALUE
|
|
|
|
# Redirected devices
|
|
@redirdevs = UNSET_VALUE
|
|
@redirfilters = UNSET_VALUE
|
|
|
|
# smartcard device
|
|
@smartcard_dev = UNSET_VALUE
|
|
|
|
# Suspend mode
|
|
@suspend_mode = UNSET_VALUE
|
|
|
|
# Autostart
|
|
@autostart = UNSET_VALUE
|
|
|
|
# Attach mgmt network
|
|
@mgmt_attach = UNSET_VALUE
|
|
|
|
# Additional QEMU commandline arguments
|
|
@qemu_args = UNSET_VALUE
|
|
|
|
# Additional QEMU commandline environment variables
|
|
@qemu_env = UNSET_VALUE
|
|
|
|
@qemu_use_session = UNSET_VALUE
|
|
|
|
# Use Qemu agent to get ip address
|
|
@qemu_use_agent = UNSET_VALUE
|
|
|
|
@serials = []
|
|
end
|
|
|
|
def boot(device)
|
|
@boot_order << device # append
|
|
end
|
|
|
|
def _get_cdrom_dev(cdroms)
|
|
exist = Hash[cdroms.collect { |x| [x[:dev], true] }]
|
|
# hda - hdc
|
|
curr = 'a'.ord
|
|
while curr <= 'd'.ord
|
|
dev = "hd#{curr.chr}"
|
|
if exist[dev]
|
|
curr += 1
|
|
next
|
|
else
|
|
return dev
|
|
end
|
|
end
|
|
|
|
# is it better to raise our own error, or let Libvirt cause the exception?
|
|
raise 'Only four cdroms may be attached at a time'
|
|
end
|
|
|
|
def _generate_numa
|
|
@numa_nodes.collect { |x|
|
|
# Perform some validation of cpu values
|
|
unless x[:cpus] =~ /^\d+-\d+$/
|
|
raise 'numa_nodes[:cpus] must be in format "integer-integer"'
|
|
end
|
|
|
|
# Convert to KiB
|
|
x[:memory] = x[:memory].to_i * 1024
|
|
}
|
|
|
|
# Grab the value of the last @numa_nodes[:cpus] and verify @cpus matches
|
|
# Note: [:cpus] is zero based and @cpus is not, so we need to +1
|
|
last_cpu = @numa_nodes.last[:cpus]
|
|
last_cpu = last_cpu.scan(/\d+$/)[0]
|
|
last_cpu = last_cpu.to_i + 1
|
|
|
|
if @cpus != last_cpu.to_i
|
|
raise 'The total number of numa_nodes[:cpus] must equal config.cpus'
|
|
end
|
|
|
|
@numa_nodes
|
|
end
|
|
|
|
def cpu_feature(options = {})
|
|
if options[:name].nil? || options[:policy].nil?
|
|
raise 'CPU Feature name AND policy must be specified'
|
|
end
|
|
|
|
@cpu_features = [] if @cpu_features == UNSET_VALUE
|
|
|
|
@cpu_features.push(name: options[:name],
|
|
policy: options[:policy])
|
|
end
|
|
|
|
def hyperv_feature(options = {})
|
|
if options[:name].nil? || options[:state].nil?
|
|
raise 'Feature name AND state must be specified'
|
|
end
|
|
|
|
if options[:name] == 'spinlocks' && options[:retries].nil?
|
|
raise 'Feature spinlocks requires retries parameter'
|
|
end
|
|
|
|
@features_hyperv = [] if @features_hyperv == UNSET_VALUE
|
|
|
|
if options[:name] == 'spinlocks'
|
|
@features_hyperv.push(name: options[:name],
|
|
state: options[:state],
|
|
retries: options[:retries])
|
|
else
|
|
@features_hyperv.push(name: options[:name],
|
|
state: options[:state])
|
|
end
|
|
end
|
|
|
|
def clock_timer(options = {})
|
|
if options[:name].nil?
|
|
raise 'Clock timer name must be specified'
|
|
end
|
|
|
|
options.each do |key, value|
|
|
case key
|
|
when :name, :track, :tickpolicy, :frequency, :mode, :present
|
|
if value.nil?
|
|
raise "Value of timer option #{key} is nil"
|
|
end
|
|
else
|
|
raise "Unknown clock timer option: #{key}"
|
|
end
|
|
end
|
|
|
|
@clock_timers.push(options.dup)
|
|
end
|
|
|
|
def cputopology(options = {})
|
|
if options[:sockets].nil? || options[:cores].nil? || options[:threads].nil?
|
|
raise 'CPU topology must have all of sockets, cores and threads specified'
|
|
end
|
|
|
|
if @cpu_topology == UNSET_VALUE
|
|
@cpu_topology = {}
|
|
end
|
|
|
|
@cpu_topology[:sockets] = options[:sockets]
|
|
@cpu_topology[:cores] = options[:cores]
|
|
@cpu_topology[:threads] = options[:threads]
|
|
end
|
|
|
|
def memorybacking(option, config = {})
|
|
case option
|
|
when :source
|
|
raise 'Source type must be specified' if config[:type].nil?
|
|
when :access
|
|
raise 'Access mode must be specified' if config[:mode].nil?
|
|
when :allocation
|
|
raise 'Allocation mode must be specified' if config[:mode].nil?
|
|
end
|
|
|
|
@memory_backing = [] if @memory_backing == UNSET_VALUE
|
|
@memory_backing.push(name: option,
|
|
config: config)
|
|
end
|
|
|
|
def input(options = {})
|
|
if options[:type].nil? || options[:bus].nil?
|
|
raise 'Input type AND bus must be specified'
|
|
end
|
|
|
|
@inputs = [] if @inputs == UNSET_VALUE
|
|
|
|
@inputs.push(type: options[:type],
|
|
bus: options[:bus])
|
|
end
|
|
|
|
def channel(options = {})
|
|
if options[:type].nil?
|
|
raise 'Channel type must be specified.'
|
|
elsif options[:type] == 'unix' && options[:target_type] == 'guestfwd'
|
|
# Guest forwarding requires a target (ip address) and a port
|
|
if options[:target_address].nil? || options[:target_port].nil? ||
|
|
options[:source_path].nil?
|
|
raise 'guestfwd requires target_address, target_port and source_path'
|
|
end
|
|
end
|
|
|
|
@channels = [] if @channels == UNSET_VALUE
|
|
|
|
@channels.push(type: options[:type],
|
|
source_mode: options[:source_mode],
|
|
source_path: options[:source_path],
|
|
target_address: options[:target_address],
|
|
target_name: options[:target_name],
|
|
target_port: options[:target_port],
|
|
target_type: options[:target_type],
|
|
disabled: options[:disabled],
|
|
)
|
|
end
|
|
|
|
def random(options = {})
|
|
if !options[:model].nil? && options[:model] != 'random'
|
|
raise 'The only supported rng backend is "random".'
|
|
end
|
|
|
|
@rng = {} if @rng == UNSET_VALUE
|
|
|
|
@rng[:model] = options[:model]
|
|
end
|
|
|
|
def pci(options = {})
|
|
if options[:bus].nil? || options[:slot].nil? || options[:function].nil?
|
|
raise 'Bus AND slot AND function must be specified. Check `lspci` for that numbers.'
|
|
end
|
|
|
|
@pcis = [] if @pcis == UNSET_VALUE
|
|
|
|
if options[:domain].nil?
|
|
pci_domain = '0x0000'
|
|
else
|
|
pci_domain = options[:domain]
|
|
end
|
|
|
|
@pcis.push(domain: pci_domain,
|
|
bus: options[:bus],
|
|
slot: options[:slot],
|
|
function: options[:function])
|
|
end
|
|
|
|
def watchdog(options = {})
|
|
if options[:model].nil?
|
|
raise 'Model must be specified.'
|
|
end
|
|
|
|
if @watchdog_dev == UNSET_VALUE
|
|
@watchdog_dev = {}
|
|
end
|
|
|
|
@watchdog_dev[:model] = options[:model]
|
|
@watchdog_dev[:action] = options[:action] || 'reset'
|
|
end
|
|
|
|
|
|
def usb_controller(options = {})
|
|
if options[:model].nil?
|
|
raise 'USB controller model must be specified.'
|
|
end
|
|
|
|
if @usbctl_dev == UNSET_VALUE
|
|
@usbctl_dev = {}
|
|
end
|
|
|
|
@usbctl_dev[:model] = options[:model]
|
|
@usbctl_dev[:ports] = options[:ports] if options[:ports]
|
|
end
|
|
|
|
def usb(options = {})
|
|
if (options[:bus].nil? || options[:device].nil?) && options[:vendor].nil? && options[:product].nil?
|
|
raise 'Bus and device and/or vendor and/or product must be specified. Check `lsusb` for these.'
|
|
end
|
|
|
|
@usbs = [] if @usbs == UNSET_VALUE
|
|
|
|
@usbs.push(bus: options[:bus],
|
|
device: options[:device],
|
|
vendor: options[:vendor],
|
|
product: options[:product],
|
|
startupPolicy: options[:startupPolicy])
|
|
end
|
|
|
|
def redirdev(options = {})
|
|
raise 'Type must be specified.' if options[:type].nil?
|
|
|
|
@redirdevs = [] if @redirdevs == UNSET_VALUE
|
|
|
|
@redirdevs.push(type: options[:type])
|
|
end
|
|
|
|
def redirfilter(options = {})
|
|
raise 'Option allow must be specified.' if options[:allow].nil?
|
|
|
|
@redirfilters = [] if @redirfilters == UNSET_VALUE
|
|
|
|
@redirfilters.push(class: options[:class] || -1,
|
|
vendor: options[:vendor] || -1,
|
|
product: options[:product] || -1,
|
|
version: options[:version] || -1,
|
|
allow: options[:allow])
|
|
end
|
|
|
|
def smartcard(options = {})
|
|
if options[:mode].nil?
|
|
raise 'Option mode must be specified.'
|
|
elsif options[:mode] != 'passthrough'
|
|
raise 'Currently only passthrough mode is supported!'
|
|
elsif options[:type] == 'tcp' && (options[:source_mode].nil? || options[:source_host].nil? || options[:source_service].nil?)
|
|
raise 'If using type "tcp", option "source_mode", "source_host" and "source_service" must be specified.'
|
|
end
|
|
|
|
if @smartcard_dev == UNSET_VALUE
|
|
@smartcard_dev = {}
|
|
end
|
|
|
|
@smartcard_dev[:mode] = options[:mode]
|
|
@smartcard_dev[:type] = options[:type] || 'spicevmc'
|
|
@smartcard_dev[:source_mode] = options[:source_mode] if @smartcard_dev[:type] == 'tcp'
|
|
@smartcard_dev[:source_host] = options[:source_host] if @smartcard_dev[:type] == 'tcp'
|
|
@smartcard_dev[:source_service] = options[:source_service] if @smartcard_dev[:type] == 'tcp'
|
|
end
|
|
|
|
# Disk driver options for primary disk
|
|
def disk_driver(options = {})
|
|
supported_opts = [:cache, :io, :copy_on_read, :discard, :detect_zeroes]
|
|
@disk_driver_opts = options.select { |k,_| supported_opts.include? k }
|
|
end
|
|
|
|
# NOTE: this will run twice for each time it's needed- keep it idempotent
|
|
def storage(storage_type, options = {})
|
|
if storage_type == :file
|
|
if options[:device] == :cdrom
|
|
_handle_cdrom_storage(options)
|
|
else
|
|
_handle_disk_storage(options)
|
|
end
|
|
end
|
|
end
|
|
|
|
def _handle_cdrom_storage(options = {})
|
|
# <disk type="file" device="cdrom">
|
|
# <source file="/home/user/virtio-win-0.1-100.iso"/>
|
|
# <target dev="hdc"/>
|
|
# <readonly/>
|
|
# <address type='drive' controller='0' bus='1' target='0' unit='0'/>
|
|
# </disk>
|
|
#
|
|
# note the target dev will need to be changed with each cdrom drive (hdc, hdd, etc),
|
|
# as will the address unit number (unit=0, unit=1, etc)
|
|
|
|
options = {
|
|
type: 'raw',
|
|
bus: 'ide',
|
|
path: nil
|
|
}.merge(options)
|
|
|
|
cdrom = {
|
|
type: options[:type],
|
|
dev: options[:dev],
|
|
bus: options[:bus],
|
|
path: options[:path]
|
|
}
|
|
|
|
@cdroms << cdrom
|
|
end
|
|
|
|
def _handle_disk_storage(options = {})
|
|
options = {
|
|
type: 'qcow2',
|
|
size: '10G', # matches the fog default
|
|
path: nil,
|
|
bus: 'virtio'
|
|
}.merge(options)
|
|
|
|
disk = {
|
|
device: options[:device],
|
|
type: options[:type],
|
|
size: options[:size],
|
|
path: options[:path],
|
|
bus: options[:bus],
|
|
cache: options[:cache] || 'default',
|
|
allow_existing: options[:allow_existing],
|
|
shareable: options[:shareable],
|
|
serial: options[:serial],
|
|
io: options[:io],
|
|
copy_on_read: options[:copy_on_read],
|
|
discard: options[:discard],
|
|
detect_zeroes: options[:detect_zeroes],
|
|
pool: options[:pool], # overrides storage_pool setting for additional disks
|
|
wwn: options[:wwn],
|
|
}
|
|
|
|
@disks << disk # append
|
|
end
|
|
|
|
def qemuargs(options = {})
|
|
@qemu_args = [] if @qemu_args == UNSET_VALUE
|
|
|
|
@qemu_args << options if options[:value]
|
|
end
|
|
|
|
def qemuenv(options = {})
|
|
@qemu_env = {} if @qemu_env == UNSET_VALUE
|
|
|
|
@qemu_env.merge!(options)
|
|
end
|
|
|
|
def serial(options={})
|
|
options = {
|
|
:type => "pty",
|
|
:source => nil,
|
|
}.merge(options)
|
|
|
|
serial = {
|
|
:type => options[:type],
|
|
:source => options[:source],
|
|
}
|
|
|
|
@serials << serial
|
|
end
|
|
|
|
def _default_uri
|
|
# Determine if any settings except driver provided explicitly, if not
|
|
# and the LIBVIRT_DEFAULT_URI var is set, use that.
|
|
#
|
|
# Skipping driver because that may be set on individual boxes rather
|
|
# than by the user.
|
|
if [
|
|
@connect_via_ssh, @host, @username, @password,
|
|
@id_ssh_key_file, @qemu_use_session, @socket,
|
|
].none?{ |v| v != UNSET_VALUE }
|
|
if ENV.fetch('LIBVIRT_DEFAULT_URI', '') != ""
|
|
@uri = ENV['LIBVIRT_DEFAULT_URI']
|
|
end
|
|
end
|
|
end
|
|
|
|
# code to generate URI from from either the LIBVIRT_URI environment
|
|
# variable or a config moved out of the connect action
|
|
def _generate_uri(qemu_use_session)
|
|
# builds the Libvirt connection URI from the given driver config
|
|
# Setup connection uri.
|
|
uri = @driver.dup
|
|
virt_path = case uri
|
|
when 'qemu', 'kvm'
|
|
qemu_use_session ? '/session' : '/system'
|
|
when 'openvz', 'uml', 'phyp', 'parallels'
|
|
'/system'
|
|
when '@en', 'esx'
|
|
'/'
|
|
when 'vbox', 'vmwarews', 'hyperv'
|
|
'/session'
|
|
else
|
|
raise "Require specify driver #{uri}"
|
|
end
|
|
if uri == 'kvm'
|
|
uri = 'qemu' # use QEMU uri for KVM domain type
|
|
end
|
|
|
|
# turn on ssh if an ssh key file is explicitly provided
|
|
if @connect_via_ssh == UNSET_VALUE && @id_ssh_key_file && @id_ssh_key_file != UNSET_VALUE
|
|
@connect_via_ssh = true
|
|
end
|
|
|
|
params = {}
|
|
|
|
if @connect_via_ssh == true
|
|
finalize_id_ssh_key_file
|
|
|
|
uri += '+ssh://'
|
|
uri += "#{@username}@" if @username && @username != UNSET_VALUE
|
|
|
|
uri += (@host && @host != UNSET_VALUE ? @host : 'localhost')
|
|
|
|
params['no_verify'] = '1'
|
|
params['keyfile'] = @id_ssh_key_file if @id_ssh_key_file
|
|
else
|
|
uri += '://'
|
|
uri += @host if @host && @host != UNSET_VALUE
|
|
end
|
|
|
|
uri += virt_path
|
|
|
|
# set path to Libvirt socket
|
|
params['socket'] = @socket if @socket
|
|
|
|
uri += '?' + params.map { |pair| pair.join('=') }.join('&') unless params.empty?
|
|
uri
|
|
end
|
|
|
|
def _parse_uri(uri)
|
|
begin
|
|
URI.parse(uri)
|
|
rescue
|
|
raise "@uri set to invalid uri '#{uri}'"
|
|
end
|
|
end
|
|
|
|
def finalize!
|
|
_default_uri if @uri == UNSET_VALUE
|
|
|
|
# settings which _generate_uri
|
|
@driver = 'kvm' if @driver == UNSET_VALUE
|
|
@password = nil if @password == UNSET_VALUE
|
|
@socket = nil if @socket == UNSET_VALUE
|
|
|
|
# If uri isn't set then let's build one from various sources.
|
|
# Default to passing false for qemu_use_session if it's not set.
|
|
if @uri == UNSET_VALUE
|
|
@uri = _generate_uri(@qemu_use_session == UNSET_VALUE ? false : @qemu_use_session)
|
|
end
|
|
|
|
finalize_from_uri
|
|
finalize_proxy_command
|
|
|
|
# forward port with id 'ssh'
|
|
@forward_ssh_port = false if @forward_ssh_port == UNSET_VALUE
|
|
|
|
@storage_pool_name = 'default' if @storage_pool_name == UNSET_VALUE
|
|
@snapshot_pool_name = @storage_pool_name if @snapshot_pool_name == UNSET_VALUE
|
|
@storage_pool_path = nil if @storage_pool_path == UNSET_VALUE
|
|
@random_hostname = false if @random_hostname == UNSET_VALUE
|
|
@management_network_device = 'virbr0' if @management_network_device == UNSET_VALUE
|
|
@management_network_name = 'vagrant-libvirt' if @management_network_name == UNSET_VALUE
|
|
@management_network_address = '192.168.121.0/24' if @management_network_address == UNSET_VALUE
|
|
@management_network_mode = 'nat' if @management_network_mode == UNSET_VALUE
|
|
@management_network_mac = nil if @management_network_mac == UNSET_VALUE
|
|
@management_network_guest_ipv6 = 'yes' if @management_network_guest_ipv6 == UNSET_VALUE
|
|
@management_network_autostart = false if @management_network_autostart == UNSET_VALUE
|
|
@management_network_pci_bus = nil if @management_network_pci_bus == UNSET_VALUE
|
|
@management_network_pci_slot = nil if @management_network_pci_slot == UNSET_VALUE
|
|
@management_network_domain = nil if @management_network_domain == UNSET_VALUE
|
|
@management_network_mtu = nil if @management_network_mtu == UNSET_VALUE
|
|
@management_network_keep = false if @management_network_keep == UNSET_VALUE
|
|
|
|
# Domain specific settings.
|
|
@title = '' if @title == UNSET_VALUE
|
|
@description = '' if @description == UNSET_VALUE
|
|
@uuid = '' if @uuid == UNSET_VALUE
|
|
@memory = 512 if @memory == UNSET_VALUE
|
|
@nodeset = nil if @nodeset == UNSET_VALUE
|
|
@memory_backing = [] if @memory_backing == UNSET_VALUE
|
|
@cpus = 1 if @cpus == UNSET_VALUE
|
|
@cpuset = nil if @cpuset == UNSET_VALUE
|
|
@cpu_mode = 'host-model' if @cpu_mode == UNSET_VALUE
|
|
@cpu_model = if (@cpu_model == UNSET_VALUE) && (@cpu_mode == 'custom')
|
|
'qemu64'
|
|
elsif @cpu_mode != 'custom'
|
|
''
|
|
else
|
|
@cpu_model
|
|
end
|
|
@cpu_topology = {} if @cpu_topology == UNSET_VALUE
|
|
@cpu_fallback = 'allow' if @cpu_fallback == UNSET_VALUE
|
|
@cpu_features = [] if @cpu_features == UNSET_VALUE
|
|
@shares = nil if @shares == UNSET_VALUE
|
|
@features = ['acpi','apic','pae'] if @features == UNSET_VALUE
|
|
@features_hyperv = [] if @features_hyperv == UNSET_VALUE
|
|
@clock_offset = 'utc' if @clock_offset == UNSET_VALUE
|
|
@clock_timers = [] if @clock_timers == UNSET_VALUE
|
|
@numa_nodes = @numa_nodes == UNSET_VALUE ? nil : _generate_numa
|
|
@loader = nil if @loader == UNSET_VALUE
|
|
@nvram = nil if @nvram == UNSET_VALUE
|
|
@machine_type = nil if @machine_type == UNSET_VALUE
|
|
@machine_arch = nil if @machine_arch == UNSET_VALUE
|
|
@machine_virtual_size = nil if @machine_virtual_size == UNSET_VALUE
|
|
@disk_bus = 'virtio' if @disk_bus == UNSET_VALUE
|
|
@disk_device = 'vda' if @disk_device == UNSET_VALUE
|
|
@disk_driver_opts = {} if @disk_driver_opts == UNSET_VALUE
|
|
@nic_model_type = nil if @nic_model_type == UNSET_VALUE
|
|
@nested = false if @nested == UNSET_VALUE
|
|
@volume_cache = nil if @volume_cache == UNSET_VALUE
|
|
@kernel = nil if @kernel == UNSET_VALUE
|
|
@cmd_line = '' if @cmd_line == UNSET_VALUE
|
|
@initrd = '' if @initrd == UNSET_VALUE
|
|
@dtb = nil if @dtb == UNSET_VALUE
|
|
@graphics_type = 'vnc' if @graphics_type == UNSET_VALUE
|
|
@graphics_autoport = 'yes' if @graphics_port == UNSET_VALUE
|
|
@graphics_autoport = 'no' if @graphics_port != UNSET_VALUE
|
|
if (@graphics_type != 'vnc' && @graphics_type != 'spice') ||
|
|
@graphics_passwd == UNSET_VALUE
|
|
@graphics_passwd = nil
|
|
end
|
|
@graphics_port = -1 if @graphics_port == UNSET_VALUE
|
|
@graphics_ip = '127.0.0.1' if @graphics_ip == UNSET_VALUE
|
|
@video_type = 'cirrus' if @video_type == UNSET_VALUE
|
|
@video_vram = 16384 if @video_vram == UNSET_VALUE
|
|
@video_accel3d = false if @video_accel3d == UNSET_VALUE
|
|
@graphics_gl = @video_accel3d if @graphics_gl == UNSET_VALUE
|
|
@sound_type = nil if @sound_type == UNSET_VALUE
|
|
@keymap = 'en-us' if @keymap == UNSET_VALUE
|
|
@kvm_hidden = false if @kvm_hidden == UNSET_VALUE
|
|
@tpm_model = 'tpm-tis' if @tpm_model == UNSET_VALUE
|
|
@tpm_type = 'passthrough' if @tpm_type == UNSET_VALUE
|
|
@tpm_path = nil if @tpm_path == UNSET_VALUE
|
|
@tpm_version = nil if @tpm_version == UNSET_VALUE
|
|
@memballoon_enabled = nil if @memballoon_enabled == UNSET_VALUE
|
|
@memballoon_model = 'virtio' if @memballoon_model == UNSET_VALUE
|
|
@memballoon_pci_bus = '0x00' if @memballoon_pci_bus == UNSET_VALUE
|
|
@memballoon_pci_slot = '0x0f' if @memballoon_pci_slot == UNSET_VALUE
|
|
@nic_adapter_count = 8 if @nic_adapter_count == UNSET_VALUE
|
|
@emulator_path = nil if @emulator_path == UNSET_VALUE
|
|
|
|
# Boot order
|
|
@boot_order = [] if @boot_order == UNSET_VALUE
|
|
|
|
# Storage
|
|
@disks = [] if @disks == UNSET_VALUE
|
|
@cdroms = [] if @cdroms == UNSET_VALUE
|
|
@cdroms.map! do |cdrom|
|
|
cdrom[:dev] = _get_cdrom_dev(@cdroms) if cdrom[:dev].nil?
|
|
cdrom
|
|
end
|
|
|
|
# Inputs
|
|
@inputs = [{ type: 'mouse', bus: 'ps2' }] if @inputs == UNSET_VALUE
|
|
|
|
# Channels
|
|
if @channels == UNSET_VALUE
|
|
@channels = []
|
|
if @qemu_use_agent == true
|
|
if @channels.all? { |channel| !channel.fetch(:target_name, '').start_with?('org.qemu.guest_agent.') }
|
|
channel(:type => 'unix', :target_name => 'org.qemu.guest_agent.0', :target_type => 'virtio')
|
|
end
|
|
end
|
|
end
|
|
|
|
# filter channels of anything explicitly disabled so it's possible to inject an entry to
|
|
# avoid the automatic addition of the guest_agent above, and disable it from subsequent use.
|
|
@channels = @channels.reject { |channel| channel[:disabled] }.tap {|channel| channel.delete(:disabled) }
|
|
|
|
# PCI device passthrough
|
|
@pcis = [] if @pcis == UNSET_VALUE
|
|
|
|
# Random number generator passthrough
|
|
@rng = {} if @rng == UNSET_VALUE
|
|
|
|
# Watchdog device
|
|
@watchdog_dev = {} if @watchdog_dev == UNSET_VALUE
|
|
|
|
# USB device passthrough
|
|
@usbs = [] if @usbs == UNSET_VALUE
|
|
|
|
# Redirected devices
|
|
@redirdevs = [] if @redirdevs == UNSET_VALUE
|
|
@redirfilters = [] if @redirfilters == UNSET_VALUE
|
|
|
|
# USB controller
|
|
if @usbctl_dev == UNSET_VALUE
|
|
@usbctl_dev = if !@usbs.empty? or !@redirdevs.empty? then {:model => 'qemu-xhci'} else {} end
|
|
end
|
|
|
|
# smartcard device
|
|
@smartcard_dev = {} if @smartcard_dev == UNSET_VALUE
|
|
|
|
# Suspend mode
|
|
@suspend_mode = 'pause' if @suspend_mode == UNSET_VALUE
|
|
|
|
# Autostart
|
|
@autostart = false if @autostart == UNSET_VALUE
|
|
|
|
# Attach mgmt network
|
|
@mgmt_attach = true if @mgmt_attach == UNSET_VALUE
|
|
|
|
# Additional QEMU commandline arguments
|
|
@qemu_args = [] if @qemu_args == UNSET_VALUE
|
|
|
|
# Additional QEMU commandline environment variables
|
|
@qemu_env = {} if @qemu_env == UNSET_VALUE
|
|
|
|
@qemu_use_agent = false if @qemu_use_agent == UNSET_VALUE
|
|
|
|
@serials = [{:type => 'pty', :source => nil}] if @serials == []
|
|
end
|
|
|
|
def validate(machine)
|
|
errors = _detected_errors
|
|
|
|
# technically this shouldn't occur, but ensure that if somehow it does, it gets rejected.
|
|
if @cpu_mode == 'host-passthrough' && @cpu_model != ''
|
|
errors << "cannot set cpu_model with cpu_mode of 'host-passthrough'. leave model unset or switch mode."
|
|
end
|
|
|
|
# The @uri and @qemu_use_session should not conflict
|
|
uri = _parse_uri(@uri)
|
|
if (uri.scheme.start_with? "qemu") && (uri.path.include? "session")
|
|
if @qemu_use_session != true
|
|
errors << "the URI and qemu_use_session configuration conflict: uri:'#{@uri}' qemu_use_session:'#{@qemu_use_session}'"
|
|
end
|
|
end
|
|
|
|
unless @qemu_use_agent == true || @qemu_use_agent == false
|
|
errors << "libvirt.qemu_use_agent must be a boolean."
|
|
end
|
|
|
|
if @qemu_use_agent == true
|
|
# if qemu agent is used to optain domain ip configuration, at least
|
|
# one qemu channel has to be configured. As there are various options,
|
|
# error out and leave configuration to the user
|
|
unless machine.provider_config.channels.any? { |channel| channel[:target_name].start_with?("org.qemu.guest_agent") }
|
|
errors << "qemu agent option enabled, but no qemu agent channel configured: please add at least one qemu agent channel to vagrant config"
|
|
end
|
|
end
|
|
|
|
machine.provider_config.disks.each do |disk|
|
|
if disk[:path] && (disk[:path][0] == '/')
|
|
errors << "absolute volume paths like '#{disk[:path]}' not yet supported"
|
|
end
|
|
end
|
|
|
|
machine.provider_config.serials.each do |serial|
|
|
if serial[:source] and serial[:source][:path].nil?
|
|
errors << "serial :source requires :path to be defined"
|
|
end
|
|
end
|
|
|
|
# this won't be able to fully resolve the disks until the box has
|
|
# been downloaded and any devices that need to be assigned to the
|
|
# disks contained have been allocated
|
|
disk_resolver = ::VagrantPlugins::ProviderLibvirt::Util::DiskDeviceResolver.new
|
|
begin
|
|
disk_resolver.resolve(machine.provider_config.disks)
|
|
rescue Errors::VagrantLibvirtError => e
|
|
errors << "#{e}"
|
|
end
|
|
|
|
machine.config.vm.networks.each do |_type, opts|
|
|
if opts[:mac]
|
|
if opts[:mac] =~ /\A([0-9a-fA-F]{12})\z/
|
|
opts[:mac] = opts[:mac].scan(/../).join(':')
|
|
end
|
|
unless opts[:mac] =~ /\A([0-9a-fA-F]{2}:){5}([0-9a-fA-F]{2})\z/
|
|
errors << "Configured NIC MAC '#{opts[:mac]}' is not in 'xx:xx:xx:xx:xx:xx' or 'xxxxxxxxxxxx' format"
|
|
end
|
|
end
|
|
end
|
|
|
|
if !machine.provider_config.volume_cache.nil? and machine.provider_config.volume_cache != UNSET_VALUE
|
|
machine.ui.warn("Libvirt Provider: volume_cache is deprecated. Use disk_driver :cache => '#{machine.provider_config.volume_cache}' instead.")
|
|
|
|
if !machine.provider_config.disk_driver_opts.empty?
|
|
machine.ui.warn("Libvirt Provider: volume_cache has no effect when disk_driver is defined.")
|
|
end
|
|
end
|
|
|
|
{ 'Libvirt Provider' => errors }
|
|
end
|
|
|
|
def merge(other)
|
|
super.tap do |result|
|
|
c = disks.dup
|
|
c += other.disks
|
|
result.disks = c
|
|
|
|
c = cdroms.dup
|
|
c += other.cdroms
|
|
result.cdroms = c
|
|
|
|
result.disk_driver_opts = disk_driver_opts.merge(other.disk_driver_opts)
|
|
|
|
c = clock_timers.dup
|
|
c += other.clock_timers
|
|
result.clock_timers = c
|
|
|
|
c = qemu_env != UNSET_VALUE ? qemu_env.dup : {}
|
|
c.merge!(other.qemu_env) if other.qemu_env != UNSET_VALUE
|
|
result.qemu_env = c
|
|
|
|
s = serials.dup
|
|
s += other.serials
|
|
result.serials = s
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def finalize_from_uri
|
|
# Parse uri to extract individual components
|
|
uri = _parse_uri(@uri)
|
|
|
|
system_uri = uri.dup
|
|
system_uri.path = '/system'
|
|
@system_uri = system_uri.to_s if @system_uri == UNSET_VALUE
|
|
|
|
# only set @connect_via_ssh if not explicitly to avoid overriding
|
|
# and allow an error to occur if the @uri and @connect_via_ssh disagree
|
|
@connect_via_ssh = uri.scheme.include? "ssh" if @connect_via_ssh == UNSET_VALUE
|
|
|
|
# Set qemu_use_session based on the URI if it wasn't set by the user
|
|
if @qemu_use_session == UNSET_VALUE
|
|
if (uri.scheme.start_with? "qemu") && (uri.path.include? "session")
|
|
@qemu_use_session = true
|
|
else
|
|
@qemu_use_session = false
|
|
end
|
|
end
|
|
|
|
# Extract host values from uri if provided, otherwise nil
|
|
@host = uri.host
|
|
@port = uri.port
|
|
# only override username if there is a value provided
|
|
@username = nil if @username == UNSET_VALUE
|
|
@username = uri.user if uri.user
|
|
if uri.query
|
|
params = CGI.parse(uri.query)
|
|
@id_ssh_key_file = params['keyfile'].first if params.has_key?('keyfile')
|
|
end
|
|
|
|
finalize_id_ssh_key_file
|
|
end
|
|
|
|
def resolve_ssh_key_file(key_file)
|
|
# set ssh key for access to Libvirt host
|
|
# if no slash, prepend $HOME/.ssh/
|
|
key_file = "#{ENV['HOME']}/.ssh/#{key_file}" if key_file && key_file !~ /\A\//
|
|
|
|
key_file
|
|
end
|
|
|
|
def finalize_id_ssh_key_file
|
|
# resolve based on the following roles
|
|
# 1) if @connect_via_ssh is set to true, and id_ssh_key_file not current set,
|
|
# set default if the file exists
|
|
# 2) if supplied the key name, attempt to expand based on user home
|
|
# 3) otherwise set to nil
|
|
|
|
if @connect_via_ssh == true && @id_ssh_key_file == UNSET_VALUE
|
|
# set default if using ssh while allowing a user using nil to disable this
|
|
id_ssh_key_file = resolve_ssh_key_file('id_rsa')
|
|
id_ssh_key_file = nil if !File.file?(id_ssh_key_file)
|
|
elsif @id_ssh_key_file != UNSET_VALUE
|
|
id_ssh_key_file = resolve_ssh_key_file(@id_ssh_key_file)
|
|
else
|
|
id_ssh_key_file = nil
|
|
end
|
|
|
|
@id_ssh_key_file = id_ssh_key_file
|
|
end
|
|
|
|
def finalize_proxy_command
|
|
if @connect_via_ssh
|
|
if @proxy_command == UNSET_VALUE
|
|
proxy_command = "ssh '#{@host}' "
|
|
proxy_command += "-p #{@port} " if @port
|
|
proxy_command += "-l '#{@username}' " if @username
|
|
proxy_command += "-i '#{@id_ssh_key_file}' " if @id_ssh_key_file
|
|
proxy_command += '-W %h:%p'
|
|
else
|
|
inputs = { host: @host }
|
|
inputs << { port: @port } if @port
|
|
inputs[:username] = @username if @username
|
|
inputs[:id_ssh_key_file] = @id_ssh_key_file if @id_ssh_key_file
|
|
|
|
proxy_command = String.new(@proxy_command)
|
|
# avoid needing to escape '%' symbols
|
|
inputs.each do |key, value|
|
|
proxy_command.gsub!("{#{key}}", value)
|
|
end
|
|
end
|
|
|
|
@proxy_command = proxy_command
|
|
else
|
|
@proxy_command = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|