nventory-client 1.65.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ module Nventory
2
+ module Client
3
+ require 'nventory'
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Nventory
2
+ module Client
3
+ VERSION = "1.65.4"
4
+ end
5
+ end
@@ -0,0 +1,1892 @@
1
+ begin
2
+ # Try loading facter w/o gems first so that we don't introduce a
3
+ # dependency on gems if it is not needed.
4
+ require 'facter' # Facter
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'facter'
8
+ end
9
+ require 'uri'
10
+ require 'net/http'
11
+ require 'net/https'
12
+ require 'cgi'
13
+ require 'rexml/document'
14
+ require 'yaml'
15
+
16
+ # fix for ruby http bug where it encodes the params incorrectly
17
+ class Net::HTTP::Put
18
+ def set_form_data(params, sep = '&')
19
+ params_array = params.map do |k,v|
20
+ if v.is_a? Array
21
+ v.inject([]){|c, val| c << "#{urlencode(k.to_s)}=#{urlencode(val.to_s)}"}.join(sep)
22
+ else
23
+ "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}"
24
+ end
25
+ end
26
+ self.body = params_array.join(sep)
27
+ self.content_type = 'application/x-www-form-urlencoded'
28
+ end
29
+ end
30
+
31
+ module PasswordCallback
32
+ @@password = nil
33
+ def self.get_password
34
+ while !@@password
35
+ system "stty -echo"
36
+ print "Password: "
37
+ @@password = $stdin.gets.chomp
38
+ system "stty echo"
39
+ end
40
+ @@password
41
+ end
42
+ end
43
+
44
+ # Module and class names are constants, and thus have to start with a
45
+ # capital letter.
46
+ module NVentory
47
+ end
48
+
49
+ CONFIG_FILES = ['/etc/nventory.conf', "#{ENV['HOME']}/.nventory.conf"]
50
+
51
+ class NVentory::Client
52
+ attr_accessor :delete
53
+
54
+ def initialize(data=nil,*moredata)
55
+ if data || moredata
56
+ parms = legacy_initializeparms(data,moredata)
57
+ # def initialize(debug=false, dryrun=false, configfile=nil, server=nil)
58
+ parms[:debug] ? (@debug = parms[:debug]) : @debug = (nil)
59
+ parms[:dryrun] ? (@dryrun = parms[:dryrun]) : @dryrun = (nil)
60
+ parms[:server] ? (@server = parms[:server]) : @server = (nil)
61
+ parms[:cookiefile] ? @cookiefile = parms[:cookiefile] : @cookiefile = "#{ENV['HOME']}/.nventory_cookie"
62
+ if parms[:proxy_server] == false
63
+ @proxy_server = 'nil'
64
+ elsif parms[:proxy_server]
65
+ @proxy_server = parms[:proxy_server]
66
+ else
67
+ @proxy_server = nil
68
+ end
69
+ parms[:sso_server] ? (@sso_server = parms[:sso_server]) : (@sso_server = nil)
70
+ parms[:configfile] ? (configfile = parms[:configfile]) : (configfile = nil)
71
+ end
72
+ @ca_file = nil
73
+ @ca_path = nil
74
+ @dhparams = '/etc/nventory/dhparams'
75
+ @delete = false # Initialize the variable, see attr_accessor above
76
+
77
+ CONFIG_FILES << configfile if configfile
78
+
79
+ CONFIG_FILES.each do |configfile|
80
+ if File.exist?(configfile)
81
+ IO.foreach(configfile) do |line|
82
+ line.chomp!
83
+ next if (line =~ /^\s*$/); # Skip blank lines
84
+ next if (line =~ /^\s*#/); # Skip comments
85
+ key, value = line.split(/\s*=\s*/, 2)
86
+ if key == 'server'
87
+ @server = value
88
+ # Warn the user, as this could potentially be confusing
89
+ # if they don't realize there's a config file lying
90
+ # around
91
+ warn "Using server #{@server} from #{configfile}" if (@debug)
92
+ elsif key == 'sso_server' && !@sso_server
93
+ @sso_server = value
94
+ warn "Using sso_server #{@sso_server} from #{configfile}" if (@debug)
95
+ elsif key == 'proxy_server' && !@proxy_server
96
+ @proxy_server = value
97
+ warn "Using proxy_server #{@proxy_server} from #{configfile}" if (@debug)
98
+ elsif key == 'ca_file'
99
+ @ca_file = value
100
+ warn "Using ca_file #{@ca_file} from #{configfile}" if (@debug)
101
+ elsif key == 'ca_path'
102
+ @ca_path = value
103
+ warn "Using ca_path #{@ca_path} from #{configfile}" if (@debug)
104
+ elsif key == 'dhparams'
105
+ @dhparams = value
106
+ warn "Using dhparams #{@dhparams} from #{configfile}" if (@debug)
107
+ elsif key == 'cookiefile'
108
+ @cookiefile = value
109
+ warn "Using cookiefile #{@cookiefile} from #{configfile}" if (@debug)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ unless @server
116
+ @server = 'http://nventory/'
117
+ warn "Using server #{@server}" if @debug
118
+ end
119
+ @sso_server = 'https://sso.example.com/' unless @sso_server
120
+
121
+ # Make sure the server URL ends in a / so that we can append paths to it
122
+ # using URI.join
123
+ if @server !~ %r{/$}
124
+ @server << '/'
125
+ end
126
+ end
127
+
128
+ def legacy_initializeparms(data,moredata)
129
+ # if data is string, it is legacy method of supplying initialize params:
130
+ # def initialize(debug=false, dryrun=false, configfile=nil, server=nil)
131
+ newdata = {}
132
+ if data.kind_of?(Hash)
133
+ newdata = data
134
+ elsif data || moredata
135
+ newdata[:debug] = data
136
+ newdata[:dryrun] = moredata[0]
137
+ newdata[:configfile] = moredata[1]
138
+ if moredata[2]
139
+ server = moredata[2] if moredata[2]
140
+ if server =~ /^http/
141
+ (server =~ /\/$/) ? (newdata[:server] = server) : (newdata[:server] = "#{server}/")
142
+ else
143
+ newdata[:server] = "http://#{server}/"
144
+ end
145
+ end
146
+ newdata[:proxy_server] = moredata[3]
147
+ else
148
+ raise 'Syntax Error'
149
+ end
150
+ warn "** Using server #{newdata[:server]} **" if newdata[:server]
151
+ warn "** Using proxy_server #{newdata[:proxy_server]} **" if newdata[:proxy_server]
152
+ return newdata
153
+ end
154
+
155
+ def legacy_getparms(data,moredata)
156
+ # if data is string, it is legacy method of supplying get_objects params:
157
+ # def get_objects(objecttype, get, exactget, regexget, exclude, andget, includes=nil, login=nil, password_callback=PasswordCallback)
158
+ newdata = {}
159
+ if data.kind_of?(String)
160
+ raise 'Syntax Error: Missing :objecttype' unless data.kind_of?(String)
161
+ newdata[:objecttype] = data
162
+ newdata[:get] = moredata[0]
163
+ newdata[:exactget] = moredata[1]
164
+ newdata[:regexget] = moredata[2]
165
+ newdata[:exclude] = moredata[3]
166
+ newdata[:andget] = moredata[4]
167
+ newdata[:includes] = moredata[5]
168
+ newdata[:login] = moredata[6]
169
+ newdata[:password_callback] = PasswordCallback
170
+ elsif data.kind_of?(Hash)
171
+ raise 'Syntax Error: Missing :objecttype' unless data[:objecttype].kind_of?(String)
172
+ newdata = data
173
+ newdata[:password_callback] = PasswordCallback unless newdata[:password_callback]
174
+ else
175
+ raise 'Syntax Error'
176
+ end
177
+ return newdata
178
+ end
179
+
180
+ # FIXME: get, exactget, regexget, exclude and includes should all merge into
181
+ # a single search options hash parameter
182
+ def get_objects(data,*moredata)
183
+ parms = legacy_getparms(data,moredata)
184
+ # def get_objects(objecttype, get, exactget, regexget, exclude, andget, includes=nil, login=nil, password_callback=PasswordCallback)
185
+ objecttype = parms[:objecttype]
186
+ get = parms[:get]
187
+ exactget = parms[:exactget]
188
+ regexget = parms[:regexget]
189
+ exclude = parms[:exclude]
190
+ andget = parms[:andget]
191
+ includes = parms[:includes]
192
+ login = parms[:login]
193
+ password_callback = parms[:password_callback]
194
+ # PS-704 - node_groups controller when format.xml, includes some custom model methods that create a lot of querying joins, so this is
195
+ # a way to 'override' it on cli side - the server will look for that param to skip these def methods when it renders. webparams = {:nodefmeth => 1}
196
+ webparams = parms[:webparams]
197
+ #
198
+ # Package up the search parameters in the format the server expects
199
+ #
200
+ metaget = []
201
+ if get
202
+ get.each_pair do |key,values|
203
+ if key == 'enable_aliases' && values == 1
204
+ metaget << "#{key}=#{values}"
205
+ elsif values.length > 1
206
+ values.each do |value|
207
+ metaget << "#{key}[]=#{CGI.escape(value)}"
208
+ end
209
+ else
210
+ # This isn't strictly necessary, specifying a single value via
211
+ # 'key[]=[value]' would work fine, but this makes for a cleaner URL
212
+ # and slightly reduced processing on the backend
213
+ metaget << "#{key}=#{CGI.escape(values[0])}"
214
+ end
215
+ end
216
+ end
217
+ if exactget
218
+ exactget.each_pair do |key,values|
219
+ if key == 'enable_aliases' && values == 1
220
+ metaget << "#{key}=#{values}"
221
+ elsif values.length > 1
222
+ values.each do |value|
223
+ metaget << "exact_#{key}[]=#{CGI.escape(value)}"
224
+ end
225
+ else
226
+ # This isn't strictly necessary, specifying a single value via
227
+ # 'key[]=[value]' would work fine, but this makes for a cleaner URL
228
+ # and slightly reduced processing on the backend
229
+ metaget << "exact_#{key}=#{CGI.escape(values[0])}"
230
+ end
231
+ end
232
+ end
233
+ if regexget
234
+ regexget.each_pair do |key,values|
235
+ if values.length > 1
236
+ values.each do |value|
237
+ metaget << "regex_#{key}[]=#{CGI.escape(value)}"
238
+ end
239
+ else
240
+ # This isn't strictly necessary, specifying a single value via
241
+ # 'key[]=[value]' would work fine, but this makes for a cleaner URL
242
+ # and slightly reduced processing on the backend
243
+ metaget << "regex_#{key}=#{CGI.escape(values[0])}"
244
+ end
245
+ end
246
+ end
247
+ if exclude
248
+ exclude.each_pair do |key,values|
249
+ if values.length > 1
250
+ values.each do |value|
251
+ metaget << "exclude_#{key}[]=#{CGI.escape(value)}"
252
+ end
253
+ else
254
+ # This isn't strictly necessary, specifying a single value via
255
+ # 'key[]=[value]' would work fine, but this makes for a cleaner URL
256
+ # and slightly reduced processing on the backend
257
+ metaget << "exclude_#{key}=#{CGI.escape(values[0])}"
258
+ end
259
+ end
260
+ end
261
+ if andget
262
+ andget.each_pair do |key,values|
263
+ if values.length > 1
264
+ values.each do |value|
265
+ metaget << "and_#{key}[]=#{CGI.escape(value)}"
266
+ end
267
+ else
268
+ # This isn't strictly necessary, specifying a single value via
269
+ # 'key[]=[value]' would work fine, but this makes for a cleaner URL
270
+ # and slightly reduced processing on the backend
271
+ metaget << "and_#{key}=#{CGI.escape(values[0])}"
272
+ end
273
+ end
274
+ end
275
+ if includes
276
+ # includes = ['status', 'rack:datacenter']
277
+ # maps to
278
+ # include[status]=&include[rack]=datacenter
279
+ includes.each do |inc|
280
+ incstring = ''
281
+ if inc.include?(':')
282
+ incparts = inc.split(':')
283
+ lastpart = incparts.pop
284
+ incstring = 'include'
285
+ incparts.each { |part| incstring << "[#{part}]" }
286
+ incstring << "=#{lastpart}"
287
+ else
288
+ incstring = "include[#{inc}]="
289
+ end
290
+ metaget << incstring
291
+ end
292
+ end
293
+ if webparams && webparams.kind_of?(Hash)
294
+ webparams.each_pair{|k,v| metaget << "#{k}=#{v}"}
295
+ end
296
+
297
+ querystring = metaget.join('&')
298
+
299
+ #
300
+ # Send the query to the server
301
+ #
302
+
303
+ uri = URI::join(@server, "#{objecttype}.xml?#{querystring}")
304
+ req = Net::HTTP::Get.new(uri.request_uri)
305
+ warn "GET URL: #{uri}" if (@debug)
306
+ response = send_request(req, uri, login, password_callback)
307
+ while response.kind_of?(Net::HTTPMovedPermanently)
308
+ uri = URI.parse(response['Location'])
309
+ req = Net::HTTP::Get.new(uri.request_uri)
310
+ response = send_request(req, uri, login, password_callback)
311
+ end
312
+ if !response.kind_of?(Net::HTTPOK)
313
+ puts response.body
314
+ response.error!
315
+ end
316
+
317
+ #
318
+ # Parse the XML data from the server
319
+ # This tries to render the XML into the best possible representation
320
+ # as a Perl hash. It may need to evolve over time.
321
+ #
322
+
323
+ puts response.body if (@debug)
324
+ results_xml = REXML::Document.new(response.body)
325
+ results = {}
326
+ if results_xml.root.elements["/#{objecttype}"]
327
+ results_xml.root.elements["/#{objecttype}"].each do |elem|
328
+ # For some reason Elements[] is returning things other than elements,
329
+ # like text nodes
330
+ next if elem.node_type != :element
331
+ data = xml_to_ruby(elem)
332
+ name = data['name'] || data['id']
333
+ if !results[name].nil?
334
+ warn "Duplicate entries for #{name}. Only one will be shown."
335
+ end
336
+ results[name] = data
337
+ end
338
+ end
339
+
340
+ #puts results.inspect if (@debug)
341
+ puts YAML.dump(results) if (@debug)
342
+ results
343
+ end
344
+
345
+ def get_field_names(objecttype, login=nil, password_callback=PasswordCallback)
346
+ uri = URI::join(@server, "#{objecttype}/field_names.xml")
347
+ req = Net::HTTP::Get.new(uri.request_uri)
348
+ warn "GET URL: #{uri}" if (@debug)
349
+ response = send_request(req, uri, login, password_callback)
350
+ while response.kind_of?(Net::HTTPMovedPermanently)
351
+ uri = URI.parse(response['Location'])
352
+ req = Net::HTTP::Get.new(uri.request_uri)
353
+ response = send_request(req, uri, login, password_callback)
354
+ end
355
+ if !response.kind_of?(Net::HTTPOK)
356
+ puts response.body
357
+ response.error!
358
+ end
359
+
360
+ puts response.body if (@debug)
361
+ results_xml = REXML::Document.new(response.body)
362
+ field_names = []
363
+ results_xml.root.elements['/field_names'].each do |elem|
364
+ # For some reason Elements[] is returning things other than elements,
365
+ # like text nodes
366
+ next if elem.node_type != :element
367
+ field_names << elem.text
368
+ end
369
+
370
+ field_names
371
+ end
372
+
373
+ def get_expanded_nodegroup(nodegroup)
374
+ getdata = {}
375
+ getdata[:objecttype] = 'node_groups'
376
+ getdata[:exactget] = {'name' => [nodegroup]}
377
+ getdata[:includes] = ['nodes', 'child_groups']
378
+ results = get_objects(getdata)
379
+ nodes = {}
380
+ if results.has_key?(nodegroup)
381
+ if results[nodegroup].has_key?('nodes')
382
+ results[nodegroup]['nodes'].each { |node| nodes[node['name']] = true }
383
+ end
384
+ if results[nodegroup].has_key?('child_groups')
385
+ results[nodegroup]['child_groups'].each do |child_group|
386
+ get_expanded_nodegroup(child_group['name']).each { |child_group_node| nodes[child_group_node] = true }
387
+ end
388
+ end
389
+ end
390
+ nodes.keys.sort
391
+ end
392
+
393
+ # The results argument can be a reference to a hash returned by a
394
+ # call to get_objects, in which case the data will be PUT to each object
395
+ # there, thus updating them. Or it can be nil, in which case the
396
+ # data will be POSTed to create a new entry.
397
+ def set_objects(objecttypes, results, data, login, password_callback=PasswordCallback)
398
+ # Convert any keys which don't already specify a model
399
+ # from 'foo' to 'objecttype[foo]'
400
+ objecttype = singularize(objecttypes)
401
+ cleandata = {}
402
+ data.each_pair do |key, value|
403
+ if key !~ /\[.+\]/
404
+ cleandata["#{objecttype}[#{key}]"] = value
405
+ else
406
+ cleandata[key] = value
407
+ end
408
+ end
409
+
410
+ #puts cleandata.inspect if (@debug)
411
+ puts YAML.dump(cleandata) if (@debug)
412
+
413
+ successcount = 0
414
+ if results && !results.empty?
415
+ results.each_pair do |result_name, result|
416
+ if @delete
417
+ warn "Deleting objects via set_objects is deprecated, use delete_objects instead"
418
+ uri = URI::join(@server, "#{objecttypes}/#{result['id']}.xml")
419
+ req = Net::HTTP::Delete.new(uri.request_uri)
420
+ req.set_form_data(cleandata)
421
+ response = send_request(req, uri, login, password_callback)
422
+ while response.kind_of?(Net::HTTPMovedPermanently)
423
+ uri = URI.parse(response['Location'])
424
+ req = Net::HTTP::Delete.new(uri.request_uri)
425
+ response = send_request(req, uri, login, password_callback)
426
+ end
427
+ if response.kind_of?(Net::HTTPOK)
428
+ successcount += 1
429
+ else
430
+ puts "DELETE to #{uri} failed for #{result_name}:"
431
+ puts response.body
432
+ end
433
+ # PUT to update an existing object
434
+ elsif result['id']
435
+ uri = URI::join(@server, "#{objecttypes}/#{result['id']}.xml")
436
+ req = Net::HTTP::Put.new(uri.request_uri)
437
+ req.set_form_data(cleandata)
438
+ warn "PUT to URL: #{uri}" if (@debug)
439
+ if !@dryrun
440
+ response = send_request(req, uri, login, password_callback)
441
+ while response.kind_of?(Net::HTTPMovedPermanently)
442
+ uri = URI.parse(response['Location'])
443
+ req = Net::HTTP::Put.new(uri.request_uri)
444
+ req.set_form_data(cleandata)
445
+ response = send_request(req, uri, login, password_callback)
446
+ end
447
+ if response.kind_of?(Net::HTTPOK)
448
+ successcount += 1
449
+ else
450
+ puts "PUT to #{uri} failed for #{result_name}:"
451
+ puts response.body
452
+ end
453
+ end
454
+ else
455
+ warn "set_objects passed a bogus results hash, #{result_name} has no id field"
456
+ end
457
+ end
458
+ else
459
+ uri = URI::join(@server, "#{objecttypes}.xml")
460
+ req = Net::HTTP::Post.new(uri.request_uri)
461
+ req.set_form_data(cleandata)
462
+ warn "POST to URL: #{uri}" if (@debug)
463
+ if !@dryrun
464
+ response = send_request(req, uri, login, password_callback)
465
+ while response.kind_of?(Net::HTTPMovedPermanently)
466
+ uri = URI.parse(response['Location'])
467
+ req = Net::HTTP::Post.new(uri.request_uri)
468
+ req.set_form_data(cleandata)
469
+ response = send_request(req, uri, login, password_callback)
470
+ end
471
+ if response.kind_of?(Net::HTTPOK) || response.kind_of?(Net::HTTPCreated)
472
+ successcount += 1
473
+ else
474
+ puts "POST to #{uri} failed."
475
+ puts response.body
476
+ end
477
+ end
478
+ end
479
+
480
+ successcount
481
+ end
482
+
483
+ # The results argument should be a reference to a hash returned by a
484
+ # call to get_objects.
485
+ def delete_objects(objecttypes, results, login, password_callback=PasswordCallback)
486
+ successcount = 0
487
+ results.each_pair do |result_name, result|
488
+ if result['id']
489
+ uri = URI::join(@server, "#{objecttypes}/#{result['id']}.xml")
490
+ req = Net::HTTP::Delete.new(uri.request_uri)
491
+ response = send_request(req, uri, login, password_callback)
492
+ while response.kind_of?(Net::HTTPMovedPermanently)
493
+ uri = URI.parse(response['Location'])
494
+ req = Net::HTTP::Delete.new(uri.request_uri)
495
+ response = send_request(req, uri, login, password_callback)
496
+ end
497
+ if response.kind_of?(Net::HTTPOK)
498
+ successcount = 0
499
+ else
500
+ warn "Delete of #{result_name} (#{result['id']}) failed:\n" + response.body
501
+ end
502
+ else
503
+ warn "delete_objects passed a bogus results hash, #{result_name} has no id field"
504
+ end
505
+ end
506
+ successcount
507
+ end
508
+
509
+ def register
510
+ data = {}
511
+
512
+ # Tell facter to load everything, otherwise it tries to dynamically
513
+ # load the individual fact libraries using a very broken mechanism
514
+ Facter.loadfacts
515
+
516
+ #
517
+ # Gather software-related information
518
+ #
519
+ data['name'] = Facter['fqdn'].value
520
+ data['updated_at'] = Time.now.strftime("%Y-%m-%d %H:%M:%S")
521
+ if Facter['kernel'] && Facter['kernel'].value == 'Linux' &&
522
+ Facter['lsbdistdescription'] && Facter['lsbdistdescription'].value
523
+ # Strip release version and code name from lsbdistdescription
524
+ lsbdistdesc = Facter['lsbdistdescription'].value
525
+ lsbdistdesc.gsub!(/ release \S+/, '')
526
+ lsbdistdesc.gsub!(/ \([^)]\)/, '')
527
+ data['operating_system[variant]'] = lsbdistdesc
528
+ data['operating_system[version_number]'] = Facter['lsbdistrelease'].value
529
+ elsif Facter['kernel'] && Facter['kernel'].value == 'Darwin' &&
530
+ Facter['macosx_productname'] && Facter['macosx_productname'].value
531
+ data['operating_system[variant]'] = Facter['macosx_productname'].value
532
+ data['operating_system[version_number]'] = Facter['macosx_productversion'].value
533
+ else
534
+ data['operating_system[variant]'] = Facter['operatingsystem'].value
535
+ data['operating_system[version_number]'] = Facter['operatingsystemrelease'].value
536
+ end
537
+ if Facter['architecture'] && Facter['architecture'].value
538
+ data['operating_system[architecture]'] = Facter['architecture'].value
539
+ else
540
+ # Not sure if this is reasonable
541
+ data['operating_system[architecture]'] = Facter['hardwaremodel'].value
542
+ end
543
+ data['kernel_version'] = Facter['kernelrelease'].value
544
+ if Facter['memorysize'] && Facter['memorysize'].value
545
+ data['os_memory'] = Facter['memorysize'].value
546
+ elsif Facter['sp_physical_memory'] && Facter['sp_physical_memory'].value # Mac OS X
547
+ # More or less a safe bet that OS memory == physical memory on Mac OS X
548
+ data['os_memory'] = Facter['sp_physical_memory'].value
549
+ end
550
+ if Facter['swapsize']
551
+ data['swap'] = Facter['swapsize'].value
552
+ end
553
+ # Currently the processorcount fact doesn't even get defined on most platforms
554
+ if Facter['processorcount'] && Facter['processorcount'].value
555
+ # This is generally a virtual processor count (cores and HTs),
556
+ # not a physical CPU count
557
+ data['os_processor_count'] = Facter['processorcount'].value
558
+ elsif Facter['sp_number_processors'] && Facter['sp_number_processors'].value
559
+ data['os_processor_count'] = Facter['sp_number_processors'].value
560
+ end
561
+ data['timezone'] = Facter['timezone'].value if Facter['timezone']
562
+
563
+ # Need custom facts for these
564
+ #data['virtual_client_ids'] =
565
+
566
+ cpu_percent = getcpupercent
567
+ login_count = getlogincount
568
+ disk_usage = getdiskusage
569
+ # have to round it up because server code only takes integer
570
+ data['utilization_metric[percent_cpu][value]'] = cpu_percent.round if cpu_percent
571
+ data['utilization_metric[login_count][value]'] = login_count if login_count
572
+ data['used_space'] = disk_usage[:used_space] if disk_usage
573
+ data['avail_space'] = disk_usage[:avail_space] if disk_usage
574
+ getvolumes.each do |key, value|
575
+ data[key] = value
576
+ end
577
+
578
+ #
579
+ # Gather hardware-related information
580
+ #
581
+ hardware_profile = NVentory::Client::get_hardware_profile
582
+ data['hardware_profile[manufacturer]'] = hardware_profile[:manufacturer]
583
+ data['hardware_profile[model]'] = hardware_profile[:model]
584
+ if Facter['serialnumber'] && Facter['serialnumber'].value
585
+ data['serial_number'] = Facter['serialnumber'].value
586
+ elsif Facter['sp_serial_number'] && Facter['sp_serial_number'].value # Mac OS X
587
+ data['serial_number'] = Facter['sp_serial_number'].value
588
+ end
589
+ if Facter['processor0'] && Facter['processor0'].value
590
+ # FIXME: Parsing this string is less than ideal, as these things
591
+ # are reported as seperate fields by dmidecode, but facter isn't
592
+ # reading that data.
593
+ # Example: Intel(R) Core(TM)2 Duo CPU T7300 @ 2.00GHz
594
+ # Example: Intel(R) Pentium(R) 4 CPU 3.60GHz
595
+ processor = Facter['processor0'].value
596
+ if processor =~ /(\S+)\s(.+)/
597
+ manufacturer = $1
598
+ model = $2
599
+ speed = nil
600
+ if model =~ /(.+\S)\s+\@\s+([\d\.]+.Hz)/
601
+ model = $1
602
+ speed = $2
603
+ elsif model =~ /(.+\S)\s+([\d\.]+.Hz)/
604
+ model = $1
605
+ speed = $2
606
+ end
607
+ data['processor_manufacturer'] = manufacturer.gsub(/\(R\)/, '')
608
+ data['processor_model'] = model
609
+ data['processor_speed'] = speed
610
+ end
611
+ elsif Facter['sp_cpu_type'] && Facter['sp_cpu_type'].value
612
+ # FIXME: Assuming the manufacturer is the first word is
613
+ # less than ideal
614
+ cpu_type = Facter['sp_cpu_type'].value
615
+ if cpu_type =~ /(\S+)\s(.+)/
616
+ data['processor_manufacturer'] = $1
617
+ data['processor_model'] = $2
618
+ end
619
+ data['processor_speed'] = Facter['sp_current_processor_speed'].value
620
+ # It's not clear if system_profiler is reporting the number
621
+ # of physical CPUs or the number seen by the OS. I'm not
622
+ # sure if there are situations in Mac OS where those two can
623
+ # get out of sync. As such this is identical to what is reported
624
+ # for the os_processor_count above.
625
+ data['processor_count'] = Facter['sp_number_processors'].value
626
+ end
627
+
628
+ if Facter['physicalprocessorcount']
629
+ data['processor_count'] = Facter['physicalprocessorcount'].value
630
+ else
631
+ # need to get from dmidecode
632
+ end
633
+
634
+ data['processor_core_count'] = get_cpu_core_count
635
+ #data['processor_socket_count'] =
636
+ #data['power_supply_count'] =
637
+ #data['physical_memory'] =
638
+ #data['physical_memory_sizes'] =
639
+
640
+ nics = []
641
+ if Facter['interfaces'] && Facter['interfaces'].value
642
+ nics = Facter['interfaces'].value.split(',')
643
+ nics.each do |nic|
644
+ data["network_interfaces[#{nic}][name]"] = nic
645
+ data["network_interfaces[#{nic}][hardware_address]"] = Facter["macaddress_#{nic}"].value
646
+ #data["network_interfaces[#{nic}][interface_type]"]
647
+ #data["network_interfaces[#{nic}][physical]"] =
648
+ #data["network_interfaces[#{nic}][up]"] =
649
+ #data["network_interfaces[#{nic}][link]"] =
650
+ #data["network_interfaces[#{nic}][autonegotiate]"] =
651
+ #data["network_interfaces[#{nic}][speed]"] =
652
+ #data["network_interfaces[#{nic}][full_duplex]"] =
653
+ # Facter only captures one address per interface
654
+ data["network_interfaces[#{nic}][ip_addresses][0][address]"] = Facter["ipaddress_#{nic}"].value
655
+ data["network_interfaces[#{nic}][ip_addresses][0][address_type]"] = 'ipv4'
656
+ data["network_interfaces[#{nic}][ip_addresses][0][netmask]"] = Facter["netmask_#{nic}"].value
657
+ #data["network_interfaces[#{nic}][ip_addresses][0][broadcast]"] =
658
+ end
659
+ end
660
+ # get additional nic info that facter doesn't know about
661
+ nic_info = get_nic_info
662
+ nic_info.each do |nic, info|
663
+ next if !nics.include?(nic)
664
+ info.each do |key, value|
665
+ data["network_interfaces[#{nic}][#{key}]"] = value
666
+ end
667
+ end
668
+
669
+ # Mark our NIC data as authoritative so that the server properly
670
+ # updates its records (removing any NICs and IPs we don't specify)
671
+ data['network_interfaces[authoritative]'] = true
672
+
673
+ data['uniqueid'] = NVentory::Client::get_uniqueid
674
+
675
+ # TODO: figure out list of guests if it's a host
676
+ vmstatus = getvmstatus
677
+ if vmstatus == 'xenu'
678
+ data['virtualmode'] = 'guest'
679
+ data['virtualarch'] = 'xen'
680
+ elsif vmstatus == 'xen0'
681
+ data['virtualmode'] = 'host'
682
+ data['virtualarch'] = 'xen'
683
+ elsif vmstatus == 'vmware_server'
684
+ data['virtualmode'] = 'host'
685
+ data['virtualarch'] = 'vmware'
686
+ elsif vmstatus == 'vmware'
687
+ data['virtualmode'] = 'guest'
688
+ data['virtualarch'] = 'vmware'
689
+ elsif vmstatus == 'kvm_host'
690
+ data['virtualmode'] = 'host'
691
+ data['virtualarch'] = 'kvm'
692
+ end
693
+
694
+ if vmstatus == 'kvm_host'
695
+ guests = get_kvm_hostinfo
696
+ guests.each do |vm, vminfo|
697
+ data["vmguest[#{vm}][vmimg_size]"] = vminfo['vmimg_size']
698
+ data["vmguest[#{vm}][vmspace_used]"] = vminfo['vmspace_used']
699
+ end
700
+ end
701
+
702
+ # Looks like this no longer works. virtual_client_ids is not valid
703
+ # field and causes ALL nodes to return....
704
+ # if data['hardware_profile[model]'] == 'VMware Virtual Platform'
705
+ # getdata = {}
706
+ # getdata[:objecttype] = 'nodes'
707
+ # getdata[:exactget] = {'virtual_client_ids' => [data['uniqueid']]}
708
+ # getdata[:login] = 'autoreg'
709
+ # results = get_objects(getdata)
710
+ # if results.length == 1
711
+ # data['virtual_parent_node_id'] = results.values.first['id']
712
+ # elsif results.length > 1
713
+ # warn "Multiple hosts claim this virtual client: #{results.keys.sort.join(',')}"
714
+ # end
715
+ # end
716
+
717
+ # Get console info
718
+ console_type = get_console_type
719
+ if console_type == "Dell DRAC"
720
+ data['console_type'] = "Dell DRAC"
721
+
722
+ drac_info = get_drac_info
723
+
724
+ # Create a NIC for the DRAC and associate it this node
725
+ unless drac_info.empty?
726
+ drac_name = (drac_info[:name] && !drac_info[:name].empty?)? drac_info[:name] : "DRAC"
727
+ data["network_interfaces[#{drac_name}][name]"] = drac_name
728
+ data["network_interfaces[#{drac_name}][hardware_address]"] = drac_info[:mac_address]
729
+ data["network_interfaces[#{drac_name}][ip_addresses][0][address]"] = drac_info[:ip_address]
730
+ data["network_interfaces[#{drac_name}][ip_addresses][0][address_type]"] = "ipv4"
731
+ end
732
+ end
733
+
734
+ # See what chassis/blade enclosure the node is in
735
+ chassis = get_chassis_info
736
+ data["chassis[service_tag]"] = chassis[:service_tag] if !chassis.empty?
737
+ data["chassis[slot_num]"] = chassis[:slot_num] if !chassis.empty?
738
+
739
+ #
740
+ # Report data to server
741
+ #
742
+
743
+ # Check to see if there's an existing entry for this host that matches
744
+ # our unique id. If so we want to update it, even if the hostname
745
+ # doesn't match our current hostname (as it probably indicates this
746
+ # host was renamed).
747
+ results = nil
748
+ if data['uniqueid']
749
+ getdata = {}
750
+ getdata[:objecttype] = 'nodes'
751
+ getdata[:exactget] = {'uniqueid' => [data['uniqueid']]}
752
+ getdata[:login] = 'autoreg'
753
+ results = get_objects(getdata)
754
+ #
755
+ # Check for a match of the reverse uniqueid.
756
+ # Background:
757
+ # Dmidecode versions earlier than 2.10 display
758
+ # the first three fields of the UUID in reverse order
759
+ # due to the use of Big-endian rather than Little-endian
760
+ # byte encoding.
761
+ # Starting with version 2.10, dmidecode uses Little-endian
762
+ # when it finds an SMBIOS >= 2.6. UUID's reported from SMBIOS'
763
+ # earlier than 2.6 are considered "incorrect".
764
+ #
765
+ # After a rebuild/upgrade, rather than creating a new node
766
+ # entry for an existing asset, we'll check for the flipped
767
+ # version of the uniqueid.
768
+ #
769
+ if results.empty? && data['uniqueid'].include?('-')
770
+ reverse_uniqueid = [data['uniqueid'].split('-')[0..2].map { |n| n.split(/(\w\w)/).reverse.join }.join('-'), data['uniqueid'].split('-',4)[3]].join('-')
771
+ getdata[:exactget] = {'uniqueid' => [reverse_uniqueid]}
772
+ results = get_objects(getdata)
773
+ end
774
+ end
775
+
776
+ # If we failed to find an existing entry based on the unique id
777
+ # fall back to the hostname. This may still fail to find an entry,
778
+ # if this is a new host, but that's OK as it will leave %results
779
+ # as undef, which triggers set_nodes to create a new entry on the
780
+ # server.
781
+ if results.empty? && data['name']
782
+ getdata = {}
783
+ getdata[:objecttype] = 'nodes'
784
+ getdata[:exactget] = {'name' => [data['name']]}
785
+ getdata[:login] = 'autoreg'
786
+ results = get_objects(getdata)
787
+ end
788
+
789
+ setresults = set_objects('nodes', results, data, 'autoreg')
790
+ puts "Command successful" if setresults == 1
791
+ end
792
+
793
+ # Add the given node into the given nodegroup by directly
794
+ # creating the node_group_node_assignment
795
+ # First argument is the id of the node
796
+ # Second argument is the id of the nodegroup
797
+ def add_node_group_node_assignment(node_id, node_group_id, login, password_callback=PasswordCallback)
798
+ setdata = {:node_id => node_id, :node_group_id => node_group_id}
799
+ puts "Adding using the following setdata #{setdata.inspect}"
800
+ set_objects('node_group_node_assignments', nil, setdata, login, password_callback)
801
+ end
802
+
803
+ # The first argument is a hash returned by a 'nodes' call to get_objects
804
+ # The second argument is a hash returned by a 'node_groups'
805
+ # call to get_objects
806
+ # This method does the same thing as the add_nodes_to_nodegroups method. However, it
807
+ # will not be susceptible to the race condition mentioned in add_nodes_to_nodegroups method
808
+ # This is because it directly talks to the node_group_node_assignments controller
809
+ def add_node_group_node_assignments(nodes, nodegroups, login, password_callback=PasswordCallback)
810
+ nodegroups.each do |nodegroup_name, nodegroup|
811
+ nodes.each do |nodename, node|
812
+ add_node_group_node_assignment(node['id'], nodegroup['id'], login, password_callback)
813
+ end
814
+ end
815
+ end
816
+
817
+ # The first argument is a hash returned by a 'nodes' call to get_objects
818
+ # The second argument is a hash returned by a 'node_groups'
819
+ # call to get_objects
820
+ # NOTE: For the node groups you must have requested that the server include 'nodes' in the result
821
+ def add_nodes_to_nodegroups(nodes, nodegroups, login, password_callback=PasswordCallback)
822
+ # The server only supports setting a complete list of members of
823
+ # a node group. So we need to retreive the current list of members
824
+ # for each group, merge in the additional nodes that the user wants
825
+ # added, and pass that off to set_nodegroup_assignments to perform
826
+ # the update.
827
+ # FIXME: This should talk directly to the node_group_node_assignments
828
+ # controller, so that we aren't exposed to the race conditions this
829
+ # method currently suffers from.
830
+ nodegroups.each_pair do |nodegroup_name, nodegroup|
831
+ # Use a hash to merge the current and new members and
832
+ # eliminate duplicates
833
+ merged_nodes = nodes.clone
834
+ nodegroup["nodes"].each do |node|
835
+ name = node['name']
836
+ merged_nodes[name] = node
837
+ end
838
+ set_nodegroup_node_assignments(merged_nodes, {nodegroup_name => nodegroup}, login, password_callback)
839
+ end
840
+ end
841
+ # The first argument is a hash returned by a 'nodes' call to get_objects
842
+ # The second argument is a hash returned by a 'node_groups'
843
+ # call to get_objects
844
+ # NOTE: For the node groups you must have requested that the server include 'nodes' in the result
845
+ def remove_nodes_from_nodegroups(nodes, nodegroups, login, password_callback=PasswordCallback)
846
+ # The server only supports setting a complete list of members of
847
+ # a node group. So we need to retreive the current list of members
848
+ # for each group, remove the nodes that the user wants
849
+ # removed, and pass that off to set_nodegroup_assignments to perform
850
+ # the update.
851
+ # FIXME: This should talk directly to the node_group_node_assignments
852
+ # controller, so that we aren't exposed to the race conditions this
853
+ # method currently suffers from.
854
+ nodegroups.each_pair do |nodegroup_name, nodegroup|
855
+ desired_nodes = {}
856
+
857
+ nodegroup['nodes'].each do |node|
858
+ name = node['name']
859
+ if !nodes.has_key?(name)
860
+ desired_nodes[name] = node
861
+ end
862
+ end
863
+
864
+ set_nodegroup_node_assignments(desired_nodes, {nodegroup_name => nodegroup}, login, password_callback)
865
+ end
866
+ end
867
+ # The first argument is a hash returned by a 'nodes' call to get_objects
868
+ # The second argument is a hash returned by a 'node_groups'
869
+ # call to get_objects
870
+ def set_nodegroup_node_assignments(nodes, nodegroups, login, password_callback=PasswordCallback)
871
+ node_ids = []
872
+ nodes.each_pair do |node_name, node|
873
+ if node['id']
874
+ node_ids << node['id']
875
+ else
876
+ warn "set_nodegroup_node_assignments passed a bogus nodes hash, #{node_name} has no id field"
877
+ end
878
+ end
879
+
880
+ nodegroupdata = {}
881
+ node_ids = 'nil' if node_ids.empty?
882
+ nodegroupdata['node_group_node_assignments[nodes][]'] = node_ids
883
+
884
+ set_objects('node_groups', nodegroups, nodegroupdata, login, password_callback)
885
+ end
886
+
887
+ # Both arguments are hashes returned by a 'node_groups' call to get_objects
888
+ # NOTE: For the parent groups you must have requested that the server include 'child_groups' in the result
889
+ def add_nodegroups_to_nodegroups(child_groups, parent_groups, login, password_callback=PasswordCallback)
890
+ # The server only supports setting a complete list of assignments for
891
+ # a node group. So we need to retreive the current list of assignments
892
+ # for each group, merge in the additional node groups that the user wants
893
+ # added, and pass that off to set_nodegroup_nodegroup_assignments to perform
894
+ # the update.
895
+ # FIXME: This should talk directly to the node_group_node_groups_assignments
896
+ # controller, so that we aren't exposed to the race conditions this
897
+ # method currently suffers from.
898
+ parent_groups.each_pair do |parent_group_name, parent_group|
899
+ # Use a hash to merge the current and new members and
900
+ # eliminate duplicates
901
+ merged_nodegroups = child_groups
902
+
903
+ if parent_group[child_groups]
904
+ parent_group[child_groups].each do |child_group|
905
+ name = child_group[name]
906
+ merged_nodegroups[name] = child_group
907
+ end
908
+ end
909
+
910
+ set_nodegroup_nodegroup_assignments(merged_nodegroups, {parent_group_name => parent_group}, login, password_callback)
911
+ end
912
+ end
913
+ # Both arguments are hashes returned by a 'node_groups' call to get_objects
914
+ # NOTE: For the parent groups you must have requested that the server include 'child_groups' in the result
915
+ def remove_nodegroups_from_nodegroups(child_groups, parent_groups, login, password_callback=PasswordCallback)
916
+ # The server only supports setting a complete list of assignments for
917
+ # a node group. So we need to retrieve the current list of assignments
918
+ # for each group, remove the node groups that the user wants
919
+ # removed, and pass that off to set_nodegroup_nodegroup_assignments to perform
920
+ # the update.
921
+ # FIXME: This should talk directly to the node_group_node_groups_assignments
922
+ # controller, so that we aren't exposed to the race conditions this
923
+ # method currently suffers from.
924
+ parent_groups.each_pair do |parent_group_name, parent_group|
925
+ desired_child_groups = {}
926
+ if parent_groups[child_groups]
927
+ parent_group[child_groups].each do |child_group|
928
+ name = child_group[name]
929
+ if !child_groups.has_key?(name)
930
+ desired_child_groups[name] = child_group
931
+ end
932
+ end
933
+ end
934
+
935
+ set_nodegroup_nodegroup_assignments(desired_child_groups, {parent_group_name => parent_group}, login, password_callback)
936
+ end
937
+ end
938
+ # Both arguments are hashes returned by a 'node_groups' call to get_objects
939
+ def set_nodegroup_nodegroup_assignments(child_groups, parent_groups, login, password_callback=PasswordCallback)
940
+ child_ids = []
941
+ child_groups.each_pair do |child_group_name, child_group|
942
+ if child_group['id']
943
+ child_ids << child_group['id']
944
+ else
945
+ warn "set_nodegroup_nodegroup_assignments passed a bogus child groups hash, #{child_group_name} has no id field"
946
+ end
947
+ end
948
+ # cannot pass empty hash therefore, add a 'nil' string. nasty hack and accomodated on the server side code
949
+ child_ids << 'nil' if child_ids.empty?
950
+ nodegroupdata = {}
951
+ nodegroupdata['node_group_node_group_assignments[child_groups][]'] = child_ids
952
+ set_objects('node_groups', parent_groups, nodegroupdata, login, password_callback)
953
+ end
954
+
955
+ # Add a new or pre-existing tag (by name string) to a node_group (by hash returned from get_objects)
956
+ def add_tag_to_node_group(ng_hash, tag_name, login, password_callback=PasswordCallback)
957
+ tag_found = get_objects({:objecttype => 'tags', :exactget => {:name => tag_name}})
958
+ if tag_found.empty?
959
+ tagset_data = { :name => tag_name }
960
+ set_objects('tags',{},tagset_data,login, password_callback)
961
+ tag_found = get_objects({:objecttype => 'tags', :exactget => {:name => tag_name}})
962
+ end
963
+ # tag_found is hash, even tho only one result
964
+ (tag_data = tag_found[tag_found.keys.first]) && (tag_id = tag_data['id'])
965
+ ng_hash.each_pair do |ng_name,ng_data|
966
+ setdata = { :taggable_type => 'NodeGroup', :taggable_id => ng_data['id'], :tag_id => tag_id }
967
+ set_objects('taggings',{},setdata,login,password_callback)
968
+ end
969
+ end
970
+
971
+ # Add a new or pre-existing tag (by name string) to a node_group (by hash returned from get_objects)
972
+ def remove_tag_from_node_group(ng_hash, tag_name, login, password_callback=PasswordCallback)
973
+ tag_found = get_objects({:objecttype => 'tags', :exactget => {:name => tag_name}})
974
+ if tag_found.empty?
975
+ puts "ERROR: Could not find any tags with the name #{tag_name}"
976
+ exit
977
+ end
978
+ # tag_found is hash, even tho only one result
979
+ (tag_data = tag_found[tag_found.keys.first]) && (tag_id = tag_data['id'])
980
+ taggings_to_del = {}
981
+ ng_hash.each_pair do |ng_name,ng_data|
982
+ get_data = {:objecttype => 'taggings',
983
+ :exactget => { :taggable_type => 'NodeGroup', :taggable_id => ng_data['id'], :tag_id => tag_id } }
984
+ tagging_found = get_objects(get_data)
985
+ unless tagging_found.empty?
986
+ taggings_to_del.merge!(tagging_found)
987
+ end
988
+ end
989
+ if taggings_to_del.empty?
990
+ puts "ERROR: Could not find any tags \"#{tag_name}\" assigned to those node_groups"
991
+ else
992
+ delete_objects('taggings', taggings_to_del, login, password_callback=PasswordCallback)
993
+ end
994
+ end
995
+
996
+ #
997
+ # Helper methods
998
+ #
999
+ def self.get_uniqueid
1000
+ os = Facter['kernel'].value
1001
+ hardware_profile = NVentory::Client::get_hardware_profile
1002
+ if os == 'Linux' or os == 'FreeBSD'
1003
+ #
1004
+ if File.exist?('/proc/modules') && `grep -q ^xen /proc/modules` && $? == 0
1005
+ uuid = Facter['macaddress'].value
1006
+ # Dell C6100 don't have unique uuid
1007
+ elsif hardware_profile[:manufacturer] =~ /Dell/ && hardware_profile[:model] == 'C6100'
1008
+ uuid = Facter['macaddress'].value
1009
+ else
1010
+ # best to use UUID from dmidecode
1011
+ uuid = getuuid
1012
+ end
1013
+ # Stupid SeaMicro boxes all have the same UUID below. So we won't
1014
+ # want to use it, use mac address instead
1015
+ if uuid && uuid != "78563412-3412-7856-90AB-CDDEEFAABBCC"
1016
+ uniqueid = uuid
1017
+ # next best thing to use is macaddress
1018
+ else
1019
+ uniqueid = Facter['macaddress'].value
1020
+ end
1021
+ elsif Facter['uniqueid'] && Facter['uniqueid'].value
1022
+ # This sucks, it's just using hostid, which is generally tied to an
1023
+ # IP address, not the physical hardware
1024
+ uniqueid = Facter['uniqueid'].value
1025
+ elsif Facter['sp_serial_number'] && Facter['sp_serial_number'].value
1026
+ # I imagine Mac serial numbers are unique
1027
+ uniqueid = Facter['sp_serial_number'].value
1028
+ end
1029
+ return uniqueid
1030
+ end
1031
+
1032
+ def self.getuuid
1033
+ uuid = nil
1034
+ # dmidecode will fail if not run as root
1035
+ if Process.euid != 0
1036
+ raise "This must be run as root"
1037
+ end
1038
+ uuid_entry = `/usr/sbin/dmidecode | grep UUID`
1039
+ if uuid_entry
1040
+ uuid = uuid_entry.split(":")[1]
1041
+ end
1042
+ return uuid.strip
1043
+ end
1044
+
1045
+ def self.get_hardware_profile
1046
+ result = {:manufacturer => 'Unknown', :model => 'Unknown'}
1047
+ if Facter['manufacturer'] && Facter['manufacturer'].value # dmidecode
1048
+ result[:manufacturer] = Facter['manufacturer'].value.strip
1049
+ result[:model] = Facter['productname'].value.strip
1050
+ elsif Facter['sp_machine_name'] && Facter['sp_machine_name'].value # Mac OS X
1051
+ # There's a small chance of this not being true...
1052
+ result[:manufacturer] = 'Apple'
1053
+ result[:model] = Facter['sp_machine_name'].value.strip
1054
+ end
1055
+ return result
1056
+ end
1057
+
1058
+ #
1059
+ # Private methods
1060
+ #
1061
+ private
1062
+
1063
+ def make_http(uri)
1064
+ http = nil
1065
+ if @proxy_server
1066
+ proxyuri = URI.parse(@proxy_server)
1067
+ proxy = Net::HTTP::Proxy(proxyuri.host, proxyuri.port)
1068
+ http = proxy.new(uri.host, uri.port)
1069
+ else
1070
+ http = Net::HTTP.new(uri.host, uri.port)
1071
+ end
1072
+ if uri.scheme == "https"
1073
+ # Eliminate the OpenSSL "using default DH parameters" warning
1074
+ if File.exist?(@dhparams)
1075
+ dh = OpenSSL::PKey::DH.new(IO.read(@dhparams))
1076
+ Net::HTTP.ssl_context_accessor(:tmp_dh_callback)
1077
+ http.tmp_dh_callback = proc { dh }
1078
+ end
1079
+ http.use_ssl = true
1080
+ if @ca_file && File.exist?(@ca_file)
1081
+ http.ca_file = @ca_file
1082
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
1083
+ end
1084
+ if @ca_path && File.directory?(@ca_path)
1085
+ http.ca_path = @ca_path
1086
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
1087
+ end
1088
+ end
1089
+ http
1090
+ end
1091
+
1092
+ # Returns the path to the cookiefile to be used.
1093
+ # Create the file and correct permissions on
1094
+ # the cookiefile if needed
1095
+ def get_cookiefile(login=nil)
1096
+ # autoreg has a special file
1097
+ if login == 'autoreg'
1098
+ @cookiefile = '/root/.nventory_cookie_autoreg'
1099
+ if ! File.directory?('/root')
1100
+ Dir.mkdir('/root')
1101
+ end
1102
+ end
1103
+ # Create the cookie file if it doesn't already exist
1104
+ if !File.exist?(@cookiefile)
1105
+ warn "Creating #{@cookiefile}"
1106
+ File.open(@cookiefile, 'w') { |file| }
1107
+ end
1108
+ # Ensure the permissions on the cookie file are appropriate,
1109
+ # as it will contain a session key that could be used by others
1110
+ # to impersonate this user to the server.
1111
+ st = File.stat(@cookiefile)
1112
+ if st.mode & 07177 != 0
1113
+ warn "Correcting permissions on #{@cookiefile}"
1114
+ File.chmod(st.mode & 0600, @cookiefile)
1115
+ end
1116
+ @cookiefile
1117
+ end
1118
+
1119
+ # Sigh, Ruby doesn't have a library for handling a persistent
1120
+ # cookie store so we have to do the dirty work ourselves. This
1121
+ # is by no means a full implementation, it's just enough to do
1122
+ # what's needed here.
1123
+
1124
+ # Break's a Set-Cookie line up into its constituent parts
1125
+ # Example from http://en.wikipedia.org/wiki/HTTP_cookie:
1126
+ # Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
1127
+ def parse_cookie(line)
1128
+ cookie = nil
1129
+ if line =~ /^Set-Cookie\d?: .+=.+/
1130
+ cookie = {}
1131
+ line.chomp!
1132
+ cookie[:line] = line
1133
+ # Remove the Set-Cookie portion of the line
1134
+ setcookie, rest = line.split(' ', 2)
1135
+ # Then break off the name and value from the cookie attributes
1136
+ namevalue, rawattributes = rest.split('; ', 2)
1137
+ name, value = namevalue.split('=', 2)
1138
+ cookie[:name] = name
1139
+ cookie[:value] = value
1140
+ attributes = {}
1141
+ rawattributes.split('; ').each do |attribute|
1142
+ attrname, attrvalue = attribute.split('=', 2)
1143
+ # The Perl cookie jar uses a non-standard syntax, which seems to
1144
+ # include wrapping some fields (particularly path) in quotes. The
1145
+ # Perl nVentory library uses the Perl cookie jar code so we need to be
1146
+ # compatible with it.
1147
+ if attrvalue =~ /^".*"$/
1148
+ attrvalue.sub!(/^"/, '')
1149
+ attrvalue.sub!(/"$/, '')
1150
+ end
1151
+ # rfc2965, 3.2.2:
1152
+ # If an attribute appears more than once in a cookie, the client
1153
+ # SHALL use only the value associated with the first appearance of
1154
+ # the attribute; a client MUST ignore values after the first.
1155
+ if !attributes[attrname]
1156
+ attributes[attrname] = attrvalue
1157
+ end
1158
+ end
1159
+ cookie[:attributes] = attributes
1160
+ else
1161
+ # Invalid lines in the form of comments and blank lines are to be
1162
+ # expected when we're called by read_cookiefile, so don't treat this as
1163
+ # a big deal.
1164
+ puts "parse_cookie passed invalid line: #{line}" if (@debug)
1165
+ end
1166
+ cookie
1167
+ end
1168
+
1169
+ # Returns an array of cookies from the specified cookiefile
1170
+ def read_cookiefile(cookiefile)
1171
+ warn "Using cookies from #{cookiefile}" if (@debug)
1172
+ cookies = []
1173
+ IO.foreach(cookiefile) do |line|
1174
+ cookie = parse_cookie(line)
1175
+ if cookie && cookie[:attributes] && cookie[:attributes]["expires"]
1176
+ if DateTime.parse(cookie[:attributes]["expires"]) < DateTime.now
1177
+ warn "Cookie expired: #{cookie[:line]}" if @debug
1178
+ next
1179
+ end
1180
+ end
1181
+ if cookie
1182
+ cookies << cookie
1183
+ end
1184
+ end
1185
+ cookies
1186
+ end
1187
+
1188
+ # This returns any cookies in the cookiefile which have domain and path
1189
+ # settings that match the specified uri.
1190
+ def get_cookies_for_uri(cookiefile, uri)
1191
+ cookies = []
1192
+ latest_cookie = []
1193
+ counter = 0
1194
+ read_cookiefile(cookiefile).each do |cookie|
1195
+ next unless uri.host =~ Regexp.new("#{cookie[:attributes]['domain']}$")
1196
+ next unless uri.path =~ Regexp.new("^#{cookie[:attributes]['path'].gsub(/,.*/,'')}") # gsub in case didn't parse out comma seperator for new cookie
1197
+ # if there are more than 1 cookie , we only want the one w/ latest expiration
1198
+ if cookie[:attributes]["expires"]
1199
+ unless latest_cookie.empty?
1200
+ cookie_expiration = DateTime.parse(cookie[:attributes]["expires"])
1201
+ latest_cookie[0] < cookie_expiration ? (latest_cookie = [cookie_expiration, cookie]) : next
1202
+ else
1203
+ latest_cookie = [ DateTime.parse(cookie[:attributes]["expires"]), cookie ]
1204
+ end
1205
+ else
1206
+ cookies << cookie
1207
+ end
1208
+ end
1209
+ cookies << latest_cookie[1] unless latest_cookie.empty?
1210
+ cookies
1211
+ end
1212
+
1213
+ # Extract cookie from response and save it to the user's cookie store
1214
+ def extract_cookie(response, uri, login=nil)
1215
+ if response['set-cookie']
1216
+ cookiefile = get_cookiefile(login)
1217
+ # It doesn't look like it matters for our purposes at the moment, but
1218
+ # according to rfc2965, 3.2.2 the Set-Cookie header can contain more
1219
+ # than one cookie, separated by commas.
1220
+ puts "extract_cookie processing #{response['set-cookie']}" if (@debug)
1221
+ newcookie = parse_cookie('Set-Cookie: ' + response['set-cookie'])
1222
+ return if newcookie.nil?
1223
+
1224
+ # Some cookie fields are optional, and should default to the
1225
+ # values in the request. We need to insert these so that we
1226
+ # save them properly.
1227
+ # http://cgi.netscape.com/newsref/std/cookie_spec.html
1228
+ if !newcookie[:attributes]['domain']
1229
+ puts "Adding domain #{uri.host} to cookie" if (@debug)
1230
+ newcookie = parse_cookie(newcookie[:line] + "; domain=#{uri.host}")
1231
+ end
1232
+ if !newcookie[:attributes]['path']
1233
+ puts "Adding path #{uri.path} to cookie" if (@debug)
1234
+ newcookie = parse_cookie(newcookie[:line] + "; path=#{uri.path}")
1235
+ end
1236
+ cookies = []
1237
+ change = false
1238
+ existing_cookie = false
1239
+ read_cookiefile(cookiefile).each do |cookie|
1240
+ # Remove any existing cookies with the same name, domain and path
1241
+ puts "Comparing #{cookie.inspect} to #{newcookie.inspect}" if (@debug)
1242
+ if cookie[:name] == newcookie[:name] &&
1243
+ cookie[:attributes]['domain'] == newcookie[:attributes]['domain'] &&
1244
+ cookie[:attributes]['path'] == newcookie[:attributes]['path']
1245
+ existing_cookie = true
1246
+ if cookie == newcookie
1247
+ puts "Existing cookie is identical to new cookie" if (@debug)
1248
+ else
1249
+ # Cookie removed by virtue of us not saving it here
1250
+ puts "Replacing existing but not identical cookie #{cookie.inspect}" if (@debug)
1251
+ cookies << newcookie
1252
+ change = true
1253
+ end
1254
+ else
1255
+ puts "Keeping non-matching cookie #{cookie.inspect}" if (@debug)
1256
+ cookies << cookie
1257
+ end
1258
+ end
1259
+ if !existing_cookie
1260
+ puts "No existing cookie matching new cookie, adding new cookie" if (@debug)
1261
+ cookies << newcookie
1262
+ change = true
1263
+ end
1264
+ if change
1265
+ puts "Updating cookiefile #{cookiefile}" if (@debug)
1266
+ File.open(cookiefile, 'w') { |file| file.puts(cookies.collect{|cookie| cookie[:line]}.join("\n")) }
1267
+ else
1268
+ puts "No cookie changes, leaving cookiefile untouched" if (@debug)
1269
+ end
1270
+ else
1271
+ puts "extract_cookie finds no cookie in response" if (@debug)
1272
+ end
1273
+ end
1274
+
1275
+ # Sends requests to the nVentory server and handles any redirects to
1276
+ # authentication pages or services.
1277
+ def send_request(req, uri, login, password_callback=PasswordCallback,loopcounter=0,stopflag=false)
1278
+ if loopcounter > 7
1279
+ if stopflag
1280
+ raise "Infinite loop detected"
1281
+ else
1282
+ warn "Loop detected. Clearing out cookiefile.."
1283
+ loopcounter = 0
1284
+ stopflag = true
1285
+ File.open(get_cookiefile(login), 'w') { |file| file.write(nil) }
1286
+ end
1287
+ end
1288
+ cookies = get_cookies_for_uri(get_cookiefile(login), uri)
1289
+ if !cookies.empty?
1290
+ cookiestring = cookies.collect{|cookie| "#{cookie[:name]}=#{cookie[:value]}" }.join('; ')
1291
+ puts "Inserting cookies into request: #{cookiestring}" if (@debug)
1292
+ req['Cookie'] = cookiestring
1293
+ end
1294
+
1295
+ response = make_http(uri).request(req)
1296
+ extract_cookie(response, uri, login)
1297
+
1298
+ # Check for signs that the server wants us to authenticate
1299
+ password = nil
1300
+ if login == 'autoreg'
1301
+ password = 'mypassword'
1302
+ end
1303
+ # nVentory will redirect to the login controller if authentication is
1304
+ # required. The scheme and port in the redirect location could be either
1305
+ # the standard server or the https variant, depending on whether or not
1306
+ # the server administration has turned on the ssl_requirement plugin.
1307
+ if response.kind_of?(Net::HTTPFound) &&
1308
+ response['Location'] &&
1309
+ URI.parse(response['Location']).host == URI.parse(@server).host &&
1310
+ URI.parse(response['Location']).path == URI.join(@server, 'login/login').path
1311
+ puts "Server responsed with redirect to nVentory login: #{response['Location']}" if (@debug)
1312
+ loginuri = URI.parse(response['Location'])
1313
+ ####################### Fix by darrendao - force it to use https ##########################
1314
+ # This is needed because if you're not usign https, then you will get
1315
+ # redirected to https login page, rather than being logged in. So the check down there will
1316
+ # will.
1317
+ loginuri.scheme = 'https'
1318
+ loginuri = URI.parse(loginuri.to_s)
1319
+ ############################################################################################
1320
+ loginreq = Net::HTTP::Post.new(loginuri.request_uri)
1321
+ if password_callback.kind_of?(Module)
1322
+ password = password_callback.get_password if (!password)
1323
+ else
1324
+ password = password_callback if !password
1325
+ end
1326
+ loginreq.set_form_data({'login' => login, 'password' => password})
1327
+ # Include the cookies so the server doesn't have to generate another
1328
+ # session for us.
1329
+ loginreq['Cookie'] = cookiestring
1330
+ loginresponse = make_http(loginuri).request(loginreq)
1331
+ if @debug
1332
+ puts "nVentory auth POST response (#{loginresponse.code}):"
1333
+ if loginresponse.body.strip.empty?
1334
+ puts '<Body empty>'
1335
+ else
1336
+ puts loginresponse.body
1337
+ end
1338
+ end
1339
+ # The server always sends back a 302 redirect in response to a login
1340
+ # attempt. You get redirected back to the login page if your login
1341
+ # failed, or redirected to your original page or the main page if the
1342
+ # login succeeded.
1343
+ if loginresponse.kind_of?(Net::HTTPFound) &&
1344
+ URI.parse(loginresponse['Location']).path != loginuri.path
1345
+ puts "Authentication against nVentory server succeeded" if (@debug)
1346
+ extract_cookie(loginresponse, loginuri, login)
1347
+ puts "Resending original request now that we've authenticated" if (@debug)
1348
+ return send_request(req, uri, login, password_callback)
1349
+ else
1350
+ puts "Authentication against nVentory server failed" if (@debug)
1351
+ end
1352
+ end
1353
+
1354
+ # An SSO-enabled app will redirect to SSO if authentication is required
1355
+ if response.kind_of?(Net::HTTPFound) && response['Location'] && URI.parse(response['Location']).host == URI.parse(@sso_server).host
1356
+ puts "Server responsed with redirect to SSO login: #{response['Location']}" if (@debug)
1357
+ if login == 'autoreg'
1358
+ loginuri = URI.join(@server, 'login/login')
1359
+ puts "** Login user is 'autoreg'. Changing loginuri to #{loginuri.to_s}" if @debug
1360
+ unless loginuri.scheme == 'https'
1361
+ loginuri.scheme = 'https'
1362
+ loginuri = URI.parse(loginuri.to_s)
1363
+ end
1364
+ else
1365
+ loginuri = URI.parse(response['Location'])
1366
+ end
1367
+ # update the loginuri to the non-redirect uri of sso
1368
+ loginuri.path = '/login'
1369
+ loginuri.query = 'noredirects=1'
1370
+ loginreq = Net::HTTP::Post.new(loginuri.request_uri)
1371
+ if password_callback.kind_of?(Module)
1372
+ password = password_callback.get_password if (!password)
1373
+ else
1374
+ password = password_callback if !password
1375
+ end
1376
+ loginreq.set_form_data({'login' => login, 'password' => password})
1377
+ # It probably doesn't matter, but include the cookies again for good
1378
+ # measure
1379
+ loginreq['Cookie'] = cookiestring
1380
+ # Telling the SSO server we want XML back gets responses that are easier
1381
+ # to parse.
1382
+ loginreq['Accept'] = 'application/xml'
1383
+ loginresponse = make_http(loginuri).request(loginreq)
1384
+ # if it's a redirect (such as due to NON-fqdn) loop so that it follows until no further redirect
1385
+ while [Net::HTTPMovedPermanently, Net::HTTPFound].include?(loginresponse.class)
1386
+ if loginresponse.kind_of?(Net::HTTPFound) && loginresponse['Location'] =~ /sso.*\/session\/token.*/
1387
+ puts "** Found session token" if @debug
1388
+ break
1389
+ end
1390
+ puts "** Following redirect #{loginresponse.class.to_s} => #{loginresponse['Location'].to_s}" if @debug
1391
+ loginuri = URI.parse(loginresponse['Location'])
1392
+ loginreq = Net::HTTP::Post.new(loginuri.request_uri)
1393
+ loginreq.set_form_data({'login' => login, 'password' => password})
1394
+ loginresponse = make_http(loginuri).request(loginreq)
1395
+ end # while loginresponse.kind_of?(Net::HTTPMovedPermanently)
1396
+
1397
+ if @debug
1398
+ puts "AUTH POST response (#{loginresponse.code}):"
1399
+ if loginresponse.body.strip.empty?
1400
+ puts '<Body empty>'
1401
+ else
1402
+ puts loginresponse.body
1403
+ end
1404
+ end
1405
+
1406
+ # SSO does a number of redirects until you get to the right domain but should just follow once and get the cookie, will become Net::HTTPNotAcceptable (406).
1407
+ if loginresponse.kind_of?(Net::HTTPFound) && loginresponse['Location'] =~ /sso.*\/session\/token.*/
1408
+ puts "** Following redirect #{loginresponse.class.to_s} => #{loginresponse['Location'].to_s}" if @debug
1409
+ loginuri = URI.parse(loginresponse['Location'])
1410
+ loginreq = Net::HTTP::Get.new(loginuri.request_uri)
1411
+ loginresponse = make_http(loginuri).request(loginreq)
1412
+ end
1413
+
1414
+ # The SSO server sends back 200 if authentication succeeds, 401 or 403
1415
+ # if it does not.
1416
+ if loginresponse.kind_of?(Net::HTTPSuccess) || (loginresponse.kind_of?(Net::HTTPFound) && loginresponse['Location'] =~ /^#{loginuri.scheme}:\/\/#{loginuri.host}\/$/ ) || loginresponse.kind_of?(Net::HTTPNotAcceptable)
1417
+ puts "Authentication against server succeeded" if (@debug)
1418
+ extract_cookie(loginresponse, loginuri, login)
1419
+ puts "Resending original request now that we've authenticated" if (@debug)
1420
+ loopcounter += 1
1421
+ return send_request(req, uri, login, password_callback, loopcounter,stopflag)
1422
+ else
1423
+ puts "Authentication against server failed" if (@debug)
1424
+ end
1425
+ end
1426
+
1427
+ response
1428
+ end
1429
+
1430
+ def xml_to_ruby(xmlnode)
1431
+ # The server includes a hint as to the type of data structure
1432
+ # in the XML
1433
+ data = nil
1434
+ if xmlnode.attributes['type'] == 'array'
1435
+ data = []
1436
+ xmlnode.elements.each { |child| data << xml_to_ruby(child) }
1437
+ elsif xmlnode.size <= 1
1438
+ data = xmlnode.text
1439
+ else
1440
+ data = {}
1441
+ xmlnode.elements.each do |child|
1442
+ field = child.name
1443
+ data[field] = xml_to_ruby(child)
1444
+ end
1445
+ end
1446
+ data
1447
+ end
1448
+
1449
+ # FIXME: Would be really nice to figure out a way to use the Rails inflector
1450
+ def singularize(word)
1451
+ singular = nil
1452
+ # statuses -> status
1453
+ # ip_addresses -> ip_address
1454
+ if (word =~ /(.*s)es$/)
1455
+ singular = $1
1456
+ # nodes -> node
1457
+ # vips -> vip
1458
+ elsif (word =~ /(.*)s$/)
1459
+ singular = $1
1460
+ else
1461
+ singular = word
1462
+ end
1463
+ singular
1464
+ end
1465
+
1466
+ def find_sar
1467
+ path_env = (ENV['PATH'] || "").split(':')
1468
+ other_paths = ["/usr/bin", "/data/svc/sysstat/bin"]
1469
+ sarname = 'sar'
1470
+ (path_env | other_paths).each do |path|
1471
+ if File.executable?(File.join(path, sarname))
1472
+ return File.join(path, sarname)
1473
+ end
1474
+ end
1475
+ end
1476
+
1477
+ def get_sar_data(sar_dir=nil, day = nil)
1478
+ sar = find_sar
1479
+ result = []
1480
+ cmd = nil
1481
+ ENV['LC_TIME']='POSIX'
1482
+ if day
1483
+ cmd = "#{sar} -u -f #{sar_dir}/sa#{day}"
1484
+ else
1485
+ cmd = "#{sar} -u"
1486
+ end
1487
+ output = `#{cmd}`
1488
+ output.split("\n").each do |line|
1489
+ result << line unless line =~ /(average|cpu|%|linux)/i
1490
+ end
1491
+ result
1492
+ end
1493
+
1494
+ # I'm sure there's a better way to do all of these. However,
1495
+ # I'm just following the way the code was written in Perl.
1496
+ def getcpupercent
1497
+ return nil if !Facter['kernel'] or Facter['kernel'].value != 'Linux'
1498
+ sar_dir = "/var/log/sa"
1499
+ end_time = Time.now
1500
+ start_time = end_time - 60*60*3
1501
+ end_date = end_time.strftime("%d")
1502
+ start_date = start_time.strftime("%d")
1503
+
1504
+ data_points = []
1505
+ # all hours in same day so just make list of all hours to look for
1506
+ if end_date == start_date
1507
+ today_sar = get_sar_data
1508
+ return false if today_sar.empty?
1509
+
1510
+ # We only take avg of last 3 hours
1511
+ (start_time.hour..end_time.hour).each do | hour |
1512
+ hour = "0#{hour}" if hour < 10
1513
+ today_sar.each do |line|
1514
+ data_points << $1.to_f if line =~ /^#{hour}:.*\s(\S+)$/
1515
+ end
1516
+ end
1517
+ else
1518
+ today_sar = get_sar_data
1519
+ yesterday_sar = get_sar_data(sar_dir, start_date)
1520
+ return false if today_sar.empty? or yesterday_sar.empty?
1521
+ # Parse today sar data
1522
+ (0..end_time.hour).each do | hour |
1523
+ hour = "0#{hour}" if hour < 10
1524
+ today_sar.each do |line|
1525
+ data_points << $1.to_f if line =~ /^#{hour}:.*\s(\S+)$/
1526
+ end
1527
+ end
1528
+
1529
+ # Parse yesterday sar data
1530
+ (start_time.hour..23).each do | hour |
1531
+ hour = "0#{hour}" if hour < 10
1532
+ yesterday_sar.each do |line|
1533
+ data_points << $1.to_f if line =~ /^#{hour}:.*\s(\S+)$/
1534
+ end
1535
+ end
1536
+ end
1537
+
1538
+ # no data points
1539
+ return nil if data_points.empty?
1540
+
1541
+ avg = data_points.inject(0.0) { |sum, el| sum + el } / data_points.size
1542
+ # sar reports % idle, so need the opposite
1543
+ result = 100 - avg
1544
+ return result
1545
+ end
1546
+
1547
+ # This is based on the perl version in OSInfo.pm
1548
+ def getlogincount
1549
+
1550
+ # darrendao: Looks like this number has to match up with how often
1551
+ # nventory-client is run in the crontab, otherwise, nventory server ends up
1552
+ # miscalculating the sum... bad...
1553
+ # How many hours of data we need to sample, not to exceed 24h
1554
+ minus_hours = 3
1555
+
1556
+ # get unix cmd 'last' content
1557
+ begin
1558
+ content = `last`
1559
+ rescue
1560
+ warn "Failed to run 'last' command"
1561
+ return nil
1562
+ end
1563
+
1564
+
1565
+ counter = 0
1566
+
1567
+ (0..minus_hours).each do | minus_hour |
1568
+ target_time = Time.now - 60*60*minus_hour
1569
+ time_str = target_time.strftime("%b %d %H")
1570
+ content.split("\n").each do |line|
1571
+ counter += 1 if line =~ /#{time_str}/
1572
+ end
1573
+ end
1574
+ return counter
1575
+ end
1576
+
1577
+ # This is based on the perl version in OSInfo.pm
1578
+ def getdiskusage
1579
+ content = ""
1580
+ begin
1581
+ content = `df -k`
1582
+ rescue
1583
+ warn "Failed to run df command"
1584
+ return nil
1585
+ end
1586
+ used_space = 0
1587
+ avail_space = 0
1588
+ content.split("\n").each do |line|
1589
+ if line =~ /\s+\d+\s+(\d+)\s+(\d+)\s+\d+%\s+\/($|home$)/
1590
+ used_space += $1.to_i
1591
+ avail_space += $2.to_i
1592
+ end
1593
+ end
1594
+ return {:avail_space => avail_space, :used_space => used_space}
1595
+ end
1596
+
1597
+ def getvolumes
1598
+ return getmountedvolumes.merge(getservedvolumes)
1599
+ end
1600
+
1601
+ # This is based on the perl version in OSInfo.pm
1602
+ def getservedvolumes
1603
+ # only support Linux for now
1604
+ return {} unless Facter['kernel'] && Facter['kernel'].value == 'Linux'
1605
+
1606
+ # Don't do anything if exports file is not there
1607
+ return {} if !File.exists?("/etc/exports")
1608
+
1609
+ served = {}
1610
+
1611
+ IO.foreach("/etc/exports") do |line|
1612
+ if line =~ /(\S+)\s+/
1613
+ vol = $1
1614
+ served["volumes[served][#{vol}][config]"] = "/etc/exports"
1615
+ served["volumes[served][#{vol}][type]"] = 'nfs'
1616
+ end
1617
+ end
1618
+ return served
1619
+ end
1620
+
1621
+ # This is based on the perl version in OSInfo.pm
1622
+ def getmountedvolumes
1623
+ # only support Linux for now
1624
+ return {} unless Facter['kernel'] && Facter['kernel'].value == 'Linux'
1625
+
1626
+ dir = "/etc"
1627
+ mounted = {}
1628
+
1629
+ # AUTOFS - gather only files named auto[._]*
1630
+ Dir.glob(File.join(dir, "*")).each do |file|
1631
+ next if file !~ /^auto[._].*/
1632
+
1633
+ # AUTOFS - match only lines that look like nfs syntax such as host:/path
1634
+ IO.foreach(file) do |line|
1635
+ if line =~ /\w:\S/ && line !~ /^\s*#/
1636
+ # Parse it, Example : " nventory_backup -noatime,intr irvnetappbk:/vol/nventory_backup "
1637
+ if line =~ /^(\w[\w\S]+)\s+\S+\s+(\w[\w\S]+):(\S+)/
1638
+ mnt = $1
1639
+ host = $2
1640
+ vol = $3
1641
+ mounted["volumes[mounted][/mnt/#{mnt}][config]"] = file
1642
+ mounted["volumes[mounted][/mnt/#{mnt}][volume_server]"] = host
1643
+ mounted["volumes[mounted][/mnt/#{mnt}][volume]"] = vol
1644
+ mounted["volumes[mounted][/mnt/#{mnt}][type]"] = 'nfs'
1645
+ end
1646
+ end
1647
+ end # IO.foreach
1648
+ end # Dir.glob
1649
+
1650
+ # FSTAB - has diff syntax than AUTOFS. Example: "server:/usr/local/pub /pub nfs rsize=8192,wsize=8192,timeo=14,intr"
1651
+ IO.foreach("/etc/fstab") do |line|
1652
+ if line =~ /^(\w[\w\S]+):(\S+)\s+(\S+)\s+nfs/
1653
+ host = $1
1654
+ vol = $2
1655
+ mnt = $3
1656
+ mounted["volumes[mounted][#{mnt}][config]"] = "/etc/fstab"
1657
+ mounted["volumes[mounted][#{mnt}][volume_server]"] = host
1658
+ mounted["volumes[mounted][#{mnt}][volume]"] = vol
1659
+ mounted["volumes[mounted][#{mnt}][type]"] = 'nfs'
1660
+ end
1661
+ end # IO.foreach
1662
+ return mounted
1663
+ end
1664
+
1665
+ def getvmstatus
1666
+ # facter virtual makes calls to commands that are under /sbin
1667
+ ENV['PATH'] = "#{ENV['PATH']}:/sbin"
1668
+ vmstatus = `facter virtual`
1669
+ vmstatus.chomp!
1670
+
1671
+ # extra check to see if we're running kvm hypervisor
1672
+ os = Facter['kernel'].value
1673
+ if os == 'Linux'
1674
+ begin
1675
+ `grep ^kvm /proc/modules`
1676
+ vmstatus = "kvm_host" if $? == 0
1677
+ rescue
1678
+ warn "Failed to get modules information"
1679
+ end
1680
+ end
1681
+ return vmstatus
1682
+ end
1683
+
1684
+ # This is based on the perl version in HardwareInfo.pm
1685
+ def get_cpu_core_count
1686
+ # only support Linux for now
1687
+ os = Facter['kernel'].value
1688
+ physicalid = nil
1689
+ coreid = nil
1690
+ corecount = nil
1691
+ cores = {}
1692
+ if os == 'Linux'
1693
+ IO.foreach("/proc/cpuinfo") do |line|
1694
+ if line =~ /^processor\s*: (\d+)/
1695
+ physicalid = nil
1696
+ coreid = nil
1697
+ elsif line =~ /^physical id\s*: (\d+)/
1698
+ physicalid = $1
1699
+ elsif line =~ /^core id\s*: (\d+)/
1700
+ coreid = $1;
1701
+ end
1702
+ if physicalid && coreid
1703
+ cores["#{physicalid}:#{coreid}"] = 1;
1704
+ end
1705
+ end # IO.foreach
1706
+ corecount = cores.size
1707
+ end # if statement
1708
+ return corecount
1709
+ end
1710
+
1711
+ def get_console_type
1712
+ console_type = nil
1713
+ # only support Linux for now
1714
+ os = Facter['kernel'].value
1715
+ if os == 'Linux'
1716
+ if get_racadm
1717
+ console_type = "Dell DRAC"
1718
+ end
1719
+ end
1720
+ return console_type
1721
+ end
1722
+
1723
+ def get_drac_info
1724
+ info = {}
1725
+ result = nil
1726
+ racadm = get_racadm
1727
+ begin
1728
+ timeout(10) do
1729
+ cmd = "#{racadm} getsysinfo"
1730
+ result = `#{cmd}` || ""
1731
+ end
1732
+ result.split("\n").each do |line|
1733
+ if line =~ /^Current IP Address\s*=/i
1734
+ info[:ip_address] = line.split("=")[1].strip
1735
+ elsif line =~ /^MAC Address\s*=/i
1736
+ info[:mac_address] = line.split("=")[1].strip
1737
+ elsif line =~ /^DNS RAC Name\s*=/i
1738
+ info[:name] = line.split("=")[1].strip
1739
+ end
1740
+ end
1741
+ rescue Timeout::Error
1742
+ warn "Timed out when trying to get drac info"
1743
+ rescue Exception => e
1744
+ warn e.inspect
1745
+ warn "Failed to get DRAC IP"
1746
+ end
1747
+ return info
1748
+ end
1749
+
1750
+ def get_racadm
1751
+ path_env = (ENV['PATH'] || "").split(':')
1752
+ other_paths = ["/usr/sbin", "/opt/dell/srvadmin/sbin"]
1753
+ (path_env | other_paths).each do |path|
1754
+ if File.executable?(File.join(path, 'racadm'))
1755
+ return File.join(path, 'racadm')
1756
+ end
1757
+ end
1758
+ return nil
1759
+ end
1760
+
1761
+ def get_chassis_info
1762
+ chassis_info = {}
1763
+ manufacturer = nil
1764
+ # Only support Dell hardware for now
1765
+ if Facter['manufacturer'] && Facter['manufacturer'].value
1766
+ manufacturer = Facter['manufacturer'].value
1767
+ if manufacturer =~ /Dell/
1768
+ chassis_info = get_dell_chassis_info
1769
+ end
1770
+ end
1771
+ return chassis_info
1772
+ end
1773
+
1774
+ # call Dell's omreport command to get service tag
1775
+ # of the chassis, and the slot value of where the
1776
+ # given node resides in. Result is stored in hash with
1777
+ # service_tag and slot_num as the keys
1778
+ def get_dell_chassis_info
1779
+ ENV['PATH'] = "#{ENV['PATH']}:/opt/dell/srvadmin/bin/"
1780
+ chassis = {}
1781
+ result = nil
1782
+ begin
1783
+ #result = `omreport modularenclosure -fmt ssv`
1784
+ #result.split("\n").each do |line|
1785
+ # if line =~ /Service Tag/
1786
+ # chassis[:service_tag] = line.split(";")[1].strip
1787
+ # break
1788
+ # end
1789
+ #end
1790
+ timeout(5) do
1791
+ result = `omreport chassis info -fmt ssv`
1792
+ end
1793
+ result.split("\n").each do |line|
1794
+ if line =~ /Server Module Location;Slot (\d+)/
1795
+ chassis[:slot_num] = $1.to_i
1796
+ elsif line =~ /Chassis Service Tag/
1797
+ chassis[:service_tag] = line.split(";")[1].strip
1798
+ end
1799
+ end
1800
+ # if no slot_number then the blade isn't really in a chassis/blade enclosure
1801
+ # such as the case with Dell PowerEdge 1950
1802
+ return {} if chassis[:slot_num].nil?
1803
+ rescue Timeout::Error
1804
+ warn "Timed out when trying to run omreport"
1805
+ rescue
1806
+ warn "Failed to run/parse Dell's omreport command"
1807
+ end
1808
+ return chassis
1809
+ end
1810
+
1811
+ # Currently, the only info this method gathers is the info
1812
+ # of the guests running on this kvm host
1813
+ def get_kvm_hostinfo
1814
+ guests = {}
1815
+ begin
1816
+ result = `virsh list --all`
1817
+ result.split("\n").each do |line|
1818
+ if line =~ /(\d+)\s+(\S+)\s+(\S+)/
1819
+ guest_hostname = $2
1820
+ guests[guest_hostname] = get_kvm_guest_info(guest_hostname)
1821
+ end
1822
+ end
1823
+ rescue
1824
+ warn "Failed to run/parse virsh command"
1825
+ end
1826
+ return guests
1827
+ end
1828
+
1829
+ # Currently, the only info this method gathers is the
1830
+ # image size
1831
+ def get_kvm_guest_info(guest)
1832
+ info = {}
1833
+ result = `virsh dumpxml #{guest}`
1834
+ result.split("\n").each do |line|
1835
+ if line =~ /source file='(.+)'/
1836
+ img_path = $1
1837
+ if File.exists?(img_path)
1838
+ # nVentory expects the value to be in KB
1839
+ info['vmimg_size'] = File.stat(img_path).size.to_i / 1024
1840
+ # how to calculate this?
1841
+ # info['vmspace_used'] = ???
1842
+ end
1843
+ end
1844
+ end
1845
+ return info.clone
1846
+ end
1847
+
1848
+ # Most of the code in this method are based on the code of the
1849
+ # perl nVentory client
1850
+ def get_nic_info
1851
+ info = {}
1852
+ os = Facter['kernel'].value
1853
+ # only support Linux right now
1854
+ return info if os != 'Linux'
1855
+
1856
+ nic = nil
1857
+ result = `/sbin/ifconfig -a`
1858
+ result.split("\n").each do |line|
1859
+ if line =~ /^(\w+\S+)/
1860
+ nic = $1
1861
+ info[nic] = {}
1862
+ end
1863
+ if line =~ /(?:HWaddr|ether) ([\da-fA-F:]+)/
1864
+ info[nic][:hardware_address] = $1
1865
+ if line =~ /ether/i
1866
+ info[nic][:interface_type] = 'Ethernet'
1867
+ end
1868
+ elsif line =~ /^\s+UP / || line =~ /flags=.*UP,/
1869
+ info[nic][:up] = 1
1870
+ end
1871
+ end
1872
+
1873
+ # Get additional info
1874
+ info.each do |nic, nic_info|
1875
+ next if nic_info[:interface_type] != 'Ethernet'
1876
+ next if nic =~ /virbr|veth|vif|peth/
1877
+ result = `/sbin/ethtool #{nic}`
1878
+ result.split("\n").each do |line|
1879
+ if line =~ /Speed: (\d+)Mb/
1880
+ nic_info[:speed] = $1
1881
+ elsif line =~ /Duplex: (\w+)/
1882
+ ($1.downcase == 'full')? nic_info[:full_duplex] = 1 : nic_info[:full_duplex] = 0
1883
+ elsif line =~ /Advertised auto-negotiation: (.*)/
1884
+ ($1.downcase == 'yes')? nic_info[:autonegotiate] = 1 : nic_info[:autonegotiate] = 0
1885
+ elsif line =~ /Link detected: (\w+)/
1886
+ ($1.downcase == 'yes')? nic_info[:link] = 1: nic_info[:link] = 0
1887
+ end
1888
+ end
1889
+ end
1890
+ return info
1891
+ end
1892
+ end