Merge pull request #1343 from abbbi/1341_poc

Retrieve IP Address of management interface using qemu-guest-agent commands #1341
This commit is contained in:
Michael Ablassmeier 2021-09-24 11:12:17 +02:00 committed by GitHub
commit e9213b6caa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 104 additions and 2 deletions

View File

@ -933,6 +933,23 @@ DHCP server. dnsmasq writes lease information in the `/var/lib/libvirt/dnsmasq`
directory. Vagrant-libvirt looks for the MAC address in this file and extracts directory. Vagrant-libvirt looks for the MAC address in this file and extracts
the corresponding IP address. the corresponding IP address.
It is also possible to use the Qemu Agent to extract the management interface
configuration from the booted virtual machine. This is helpful in libvirt
environments where no local dnsmasq is used for automatic address assigment,
but external dhcp services via bridged libvirt networks.
Prerequisite is to enable the qemu agent channel via ([Libvirt communication
channels](#libvirt-communication-channels)) and the virtual machine image must
have the agent pre-installed before deploy. The agent will start automatically
if it detects an attached channel during boot.
* `qemu_use_agent` - false by default, if set to true, attempt to extract configured
ip address via qemu agent.
To use the management network interface with an external dhcp service you need
to setup a bridged host network manually and define it via
`management_network_name` in your Vagrantfile.
## Additional Disks ## Additional Disks
You can create and attach additional disks to a VM via `libvirt.storage :file`. You can create and attach additional disks to a VM via `libvirt.storage :file`.

View File

@ -162,7 +162,7 @@ module VagrantPlugins
if @interface_network[:created] if @interface_network[:created]
# Just check for mismatch error here - if name and ip from # Just check for mismatch error here - if name and ip from
# config match together. # config match together.
if @options[:network_name] != @interface_network[:name] if @options[:network_name] != @interface_network[:name] and @qemu_use_agent = false
raise Errors::NetworkNameAndAddressMismatch, raise Errors::NetworkNameAndAddressMismatch,
ip_address: @options[:ip], ip_address: @options[:ip],
network_name: @options[:network_name] network_name: @options[:network_name]

View File

@ -193,6 +193,9 @@ module VagrantPlugins
# Use QEMU session instead of system # Use QEMU session instead of system
attr_accessor :qemu_use_session attr_accessor :qemu_use_session
# Use QEMU Agent to get ip address
attr_accessor :qemu_use_agent
def initialize def initialize
@uri = UNSET_VALUE @uri = UNSET_VALUE
@driver = UNSET_VALUE @driver = UNSET_VALUE
@ -332,6 +335,9 @@ module VagrantPlugins
@qemu_env = UNSET_VALUE @qemu_env = UNSET_VALUE
@qemu_use_session = UNSET_VALUE @qemu_use_session = UNSET_VALUE
# Use Qemu agent to get ip address
@qemu_use_agent = UNSET_VALUE
end end
def boot(device) def boot(device)
@ -936,6 +942,8 @@ module VagrantPlugins
# Additional QEMU commandline environment variables # Additional QEMU commandline environment variables
@qemu_env = {} if @qemu_env == UNSET_VALUE @qemu_env = {} if @qemu_env == UNSET_VALUE
@qemu_use_agent = true if @qemu_use_agent != UNSET_VALUE
end end
def validate(machine) def validate(machine)
@ -949,6 +957,17 @@ module VagrantPlugins
end end
end 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| machine.provider_config.disks.each do |disk|
if disk[:path] && (disk[:path][0] == '/') if disk[:path] && (disk[:path][0] == '/')
errors << "absolute volume paths like '#{disk[:path]}' not yet supported" errors << "absolute volume paths like '#{disk[:path]}' not yet supported"

View File

@ -3,6 +3,7 @@
require 'fog/libvirt' require 'fog/libvirt'
require 'libvirt' require 'libvirt'
require 'log4r' require 'log4r'
require 'json'
module VagrantPlugins module VagrantPlugins
module ProviderLibvirt module ProviderLibvirt
@ -58,7 +59,7 @@ module VagrantPlugins
config = @machine.provider_config config = @machine.provider_config
@@system_connection = Libvirt::open_read_only(config.system_uri) @@system_connection = Libvirt::open(config.system_uri)
@@system_connection @@system_connection
end end
@ -99,6 +100,12 @@ module VagrantPlugins
return get_ipaddress_from_system domain.mac return get_ipaddress_from_system domain.mac
end end
# attempt to get ip address from qemu agent
if @machine.provider_config.qemu_use_agent == true
@logger.info('Get IP via qemu agent')
return get_ipaddress_from_qemu_agent(domain, machine.id)
end
# Get IP address from dhcp leases table # Get IP address from dhcp leases table
begin begin
ip_address = get_ipaddress_from_domain(domain) ip_address = get_ipaddress_from_domain(domain)
@ -146,6 +153,40 @@ module VagrantPlugins
ip_address ip_address
end end
def get_ipaddress_from_qemu_agent(domain, machine_id)
ip_address = nil
addresses = nil
dom = system_connection.lookup_domain_by_uuid(machine_id)
begin
response = dom.qemu_agent_command('{"execute":"guest-network-get-interfaces"}', timeout=10)
@logger.debug("Got Response from qemu agent")
@logger.debug(response)
addresses = JSON.parse(response)
rescue => e
@logger.debug("Unable to receive IP via qemu agent: [%s]" % e.message)
end
unless addresses.nil?
addresses["return"].each{ |interface|
if domain.mac == interface["hardware-address"]
@logger.debug("Found mathing interface: [%s]" % interface["name"])
if interface.has_key?("ip-addresses")
interface["ip-addresses"].each{ |ip|
# returning ipv6 addresses might break windows guests because
# winrm cant handle connection, winrm fails with "invalid uri"
if ip["ip-address-type"] == "ipv4"
ip_address = ip["ip-address"]
@logger.debug("Return IP: [%s]" % ip_address)
break
end
}
end
end
}
end
ip_address
end
def get_ipaddress_from_domain(domain) def get_ipaddress_from_domain(domain)
ip_address = nil ip_address = nil
domain.wait_for(2) do domain.wait_for(2) do

View File

@ -20,6 +20,7 @@ module VagrantPlugins
def configured_networks(env, logger) def configured_networks(env, logger)
qemu_use_session = env[:machine].provider_config.qemu_use_session qemu_use_session = env[:machine].provider_config.qemu_use_session
qemu_use_agent = env[:machine].provider_config.qemu_use_agent
management_network_device = env[:machine].provider_config.management_network_device management_network_device = env[:machine].provider_config.management_network_device
management_network_name = env[:machine].provider_config.management_network_name management_network_name = env[:machine].provider_config.management_network_name
management_network_address = env[:machine].provider_config.management_network_address management_network_address = env[:machine].provider_config.management_network_address

13
tests/qemu_agent/Vagrantfile vendored Normal file
View File

@ -0,0 +1,13 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
#
# frozen_string_literal: true
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian10"
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.provider :libvirt do |libvirt|
libvirt.channel :type => 'unix', :target_name => 'org.qemu.guest_agent.0', :target_type => 'virtio'
libvirt.qemu_use_agent = true
end
end

View File

@ -119,6 +119,17 @@ cleanup() {
cleanup cleanup
} }
@test "bring up and use qemu agent for connectivity" {
export VAGRANT_CWD=tests/qemu_agent
cleanup
run ${VAGRANT_CMD} up ${VAGRANT_OPT}
echo "${output}"
echo "status = ${status}"
[ "$status" -eq 0 ]
echo "${output}"
cleanup
}
@test "ip is reachable with private network" { @test "ip is reachable with private network" {
export VAGRANT_CWD=tests/private_network export VAGRANT_CWD=tests/private_network
cleanup cleanup