brocadesan 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/README +113 -0
  2. data/Rakefile +46 -0
  3. data/brocadesan.gemspec +14 -0
  4. data/lib/brocadesan.rb +8 -0
  5. data/lib/brocadesan/alias.rb +64 -0
  6. data/lib/brocadesan/config/brocade/san/switch_cmd_mapping.yml +116 -0
  7. data/lib/brocadesan/config/parser_mapping.yml +21 -0
  8. data/lib/brocadesan/device.rb +296 -0
  9. data/lib/brocadesan/monkey/string.rb +11 -0
  10. data/lib/brocadesan/provisioning.rb +894 -0
  11. data/lib/brocadesan/switch.rb +882 -0
  12. data/lib/brocadesan/wwn.rb +63 -0
  13. data/lib/brocadesan/zone.rb +60 -0
  14. data/lib/brocadesan/zone_configuration.rb +38 -0
  15. data/lib/meta_methods.rb +263 -0
  16. data/test/alias_test.rb +68 -0
  17. data/test/device_test.rb +203 -0
  18. data/test/output_helpers.rb +308 -0
  19. data/test/outputs/agshow_1.txt +7 -0
  20. data/test/outputs/agshow_1.yml +31 -0
  21. data/test/outputs/agshow_2.txt +4 -0
  22. data/test/outputs/agshow_2.yml +3 -0
  23. data/test/outputs/apt_policy_1.txt +6 -0
  24. data/test/outputs/apt_policy_1.yml +3 -0
  25. data/test/outputs/cfgshow_1.txt +5 -0
  26. data/test/outputs/cfgshow_1.yml +7 -0
  27. data/test/outputs/cfgshow_2.txt +31 -0
  28. data/test/outputs/cfgshow_2.yml +32 -0
  29. data/test/outputs/cfgshow_3.txt +9 -0
  30. data/test/outputs/cfgshow_3.yml +12 -0
  31. data/test/outputs/cfgtransshow_1.txt +2 -0
  32. data/test/outputs/cfgtransshow_1.yml +5 -0
  33. data/test/outputs/cfgtransshow_2.txt +3 -0
  34. data/test/outputs/cfgtransshow_2.yml +4 -0
  35. data/test/outputs/cfgtransshow_3.txt +3 -0
  36. data/test/outputs/cfgtransshow_3.yml +4 -0
  37. data/test/outputs/chassisname_1.txt +2 -0
  38. data/test/outputs/chassisname_1.yml +3 -0
  39. data/test/outputs/dlsshow_1.txt +4 -0
  40. data/test/outputs/dlsshow_1.yml +4 -0
  41. data/test/outputs/dlsshow_2.txt +4 -0
  42. data/test/outputs/dlsshow_2.yml +4 -0
  43. data/test/outputs/fabricshow_1.txt +10 -0
  44. data/test/outputs/fabricshow_1.yml +34 -0
  45. data/test/outputs/iodshow_1.txt +4 -0
  46. data/test/outputs/iodshow_1.yml +4 -0
  47. data/test/outputs/islshow_1.txt +6 -0
  48. data/test/outputs/islshow_1.yml +62 -0
  49. data/test/outputs/islshow_2.txt +2 -0
  50. data/test/outputs/islshow_2.yml +2 -0
  51. data/test/outputs/lscfg_show_1.txt +71 -0
  52. data/test/outputs/lscfg_show_1.yml +5 -0
  53. data/test/outputs/ns_1.txt +80 -0
  54. data/test/outputs/ns_1.yml +39 -0
  55. data/test/outputs/ns_2.txt +37 -0
  56. data/test/outputs/ns_2.yml +21 -0
  57. data/test/outputs/putty.log +1867 -0
  58. data/test/outputs/switch_1.txt +25 -0
  59. data/test/outputs/switch_1.yml +73 -0
  60. data/test/outputs/switch_2.txt +18 -0
  61. data/test/outputs/switch_2.yml +42 -0
  62. data/test/outputs/switch_3.txt +14 -0
  63. data/test/outputs/switch_3.yml +46 -0
  64. data/test/outputs/switchstatusshow_1.txt +21 -0
  65. data/test/outputs/switchstatusshow_1.yml +19 -0
  66. data/test/outputs/trunkshow_1.txt +8 -0
  67. data/test/outputs/trunkshow_1.yml +44 -0
  68. data/test/outputs/trunkshow_2.txt +2 -0
  69. data/test/outputs/trunkshow_2.yml +2 -0
  70. data/test/outputs/version_1.txt +6 -0
  71. data/test/outputs/version_1.yml +8 -0
  72. data/test/outputs/vf_switch_1.txt +25 -0
  73. data/test/outputs/vf_switch_1.yml +73 -0
  74. data/test/provisioning_test.rb +1043 -0
  75. data/test/switch_test.rb +476 -0
  76. data/test/wwn_test.rb +41 -0
  77. data/test/zone_configuration_test.rb +65 -0
  78. data/test/zone_test.rb +73 -0
  79. metadata +170 -0
