-
Notifications
You must be signed in to change notification settings - Fork 0
/
chassis_restore.py
1546 lines (1402 loc) · 75 KB
/
chassis_restore.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2023 Consoli Solutions, LLC. All rights reserved.
#
# NOT BROADCOM SUPPORTED
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may also obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
:mod:`chassis_restore` - Restores a switch to a previously captured chassis DB
Version Control::
+-----------+---------------+-----------------------------------------------------------------------------------+
| Version | Last Edit | Description |
+===========+===============+===================================================================================+
| 4.0.0 | 04 Aug 2023 | Re-Launch |
+-----------+---------------+-----------------------------------------------------------------------------------+
"""
__author__ = 'Jack Consoli'
__copyright__ = 'Copyright 2023 Consoli Solutions, LLC'
__date__ = '04 August 2023'
__license__ = 'Apache License, Version 2.0'
__email__ = '[email protected]'
__maintainer__ = 'Jack Consoli'
__status__ = 'Released'
__version__ = '4.0.0'
import argparse
import collections
import pprint
import sys
import datetime
import copy
import base64
import brcdapi.log as brcdapi_log
import brcdapi.fos_auth as fos_auth
import brcdapi.brcdapi_rest as brcdapi_rest
import brcdapi.gen_util as gen_util
import brcdapi.util as brcdapi_util
import brcdapi.file as brcdapi_file
import brcdapi.switch as brcdapi_switch
import brcdapi.port as brcdapi_port
import brcdapi.zone as brcdapi_zone
import brcddb.brcddb_project as brcddb_project
import brcddb.brcddb_common as brcddb_common
import brcddb.classes.util as class_util
import brcddb.api.interface as api_int
import brcddb.brcddb_chassis as brcddb_chassis
import brcddb.brcddb_switch as brcddb_switch
import brcddb.api.zone as brcddb_zone
_DOC_STRING = False # Should always be False. Prohibits any code execution. Only useful for building documentation
_DEBUG = False # When True, use _DEBUG_xxx below instead of parameters passed from the command line.
_DEBUG_ip = 'xx.xxx.x.xxx'
_DEBUG_id = 'admin'
_DEBUG_pw = 'password'
_DEBUG_s = 'self'
_DEBUG_i = 'test/sw_139'
_DEBUG_eh = False
_DEBUG_wwn = 'xx:xx:xx:xx:xx:xx:xx:xx'
_DEBUG_fm = None
_DEBUG_scan = False
_DEBUG_p = '*'
_DEBUG_e = True
_DEBUG_sup = False
_DEBUG_d = False
_DEBUG_log = '_logs'
_DEBUG_nl = False
_temp_password = 'Passw0rd!'
_basic_capture_kpi_l = [
# 'running/brocade-fabric/fabric-switch', Done automatically in brcddb.api.interface.get_chassis()
'running/brocade-fibrechannel-switch/fibrechannel-switch',
'running/brocade-fibrechannel-configuration/switch-configuration',
'running/brocade-fibrechannel-configuration/port-configuration',
'running/brocade-interface/fibrechannel',
]
_full_capture_l = _basic_capture_kpi_l.copy()
_restore_parameters = dict(
m='Mandatory data capture. Automatically included whether specified or not.',
c='Chassis and switch. All chassis and logical settings except those specified with "vf" and "s"',
# maps='MAPS. All MAPS custom rules.',
vf='Virtual Fabric. Delete and restore all logical switches.',
s='Security. Limited to creating non-default users only',
# l='Logging. Does nothing in this release',
# fcr='Fibre Chnnel Routing. Does nothing in this release',
# isl='ISL. Does nothing in this release',
# ficon='FICON. Does nothing in this release',
# fcip='Fibre Channel Over IP. Does nothing in this release',
p='Port. All port configurations',
z='Zoning. Restores the zoning configurations for each fabric',
)
_eh = [
'',
'Overview',
'________',
'',
'The typical use for this module is for modifying a chassis for:',
'',
' * A service action replacement',
' * An upgrade',
' * Reallocation of a SAN resource',
' * Template',
' * It may be useful to configure chassis and switches to use as',
' a template and make further modifications via other modules.',
'',
'Nomenclature:',
'',
' Restore Refers to the reference chassis (chassis being',
' restored from).',
'',
' Target Refers to the chassis being rebuilt.',
'',
'The intended purpose of this script is primarily to be used as a template',
'either for deploying new or reallocating resources. It may be desirable',
'therefore to only update certain configuration parameters, certain FIDS,',
'or to map FIDs on the restore chassis to different FIDS on the target',
'chassis. The options -p and -fm are used for this purpose.',
'',
'A capture must be performed from the restore chassis prior to using',
'this module. It is not necessary to collect all data; however, keep in',
'mind that what ever data wasn\'t collected can\'t be restored.',
'',
'Operation',
'_________',
'',
'A best effort attempt is made to restore the chassis:',
'',
' * All errors are reported but are otherwise ignored. The intent is to',
' restore as much as possible.',
' * Ports from the previous data capture that do not exist in the chassis',
' are ignored',
' * Ports that do not exist in the previous data are left in the default switch',
'',
'The general process is:',
'',
'1. Read the input file, -i, for the restore chassis',
'2. A GET request is issued for each URI associated with \'k\' in the',
' action list.',
'3. The action list is then processed in order. Typical actions are to',
' modify values in the target chassis that differ from the restore chassis',
' but action methods can be written to do anything. For a discussion of',
' pre-written action methods, search for _action_l.',
'',
'All non-default users are re-created with default password:',
_temp_password,
'',
'FID MAP, -fm',
'__________________________',
'',
'By default, the actions specified with the -p option are applied to all FIDs.',
'The operand is a CSV list. A colon, ":", is used to map FIDs. Regardless of',
'the order specified, non-mapped FIDs are applied first.',
'',
'Example: Restore any FID except FID 128.',
'',
'-fm 1-127',
'',
'Example: Restore FIDS 1-10 and FID 20. Restore FIDs 3 and 4 to FIDs 13 and 14'
'',
'-fm 1-10,20,3:13,4:14',
'',
'WARNING: When -vf is specified, all logical switches in the target chassis',
'are deleted regardless of what FIDs are specified. Only the FIDs specified',
'by -fm are created.',
'',
'Exceptions and Other Notes',
'__________________________',
'',
' * Existing RBAC users on the target chassis are not modified.',
' * If enable, -e, was specified, only those switches and ports that were',
' enabled on the restore chassis are enabled on the target chassis.',
' * All switch and port configurations are completed before enabling them.',
' * All errors are reported but are otherwise ignored. The intent is to',
' restore as much as possible.',
' * Ports from the previous data capture that do not exist in the target',
' chassis are ignored',
' * Ports that do not exist in the previous data are left in the default switch',
' * Some actions require the affected switch or port to be disabled. If vf was',
' not specified, you may need to disable ports in the target switch.'
'',
'Parameter, -p, options:',
'__________________________',
'',
'* All (full restore)',
]
for _key, _buf in _restore_parameters.items():
_eh.append(gen_util.pad_string(str(_key), 8, ' ', append=True) + _buf)
_eh.append('')
###################################################################
#
# Support Methods for Branch Actions
#
###################################################################
def _send_request(session, http_method, key, content, fid=None):
"""Generic method to send requests used by the Branch Actions
:param session: Session object, or list of session objects, returned from brcdapi.fos_auth.login()
:type session: dict
:param http_method: HTTP method. PATCH, POST, ...
:type http_method: str
:param key: Reference key into _control_d in slash notation.
:type key: str
:param content: Content to send to switch. If content is None or empty, nothing is sent to the switch
:type content: None, dict, list
:return: List of error messages as str encountered.
:rtype: list
"""
el = list()
if content is None or len(content) == 0:
return el
uri = 'running/' + key
obj = brcdapi_rest.send_request(session, uri, http_method, {key.split('/').pop(): content}, fid=fid)
if fos_auth.is_error(obj):
el.extend(['Error updating chassis URI:', ' ' + uri, 'FOS error:', fos_auth.formatted_error_msg(obj)])
return el
def _enable_disable_chassis(session, state, e_text):
"""Disables or Enables the chassis
:param session: Session object, or list of session objects, returned from brcdapi.fos_auth.login()
:type session: dict
:param state: If Ture, enable the chassis. Otherwise, disable the chassis
:type state: bool
:param e_text: Text to append to error messages
:type e_text: str
:return: List of error messages
:rtype: list
"""
el = list()
obj = _send_request(session, 'PATCH', 'brocade-chassis/chassis', dict(chassis={'chassis-enabled': state}))
if fos_auth.is_error(obj):
el.extend(['Error updating chassis URI: brocade-chassis/chassis' + e_text,
'FOS error:',
fos_auth.formatted_error_msg(obj)])
return el
def _fmt_errors(to_format, full=False):
"""Formats the standard inputs to the branch actions for error reporting.
:param to_format: List of objects to format.
:type to_format: list
:return: Formatted text in list entries
:rtype: list
"""
rl = list()
for obj in gen_util.convert_to_list(to_format):
rl.extend(class_util.format_obj(obj, full=full))
return rl
def _patch_content(r_obj, t_obj, d):
""" Builds a dictionary with just the differences between the restore object and target object
:param r_obj: Restore object
:type r_obj: brcddb.classes.chassis.ChassisObj, brcddb.classes.switch.SwitchObj
:param t_obj: Restore object
:type t_obj: brcddb.classes.chassis.ChassisObj, brcddb.classes.switch.SwitchObj
:param d: Actions to take. This is a dictionary from _control_d
:type d: dict
:return: List of error messages as str encountered.
:rtype: list
:return content_d: Dictionary differences
:rtype content_d: dict()
"""
el = list()
try:
rd, content_d, key, rw_d = dict(), dict(), d['k'], d.get('rw')
# Validate the input
for obj in (r_obj, t_obj):
obj_type = str(class_util.get_simple_class_type(obj))
if obj_type not in ('ChassisObj', 'SwitchObj'):
el.append('Invalid object type: ' + obj_type + '. ' + d['e'])
if type(r_obj) != type(t_obj):
el.append('Object types do not match. ' + d['e'])
if key is None:
el.append('Missing k in dictionary for ' + d['e'])
if not isinstance(rw_d, dict):
el.append('rw missing in dictionary for ' + d['e'])
# Figure out the differences
if len(el) == 0:
for k, method in rw_d.items():
r_val, t_val = method(r_obj, d['k'], k), method(t_obj, d['k'], k)
if r_val is not None:
if t_val is None or type(t_val) != type(r_val) or r_val != t_val:
content_d.update({k: r_val})
# Figure out what the return content should be
if len(content_d) > 0:
rd.update({key.split('/').pop(): content_d})
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, obj, r_obj, t_obj, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el, rd
def _post_content(r_obj, d):
"""Creates a copy of the restore object with just values in d['rw']
:param r_obj: Restore object
:type r_obj: brcddb.classes.chassis.ChassisObj, brcddb.classes.switch.SwitchObj
:param d: Actions to take. This is a dictionary from _control_d
:type d: dict
:return: List of error messages as str encountered.
:rtype: list
:return content_d: Dictionary differences
:rtype content_d: dict()
"""
el = list()
try:
rd, content_d, key, rw_d = dict(), dict(), d['k'], d.get('rw')
# Validate the input
obj_type = str(class_util.get_simple_class_type(r_obj))
if obj_type not in ('ChassisObj', 'SwitchObj'):
el.append('Invalid object type: ' + obj_type + '. ' + d['e'])
if key is None:
el.append('Missing k in dictionary for ' + d['e'])
if not isinstance(rw_d, dict):
el.append('rw missing in dictionary for ' + d['e'])
# Figure out what the return content should be
for key in [k for k, v in rw_d.items() if v]:
rd.update({key.split('/').pop(): _conv_lookup_act(r_obj, key)})
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, r_obj, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el, rd
###################################################################
#
# Leaf Actions (see rw in _action_l)
#
###################################################################
def _conv_none_act(obj, key, sub_key=None):
""" Always returns None
:param obj: chassis or switch object
:type obj: brcddb.classes.chassis.ChassisObj, brcddb.classes.switch.SwitchObj
:param key: The branch Key in obj to lookup. If just one key, use this and make sub_key None
:type key: str
:param sub_key: The leaf name
:type sub_key: str, None
:return: Value in obj associated with key
:rtype: None, str, float, bool, int, list, dict
"""
return None
def _conv_lookup_act(obj, key, sub_key=None):
"""Simple lookup. See _conv_none_act() for parameters"""
try:
full_key = key if sub_key is None else key + '/' + sub_key
return copy.deepcopy(gen_util.get_key_val(obj, full_key))
except BaseException as e:
brcdapi_log.exception(['Key: ' + str(key), 'sub_key: ' + str(sub_key)] + _fmt_errors(obj, full=True), echo=True)
return None
def _conv_ficon_lookup_act(obj, key, sub_key=None):
"""Returns None of any parameter with a FICON switch that must not be set. See _conv_none_act() for parameters"""
try:
if bool(obj.r_get('r_is_ficon')):
return None
full_key = key if sub_key is None else key + '/' + sub_key
return copy.deepcopy(gen_util.get_key_val(obj, full_key))
except BaseException as e:
brcdapi_log.exception(['Key: ' + str(key), 'sub_key: ' + str(sub_key)] + _fmt_errors(obj, full=True), echo=True)
return None
def _default_user_pw_act(obj, key, sub_key=None):
"""Returns an encoded default new user password: "Passw0rd!". See _conv_none_act() for parameters"""
global _temp_password
return base64.b64encode(_temp_password.encode('utf-8')).decode('utf-8')
def _true_act(obj, key, sub_key=None):
"""Returns logical True. See _conv_none_act() for parameters"""
return True
###################################################################
#
# Branch Actions
#
###################################################################
def _data_capture(cd, d):
"""Captures a list of URIs from the target chassis
:param cd: Session, project info, etc. See local_control_d in pseudo_main()
:type cd: dict
:param d: Actions to take. This is a dictionary from _control_d
:type d: dict
:return: List of error messages as str encountered.
:rtype: list
"""
try:
el = list()
if api_int.get_batch(cd['session'], cd['proj_obj'], d['rl']):
cd['t_chassis_obj'] = cd['proj_obj'].r_chassis_obj(cd['session']['chassis_wwn'])
cd['fid'] = dict(default=brcddb_switch.switch_fid(cd['t_chassis_obj'].r_default_switch_obj()),
all=cd['t_chassis_obj'].r_fid_list())
cd['fid'].update(all_non_default=[fid for fid in cd['fid']['all'] if fid != cd['fid']['default']])
else:
el.append('Error(s) capturing data. Check the log for details')
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, e]))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _data_clear(cd, d):
"""Clears all data from captured session. See _data_capture() for parameter definitions"""
try:
el = list()
cd['proj_obj'].s_del_chassis(cd['session'].pop('chassis_wwn'))
try:
for key in ('t_chassis_obj', 'fid'):
cd.pop(key)
except AttributeError:
pass
except KeyError:
pass # Future proofing. At the time this was written, there would always be a chassis to delete
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, e]))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _del_switches(cd, d):
"""Deletes all logical switches. See _data_capture() for parameter definitions"""
el = list()
try:
for fid in cd['fid'][d['fid']]:
obj = brcdapi_switch.delete_switch(cd['session'], fid, echo=True)
if fos_auth.is_error(obj):
el.extend(['Error deleting FID ' + str(fid), fos_auth.formatted_error_msg(obj)])
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, obj, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _vf_enable(cd, d):
"""Enable/Disable virtual fabrics. See _data_capture() for parameter definitions"""
el = list()
try:
el, content_d = _patch_content(cd['r_chassis_obj'], cd['t_chassis_obj'], d)
if len(content_d) > 0:
el.extend(_enable_disable_chassis(cd['session'], False, '_vf_enable, index: ' + d['e'])) # Disable chassis
if len(el) == 0:
# Set VF
obj = brcdapi_rest.send_request(cd['session'], d['k'], d['m'], content_d)
if fos_auth.is_error(obj):
el.extend(['Error updating chassis URI: ' + d['k'] + ', Index: ' + d['e'],
'FOS error:',
fos_auth.formatted_error_msg(obj)])
# Re-enable chassis. Note: If the chassis was disabled from the start, we wouldn't have gotten this far
el.extend(_enable_disable_chassis(cd['session'], True, '_vf_enable, index: ' + d['e']))
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, obj, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _restore_switches(cd, d):
"""Re-creates logical switches. See _data_capture() for parameter definitions"""
try:
el, r_chassis_obj, t_chassis_obj = list(), cd['r_chassis_obj'], cd['t_chassis_obj']
for r_switch_obj in [obj for obj in r_chassis_obj.r_switch_objects() if not obj.r_is_default()]:
# Should the switch be created?
fid = cd['fid_map'].get(brcddb_switch.switch_fid(r_switch_obj))
if fid is None:
continue
# Create the logical switch
obj = brcdapi_switch.create_switch(cd['session'],
fid,
r_switch_obj.r_is_base(),
r_switch_obj.r_is_ficon(),
echo=True)
if fos_auth.is_error(obj):
el.extend(['Error creating ' + brcddb_switch.best_switch_name(r_switch_obj, fid=True, did=True) +d['e'],
fos_auth.formatted_error_msg(obj)])
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, r_switch_obj, fid, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _restore_ports(cd, d):
"""Move ports from default switch to logical switch. See _data_capture() for parameter definitions"""
try:
el, r_chassis_obj, t_chassis_obj = list(), cd['r_chassis_obj'], cd['t_chassis_obj']
for rs_obj in [obj for obj in r_chassis_obj.r_switch_objects() if not obj.r_is_default()]:
# Make sure it's one of the FIDs to be restored before continuing
fid = cd['fid_map'].get(brcddb_switch.switch_fid(rs_obj))
ts_obj, ds_obj = None, t_chassis_obj.r_default_switch_obj()
if fid is not None:
ts_obj = t_chassis_obj.r_switch_obj_for_fid(fid)
if ts_obj is None: # ts_obj can be None if there was an error creating the LS on the target chassis
continue
# Add the ports
obj = brcdapi_switch.add_ports(cd['session'],
fid,
cd['fid']['default'],
[p for p in rs_obj.r_port_keys() if ds_obj.r_port_obj(p) is not None],
[p for p in rs_obj.r_ge_port_keys() if ds_obj.r_ge_port_obj(p) is not None],
echo=True)
if fos_auth.is_error(obj):
buf = 'Error adding ports to ' + brcddb_switch.best_switch_name(ts_obj, fid=True, did=True) + d['e']
brcdapi_log.exception(_fmt_errors([buf, fos_auth.formatted_error_msg(obj)], full=True))
el.extend([buf, fos_auth.formatted_error_msg(obj)])
continue
# Bind any addresses that need to be bound.
port_d = dict()
for port_obj in rs_obj.r_port_objects():
if brcddb_switch.switch_type(rs_obj) != 'FICON':
break # This is a bug in FOS. The address mode is always 8-bit with a bound address list
bind_l = port_obj.r_get('fibrechannel/bound-address-list/bound-address')
if len(bind_l) > 0:
port_d.update({port_obj.r_obj_key(): bind_l[0]})
if len(port_d) > 0:
brcdapi_port.bind_addresses(cd['session'], fid, port_d)
except BaseException as e:
brcdapi_log.exception(['Software error. Exception:'] + _fmt_errors(e))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _none_act(cd, d):
return list()
def _fibrechannel_switch(cd, d):
"""Actions for brocade-fibrechannel-switch. See _data_capture() for parameter definitions"""
try:
el = list()
brcdapi_log.log('_fibrechannel_switch', echo=True)
el.append('WIP: _fibrechannel_switch')
except BaseException as e:
brcdapi_log.exception(['Software error. Exception:'] + _fmt_errors(e))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _trunk_act(cd, d):
"""Re-create trunk groups. See _data_capture() for parameter definitions"""
try:
el = list()
el.apend('WIP: _trunk_act')
except BaseException as e:
brcdapi_log.exception(['Software error. Exception:'] + _fmt_errors(e))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
# rl = [{'trunk-index': d['trunk-index'], 'trunk-members': d['trunk-members']} for d in
# gen_util.convert_to_list(d['r_obj'].r_get(d['key']))]
return None if len(rl) == 0 else rl
def _user_act(cd, d):
"""Re-create non-default users. See _data_capture() for parameter definitions"""
global _temp_password
el, content_l = list(), list()
try:
key, rw_d, r_chassis_obj, t_chassis_obj = d['k'], d['rw'], cd['r_chassis_obj'], cd['t_chassis_obj']
t_user_l = gen_util.convert_to_list(cd['t_chassis_obj'].r_get(key))
if len(t_user_l) > 0:
existing_user_d = dict(root=True) # Newer versions of FOS don't support root. Make sure root gets skipped
for td in gen_util.convert_to_list(t_chassis_obj.r_get(key)):
existing_user_d.update({td['name']: True})
for rd in gen_util.convert_to_list(r_chassis_obj.r_get(key)):
if not bool(existing_user_d.get(rd['name'])):
content_d = collections.OrderedDict() if isinstance(rw_d, collections.OrderedDict) else dict()
for k in rw_d.keys():
content_d.update({k: rw_d[k](rd, k)})
if k == 'password':
el.append('Created user: ' + str(rw_d.get('name')) + '. Temp password: ' + _temp_password)
content_l.append(content_d)
if len(content_l) > 0:
return _send_request(cd['session'], d['m'], key, content_l)
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _zone_restore(cd, d):
"""Restore zoning. See _data_capture() for parameter definitions"""
el = list()
for r_switch_obj in cd['r_chassis_obj'].r_switch_objects():
fid = cd['fid_map'].get(brcddb_switch.switch_fid(r_switch_obj))
if fid is None:
continue
if cd['t_chassis_obj'].r_switch_obj_for_fid(fid) is None:
continue
obj = brcddb_zone.replace_zoning(cd['session'], r_switch_obj.r_fabric_obj(), fid)
if fos_auth.is_error(obj):
buf = 'Failed to replace zoning in FID ' + str(fid)
brcdapi_log.log([buf] + _fmt_errors(fos_auth.formatted_error_msg(obj)), echo=True)
el.append('Failed to replace zoning in FID ' + str(fid))
return el
def _chassis_update_act(cd, d):
"""Updates that do not have any special considerations. See _data_capture() for parameter definitions"""
el, content_l = list(), list()
try:
key, rw_d, r_chassis_obj, t_chassis_obj = d['k'], d['rw'], cd['r_chassis_obj'], cd['t_chassis_obj']
r_name, t_name =\
brcddb_chassis.best_chassis_name(r_chassis_obj), brcddb_chassis.best_chassis_name(t_chassis_obj)
rd, td = r_chassis_obj.r_get(key), t_chassis_obj.r_get(key)
if rd is not None and not isinstance(td, type(rd)):
el.append(key + ' not supported in target chassis ' + t_name)
elif td is not None and not isinstance(rd, type(td)):
el.append(key + ' not supported in restore chassis ' + r_name)
if len(el) > 0:
return el
for k, method in rw_d.items():
r_val, t_val = method(rd, k), method(td, k)
if t_val is not None and not isinstance(r_val, type(t_val)):
el.append(key + '/' + str(k) + ' not supported in restore chassis ' + r_name)
continue
elif r_val is not None and not isinstance(t_val, type(r_val)):
el.append(key + '/' + str(k) + ' not supported in target chassis ' + t_name)
continue
if d['m'] == 'POST':
content_l.append({k: r_val})
elif d['m'] == 'PATCH' and (str(r_val) != str(t_val)):
content_l.append({k: r_val})
if len(content_l) > 0:
return _send_request(cd['session'], d['m'], key, content_l)
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _switch_update_act(cd, d):
"""Updates that do not have any special considerations. See _data_capture() for parameter definitions"""
el, content_d = list(), dict()
try:
key, rw_d, r_chassis_obj, t_chassis_obj = d['k'], d['rw'], cd['r_chassis_obj'], cd['t_chassis_obj']
for fid in cd['r_chassis_obj'].r_fid_list():
r_switch_obj, t_switch_obj =\
r_chassis_obj.r_switch_obj_for_fid(fid), t_chassis_obj.r_switch_obj_for_fid(fid)
if t_switch_obj is None:
el.append('FID ' + str(fid) + ' not in ' + brcddb_chassis.best_chassis_name(t_chassis_obj))
continue
r_name = brcddb_switch.best_switch_name(r_switch_obj, fid=True)
t_name = brcddb_switch.best_switch_name(t_switch_obj, fid=True)
for k, method in rw_d.items():
r_val, t_val = method(r_switch_obj, key, k), method(t_switch_obj, key, k)
if t_val is not None and not isinstance(r_val, type(t_val)):
el.append(key + '/' + str(k) + ' not supported in restore chassis ' + r_name)
continue
elif r_val is not None and not isinstance(t_val, type(r_val)):
el.append(key + '/' + str(k) + ' not supported in target chassis ' + t_name)
continue
if d['m'] == 'POST':
content_d.update({k: r_val})
elif d['m'] == 'PATCH' and (str(r_val) != str(t_val)):
content_d.update({k: r_val})
if len(content_d) > 0:
if 'brocade-fibrechannel-switch' in key:
obj = brcdapi_switch.fibrechannel_switch(cd['session'], fid, content_d, t_switch_obj.r_obj_key())
else:
temp_l = key.split('/')
d = {temp_l.pop(): content_d}
el.extend(_send_request(cd['session'], d['m'], d['k'], d, fid=fid))
obj = None
if fos_auth.is_error(obj):
el.extend(['Error updating chassis URI:',
' ' + key,
pprint.pformat(content_d),
'FOS error:',
fos_auth.formatted_error_msg(obj)])
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _port_update_act(cd, d):
"""Updates that do not have any special considerations. See _data_capture() for parameter definitions"""
el = list()
try:
key = d['k'].replace('brocade-interface/', '')
rw_d, r_chassis_obj, t_chassis_obj = d['rw'], cd['r_chassis_obj'], cd['t_chassis_obj']
for r_switch_obj in r_chassis_obj.r_switch_objects():
# Find the switch on the target chassis
r_fid = brcddb_switch.switch_fid(r_switch_obj)
t_fid = cd['fid_map'].get(r_fid)
if t_fid is None:
continue
t_switch_obj = t_chassis_obj.r_switch_obj_for_fid(t_fid)
if t_switch_obj is None:
continue # If there was an error creating the switch it was already reported.
# Update the ports
content_l = list()
for r_port_obj in r_switch_obj.r_port_objects():
t_port_obj = t_switch_obj.r_port_obj(r_port_obj.r_obj_key())
if t_port_obj is None:
continue
sub_d = dict(name=t_port_obj.r_obj_key())
for k, method in rw_d.items():
r_val, t_val = method(r_port_obj, key, k), method(t_port_obj, key, k)
if r_val is None and t_val is None:
continue
elif r_val is None or t_val is None:
el.append(key + '/' + str(k) + ' not restored in port ' + t_port_obj.r_obj_key())
continue
if d['m'] == 'POST':
sub_d.update({k: r_val})
elif d['m'] == 'PATCH' and (str(r_val) != str(t_val)):
sub_d.update({k: r_val})
if len(sub_d) > 1:
content_l.append(sub_d)
if len(content_l) > 0:
el.extend(_send_request(cd['session'], d['m'], d['k'], content_l, fid=t_fid))
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
def _maps_rule_act(cd, d):
"""Updates for brocade-maps/rule. See _data_capture() for parameter definitions"""
el, content_l = list(), list()
try:
print('_maps_rule_act')
except BaseException as e:
brcdapi_log.exception(_fmt_errors([d, cd, e], full=True))
el.append('Software error. Check the log for "brcdapi library exception call"')
return el
"""_control_d is a dictionary of dictionaries. The first key is the highest level branch (right after running) and
the second is the next branch level URI. The final dictionary instructs the machine in psuedo_main() how to act on
the URI branch as noted in the table below.
Ordered dictionaries were used because in some cases the order of operations is significant. For example, user IDs must
be created before they can be modified.
Special keys begin with '_'. Special keys are different in that they are not processed as a simple compare and update
differences. Special keys are used to capture, refresh, and create switches. Creating switches and adding ports are
treated as special circumstances abe they have timing and other considerations which are addressed by modules in the
brcdapi library.
+-------+-----------+-----------------------------------------------------------------------------------------------+
| Key | Type | Description |
+=======+===========+===============================================================================================+
| a | method | Pointer to method to call when interpreting the branch. If None or not present, the URI is |
| | | put in _full_capture_l, so a GET is performed, but no action is taken. |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| e | str | Text to append to error messages. Only useful for trouble shooting. |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| fid | str | 'all' for all logical switches. 'default' for the default switch only. 'all_non_default' for |
| | | logical switches that are not the default. May also be specific FID or CSV list of FIDs or |
| | | range of FIDs. None or not present implies the operation is for a chassis |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| k | str | KPI (URI not including the leading 'running/' or anything prior). Only used if the action |
| | | method needs to access an API resource. May be a CSV if multiple KPIs are required. |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| m | HTTP | HTTP method |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| rl | str, list | Data specific to the action method. |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| p | None, str | If None, always execute this entry. Otherwise, the entry is only executed if specified with |
| | | the -p option on the command line. See -p options description in _get_input() for details. |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| rw | dict | Dictionary of leaf names that support read and write. The value is a pointer to a method that |
| | | interprets the value for the leaf. At the time this was written, there were only two options: |
| | | |
| | | _conv_none_act Always returns None. None is assumed for any leaf not in the |
| | | dictionary so this is only useful as a place holder for a leaf you may |
| | | want to change at a future date. |
| | | |
| | | _conv_lookup_act A simple deepcopy of what ever the value for the leaf is. |
+-------+-----------+-----------------------------------------------------------------------------------------------+
| skip | bool | If True, skip this item. Essentially comments out this entry. Set True for all untested |
| | | entries. |
+-------+-----------+-----------------------------------------------------------------------------------------------+
A basic capture (capture_0) is required for deleting (_del_switches), creating logical switches (_restore_switches),
and restoring ports (_restore_ports). Data should always be cleared (_data_clear) before capturing (_data_capture) a
fresh set of data. A fresh set of data should always be captured after making changes that may effect other
operations.
For a partial restore, comment out any entries in _control_d you don't want to modify.
The following key/value pairs are added to the dictionaries in _control_d as data is collected:
+-------------------+---------------+-------------------------------------------------------------------------------+
| Key | Type | Description |
+===================+===============+===============================================================================+
| session | dict | Session object returned from brcdapi.fos_auth.login() |
+-------------------+---------------+-------------------------------------------------------------------------------+
| r_chassis_obj | ChassisObj | Restore from chassis object. |
+-------------------+---------------+-------------------------------------------------------------------------------+
| t_chassis_obj | ChassisObj | Target chassis object. |
+-------------------+---------------+-------------------------------------------------------------------------------+
| r_default_fid | int | Restore chassis default FID |
+-------------------+---------------+-------------------------------------------------------------------------------+
| fid | dict | Key value pairs are: |
| | | default List with one entry, the default switch FID of the restore |
| | | chassis |
| | | all List of all FIDs in the restore chassis |
| | | all_non_default List of all FIDs in the restore chassis except the default |
| | | FID |
+-------------------+---------------+-------------------------------------------------------------------------------+
| act_d | dict | Keys are the same keys in _restore_parameters. Value is True if entered in |
| | | the shell prompt. |
+-------------------+---------------+-------------------------------------------------------------------------------+
WARNING: This is a template with some suggestions. Anything with _conv_none_act or _none_act in most cases is a work
in progress
"""
# _control_d = collections.OrderedDict()
# _control_d['_control'] = collections.OrderedDict()
_action_l = [
# Virtual Fabrics
dict(a=_data_capture, rl=_basic_capture_kpi_l, e='Basic Capture 0', p='vf'),
dict(k='brocade-chassis/chassis', a=_vf_enable, e='_vf_enable', p='vf', rw={'vf-enabled': _conv_lookup_act}),
dict(a=_del_switches, fid='all_non_default', e='_del_switches', p='vf'),
dict(a=_data_clear, e='_data_clear 1', p='vf'),
dict(a=_data_capture, rl=_basic_capture_kpi_l, e='Basic Capture 1', p='vf'),
dict(k='brocade-fabric/fabric-switch', a=_restore_switches, fid='all_non_default', e='_restore_switches', p='vf'),
dict(a=_data_clear, e='_data_clear 2', p='vf'),
dict(a=_data_capture, rl=_basic_capture_kpi_l, e='Basic Capture 2', p='vf'),
dict(k='brocade-interface/fibrechannel', a=_restore_ports, fid='all_non_default', e='_restore_ports', p='vf'),
dict(a=_data_clear, e='_data_clear 3', p='vf'),
# Capture a full set of data before continuing.
dict(a=_data_capture, rl=_full_capture_l, e='Full Capture', p='m'),
# c: Chassis parameters
dict(k='brocade-chassis/chassis', a=_chassis_update_act, m='PATCH', p='c', rw={
'chassis-user-friendly-name': _conv_lookup_act,
'fcr-enabled': _conv_lookup_act,
'chassis-enabled': _conv_none_act,
'shell-timeout': _conv_lookup_act,
'session-timeout': _conv_lookup_act,
'tcp-timeout-level': _conv_lookup_act,
}),
# Not tested
dict(k='brocade-chassis/management-interface-configuration', a=_none_act, m='PATCH', p='c', skip=True, rw={
'rest-enabled,https-protocol-enabled': _conv_lookup_act,
'max-rest-sessions': _conv_lookup_act,
'https-keep-alive-enabled': _conv_none_act,
'cp-name': _conv_lookup_act,
'interface-name': _conv_lookup_act,
'auto-negotiate': _conv_lookup_act,
'speed': _conv_lookup_act,
'lldp-enabled': _conv_lookup_act,
}),
# Not tested
dict(k='brocade-chassis/management-port-track-configuration', a=_none_act, m='PATCH', p='c', skip=True, rw={
'tracking-enabled,scan-interval': _conv_lookup_act,
}),
# Not tested
dict(k='brocade-chassis/credit-recovery', a=_none_act, m='PATCH', p='c', skip=True, rw={ # Not tested
'mode': _conv_lookup_act,
'link-reset-threshold': _conv_lookup_act,
'fault-option': _conv_lookup_act,
'backend-credit-loss-enabled': _conv_lookup_act,
'backend-loss-of-sync-enabled': _conv_lookup_act,
}),
# s: Security
dict(k='brocade-security/user-config', a=_user_act, m='POST', p='s', rw={
'name': _conv_lookup_act,
'password': _default_user_pw_act,
# 'role': _conv_lookup_act, # In the Rest API Guide but not present in FOS
'account-description': _conv_lookup_act,
'account-enabled': _conv_lookup_act,
'password-change-enforced': _true_act,
# 'account-locked': _conv_lookup_act, # FOS defect? I get an error no matter how I set this
'access-start-time': _conv_lookup_act,
'access-end-time': _conv_lookup_act,
'home-virtual-fabric': _conv_lookup_act,
'chassis-access-role': _conv_lookup_act,
'virtual-fabric-role-id-list': _conv_lookup_act,
}),
dict(k='brocade-security/user-specific-password-cfg', a=_none_act, m='POST', p='s', skip=True), # Not tested
dict(k='brocade-security/auth-spec', a=_none_act, m='POST', p='s', skip=True), # Not tested
# Not tested
dict(k='brocade-lldp/lldp-global', a=_switch_update_act, m='PATCH', p='s', skip=True, rw={
'multiplier': _conv_lookup_act,
'tx-interval': _conv_lookup_act,
'system-name': _conv_lookup_act,
'system-description': _conv_lookup_act,
'enabled-state': _conv_lookup_act,
'optional-tlvs': _conv_lookup_act,
}),
# Not tested
dict(k='brocade-lldp/lldp-profile', a=_none_act, m='PATCH', p='s', skip=True, rw={
'name': _conv_lookup_act,
'multiplier': _conv_lookup_act,
'tx-interval': _conv_lookup_act,
'enabled-tlvs': _conv_lookup_act,
}),
# Logging
dict(k='brocade-logging/audit', a=_none_act, m='PATCH', p='l', skip=True, rw={ # Not tested
'audit-enabled': _conv_lookup_act,
'severity-level': _conv_lookup_act,
'filter-class-list': _conv_lookup_act,
}),
dict(k='brocade-logging/syslog-server', a=_none_act, m='PATCH', p='s', skip=True, rw={ # Not tested
'server': _conv_lookup_act,
'port': _conv_lookup_act,
'secure-mode': _conv_lookup_act,
}),
dict(k='brocade-logging/raslog', a=_none_act, m='PATCH', p='s', skip=True, rw={ # Not tested
'audit-enabled': _conv_lookup_act,
'severity-level': _conv_lookup_act,
'filter-class-list': _conv_lookup_act,
}),
dict(k='brocade-logging/raslog-module', a=_none_act, m='PATCH', p='s', skip=True, rw={ # Not tested
'module-id': _conv_lookup_act,
'log-enabled': _conv_lookup_act,
}),
dict(k='brocade-logging/log-quiet-control', a=_none_act, m='PATCH', p='s', skip=True, rw={ # Not tested
'log-type': _conv_lookup_act,
'quiet-enabled': _conv_lookup_act,
'start-time': _conv_lookup_act,
'end-time': _conv_lookup_act,
'days-of-week': _conv_lookup_act,
}),
dict(k='brocade-logging/log-setting', a=_none_act, m='PATCH', p='s', skip=True, rw={ # Not tested
'syslog-facility-level': _conv_lookup_act,
'keep-alive-period': _conv_lookup_act,
'clear-log': _conv_lookup_act,
}),
dict(k='brocade-logging/supportftp', a=_none_act, m='PATCH', p='s', skip=True, rw={ # Not tested
'host': _conv_lookup_act,
'user-name': _conv_lookup_act,
'password': _conv_lookup_act,
'remote-directory': _conv_lookup_act,
'auto-enabled': _conv_lookup_act,
'protocol': _conv_lookup_act,
'port': _conv_lookup_act,
'onnectivity-check-interval': _conv_lookup_act,
}),
dict(k='brocade-fibrechannel-configuration/chassis-config-settings', a=_chassis_update_act, m='PATCH', rl='name',
p='c', rw={
'firmware-synchronization-enabled': _conv_lookup_act,
'http-session-ttl': _conv_lookup_act,
'ezserver-enabled': _conv_lookup_act,
'cs-ctl-mode': _conv_lookup_act,
'sddq-limit': _conv_lookup_act,
'vtap-qos-compatibility-mode': _conv_lookup_act,
'secure-mode-enabled': _conv_lookup_act,
'file-suffix-enabled': _conv_lookup_act,
# 'custom-index': _conv_lookup_act # Probably only useful for OEM build out process
}),
# Logical Switch Configurations: General
dict(k='brocade-fibrechannel-switch/fibrechannel-switch', a=_switch_update_act, m='PATCH', rl='name', p='s',
rw={
'domain-id': _conv_lookup_act,
'user-friendly-name': _conv_lookup_act,
'ip-address': _conv_lookup_act, # WIP
'ip-static-gateway-list': _conv_lookup_act, # WIP