rubyipmi 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +20 -0
- data/README.md +267 -0
- data/README.rdoc +18 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/rubyipmi.rb +80 -0
- data/lib/rubyipmi/commands/basecommand.rb +171 -0
- data/lib/rubyipmi/freeipmi/commands/basecommand.rb +60 -0
- data/lib/rubyipmi/freeipmi/commands/bmc.rb +37 -0
- data/lib/rubyipmi/freeipmi/commands/bmcconfig.rb +57 -0
- data/lib/rubyipmi/freeipmi/commands/bmcinfo.rb +76 -0
- data/lib/rubyipmi/freeipmi/commands/chassis.rb +123 -0
- data/lib/rubyipmi/freeipmi/commands/chassisconfig.rb +100 -0
- data/lib/rubyipmi/freeipmi/commands/lan.rb +111 -0
- data/lib/rubyipmi/freeipmi/commands/power.rb +72 -0
- data/lib/rubyipmi/freeipmi/connection.rb +43 -0
- data/lib/rubyipmi/freeipmi/errorcodes.rb +15 -0
- data/lib/rubyipmi/ipmitool/commands/basecommand.rb +60 -0
- data/lib/rubyipmi/ipmitool/commands/bmc.rb +95 -0
- data/lib/rubyipmi/ipmitool/commands/chassis.rb +106 -0
- data/lib/rubyipmi/ipmitool/commands/chassisconfig.rb +52 -0
- data/lib/rubyipmi/ipmitool/commands/lan.rb +162 -0
- data/lib/rubyipmi/ipmitool/commands/power.rb +74 -0
- data/lib/rubyipmi/ipmitool/connection.rb +46 -0
- data/lib/rubyipmi/ipmitool/errorcodes.rb +18 -0
- data/lib/rubyipmi/observablehash.rb +20 -0
- data/rubyipmi.gemspec +91 -0
- data/spec/bmc_spec.rb +35 -0
- data/spec/chassis_config_spec.rb +38 -0
- data/spec/chassis_spec.rb +24 -0
- data/spec/connection_spec.rb +31 -0
- data/spec/lan_spec.rb +61 -0
- data/spec/power_spec.rb +40 -0
- data/spec/rubyipmi_spec.rb +59 -0
- data/spec/spec_helper.rb +12 -0
- metadata +181 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
require "observer"
|
2
|
+
|
3
|
+
module Rubyipmi
|
4
|
+
|
5
|
+
class BaseCommand
|
6
|
+
include Observable
|
7
|
+
|
8
|
+
attr_reader :cmd
|
9
|
+
attr_accessor :options
|
10
|
+
attr_reader :lastcall
|
11
|
+
|
12
|
+
def makecommand
|
13
|
+
# override in subclass
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
makecommand
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(commandname, opts = ObservableHash.new)
|
21
|
+
# This will locate the command path or raise an error if not found
|
22
|
+
@cmdname = commandname
|
23
|
+
@cmd = `which #{commandname}`.strip
|
24
|
+
if not $?.success?
|
25
|
+
raise "#{commandname} command not found, is #{commandname} installed?"
|
26
|
+
end
|
27
|
+
@options = opts
|
28
|
+
@options.add_observer(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Use this function to run the command line tool, it will inherently use the options hash for all options
|
32
|
+
# That need to be specified on the command line
|
33
|
+
def runcmd(debug=false)
|
34
|
+
@success = false
|
35
|
+
|
36
|
+
result = catch(:ipmierror){
|
37
|
+
@success = run(debug)
|
38
|
+
}
|
39
|
+
# if error occurs try and find the fix
|
40
|
+
if result and not @success
|
41
|
+
findfix(result, nil, debug, "runcmd")
|
42
|
+
end
|
43
|
+
return @success
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def runcmd_without_opts(args=[], debug=false)
|
50
|
+
|
51
|
+
@success = false
|
52
|
+
result = catch(:ipmierror){
|
53
|
+
@success = run_without_opts(args, debug)
|
54
|
+
|
55
|
+
}
|
56
|
+
# if error occurs try and find the fix and rerun the method
|
57
|
+
if result and not @success
|
58
|
+
findfix(result, args, debug, "runcmd_with_args")
|
59
|
+
end
|
60
|
+
return @success
|
61
|
+
end
|
62
|
+
|
63
|
+
# The findfix method acts like a recursive method and applies fixes defined in the errorcodes
|
64
|
+
# If a fix is found it is applied to the options hash, and then the last run command is retried
|
65
|
+
# until all the fixes are exhausted or a error not defined in the errorcodes is found
|
66
|
+
def findfix(result, args, debug, runmethod)
|
67
|
+
if result
|
68
|
+
# The errorcode code hash contains the fix
|
69
|
+
fix = ErrorCodes.code[result]
|
70
|
+
if not fix
|
71
|
+
raise "Ipmi Fix not found, email author with error: #{result}"
|
72
|
+
else
|
73
|
+
@options.merge_notify!(fix)
|
74
|
+
# retry the last called method
|
75
|
+
# its quicker to explicitly call these commands than calling a command block
|
76
|
+
if runmethod == "run"
|
77
|
+
run(debug)
|
78
|
+
else
|
79
|
+
run_without_opts(args, debug)
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def run(debug=false)
|
89
|
+
@result = nil
|
90
|
+
command = makecommand
|
91
|
+
if debug
|
92
|
+
return command
|
93
|
+
end
|
94
|
+
|
95
|
+
@lastcall = "#{command}"
|
96
|
+
@result = `#{command} 2>&1`
|
97
|
+
#puts "Last Call: #{@lastcall}"
|
98
|
+
|
99
|
+
# sometimes the command tool doesnt return the correct result
|
100
|
+
process_status = validate_status($?)
|
101
|
+
|
102
|
+
if not process_status
|
103
|
+
throwError
|
104
|
+
end
|
105
|
+
return process_status
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
def run_without_opts(args=[], debug=false)
|
111
|
+
@result = ""
|
112
|
+
if debug
|
113
|
+
return "#{cmd} #{args.join(" ")}"
|
114
|
+
else
|
115
|
+
@lastcall = "#{cmd} #{args.join(" ")}"
|
116
|
+
@result = `#{cmd} #{args.join(" ")} 2>&1`
|
117
|
+
end
|
118
|
+
|
119
|
+
process_status = validate_status($?)
|
120
|
+
|
121
|
+
if not process_status
|
122
|
+
throwError
|
123
|
+
end
|
124
|
+
return process_status
|
125
|
+
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
def update(opts)
|
130
|
+
@options.merge!(opts)
|
131
|
+
#puts "Options were updated: #{@options.inspect}"
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
# This method will check if the results are really valid as the exit code can be misleading and incorrect
|
138
|
+
def validate_status(exitstatus)
|
139
|
+
# override in child class if needed
|
140
|
+
return exitstatus.success?
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
def throwError
|
145
|
+
# Find out what kind of error is happening, parse results
|
146
|
+
# Check for authentication or connection issue
|
147
|
+
#puts "ipmi call: #{@lastcall}"
|
148
|
+
|
149
|
+
if @result =~ /timeout|timed\ out/
|
150
|
+
code = "ipmi call: #{@lastcall} timed out"
|
151
|
+
raise code
|
152
|
+
else
|
153
|
+
code = @result.split(":").last.chomp.strip if not @result.empty?
|
154
|
+
end
|
155
|
+
case code
|
156
|
+
when "invalid hostname"
|
157
|
+
raise code
|
158
|
+
when "password invalid"
|
159
|
+
raise code
|
160
|
+
when "username invalid"
|
161
|
+
raise code
|
162
|
+
else
|
163
|
+
throw :ipmierror, code
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Rubyipmi::Freeipmi
|
2
|
+
|
3
|
+
class BaseCommand < Rubyipmi::BaseCommand
|
4
|
+
|
5
|
+
def makecommand
|
6
|
+
# need to format the options to freeipmi format
|
7
|
+
args = @options.collect { |k, v|
|
8
|
+
if not v
|
9
|
+
"--#{k}"
|
10
|
+
else
|
11
|
+
"--#{k}=#{v}"
|
12
|
+
end
|
13
|
+
}.join(" ")
|
14
|
+
|
15
|
+
return "#{cmd} #{args}"
|
16
|
+
end
|
17
|
+
|
18
|
+
# This method will check if the results are really valid as the exit code can be misleading and incorrect
|
19
|
+
def validate_status(exitstatus)
|
20
|
+
case @cmdname
|
21
|
+
when "ipmipower"
|
22
|
+
# until ipmipower returns the correct exit status this is a hack
|
23
|
+
# essentially any result greater than 23 characters is an error
|
24
|
+
if @result.length > 23
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
when "bmc-config"
|
28
|
+
if @result.length > 100
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
return exitstatus.success?
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
# The findfix method acts like a recursive method and applies fixes defined in the errorcodes
|
37
|
+
# If a fix is found it is applied to the options hash, and then the last run command is retried
|
38
|
+
# until all the fixes are exhausted or a error not defined in the errorcodes is found
|
39
|
+
def findfix(result, args, debug, runmethod)
|
40
|
+
if result
|
41
|
+
# The errorcode code hash contains the fix
|
42
|
+
fix = Rubyipmi::Freeipmi::ErrorCodes.code[result]
|
43
|
+
if not fix
|
44
|
+
raise "Ipmi Fix not found, email author with error: #{result}"
|
45
|
+
else
|
46
|
+
@options.merge_notify!(fix)
|
47
|
+
# retry the last called method
|
48
|
+
# its quicker to explicitly call these commands than calling a command block
|
49
|
+
if runmethod == "runcmd"
|
50
|
+
runcmd(debug)
|
51
|
+
else
|
52
|
+
runcmd_without_opts(args, debug)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Rubyipmi::Freeipmi
|
2
|
+
|
3
|
+
class Bmc
|
4
|
+
|
5
|
+
attr_accessor :options
|
6
|
+
attr_accessor :config
|
7
|
+
|
8
|
+
def initialize(opts = ObservableHash.new)
|
9
|
+
@options = opts
|
10
|
+
@bmcinfo = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def info
|
14
|
+
if @bmcinfo.length > 0
|
15
|
+
@bmcinfo
|
16
|
+
else
|
17
|
+
information.retrieve
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def guid
|
22
|
+
information.guid
|
23
|
+
end
|
24
|
+
|
25
|
+
def config
|
26
|
+
@config ||= Rubyipmi::Freeipmi::BmcConfig.new(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def lan
|
30
|
+
@lan ||= Rubyipmi::Freeipmi::Lan.new(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def information
|
34
|
+
@info ||= Rubyipmi::Freeipmi::BmcInfo.new(@options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Rubyipmi::Freeipmi
|
2
|
+
|
3
|
+
class BmcConfig < Rubyipmi::Freeipmi::BaseCommand
|
4
|
+
|
5
|
+
def initialize(opts = ObservableHash.new)
|
6
|
+
super("bmc-config", opts)
|
7
|
+
@sections = []
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
def section(section)
|
12
|
+
@options["checkout"] = false
|
13
|
+
@options["section"] = section
|
14
|
+
value = runcmd
|
15
|
+
@options.delete_notify("checkout")
|
16
|
+
@options.delete_notify("section")
|
17
|
+
@result
|
18
|
+
end
|
19
|
+
|
20
|
+
def setsection(section, key, value)
|
21
|
+
keypair = "#{section}:#{key}=#{value}"
|
22
|
+
@options["commit"] = false
|
23
|
+
if not keypair.empty?
|
24
|
+
@options["key-pair"] = keypair
|
25
|
+
value = runcmd
|
26
|
+
@options.delete_notify("commit")
|
27
|
+
@options.delete_notify("key-pair")
|
28
|
+
return value
|
29
|
+
end
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# returns the entire bmc-config configuration, can take a while to execute
|
35
|
+
def configuration
|
36
|
+
@options["checkout"] = false
|
37
|
+
value = runcmd
|
38
|
+
@options.delete_notify("checkout")
|
39
|
+
@result
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a list of available sections to inspect
|
43
|
+
def listsections
|
44
|
+
if @sections.length < 1
|
45
|
+
@options["listsections"] = false
|
46
|
+
value = runcmd
|
47
|
+
@options.delete_notify("listsections")
|
48
|
+
if value
|
49
|
+
@sections = @result.split(/\n/)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
@sections
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Rubyipmi::Freeipmi
|
2
|
+
|
3
|
+
class BmcInfo < Rubyipmi::Freeipmi::BaseCommand
|
4
|
+
|
5
|
+
def initialize(opts = ObservableHash.new)
|
6
|
+
super("bmc-info", opts)
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def guid
|
12
|
+
@options["guid"] = false
|
13
|
+
status = runcmd
|
14
|
+
@options.delete_notify["guid"]
|
15
|
+
if not status
|
16
|
+
raise @result
|
17
|
+
else
|
18
|
+
@result.chomp.strip
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
# freeipmi
|
23
|
+
# Device ID: 17
|
24
|
+
# Device Revision: 1
|
25
|
+
# [SDR Support]
|
26
|
+
# Firmware Revision: 2.09
|
27
|
+
# [Device Available (normal operation)]
|
28
|
+
# IPMI Version: 2.0
|
29
|
+
# Additional Device Support:
|
30
|
+
# [Sensor Device]
|
31
|
+
# [SDR Repository Device]
|
32
|
+
# [SEL Device]
|
33
|
+
# [FRU Inventory Device]
|
34
|
+
# Manufacturer ID: 11
|
35
|
+
# Product ID: 8192
|
36
|
+
# Channel Information:
|
37
|
+
# Channel No: 2
|
38
|
+
# Medium Type: 802.3 LAN
|
39
|
+
# Protocol Type: IPMB-1.0
|
40
|
+
# Channel No: 7
|
41
|
+
# Medium Type: OEM
|
42
|
+
# Protocol Type: KCS
|
43
|
+
def retrieve
|
44
|
+
bmcinfo = {}
|
45
|
+
status = runcmd
|
46
|
+
subkey = nil
|
47
|
+
if not status
|
48
|
+
raise @result
|
49
|
+
else
|
50
|
+
@result.lines.each do |line|
|
51
|
+
# clean up the data from spaces
|
52
|
+
item = line.split(':')
|
53
|
+
key = item.first.strip
|
54
|
+
value = item.last.strip
|
55
|
+
# if the following condition is met we have subvalues
|
56
|
+
if key == value and not subkey
|
57
|
+
subkey = key
|
58
|
+
bmcinfo[subkey] = []
|
59
|
+
elsif key == value and subkey
|
60
|
+
# subvalue found
|
61
|
+
bmcinfo[subkey] << value.gsub(/\[|\]/, "")
|
62
|
+
else
|
63
|
+
# Normal key/value pair with no subkeys
|
64
|
+
subkey = nil
|
65
|
+
bmcinfo[key] = value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
return bmcinfo
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Rubyipmi::Freeipmi
|
2
|
+
|
3
|
+
class Chassis < Rubyipmi::Freeipmi::BaseCommand
|
4
|
+
|
5
|
+
def initialize(opts = ObservableHash.new)
|
6
|
+
super("ipmi-chassis", opts)
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
# Turn the led light on / off or with a delay
|
11
|
+
def identify(status=false, delay=0)
|
12
|
+
if status
|
13
|
+
if delay <= 0
|
14
|
+
options["chassis-identify"] = "FORCE"
|
15
|
+
else
|
16
|
+
options["chassis-identify"] = delay
|
17
|
+
end
|
18
|
+
else
|
19
|
+
options["chassis-identify"] = "TURN-OFF"
|
20
|
+
end
|
21
|
+
# Run the command
|
22
|
+
value = runcmd
|
23
|
+
options.delete_notify("chassis-identify")
|
24
|
+
return value
|
25
|
+
end
|
26
|
+
|
27
|
+
# Access to the power command created on the fly
|
28
|
+
def power
|
29
|
+
@power ||= Rubyipmi::Freeipmi::Power.new(@options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Access to the config command created on the fly
|
33
|
+
def config
|
34
|
+
@config ||= Rubyipmi::Freeipmi::ChassisConfig.new(@options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# set boot device from given boot device
|
38
|
+
def bootdevice(device, reboot=false, persistent=false)
|
39
|
+
if config.bootdevices.include?(device)
|
40
|
+
bootstatus = config.bootdevice(device, persistent)
|
41
|
+
if reboot and bootstatus
|
42
|
+
power.cycle
|
43
|
+
end
|
44
|
+
|
45
|
+
else
|
46
|
+
raise "Device with name: #{device} is not a valid boot device for host #{options["hostname"]}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# set boot device to pxe with option to reboot
|
51
|
+
def bootpxe(reboot=false,persistent=false)
|
52
|
+
bootstatus = config.bootpxe(persistent)
|
53
|
+
# Only reboot if setting the boot flag was successful
|
54
|
+
if reboot and bootstatus
|
55
|
+
power.cycle
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# set boot device to disk with option to reboot
|
60
|
+
def bootdisk(reboot=false,persistent=false)
|
61
|
+
bootstatus = config.bootdisk(persistent)
|
62
|
+
# Only reboot if setting the boot flag was successful
|
63
|
+
if reboot and bootstatus
|
64
|
+
power.cycle
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
# set boot device to cdrom with option to reboot
|
70
|
+
def bootcdrom(reboot=false,persistent=false)
|
71
|
+
bootstatus = config.bootcdrom(persistent)
|
72
|
+
# Only reboot if setting the boot flag was successful
|
73
|
+
if reboot and bootstatus
|
74
|
+
power.cycle
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# boot into bios setup with option to reboot
|
79
|
+
def bootbios(reboot=false,persistent=false)
|
80
|
+
bootstatus = config.bootbios(persistent)
|
81
|
+
# Only reboot if setting the boot flag was successful
|
82
|
+
if reboot and bootstatus
|
83
|
+
power.cycle
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def status
|
88
|
+
options["get-status"] = false
|
89
|
+
value = runcmd
|
90
|
+
options.delete_notify("get-status")
|
91
|
+
if value
|
92
|
+
return parsestatus
|
93
|
+
else
|
94
|
+
return value
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
# A currently unsupported method to retrieve the led status
|
100
|
+
def identifystatus
|
101
|
+
# TODO implement this function
|
102
|
+
# parse out the identify status
|
103
|
+
# status.result
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
def parsestatus
|
108
|
+
statusresult = @result
|
109
|
+
statusvalues = {}
|
110
|
+
subkey = nil
|
111
|
+
statusresult.lines.each do |line|
|
112
|
+
# clean up the data from spaces
|
113
|
+
item = line.split(':')
|
114
|
+
key = item.first.strip
|
115
|
+
value = item.last.strip
|
116
|
+
statusvalues[key] = value
|
117
|
+
end
|
118
|
+
return statusvalues
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|