lib/nventory.rb in nventory-client-1.65.4 vs lib/nventory.rb in nventory-client-1.67
- old
+ new
@@ -1,21 +1,38 @@
+# $Id: nventory.rb 343 2013-02-15 23:09:46Z saltmanm $
begin
# Try loading facter w/o gems first so that we don't introduce a
# dependency on gems if it is not needed.
require 'facter' # Facter
rescue LoadError
require 'rubygems'
require 'facter'
end
+require 'facter/util/memory' # used for converting MB to GB and stuff
require 'uri'
require 'net/http'
require 'net/https'
require 'cgi'
require 'rexml/document'
require 'yaml'
-# fix for ruby http bug where it encodes the params incorrectly
+# Only use json gem if its there
+begin
+ require 'rubygems'
+ require 'json'
+ HAS_JSON_GEM = true
+rescue LoadError
+ HAS_JSON_GEM = false
+end
+
+#
+# 1.9 compatible solution for ruby 1.8 net/http bug that improperly encodes params in an array.
+# Example:
+# params = {:param1 => ["1", "2", "3"]}
+# bad = param1=123
+# good = param1=1¶m1=2¶m1=3
+#
class Net::HTTP::Put
def set_form_data(params, sep = '&')
params_array = params.map do |k,v|
if v.is_a? Array
v.inject([]){|c, val| c << "#{urlencode(k.to_s)}=#{urlencode(val.to_s)}"}.join(sep)
@@ -23,11 +40,11 @@
"#{urlencode(k.to_s)}=#{urlencode(v.to_s)}"
end
end
self.body = params_array.join(sep)
self.content_type = 'application/x-www-form-urlencoded'
- end
+ end if method_defined? :urlencode
end
module PasswordCallback
@@password = nil
def self.get_password
@@ -69,12 +86,12 @@
parms[:sso_server] ? (@sso_server = parms[:sso_server]) : (@sso_server = nil)
parms[:configfile] ? (configfile = parms[:configfile]) : (configfile = nil)
end
@ca_file = nil
@ca_path = nil
- @dhparams = '/etc/nventory/dhparams'
@delete = false # Initialize the variable, see attr_accessor above
+ @dmi_data = nil
CONFIG_FILES << configfile if configfile
CONFIG_FILES.each do |configfile|
if File.exist?(configfile)
@@ -99,13 +116,10 @@
@ca_file = value
warn "Using ca_file #{@ca_file} from #{configfile}" if (@debug)
elsif key == 'ca_path'
@ca_path = value
warn "Using ca_path #{@ca_path} from #{configfile}" if (@debug)
- elsif key == 'dhparams'
- @dhparams = value
- warn "Using dhparams #{@dhparams} from #{configfile}" if (@debug)
elsif key == 'cookiefile'
@cookiefile = value
warn "Using cookiefile #{@cookiefile} from #{configfile}" if (@debug)
end
end
@@ -298,11 +312,22 @@
#
# Send the query to the server
#
- uri = URI::join(@server, "#{objecttype}.xml?#{querystring}")
+ if parms[:format] == 'json'
+ if HAS_JSON_GEM
+ uri = URI::join(@server, "#{objecttype}.json?#{querystring}")
+ else
+ warn "Warning: Cannot use json format because json gem is not installed. Using xml format instead."
+ parms[:format] = 'xml'
+ uri = URI::join(@server, "#{objecttype}.xml?#{querystring}")
+ end
+ else
+ uri = URI::join(@server, "#{objecttype}.xml?#{querystring}")
+ end
+
req = Net::HTTP::Get.new(uri.request_uri)
warn "GET URL: #{uri}" if (@debug)
response = send_request(req, uri, login, password_callback)
while response.kind_of?(Net::HTTPMovedPermanently)
uri = URI.parse(response['Location'])
@@ -312,30 +337,32 @@
if !response.kind_of?(Net::HTTPOK)
puts response.body
response.error!
end
- #
- # Parse the XML data from the server
- # This tries to render the XML into the best possible representation
- # as a Perl hash. It may need to evolve over time.
- #
-
- puts response.body if (@debug)
- results_xml = REXML::Document.new(response.body)
- results = {}
- if results_xml.root.elements["/#{objecttype}"]
- results_xml.root.elements["/#{objecttype}"].each do |elem|
- # For some reason Elements[] is returning things other than elements,
- # like text nodes
- next if elem.node_type != :element
- data = xml_to_ruby(elem)
- name = data['name'] || data['id']
- if !results[name].nil?
- warn "Duplicate entries for #{name}. Only one will be shown."
+ if parms[:format] == 'json'
+ results = JSON.parse(response.body)
+ else
+ #
+ # Parse the XML data from the server
+ # This tries to render the XML into the best possible representation
+ # as a Perl hash. It may need to evolve over time.
+ puts response.body if (@debug)
+ results_xml = REXML::Document.new(response.body)
+ results = {}
+ if results_xml.root.elements["/#{objecttype}"]
+ results_xml.root.elements["/#{objecttype}"].each do |elem|
+ # For some reason Elements[] is returning things other than elements,
+ # like text nodes
+ next if elem.node_type != :element
+ data = xml_to_ruby(elem)
+ name = data['name'] || data['id']
+ if !results[name].nil?
+ warn "Duplicate entries for #{name}. Only one will be shown."
+ end
+ results[name] = data
end
- results[name] = data
end
end
#puts results.inspect if (@debug)
puts YAML.dump(results) if (@debug)
@@ -539,15 +566,15 @@
else
# Not sure if this is reasonable
data['operating_system[architecture]'] = Facter['hardwaremodel'].value
end
data['kernel_version'] = Facter['kernelrelease'].value
- if Facter['memorysize'] && Facter['memorysize'].value
- data['os_memory'] = Facter['memorysize'].value
- elsif Facter['sp_physical_memory'] && Facter['sp_physical_memory'].value # Mac OS X
+ if Facter.value('memorysize')
+ data['os_memory'] = Facter.value('memorysize')
+ elsif Facter.value('sp_physical_memory') # Mac OS X
# More or less a safe bet that OS memory == physical memory on Mac OS X
- data['os_memory'] = Facter['sp_physical_memory'].value
+ data['os_memory'] = Facter.value('sp_physical_memory')
end
if Facter['swapsize']
data['swap'] = Facter['swapsize'].value
end
# Currently the processorcount fact doesn't even get defined on most platforms
@@ -632,13 +659,15 @@
end
data['processor_core_count'] = get_cpu_core_count
#data['processor_socket_count'] =
#data['power_supply_count'] =
- #data['physical_memory'] =
#data['physical_memory_sizes'] =
+ physical_memory = get_physical_memory
+ data['physical_memory'] = Facter::Memory.scale_number(physical_memory, "MB") if physical_memory
+
nics = []
if Facter['interfaces'] && Facter['interfaces'].value
nics = Facter['interfaces'].value.split(',')
nics.each do |nic|
data["network_interfaces[#{nic}][name]"] = nic
@@ -771,23 +800,35 @@
getdata[:exactget] = {'uniqueid' => [reverse_uniqueid]}
results = get_objects(getdata)
end
end
- # If we failed to find an existing entry based on the unique id
- # fall back to the hostname. This may still fail to find an entry,
- # if this is a new host, but that's OK as it will leave %results
- # as undef, which triggers set_nodes to create a new entry on the
- # server.
+ # If we failed to find an existing entry based on the unique id,
+ # fall back to the hostname.
if results.empty? && data['name']
getdata = {}
getdata[:objecttype] = 'nodes'
getdata[:exactget] = {'name' => [data['name']]}
getdata[:login] = 'autoreg'
results = get_objects(getdata)
end
+ # If we failed to find an existing entry based on the uniqueid and hostname,
+ # fall back to using serial number. This may still fail to find an entry,
+ # if this is a new host, but that's OK as it will leave %results
+ # as undef, which triggers set_nodes to create a new entry on the
+ # server.
+ if results.empty? && data['serial_number'] &&
+ !data['serial_number'].empty? &&
+ data['serial_number'] != "Not Specified"
+ getdata = {}
+ getdata[:objecttype] = 'nodes'
+ getdata[:exactget] = {'serial_number' => [data['serial_number']]}
+ getdata[:login] = 'autoreg'
+ results = get_objects(getdata)
+ end
+
setresults = set_objects('nodes', results, data, 'autoreg')
puts "Command successful" if setresults == 1
end
# Add the given node into the given nodegroup by directly
@@ -898,13 +939,13 @@
parent_groups.each_pair do |parent_group_name, parent_group|
# Use a hash to merge the current and new members and
# eliminate duplicates
merged_nodegroups = child_groups
- if parent_group[child_groups]
- parent_group[child_groups].each do |child_group|
- name = child_group[name]
+ if parent_group['child_groups']
+ parent_group['child_groups'].each do |child_group|
+ name = child_group['name']
merged_nodegroups[name] = child_group
end
end
set_nodegroup_nodegroup_assignments(merged_nodegroups, {parent_group_name => parent_group}, login, password_callback)
@@ -921,22 +962,23 @@
# FIXME: This should talk directly to the node_group_node_groups_assignments
# controller, so that we aren't exposed to the race conditions this
# method currently suffers from.
parent_groups.each_pair do |parent_group_name, parent_group|
desired_child_groups = {}
- if parent_groups[child_groups]
- parent_group[child_groups].each do |child_group|
- name = child_group[name]
+ if parent_group['child_groups']
+ parent_group['child_groups'].each do |child_group|
+ name = child_group['name']
if !child_groups.has_key?(name)
desired_child_groups[name] = child_group
end
end
end
set_nodegroup_nodegroup_assignments(desired_child_groups, {parent_group_name => parent_group}, login, password_callback)
end
end
+
# Both arguments are hashes returned by a 'node_groups' call to get_objects
def set_nodegroup_nodegroup_assignments(child_groups, parent_groups, login, password_callback=PasswordCallback)
child_ids = []
child_groups.each_pair do |child_group_name, child_group|
if child_group['id']
@@ -991,10 +1033,64 @@
else
delete_objects('taggings', taggings_to_del, login, password_callback=PasswordCallback)
end
end
+ # Add a new graffiti to given objects. We're assuming that graffiti is a string
+ # of "key:value" format
+ # obj_type is a string that describe the type of the obj (e.g NodeGroup)
+ # obj_hash is the hash returned from calling get_objects
+ def add_graffiti(obj_type, obj_hash, graffiti, login, password_callback=PasswordCallback)
+ name,value = graffiti.split(':')
+ obj_hash.each_value do |obj|
+ set_objects('graffitis', nil,
+ { :name => name,
+ :value => value,
+ :graffitiable_id => obj['id'],
+ :graffitiable_type => obj_type,
+ },
+ login, password_callback);
+ end
+ end
+
+ # Delete the graffiti (based on the name) from the given objects
+ # obj_type is a string that describe the type of the obj (e.g NodeGroup)
+ # obj_hash is the hash returned from calling get_objects
+ def delete_graffiti(obj_type, obj_hash, graffiti_name, login, password_callback=PasswordCallback)
+ obj_hash.each_value do |obj|
+ getdata = {:objecttype => 'graffitis',
+ :exactget => {:name => graffiti_name,
+ :graffitiable_id => obj['id'],
+ :graffitiable_type => obj_type}
+ }
+ graffitis_to_delete = get_objects(getdata)
+ delete_objects('graffitis', graffitis_to_delete, login, password_callback)
+ end
+ end
+
+ def get_service_tree(service_name)
+ getdata = {}
+ getdata[:objecttype] = 'services'
+ getdata[:exactget] = {'name' => [service_name]}
+ getdata[:includes] = ['nodes', 'parent_services']
+ services = {service_name => []}
+ results = get_objects(getdata)
+
+ if results.has_key?(service_name)
+ if results[service_name].has_key?('parent_services') && !results[service_name]['parent_services'].empty?
+ results[service_name]['parent_services'].each do |service|
+ services[service_name] << get_service_tree(service['name'])
+ end
+ else
+ return service_name
+ end
+ else # no such service
+ return {}
+ end
+ return services
+ end
+
#
# Helper methods
#
def self.get_uniqueid
os = Facter['kernel'].value
@@ -1037,11 +1133,15 @@
end
uuid_entry = `/usr/sbin/dmidecode | grep UUID`
if uuid_entry
uuid = uuid_entry.split(":")[1]
end
- return uuid.strip
+ if uuid
+ return uuid.strip
+ else
+ return nil
+ end
end
def self.get_hardware_profile
result = {:manufacturer => 'Unknown', :model => 'Unknown'}
if Facter['manufacturer'] && Facter['manufacturer'].value # dmidecode
@@ -1068,16 +1168,10 @@
http = proxy.new(uri.host, uri.port)
else
http = Net::HTTP.new(uri.host, uri.port)
end
if uri.scheme == "https"
- # Eliminate the OpenSSL "using default DH parameters" warning
- if File.exist?(@dhparams)
- dh = OpenSSL::PKey::DH.new(IO.read(@dhparams))
- Net::HTTP.ssl_context_accessor(:tmp_dh_callback)
- http.tmp_dh_callback = proc { dh }
- end
http.use_ssl = true
if @ca_file && File.exist?(@ca_file)
http.ca_file = @ca_file
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
@@ -1210,18 +1304,26 @@
cookies
end
# Extract cookie from response and save it to the user's cookie store
def extract_cookie(response, uri, login=nil)
- if response['set-cookie']
- cookiefile = get_cookiefile(login)
+ # response['set-cookie'] returns multiple cookies comma-separated,
+ # which is difficult to parse since Expires field also uses comma in it.
+ # Fortunately, response.get_fields('set-cookie') returns an array of cookies.
+ cookies = response.get_fields('set-cookie')
+ if cookies.nil?
+ puts "extract_cookie finds no cookie in response" if (@debug)
+ return
+ end
+ cookiefile = get_cookiefile(login)
+ cookies.each do |one_cookie|
# It doesn't look like it matters for our purposes at the moment, but
# according to rfc2965, 3.2.2 the Set-Cookie header can contain more
# than one cookie, separated by commas.
- puts "extract_cookie processing #{response['set-cookie']}" if (@debug)
- newcookie = parse_cookie('Set-Cookie: ' + response['set-cookie'])
- return if newcookie.nil?
+ puts "extract_cookie processing #{one_cookie}" if (@debug)
+ newcookie = parse_cookie('Set-Cookie: ' + one_cookie)
+ next if newcookie.nil?
# Some cookie fields are optional, and should default to the
# values in the request. We need to insert these so that we
# save them properly.
# http://cgi.netscape.com/newsref/std/cookie_spec.html
@@ -1265,12 +1367,10 @@
puts "Updating cookiefile #{cookiefile}" if (@debug)
File.open(cookiefile, 'w') { |file| file.puts(cookies.collect{|cookie| cookie[:line]}.join("\n")) }
else
puts "No cookie changes, leaving cookiefile untouched" if (@debug)
end
- else
- puts "extract_cookie finds no cookie in response" if (@debug)
end
end
# Sends requests to the nVentory server and handles any redirects to
# authentication pages or services.
@@ -1662,12 +1762,11 @@
return mounted
end
def getvmstatus
# facter virtual makes calls to commands that are under /sbin
- ENV['PATH'] = "#{ENV['PATH']}:/sbin"
- vmstatus = `facter virtual`
+ vmstatus = Facter.virtual
vmstatus.chomp!
# extra check to see if we're running kvm hypervisor
os = Facter['kernel'].value
if os == 'Linux'
@@ -1887,6 +1986,104 @@
end
end
end
return info
end
+
+ # Parse dmidecode data and put it into a hash
+ # This method is based on the corresponding method in the perl client
+ def get_dmi_data
+ return @dmi_data if @dmi_data
+
+ case Facter.value(:kernel)
+ when 'Linux'
+ return nil unless FileTest.exists?("/usr/sbin/dmidecode")
+
+ output=%x{/usr/sbin/dmidecode 2>/dev/null}
+ when 'FreeBSD'
+ return nil unless FileTest.exists?("/usr/local/sbin/dmidecode")
+
+ output=%x{/usr/local/sbin/dmidecode 2>/dev/null}
+ when 'NetBSD'
+ return nil unless FileTest.exists?("/usr/pkg/sbin/dmidecode")
+
+ output=%x{/usr/pkg/sbin/dmidecode 2>/dev/null}
+ when 'SunOS'
+ return nil unless FileTest.exists?("/usr/sbin/smbios")
+
+ output=%x{/usr/sbin/smbios 2>/dev/null}
+ else
+ warn "Can't get dmi_data because of unsupported OS"
+ return
+ end
+
+ look_for_section_name = false
+ dmi_section = nil
+ dmi_section_data = {}
+ dmi_section_array = nil
+ @dmi_data = {}
+
+ output.split("\n").each do |line|
+ if line =~ /^Handle/
+ if dmi_section && !dmi_section_data.empty?
+ @dmi_data[dmi_section] ||= []
+ @dmi_data[dmi_section] << dmi_section_data
+ end
+ dmi_section = nil
+ dmi_section_data = {}
+ dmi_section_array = nil
+ look_for_section_name = true
+ elsif look_for_section_name
+ next if line =~ /^\s*DMI type/
+ if line =~ /^\s*(.*)/
+ dmi_section = $1
+ look_for_section_name = false
+ end
+ elsif dmi_section && line =~ /^\s*([^:]+):\s*(\S.*)/
+ dmi_section_data[$1] = $2;
+ dmi_section_array = nil
+ elsif dmi_section && line =~ /^\s*([^:]+):$/
+ dmi_section_array = $1
+ elsif dmi_section && dmi_section_array && line =~ /^\s*(\S.+)$/
+ dmi_section_data[dmi_section_array] ||= []
+ dmi_section_data[dmi_section_array] << $1
+ end
+ end
+ @dmi_data
+ end
+
+ # This method is based on the one in the perl client
+ def get_physical_memory
+ # only support Linux and FreeBSD right now
+ os = Facter['kernel']
+ return if os.nil? or (os.value != 'Linux' and os.value != 'FreeBSD')
+
+ physical_memory = 0
+ dmi_data = get_dmi_data
+
+ return if dmi_data.nil? or dmi_data['Memory Device'].nil?
+
+ dmi_data['Memory Device'].each do |mem_dev|
+
+ size = mem_dev['Size']
+ form_factor = mem_dev['Form Factor']
+ locator = mem_dev['Locator']
+ # Some systems report little chunks of memory other than
+ # main system memory as Memory Devices, the 'DIMM' as
+ # form factor seems to indicate main system memory.
+ # Unfortunately some DIMMs are reported with a form
+ # factor of '<OUT OF SPEC>'. In that case fall back to
+ # checking for signs of it being a DIMM in the locator
+ # field.
+ if (size != 'No Module Installed' &&
+ ((form_factor == 'DIMM' || form_factor == 'FB-DIMM' || form_factor == 'SODIMM') ||
+ (form_factor == '<OUT OF SPEC>' && locator =~ /DIMM/)))
+ megs, units = size.split(' ')
+
+ next if units != 'MB'
+ physical_memory += megs.to_i;
+ end
+ end
+ physical_memory
+ end
+
end