348 lines
9.1 KiB
Ruby
Executable File
348 lines
9.1 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
|
|
begin
|
|
require 'rubygems'
|
|
rescue LoadError
|
|
end
|
|
require 'ohai/system'
|
|
require 'json'
|
|
require 'httpclient'
|
|
require 'logger'
|
|
require 'optparse'
|
|
require 'yaml'
|
|
|
|
# The following check doesn't pass on bootstrap,
|
|
# and node will not be discovered. Disabling this
|
|
# until issue is found and resolved.
|
|
#unless /^root$/.match ENV['USER']
|
|
#puts "You must be root"
|
|
#exit 1
|
|
#end
|
|
|
|
AGENT_CONFIG = "/etc/nailgun-agent/config.yaml"
|
|
|
|
class McollectiveConfig
|
|
def initialize(logger)
|
|
@logger = logger
|
|
@configfile = '/etc/mcollective/server.cfg'
|
|
end
|
|
|
|
def get_config_by_key(find_key)
|
|
found_key = nil
|
|
found_value = nil
|
|
# This code is from mcollective's sources
|
|
File.open(@configfile, "r").each do |line|
|
|
# strip blank spaces, tabs etc off the end of all lines
|
|
line.gsub!(/\s*$/, "")
|
|
unless line =~ /^#|^$/
|
|
if (line =~ /(.+?)\s*=\s*(.+)/)
|
|
key = $1
|
|
val = $2
|
|
if key == find_key
|
|
found_key = key
|
|
found_value = val
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if found_key
|
|
return found_value
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def replace_identity(new_id)
|
|
# check if id complies reqs
|
|
raise 'Identities can only match /\w\.\-/' unless new_id.to_s.match(/^[\w\.\-]+$/)
|
|
|
|
value_from_config = get_config_by_key('identity')
|
|
|
|
if value_from_config == new_id.to_s
|
|
@logger.info "MCollective is up to date with identity = #{new_id}"
|
|
else
|
|
config = File.open(@configfile, "rb").read
|
|
unless value_from_config # if key was not found
|
|
config += "\nidentity = #{new_id}\n"
|
|
@logger.info "Identity in mcollective server.cfg has not been found. Setting to '#{new_id}'"
|
|
File.open(@configfile, "w") { |f| f.write(config) }
|
|
else
|
|
# Key found, but it has other value
|
|
@logger.info "Replacing identity in mcollective server.cfg to new value = '#{new_id}'"
|
|
config.gsub!(/^identity[ =].*$/, "identity = #{new_id}")
|
|
File.open(@configfile, "w") { |f| f.write(config) }
|
|
end
|
|
puts `/sbin/service mcollective restart`
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
class NodeAgent
|
|
def initialize(logger, url=nil)
|
|
@logger = logger
|
|
|
|
@api_default_address = "localhost"
|
|
@api_default_port = "8000"
|
|
|
|
@api_url = url
|
|
|
|
if @api_url
|
|
@api_url.chomp!('/')
|
|
else
|
|
begin
|
|
cmdline = ::File.read("/proc/cmdline")
|
|
api_ip = cmdline.match(/\burl=http:\/\/((\d{1,3}\.){3}\d{1,3})/)[1]
|
|
@logger.info("Found admin node IP address in kernel cmdline: #{api_ip}")
|
|
rescue
|
|
@logger.info("Can't get API url from /proc/cmdline. Will use localhost.")
|
|
api_ip = "127.0.0.1"
|
|
end
|
|
@api_url = "http://#{api_ip}:#{@api_default_port}/api"
|
|
end
|
|
|
|
@os = Ohai::System.new()
|
|
@os.all_plugins
|
|
end
|
|
|
|
def put
|
|
headers = {"Content-Type" => "application/json"}
|
|
htclient = HTTPClient.new
|
|
@logger.debug("Trying to put host info into #{@api_url}")
|
|
res = htclient.put("#{@api_url}/nodes/", [_data].to_json, headers)
|
|
if res.status < 200 or res.status >= 300
|
|
@logger.error("HTTP PUT failed: #{res.inspect}")
|
|
end
|
|
return res
|
|
end
|
|
|
|
def post
|
|
headers = {"Content-Type" => "application/json"}
|
|
htclient = HTTPClient.new
|
|
@logger.debug("Trying to create host using #{@api_url}")
|
|
res = htclient.post("#{@api_url}/nodes/", _data.to_json, headers)
|
|
return res
|
|
end
|
|
|
|
def _interfaces
|
|
interfaces = @os[:network][:interfaces].inject([]) do |result, elm|
|
|
result << { :name => elm[0], :addresses => elm[1]["addresses"] }
|
|
end
|
|
interfaces << { "default_interface" => @os["network"]["default_interface"] }
|
|
interfaces << { "default_gateway" => @os["network"]["default_gateway"] }
|
|
interfaces
|
|
end
|
|
|
|
def _metadata
|
|
{
|
|
:block_device => @os["block_device"].to_hash,
|
|
:interfaces => _interfaces,
|
|
:cpu => @os["cpu"].to_hash,
|
|
:memory => @os["memory"].to_hash,
|
|
:serial => _serial
|
|
}
|
|
end
|
|
|
|
def _detailed
|
|
detailed_meta = {
|
|
:system => {
|
|
:manufacturer => _manufacturer,
|
|
:serial => _serial,
|
|
:product => _product_name,
|
|
:family => (@os[:dmi][:system][:family] rescue nil),
|
|
:version => (@os[:dmi][:system][:version] rescue nil),
|
|
},
|
|
:interfaces => [],
|
|
:cpu => {
|
|
:total => (@os[:cpu][:total].to_i rescue nil),
|
|
:real => (@os[:cpu][:real].to_i rescue nil),
|
|
:spec => [],
|
|
},
|
|
:disks => [],
|
|
:memory => (_dmi_memory or _ohai_memory),
|
|
}
|
|
|
|
(@os[:network][:interfaces] or {} rescue {}).each do |int, intinfo|
|
|
(intinfo[:addresses] or {} rescue {}).each do |addr, addrinfo|
|
|
if (addrinfo[:family] rescue nil) =~ /lladdr/
|
|
detailed_meta[:interfaces] << {
|
|
:name => int,
|
|
:mac => addr
|
|
}
|
|
end
|
|
end
|
|
end
|
|
|
|
(@os[:cpu] or {} rescue {}).each do |cpu, cpuinfo|
|
|
if cpu =~ /^[\d]+/ and cpuinfo
|
|
detailed_meta[:cpu][:spec] << {
|
|
# .to_i.to_s is applied in order to round to integer
|
|
:mhz => (cpuinfo[:mhz].to_i rescue nil),
|
|
:model => (cpuinfo[:model_name].gsub(/ +/, " ") rescue nil)
|
|
}
|
|
end
|
|
end
|
|
|
|
(@os[:block_device] or {} rescue {}).each do |bname, binfo|
|
|
if bname =~ /^(sd|hd|vd)./ and binfo
|
|
# 512 bytes is the size of one sector by default
|
|
block_size = 512
|
|
block_size = `cat /sys/block/#{bname}/queue/logical_block_size`.to_i if $?.to_i == 0
|
|
detailed_meta[:disks] << {
|
|
:name => bname,
|
|
:model => binfo[:model],
|
|
:size => (binfo[:size].to_i * block_size),
|
|
}
|
|
end
|
|
|
|
end
|
|
detailed_meta
|
|
end
|
|
|
|
def _is_virtual
|
|
@os["virtualization"]["role"] == "guest" rescue false
|
|
end
|
|
|
|
def _manufacturer
|
|
if _is_virtual
|
|
return @os["virtualization"]["system"].upcase rescue "Unknown"
|
|
else
|
|
# Check in other place for VBox: ohai doesn't show it's virtual with VBox 4.2.4
|
|
return "VBOX" if @os["dmi"]["system"]["product_name"] == "VirtualBox" rescue nil
|
|
return @os[:dmi][:system][:manufacturer] rescue nil
|
|
end
|
|
end
|
|
|
|
def _product_name
|
|
if _is_virtual
|
|
@os["virtualization"]["role"] rescue nil
|
|
else
|
|
@os[:dmi][:system][:product_name] rescue nil
|
|
end
|
|
end
|
|
|
|
def _serial
|
|
@os[:dmi][:system][:serial_number] rescue nil
|
|
end
|
|
|
|
def _dmi_memory
|
|
dmi = `dmidecode`
|
|
return nil if $?.to_i != 0
|
|
mem_mapped = false
|
|
dmi.each do |line|
|
|
mem_mapped = true if /^Memory Array Mapped Address$/.match(line)
|
|
if /^\s*Range Size:\s+(\d+)\s+(mb|gb|kb)/i.match(line) and mem_mapped
|
|
size, unit = $1.to_i, $2
|
|
case unit
|
|
when /^kb$/i
|
|
return (size * 1024)
|
|
when /^mb$/i
|
|
return (size * 1048576)
|
|
when /^gb$/i
|
|
return (size * 1073741824)
|
|
end
|
|
nil
|
|
end
|
|
end
|
|
nil
|
|
end
|
|
|
|
def _ohai_memory
|
|
size = @os['memory']['total'].gsub(/(kb|mb|gb)$/i, "").to_i rescue (return nil)
|
|
unit = $1
|
|
case unit
|
|
when /^kb$/i
|
|
return (size * 1024)
|
|
when /^mb$/i
|
|
return (size * 1048576)
|
|
when /^gb$/i
|
|
return (size * 1073741824)
|
|
end
|
|
nil
|
|
end
|
|
|
|
def _data
|
|
res = {
|
|
:mac => (@os[:macaddress] rescue nil),
|
|
:ip => (@os[:ipaddress] rescue nil),
|
|
:os_platform => (@os[:platform] rescue nil)
|
|
}
|
|
begin
|
|
res.merge!({
|
|
:manufacturer => _manufacturer,
|
|
:platform_name => _product_name,
|
|
:meta => _detailed
|
|
})
|
|
rescue Exception => e
|
|
@logger.error("Error '#{e.message}' in metadata calculation: #{e.backtrace}")
|
|
end
|
|
|
|
res[:status] = @node_state if @node_state
|
|
res
|
|
end
|
|
|
|
def update_state
|
|
@node_state = nil
|
|
if File.exist?("/etc/nailgun_systemtype")
|
|
fl = File.open("/etc/nailgun_systemtype", "r")
|
|
system_type = fl.readline.rstrip
|
|
@node_state = "discover" if system_type == "bootstrap"
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
def write_data_to_file(logger, filename, data)
|
|
if File.exist?(filename)
|
|
File.open(filename, 'r') do |fo|
|
|
text = fo.read
|
|
end
|
|
else
|
|
text = ''
|
|
end
|
|
|
|
if text != data
|
|
begin
|
|
File.open(filename, 'w') do |fo|
|
|
fo.write(data)
|
|
end
|
|
logger.info("Writed data to file '#{filename}'. Data: #{data}")
|
|
rescue Exception => e
|
|
logger.warning("Can't write data to file '#{filename}'. Reason: #{e.message}")
|
|
end
|
|
else
|
|
logger.info("File '#{filename}' is up to date.")
|
|
end
|
|
end
|
|
|
|
|
|
logger = Logger.new(STDOUT)
|
|
logger.level = Logger::DEBUG
|
|
|
|
begin
|
|
logger.info("Trying to load agent config #{AGENT_CONFIG}")
|
|
url = YAML.load_file(AGENT_CONFIG)['url']
|
|
logger.info("Obtained service url from config file: '#{url}'")
|
|
rescue Exception => e
|
|
logger.info("Could not get url from configuration file: #{e.message}, trying other ways..")
|
|
end
|
|
|
|
agent = NodeAgent.new(logger, url)
|
|
agent.update_state
|
|
|
|
post_res = agent.post
|
|
if post_res.status == 409:
|
|
put_res = agent.put
|
|
new_id = JSON.parse(put_res.body)[0]['id']
|
|
elsif post_res.status == 201
|
|
new_id = JSON.parse(post_res.body)['id']
|
|
else
|
|
logger.error post_res.body
|
|
exit 1
|
|
end
|
|
|
|
mc_config = McollectiveConfig.new(logger)
|
|
mc_config.replace_identity(new_id)
|
|
write_data_to_file(logger, '/etc/nailgun_uid', new_id.to_s)
|