Merge branch 'qemu-session-support' of git://github.com/qazokm/vagrant-libvirt into qazokm-qemu-session-support

This commit is contained in:
dima 2018-03-17 13:51:29 +01:00
commit 29ce8443b1
16 changed files with 269 additions and 50 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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')
)

View File

@ -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']}")

View File

@ -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)
@ -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,7 @@ 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
# generate a URI if none is supplied
@uri = _generate_uri if @uri == UNSET_VALUE
@ -709,6 +727,7 @@ module VagrantPlugins
@mgmt_attach = true if @mgmt_attach == UNSET_VALUE
@qemu_args = [] if @qemu_args == UNSET_VALUE
@qemu_use_session = false if @qemu_use_session == UNSET_VALUE
end
def validate(machine)

View File

@ -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

View File

@ -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

View File

@ -3,11 +3,11 @@
<source>
</source>
<target>
<path>/var/lib/libvirt/images</path>
<path><%= @storage_pool_path %></path>
<permissions>
<mode>0755</mode>
<owner>-1</owner>
<group>-1</group>
<owner><%= @storage_pool_uid %></owner>
<group><%= @storage_pool_gid %></group>
</permissions>
</target>
</pool>

View File

@ -0,0 +1,14 @@
<volume>
<name><%= @name %></name>
<allocation unit="<%= split_size_unit(@allocation)[1] %>"><%= split_size_unit(@allocation)[0] %></allocation>
<capacity unit="<%= split_size_unit(@capacity)[1] %>"><%= split_size_unit(@capacity)[0] %></capacity>
<target>
<format type="<%= @format_type %>"/>
<permissions>
<owner><%= @storage_volume_uid %></owner>
<group><%= @storage_volume_gid %></group>
<mode>0744</mode>
<label>virt_image_t</label>
</permissions>
</target>
</volume>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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: |-