From 05ee6c0d10143c0aa9bca0efd3aa871efcf34f7e Mon Sep 17 00:00:00 2001 From: Oana Tanasoiu Date: Tue, 26 May 2020 15:55:54 +0300 Subject: [PATCH] (FACT-2609) Add lspci resolver --- lib/facts/linux/virtual.rb | 21 +++++++++++-- lib/resolvers/lspci.rb | 39 ++++++++++++++++++++++++ spec/facter/facts/linux/virtual_spec.rb | 33 +++++++++++++++++++- spec/facter/resolvers/lspci_spec.rb | 40 +++++++++++++++++++++++++ spec/facter/resolvers/vmware_spec.rb | 2 +- spec/fixtures/lspci_aws | 3 ++ spec/fixtures/lspci_vmware | 6 ++++ 7 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 lib/resolvers/lspci.rb create mode 100644 spec/facter/resolvers/lspci_spec.rb create mode 100644 spec/fixtures/lspci_aws create mode 100644 spec/fixtures/lspci_vmware diff --git a/lib/facts/linux/virtual.rb b/lib/facts/linux/virtual.rb index 5ee19f028..75d0d8f8a 100644 --- a/lib/facts/linux/virtual.rb +++ b/lib/facts/linux/virtual.rb @@ -4,10 +4,14 @@ module Facts module Linux class Virtual FACT_NAME = 'virtual' + HYPERVISORS_HASH = { 'VMware' => 'vmware', 'VirtualBox' => 'virtualbox', 'Parallels' => 'parallels', + 'KVM' => 'kvm', 'Virtual Machine' => 'hyperv', 'RHEV Hypervisor' => 'rhev', + 'oVirt Node' => 'ovirt', 'HVM domU' => 'xenhvm', 'Bochs' => 'bochs', 'OpenBSD' => 'vmm', + 'BHYVE' => 'bhyve' }.freeze - def call_the_resolver + def call_the_resolver # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity fact_value = check_docker_lxc || check_gce || retrieve_from_virt_what || check_vmware - fact_value ||= check_open_vz || check_vserver || check_xen + fact_value ||= check_open_vz || check_vserver || check_xen || check_other_facts || check_lspci || 'physical' Facter::ResolvedFact.new(FACT_NAME, fact_value) end @@ -40,6 +44,19 @@ def check_vserver def check_xen Facter::Resolvers::Xen.resolve(:vm) end + + def check_other_facts + product_name = Facter::Resolvers::Linux::DmiBios.resolve(:product_name) + bios_vendor = Facter::Resolvers::Linux::DmiBios.resolve(:bios_vendor) + return 'kvm' if bios_vendor&.include?('Amazon EC2') + return unless product_name + + HYPERVISORS_HASH.each { |key, value| return value if product_name.include?(key) } + end + + def check_lspci + Facter::Resolvers::Lspci.resolve(:vm) + end end end end diff --git a/lib/resolvers/lspci.rb b/lib/resolvers/lspci.rb new file mode 100644 index 000000000..0e0a7c3b7 --- /dev/null +++ b/lib/resolvers/lspci.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Facter + module Resolvers + class Lspci < BaseResolver + @semaphore = Mutex.new + @fact_list ||= {} + + REGEX_VALUES = { 'VirtualBox' => 'virtualbox', 'XenSource' => 'xenhvm', + 'Microsoft Corporation Hyper-V' => 'hyperv', 'Class 8007: Google, Inc' => 'gce' }.freeze + + class << self + private + + def post_resolve(fact_name) + @fact_list.fetch(fact_name) { lspci_command(fact_name) } + end + + def lspci_command(fact_name) + output = Facter::Core::Execution.execute('lspci', logger: log) + return if output.empty? + + @fact_list[:vm] = retrieve_vm(output) + @fact_list[fact_name] + end + + def retrieve_vm(output) + output.split("\n").each do |line| + REGEX_VALUES.each { |key, value| return value if line =~ /#{key}/ } + + return 'vmware' if line =~ /VM[wW]are/ + return 'parallels' if line =~ /1ab8:|[Pp]arallels/ + return 'kvm' if line =~ /virtio/i + end + end + end + end + end +end diff --git a/spec/facter/facts/linux/virtual_spec.rb b/spec/facter/facts/linux/virtual_spec.rb index da28b1f66..e88005e28 100644 --- a/spec/facter/facts/linux/virtual_spec.rb +++ b/spec/facter/facts/linux/virtual_spec.rb @@ -107,11 +107,42 @@ end end - context 'when resolver returns nil' do + context 'when is bochs discovered with dmi product_name' do let(:vm) { nil } + let(:value) { 'bochs' } before do allow(Facter::Resolvers::Linux::DmiBios).to receive(:resolve).with(:bios_vendor).and_return(nil) + allow(Facter::Resolvers::Linux::DmiBios).to receive(:resolve).with(:product_name).and_return('Bochs Machine') + end + + it 'returns virtual fact' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'virtual', value: value) + end + end + + context 'when is hyper-v discovered with lspci' do + let(:vm) { nil } + let(:value) { 'hyperv' } + + before do + allow(Facter::Resolvers::Lspci).to receive(:resolve).with(:vm).and_return(value) + end + + it 'returns virtual fact' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'virtual', value: value) + end + end + + context 'when resolvers return nil ' do + let(:vm) { 'physical' } + + before do + allow(Facter::Resolvers::Linux::DmiBios).to receive(:resolve).with(:bios_vendor).and_return(nil) + allow(Facter::Resolvers::Linux::DmiBios).to receive(:resolve).with(:product_name).and_return(nil) + allow(Facter::Resolvers::Lspci).to receive(:resolve).with(:vm).and_return(nil) end it 'returns virtual fact as nil' do diff --git a/spec/facter/resolvers/lspci_spec.rb b/spec/facter/resolvers/lspci_spec.rb new file mode 100644 index 000000000..4a237da9a --- /dev/null +++ b/spec/facter/resolvers/lspci_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +describe Facter::Resolvers::Lspci do + subject(:lspci_resolver) { Facter::Resolvers::Lspci } + + let(:log_spy) { instance_spy(Facter::Log) } + + before do + lspci_resolver.instance_variable_set(:@log, log_spy) + allow(Facter::Core::Execution).to receive(:execute).with('lspci', logger: log_spy).and_return(output) + end + + after do + lspci_resolver.invalidate_cache + end + + context 'when lspci fails' do + let(:output) { '' } + + it 'returns nil' do + expect(lspci_resolver.resolve(:vm)).to be_nil + end + end + + context 'when lspci detects vmware' do + let(:output) { load_fixture('lspci_vmware').read } + + it 'returns hypervisor name' do + expect(lspci_resolver.resolve(:vm)).to eq('vmware') + end + end + + context 'when lspci detects xen' do + let(:output) { load_fixture('lspci_aws').read } + + it 'returns hypervisor name' do + expect(lspci_resolver.resolve(:vm)).to eq('xenhvm') + end + end +end diff --git a/spec/facter/resolvers/vmware_spec.rb b/spec/facter/resolvers/vmware_spec.rb index d223e755c..11a6e6720 100644 --- a/spec/facter/resolvers/vmware_spec.rb +++ b/spec/facter/resolvers/vmware_spec.rb @@ -3,7 +3,7 @@ describe Facter::Resolvers::Vmware do subject(:vmware_resolver) { Facter::Resolvers::Vmware } - let(:log_spy) { Facter::Log } + let(:log_spy) { instance_spy(Facter::Log) } before do vmware_resolver.instance_variable_set(:@log, log_spy) diff --git a/spec/fixtures/lspci_aws b/spec/fixtures/lspci_aws new file mode 100644 index 000000000..7601ef692 --- /dev/null +++ b/spec/fixtures/lspci_aws @@ -0,0 +1,3 @@ +00:00.0 Host bridge: Intel Corporation FX PMC [Natoma] (rev 02) +00:02.0 VGA compatible controller: Cirrus Logic GD 5446 +00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01) \ No newline at end of file diff --git a/spec/fixtures/lspci_vmware b/spec/fixtures/lspci_vmware new file mode 100644 index 000000000..3c18fa755 --- /dev/null +++ b/spec/fixtures/lspci_vmware @@ -0,0 +1,6 @@ +00:10.0 SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01) +00:11.0 PCI bridge: VMware PCI bridge (rev 02) +00:18.5 PCI bridge: VMware PCI Express Root Port (rev 01) +00:18.6 PCI bridge: VMware PCI Express Root Port (rev 01) +00:18.7 PCI bridge: VMware PCI Express Root Port (rev 01) +03:00.0 Ethernet controller: VMware VMXNET3 Ethernet Controller (rev 01) \ No newline at end of file