diff --git a/lib/facter/facts/solaris/virtual.rb b/lib/facter/facts/solaris/virtual.rb new file mode 100644 index 0000000000..fd8c3c839b --- /dev/null +++ b/lib/facter/facts/solaris/virtual.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Facts + module Solaris + class Virtual + FACT_NAME = 'virtual' + + def initialize + @log = Facter::Log.new(self) + end + + def call_the_resolver + @log.debug('Solaris Virtual Resolver') + + fact_value = check_ldom || check_zone || check_xen || check_other_facts || 'physical' + + @log.debug("Fact value is: #{fact_value}") + + Facter::ResolvedFact.new(FACT_NAME, fact_value) + end + + def check_ldom + @log.debug('Checking LDoms') + return unless Facter::Resolvers::Solaris::Ldom.resolve(:role_control) == 'false' + + Facter::Resolvers::Solaris::Ldom.resolve(:role_impl) + end + + def check_zone + @log.debug('Checking LDoms') + zone_name = Facter::Resolvers::Solaris::ZoneName.resolve(:current_zone_name) + + return if zone_name == 'global' + + 'zone' + end + + def check_xen + @log.debug('Checking XEN') + Facter::Resolvers::Xen.resolve(:vm) + end + + def check_other_facts + isa = Facter::Resolvers::Uname.resolve(:processor) + klass = isa == 'sparc' ? 'DmiSparc' : 'Dmi' + + product_name = Facter::Resolvers::Solaris.const_get(klass).resolve(:product_name) + bios_vendor = Facter::Resolvers::Solaris.const_get(klass).resolve(:bios_vendor) + + return 'kvm' if bios_vendor&.include?('Amazon EC2') + + return unless product_name + + Facter::FactsUtils::HYPERVISORS_HASH.each { |key, value| return value if product_name.include?(key) } + + nil + end + end + end +end diff --git a/lib/facter/resolvers/solaris/ldom.rb b/lib/facter/resolvers/solaris/ldom.rb new file mode 100644 index 0000000000..7ecb43f5d6 --- /dev/null +++ b/lib/facter/resolvers/solaris/ldom.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +module Facter + module Resolvers + module Solaris + class Ldom < BaseResolver + # :chassis_serial + # :control_domain + # :domain_name + # :domain_uuid + # :role_control + # :role_io + # :role_root + # :role_service + # :role_impl + + @semaphore = Mutex.new + @fact_list ||= {} + + VIRTINFO_MAPPING = { + chassis_serial: %w[DOMAINCHASSIS serialno], + control_domain: %w[DOMAINCONTROL name], + domain_name: %w[DOMAINNAME name], + domain_uuid: %w[DOMAINUUID uuid], + role_control: %w[DOMAINROLE control], + role_io: %w[DOMAINROLE io], + role_root: %w[DOMAINROLE root], + role_service: %w[DOMAINROLE service], + role_impl: %w[DOMAINROLE impl] + }.freeze + + class << self + private + + def post_resolve(fact_name) + @fact_list.fetch(fact_name) { call_virtinfo(fact_name) } + end + + def call_virtinfo(fact_name) + # return unless File.executable?('/usr/sbin/virtinfo') + + virtinfo_output = Facter::Core::Execution.execute('/usr/sbin/virtinfo -a -p', logger: log) + return if virtinfo_output.empty? + + output_hash = parse_output(virtinfo_output) + return if output_hash.empty? + + VIRTINFO_MAPPING.each do |key, value| + @fact_list[key] = output_hash.dig(*value)&.strip + end + + @fact_list[fact_name] + end + + def parse_output(output) + result = {} + output.each_line do |line| + next unless line.include? 'DOMAIN' + + x = line.split('|') + result[x.shift] = x.map { |f| f.split('=') }.to_h + end + + result + end + end + end + end + end +end diff --git a/spec/facter/facts/solaris/virtual_spec.rb b/spec/facter/facts/solaris/virtual_spec.rb new file mode 100644 index 0000000000..78f61075b7 --- /dev/null +++ b/spec/facter/facts/solaris/virtual_spec.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +describe Facts::Solaris::Virtual do + describe '#call_the_resolver' do + subject(:fact) { Facts::Solaris::Virtual.new } + + let(:processor) { 'i386' } + + before do + allow(Facter::Resolvers::Uname).to receive(:resolve).with(:processor).and_return(processor) + end + + context 'when no hypervisor is found' do + let(:vm) { 'physical' } + let(:current_zone_name) { 'global' } + let(:role_control) { 'false' } + + before do + allow(Facter::Resolvers::Solaris::ZoneName) + .to receive(:resolve) + .with(:current_zone_name) + .and_return(current_zone_name) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_impl).and_return(nil) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_control).and_return(nil) + allow(Facter::Resolvers::Solaris::Dmi).to receive(:resolve).with(:product_name).and_return('unkown') + allow(Facter::Resolvers::Solaris::Dmi).to receive(:resolve).with(:bios_vendor).and_return('unkown') + allow(Facter::Resolvers::VirtWhat).to receive(:resolve).with(:vm).and_return(nil) + end + + it 'returns virtual fact as physical' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'virtual', value: vm) + end + end + + context 'when ldom hypervisor is found' do + let(:vm) { 'LDoms' } + let(:current_zone_name) { 'global' } + + before do + allow(Facter::Resolvers::Solaris::ZoneName) + .to receive(:resolve) + .with(:current_zone_name) + .and_return(current_zone_name) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_impl).and_return(vm) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_control).and_return(role_control) + end + + context 'when role_control is false' do + let(:role_control) { 'false' } + + it 'returns virtual fact as physical' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'virtual', value: vm) + end + end + + context 'when role_control is true' do + let(:role_control) { 'true' } + let(:vm) { 'physical' } + + it 'returns virtual fact as physical' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'virtual', value: vm) + end + end + end + + context 'when zone hypervisor is found' do + let(:vm) { 'zone' } + + before do + allow(Facter::Resolvers::Solaris::ZoneName).to receive(:resolve).with(:current_zone_name).and_return(vm) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_impl).and_return(nil) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_control).and_return(nil) + end + + it 'returns virtual fact as physical' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'virtual', value: vm) + end + end + + context 'when xen hypervisor is found' do + let(:current_zone_name) { 'global' } + let(:role_control) { 'false' } + let(:xen_vm) { 'xenhvm' } + + before do + allow(Facter::Resolvers::Solaris::ZoneName) + .to receive(:resolve) + .with(:current_zone_name) + .and_return(current_zone_name) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_impl).and_return(nil) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_control).and_return(nil) + allow(Facter::Resolvers::Solaris::Dmi).to receive(:resolve).with(:product_name).and_return('unkown') + allow(Facter::Resolvers::Solaris::Dmi).to receive(:resolve).with(:bios_vendor).and_return('unkown') + allow(Facter::Resolvers::Xen).to receive(:resolve).with(:vm).and_return(xen_vm) + end + + it 'returns virtual fact' do + expect(fact.call_the_resolver).to be_an_instance_of(Facter::ResolvedFact).and \ + have_attributes(name: 'virtual', value: xen_vm) + end + end + + context 'when other hypervisors' do + let(:vm) { 'global' } + + before do + allow(Facter::Resolvers::Solaris::ZoneName).to receive(:resolve).with(:current_zone_name).and_return(vm) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_impl).and_return(nil) + allow(Facter::Resolvers::Solaris::Ldom).to receive(:resolve).with(:role_control).and_return(nil) + end + + context 'when processor is i386' do + let(:processor) { 'i386' } + let(:dmi) { class_double(Facter::Resolvers::Solaris::Dmi).as_stubbed_const } + + before do + allow(dmi).to receive(:resolve) + end + + it 'calls Dmi resolver for product_name' do + fact.call_the_resolver + expect(dmi).to have_received(:resolve).with(:product_name) + end + + it 'calls Dmi resolver for bios_vendor' do + fact.call_the_resolver + expect(dmi).to have_received(:resolve).with(:bios_vendor) + end + end + + context 'when processor is sparc' do + let(:processor) { 'sparc' } + let(:dmi) { class_double(Facter::Resolvers::Solaris::DmiSparc).as_stubbed_const } + + before do + allow(dmi).to receive(:resolve) + end + + it 'calls DmiSparc resolver for product_name' do + fact.call_the_resolver + expect(dmi).to have_received(:resolve).with(:product_name) + end + + it 'calls DmiSparc resolver for bios_vendor' do + fact.call_the_resolver + expect(dmi).to have_received(:resolve).with(:bios_vendor) + end + end + end + end +end diff --git a/spec/facter/resolvers/solaris/ldom_spec.rb b/spec/facter/resolvers/solaris/ldom_spec.rb new file mode 100644 index 0000000000..b45db9f8e9 --- /dev/null +++ b/spec/facter/resolvers/solaris/ldom_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +describe Facter::Resolvers::Solaris::Ldom do + subject(:resolver) { Facter::Resolvers::Solaris::Ldom } + + let(:log_spy) { instance_spy(Facter::Log) } + + before do + resolver.instance_variable_set(:@log, log_spy) + allow(Facter::Core::Execution) + .to receive(:execute) + .with('/usr/sbin/virtinfo -a -p', logger: log_spy) + .and_return(output) + end + + after do + resolver.invalidate_cache + end + + context 'when syscall returns valid output' do + let(:output) { load_fixture('virtinfo').read } + + it 'parses chassis_serial' do + expect(resolver.resolve(:chassis_serial)).to eq('AK00358110') + end + + it 'parses control_domain' do + expect(resolver.resolve(:control_domain)).to eq('opdx-a0-sun2') + end + + it 'parses domain_name' do + expect(resolver.resolve(:domain_name)).to eq('sol11-11') + end + + it 'parses domain_uuid' do + expect(resolver.resolve(:domain_uuid)).to eq('415dfab4-c373-4ac0-9414-8bf00801fb72') + end + + it 'parses role_control' do + expect(resolver.resolve(:role_control)).to eq('false') + end + + it 'parses role_io' do + expect(resolver.resolve(:role_io)).to eq('false') + end + + it 'parses role_root' do + expect(resolver.resolve(:role_root)).to eq('false') + end + + it 'parses role_service' do + expect(resolver.resolve(:role_service)).to eq('false') + end + end + + context 'when syscall returns invalid output' do + let(:output) { 'iNvAlId OuTpUt' } + + it 'parses chassis_serial to nil' do + expect(resolver.resolve(:chassis_serial)).to be_nil + end + + it 'parses control_domain to nil' do + expect(resolver.resolve(:control_domain)).to be_nil + end + + it 'parses domain_name to nil' do + expect(resolver.resolve(:domain_name)).to be_nil + end + + it 'parses domain_uuid to nil' do + expect(resolver.resolve(:domain_uuid)).to be_nil + end + + it 'parses role_control to nil' do + expect(resolver.resolve(:role_control)).to be_nil + end + + it 'parses role_io to nil' do + expect(resolver.resolve(:role_io)).to be_nil + end + + it 'parses role_root to nil' do + expect(resolver.resolve(:role_root)).to be_nil + end + + it 'parses role_service to nil' do + expect(resolver.resolve(:role_service)).to be_nil + end + end + + context 'when syscall has no output' do + let(:output) { '' } + + it 'parses chassis_serial to nil' do + expect(resolver.resolve(:chassis_serial)).to be_nil + end + + it 'parses control_domain to nil' do + expect(resolver.resolve(:control_domain)).to be_nil + end + + it 'parses domain_name to nil' do + expect(resolver.resolve(:domain_name)).to be_nil + end + + it 'parses domain_uuid to nil' do + expect(resolver.resolve(:domain_uuid)).to be_nil + end + + it 'parses role_control to nil' do + expect(resolver.resolve(:role_control)).to be_nil + end + + it 'parses role_io to nil' do + expect(resolver.resolve(:role_io)).to be_nil + end + + it 'parses role_root to nil' do + expect(resolver.resolve(:role_root)).to be_nil + end + + it 'parses role_service to nil' do + expect(resolver.resolve(:role_service)).to be_nil + end + end +end diff --git a/spec/fixtures/virtinfo b/spec/fixtures/virtinfo new file mode 100644 index 0000000000..c20ee39262 --- /dev/null +++ b/spec/fixtures/virtinfo @@ -0,0 +1,6 @@ +VERSION 1.0 +DOMAINROLE|impl=LDoms|control=false|io=false|service=false|root=false +DOMAINNAME|name=sol11-11 +DOMAINUUID|uuid=415dfab4-c373-4ac0-9414-8bf00801fb72 +DOMAINCONTROL|name=opdx-a0-sun2 +DOMAINCHASSIS|serialno=AK00358110