elesai 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright (c) 2012 by Evernote Corporation, All rights reserved.
3
+ *
4
+ * Use of the source code and binary libraries included in this package
5
+ * is permitted under the following terms:
6
+ *
7
+ * Redistribution and use in source and binary forms, with or without
8
+ * modification, are permitted provided that the following conditions
9
+ * are met:
10
+ *
11
+ * 1. Redistributions of source code must retain the above copyright
12
+ * notice, this list of conditions and the following disclaimer.
13
+ * 2. Redistributions in binary form must reproduce the above copyright
14
+ * notice, this list of conditions and the following disclaimer in the
15
+ * documentation and/or other materials provided with the distribution.
16
+ *
17
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
+ *
28
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
29
+ * not use this file except in compliance with the License. You may obtain a
30
+ * copy of the License at http://www.apache.org/licenses/LICENSE-2.0
31
+ */
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # OVERVIEW
2
+
3
+ *Elesai* is a wrapper around LSI's `MegaCli` utility that provides access to common types of information about the RAID controllers via a `show` action without the need to speak martian. It is a line-oriented tool so that it can be combined with other Unix command-line tools to process and manipulate the data (i.e., `sed`, `awk`, and friends). It also provides a `check` action (currently as a Nagios plugin in both active and passive modes) which monitors the health of the array and its components and reports it accordingly (this is not yet configurable).
4
+
5
+ # SYNOPSIS
6
+
7
+ elesai [global-options] <action> [action-options] [<component>]
8
+
9
+ where:
10
+
11
+ * `<action>` is one of `show` or `check`
12
+ * `<component>` is one of `virtualdrive` (or `vd`) or `physicaldrive` (or `pd`) (for `show` action)
13
+
14
+ Global options include:
15
+
16
+ * `-d`, `--debug`: enable *debug* mode
17
+ * `-f`, `--fake DIRECTORY`: specifies path to directory containing output of MegaCli invocations:
18
+ * `ldlist_aall`: output from `MegaCli -pdlist -aall`
19
+ * `ldpdinfo_aall`: output from `MegaCli -ldpdinfo -aall`
20
+ * `-m`, `--megacli MEGACLI`: path to `MegaCli` binary (if noth in `$PATH`)
21
+ * `-a`, `--about`: display general information about `elesai`
22
+ * `-V`, `--version`: display `elesai`'s version
23
+
24
+ The `<check>` can have options specific to itself:
25
+
26
+ * `-M`, `--monitor MONITOR`: Monitoring system (default: `nagios`)
27
+ * `-m`, `--mode [active|passive]`: Monitoring mode
28
+ * `-H`, `--nsca_hostname HOSTNAME`: NSCA hostname to send passive checks
29
+ * `-c`, `--config CONFIG`: Path to Senedsa (send_nsca) configuration
30
+ * `-S`, `--svc_descr SVC_DESCR`: Nagios service description
31
+
32
+ *Elesai* uses [Senedsa](https://rubygems.org/gems/senedsa "Senedsa") for Nagios passive check submission, which can use a configuration file to set options.
33
+
34
+ # Invocations
35
+
36
+ ## Normal Invocation
37
+
38
+ root@boxie:~# elesai show pd | sort -k 4
39
+ [PD] e253s3 27 online:spunup 1.82TB HDD SATA 0 0 9WM1B7VDST32000644NS SN11
40
+ [PD] e252s7 23 online:spunup 1.82TB HDD SATA 0 0 9WM1FX1LST32000644NS SN11
41
+ [PD] e253s0 24 online:spunup 1.82TB HDD SATA 0 0 9WM1GF2NST32000644NS SN11
42
+ [PD] e253s1 25 online:spunup 1.82TB HDD SATA 0 0 9WM1GY85ST32000644NS SN11
43
+ [PD] e252s6 22 online:spunup 1.82TB HDD SATA 0 0 9WM1GYKJST32000644NS SN11
44
+ [PD] e253s2 26 online:spunup 1.82TB HDD SATA 0 0 9WM1HA0NST32000644NS SN11
45
+ [PD] e253s5 29 online:spunup 1.82TB HDD SATA 0 0 9WM7L834ST32000644NS SN12
46
+ [PD] e252s4 12 online:spunup 223.06GB SSD SATA 0 0 CVPR138405AQ300EGN INTEL SSDSA2CW300G3 4PC10362
47
+ [PD] e252s2 9 online:spunup 223.06GB SSD SATA 0 0 CVPR140100MQ300EGN INTEL SSDSA2CW300G3 4PC10362
48
+ [PD] e252s0 11 online:spunup 223.06GB SSD SATA 0 0 CVPR140201KZ300EGN INTEL SSDSA2CW300G3 4PC10362
49
+ [PD] e252s1 10 online:spunup 223.06GB SSD SATA 0 0 CVPR141300K9300EGN INTEL SSDSA2CW300G3 4PC10362
50
+ [PD] e252s3 8 online:spunup 223.06GB SSD SATA 0 0 CVPR141301KG300EGN INTEL SSDSA2CW300G3 4PC10362
51
+ [PD] e253s4 30 failed:spunup 1.82TB HDD SATA 0 0 9WM5Y4AEST32000644NS SN12
52
+ [PD] e252s5 13 hotspare:spunup 223.06GB SSD SATA 0 0 CVPR140100TT300EGN INTEL SSDSA2CW300G3 4PC10362
53
+
54
+ ## Remote Invocation
55
+
56
+ Pipe output to `elesai`:
57
+
58
+ root@boxie:~# ssh server.example.com sudo MegaCli -pdlist -aall | elesai show pd
59
+ [PD] e252s0 16 online:spunup 278.46GB HDD SAS 0 0 a0 SEAGATE ST3300657SS 00066SJ01W4G
60
+ [PD] e252s1 17 online:spunup 278.46GB HDD SAS 0 0 a0 SEAGATE ST3300657SS 00066SJ01TTR
61
+ [PD] e252s2 20 online:spunup 278.46GB HDD SAS 0 0 a0 SEAGATE ST3300657SS 00066SJ02BCX
62
+ [PD] e252s3 21 online:spunup 278.46GB HDD SAS 0 0 a0 SEAGATE ST3300657SS 00066SJ025LP
63
+ [PD] e252s4 18 online:spunup 1.82TB HDD SATA 0 0 a0 9WM1G6MXST32000644NS SN11
64
+ [PD] e252s5 28 online:spunup 1.82TB HDD SATA 0 0 a0 9WM0FFYCST32000644NS SN11
65
+ [PD] e252s6 22 online:spunup 1.82TB HDD SATA 0 0 a0 9WM1GYKJST32000644NS SN11
66
+ [PD] e252s7 23 online:spunup 1.82TB HDD SATA 0 0 a0 9WM1FX1LST32000644NS SN11
67
+ [PD] e253s0 24 online:spunup 1.82TB HDD SATA 0 0 a0 9WM1GF2NST32000644NS SN11
68
+ [PD] e253s1 25 online:spunup 1.82TB HDD SATA 0 0 a0 9WM1GY85ST32000644NS SN11
69
+ [PD] e253s2 26 online:spunup 1.82TB HDD SATA 0 0 a0 9WM1HA0NST32000644NS SN11
70
+ [PD] e253s3 27 online:spunup 1.82TB HDD SATA 0 0 a0 9WM1B7VDST32000644NS SN11
71
+ [PD] e253s4 30 online:spunup 1.82TB HDD SATA 0 0 a0 9WM5Y4AEST32000644NS SN12
72
+ [PD] e253s5 29 online:spunup 1.82TB HDD SATA 0 0 a0 9WM7L834ST32000644NS SN12
73
+ [PD] e253s6 31 unconfigured(bad): 0.00KB HDD SAS 0 0 a0 SEAGATE ST33000650SS 0003Z29182VM
74
+ [PD] e253s7 32 unconfigured(bad): 0.00KB HDD SAS 0 0 a0 SEAGATE ST33000650SS 0003Z291A3QV
75
+
76
+ # STATUS
77
+
78
+ Very much in progress. The tool does not yet poke MegaCli itself.
79
+
80
+
81
+
data/bin/check_elesai ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'socket'
7
+ require 'rubygems'
8
+ require 'elesai'
9
+ require 'elesai/cli'
10
+
11
+ module Elesai
12
+
13
+ ID = File.basename($PROGRAM_NAME).to_sym
14
+
15
+ app = CLI.new(ARGV)
16
+ app.run
17
+
18
+ end
data/bin/elesai ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'socket'
7
+ require 'rubygems'
8
+ require 'elesai'
9
+ require 'elesai/cli'
10
+
11
+ module Elesai
12
+
13
+ ID = File.basename($PROGRAM_NAME).to_sym
14
+
15
+ app = CLI.new(ARGV)
16
+ app.run
17
+
18
+ end
@@ -0,0 +1,5 @@
1
+ require 'elesai/version'
2
+ module Elesai
3
+ ME = :elesai
4
+ ABOUT = "#{ME} v#{VERSION}\nhttps://github.com/evernote/ops-#{ME}\nCopyright (c) 2012 by Evernote Corporation\nLicensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)"
5
+ end
data/lib/elesai/cli.rb ADDED
@@ -0,0 +1,203 @@
1
+ require 'optparse'
2
+ require 'senedsa'
3
+ include Senedsa
4
+ require 'elesai/version'
5
+ require 'elesai/about'
6
+
7
+ module Elesai
8
+
9
+ class CLI
10
+
11
+ COMMANDS = %w(show check)
12
+ COMPONENTS = %w(virtualdrive vd physicaldrive pd)
13
+ DEFAULT_CONFIG_FILE = File.join(ENV['HOME'],"/.senedsa/config")
14
+
15
+ def initialize(arguments)
16
+ @arguments = arguments
17
+
18
+ @global_options = { :debug => false, :megacli => 'MegaCli' }
19
+ @action_options = { :monitor => 'nagios', :mode => 'active' }
20
+ @action = nil
21
+ end
22
+
23
+ def run
24
+ begin
25
+ parsed_options?
26
+
27
+ @log = Elesai::Logger.instance.log
28
+ @log.level = Log4r::INFO unless @global_options[:debug]
29
+
30
+ config_options?
31
+ arguments_valid?
32
+ options_valid?
33
+ process_options
34
+ process_arguments
35
+ process_command
36
+
37
+ rescue => e #ArgumentError, OptionParser::MissingArgument, Senedsa::SendNsca::ConfigurationError => e
38
+ output_message e.message, 1
39
+ end
40
+ end
41
+
42
+ protected
43
+
44
+ def parsed_options?
45
+ opts = OptionParser.new
46
+
47
+ opts.banner = "Usage: #{ID} [options] <action> [options]"
48
+ opts.separator ""
49
+ opts.separator "Actions:"
50
+ opts.separator " show Displays component information"
51
+ opts.separator " check Performs health checks"
52
+ opts.separator ""
53
+ opts.separator "General options:"
54
+ opts.on('-m', '--megacli MEGACLI', String, "Path to MegaCli binary") { |megacli| @global_options[:megacli] = megacli }
55
+ opts.on('-f', '--fake DIRECTORY', String, "Path to directory with Megacli output") { |dir| @global_options[:fake] = dir }
56
+ opts.on('-d', '--debug', "Enable debug mode") { @global_options[:debug] = true}
57
+ opts.on('-a', '--about', "Display #{ID} information") { output_message ABOUT, 0 }
58
+ opts.on('-V', '--version', "Display #{ID} version") { output_message VERSION, 0 }
59
+ opts.on_tail('--help', "Show this message") { @global_options[:HELP] = true }
60
+
61
+ actions = {
62
+ :show => OptionParser.new do |aopts|
63
+ aopts.banner = "Usage: #{ID} [options] show <component>"
64
+ aopts.separator ""
65
+ aopts.separator " <component> is physicaldisk|pd, virtualdisk|vd"
66
+ end,
67
+ :check => OptionParser.new do |aopts|
68
+ aopts.banner = "Usage: #{ID} [options] check [check_options]"
69
+ aopts.separator ""
70
+ aopts.separator "Check Options"
71
+ aopts.on('-M', '--monitor [nagios]', [:nagios], "Monitoring system") { |monitor| @action_options[:monitor] = monitor }
72
+ aopts.on('-m', '--mode [active|passive]', [:active, :passive], "Monitoring mode") { |mode| @action_options[:mode] = mode }
73
+ aopts.on('-H', '--nsca_hostname HOSTNAME', String, "NSCA hostname to send passive checks") { |nsca_hostname| @action_options[:nsca_hostame] = nsca_hostname }
74
+ aopts.on('-c', '--config CONFIG', String, "Path to Senedsa (send_nsca) configuration" ) { |config| @action_options[:senedsa_config] = config }
75
+ aopts.on('-S', '--svc_descr SVC_DESR', String, "Nagios service description") { |svc_descr| @action_options[:svc_descr] = svc_descr }
76
+ end
77
+ }
78
+
79
+ opts.order!
80
+ output_message opts, 0 if @arguments.size == 0 or @global_options[:HELP]
81
+
82
+ @action = ARGV.shift.to_sym
83
+ actions[@action].order!
84
+ end
85
+
86
+ def config_options?
87
+ cfg_file = nil
88
+ cfg_file = @action_options[:senedsa_config] unless @action_options[:senedsa_config].nil?
89
+ cfg_file = DEFAULT_CONFIG_FILE if @action_options[:senedsa_config].nil? and File.readable? DEFAULT_CONFIG_FILE
90
+
91
+ unless cfg_file.nil?
92
+ @action_options.merge!(Senedsa::SendNsca.configure(cfg_file))
93
+ @action_options[:senedsa_config] = cfg_file
94
+ end
95
+ end
96
+
97
+ def arguments_valid?
98
+ true
99
+ end
100
+
101
+ def options_valid?
102
+ case @action
103
+ when :check
104
+ raise OptionParser::MissingArgument, "NSCA hostname (-H) must be specified" if @action_options[:nsca_hostname].nil?
105
+ raise OptionParser::MissingArgument, "service description (-S) must be specified" if @action_options[:svc_descr].nil?
106
+ end
107
+ end
108
+
109
+ def process_options
110
+ true
111
+ end
112
+
113
+ def process_arguments
114
+ @action_options[:hint] = @arguments[0].nil? ? nil : @arguments[0].to_sym
115
+ true
116
+ end
117
+
118
+ def process_command
119
+
120
+ @lsi = LSIArray.new(:megacli => @global_options[:megacli], :fake => @global_options[:fake], :hint => @action_options[:hint])
121
+
122
+ case @action
123
+ when :show then run_show
124
+ when :check then run_check
125
+ end
126
+
127
+ end
128
+
129
+ def run_show
130
+
131
+ raise ArgumentError, "missing component" if @arguments.size == 0
132
+ component = @arguments[0]
133
+
134
+ case component
135
+ when 'virtualdrive', 'vd'
136
+ @lsi.virtualdrives.each do |virtualdrive|
137
+ print "#{virtualdrive}\n"
138
+ end
139
+ when 'physicaldrive', 'pd'
140
+ @lsi.physicaldrives.each do |id,physicaldrive|
141
+ print "#{physicaldrive}\n"
142
+ end
143
+ else
144
+ raise ArgumentError, "invalid component #{component}"
145
+ end
146
+ end
147
+
148
+ def run_check
149
+
150
+ plugin_output = ""
151
+ plugin_status = ""
152
+
153
+ @lsi.physicaldrives.each do |id,physicaldrive|
154
+ drive_plugin_string = "[PD:#{physicaldrive._id}:#{physicaldrive[:size]}:#{physicaldrive[:mediatype]}:#{physicaldrive[:pdtype]}]"
155
+ unless physicaldrive[:firmwarestate].state == :online or physicaldrive[:firmwarestate].state == :hotspare
156
+ plugin_output += " #{drive_plugin_string}:#{physicaldrive[:firmwarestate].state}"
157
+ plugin_status = :warning if plugin_status.empty?
158
+ end
159
+ unless physicaldrive[:mediaerrorcount].to_i == 0
160
+ plugin_output += " #{drive_plugin_string}:me:#{physicaldrive[:mediaerrorcount]}"
161
+ plugin_status = :warning if plugin_status.empty?
162
+ end
163
+ unless physicaldrive[:predictivefailurecount].to_i == 0
164
+ plugin_output += " #{drive_plugin_string}:pf:#{physicaldrive[:predictivefailurecount]}"
165
+ plugin_status = :warning if plugin_status.empty?
166
+ end
167
+ end
168
+
169
+ plugin_output = "no RAID subsystems errors found" if plugin_output.empty? and plugin_status.empty?
170
+ plugin_status = :ok if plugin_status.empty?
171
+
172
+ case @action_options[:monitor]
173
+ when 'nagios'
174
+ case @action_options[:mode]
175
+ when 'active'
176
+ puts "#{plugin_status}: #{plugin_output}"
177
+ exit SendNsca::STATUS[plugin_status]
178
+ when 'passive'
179
+ sn = SendNsca.new Socket.gethostname,'raid/lsi'
180
+ sn.nsca_hostname = @command_opts[:nsca_hostname]
181
+ begin
182
+ sn.send plugin_status , plugin_output
183
+ rescue SendNsca::SendNscaError => e
184
+ $stderr.write "#{ME}: error: send_nsca failed: #{e.message}\n"
185
+ exit
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ def output_message(message, exitstatus=nil)
192
+ m = (! exitstatus.nil? and exitstatus > 0) ? "%s: error: %s" % [ID, message] : message
193
+ $stderr.write "#{m}\n"
194
+ exit exitstatus unless exitstatus.nil?
195
+ end
196
+
197
+ end
198
+ end
199
+
200
+
201
+
202
+
203
+
@@ -0,0 +1,20 @@
1
+ require 'singleton'
2
+ require 'log4r'
3
+
4
+ module Elesai
5
+
6
+ class Logger
7
+
8
+ include Singleton
9
+
10
+ attr_reader :log
11
+
12
+ def initialize
13
+ @log = Log4r::Logger.new("elesai")
14
+ @log.add Log4r::StderrOutputter.new('console', :formatter => Log4r::PatternFormatter.new(:pattern => "%c [%l] %m"), :level => Log4r::DEBUG)
15
+ end
16
+
17
+
18
+
19
+ end
20
+ end
data/lib/elesai/lsi.rb ADDED
@@ -0,0 +1,210 @@
1
+ module Elesai
2
+
3
+ class LSIArray
4
+
5
+ attr_reader :adapters, :virtualdrives, :physicaldrives, :enclosures
6
+
7
+ def initialize(opts)
8
+ @adapters = []
9
+ @virtualdrives = []
10
+ @physicaldrives = {}
11
+ @enclosures = []
12
+ @spans = []
13
+
14
+ case opts[:hint]
15
+ when :pd,:physicaldrive
16
+ PDlist_aAll.new.parse!(self,opts)
17
+ when :vd, :virtualdrive
18
+ puts "vd!"
19
+ LDPDinfo_aAll.new.parse!(self,opts)
20
+ else
21
+ PDlist_aAll.new.parse!(self,opts)
22
+ LDPDinfo_aAll.new.parse!(self,opts)
23
+ end
24
+ end
25
+
26
+ def add_adapter(a)
27
+ @adapters[a[:id]] = a if @adapters[a[:id]].nil?
28
+ end
29
+
30
+ def add_virtualdrive(vd)
31
+ @virtualdrives.push(vd)
32
+ end
33
+
34
+ def add_physicaldrive(pd)
35
+ @physicaldrives[pd._id] = pd if @physicaldrives[pd._id].nil?
36
+ @physicaldrives[pd._id]
37
+ end
38
+
39
+ def to_s
40
+ lsiarrayout = "LSI Array\n"
41
+ @adapters.each do |adapter|
42
+ lsiarrayout += " adapter #{adapter.id}\n"
43
+ adapter.virtualdrives.each do |virtualdrive|
44
+ lsiarrayout += " +--+ #{virtualdrive.to_str}\n"
45
+ virtualdrive.physicaldrives.each do |id,physicaldrive|
46
+ lsiarrayout += " | |-- pd #{physicaldrive.to_str}\n"
47
+ end
48
+ end
49
+ end
50
+ lsiarrayout
51
+ end
52
+
53
+ ### Adapter
54
+
55
+ class Adapter < Hash
56
+
57
+ def initialize
58
+ self[:virtualdrives] = []
59
+ self[:physicaldrives] = {}
60
+ super
61
+ end
62
+
63
+ def _id
64
+ "#{self[:id]}"
65
+ end
66
+
67
+ def type
68
+ :adapter
69
+ end
70
+
71
+ def type_of?(type)
72
+ self.type == type
73
+ end
74
+
75
+ def inspect
76
+ "#{self.class}:#{self.__id__}"
77
+ end
78
+
79
+ def add_physicaldrive(pd)
80
+ self[:physicaldrives][pd._id] = pd unless self[:physicaldrives][pd._id].nil?
81
+ end
82
+
83
+ end
84
+
85
+ ### Virtual Drive
86
+
87
+ class VirtualDrive < Hash
88
+
89
+ STATES = {
90
+ :optimal => 'Optimal',
91
+ :degraded => 'Degraded',
92
+ :partial_degraded => 'Partial Degraded',
93
+ :failed => 'Failed',
94
+ :offline => 'Offline'
95
+ }
96
+
97
+ class Size < Struct.new(:number, :unit)
98
+ def to_s ; "%8.2f%s" % [self.number,self.unit] end
99
+ end
100
+ class RaidLevel < Struct.new(:primary, :secondary)
101
+ def to_s ; "raid%s:raid%s" % [self.primary,self.secondary] end
102
+ end
103
+
104
+ def initialize
105
+ self[:physicaldrives] = []
106
+ end
107
+
108
+ def _id
109
+ self[:targetid]
110
+ end
111
+
112
+ def type
113
+ :virtualdrive
114
+ end
115
+
116
+ def type_of?(type)
117
+ self.type == type
118
+ end
119
+
120
+ def inspect
121
+ "#{self.class}:#{self.__id__}"
122
+ end
123
+
124
+ def add_physicaldrive(pd)
125
+
126
+ end
127
+
128
+ def to_s
129
+ "[VD] %4s %18s %s %s %d" % [ self._id, self[:state], self[:size], self[:raidlevel], self[:physicaldrives].size ]
130
+ end
131
+
132
+ end
133
+
134
+ ### Physical Drive
135
+
136
+ class PhysicalDrive < Hash
137
+
138
+ STATES = {
139
+ :online => 'Online',
140
+ :unconfigured_good => 'Unconfigured(good)',
141
+ :hotspare => 'Hotspare',
142
+ :failed => 'Failed',
143
+ :rebuild => 'Rebuild',
144
+ :unconfigured_bad => 'Unconfigured(bad)',
145
+ :missing => 'Missing',
146
+ :offline => 'Offline'
147
+ }
148
+
149
+ SPINS = {
150
+ :spun_up => 'Spun up'
151
+ }
152
+
153
+ class Size < Struct.new(:number, :unit)
154
+ def to_s ; "%8.2f%s" % [self.number,self.unit] end
155
+ end
156
+ class FirmwareState < Struct.new(:state, :spin)
157
+ def to_s
158
+ "#{self.state}:#{self.spin}"
159
+ end
160
+ end
161
+
162
+ def initialize
163
+ self[:_adapter] = nil
164
+ self[:_virtualdrives] = []
165
+ end
166
+
167
+ def _id
168
+ "e#{self[:enclosuredeviceid].to_s}s#{self[:slotnumber].to_s}".to_sym
169
+ end
170
+
171
+ def type
172
+ :physicaldrive
173
+ end
174
+
175
+ def type_of?(type)
176
+ self.type == type
177
+ end
178
+
179
+ def to_s
180
+ keys = [:deviceid, :firmwarestate, :coercedsize, :mediatype, :pdtype, :mediaerrorcount, :predictivefailurecount,:inquirydata]
181
+ #"[PD] %8s %4s %19s %8.2f%s %5s %5s %3d %3d %s" % [ self.id, @deviceid, "#{@state}:#{@spin}", @_size.number, @_size.unit, @mediatype, @pdtype, @mediaerrors, @predictivefailure, @inquirydata ]
182
+ "[PD] %8s %4s %19s %s %5s %5s %3d %3d a%s %s" % [ self._id, self[:deviceid], self[:firmwarestate], self[:coercedsize], self[:mediatype], self[:pdtype], self[:mediaerrorcount], self[:predictivefailurecount], self[:_adapter]._id, self[:inquirydata] ]
183
+ end
184
+
185
+ def inspect
186
+ "#{self.class}:#{self.__id__}"
187
+ end
188
+
189
+ def add_adapter(a)
190
+ self[:_adapter] = a
191
+ end
192
+
193
+ def get_adapter
194
+ self[:_adapter]
195
+ end
196
+
197
+ def add_virtualdrive(vd)
198
+ self[:_virtualdrives][vd._id] = vd if self[:_virtualdrives][vd._id].nil?
199
+ end
200
+
201
+ def get_virtualdrive(vd_id)
202
+ self[:_virtualdrives][vd_id]
203
+ end
204
+
205
+ def get_virtualdrives
206
+ self[:_virtualdrives]
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,393 @@
1
+ require 'workflow'
2
+ require 'open3'
3
+
4
+ module Elesai
5
+
6
+ class Megacli
7
+
8
+ include Workflow
9
+
10
+ ADAPTER_RE = /^Adapter\s+#*(?<value>\d+)/
11
+ VIRTUALDRIVE_RE = /^Virtual\s+Drive:\s+\d+\s+\((?<key>Target\s+Id):\s+(?<value>\d+)\)/
12
+ SPAN_RE = /^Span:\s+(?<value>\d+)/
13
+ PHYSICALDRIVE_RE = /^(?<key>Enclosure\s+Device\s+ID):\s+(?<value>\d+)/
14
+ ATTRIBUTE_RE = /^(?<key>[A-Za-z0-9()\s#]+):(?<value>.*)/
15
+ EXIT_RE = /^Exit Code: /
16
+
17
+ ### Context
18
+
19
+ class Context
20
+
21
+ def initialize(current_state,lsi)
22
+ current_state.meta[:context] = { :stack => [], :adapter => nil, :virtualdrive => nil, :physicaldrive => nil }
23
+ @context = current_state.meta[:context]
24
+ @lsi = lsi
25
+ @log = Elesai::Logger.instance.log
26
+ end
27
+
28
+ def open(component)
29
+ @log.debug " * Open #{component.inspect}"
30
+ @context[:stack].push(component)
31
+ @context[component.type] = component
32
+ @log.debug " + context: #{@context[:stack]}"
33
+ end
34
+
35
+ def flash!(new_state)
36
+ new_state.meta[:context] = @context
37
+ @context = nil
38
+ @context = new_state.meta[:context]
39
+ @log.debug " + Flash context: #{@context[:stack]}"
40
+ end
41
+
42
+ def close
43
+ component = @context[:stack].pop
44
+ @context[component.type] = nil
45
+ @log.debug " * Close #{component.inspect}"
46
+ if component.type_of? :physicaldrive
47
+ pd = @lsi.add_physicaldrive(component)
48
+ pd.add_adapter(adapter)
49
+ pd.add_virtualdrive(virtualdrive) unless virtualdrive.nil?
50
+ adapter.add_physicaldrive(pd)
51
+ elsif component.type_of? :virtualdrive
52
+ vd = @lsi.add_virtualdrive(component)
53
+ elsif component.type_of? :adapter
54
+ @lsi.add_adapter(component)
55
+ end
56
+ @log.debug " + context: #{@context[:stack]}"
57
+ end
58
+
59
+ def current
60
+ @context[:stack][-1]
61
+ end
62
+
63
+ def adapter
64
+ @context[:adapter]
65
+ end
66
+
67
+ def virtualdrive
68
+ @context[:virtualdrive]
69
+ end
70
+
71
+ def physicaldrive
72
+ @context[:physicaldrive]
73
+ end
74
+
75
+ end
76
+
77
+ ### State Machine Handlers
78
+
79
+ # Start
80
+
81
+ def on_start_exit(new_state, event, *args)
82
+ @log.debug " [#{current_state}]: on_exit : #{event} -> #{new_state}; args: #{args}"
83
+ @context = Context.new(current_state,@lsi)
84
+ end
85
+
86
+ # Adapter
87
+
88
+ def adapter_line(adapter,key,value)
89
+ @log.debug " [#{current_state}] event adapter_line: new #{adapter.inspect}"
90
+ adapter[key.to_sym] = value.to_i
91
+ end
92
+
93
+ def on_adapter_entry(old_state, event, *args)
94
+ @log.debug " [#{current_state}] on_entry: leaving #{old_state}; args: #{args}"
95
+
96
+ @context.close unless @context.current.nil? or @context.current.type_of? :adapter
97
+ @context.open args[0]
98
+
99
+ end
100
+
101
+ def on_adapter_exit(new_state, event, *args)
102
+ @log.debug " [#{current_state}] on_exit: entering #{new_state}; args: #{args}"
103
+ @context.flash!(new_state)
104
+ end
105
+
106
+ # Virtual Drive
107
+
108
+ def virtualdrive_line(virtualdrive,key,value)
109
+ @log.debug " [#{current_state}] event: virtualdrive_line: new #{virtualdrive.inspect}"
110
+ virtualdrive[key.to_sym] = value.to_i
111
+ end
112
+
113
+ def on_virtualdrive_entry(old_state, event, *args)
114
+ @log.debug " [#{current_state}] on_entry: leaving #{old_state}; args: #{args}"
115
+
116
+ unless @context.current.nil?
117
+ if @context.current.type_of? :virtualdrive
118
+ @context.close
119
+ end
120
+ end
121
+ virtualdrive = args[0]
122
+ @context.open virtualdrive
123
+ end
124
+
125
+ def on_virtualdrive_exit(new_state, event, *args)
126
+ @log.debug " [#{current_state}] on_exit: entering #{new_state}; args: #{args}"
127
+ @context.flash!(new_state)
128
+ end
129
+
130
+ # Physical Drive
131
+
132
+ def physicaldrive_line(physicaldrive,key,value)
133
+ @log.debug " [#{current_state}] event: physicaldrive_line: new #{physicaldrive.inspect}"
134
+ physicaldrive[key.to_sym] = value.to_i
135
+ end
136
+
137
+ def on_physicaldrive_entry(old_state, event, *args)
138
+ @log.debug " [#{current_state}] on_entry: leaving #{old_state}; args: #{args}"
139
+ @context.open args[0]
140
+ end
141
+
142
+ def on_physicaldrive_exit(new_state, event, *args)
143
+ @log.debug " [#{current_state}] on_exit: entering #{new_state}; args: #{args}"
144
+ @context.flash!(new_state)
145
+ end
146
+
147
+ # Attribute
148
+
149
+ def attribute_line(key,value)
150
+ @log.debug " [#{current_state}] event: attribute_line: #{key} => #{value}"
151
+ end
152
+
153
+ def on_attribute_entry(old_state, event, *args)
154
+ @log.debug " [#{current_state}] entry: leaving #{old_state}; args: #{args}"
155
+
156
+
157
+ c = @context.current
158
+ k = args[0].to_sym
159
+ v = args[1]
160
+
161
+ # Some attributes require special treatment for our purposes
162
+
163
+ case k
164
+ when :coercedsize, :noncoercedsize, :rawsize, :size
165
+ m = /(?<number>[0-9\.]+)\s+(?<unit>[A-Z]+)/.match(v)
166
+ v = LSIArray::PhysicalDrive::Size.new(m[:number],m[:unit])
167
+ when :raidlevel
168
+ m = /Primary-(?<primary>\d+),\s+Secondary-(?<secondary>\d+)/.match(v)
169
+ v = LSIArray::VirtualDrive::RaidLevel.new(m[:primary],m[:secondary])
170
+ when :firmwarestate
171
+ st,sp = v.gsub(/\s/,'').split(/,/)
172
+ state = st.gsub(/\s/,'_').downcase.to_sym
173
+ spin = sp.gsub(/\s/,'_').downcase.to_sym unless sp.nil?
174
+ v = LSIArray::PhysicalDrive::FirmwareState.new(state,spin)
175
+ when :state
176
+ v = v.gsub(/\s/,'_').downcase.to_sym
177
+ when :mediatype
178
+ v = v.scan(/[A-Z]/).join
179
+ when :inquirydata
180
+ v = v.gsub(/\s+/,' ')
181
+ end
182
+ c[k] = v
183
+ end
184
+
185
+ def on_attribute_exit(new_state, event, *args)
186
+ @log.debug " [#{current_state}] exit: entering #{new_state} throught event #{event}; args: #{args}"
187
+ @context.close if @context.current.type_of? :physicaldrive and event != :attribute_line
188
+
189
+ @context.flash!(new_state)
190
+ end
191
+
192
+ # Exit
193
+
194
+ def exit_line
195
+ @log.debug " [#{current_state}] event: exit_line"
196
+ end
197
+
198
+ def on_exit_entry(new_state, event, *args)
199
+ @log.debug " [#{current_state}] exit: entering #{new_state} throught event #{event}; args: #{args}"
200
+ until @context.current.nil? do
201
+ @context.close
202
+ end
203
+ end
204
+
205
+ ### Regular Expression Match Handlers
206
+
207
+ # Adapter
208
+
209
+ def adapter_match(match)
210
+ @log.debug "ADAPTER! #{match.string}"
211
+ key = 'id'
212
+ value = match[:value]
213
+ adapter_line!(LSIArray::Adapter.new,key,value)
214
+ end
215
+
216
+ # Virtual Drive
217
+
218
+ def virtualdrive_match(match)
219
+ @log.debug "VIRTUALDRIVE! #{match.string}"
220
+ key = match[:key].gsub(/\s+/,"").downcase
221
+ value = match[:value]
222
+ virtualdrive_line!(LSIArray::VirtualDrive.new,key,value)
223
+ end
224
+
225
+ # Physical Drive
226
+
227
+ def physicaldrive_match(match)
228
+ @log.debug "PHYSICALDRIVE! #{match.string}"
229
+ key = match[:key].gsub(/\s+/,"").downcase
230
+ value = match[:value]
231
+ physicaldrive_line!(LSIArray::PhysicalDrive.new,key,value)
232
+ end
233
+
234
+ # Attribute
235
+
236
+ def attribute_match(match)
237
+ @log.debug "ATTRIBUTE! #{match.string}"
238
+ key = match[:key].gsub(/\s+/,"").downcase
239
+ value = match[:value].strip
240
+ attribute_line!(key,value)
241
+ end
242
+
243
+ # Exit
244
+
245
+ def exit_match(match)
246
+ @log.debug "EXIT! #{match.string}"
247
+ exit_line!
248
+ end
249
+
250
+ ### Parse!
251
+
252
+ def parse!(lsi,opts)
253
+
254
+ @lsi = lsi
255
+ @log = Elesai::Logger.instance.log
256
+
257
+ if STDIN.tty?
258
+ if opts[:fake].start_with? '-'
259
+ megacli = opts[:megacli].nil? ? "Megacli" : opts[:megacli]
260
+ command = "#{megacli} #{opts[:fake]}"
261
+ output = Open3.popen3(command) do |stdin, stdout, stderr, wait_thr|
262
+ stdin.close
263
+ raise RuntimeError, stderr.gets.chomp unless wait_thr.value.exitstatus == 0
264
+ stdout.gets
265
+ end
266
+ else
267
+ output = File.read(opts[:fake])
268
+ end
269
+ else
270
+ output = STDIN.read
271
+ end
272
+
273
+ output.each_line do |line|
274
+ line.strip!
275
+ next if line == ''
276
+
277
+ case line
278
+ when ADAPTER_RE then adapter_match(ADAPTER_RE.match(line))
279
+ when VIRTUALDRIVE_RE then virtualdrive_match(VIRTUALDRIVE_RE.match(line))
280
+ when PHYSICALDRIVE_RE then physicaldrive_match(PHYSICALDRIVE_RE.match(line))
281
+ when EXIT_RE then exit_match(EXIT_RE.match(line))
282
+ when ATTRIBUTE_RE then attribute_match(ATTRIBUTE_RE.match(line))
283
+ else raise StandardError, "cannot parse '#{line}'"
284
+ end
285
+
286
+ @log.debug "\n\n"
287
+ end
288
+ end
289
+
290
+ end
291
+
292
+ class PDlist_aAll < Megacli
293
+
294
+ def parse!(lsi,opts)
295
+ fake = opts[:fake].nil? ? "-pdlist -aall" : File.join(opts[:fake],"pdlist_aall")
296
+ super lsi, :fake => fake, :megacli => opts[:megacli]
297
+ end
298
+
299
+ workflow do
300
+
301
+ state :start do
302
+ event :adapter_line, :transitions_to => :adapter
303
+ event :exit_line, :transitions_to => :exit
304
+ end
305
+
306
+ state :adapter do
307
+ event :adapter_line, :transitions_to => :adapter # empty adapter
308
+ event :physicaldrive_line, :transitions_to => :physicaldrive
309
+ event :exit_line, :transitions_to => :exit
310
+ end
311
+
312
+ state :physicaldrive do
313
+ event :attribute_line, :transitions_to => :physicaldrive
314
+ event :exit_line, :transitions_to => :exit
315
+ event :adapter_line, :transitions_to => :adapter
316
+ event :physicaldrive_line, :transitions_to => :physicaldrive
317
+ event :attribute_line, :transitions_to => :attribute
318
+ end
319
+
320
+ state :attribute do
321
+ event :attribute_line, :transitions_to => :attribute
322
+ event :physicaldrive_line, :transitions_to => :physicaldrive
323
+ event :adapter_line, :transitions_to => :adapter
324
+ event :exit_line, :transitions_to => :exit
325
+ end
326
+
327
+ state :exit
328
+
329
+ on_transition do |from, to, triggering_event, *event_args|
330
+ #puts self.spec.states[to].class
331
+ # puts " transition: #{from} >> #{triggering_event}! >> #{to}: #{event_args.join(' ')}"
332
+ #puts " #{current_state.meta}"
333
+ end
334
+ end
335
+
336
+ end
337
+
338
+ class LDPDinfo_aAll < Megacli
339
+
340
+ def parse!(lsi,opts)
341
+ fake = opts[:fake].nil? ? "-ldpdinfo -aall" : File.join(opts[:fake],"ldpdinfo_aall")
342
+ super lsi, :fake => fake, :megacli => opts[:megacli]
343
+ end
344
+
345
+
346
+ workflow do
347
+
348
+ state :start do
349
+ event :adapter_line, :transitions_to => :adapter
350
+ event :exit_line, :transitions_to => :exit
351
+ end
352
+
353
+ state :adapter do
354
+ event :adapter_line, :transitions_to => :adapter
355
+ event :attribute_line, :transitions_to => :attribute
356
+ event :virtualdrive_line, :transitions_to => :virtualdrive
357
+ event :exit_line, :transitions_to => :exit
358
+ end
359
+
360
+ state :physicaldrive do
361
+ event :attribute_line, :transitions_to => :physicaldrive
362
+ event :exit_line, :transitions_to => :exit
363
+ event :adapter_line, :transitions_to => :adapter
364
+ event :physicaldrive_line, :transitions_to => :physicaldrive
365
+ event :attribute_line, :transitions_to => :attribute
366
+ end
367
+
368
+ state :virtualdrive do
369
+ event :physicaldrive_line, :transitions_to => :physicaldrive
370
+ event :attribute_line, :transitions_to => :attribute
371
+ end
372
+
373
+ state :attribute do
374
+ event :attribute_line, :transitions_to => :attribute
375
+ event :virtualdrive_line, :transitions_to => :virtualdrive
376
+ event :physicaldrive_line, :transitions_to => :physicaldrive
377
+ event :adapter_line, :transitions_to => :adapter
378
+ event :exit_line, :transitions_to => :exit
379
+ end
380
+
381
+ state :exit
382
+
383
+ on_transition do |from, to, triggering_event, *event_args|
384
+ #puts self.spec.states[to].class
385
+ # puts " transition: #{from} >> #{triggering_event}! >> #{to}: #{event_args.join(' ')}"
386
+ #puts " #{current_state.meta}"
387
+ end
388
+ end
389
+
390
+ end
391
+
392
+
393
+ end
@@ -0,0 +1,3 @@
1
+ module Elesai
2
+ VERSION = "0.4.0"
3
+ end
data/lib/elesai.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'elesai/logger'
2
+ require 'elesai/lsi'
3
+ require 'elesai/megacli'
4
+
5
+ module Elesai
6
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elesai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Gerardo López-Fernádez
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: log4r
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.9
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.9
30
+ - !ruby/object:Gem::Dependency
31
+ name: senedsa
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 0.1.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 0.1.0
46
+ description: Senedsa is a small utility and library wrapper for the Nagios send_nsca.
47
+ email: gerir@evernote.com
48
+ executables:
49
+ - elesai
50
+ - check_elesai
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/elesai/about.rb
55
+ - lib/elesai/cli.rb
56
+ - lib/elesai/logger.rb
57
+ - lib/elesai/lsi.rb
58
+ - lib/elesai/megacli.rb
59
+ - lib/elesai/version.rb
60
+ - lib/elesai.rb
61
+ - bin/check_elesai
62
+ - bin/elesai
63
+ - LICENSE
64
+ - README.md
65
+ homepage: https://github.com/evernote/ops-elesai
66
+ licenses:
67
+ - Apache License, Version 2.0
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: 1.3.5
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 1.8.23
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Utility and library wrapper for Nagios send_nsca utility
90
+ test_files: []