From d0787c803dd926e36f779045e2ec507a88b6e9a8 Mon Sep 17 00:00:00 2001 From: zzambers Date: Wed, 16 Dec 2020 20:19:24 +0100 Subject: [PATCH] Add support for clock setup (#1047) This adds support for setting clock offset and timers. See https://libvirt.org/formatdomain.html#elementsTime for more info. --- README.md | 22 +++++++ lib/vagrant-libvirt/action/create_domain.rb | 6 ++ lib/vagrant-libvirt/action/start_domain.rb | 31 +++++++++ lib/vagrant-libvirt/config.rb | 29 +++++++++ lib/vagrant-libvirt/templates/domain.xml.erb | 6 +- spec/unit/action/start_domain_spec.rb | 63 +++++++++++++++++++ .../start_domain_spec/clock_timer_rtc.xml | 50 +++++++++++++++ spec/unit/config_spec.rb | 54 ++++++++++++++++ spec/unit/templates/domain_all_settings.xml | 5 +- .../templates/domain_custom_cpu_model.xml | 3 +- spec/unit/templates/domain_defaults.xml | 3 +- spec/unit/templates/domain_spec.rb | 3 + spec/unit/templates/tpm/version_1.2.xml | 3 +- spec/unit/templates/tpm/version_2.0.xml | 3 +- 14 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 spec/unit/action/start_domain_spec/clock_timer_rtc.xml diff --git a/README.md b/README.md index fa8f655..9c42402 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ can help a lot :-) * [Watchdog device](#watchdog-device) * [Smartcard device](#smartcard-device) * [Hypervisor Features](#hypervisor-features) +* [Clock](#clock) * [CPU features](#cpu-features) * [Memory Backing](#memory-backing) * [No box and PXE boot](#no-box-and-pxe-boot) @@ -1185,6 +1186,27 @@ Vagrant.configure("2") do |config| end ``` +## Clock + +Clock offset can be specified via `libvirt.clock_offset`. (Default is utc) + +Additionally timers can be specified via `libvirt.clock_timer`. +Available options for timers are: name, track, tickpolicy, frequency, mode, present + +```ruby +Vagrant.configure("2") do |config| + config.vm.provider :libvirt do |libvirt| + # Set clock offset to localtime + libvirt.clock_offset = 'localtime' + # Timers ... + libvirt.clock_timer :name => 'rtc', :tickpolicy => 'catchup' + libvirt.clock_timer :name => 'pit', :tickpolicy => 'delay' + libvirt.clock_timer :name => 'hpet', :present => 'no' + libvirt.clock_timer :name => 'hypervclock', :present => 'yes' + end +end +``` + ## CPU features You can specify CPU feature policies via `libvirt.cpu_feature`. Available diff --git a/lib/vagrant-libvirt/action/create_domain.rb b/lib/vagrant-libvirt/action/create_domain.rb index ddd45a5..6d94fca 100644 --- a/lib/vagrant-libvirt/action/create_domain.rb +++ b/lib/vagrant-libvirt/action/create_domain.rb @@ -42,6 +42,8 @@ module VagrantPlugins @nodeset = config.nodeset @features = config.features @features_hyperv = config.features_hyperv + @clock_offset = config.clock_offset + @clock_timers = config.clock_timers @shares = config.shares @cpu_mode = config.cpu_mode @cpu_model = config.cpu_model @@ -226,6 +228,10 @@ module VagrantPlugins @features_hyperv.each do |feature| env[:ui].info(" -- Feature (HyperV): name=#{feature[:name]}, state=#{feature[:state]}") end + env[:ui].info(" -- Clock offset: #{@clock_offset}") + @clock_timers.each do |timer| + env[:ui].info(" -- Clock timer: #{timer.map { |k,v| "#{k}=#{v}"}.join(', ')}") + end env[:ui].info(" -- Memory: #{@memory_size / 1024}M") unless @nodeset.nil? env[:ui].info(" -- Nodeset: #{@nodeset}") diff --git a/lib/vagrant-libvirt/action/start_domain.rb b/lib/vagrant-libvirt/action/start_domain.rb index 1058015..5e09cfe 100644 --- a/lib/vagrant-libvirt/action/start_domain.rb +++ b/lib/vagrant-libvirt/action/start_domain.rb @@ -37,6 +37,9 @@ module VagrantPlugins xml_descr = REXML::Document.new descr descr_changed = false + # For outputting XML for comparison + formatter = REXML::Formatters::Pretty.new + # additional disk bus config.disks.each do |disk| device = disk[:device] @@ -152,6 +155,34 @@ module VagrantPlugins end end + # Clock + clock = REXML::XPath.first(xml_descr, '/domain/clock') + if clock.attributes['offset'] != config.clock_offset + @logger.debug "clock offset changed" + descr_changed = true + clock.attributes['offset'] = config.clock_offset + end + + # clock timers - because timers can be added/removed, just rebuild and then compare + if !config.clock_timers.empty? || clock.has_elements? + oldclock = '' + formatter.write(REXML::XPath.first(xml_descr, '/domain/clock'), oldclock) + clock.delete_element('//timer') + config.clock_timers.each do |clock_timer| + timer = REXML::Element.new('timer', clock) + clock_timer.each do |attr, value| + timer.attributes[attr.to_s] = value + end + end + + newclock = '' + formatter.write(clock, newclock) + unless newclock.eql? oldclock + @logger.debug "clock timers config changed" + descr_changed = true + end + end + # Graphics graphics = REXML::XPath.first(xml_descr, '/domain/devices/graphics') if config.graphics_type != 'none' diff --git a/lib/vagrant-libvirt/config.rb b/lib/vagrant-libvirt/config.rb index 900649d..06956b7 100644 --- a/lib/vagrant-libvirt/config.rb +++ b/lib/vagrant-libvirt/config.rb @@ -84,6 +84,8 @@ module VagrantPlugins attr_accessor :shares attr_accessor :features attr_accessor :features_hyperv + attr_accessor :clock_offset + attr_accessor :clock_timers attr_accessor :numa_nodes attr_accessor :loader attr_accessor :nvram @@ -222,6 +224,8 @@ module VagrantPlugins @shares = UNSET_VALUE @features = UNSET_VALUE @features_hyperv = UNSET_VALUE + @clock_offset = UNSET_VALUE + @clock_timers = [] @numa_nodes = UNSET_VALUE @loader = UNSET_VALUE @nvram = UNSET_VALUE @@ -392,6 +396,25 @@ module VagrantPlugins state: options[:state]) end + def clock_timer(options = {}) + if options[:name].nil? + raise 'Clock timer name must be specified' + end + + options.each do |key, value| + case key + when :name, :track, :tickpolicy, :frequency, :mode, :present + if value.nil? + raise "Value of timer option #{key} is nil" + end + else + raise "Unknown clock timer option: #{key}" + end + end + + @clock_timers.push(options.dup) + end + def cputopology(options = {}) if options[:sockets].nil? || options[:cores].nil? || options[:threads].nil? raise 'CPU topology must have all of sockets, cores and threads specified' @@ -762,6 +785,8 @@ module VagrantPlugins @shares = nil if @shares == UNSET_VALUE @features = ['acpi','apic','pae'] if @features == UNSET_VALUE @features_hyperv = [] if @features_hyperv == UNSET_VALUE + @clock_offset = 'utc' if @clock_offset == UNSET_VALUE + @clock_timers = [] if @clock_timers == UNSET_VALUE @numa_nodes = @numa_nodes == UNSET_VALUE ? nil : _generate_numa @loader = nil if @loader == UNSET_VALUE @nvram = nil if @nvram == UNSET_VALUE @@ -903,6 +928,10 @@ module VagrantPlugins c += other.cdroms result.cdroms = c + c = clock_timers.dup + c += other.clock_timers + result.clock_timers = c + c = qemu_env != UNSET_VALUE ? qemu_env.dup : {} c.merge!(other.qemu_env) if other.qemu_env != UNSET_VALUE result.qemu_env = c diff --git a/lib/vagrant-libvirt/templates/domain.xml.erb b/lib/vagrant-libvirt/templates/domain.xml.erb index 992a28d..99d52de 100644 --- a/lib/vagrant-libvirt/templates/domain.xml.erb +++ b/lib/vagrant-libvirt/templates/domain.xml.erb @@ -104,7 +104,11 @@ <% end %> - + + <% @clock_timers.each do |clock_timer| %> + <%= attr %>='<%= value %>'<% end %>/> + <% end %> + <% if @emulator_path %> <%= @emulator_path %> diff --git a/spec/unit/action/start_domain_spec.rb b/spec/unit/action/start_domain_spec.rb index 1c0743c..437517f 100644 --- a/spec/unit/action/start_domain_spec.rb +++ b/spec/unit/action/start_domain_spec.rb @@ -27,7 +27,9 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do allow(connection).to receive(:servers).and_return(servers) allow(servers).to receive(:get).and_return(domain) + expect(logger).to receive(:info) + expect(ui).to_not receive(:error) end context 'default config' do @@ -164,5 +166,66 @@ describe VagrantPlugins::ProviderLibvirt::Action::StartDomain do end end end + + context 'clock_timers' do + let(:test_file) { 'clock_timer_rtc.xml' } + + before do + allow(libvirt_domain).to receive(:xml_desc).and_return(domain_xml) + + allow(libvirt_domain).to receive(:max_memory).and_return(512*1024) + allow(libvirt_domain).to receive(:num_vcpus).and_return(1) + end + + context 'timers unchanged' do + let(:vagrantfile_providerconfig) do + <<-EOF + libvirt.clock_timer(:name => "rtc") + EOF + end + + it 'should not modify the domain' do + expect(logger).to_not receive(:debug).with('clock timers config changed') + expect(servers).to_not receive(:create) + expect(libvirt_domain).to receive(:autostart=) + expect(domain).to receive(:start) + + expect(subject.call(env)).to be_nil + end + end + + context 'timers added' do + let(:vagrantfile_providerconfig) do + <<-EOF + libvirt.clock_timer(:name => "rtc") + libvirt.clock_timer(:name => "tsc") + EOF + end + + it 'should modify the domain' do + expect(libvirt_domain).to receive(:undefine) + expect(logger).to receive(:debug).with('clock timers config changed') + expect(servers).to receive(:create).with(xml: match(/\s*\s*\s*<\/clock>/)) + expect(libvirt_domain).to receive(:autostart=) + expect(domain).to receive(:start) + + expect(subject.call(env)).to be_nil + end + end + + context 'timers removed' do + let(:updated_test_file) { 'default.xml' } + + it 'should modify the domain' do + expect(libvirt_domain).to receive(:undefine) + expect(logger).to receive(:debug).with('clock timers config changed') + expect(servers).to receive(:create).with(xml: match(/\s*<\/clock>/)) + expect(libvirt_domain).to receive(:autostart=) + expect(domain).to receive(:start) + + expect(subject.call(env)).to be_nil + end + end + end end end diff --git a/spec/unit/action/start_domain_spec/clock_timer_rtc.xml b/spec/unit/action/start_domain_spec/clock_timer_rtc.xml new file mode 100644 index 0000000..fe1f199 --- /dev/null +++ b/spec/unit/action/start_domain_spec/clock_timer_rtc.xml @@ -0,0 +1,50 @@ + + + + <description/> + <uuid/> + <memory/> + <vcpu>1</vcpu> + + + <cpu mode='host-model'> + <model fallback='allow'/> + </cpu> + + + <os> + <type>hvm</type> + <kernel/> + <initrd/> + <cmdline/> + </os> + <features> + <acpi/> + <apic/> + <pae/> + </features> + <clock offset='utc'> + <timer name='rtc'/> + </clock> + <devices> + + + <serial type='pty'> + <target port='0'/> + </serial> + <console type='pty'> + <target port='0'/> + </console> + + + <input bus='ps2' type='mouse'/> + + <graphics autoport='yes' keymap='en-us' listen='127.0.0.1' port='-1' type='vnc'/> + <video> + <model heads='1' type='cirrus' vram='9216'/> + </video> + + + </devices> + +</domain> diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb index c202d01..4900f44 100644 --- a/spec/unit/config_spec.rb +++ b/spec/unit/config_spec.rb @@ -8,6 +8,50 @@ describe VagrantPlugins::ProviderLibvirt::Config do let(:fake_env) { Hash.new } + describe '#clock_timer' do + it 'should handle all options' do + expect( + subject.clock_timer( + :name => 'rtc', + :track => 'wall', + :tickpolicy => 'delay', + :present => 'yes', + ).length + ).to be(1) + expect( + subject.clock_timer( + :name => 'tsc', + :tickpolicy => 'delay', + :frequency => '100', + :mode => 'auto', + :present => 'yes', + ).length + ).to be(2) + end + + it 'should correctly save the options' do + opts = {:name => 'rtc', :track => 'wall'} + expect(subject.clock_timer(opts).length).to be(1) + + expect(subject.clock_timers[0]).to eq(opts) + + opts[:name] = 'tsc' + expect(subject.clock_timers[0]).to_not eq(opts) + end + + it 'should error name option is missing' do + expect{ subject.clock_timer(:track => "wall") }.to raise_error("Clock timer name must be specified") + end + + it 'should error if nil value for option supplied' do + expect{ subject.clock_timer(:name => "rtc", :track => nil) }.to raise_error("Value of timer option track is nil") + end + + it 'should error if unrecognized option specified' do + expect{ subject.clock_timer(:name => "tsc", :badopt => "value") }.to raise_error("Unknown clock timer option: badopt") + end + end + describe '#finalize!' do it 'is valid with defaults' do subject.finalize! @@ -282,5 +326,15 @@ describe VagrantPlugins::ProviderLibvirt::Config do end end end + + context 'clock_timers' do + it 'should merge clock_timers' do + one.clock_timer(:name => 'rtc', :tickpolicy => 'catchup') + two.clock_timer(:name => 'hpet', :present => 'no') + + expect(subject.clock_timers).to include(include(name: 'rtc'), + include(name: 'hpet')) + end + end end end diff --git a/spec/unit/templates/domain_all_settings.xml b/spec/unit/templates/domain_all_settings.xml index 1955360..0c58ff7 100644 --- a/spec/unit/templates/domain_all_settings.xml +++ b/spec/unit/templates/domain_all_settings.xml @@ -36,7 +36,10 @@ <BBB state='on' /> </hyperv> </features> - <clock offset='utc'/> + <clock offset='variable'> + <timer name='t1'/> + <timer name='t2' track='b' tickpolicy='c' frequency='d' mode='e' present='yes'/> + </clock> <devices> <emulator>/usr/bin/kvm-spice</emulator> <disk type='file' device='disk'> diff --git a/spec/unit/templates/domain_custom_cpu_model.xml b/spec/unit/templates/domain_custom_cpu_model.xml index 1e90f3d..0558bca 100644 --- a/spec/unit/templates/domain_custom_cpu_model.xml +++ b/spec/unit/templates/domain_custom_cpu_model.xml @@ -23,7 +23,8 @@ <apic/> <pae/> </features> - <clock offset='utc'/> + <clock offset='utc'> + </clock> <devices> diff --git a/spec/unit/templates/domain_defaults.xml b/spec/unit/templates/domain_defaults.xml index 20cbde9..e4748cc 100644 --- a/spec/unit/templates/domain_defaults.xml +++ b/spec/unit/templates/domain_defaults.xml @@ -23,7 +23,8 @@ <apic/> <pae/> </features> - <clock offset='utc'/> + <clock offset='utc'> + </clock> <devices> diff --git a/spec/unit/templates/domain_spec.rb b/spec/unit/templates/domain_spec.rb index 5fb3a8a..3e67cf0 100644 --- a/spec/unit/templates/domain_spec.rb +++ b/spec/unit/templates/domain_spec.rb @@ -33,6 +33,9 @@ describe 'templates/domain' do domain.cpu_mode = 'custom' domain.cpu_feature(name: 'AAA', policy: 'required') domain.hyperv_feature(name: 'BBB', state: 'on') + domain.clock_offset = 'variable' + domain.clock_timer(name: 't1') + domain.clock_timer(name: 't2', track: 'b', tickpolicy: 'c', frequency: 'd', mode: 'e', present: 'yes') domain.cputopology(sockets: '1', cores: '3', threads: '2') domain.machine_type = 'pc-compatible' domain.machine_arch = 'x86_64' diff --git a/spec/unit/templates/tpm/version_1.2.xml b/spec/unit/templates/tpm/version_1.2.xml index c33f9d0..f281d28 100644 --- a/spec/unit/templates/tpm/version_1.2.xml +++ b/spec/unit/templates/tpm/version_1.2.xml @@ -23,7 +23,8 @@ <apic/> <pae/> </features> - <clock offset='utc'/> + <clock offset='utc'> + </clock> <devices> diff --git a/spec/unit/templates/tpm/version_2.0.xml b/spec/unit/templates/tpm/version_2.0.xml index fc92aef..f2c7435 100644 --- a/spec/unit/templates/tpm/version_2.0.xml +++ b/spec/unit/templates/tpm/version_2.0.xml @@ -23,7 +23,8 @@ <apic/> <pae/> </features> - <clock offset='utc'/> + <clock offset='utc'> + </clock> <devices>