Support setting iface name for target dev on private networks (#1692)

Allow for iface_name to be set on public_network configurations to
control the name of the interface created by libvirt. This overrides the
default that would be created automatically, but cannot use a reserved
name as it will be ignored.

Closes: #799
This commit is contained in:
Darragh Bailey
2022-12-11 11:21:34 +00:00
committed by GitHub
parent 1741ee2f6d
commit d8c8d3d85f
6 changed files with 372 additions and 29 deletions

View File

@@ -494,9 +494,11 @@ starts with `libvirt__` string. Here is a list of those options:
for for more information. *Note: takes either 'yes' or 'no' for value*
* `:libvirt__ipv6_address` - Define ipv6 address, require also prefix.
* `:libvirt__ipv6_prefix` - Define ipv6 prefix. generate string `<ip family="ipv6" address="address" prefix="prefix" >`
* `:libvirt__iface_name` - Define a name for the private network interface.
With this feature one can [simulate physical link
failures](https://github.com/vagrant-libvirt/vagrant-libvirt/pull/498)
* `:libvirt__iface_name` - Define a name for the corresponding network interface
created on the host. With this feature one can [simulate physical link
failures](https://github.com/vagrant-libvirt/vagrant-libvirt/pull/498). Note
that you cannot use names reserved for libvirt's usage based on [documentation](
https://libvirt.org/formatdomain.html#overriding-the-target-element).
* `:mac` - MAC address for the interface. *Note: specify this in lowercase
since Vagrant network scripts assume it will be!*
* `:libvirt__mtu` - MTU size for the Libvirt network, if not defined, the
@@ -542,6 +544,11 @@ virtual network.
* `:trust_guest_rx_filters` - Support trustGuestRxFilters attribute. Details
are listed [here](http://www.libvirt.org/formatdomain.html#elementsNICSDirect).
Default is 'false'.
* `:libvirt__iface_name` - Define a name for the corresponding network interface
that is created on the host connected to the bridge dev. This can be used to
help attach VLAN tags to specific VMs by adjusting the pattern to match. Note
that you cannot use names reserved for libvirt's usage based on [documentation](
https://libvirt.org/formatdomain.html#overriding-the-target-element).
Additionally for public networks, to facilitate validating if the device provided
can be used, vagrant-libvirt will check both the host interfaces visible to libvirt

View File

@@ -4,6 +4,9 @@ require 'log4r'
require 'vagrant/util/network_ip'
require 'vagrant/util/scoped_hash_override'
require 'vagrant-libvirt/util/erb_template'
require 'vagrant-libvirt/util/network_util'
module VagrantPlugins
module ProviderLibvirt
module Action
@@ -96,6 +99,7 @@ module VagrantPlugins
@driver_iommu = iface_configuration.fetch(:driver_iommu, false )
@driver_name = iface_configuration.fetch(:driver_name, false)
@driver_queues = iface_configuration.fetch(:driver_queues, false)
@device_name = iface_configuration.fetch(:iface_name, nil)
@portgroup = iface_configuration.fetch(:portgroup, nil)
@network_name = iface_configuration.fetch(:network_name, @network_name)
template_name = 'public_interface'
@@ -169,7 +173,7 @@ module VagrantPlugins
@logger.debug {
"Attaching Network Device with XML:\n#{xml}"
}
domain.attach_device(xml)
env[:machine].provider.driver.attach_device(xml)
rescue => e
raise Errors::AttachDeviceError,
error_message: e.message

View File

@@ -1412,6 +1412,13 @@ module VagrantPlugins
errors << "network configuration #{index} for machine #{machine.name} is a public_network referencing host device '#{hostdev}' which does not exist, consider adding ':dev => ....' referencing one of #{devices.join(", ")}"
end
end
unless network[:iface_name].nil?
restricted_devnames = ['vnet', 'vif', 'macvtap', 'macvlan']
if restricted_devnames.any? { |restricted| network[:iface_name].start_with?(restricted) }
errors << "network configuration for machine #{machine.name} with setting :libvirt__iface_name => '#{network[:iface_name]}' starts with a restricted prefix according to libvirt docs https://libvirt.org/formatdomain.html#overriding-the-target-element, please use a device name that does not start with one of #{restricted_devnames.join(", ")}"
end
end
end
errors

View File

@@ -1,34 +1,36 @@
<interface type='<%= @type %>'<% if @trust_guest_rx_filters %> trustGuestRxFilters='yes'<% end %>>
<alias name='ua-net-<%= @iface_number %>'/>
<% if @mac %>
<%- if @mac %>
<mac address='<%= @mac %>'/>
<% end %>
<%if @type == 'direct'%>
<%- end %>
<%- if @type == 'direct'%>
<source dev='<%= @device %>' mode='<%= @mode %>'/>
<% elsif !@portgroup.nil? %>
<%- elsif !@portgroup.nil? %>
<source network='<%=@network_name%>' portgroup='<%=@portgroup%>'/>
<% else %>
<%- else %>
<source bridge='<%=@device%>'/>
<% end %>
<%- end %>
<%- unless @device_name.nil? %>
<target dev='<%= @device_name %>'/>
<%- end %>
<model type='<%=@model_type%>'/>
<% if @driver_name and @driver_queues %>
<%- if @driver_name and @driver_queues %>
<driver <% if @driver_iommu %> iommu="on" <% end %> name='<%=@driver_name%>' queues='<%=@driver_queues%>'/>
<% elsif @driver_queues %>
<%- elsif @driver_queues %>
<driver <% if @driver_iommu %> iommu="on" <% end %> queues='<%=@driver_queues%>'/>
<% elsif @driver_name %>
<%- elsif @driver_name %>
<driver <% if @driver_iommu %> iommu="on" <% end %> name='<%=@driver_name%>'/>
<% elsif @driver_iommu %>
<%- elsif @driver_iommu %>
<driver iommu='on' />
<% end %>
<% if @ovs %>
<%- end %>
<%- if @ovs %>
<virtualport type='openvswitch'>
<% if @ovs_interfaceid %>
<%- if @ovs_interfaceid %>
<parameters interfaceid='<%=@ovs_interfaceid%>'/>
<% end %>
<%- end %>
</virtualport>
<% end %>
<% if @pci_bus and @pci_slot %>
<%- end %>
<%- if @pci_bus and @pci_slot %>
<address type='pci' bus='<%=@pci_bus%>' slot='<%=@pci_slot%>' />
<% end %>
<%- end %>
</interface>

View File

@@ -0,0 +1,305 @@
# frozen_string_literal: true
require_relative '../../spec_helper'
require 'vagrant-libvirt/errors'
require 'vagrant-libvirt/action/create_network_interfaces'
require 'vagrant-libvirt/util/unindent'
describe VagrantPlugins::ProviderLibvirt::Action::CreateNetworkInterfaces do
subject { described_class.new(app, env) }
include_context 'unit'
include_context 'libvirt'
let(:networks) { [
instance_double(::Libvirt::Network),
instance_double(::Libvirt::Network),
] }
let(:default_network_xml) {
<<-EOF
<network>
<name>default</name>
<uuid>e5f871eb-2899-48b2-83df-78aa43efa360</uuid>
<forward mode='nat'>
<nat>
<port start='1024' end='65535'/>
</nat>
</forward>
<bridge name='virbr0' stp='on' delay='0'/>
<mac address='52:54:00:71:ce:a6'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.2' end='192.168.122.254'/>
</dhcp>
</ip>
</network>
EOF
}
let(:management_network_xml) {
<<-EOF
<network ipv6='yes'>
<name>vagrant-libvirt</name>
<uuid>46360938-0607-4168-a182-1352fac4a4f9</uuid>
<forward mode='nat'/>
<bridge name='virbr1' stp='on' delay='0'/>
<mac address='52:54:00:c2:d5:a5'/>
<ip address='192.168.121.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.121.1' end='192.168.121.254'/>
</dhcp>
</ip>
</network>
EOF
}
let(:default_management_nic_xml) {
<<-EOF.unindent
<interface type="network">
<alias name="ua-net-0"></alias>
<source network="vagrant-libvirt"></source>
<target dev="vnet0"></target>
<model type="virtio"></model>
<driver iommu="off"></driver>
</interface>
EOF
}
before do
allow(app).to receive(:call)
allow(libvirt_client).to receive(:lookup_domain_by_uuid).and_return(libvirt_domain)
allow(driver).to receive(:list_all_networks).and_return(networks)
allow(networks[0]).to receive(:xml_desc).and_return(default_network_xml)
allow(networks[0]).to receive(:name).and_return('default')
allow(networks[0]).to receive(:bridge_name).and_return('virbr0')
allow(networks[0]).to receive(:active?).and_return(true)
allow(networks[0]).to receive(:autostart?).and_return(true)
allow(networks[1]).to receive(:xml_desc).and_return(management_network_xml)
allow(networks[1]).to receive(:name).and_return('vagrant-libvirt')
allow(networks[1]).to receive(:bridge_name).and_return('virbr1')
allow(networks[1]).to receive(:active?).and_return(true)
allow(networks[1]).to receive(:autostart?).and_return(false)
allow(logger).to receive(:info)
allow(logger).to receive(:debug)
end
describe '#call' do
it 'should inject the management network definition' do
expect(driver).to receive(:attach_device).with(default_management_nic_xml)
expect(subject.call(env)).to be_nil
end
context 'private network' do
let(:vagrantfile) do
<<-EOF
Vagrant.configure('2') do |config|
config.vm.box = "vagrant-libvirt/test"
config.vm.define :test
config.vm.provider :libvirt do |libvirt|
#{vagrantfile_providerconfig}
end
config.vm.network :private_network, :ip => "10.20.30.40"
end
EOF
end
let(:private_network) { instance_double(::Libvirt::Network) }
let(:private_network_xml) {
<<-EOF
<network ipv6='yes'>
<name>test1</name>
<uuid>46360938-0607-4168-a182-1352fac4a4f9</uuid>
<forward mode='nat'/>
<bridge name='virbr2' stp='on' delay='0'/>
<mac address='52:54:00:c2:d5:a5'/>
<ip address='10.20.30.1' netmask='255.255.255.0'>
<dhcp>
<range start='10.20.30.1' end='10.20.30.254'/>
</dhcp>
</ip>
</network>
EOF
}
let(:private_nic_xml) {
<<-EOF.unindent
<interface type="network">
<alias name="ua-net-1"></alias>
<source network="test1"></source>
<target dev="vnet1"></target>
<model type="virtio"></model>
<driver iommu="off"></driver>
</interface>
EOF
}
before do
allow(private_network).to receive(:xml_desc).and_return(private_network_xml)
allow(private_network).to receive(:name).and_return('test1')
allow(private_network).to receive(:bridge_name).and_return('virbr2')
allow(private_network).to receive(:active?).and_return(true)
allow(private_network).to receive(:autostart?).and_return(false)
end
it 'should attach for two networks' do
expect(driver).to receive(:list_all_networks).and_return(networks + [private_network])
expect(driver).to receive(:attach_device).with(default_management_nic_xml)
expect(driver).to receive(:attach_device).with(private_nic_xml)
expect(guest).to receive(:capability).with(:configure_networks, any_args)
expect(subject.call(env)).to be_nil
end
context 'when iface name is set' do
let(:private_nic_xml) {
<<-EOF.unindent
<interface type="network">
<alias name="ua-net-1"></alias>
<source network="test1"></source>
<target dev="myvnet0"></target>
<model type="virtio"></model>
<driver iommu="off"></driver>
</interface>
EOF
}
before do
machine.config.vm.networks[0][1][:libvirt__iface_name] = "myvnet0"
end
it 'should set target appropriately' do
expect(driver).to receive(:list_all_networks).and_return(networks + [private_network])
expect(driver).to receive(:attach_device).with(default_management_nic_xml)
expect(driver).to receive(:attach_device).with(private_nic_xml)
expect(guest).to receive(:capability).with(:configure_networks, any_args)
expect(subject.call(env)).to be_nil
end
end
it 'should skip configuring networks in guest without box' do
machine.config.vm.box = nil
expect(driver).to receive(:list_all_networks).and_return(networks + [private_network])
expect(driver).to receive(:attach_device).with(default_management_nic_xml)
expect(driver).to receive(:attach_device).with(private_nic_xml)
expect(guest).to_not receive(:capability).with(:configure_networks, any_args)
expect(subject.call(env)).to be_nil
end
end
context 'public network' do
let(:vagrantfile) do
<<-EOF
Vagrant.configure('2') do |config|
config.vm.box = "vagrant-libvirt/test"
config.vm.define :test
config.vm.provider :libvirt do |libvirt|
#{vagrantfile_providerconfig}
end
config.vm.network :public_network, :dev => "virbr1", :mode => "bridge", :type => "bridge"
end
EOF
end
let(:public_network) { instance_double(::Libvirt::Network) }
let(:public_network_xml) {
<<-EOF
<network ipv6='yes'>
<name>test1</name>
<uuid>46360938-0607-4168-a182-1352fac4a4f9</uuid>
<forward mode='nat'/>
<bridge name='virbr2' stp='on' delay='0'/>
<mac address='52:54:00:c2:d5:a5'/>
<ip address='10.20.30.1' netmask='255.255.255.0'>
<dhcp>
<range start='10.20.30.1' end='10.20.30.254'/>
</dhcp>
</ip>
</network>
EOF
}
let(:public_nic_xml) {
<<-EOF.unindent
<interface type='bridge'>
<alias name='ua-net-1'/>
<source bridge='virbr1'/>
<model type='virtio'/>
</interface>
EOF
}
let(:domain_xml) {
# don't need full domain here, just enough for the network element to work
<<-EOF.unindent
<domain type='qemu'>
<devices>
<interface type='network'>
<alias name='ua-net-0'/>
<mac address='52:54:00:7d:14:0e'/>
<source network='vagrant-libvirt'/>
<model type='virtio'/>
<driver iommu='off'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
</interface>
<interface type='network'>
<alias name='ua-net-1'/>
<mac address='52:54:00:7d:14:0f'/>
<source bridge='virbr1'/>
<model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
</interface>
</devices>
</domain>
EOF
}
before do
allow(public_network).to receive(:xml_desc).and_return(public_network_xml)
allow(public_network).to receive(:name).and_return('test1')
allow(public_network).to receive(:bridge_name).and_return('virbr2')
allow(public_network).to receive(:active?).and_return(true)
allow(public_network).to receive(:autostart?).and_return(false)
allow(libvirt_domain).to receive(:xml_desc).and_return(domain_xml)
end
it 'should attach for two networks' do
expect(driver).to receive(:list_all_networks).and_return(networks + [public_network])
expect(driver).to receive(:attach_device).with(default_management_nic_xml)
expect(driver).to receive(:attach_device).with(public_nic_xml)
expect(guest).to receive(:capability).with(:configure_networks, any_args)
expect(subject.call(env)).to be_nil
end
context 'when iface name is set' do
let(:public_nic_xml) {
<<-EOF.unindent
<interface type='bridge'>
<alias name='ua-net-1'/>
<source bridge='virbr1'/>
<target dev='myvnet0'/>
<model type='virtio'/>
</interface>
EOF
}
before do
machine.config.vm.networks[0][1][:libvirt__iface_name] = "myvnet0"
end
it 'should set target appropriately' do
expect(driver).to receive(:list_all_networks).and_return(networks + [public_network])
expect(driver).to receive(:attach_device).with(default_management_nic_xml)
expect(driver).to receive(:attach_device).with(public_nic_xml)
expect(guest).to receive(:capability).with(:configure_networks, any_args)
expect(subject.call(env)).to be_nil
end
end
end
end
end

View File

@@ -801,8 +801,9 @@ describe VagrantPlugins::ProviderLibvirt::Config do
'virbr0',
] }
let(:driver) { instance_double(::VagrantPlugins::ProviderLibvirt::Driver) }
let(:device_name) { 'eth0' }
before do
machine.config.vm.network :public_network, dev: 'eth0', ip: "192.168.2.157"
machine.config.vm.network :public_network, dev: device_name, ip: "192.168.2.157"
allow(machine.provider).to receive(:driver).and_return(driver)
expect(driver).to receive(:host_devices).and_return(host_devices).at_least(:once).times
end
@@ -812,9 +813,7 @@ describe VagrantPlugins::ProviderLibvirt::Config do
end
context 'when default device not on host' do
before do
machine.config.vm.network :public_network, dev: 'eno1', ip: "192.168.2.157"
end
let(:device_name) { 'eno1' }
it 'should be invalid' do
assert_invalid
@@ -822,9 +821,7 @@ describe VagrantPlugins::ProviderLibvirt::Config do
end
context 'when using excluded host device' do
before do
machine.config.vm.network :public_network, dev: 'virbr0', ip: "192.168.2.157"
end
let(:device_name) { 'virbr0' }
it 'should be invalid' do
assert_invalid
@@ -840,6 +837,27 @@ describe VagrantPlugins::ProviderLibvirt::Config do
end
end
end
context 'when setting iface_name' do
let(:iface_name) { 'myvnet1' }
before do
machine.config.vm.network :public_network, libvirt__iface_name: iface_name, ip: "192.168.2.157"
end
it 'should valididate' do
assert_valid
end
context 'when set to reserved value' do
let(:iface_name) { 'vnet1' }
it 'should be invalid' do
errors = assert_invalid
expect(errors).to include(match(/network configuration for machine test with setting :libvirt__iface_name => '#{iface_name}' starts/))
end
end
end
end
context 'with nvram defined' do