From 6c5ecf09af55a2f82f3568201f49e74755a90de6 Mon Sep 17 00:00:00 2001 From: ma Date: Wed, 22 Sep 2021 11:41:19 +0200 Subject: [PATCH] Retrieve IP Address of management interface using qemu-guest-agent commands #1341 --- README.md | 17 ++++++++ lib/vagrant-libvirt/action/create_networks.rb | 2 +- lib/vagrant-libvirt/config.rb | 19 ++++++++ lib/vagrant-libvirt/driver.rb | 43 ++++++++++++++++++- lib/vagrant-libvirt/util/network_util.rb | 1 + tests/qemu_agent/Vagrantfile | 13 ++++++ tests/runtests.bats | 11 +++++ 7 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 tests/qemu_agent/Vagrantfile diff --git a/README.md b/README.md index 694679f..b77ec60 100644 --- a/README.md +++ b/README.md @@ -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 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 You can create and attach additional disks to a VM via `libvirt.storage :file`. diff --git a/lib/vagrant-libvirt/action/create_networks.rb b/lib/vagrant-libvirt/action/create_networks.rb index ba0694e..79e95d5 100644 --- a/lib/vagrant-libvirt/action/create_networks.rb +++ b/lib/vagrant-libvirt/action/create_networks.rb @@ -162,7 +162,7 @@ module VagrantPlugins if @interface_network[:created] # Just check for mismatch error here - if name and ip from # 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, ip_address: @options[:ip], network_name: @options[:network_name] diff --git a/lib/vagrant-libvirt/config.rb b/lib/vagrant-libvirt/config.rb index fdffd98..e664971 100644 --- a/lib/vagrant-libvirt/config.rb +++ b/lib/vagrant-libvirt/config.rb @@ -193,6 +193,9 @@ module VagrantPlugins # Use QEMU session instead of system attr_accessor :qemu_use_session + # Use QEMU Agent to get ip address + attr_accessor :qemu_use_agent + def initialize @uri = UNSET_VALUE @driver = UNSET_VALUE @@ -332,6 +335,9 @@ module VagrantPlugins @qemu_env = UNSET_VALUE @qemu_use_session = UNSET_VALUE + + # Use Qemu agent to get ip address + @qemu_use_agent = UNSET_VALUE end def boot(device) @@ -936,6 +942,8 @@ module VagrantPlugins # Additional QEMU commandline environment variables @qemu_env = {} if @qemu_env == UNSET_VALUE + + @qemu_use_agent = true if @qemu_use_agent != UNSET_VALUE end def validate(machine) @@ -949,6 +957,17 @@ module VagrantPlugins 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| if disk[:path] && (disk[:path][0] == '/') errors << "absolute volume paths like '#{disk[:path]}' not yet supported" diff --git a/lib/vagrant-libvirt/driver.rb b/lib/vagrant-libvirt/driver.rb index b5ff52f..1ec5af2 100644 --- a/lib/vagrant-libvirt/driver.rb +++ b/lib/vagrant-libvirt/driver.rb @@ -3,6 +3,7 @@ require 'fog/libvirt' require 'libvirt' require 'log4r' +require 'json' module VagrantPlugins module ProviderLibvirt @@ -58,7 +59,7 @@ module VagrantPlugins config = @machine.provider_config - @@system_connection = Libvirt::open_read_only(config.system_uri) + @@system_connection = Libvirt::open(config.system_uri) @@system_connection end @@ -99,6 +100,12 @@ module VagrantPlugins return get_ipaddress_from_system domain.mac 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 begin ip_address = get_ipaddress_from_domain(domain) @@ -146,6 +153,40 @@ module VagrantPlugins ip_address 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) ip_address = nil domain.wait_for(2) do diff --git a/lib/vagrant-libvirt/util/network_util.rb b/lib/vagrant-libvirt/util/network_util.rb index c1ad22c..2f508ee 100644 --- a/lib/vagrant-libvirt/util/network_util.rb +++ b/lib/vagrant-libvirt/util/network_util.rb @@ -20,6 +20,7 @@ module VagrantPlugins def configured_networks(env, logger) 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_name = env[:machine].provider_config.management_network_name management_network_address = env[:machine].provider_config.management_network_address diff --git a/tests/qemu_agent/Vagrantfile b/tests/qemu_agent/Vagrantfile new file mode 100644 index 0000000..3e74d77 --- /dev/null +++ b/tests/qemu_agent/Vagrantfile @@ -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 diff --git a/tests/runtests.bats b/tests/runtests.bats index dbcde9b..b659019 100644 --- a/tests/runtests.bats +++ b/tests/runtests.bats @@ -119,6 +119,17 @@ 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" { export VAGRANT_CWD=tests/private_network cleanup