mirror of
https://github.com/vagrant-libvirt/vagrant-libvirt.git
synced 2025-02-25 18:55:27 -06:00
Facilitate disabling serial devices by allowing for the configuration to explicitly set serials to an empty array and ignoring adding the default serial if this is the case.
1168 lines
40 KiB
Ruby
1168 lines
40 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 = UNSET_VALUE
|
|
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={})
|
|
@serials = [] if @serials == UNSET_VALUE
|
|
|
|
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 == UNSET_VALUE
|
|
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
|
|
|
|
if serials != UNSET_VALUE
|
|
s = serials.dup
|
|
s += other.serials
|
|
result.serials = s
|
|
end
|
|
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
|