diff --git a/README.md b/README.md index 0364193..9075191 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ can help a lot :-) - [SSH Access To VM](#ssh-access-to-vm) - [Forwarded Ports](#forwarded-ports) - [Synced Folders](#synced-folders) +- [QEMU Session Support](#qemu-session-support) - [Customized Graphics](#customized-graphics) - [Box Format](#box-format) - [Create Box](#create-box) @@ -1084,7 +1085,7 @@ Name of network "foreman_managed" is key for define boot order ```ruby config.vm.define :pxeclient do |pxeclient| pxeclient.vm.network :private_network,ip: '10.0.0.5', - libvirt__network_name: "foreman_managed", + libvirt__network_name: "foreman_managed". libvirt__dhcp_enabled: false, libvirt__host_ip: '10.0.0.1' @@ -1163,6 +1164,34 @@ Further documentation on using 9p can be found in [kernel docs](https://www.kern **SECURITY NOTE:** for remote libvirt, nfs synced folders requires a bridged public network interface and you must connect to libvirt via ssh. +## QEMU Session Support + +vagrant-libvirt supports using the QEMU session connection to maintain Vagrant VMs. As the session connection does not have root access to the system features which require root will not work. Access to networks created by the system QEMU connection can be granted by using the [QEMU bridge helper](https://wiki.qemu.org/Features/HelperNetworking). The bridge helper is enabled by default on some distros but may need to be enabled/installed on others. + +An example configuration of a machine using the QEMU session connection: + +```ruby +Vagrant.configure("2") do |config| + config.vm.provider :libvirt do |libvirt| + # Use QEMU session instead of system connection + libvirt.qemu_use_session = true + # URI of QEMU session connection, default is as below + libvirt.uri = 'qemu:///session' + # URI of QEMU system connection, use to obtain IP address for management + libvirt.system_uri = 'qemu:///system' + # Path to store libvirt images for the virtual machine, default is as ~/.local/share/libvirt/images + libvirt.storage_pool_path = '/home/user/.local/share/libvirt/images' + # Management network device + libvirt.management_network_device = 'virbr0' + end + + # Public network configuration using existing network device + # Note: Private networks do not work with QEMU session enabled as root access is required to create new network devices + config.vm.network :public_network, :dev => "virbr1", + :mode => "bridge", + :type => "bridge" +end +``` ## Customized Graphics diff --git a/lib/vagrant-libvirt/action/create_domain.rb b/lib/vagrant-libvirt/action/create_domain.rb index f146c55..8077b67 100644 --- a/lib/vagrant-libvirt/action/create_domain.rb +++ b/lib/vagrant-libvirt/action/create_domain.rb @@ -129,13 +129,15 @@ module VagrantPlugins # 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_pool = env[:machine].provider.driver.connection.client.lookup_storage_pool_by_name(@storage_pool_name) - raise Errors::NoStoragePool if storage_pool.nil? - xml = Nokogiri::XML(storage_pool.xml_desc) - storage_prefix = xml.xpath('/pool/target/path').inner_text.to_s + '/' + if !config.qemu_use_session + if env[:machine].config.vm.box + storage_prefix = File.dirname(@domain_volume_path) + '/' # steal + else + storage_pool = env[:machine].provider.driver.connection.client.lookup_storage_pool_by_name(@storage_pool_name) + raise Errors::NoStoragePool if storage_pool.nil? + xml = Nokogiri::XML(storage_pool.xml_desc) + storage_prefix = xml.xpath('/pool/target/path').inner_text.to_s + '/' + end end @disks.each do |disk| diff --git a/lib/vagrant-libvirt/action/create_domain_volume.rb b/lib/vagrant-libvirt/action/create_domain_volume.rb index 45aa5c2..6633788 100644 --- a/lib/vagrant-libvirt/action/create_domain_volume.rb +++ b/lib/vagrant-libvirt/action/create_domain_volume.rb @@ -8,6 +8,7 @@ module VagrantPlugins # image as new domain volume. class CreateDomainVolume include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate + include VagrantPlugins::ProviderLibvirt::Util::StorageUtil def initialize(app, _env) @logger = Log4r::Logger.new('vagrant_libvirt::action::create_domain_volume') @@ -48,8 +49,8 @@ module VagrantPlugins xml.target do xml.format(type: 'qcow2') xml.permissions do - xml.owner 0 - xml.group 0 + xml.owner storage_uid(env) + xml.group storage_gid(env) xml.mode '0600' xml.label 'virt_image_t' end @@ -58,8 +59,8 @@ module VagrantPlugins xml.path(@backing_file) xml.format(type: 'qcow2') xml.permissions do - xml.owner 0 - xml.group 0 + xml.owner storage_uid(env) + xml.group storage_gid(env) xml.mode '0600' xml.label 'virt_image_t' end diff --git a/lib/vagrant-libvirt/action/create_networks.rb b/lib/vagrant-libvirt/action/create_networks.rb index 5bb6809..f5ee5b5 100644 --- a/lib/vagrant-libvirt/action/create_networks.rb +++ b/lib/vagrant-libvirt/action/create_networks.rb @@ -27,6 +27,11 @@ module VagrantPlugins end def call(env) + if env[:machine].provider_config.qemu_use_session + @app.call(env) + return + end + # only one vm at a time should try to set up networks # otherwise they'll have inconsitent views of current state # and conduct redundant operations that cause errors diff --git a/lib/vagrant-libvirt/action/destroy_networks.rb b/lib/vagrant-libvirt/action/destroy_networks.rb index 9039bb6..ede4c93 100644 --- a/lib/vagrant-libvirt/action/destroy_networks.rb +++ b/lib/vagrant-libvirt/action/destroy_networks.rb @@ -13,6 +13,11 @@ module VagrantPlugins end def call(env) + if env[:machine].provider_config.qemu_use_session + @app.call(env) + return + end + # If there were some networks created for this machine, in machines # data directory, created_networks file holds UUIDs of each network. created_networks_file = env[:machine].data_dir + 'created_networks' diff --git a/lib/vagrant-libvirt/action/handle_box_image.rb b/lib/vagrant-libvirt/action/handle_box_image.rb index a4f2255..990ea7d 100644 --- a/lib/vagrant-libvirt/action/handle_box_image.rb +++ b/lib/vagrant-libvirt/action/handle_box_image.rb @@ -4,6 +4,10 @@ module VagrantPlugins module ProviderLibvirt module Action class HandleBoxImage + include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate + include VagrantPlugins::ProviderLibvirt::Util::StorageUtil + + @@lock = Mutex.new def initialize(app, _env) @@ -31,11 +35,12 @@ module VagrantPlugins config = env[:machine].provider_config box_image_file = env[:machine].box.directory.join('box.img').to_s env[:box_volume_name] = env[:machine].box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-') - env[:box_volume_name] << "_vagrant_box_image_#{begin - env[:machine].box.version.to_s - rescue - '' - end}.img" + env[:box_volume_name] << "_vagrant_box_image_#{ + begin + env[:machine].box.version.to_s + rescue + '' + end}.img" # Override box_virtual_size if config.machine_virtual_size @@ -44,7 +49,7 @@ module VagrantPlugins # is not supported and will be ignored env[:ui].warn I18n.t( 'vagrant_libvirt.warnings.ignoring_virtual_size_too_small', - requested: config.machine_virtual_size, minimum: box_virtual_size + requested: config.machine_virtual_size, minimum: box_virtual_size ) else env[:ui].info I18n.t('vagrant_libvirt.manual_resize_required') @@ -75,17 +80,41 @@ module VagrantPlugins message = "Creating volume #{env[:box_volume_name]}" message << " in storage pool #{config.storage_pool_name}." @logger.info(message) - begin - fog_volume = env[:machine].provider.driver.connection.volumes.create( - name: env[:box_volume_name], - allocation: "#{box_image_size / 1024 / 1024}M", - capacity: "#{box_virtual_size}G", - format_type: box_format, - pool_name: config.storage_pool_name - ) - rescue Fog::Errors::Error => e - raise Errors::FogCreateVolumeError, - error_message: e.message + + if config.qemu_use_session + begin + @name = env[:box_volume_name] + @allocation = "#{box_image_size / 1024 / 1024}M" + @capacity = "#{box_virtual_size}G" + @format_type = box_format ? box_format : 'raw' + + @storage_volume_uid = storage_uid env + @storage_volume_gid = storage_gid env + + libvirt_client = env[:machine].provider.driver.connection.client + libvirt_pool = libvirt_client.lookup_storage_pool_by_name( + config.storage_pool_name + ) + libvirt_volume = libvirt_pool.create_volume_xml( + to_xml('default_storage_volume') + ) + rescue => e + raise Errors::CreatingVolumeError, + error_message: e.message + end + else + begin + fog_volume = env[:machine].provider.driver.connection.volumes.create( + name: env[:box_volume_name], + allocation: "#{box_image_size / 1024 / 1024}M", + capacity: "#{box_virtual_size}G", + format_type: box_format, + pool_name: config.storage_pool_name + ) + rescue Fog::Errors::Error => e + raise Errors::FogCreateVolumeError, + error_message: e.message + end end # Upload box image to storage pool @@ -103,7 +132,11 @@ module VagrantPlugins # storage pool. if env[:interrupted] || !ret begin - fog_volume.destroy + if config.qemu_use_session + libvirt_volume.delete + else + fog_volume.destroy + end rescue nil end @@ -113,6 +146,19 @@ module VagrantPlugins @app.call(env) end + def split_size_unit(text) + if text.kind_of? Integer + # if text is an integer, match will fail + size = text + unit = 'G' + else + matcher = text.match(/(\d+)(.+)/) + size = matcher[1] + unit = matcher[2] + end + [size, unit] + end + protected # Fog libvirt currently doesn't support uploading images to storage diff --git a/lib/vagrant-libvirt/action/handle_storage_pool.rb b/lib/vagrant-libvirt/action/handle_storage_pool.rb index 924f80d..04be07c 100644 --- a/lib/vagrant-libvirt/action/handle_storage_pool.rb +++ b/lib/vagrant-libvirt/action/handle_storage_pool.rb @@ -5,6 +5,8 @@ module VagrantPlugins module Action class HandleStoragePool include VagrantPlugins::ProviderLibvirt::Util::ErbTemplate + include VagrantPlugins::ProviderLibvirt::Util::StorageUtil + @@lock = Mutex.new @@ -37,6 +39,9 @@ module VagrantPlugins # Fog libvirt currently doesn't support creating pools. Use # ruby-libvirt client directly. begin + @storage_pool_path = storage_pool_path(env) + @storage_pool_uid = storage_uid(env) + @storage_pool_gid = storage_gid(env) libvirt_pool = env[:machine].provider.driver.connection.client.define_storage_pool_xml( to_xml('default_storage_pool') ) diff --git a/lib/vagrant-libvirt/action/wait_till_up.rb b/lib/vagrant-libvirt/action/wait_till_up.rb index 3ee77ca..972f4e4 100644 --- a/lib/vagrant-libvirt/action/wait_till_up.rb +++ b/lib/vagrant-libvirt/action/wait_till_up.rb @@ -31,22 +31,39 @@ module VagrantPlugins # from arp table, either localy or remotely via ssh, if libvirt # connection was done via ssh. env[:ip_address] = nil - env[:metrics]['instance_ip_time'] = Util::Timer.time do - @logger.debug("Searching for IP for MAC address: #{domain.mac}") - env[:ui].info(I18n.t('vagrant_libvirt.waiting_for_ip')) - retryable(on: Fog::Errors::TimeoutError, tries: 300) do - # If we're interrupted don't worry about waiting - return terminate(env) if env[:interrupted] + @logger.debug("Searching for IP for MAC address: #{domain.mac}") + env[:ui].info(I18n.t('vagrant_libvirt.waiting_for_ip')) - # Wait for domain to obtain an ip address - domain.wait_for(2) do - addresses.each_pair do |_type, ip| - env[:ip_address] = ip[0] unless ip[0].nil? + if env[:machine].provider_config.qemu_use_session + env[:metrics]['instance_ip_time'] = Util::Timer.time do + retryable(on: Fog::Errors::TimeoutError, tries: 300) do + # If we're interrupted don't worry about waiting + return terminate(env) if env[:interrupted] + + # Wait for domain to obtain an ip address + domain.wait_for(2) do + env[:ip_address] = env[:machine].provider.driver.get_ipaddress_system(domain.mac) + !env[:ip_address].nil? + end + end + end + else + env[:metrics]['instance_ip_time'] = Util::Timer.time do + retryable(on: Fog::Errors::TimeoutError, tries: 300) do + # If we're interrupted don't worry about waiting + return terminate(env) if env[:interrupted] + + # Wait for domain to obtain an ip address + domain.wait_for(2) do + addresses.each_pair do |_type, ip| + env[:ip_address] = ip[0] unless ip[0].nil? + end + !env[:ip_address].nil? end - !env[:ip_address].nil? end end end + @logger.info("Got IP address #{env[:ip_address]}") @logger.info("Time for getting IP: #{env[:metrics]['instance_ip_time']}") diff --git a/lib/vagrant-libvirt/config.rb b/lib/vagrant-libvirt/config.rb index 196e271..73d6e5d 100644 --- a/lib/vagrant-libvirt/config.rb +++ b/lib/vagrant-libvirt/config.rb @@ -40,11 +40,13 @@ module VagrantPlugins # Libvirt storage pool name, where box image and instance snapshots will # be stored. attr_accessor :storage_pool_name + attr_accessor :storage_pool_path # 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 @@ -54,6 +56,9 @@ module VagrantPlugins attr_accessor :management_network_pci_bus attr_accessor :management_network_pci_slot + # System connection information + attr_accessor :system_uri + # Default host prefix (alternative to use project folder name) attr_accessor :default_prefix @@ -149,6 +154,9 @@ module VagrantPlugins # Additional qemuargs arguments attr_accessor :qemu_args + # Use qemu session instead of system + attr_accessor :qemu_use_session + def initialize @uri = UNSET_VALUE @driver = UNSET_VALUE @@ -159,6 +167,7 @@ module VagrantPlugins @id_ssh_key_file = UNSET_VALUE @storage_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 @@ -168,6 +177,9 @@ module VagrantPlugins @management_network_pci_slot = UNSET_VALUE @management_network_pci_bus = UNSET_VALUE + # System connection information + @system_uri = UNSET_VALUE + # Domain specific settings. @uuid = UNSET_VALUE @memory = UNSET_VALUE @@ -253,6 +265,7 @@ module VagrantPlugins @mgmt_attach = UNSET_VALUE @qemu_args = [] + @qemu_use_session = UNSET_VALUE end def boot(device) @@ -334,7 +347,7 @@ module VagrantPlugins @cpu_topology[:sockets] = options[:sockets] @cpu_topology[:cores] = options[:cores] - @cpu_topology[:threads] = options[:threads] + @cpu_topology[:threads] = options[:threads] end def memorybacking(option, config = {}) @@ -544,7 +557,9 @@ module VagrantPlugins # Setup connection uri. uri = @driver.dup virt_path = case uri - when 'qemu', 'openvz', 'uml', 'phyp', 'parallels', 'kvm' + when 'qemu', 'kvm' + @qemu_use_session ? '/session' : '/system' + when 'openvz', 'uml', 'phyp', 'parallels' '/system' when '@en', 'esx' '/' @@ -594,7 +609,9 @@ module VagrantPlugins @password = nil if @password == UNSET_VALUE @id_ssh_key_file = 'id_rsa' if @id_ssh_key_file == UNSET_VALUE @storage_pool_name = 'default' if @storage_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 @@ -603,6 +620,9 @@ module VagrantPlugins @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 + @system_uri = 'qemu:///system' if @system_uri == UNSET_VALUE + + @qemu_use_session = false if @qemu_use_session == UNSET_VALUE # generate a URI if none is supplied @uri = _generate_uri if @uri == UNSET_VALUE diff --git a/lib/vagrant-libvirt/driver.rb b/lib/vagrant-libvirt/driver.rb index c9482a0..8d64ac4 100644 --- a/lib/vagrant-libvirt/driver.rb +++ b/lib/vagrant-libvirt/driver.rb @@ -1,4 +1,5 @@ require 'fog/libvirt' +require 'libvirt' require 'log4r' module VagrantPlugins @@ -10,6 +11,7 @@ module VagrantPlugins # settings as a key to allow per machine connection attributes # to be used. @@connection = nil + @@system_connection = nil def initialize(machine) @logger = Log4r::Logger.new('vagrant_libvirt::driver') @@ -47,6 +49,17 @@ module VagrantPlugins @@connection end + def system_connection + # If already connected to libvirt, just use it and don't connect + # again. + return @@system_connection if @@system_connection + + config = @machine.provider_config + + @@system_connection = Libvirt::open_read_only(config.system_uri) + @@system_connection + end + def get_domain(mid) begin domain = connection.servers.get(mid) @@ -70,6 +83,9 @@ module VagrantPlugins def get_ipaddress(machine) # Find the machine domain = get_domain(machine.id) + if @machine.provider_config.qemu_use_session + return get_ipaddress_system domain.mac + end if domain.nil? # The machine can't be found @@ -99,6 +115,19 @@ module VagrantPlugins ip_address end + def get_ipaddress_system(mac) + ip_address = nil + + system_connection.list_all_networks.each do |net| + leases = net.dhcp_leases(mac, 0) + # Assume the lease expiring last is the current IP address + ip_address = leases.sort_by { |lse| lse["expirytime"] }.last["ipaddr"] if !leases.empty? + break if ip_address + end + + return ip_address + end + def state(machine) # may be other error states with initial retreival we can't handle begin diff --git a/lib/vagrant-libvirt/errors.rb b/lib/vagrant-libvirt/errors.rb index 506b973..52ca770 100644 --- a/lib/vagrant-libvirt/errors.rb +++ b/lib/vagrant-libvirt/errors.rb @@ -29,6 +29,10 @@ module VagrantPlugins error_key(:creating_storage_pool_error) end + class CreatingVolumeError < VagrantLibvirtError + error_key(:creating_volume_error) + end + class ImageUploadError < VagrantLibvirtError error_key(:image_upload_error) end diff --git a/lib/vagrant-libvirt/templates/default_storage_pool.xml.erb b/lib/vagrant-libvirt/templates/default_storage_pool.xml.erb index b63eea7..3e560ec 100644 --- a/lib/vagrant-libvirt/templates/default_storage_pool.xml.erb +++ b/lib/vagrant-libvirt/templates/default_storage_pool.xml.erb @@ -3,11 +3,11 @@ - /var/lib/libvirt/images + <%= @storage_pool_path %> 0755 - -1 - -1 + <%= @storage_pool_uid %> + <%= @storage_pool_gid %> diff --git a/lib/vagrant-libvirt/templates/default_storage_volume.xml.erb b/lib/vagrant-libvirt/templates/default_storage_volume.xml.erb new file mode 100644 index 0000000..c4b2268 --- /dev/null +++ b/lib/vagrant-libvirt/templates/default_storage_volume.xml.erb @@ -0,0 +1,14 @@ + + <%= @name %> + <%= split_size_unit(@allocation)[0] %> + <%= split_size_unit(@capacity)[0] %> + + + + <%= @storage_volume_uid %> + <%= @storage_volume_gid %> + 0744 + + + + diff --git a/lib/vagrant-libvirt/util.rb b/lib/vagrant-libvirt/util.rb index f81c607..5efb3d9 100644 --- a/lib/vagrant-libvirt/util.rb +++ b/lib/vagrant-libvirt/util.rb @@ -5,6 +5,7 @@ module VagrantPlugins autoload :Collection, 'vagrant-libvirt/util/collection' autoload :Timer, 'vagrant-libvirt/util/timer' autoload :NetworkUtil, 'vagrant-libvirt/util/network_util' + autoload :StorageUtil, 'vagrant-libvirt/util/storage_util' autoload :ErrorCodes, 'vagrant-libvirt/util/error_codes' end end diff --git a/lib/vagrant-libvirt/util/network_util.rb b/lib/vagrant-libvirt/util/network_util.rb index 1234da3..610bbbf 100644 --- a/lib/vagrant-libvirt/util/network_util.rb +++ b/lib/vagrant-libvirt/util/network_util.rb @@ -8,6 +8,8 @@ module VagrantPlugins include Vagrant::Util::NetworkIP def configured_networks(env, logger) + qemu_use_session = env[:machine].provider_config.qemu_use_session + management_network_device = env[:machine].provider_config.management_network_device management_network_name = env[:machine].provider_config.management_network_name management_network_address = env[:machine].provider_config.management_network_address management_network_mode = env[:machine].provider_config.management_network_mode @@ -33,18 +35,31 @@ module VagrantPlugins error_message: "#{management_network_address} does not include both an address and subnet mask" end - management_network_options = { - iface_type: :private_network, - network_name: management_network_name, - ip: Regexp.last_match(1), - netmask: Regexp.last_match(2), - dhcp_enabled: true, - forward_mode: management_network_mode, - guest_ipv6: management_network_guest_ipv6, - autostart: management_network_autostart, - bus: management_network_pci_bus, - slot: management_network_pci_slot - } + if qemu_use_session + management_network_options = { + iface_type: :public_network, + dev: management_network_device, + mode: 'bridge', + type: 'bridge', + bus: management_network_pci_bus, + slot: management_network_pci_slot + } + else + management_network_options = { + iface_type: :private_network, + network_name: management_network_name, + ip: Regexp.last_match(1), + netmask: Regexp.last_match(2), + dhcp_enabled: true, + forward_mode: management_network_mode, + guest_ipv6: management_network_guest_ipv6, + autostart: management_network_autostart, + bus: management_network_pci_bus, + slot: management_network_pci_slot + } + end + + unless management_network_mac.nil? management_network_options[:mac] = management_network_mac diff --git a/lib/vagrant-libvirt/util/storage_util.rb b/lib/vagrant-libvirt/util/storage_util.rb new file mode 100644 index 0000000..2b03547 --- /dev/null +++ b/lib/vagrant-libvirt/util/storage_util.rb @@ -0,0 +1,27 @@ + +module VagrantPlugins + module ProviderLibvirt + module Util + module StorageUtil + def storage_uid(env) + env[:machine].provider_config.qemu_use_session ? Process.uid : 0 + end + + def storage_gid(env) + env[:machine].provider_config.qemu_use_session ? Process.gid : 0 + end + + def storage_pool_path(env) + if env[:machine].provider_config.storage_pool_path + env[:machine].provider_config.storage_pool_path + elsif env[:machine].provider_config.qemu_use_session + File.expand_path('~/.local/share/libvirt/images') + else + '/var/lib/libvirt/images' + end + end + end + end + end +end + diff --git a/locales/en.yml b/locales/en.yml index cd88b56..207f881 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -107,6 +107,8 @@ en: `vagrant up` command again. creating_storage_pool_error: |- There was error while creating libvirt storage pool: %{error_message} + creating_volume_error: |- + There was error while creating libvirt volume: %{error_message} image_upload_error: |- Error while uploading image to storage pool: %{error_message} no_domain_error: |-