rubyipmi 0.3.0
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.
- 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
|