rubyipmi 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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