@@ -0,0 +1,882 @@
1
+ require 'net/ssh'
2
+ require 'yaml'
3
+
4
+ # Brocade namespace
5
+ module Brocade
6
+ # SAN namespace
7
+ module SAN
8
+ # TODO: zoneshow, cfgshow and alishow tests
9
+ # configuration class
10
+ class Configuration #:nodoc:
11
+ def self.cmd_mapping_path(_class)
12
+ File.realpath("../config/#{_class.name.underscore}_cmd_mapping.yml",__FILE__)
13
+ end
14
+
15
+ def self.parser_path
16
+ File.realpath("../config/parser_mapping.yml",__FILE__)
17
+ end
18
+ end
19
+
20
+ # Class to model SAN switch from Brocade
21
+ class Switch
22
+
23
+ include SshDevice
24
+
25
+ # Maps each method name to command to be run to obtain it and to hash key where it ill be stored.
26
+ # As well sets the format of the returned value. This is used only for documentation purposes.
27
+ #
28
+ # See lib/config/brocade/san/switch_cmd_mapping.yml for details
29
+ #
30
+ # Example:
31
+ # :name:
32
+ # :cmd: switchshow
33
+ # :attr: switch_name
34
+ # :format: string
35
+ #
36
+ # This will cause that class will have instance method called name(forced=true).
37
+ # When the method is called first time or +forced+ is true the +switchshow+ command
38
+ # will be queried on the switch.
39
+ # The query response will be then parsed and stored in Response +parsed+ hash under +:switch_name+ key.
40
+ # The +parsed+ hash is then merged into switch +configuration+ hash.
41
+ # At the end the +:switch_name+ key is returned from +configuration+ hash.
42
+ #
43
+ # The parser needs to ensure that it parses the +cmd+ output and stores the proper value into Response +parsed+ under +:attr+ key.
44
+
45
+ CMD_MAPPING=YAML.load(File.read(Configuration::cmd_mapping_path(self)))
46
+
47
+ # Maps each command to the parser method to use
48
+ #
49
+ # See lib/config/parser_mapping.yml for details
50
+ PARSER_MAPPING=YAML.load(File.read(Configuration::parser_path))
51
+
52
+ # zone configuration, zone and zone aliases naming rule
53
+ # must start with an alphabetic character and may contain
54
+ # alphanumeric characters and the underscore ( _ ) character.
55
+ NAME_RULE='^[a-z][a-z_\d]*$'
56
+
57
+ # Used to dynamically create named methods based on CMD_MAPPING
58
+ def self.attributes(args)
59
+ args.each do |arg|
60
+ define_method arg do |forced=false|
61
+ self.get(arg,forced)
62
+ end
63
+ end
64
+ end
65
+
66
+ # verifies if name matches convetion defined in NAME_RULE
67
+ # raises Switch::Error: Incorrect name format if not
68
+ # this method is used internally mostly
69
+
70
+ def self.verify_name(name)
71
+ raise Switch::Error.incorrect(name) if !name.match(/#{NAME_RULE}/i)
72
+ end
73
+
74
+ # Creates a Switch instance and tests a connection.
75
+ #
76
+ # Checks as well if the switch is virtual fabric enabled since that defines the way it will be queried further.
77
+ def initialize(*params)
78
+ super(*params)
79
+ @configuration={}
80
+ vf
81
+ end
82
+
83
+ # Hash containing parsed attributes
84
+ #
85
+ # Can be used to obtain parsed attributes for which there is no named method.
86
+ # These attributes however has to be obtained as colateral of running another public method.
87
+ #
88
+ # Generally this attribute should not be accessed directly but named method for the given attribute should be created.
89
+ #
90
+ # Example:
91
+ #
92
+ # # this will call switchshow (see CMD_MAPPING)
93
+ # # and load :several switchow values into configuration as well
94
+ # # the command however returns only whether the ls_attributtes
95
+ #
96
+ # switch.configuration
97
+ # => nil
98
+ # switch.ls_attributes
99
+ # => {:fid=>"128", :base_switch=>"no", :default_switch=>"yes", :address_mode=>"0"}
100
+ # switch.configuration
101
+ # => {:vf=>"enabled", :parsing_position=>"end", :switch_name=>"H2C04R065-U03-A01", :switch_type=>"62.3", :switch_state=>"Online", ...
102
+ # switch.configuration[:switch_name]
103
+ # => "H2C04R065-U03-A01"
104
+ #
105
+ # # this swichname will be taken from cache as the switchshow was started as part of ls_attributes
106
+ # switch.name
107
+ # => "H2C04R065-U03-A01"
108
+
109
+
110
+ attr_reader :configuration
111
+
112
+ # Fabric id of the switch to be queried. Can be set using +set_context+.
113
+ #
114
+ # If the given +fid+ does not exist default switch for the provided
115
+ # account will be queried.
116
+
117
+ attr_reader :fid
118
+
119
+ attributes CMD_MAPPING.keys
120
+
121
+ # Sets the FID of the switch to be queried and clears cache.
122
+ # Next queries will be done directly on switch.
123
+ #
124
+ # If not +fid+ is given it will set it to default 128.
125
+ # If the switch does not support virtual fabrics this will be ignored.
126
+ #
127
+ # Returns the fid that was set
128
+ def set_context(fid=128)
129
+ @loaded={}
130
+ @fid = fid.to_i==0 ? 128 : fid.to_i
131
+ end
132
+
133
+ # gets the +attr+
134
+ #
135
+ # +attr+ has to be speficied in the CMD_MAPPING
136
+ #
137
+ # named methods are wrappers around this method so you should not use this directly
138
+ def get(attr,forced=false)
139
+ raise Switch::Error.unknown if CMD_MAPPING[attr.to_sym].nil?
140
+
141
+ cmd=CMD_MAPPING[attr.to_sym][:cmd]
142
+
143
+ refresh(cmd,"",forced)
144
+
145
+ @configuration[CMD_MAPPING[attr.to_sym][:attr].to_sym]
146
+ end
147
+
148
+ # returns switches in the fabric in hash form
149
+
150
+ def fabric(forced=false)
151
+ cmd="fabricshow"
152
+ refresh(cmd,"",forced)
153
+ @configuration[:fabric]
154
+ end
155
+
156
+ # If called with +true+ argument it will get the virtual_fabric from the switch instead of cache
157
+ #
158
+ # Returns value in (string) format
159
+
160
+ def vf(forced=false)
161
+ if !@configuration[:vf] || forced
162
+ # using this instead of fosconfig --show as that command does not work everywhere
163
+ # we could user #ls_attributes method but that loaded whole switchshow os this will be a bit faster, especially with big switches
164
+ # it needs to be faster as vf is called during initialization
165
+ # if the switch is vf there will be LS Attributes line, otherwise it will be empty
166
+ response=query("switchshow|grep \"^LS Attributes\"")
167
+ @configuration[:vf] = response.data.split("\n").size == 2 ? "enabled" : "disabled"
168
+ end
169
+ @configuration[:vf]
170
+ end
171
+
172
+ # Returns ZoneConfigurations array
173
+ #
174
+ # If +full+ is set to true it loads full configurations with zones and aliases, otherwise it gets just the names.
175
+ #
176
+ # It laods even zone configurations that were create as part of ongoing transaction.
177
+ def zone_configurations(full=false,forced=false)
178
+ get_configshow(full,forced)[:zone_configurations]
179
+ end
180
+
181
+ # Returns effective ZoneConfiguration
182
+ #
183
+ # If +full+ is set to true it loads full effective configuration with zones and aliases, otherwise it gets just the name
184
+ def effective_configuration(full=false,forced=false)
185
+ zone_configurations(full,forced).select {|z| z.effective == true}.first
186
+ end
187
+
188
+ # returns all zones defined on the switch including zones created in ongoing transaction as array of Zone
189
+
190
+ def zones(forced=false)
191
+ get_configshow(true,forced)[:zones]
192
+ end
193
+
194
+ # returns all aliases defined on the switch including aliases created in ongoing transaction as array of Alias
195
+
196
+ def aliases(forced=false)
197
+ get_configshow(true,forced)[:aliases]
198
+ end
199
+
200
+ # returns Zone with name of +str+ if exists, +nil+ otherwise
201
+ def find_zone(str)
202
+ find(str,:object=>:zone)
203
+ end
204
+
205
+ # returns Zone array of Zones with name matching +regexp+ if exists, [] otherwise
206
+ #
207
+ # find is case insesitive
208
+ def find_zones(regexp)
209
+ zones = find(regexp,:object=>:zone,:find_mode=>:partial)
210
+ return [] if zones==[nil]
211
+ zones
212
+ end
213
+
214
+ # returns Alias with name of +str+ if exists, +nil+ otherwise
215
+ def find_alias(str)
216
+ al = find(str,:object=>:alias)
217
+ end
218
+
219
+ # returns Alias array of Aliases with name matching +regexp+ if exists, [] otherwise
220
+ #
221
+ # find is case insesitive
222
+ def find_aliases(regexp)
223
+ aliases = find(regexp,:object=>:alias,:find_mode=>:partial)
224
+ return [] if aliases==[nil]
225
+ aliases
226
+ end
227
+
228
+ # returns all WWNs
229
+ #
230
+ # +mode+
231
+ #
232
+ # [:local] returns all local WWNs
233
+ # [:cached] returns wwns cached from remote switches
234
+ # [:all] returns all local and remote wwns
235
+
236
+ def wwns(forced=false,mode=:local)
237
+ if mode==:local
238
+ get_ns(true,forced,:local=>true)
239
+ elsif mode==:cached
240
+ get_ns(true,forced,:remote=>true)
241
+ else
242
+ get_ns(true,forced,:local=>true).concat get_ns(true,forced,:remote=>true)
243
+ end
244
+ end
245
+
246
+ # returns WWN of given +value+ if exists, +nil+ if not
247
+ #
248
+ # if +forced+ is true it will load the data from switch instead of the cache
249
+ #
250
+ # +opts+
251
+ #
252
+ # [:fabric_wide] searches whole fabric
253
+ def find_wwn(value,forced=true,opts={:fabric_wide=>false})
254
+ objs = opts[:fabric_wide]==true ? get_ns(true,forced,:local=>true).concat(get_ns(true,forced,:remote=>true)) : get_ns(true,forced,:local=>true)
255
+ objs.find {|k| value.downcase == k.value.downcase}
256
+ end
257
+
258
+ # finds configuration object by +str+. Case insensitive.
259
+ # If the +object+ option is not specified it searches :zone objects. It finds only saved objects. If the object was created but the transaction is not confirmed it will not find it.
260
+ #
261
+ # +opts+
262
+ #
263
+ # [:object] :zones (default) - finds zones
264
+ #
265
+ # :aliases - finds aliases
266
+ # [:find_mode] :partial - find partial matches
267
+ #
268
+ # :exact(default) - finds exact matches
269
+ #
270
+ # Example:
271
+ #
272
+ # switch.find("zone1",:object=>:zone)
273
+ def find(str,opts={})
274
+ # do not change the following 3 lines without writing test for it
275
+ obj = !opts[:object].nil? && [:zone,:alias].include?(opts[:object]) ? opts[:object] : :zone
276
+ mode = !opts[:find_mode].nil? && [:partial,:exact].include?(opts[:find_mode]) ? opts[:find_mode] : :exact
277
+ grep_exp = mode==:exact ? " | grep -i -E ^#{obj}\.#{str}:" : " | grep -i -E ^#{obj}\..*#{str}.*:"
278
+
279
+ response = script_mode do
280
+ query("configshow"+grep_exp)
281
+ end
282
+ response.parse
283
+
284
+ #output of configshow is stored to find_results
285
+
286
+ objs=response.parsed[:find_results]
287
+ objs||=[]
288
+
289
+ result=[]
290
+
291
+ objs.each do |item|
292
+ i = obj==:zone ? Zone.new(item[:obj]) : Alias.new(item[:obj])
293
+ item[:members].split(";").each do |member|
294
+ i.add_member(member)
295
+ end
296
+ result<<i
297
+ end
298
+ # result is array of Zone or Alias instances
299
+ result
300
+ end
301
+
302
+ # finds configuration objects that have member specified by +str+. Case insensitive.
303
+ # If the +object+ option is not specified it searches :zone objects.
304
+ # See find_by_member_from_cfgshow.
305
+ #
306
+ # +opts+
307
+ #
308
+ # [:object] :all (default) - finds all objects (zones, configs, aliases)
309
+ #
310
+ # :aliases - finds aliases
311
+ #
312
+ # :zones - finds zones
313
+ #
314
+ # [:find_mode] :partial - find partial matches
315
+ #
316
+ # :exact(default) - finds exact matches
317
+ #
318
+ # [:transaction] false (defualt) - ignores object in ongoing transaction
319
+ #
320
+ # true - includes objects in ongoing transaction
321
+ #
322
+ # Example:
323
+ #
324
+ # switch.find_by_member("zone1",:object=>:cfg)
325
+ #
326
+ # Note:
327
+ #
328
+ # Zone can be only members of zone configuration and alias can be only members of zones so finding object that have them as members works with default :all :object.
329
+ # However WWNs can be part both of zones and aliases so that is why there is option to speficy the object
330
+
331
+ def find_by_member(str,opts={})
332
+ obj = !opts[:object].nil? && [:zone,:alias,:all].include?(opts[:object]) ? opts[:object] : :all
333
+ obj = "zone|alias|cfg" if obj==:all
334
+ mode = !opts[:find_mode].nil? && [:partial,:exact].include?(opts[:find_mode]) ? opts[:find_mode] : :exact
335
+
336
+ trans_inc = !opts[:transaction].nil? && [true,false].include?(opts[:transaction]) ? opts[:transaction] : false
337
+
338
+ base_grep1 = "^#{obj}\."
339
+ base_grep2 = mode==:exact ? "(:|;)#{str}(;|$)" : ":.*#{str}"
340
+
341
+ if trans_inc
342
+ grep_exp1 = /#{base_grep1}/i
343
+ grep_exp2 = /#{base_grep2}/i
344
+ response = script_mode do
345
+ query("cfgshow")
346
+ end
347
+ response.send :cfgshow_to_configshow, grep_exp1, grep_exp2
348
+ else
349
+ grep_exp = " | grep -i -E \"#{base_grep1}\""
350
+ grep_exp += " | grep -i -E \"#{base_grep2}\""
351
+ response = script_mode do
352
+ query("configshow"+grep_exp)
353
+ end
354
+ end
355
+
356
+ response.parse
357
+ #output of configshow is stored to find_results
358
+
359
+ objs=response.parsed[:find_results]
360
+ objs||=[]
361
+
362
+ result=[]
363
+
364
+ objs.each do |item|
365
+ i = case item[:type]
366
+ when :zone
367
+ Zone.new(item[:obj])
368
+ when :alias
369
+ Alias.new(item[:obj])
370
+ when :cfg
371
+ ZoneConfiguration.new(item[:obj])
372
+ end
373
+ item[:members].split(";").each do |member|
374
+ i.add_member(member)
375
+ end
376
+ result<<i
377
+ end
378
+ # result is array of Zone, Alias, and ZoneConfiguration instances
379
+ result
380
+ end
381
+
382
+ private
383
+
384
+ def should_refresh?(cmd, forced)
385
+ !@loaded || !@loaded[key(cmd)] || forced
386
+ end
387
+
388
+ def get_configshow(full=false,forced=false)
389
+ cmd="cfgshow"
390
+ filter = full==false ? "-e cfg: -e configuration:" : ""
391
+
392
+ refresh(cmd,filter,forced)
393
+
394
+ #storing configs
395
+ tmp_cfg={}
396
+ tmp_cfg[:zone_configurations]=[]
397
+
398
+
399
+ # storing defined
400
+ @configuration[:defined_configuration][:cfg].each do |config,members|
401
+
402
+ effective = @configuration[:effective_configuration][:cfg].keys[0]==config ? true : false
403
+
404
+ cfg=ZoneConfiguration.new(config,:effective=>effective)
405
+ members.each do |member|
406
+ cfg.add_member member
407
+ end
408
+
409
+ tmp_cfg[:zone_configurations]<<cfg
410
+ end
411
+
412
+ if full
413
+ # storing zones
414
+ tmp_cfg[:zones]=[]
415
+
416
+ if @configuration[:defined_configuration][:zone]
417
+ # storing defined
418
+ active_zones = tmp_cfg[:zone_configurations].select {|z| z.effective == true}.first.members
419
+ @configuration[:defined_configuration][:zone].each do |zone,members|
420
+ active = active_zones.include?(zone) ? true : false
421
+ z=Zone.new(zone,:active=>active)
422
+ members.each do |member|
423
+ z.add_member member
424
+ end
425
+ tmp_cfg[:zones]<<z
426
+ end
427
+ end
428
+
429
+
430
+ # storing aliases
431
+ tmp_cfg[:aliases]=[]
432
+
433
+ if @configuration[:defined_configuration][:alias]
434
+ # storing defined
435
+ @configuration[:defined_configuration][:alias].each do |al_name,members|
436
+ al=Alias.new(al_name)
437
+ members.each do |member|
438
+ al.add_member member
439
+ end
440
+ tmp_cfg[:aliases]<<al
441
+ end
442
+ end
443
+ end
444
+
445
+ tmp_cfg
446
+ end
447
+
448
+ def get_ns(full=false,forced=false,opts={:local=>true})
449
+ cmd = opts[:local] ? "nsshow -t" : "nscamshow -t"
450
+ key = opts[:local] ? :wwn_local : :wwn_remote
451
+ filter = "" #full==false ? "-e cfg: -e configuration:" : ""
452
+
453
+ refresh(cmd,filter,forced)
454
+
455
+ #storing wwns
456
+ tmp_wwns=[]
457
+
458
+ # storing defined
459
+ @configuration[key].each do |wwn|
460
+ domain_id=wwn[:domain_id]==0 ? self.domain.to_i : wwn[:domain_id]
461
+ w=Wwn.new(wwn[:value],wwn[:dev_type],domain_id,wwn[:port_index],:symbol=>wwn[:symbol])
462
+ tmp_wwns<<w
463
+ end
464
+
465
+ tmp_wwns
466
+ end
467
+
468
+ def refresh(cmd,filter="",forced=true)
469
+ return @loaded[key(cmd)] if !should_refresh?(cmd+filter,forced)
470
+ grep_exp=filter.empty? ? "" : " | grep #{filter}"
471
+ response=script_mode do
472
+ query(cmd+grep_exp)
473
+ end
474
+ response.parse
475
+
476
+ #puts response.data
477
+
478
+ @configuration||={}
479
+ @configuration.merge!(response.parsed)
480
+
481
+ @loaded||={}
482
+ # when we use filter we need to mark the cmd as false
483
+ @loaded[key(cmd)]=false
484
+ @loaded[key(cmd+filter.to_s)]=true
485
+
486
+ return @loaded[key(cmd)]
487
+ end
488
+
489
+ def key(cmd)
490
+ cmd.gsub(/\s+/,'_').to_sym
491
+ end
492
+
493
+ def fullcmd(cmd)
494
+ if @configuration[:vf]=="enabled" && @fid
495
+ cmds = cmd.split("|")
496
+ if cmds.size>1
497
+ "fosexec --fid #{@fid} \'#{cmds.shift}\' |#{cmds.join("|")}"
498
+ else
499
+ "fosexec --fid #{@fid} \'#{cmds.shift}\'"
500
+ end
501
+ else
502
+ cmd
503
+ end
504
+ end
505
+ end
506
+
507
+ class Switch
508
+ # class extending SshDevice::Response
509
+ class Response < self::Response
510
+
511
+ # Wrapper around SshDevice::Response +parse+ that
512
+ # includes before and after hooks
513
+
514
+ def parse # :nodoc:
515
+ before_parse
516
+ super
517
+ after_parse
518
+ end
519
+
520
+ private
521
+
522
+ # transfers cfgshow to configshow format
523
+ def cfgshow_to_configshow(*greps)
524
+ # check if we have configshow output
525
+ return false if !@data.match(/> cfgshow/m)
526
+ # replace command
527
+ @data.gsub!("cfgshow","configshow")
528
+ # remove all lines below Effective configuration: included and Defined configuration line
529
+ @data.gsub!(/(Defined configuration:\s*|Effective configuration:.*)/im,"")
530
+ # remove spaces followig ; (includes newlines)
531
+ @data.gsub!(/;\s+/m,";")
532
+ # remove newlines before first member
533
+ @data.gsub!(/\t\n\t+/m,":")
534
+ # removes tab before first member
535
+ @data.gsub!(/([a-z_0-9])\t+([a-z_0-9])/im,'\1:\2')
536
+ # substitute alias,zone and cfg name
537
+ @data.gsub!(/^\s*(zone|alias|cfg):\s+/,'\1.')
538
+ #removes any empty character except newline
539
+ @data.gsub!(/[ \t]+\n/,"\n")
540
+ greps.each do |grep_t|
541
+ @data=@data.split("\n").grep(grep_t).join("\n")
542
+ end
543
+ @data="> configshow\n#{@data}"
544
+ true
545
+ end
546
+
547
+ def before_parse
548
+ reset
549
+ @parsed[:find_results]=[]
550
+ @parsed[:base]={}
551
+ end
552
+
553
+ def after_parse
554
+ @parsed[:ports].uniq! if @parsed[:ports]
555
+ to_purge = [:pointer,:last_key,:key,:domain,:was_popped]
556
+ to_purge<<:find_results if @parsed[:find_results].empty?
557
+ to_purge<<:base if @parsed[:base].empty?
558
+ to_purge.each {|k| @parsed.delete(k)}
559
+ end
560
+
561
+ def parse_line(line)
562
+ return if line.empty?
563
+ # we detect which command output we parse - commands start with defined prompt on the XML line
564
+ @parsed[:parsing_position] = case
565
+ # stripping fosexec, all pipes and ' to get pure command
566
+ when line.match(/^#{@prompt}/) then line.gsub(/(fosexec --fid \d+ \')|\'$|\' \|.*$/,"").split(" ")[1]
567
+ else @parsed[:parsing_position]
568
+ end
569
+ #some default processing
570
+ if line.match(/^#{@prompt}/)
571
+ case @parsed[:parsing_position]
572
+ when "islshow"
573
+ @parsed[:isl_links]||=[]
574
+ when "trunkshow"
575
+ @parsed[:trunk_links]||=[]
576
+ when "agshow"
577
+ @parsed[:ag]||=[]
578
+ when "cfgtransshow"
579
+ # if empty hash is returned the response was unexpected
580
+ @parsed[:cfg_transaction]={}
581
+ end
582
+ end
583
+ #do not process if we are on command line
584
+ return if line.match(/^#{@prompt}/)
585
+
586
+ # we parse only certain commands definned in PARSER_MAPPING
587
+ # all other commands are ignored by parser
588
+ # you can call them usign query and parse by yourself if you need some other
589
+ # or define parser mapping, command mapping and update given parser
590
+ case
591
+ when @parsed[:parsing_position].match(/#{PARSER_MAPPING.map{ |k,v| v=='simple' ? k : nil }.compact.join("|")}/i)
592
+ parse_simple(line)
593
+ when @parsed[:parsing_position].match(/#{PARSER_MAPPING.map{ |k,v| v=='oneline' ? k : nil }.compact.join("|")}/i)
594
+ parse_oneline(line)
595
+ when @parsed[:parsing_position].match(/#{PARSER_MAPPING.map{ |k,v| v=='multiline' ? k : nil }.compact.join("|")}/i)
596
+ parse_multiline(line)
597
+ when @parsed[:parsing_position].match(/#{PARSER_MAPPING.map{ |k,v| v=='cfgshow' ? k : nil }.compact.join("|")}/i)
598
+ parse_cfgshow(line)
599
+ when @parsed[:parsing_position].match(/#{PARSER_MAPPING.map{ |k,v| v=='ns' ? k : nil }.compact.join("|")}/i)
600
+ parse_ns(line)
601
+ when @parsed[:parsing_position].match(/#{PARSER_MAPPING.map{ |k,v| v=='trunk' ? k : nil }.compact.join("|")}/i)
602
+ parse_trunk(line)
603
+ end
604
+ end
605
+
606
+ # parser used to parse commands with 1 line output
607
+ def parse_oneline(line)
608
+ @parsed[@parsed[:parsing_position].to_sym]=line
609
+ end
610
+
611
+ # parser used to parse commands with multi lines output
612
+ def parse_multiline(line)
613
+ # switchstatusshow
614
+ case
615
+ when line.match(/^\s*[a-z]+.*:/i)
616
+ arr = line.split(":")
617
+ @parsed[arr[0].strip.gsub(/\s+/,"_").gsub(/([a-z])([A-Z])/,'\1_\2').downcase.to_sym]=arr[1..-1].join(":").strip
618
+ when line.match(/^There is no outstanding/) && @parsed[:parsing_position]=="cfgtransshow"
619
+ @parsed[:cfg_transaction] = {:id=>-1, :abortable=>nil, :msg => "no transaction"}
620
+ when line.match(/^Current transaction token is (.+)$/) && @parsed[:parsing_position]=="cfgtransshow"
621
+ @parsed[:cfg_transaction][:id] = $1
622
+ when line.match(/It is(.+)abortable$/) && @parsed[:parsing_position]=="cfgtransshow"
623
+ @parsed[:cfg_transaction][:abortable] = $1.match(/not/) ? false : true
624
+ else
625
+ #supportshow
626
+ @parsed[@parsed[:parsing_position].to_sym]||=""
627
+ @parsed[@parsed[:parsing_position].to_sym]+=line+"\n"
628
+ end
629
+ end
630
+
631
+ # parser for multiline output where each line is independent
632
+ def parse_simple(line)
633
+ case
634
+ #extra handling
635
+ when line.match(/^zoning/)
636
+ @parsed[:zoning_enabled]=line.match(/:\s+ON/) ? true : false
637
+ @parsed[:active_config]=@parsed[:zoning_enabled] ? line.gsub(/(.*\()|(\).*)/,'') : nil
638
+
639
+ when line.match(/^LS Attributes/)
640
+ @parsed[:ls_attributes]||={}
641
+ sub_ls_attrs=line.gsub(/(.*\[)|(\].*)/,'').split(",")
642
+ sub_ls_attrs.each do |attr|
643
+ if attr.match(/Address Mode/)
644
+ @parsed[:ls_attributes][:address_mode]=attr.gsub(/Address Mode/,'').strip
645
+ else
646
+ key , value = attr.split(":").map {|a| a.strip.downcase}
647
+ @parsed[:ls_attributes][key.gsub(/\s+/,"_").to_sym]=value
648
+ end
649
+ end
650
+ # port line in director
651
+ when line.match(/\s?\d{1,3}\s+\d{1,2}\s+\d{1,2}\s+[\dabcdef-]{6}/i)
652
+ l=line.split
653
+ @parsed[:ports]||=[]
654
+ @parsed[:ports]<<{:index => l[0].to_i,:slot=>l[1].to_i, :port=>l[2].to_i, :address=>l[3].strip, :media => l[4].strip,
655
+ :speed=>l[5].strip, :state=>l[6].strip, :proto=>l[7].strip, :comment=>l[8..-1].join(" ")}
656
+ # port line in san blade
657
+ when line.match(/\s?\d{1,3}\s+\d{1,2}\s+[\dabcdef]{6}/i)
658
+ l=line.split
659
+ @parsed[:ports]||=[]
660
+ @parsed[:ports]<<{:index => l[0].to_i, :port=>l[1].to_i, :address=>l[2].strip, :media => l[3].strip,
661
+ :speed=>l[4].strip, :state=>l[5].strip, :proto=>l[6].strip, :comment=>l[7..-1].join(" ")}
662
+ when line.match(/^Index|^=/)
663
+ ""
664
+ when line.match(/Created switches/)
665
+ @parsed[:created_switches]=line.split(":")[1].strip.split(" ").map {|l1| l1.to_i}
666
+ #fabrics
667
+ when line.match(/^\s*\d+:\s[a-f0-9]{6}/i)
668
+ l=line.split(" ")
669
+ @parsed[:fabric]||=[]
670
+ @parsed[:fabric] << {:domain_id => l[0].gsub(/:/,"").strip, :sid => l[1].strip, :wwn=>l[2].strip, :eth_ip=>l[3].strip, :fc_ip=>l[4].strip, :name=>l[5].strip.gsub(/\"|>/,""), :local => l[5].strip.match(/^>/) ? true : false }
671
+ when line.match(/^(zone|alias|cfg)\./)
672
+ l=line.gsub!(/^(zone|alias|cfg)\./,"").split(":")
673
+ @parsed[:find_results] << {:obj=>l.shift,:members=>l.join(":"), :type => $1.to_sym}
674
+ #agshow
675
+ when line.match(/^([a-f0-9]{2}:){7}[a-f0-9]{2}\s+\d+/i) && @parsed[:parsing_position]=="agshow"
676
+ l=line.split(" ")
677
+ @parsed[:ag]||=[]
678
+ @parsed[:ag] << {:wwn => l[0].strip, :ports => l[1].to_i, :eth_ip=>l[2].strip, :version=>l[3].strip, :local => l[4].strip.match(/^local/) ? true : false, :name => l[5].strip }
679
+ #islshow
680
+ # 1: 0-> 0 10:00:00:05:33:23:86:00 1 H2C04R065-U03-A sp: 8.000G bw: 64.000G TRUNK QOS
681
+ when line.match(/\d+:\s*\d+->.+sp:/)
682
+ l_tricky, l_simple = line.split("->")
683
+ l_t = l_tricky.split(":")
684
+
685
+ l = l_simple.split(" ")
686
+ @parsed[:isl_links]||=[]
687
+ @parsed[:isl_links] << {:id => l_t[0].to_i,
688
+ :source_port_index => l_t[1].to_i,
689
+ :destination_port_index => l[0].to_i,
690
+ :destination_switch_wwn => l[1].strip,
691
+ :destination_switch_domain => l[2].to_i,
692
+ :destination_switch_name => l[3].strip,
693
+ :speed => l[5].to_i,
694
+ :bandwidth => l[7].to_i,
695
+ :trunk => l[8..-1].include?("TRUNK"),
696
+ :qos => l[8..-1].include?("QOS"),
697
+ :cr_recov => l[8..-1].include?("CR_RECOV")
698
+ }
699
+ #default handling if it doesno match specialized match
700
+ # parse lines formated like
701
+ # param: value
702
+ else
703
+ if line.match(/^\s*[a-z]+.*:/i)
704
+ arr = line.split(":")
705
+ @parsed[str_to_key(arr[0])]=arr[1..-1].join(":").strip
706
+ end
707
+ end
708
+ end
709
+
710
+ # parser dedicated to cfgshow format
711
+ def parse_cfgshow(line)
712
+ # once effective_configuration is loaded we ignore rest
713
+ return if @parsed[:effective_configuration] and !@parsed[:effective_configuration][:cfg].nil?
714
+
715
+ # we use array stack to point to the object being parsed
716
+ @parsed[:pointer]||=[]
717
+ @parsed[:pointer].push @parsed[:base] if @parsed[:pointer].empty?
718
+ @parsed[:key]||=[]
719
+ @parsed[:last_key]||=nil
720
+ @parsed[:was_popped]||=false
721
+
722
+ if (matches=line.match(/^\s*([a-z]+\s*[a-z]+):(.*)/i))
723
+ key=str_to_key(matches[1])
724
+ after_colon=matches[2].strip
725
+
726
+ # superkey -> defined_configuration, efective_configuration, cfg, zone, alias
727
+ if after_colon.empty?
728
+ @parsed[key]||={}
729
+ # we have new superkey so we pop the old from pointer stack
730
+ # and we push the new in
731
+ @parsed[:pointer].pop if !@parsed[:pointer].empty?
732
+ @parsed[:pointer].push @parsed[key]
733
+ # subkey
734
+ else
735
+ # sometimes the previous key does not have any members (or they are filtered out)
736
+ # in that case the key and pointer were not poped below so we pop them now
737
+ if @parsed[:last_key]==key && !@parsed[:was_popped]
738
+ @parsed[:pointer].pop
739
+ @parsed[:key].pop
740
+ end
741
+ # we define the last subkey as hash
742
+ # and push the array to pointer stack
743
+ @parsed[:pointer].last[key]||={}
744
+ @parsed[:pointer].push @parsed[:pointer].last[key]
745
+
746
+ # first value is name of the key
747
+ # 2nd is list of key members
748
+ value=after_colon.split(" ")[0].strip
749
+ members=after_colon.split(" ").length>1 ? after_colon.split(" ")[1..-1].join(" ") : ""
750
+
751
+ @parsed[:key].push value
752
+
753
+ # adding value into the current pointer
754
+ @parsed[:pointer].last[value]||=[]
755
+
756
+ # assign members into the last pointer array item
757
+ if !members.empty?
758
+ @parsed[:pointer].last[value]||=[]
759
+ members.split(";").each do |member|
760
+ @parsed[:pointer].last[value]<<member.strip if !member.match(/^\s*$/)
761
+ end
762
+ # if the line does not end with ; next line starts with new key or super key
763
+ # hence we remove last pointer from stack as we are done with it
764
+ if !line.strip.match(/;$/)
765
+ @parsed[:pointer].pop
766
+ @parsed[:key].pop
767
+ @parsed[:was_popped]=true
768
+ else
769
+ @parsed[:was_popped]=false
770
+ end
771
+ else
772
+ @parsed[:was_popped]=false
773
+ end
774
+ end
775
+ @parsed[:last_key]=key
776
+ # this line defines another members of last pointer key array item
777
+ elsif line.match(/^\t/)
778
+ @parsed[:last_key]=nil
779
+ # sometimes it is not defined yet
780
+ # we push the members in
781
+ @parsed[:pointer].last[@parsed[:key].last]||=[]
782
+ line.split(";").each do |member|
783
+ @parsed[:pointer].last[@parsed[:key].last]<<member.strip if !member.match(/^\s*$/)
784
+ end
785
+ # if the line does not end with ; next line starts with new key or super key
786
+ # hence we remove last pointer from stack as we are done with it
787
+ if !line.strip.match(/;$/)
788
+ @parsed[:pointer].pop
789
+ @parsed[:key].pop
790
+ @parsed[:was_popped]=true
791
+ else
792
+ @parsed[:was_popped]=false
793
+ end
794
+ end
795
+ true
796
+ end
797
+
798
+ # name server parser
799
+ def parse_ns(line)
800
+ @parsed[:domain]||=0
801
+ @parsed[:key] = @parsed[:parsing_position]=="nsshow" ? :wwn_local : :wwn_remote
802
+ @parsed[@parsed[:key]]||=[]
803
+
804
+ case
805
+
806
+ # changing domain id
807
+ when line.match(/Switch entry for/)
808
+ @parsed[:domain]=line.split(" ").last.to_i
809
+
810
+ # new WWN
811
+ when line.match(/(^\s+N |^\s+U )/)
812
+ @parsed[@parsed[:key]].push Hash.new
813
+ @parsed[@parsed[:key]].last[:value]=line.split(";")[2]
814
+ @parsed[@parsed[:key]].last[:domain_id]=@parsed[:domain]
815
+ @parsed[@parsed[:key]].last[:symbol]||=""
816
+
817
+ # symbol
818
+ when line.match(/(PortSymb|NodeSymb)/)
819
+ @parsed[@parsed[:key]].last[:symbol]=line.split(":")[1..-1].join(":").strip
820
+
821
+ # detype
822
+ when line.match(/(Device type)/)
823
+ @parsed[@parsed[:key]].last[:dev_type]=line.split(":")[1].strip
824
+ when line.match(/(Port Index)/)
825
+ @parsed[@parsed[:key]].last[:port_index]=line.split(":")[1].strip.to_i
826
+ end
827
+
828
+ end
829
+
830
+ # trunk parser
831
+ def parse_trunk(line)
832
+ return if line.match(/No trunking/i)
833
+ @parsed[:trunk_links]||=[]
834
+ l_tricky, l_simple = line.split("->")
835
+ l_t = l_tricky.split(":")
836
+
837
+ l = l_simple.split(" ")
838
+ case
839
+ when line.match(/\d+:\s*\d+->/)
840
+ @parsed[:trunk_links]<<{:id => l_t[0].to_i, :members => [
841
+ {
842
+ :source_port_index => l_t[1].to_i,
843
+ :destination_port_index => l[0].to_i,
844
+ :destination_switch_wwn => l[1].strip,
845
+ :destination_switch_domain => l[2].to_i,
846
+ :deskew => l[4].to_i,
847
+ :master => l[5]=="MASTER"
848
+ }]
849
+ }
850
+ else
851
+ @parsed[:trunk_links].last[:members]<<{
852
+ :source_port_index => l_t[0].to_i,
853
+ :destination_port_index => l[0].to_i,
854
+ :destination_switch_wwn => l[1].strip,
855
+ :destination_switch_domain => l[2].to_i,
856
+ :deskew => l[4].to_i,
857
+ :master => l[5]=="MASTER"
858
+ }
859
+ end
860
+ end
861
+
862
+ # transforamtino method to define parsed key based on the string
863
+ def str_to_key(str)
864
+ str.strip.gsub(/\s+/,"_").gsub(/([a-z])([A-Z])/,'\1_\2').downcase.to_sym
865
+ end
866
+ end
867
+ end
868
+
869
+ class Switch
870
+ # class extending SshDevice::Error
871
+ class Error < self::Error
872
+ WRONG_FORMAT="Error: Incorrect format"
873
+ def self.incorrect(str) #:nodoc:
874
+ self.new("#{WRONG_FORMAT} - #{str}")
875
+ end
876
+
877
+ def self.unknown
878
+ self.new("Unknown attribute")
879
+ end
880
+ end
881
+ end
882
+ end; end