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.
Files changed (40) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +14 -0
  4. data/Gemfile.lock +33 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +267 -0
  7. data/README.rdoc +18 -0
  8. data/Rakefile +49 -0
  9. data/VERSION +1 -0
  10. data/lib/rubyipmi.rb +80 -0
  11. data/lib/rubyipmi/commands/basecommand.rb +171 -0
  12. data/lib/rubyipmi/freeipmi/commands/basecommand.rb +60 -0
  13. data/lib/rubyipmi/freeipmi/commands/bmc.rb +37 -0
  14. data/lib/rubyipmi/freeipmi/commands/bmcconfig.rb +57 -0
  15. data/lib/rubyipmi/freeipmi/commands/bmcinfo.rb +76 -0
  16. data/lib/rubyipmi/freeipmi/commands/chassis.rb +123 -0
  17. data/lib/rubyipmi/freeipmi/commands/chassisconfig.rb +100 -0
  18. data/lib/rubyipmi/freeipmi/commands/lan.rb +111 -0
  19. data/lib/rubyipmi/freeipmi/commands/power.rb +72 -0
  20. data/lib/rubyipmi/freeipmi/connection.rb +43 -0
  21. data/lib/rubyipmi/freeipmi/errorcodes.rb +15 -0
  22. data/lib/rubyipmi/ipmitool/commands/basecommand.rb +60 -0
  23. data/lib/rubyipmi/ipmitool/commands/bmc.rb +95 -0
  24. data/lib/rubyipmi/ipmitool/commands/chassis.rb +106 -0
  25. data/lib/rubyipmi/ipmitool/commands/chassisconfig.rb +52 -0
  26. data/lib/rubyipmi/ipmitool/commands/lan.rb +162 -0
  27. data/lib/rubyipmi/ipmitool/commands/power.rb +74 -0
  28. data/lib/rubyipmi/ipmitool/connection.rb +46 -0
  29. data/lib/rubyipmi/ipmitool/errorcodes.rb +18 -0
  30. data/lib/rubyipmi/observablehash.rb +20 -0
  31. data/rubyipmi.gemspec +91 -0
  32. data/spec/bmc_spec.rb +35 -0
  33. data/spec/chassis_config_spec.rb +38 -0
  34. data/spec/chassis_spec.rb +24 -0
  35. data/spec/connection_spec.rb +31 -0
  36. data/spec/lan_spec.rb +61 -0
  37. data/spec/power_spec.rb +40 -0
  38. data/spec/rubyipmi_spec.rb +59 -0
  39. data/spec/spec_helper.rb +12 -0
  40. 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