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,11 @@
1
+ class String #:nodoc:
2
+ def underscore
3
+ word = self.dup
4
+ word.gsub!(/::/, '/')
5
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
6
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
7
+ word.tr!("-", "_")
8
+ word.downcase!
9
+ word
10
+ end
11
+ end
@@ -0,0 +1,894 @@
1
+ # Brocade namespace
2
+ module Brocade
3
+ # SAN namespace
4
+ module SAN
5
+
6
+ # Provisioning namespace
7
+ module Provisioning
8
+ # holds replies strings as constants
9
+ module Replies
10
+ # from cfgtransshow
11
+ NO_ZONING_TRANSACTION = "There is no outstanding zoning transaction"
12
+ # from cfgtransabort
13
+ NO_TRANSACTION = "There is no outstanding transaction"
14
+ OUTSTANDING_TRANSACTION = "trans_abort: there is an outstanding transaction"
15
+ OPERATION_CANCELLED = "Operation cancelled"
16
+ NO_CHANGE="Nothing changed: nothing to save, returning"
17
+ FLASH_UPDT="Updating flash"
18
+ DOES_NOT_EXIST="does not exist"
19
+ RBAC_DENIED="RBAC permission denied"
20
+ CURRENT_TRANSACTION = "Current transaction token is"
21
+
22
+ end
23
+ # Agent class, used for provisioning tasks
24
+ #
25
+ # Under development - do not use
26
+ #
27
+ # TODO: need to properly test it live (partialy done)
28
+
29
+ class Agent < Brocade::SAN::Switch
30
+
31
+ # Creates a Brocade::SAN::Provisioning::Agent instance, tests a connection and test if user has enough rights for provisioning.
32
+ # Raises Error otherwise
33
+ #
34
+ # Checks as well if the switch is virtual fabric enabled since that defines the way it will be queried further.
35
+ def self.create(*params)
36
+ agent=new(*params)
37
+ end
38
+
39
+ # Queries the agent for ongoing transaction.
40
+ #
41
+ # Retruns Transaction instance or false if there is no transaction.
42
+ #
43
+ # Raises Error when transaction details could not be obtained
44
+ def get_transaction
45
+ trans=Transaction.new(cfg_transaction(true))
46
+ return false if trans.id==-1
47
+ raise Error.new(Error::TRANS_UNEXPECTED) if trans.id.nil?
48
+ trans
49
+ end
50
+
51
+ # Opens a provisioning transaction.
52
+ #
53
+ # Transaction runs in 1 session.
54
+ #
55
+ # Command allows transaction within transaction.
56
+ #
57
+ # cfg_save will be run at the end of the transaction is there was no error raised and this is the top-most transaction block.
58
+ #
59
+ # cfg_save wil not run as result of the transaction, you can however run it as last command in transaction
60
+
61
+ def transaction(opts={:auto_enable => false})
62
+ @transaction_level||=0
63
+ @transaction_level+=1
64
+ session do
65
+ raise_if_transaction_running
66
+ @transaction ||= true
67
+ raise Error.cannot_obtain_transaction_lock if not lock_transaction
68
+ begin
69
+ yield
70
+ # get_transaction in case cfgsave or cfgenable was run in transaction block
71
+ # if there is no transaction we do not need to run it
72
+ # if there is transaction but opend by someone else then t
73
+ cfg_save if @transaction_level==1 && get_transaction
74
+ rescue => e
75
+ abort_transaction
76
+ raise e
77
+ end
78
+ end
79
+ ensure
80
+ @transaction_level-=1
81
+ @transaction = nil if @transaction_level==0
82
+ end
83
+
84
+ # Creates alias and saves config
85
+ #
86
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns
87
+ #
88
+ # +al+ must be of class Alias. It will be created as it is with all the members of +al+ instance
89
+ #
90
+ # raises error if alias exists or different transaction is running or response is unexpected
91
+ #
92
+ # Returns Alias
93
+
94
+ def alias_create(al)
95
+ obj_create al, Alias
96
+ end
97
+
98
+ # Creates +zone+ and saves config.
99
+ #
100
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
101
+ #
102
+ # +zone+ must be of class Zone. It will be created as it is with all the +zone+ members.
103
+ #
104
+ # +members+ must exist. Exaception is members matching WWN and D,P notation.
105
+ #
106
+ # Raises error if +zone+ exists already or different transaction is running or response is unexpected.
107
+ #
108
+ # Returns Zone
109
+
110
+ def zone_create(zone)
111
+ obj_create zone, Zone
112
+ end
113
+
114
+ # Creates zone configuration and saves fabric configuration.
115
+ #
116
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
117
+ #
118
+ # +cfg+ must be of class ZoneConfiguration. It will be created as it is with all the members of +cfg+.
119
+ #
120
+ # Members must exist on the switch as zones.
121
+ #
122
+ # Raises error if +cfg already exists or some of the members do not exist or different transaction is running or response is unexpected.
123
+ #
124
+ # Returns ZoneConfiguration
125
+
126
+ def cfg_create(cfg)
127
+ obj_create cfg, ZoneConfiguration
128
+ end
129
+
130
+ # Deletes alias and saves config
131
+ #
132
+ # NOTE: This command checks if every member exists before creating the cfg, if the cfg has many members it will take lot of time, however cfg creation is on daily task.
133
+ #
134
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns
135
+ #
136
+ # +al+ must be of class Alias.
137
+ #
138
+ # raises error if alias does not exist or transaction is running or response is unexpected
139
+ #
140
+ # this is low level command and it only removes the alias
141
+ #
142
+ # it will not remove alias reference from zones, see Agent::#alias_purge that removes all
143
+ #
144
+ # Returns nil if deletion is successful
145
+
146
+ def alias_delete(al)
147
+ obj_delete al, Alias
148
+ end
149
+
150
+
151
+ # Removes +zone+ and saves config.
152
+ #
153
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
154
+ #
155
+ # +zone+ must be of class Zone.
156
+ #
157
+ # Raises error if +zone+ does not exist or different transaction is running or response is unexpected.
158
+ #
159
+ # This is low level command, it will remove the +zone+ record but not zone references from zone configurations, use #zone_purge for this purpose.
160
+ #
161
+ # Returns nil if deletion is successful
162
+
163
+ def zone_delete(zone)
164
+ obj_delete zone, Zone
165
+ end
166
+
167
+ # Removes zone configuration and saves fabric configuration.
168
+ #
169
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
170
+ #
171
+ # +cfg+ must be of class ZoneConfiguration. It will delete only zone config, keep the members intact.
172
+ #
173
+ # Raises error if zone configuration does not exist or different transaction is running or response is unexpected.
174
+ #
175
+ # Returns nil if deletion is successful
176
+
177
+ def cfg_delete(cfg)
178
+ obj_delete cfg, ZoneConfiguration
179
+ end
180
+
181
+ # Changes alias and saves config
182
+ #
183
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns
184
+ #
185
+ # +al+ must be of class Alias. alias with the Alias#name will be removed and it will be created anew as it is with all the members defined in +al+ instance
186
+ #
187
+ # raises error if alias does not exist or different transaction is running or response is unexpected
188
+ #
189
+ # this is shorthand method that instead of modifiyng the alias removes the alias and recreates it
190
+ #
191
+ # use alias_remove and alias_add if the above is not an option
192
+ #
193
+ # Returns Alias
194
+
195
+ def alias_change(al)
196
+ obj_change al, Alias
197
+ end
198
+
199
+
200
+ # Changes +zone+ and saves config.
201
+ #
202
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
203
+ #
204
+ # +zone+ must be of class Zone. +zone+ with the name will be removed and it will be created anew as it is with all the +zone+ members.
205
+ #
206
+ # Raises error if +member+ does not exist (WWN and D,P members are exception) or different transaction is running or response is unexpected.
207
+ #
208
+ # This is shorthand method that instead of modifiyng the zone removes the zone and then recreates it.
209
+ #
210
+ # Returns Zone
211
+
212
+ def zone_change(zone)
213
+ obj_change zone, Zone
214
+ end
215
+
216
+ # Removes member from alias +al+
217
+ #
218
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns
219
+ #
220
+ # +al+ must be instance of Alias and +member+ must minstance of Wwn or String matching Alias::MEMBER_RULE
221
+ #
222
+ # raises error if alias does not exist, +member+ does not match the rule or different transaction is running or response is unexpected
223
+ #
224
+ # Returns Alias or nil if the removed member was last one (it removes the Alias as well)
225
+
226
+ def alias_remove(al,member)
227
+ obj_remove(al,Alias,member)
228
+ end
229
+
230
+ # Remove +member+ from +zone+.
231
+ #
232
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
233
+ #
234
+ # +zone+ must be of class Zone and +member+ of class Alias or Wwn or String matching Alias::MEMBER_RULE
235
+ #
236
+ # Raises error if zone does not exist or different transaction is running or response is unexpected.
237
+ #
238
+ # Returns Zone or nil if the removed member was last one (it removes the Zone as well)
239
+
240
+ def zone_remove(zone,member)
241
+ obj_remove(zone,Zone,member)
242
+ end
243
+
244
+ # Remove zone +member+ from configuration +cfg+.
245
+ #
246
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
247
+ #
248
+ # +member+ must be of class Zone and +cfg+ of class ZoneConfiguration.
249
+ #
250
+ # Raises error if +cfg+ does not exist or different transaction is running or response is unexpected.
251
+ #
252
+ # Returns ZoneConfiguration or nil if the removed member was last one (it removes the ZoneConfiguration as well)
253
+
254
+ def cfg_remove(cfg,member)
255
+ obj_remove(cfg,ZoneConfiguration,member)
256
+ end
257
+
258
+ # Purges the Zone +zone+ completely, along with all references.
259
+ #
260
+ # +zone+ must me instance of Zone.
261
+ # The method first removes the zone from all zone configurations it is member of.
262
+ # Then deletes the zone and saves configuration.
263
+ #
264
+ # Should return nil if the +zone+ was purged.
265
+
266
+ def zone_purge(zone)
267
+ obj_purge(zone,Zone)
268
+ end
269
+
270
+ # Purges the Alias +al+ completely, along with all references.
271
+ #
272
+ # +al+ must be instance of Alias.
273
+ #
274
+ # The method first removes the alias from all zones it is member of.
275
+ # Then deletes the alias and saves configuration.
276
+ #
277
+ # Should return nil if the +al+ was purged.
278
+ def alias_purge(al)
279
+ obj_purge(al,Alias)
280
+ end
281
+
282
+ # Adds zone cofiguration member to zone configuration.
283
+ #
284
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns.
285
+ #
286
+ # +member+ must be instance of Zone and +cfg+ instance of class ZoneConfiguration.
287
+ #
288
+ # Zone and zone configuration must both exist.
289
+ #
290
+ # Raises error if +cfg+ or +member+ do not exist or different transaction is running or response is unexpected.
291
+ #
292
+ # Returns ZoneConfiguration
293
+
294
+ def cfg_add(cfg,member)
295
+ obj_add(cfg,ZoneConfiguration,member)
296
+ end
297
+
298
+ # Adds zone +member+ to +zone+.
299
+ #
300
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns
301
+ #
302
+ # +member+ must be of class Alias or Wwn or Strign matching Alias::MEMBER_RULE and +zone+ of class Zone.
303
+ #
304
+ # Zone and member must exist. Exception is if +member+ is Wwn.
305
+ #
306
+ # Raises error if zone or member does not exist or transaction is running or response is unexpected.
307
+ #
308
+ # Returns Zone
309
+
310
+ def zone_add(zone,member)
311
+ obj_add(zone,Zone,member)
312
+ end
313
+
314
+ # Adds alias member to alias
315
+ #
316
+ # If started outside transaction block it runs as single command transaction, otherwise it is not commited when the command returns
317
+ #
318
+ # +member+ must be instance of Wwn or String matching Alias::MEMBER_RULE (WWN or D,P)
319
+ #
320
+ # +al+ must be Alias and must exist in switch configuration, the +member+ does not need to, it has only to match the rule
321
+ #
322
+ # raises error if +al+ does not exist, member does not match the rule, dirrefent transaction is running or response is unexpected
323
+ #
324
+ # Returns Alias
325
+
326
+ def alias_add(al,member)
327
+ obj_add(al,Alias,member)
328
+ end
329
+
330
+ # Renames the +object+ to +newname+
331
+ #
332
+ # +object+ and +newname can be instance of Alias, Zone, ZoneConfiguration or String
333
+ #
334
+ # Returns renamed object or raises error
335
+ def rename_object(object,newname)
336
+ session do
337
+ response = script_mode do
338
+ query("zoneobjectrename \"#{object}\", \"#{newname}\"")
339
+ end
340
+
341
+ validate_and_save(response)
342
+ pull newname.to_s, :object => get_obj_type(object)
343
+ end
344
+ end
345
+
346
+ # Enables zoning configuration +cfg+.
347
+ #
348
+ # +cfg+ must be ZoneConfiguration instance, will only use it's name and ignore its members.
349
+ #
350
+ # Raises erros if cancelled, nothing saved or unexpected result, otherwise returns true.
351
+ def cfg_enable(cfg)
352
+ raise Agent::Error.new(Agent::Error::CFG_BAD) if !cfg.kind_of? ZoneConfiguration
353
+
354
+ response = interactive_mode do
355
+ query("cfgenable \"#{cfg}\"","y")
356
+ end
357
+
358
+ case
359
+ when response.data.match(/#{Replies::OPERATION_CANCELLED}/)
360
+ raise Agent::Error.new(Agent::Error::CFGSAVE_CANC)
361
+ when response.data.match(/#{Replies::FLASH_UPDT}/)
362
+ # if any transaction was in progres it is closed now
363
+ @transaction=nil
364
+ return true
365
+ else
366
+ raise Agent::Error.new(response.data)
367
+ end
368
+ end
369
+
370
+ # Check if there is different zoning transaction in progress.
371
+ #
372
+ # Returns +true+ or +false+.
373
+ #
374
+ # if started in within transactino block it will simply return false.
375
+ def check_for_running_transaction
376
+ # ignore this command when within transaction block
377
+ # as this command is started at the begining of transaction
378
+ return false if @transaction
379
+ response = script_mode do
380
+ query("cfgtransshow")
381
+ end
382
+ response.data.match(/#{Replies::NO_ZONING_TRANSACTION}/) ? false : true
383
+ end
384
+
385
+ # Aborts transaction
386
+
387
+ # returns
388
+ # +true+ if transaction was aborted and
389
+ # +false+ if there was no transaction to abort and
390
+ # raises error if it is not owner of the transaction
391
+ def abort_transaction
392
+ response = script_mode do
393
+ query("cfgtransabort")
394
+ end
395
+ #empty response is ok
396
+ case
397
+ when response.data.split("\n").size==1
398
+ return true
399
+ when response.data.match(/#{Replies::NO_TRANSACTION}/)
400
+ return false
401
+ when response.data.match(/#{Replies::OUTSTANDING_TRANSACTION}/)
402
+ raise Agent::Error.new(Agent::Error::TRANS_NOTOWNER)
403
+ else
404
+ error = response.data.split("\n").delete_if {|item| item.match(/^#{@prompt}/)}.join("\n")
405
+ raise Agent::Error.new(error)
406
+ end
407
+ end
408
+
409
+ # Checks if object exists in fabric. Finds even objects not stored in memory but as well object created in open transaction.
410
+ #
411
+ # +str+ is name of the object
412
+ #
413
+ # +opts+
414
+ # :object => :zone, :alias, :cfg
415
+ #
416
+ # if no :object is specified :zone is used by default
417
+ def exist?(str,opts={})
418
+ obj = !opts[:object].nil? && [:zone,:alias,:cfg].include?(opts[:object]) ? opts[:object] : :zone
419
+
420
+ response = script_mode do
421
+ query("#{show_cmd(obj)} \"#{str}\"")
422
+ end
423
+ return response.data.match(/#{Replies::DOES_NOT_EXIST}/) ? false : true
424
+ end
425
+
426
+ # Pulls object matching +str+ from switch configuration, even objects not yet saved
427
+ #
428
+ # Returns the object or nil
429
+ #
430
+ # options
431
+ # [:object] :zone (default)
432
+ # :alias
433
+ # :cfg
434
+ # :all
435
+ def pull(str,opts={})
436
+ obj = !opts[:object].nil? && [:zone,:alias,:cfg, :all].include?(opts[:object]) ? opts[:object] : :zone
437
+
438
+ response=nil
439
+
440
+ session do
441
+ if obj == :all
442
+ [:cfg, :zone, :alias].each do |type|
443
+ obj = type
444
+ response = script_mode do
445
+ query("#{show_cmd(type)} \"#{str}\"")
446
+ end
447
+ response.parse
448
+ break if !response.parsed[:base].nil?
449
+ end
450
+ else
451
+ response = script_mode do
452
+ query("#{show_cmd(obj)} \"#{str}\"")
453
+ end
454
+ response.parse
455
+ end
456
+ end
457
+ return nil if response.parsed[:base].nil?
458
+
459
+ object = response.parsed[:base][obj] #hash with single key
460
+ name = object.keys[0]
461
+ case obj
462
+ when :zone
463
+ item = Zone.new(name)
464
+ when :alias
465
+ item = Alias.new(name)
466
+ when :cfg
467
+ item = ZoneConfiguration.new(name)
468
+ end
469
+ object[name].each do |member|
470
+ item.add_member(member)
471
+ end
472
+
473
+ return response.data.match(/#{Replies::DOES_NOT_EXIST}/) ? nil : item
474
+ end
475
+
476
+ private_class_method :new
477
+
478
+ private
479
+
480
+ def initialize(*params) #:nodoc:
481
+ super(*params)
482
+ raise Error.new(Error::BAD_USER) if not verify
483
+ end
484
+
485
+ def show_cmd(obj_sym)
486
+ cmd = case obj_sym
487
+ when :zone
488
+ "zoneshow"
489
+ when :alias
490
+ "alishow"
491
+ when :cfg
492
+ "cfgshow"
493
+ end
494
+ end
495
+ # verifies if the specified user can change zoning configuration
496
+ #
497
+ # returns true if yes, false if no
498
+ def verify
499
+ response = script_mode do
500
+ query("configshow | grep RBAC")
501
+ end
502
+ response.data.match(/#{Replies::RBAC_DENIED}/) ? false : true
503
+ end
504
+
505
+ # saves if the response if empty and if so it save config, otherwise raises error
506
+ def validate_and_save(response)
507
+ #true response is ok
508
+ if response.data.split("\n").size==1
509
+ # will not save if part of transaction
510
+ cfg_save if @transaction.nil?
511
+ else
512
+ error = response.data.split("\n").delete_if {|item| item.match(/^#{@prompt}/)}.join("\n")
513
+ raise Agent::Error.new(error)
514
+ end
515
+ true
516
+ end
517
+
518
+ # Obtains transaction lock and stores that transaction in @transaction
519
+ #
520
+ # Returns +false+ otherwise
521
+ def lock_transaction
522
+ begin
523
+ return true if @transaction.kind_of? Transaction
524
+ if get_transaction==false
525
+ lock_alias=Alias.new("brocade_san_lock")
526
+ lock_alias.add_member("50:00:00:00:00:00:00:00")
527
+ alias_create lock_alias
528
+ alias_delete lock_alias
529
+ @transaction = get_transaction
530
+ true
531
+ else
532
+ false
533
+ end
534
+ rescue
535
+ return false
536
+ end
537
+ end
538
+
539
+ # Saves config
540
+ # Raises erros if cancelled, nothing saved or unexpected result, otherwise returns true
541
+ # this method will be called by adhoc methods or by finishing transaction, should not be called directly
542
+ # because it does not check for running transaction, it expect the caller to take care of that
543
+ def cfg_save
544
+ response = interactive_mode do
545
+ query("cfgsave","y")
546
+ end
547
+
548
+ case
549
+ when response.data.match(/#{Replies::OPERATION_CANCELLED}/)
550
+ raise Agent::Error.new(Agent::Error::CFGSAVE_CANC)
551
+ when response.data.match(/#{Replies::NO_CHANGE}/)
552
+ raise Agent::Error.new(Agent::Error::CFGSAVE_NOCHANGE)
553
+ when response.data.match(/#{Replies::FLASH_UPDT}/)
554
+ @transaction=nil
555
+ return true
556
+ else
557
+ raise Agent::Error.new(response.data)
558
+ end
559
+ end
560
+
561
+ # pulls given object from switch
562
+ def obj_pull(obj,klass)
563
+ pull obj.to_s, :object=> get_obj_type(obj)
564
+ end
565
+
566
+ # creates the appropriate object
567
+ def obj_create(obj,klass)
568
+ obj_manipulate obj,klass do |man|
569
+ man.in_session do
570
+ raise_if_members_do_not_exist obj
571
+ get_response_and_validate_for(obj,klass,"create",nil)
572
+ end
573
+ end
574
+ end
575
+
576
+
577
+ # deletes the appropriate object
578
+ def obj_delete(obj,klass)
579
+ obj_manipulate obj,klass do |man|
580
+ man.in_session do
581
+ get_response_and_validate_for(obj,klass,"delete",nil)
582
+ end
583
+ end
584
+ end
585
+
586
+ # changes the appropriate object
587
+ def obj_change(obj,klass)
588
+ #raise_if_obj_is_not obj , klass
589
+ session do
590
+ # raise_if_transaction_running
591
+ cmd = klass.to_s.split("::").last.downcase
592
+ transaction do
593
+ self.send("#{cmd}_delete", obj)
594
+ self.send("#{cmd}_create", obj)
595
+ end
596
+ obj_pull obj, klass
597
+ end
598
+ end
599
+
600
+ # removes member from object
601
+ def obj_remove(obj,klass,member)
602
+ obj_manipulate obj,klass do |man|
603
+ man.checks do
604
+ raise_if_member_is_not_valid_for klass, member
605
+ end
606
+ man.in_session do
607
+ raise_if_obj_do_not_exist obj
608
+ get_response_and_validate_for(obj,klass,"remove",member)
609
+ end
610
+ end
611
+ end
612
+
613
+ # adds member to object
614
+ def obj_add(obj,klass,member)
615
+ obj_manipulate obj,klass do |man|
616
+ man.checks do
617
+ raise_if_member_is_not_valid_for klass, member
618
+ end
619
+ man.in_session do
620
+ raise_if_obj_do_not_exist obj
621
+ raise_if_obj_do_not_exist member
622
+ get_response_and_validate_for(obj,klass,"add",member)
623
+ end
624
+ end
625
+ end
626
+
627
+
628
+ # purges given object compltely
629
+
630
+ def obj_purge(obj,klass)
631
+ obj_manipulate obj,klass do |man|
632
+ man.in_session do
633
+ parents = find_by_member(obj.name, :find_mode => :exact, :transaction => true)
634
+ transaction do
635
+ parents.each do |par|
636
+ obj_remove par, par.class, obj
637
+ end
638
+ obj_delete obj, obj.class
639
+ end
640
+ end
641
+ end
642
+ end
643
+
644
+ # raises error if member is not valid for given class
645
+
646
+ def raise_if_member_is_not_valid_for(klass,member)
647
+ case klass.to_s
648
+ when "Brocade::SAN::ZoneConfiguration"
649
+ raise Agent::Error.new(Agent::Error::ZONE_BAD) if !member.kind_of? Zone
650
+ when "Brocade::SAN::Zone"
651
+ raise Agent::Error.new(Agent::Error::MEMBER_BAD) if !member.kind_of?(Alias) && !member.kind_of?(Wwn) && !member.match(/^#{Alias::MEMBER_RULE}$/)
652
+ when "Brocade::SAN::Alias"
653
+ case member.class.to_s
654
+ when "Brocade::SAN::Wwn"
655
+ when "String"
656
+ raise Agent::Error.new(Agent::Error::ALIAS_MEMBER_BAD) if !member.match(/^#{Alias::MEMBER_RULE}$/)
657
+ else
658
+ raise Agent::Error.new(Agent::Error::ALIAS_MEMBER_BAD)
659
+ end
660
+ end
661
+ end
662
+
663
+ # raises proper error if obj is not of class klass
664
+ def raise_if_obj_is_not(obj,klass)
665
+ case klass.to_s
666
+ when "Brocade::SAN::ZoneConfiguration"
667
+ raise Agent::Error.new(Agent::Error::CFG_BAD) if !obj.kind_of? klass
668
+ when "Brocade::SAN::Zone"
669
+ raise Agent::Error.new(Agent::Error::ZONE_BAD) if !obj.kind_of? klass
670
+ when "Brocade::SAN::Alias"
671
+ raise Agent::Error.new(Agent::Error::ALIAS_BAD) if !obj.kind_of? klass
672
+ end
673
+ end
674
+
675
+ # raises error if another transcation is already running
676
+ def raise_if_transaction_running
677
+ raise Agent::Error.new(Agent::Error::TRNS_IPRG) if check_for_running_transaction
678
+ end
679
+
680
+ # raise error if object does not actually exist in configuration
681
+ def raise_if_obj_do_not_exist(obj)
682
+ klass = obj.class.to_s
683
+ case klass
684
+ when "Brocade::SAN::ZoneConfiguration"
685
+ raise Agent::Error.does_not_exist("Config #{obj.name}") if !exist?(obj.name,:object => :cfg)
686
+ when "Brocade::SAN::Zone"
687
+ raise Agent::Error.does_not_exist("Zone #{obj.name}") if !exist?(obj.name,:object => :zone)
688
+ when "Brocade::SAN::Alias"
689
+ raise Agent::Error.does_not_exist("Alias #{obj.name}") if !exist?(obj.name,:object => :alias)
690
+ when "String" # Wwn and D,P notation for zone and alias members
691
+ raise Agent::Error.new(Agent::Error::MEMBER_BAD_2) if !obj.match(/^#{Alias::MEMBER_RULE}$/)
692
+ end
693
+ end
694
+
695
+ # raise error if members of obj do not actually exists in configuration
696
+ def raise_if_members_do_not_exist(obj)
697
+ klass = obj.class.to_s
698
+ raise Agent::Error.members_empty if obj.members.empty?
699
+ # pull all zones
700
+ if klass == "Brocade::SAN::ZoneConfiguration"
701
+ zones(true)
702
+ end
703
+ obj.members.each do |member|
704
+ case klass
705
+ when "Brocade::SAN::ZoneConfiguration"
706
+ # exist? is slow for for big configurations
707
+ #raise Agent::Error.does_not_exist("Zone #{member}") if !exist?(member,:object => :zone)
708
+ raise Agent::Error.does_not_exist("Zone #{member}") if zones.select {|z|z.name==member}.empty?
709
+ when "Brocade::SAN::Zone"
710
+ # no need to check if WWNs exist
711
+ next if member.match(/#{Alias::MEMBER_RULE}/i)
712
+ # check if other members => only aliases exist
713
+ raise Agent::Error.does_not_exist("Alias #{member}") if !exist?(member,:object => :alias)
714
+ # no checks for Alias required - the members do not need to exists for Alias
715
+ end
716
+ end
717
+ end
718
+
719
+ #return proper cmd to use
720
+ def get_cmd(klass,task)
721
+ cmd = case klass.to_s
722
+ when "Brocade::SAN::ZoneConfiguration"
723
+ "cfg#{task}"
724
+ when "Brocade::SAN::Zone"
725
+ "zone#{task}"
726
+ when "Brocade::SAN::Alias"
727
+ "ali#{task}"
728
+ end
729
+ end
730
+
731
+ #return proper cmd to use
732
+ def get_obj_type(obj)
733
+ type = case obj.class.to_s
734
+ when "Brocade::SAN::ZoneConfiguration"
735
+ :cfg
736
+ when "Brocade::SAN::Zone"
737
+ :zone
738
+ when "Brocade::SAN::Alias"
739
+ :alias
740
+ when "String"
741
+ :all
742
+ end
743
+ end
744
+
745
+ #return proper member part of provisioning query command for given word, obj and member
746
+ def get_member_part(obj, word, member=nil)
747
+ case word
748
+ when "delete"
749
+ ""
750
+ when "create"
751
+ ", \"#{obj.members.join(";")}\""
752
+ when "remove","add"
753
+ ", \"#{member}\""
754
+ else
755
+ ""
756
+ end
757
+ end
758
+
759
+ # manipulates object
760
+ # this is private method that is for use only in another private methods
761
+ # follows this template for given obj,klass and member arguments
762
+ # raise_if_obj_is_not obj , klass
763
+ # checks
764
+ # session do
765
+ # raise_if_transaction_running
766
+ # in_sesssion
767
+ # end
768
+ # the checks and in_session blocks can be defined on the manipulator object yielded to the block
769
+ # Example:
770
+ # obj_manipulate obj,klass,member do |man|
771
+ # man.checks do
772
+ # #puts checks here
773
+ # end
774
+ # man.in_session do
775
+ # #puts session block here
776
+ # end
777
+ # end
778
+
779
+ def obj_manipulate(*args,&block)
780
+ obj,klass,member = args
781
+ manipulator = Brocade::SAN::Provisioning::ObjectManipulator.new
782
+ # block can define checks and in_session handlers
783
+ yield manipulator
784
+ # standard template follows
785
+ # check if class is proper
786
+ raise_if_obj_is_not obj , klass
787
+ # run run check block - should contain checks not requiring session -> switch access
788
+ manipulator.run_checks
789
+ # starting session
790
+ session do
791
+ # standard transaction check
792
+ raise_if_transaction_running
793
+ # run block defined in in_session block
794
+ manipulator.run_session
795
+ # returns the fresh object
796
+ obj_pull(obj,klass)
797
+ end
798
+ end
799
+
800
+ # gets response and validates
801
+ def get_response_and_validate_for(obj,klass,word,member=nil)
802
+ # gets proper command for given word and klass
803
+ cmd_part = get_cmd klass, word
804
+ object_member_part = get_member_part obj, word, member
805
+ response = script_mode do
806
+ query("#{cmd_part} \"#{obj}\"#{object_member_part}")
807
+ end
808
+ validate_and_save(response)
809
+ end
810
+
811
+ end
812
+
813
+ class Agent
814
+ # Class that holds Agent::Error messages
815
+ class Error < self::Error
816
+ BAD_USER = "User has insufficient rights to do provisioning"
817
+ TRNS_IPRG = "Another zoning transaction is already in progress"
818
+ ALIAS_BAD = "Parameter should be of Alias class"
819
+ MEMBER_BAD = "Parameter should be of Alias or Wwn class"
820
+ MEMBER_BAD_2 = "Parameter should be of Wwn or D,P notation"
821
+ ZONE_BAD = "Parameter should be of Zone class"
822
+ CFG_BAD = "Parameter should be of ZoneConfiguration class"
823
+ CFGSAVE_CANC = "cfgsave was cancelled"
824
+ CFGSAVE_NOCHANGE = "cfgsave: nothing changed, nothing to save"
825
+ TRANS_NOTOWNER = "Cannot abort transaction you are not owner of"
826
+ OBJ_NOTEXIST = "does not exist"
827
+ MEMBERS_EMPTY = "Cannot create. Object has no members"
828
+ ALIAS_MEMBER_BAD = "Not correct alias member"
829
+ TRANS_UNEXPECTED="Cannot get transaction details"
830
+ TRANS_UNLOCKABLE="Cannot get transaction lock"
831
+
832
+ # used to raise modifyable "does not exist message"
833
+ def self.does_not_exist(str) #:nodoc:
834
+ new("#{str} #{OBJ_NOTEXIST}")
835
+ end
836
+
837
+ def self.members_empty #:nodoc:
838
+ new(MEMBERS_EMPTY)
839
+ end
840
+
841
+ def self.cannot_obtain_transaction_lock
842
+ new(TRANS_UNLOCKABLE)
843
+ end
844
+ end
845
+
846
+ # simple Transaction class that processes Switch#cfg_transaction output
847
+ #
848
+ # Value of +id+ means:
849
+ # [:id] -1 - no transaction exist
850
+ #
851
+ # nil - unexpected result
852
+ #
853
+ # string - string representation of current transaction id
854
+
855
+ class Transaction
856
+ attr_writer :abortable
857
+ # Specifies the id of Transaction
858
+ attr_accessor :id
859
+
860
+ # takes hash returned by cfg_transaction command
861
+ def initialize(opts={}) #:nodoc:
862
+ # empty hash means unexpected result of cfg_transaction
863
+ if [:id, :abortable].all? {|k| opts.key? k}
864
+ @id = opts[:id]
865
+ @abortable = opts[:abortable]
866
+ end
867
+ end
868
+
869
+ # Returns abortable flag
870
+ def abortable?
871
+ @abortable
872
+ end
873
+ end
874
+ end
875
+
876
+ # manipulator object used on #obj_manipulate to insert block of commands into standard obj manipulation template, see the #obj_manipulate
877
+ class ObjectManipulator #:nodoc:
878
+ def checks(&block)
879
+ @checks = block
880
+ end
881
+
882
+ def run_checks
883
+ @checks.call if @checks
884
+ end
885
+
886
+ def in_session(&block)
887
+ @in_session = block
888
+ end
889
+
890
+ def run_session
891
+ @in_session.call if @in_session
892
+ end
893
+ end
894
+ end; end; end