-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmigrate.py
348 lines (307 loc) · 19.7 KB
/
migrate.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
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Copyright 2024 Consoli Solutions, LLC. All rights reserved.
**License**
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 https://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.
The license is free for single customer use (internal applications). Use of this module in the production,
redistribution, or service delivery for commerce requires an additional license. Contact jack@consoli-solutions.com for
details.
**Description**
Performs a node find in a project
**Version Control**
+-----------+---------------+-----------------------------------------------------------------------------------+
| Version | Last Edit | Description |
+===========+===============+===================================================================================+
| 1.0.0 | 06 Dec 2024 | Initial launch. |
+-----------+---------------+-----------------------------------------------------------------------------------+
"""
__author__ = 'Jack Consoli'
__copyright__ = 'Copyright 2024 Consoli Solutions, LLC'
__date__ = '06 Dec 2024'
__license__ = 'Apache License, Version 2.0'
__email__ = 'jack@consoli-solutions.com'
__maintainer__ = 'Jack Consoli'
__status__ = 'Released'
__version__ = '1.0.0'
import os
import brcdapi.log as brcdapi_log
import brcdapi.util as brcdapi_util
import brcdapi.gen_util as gen_util
import brcdapi.excel_util as excel_util
import brcdapi.file as brcdapi_file
import brcddb.brcddb_common as brcddb_common
import brcddb.brcddb_project as brcddb_project
import brcddb.brcddb_switch as brcddb_switch
import brcddb.util.search as brcddb_search
import brcddb.report.login as report_login
import brcddb.brcddb_fabric as brcddb_fabric
import brcddb.brcddb_login as brcddb_login
import brcddb.brcddb_port as brcddb_port
import brcddb.util.obj_convert as obj_convert
_version_d = dict(
brcdapi_log=brcdapi_log.__version__,
brcdapi_util=brcdapi_util.__version__,
gen_util=gen_util.__version__,
excel_util=excel_util.__version__,
brcdapi_file=brcdapi_file.__version__,
brcddb_common=brcddb_common.__version__,
brcddb_project=brcddb_project.__version__,
brcddb_switch=brcddb_switch.__version__,
brcddb_search=brcddb_search.__version__,
report_login=report_login.__version__,
brcddb_fabric=brcddb_fabric.__version__,
brcddb_login=brcddb_login.__version__,
brcddb_port=brcddb_port.__version__,
)
_DOC_STRING = False # Should always be False. Prohibits any code execution. Only useful for building documentation
# _STAND_ALONE: True: Executes as a standalone module taking input from the command line. False: Does not automatically
# execute. This is useful when importing this module into another module that calls psuedo_main().
_STAND_ALONE = True # See note above
_search_type_d = dict(wild='wild', regex_m='regex-m', regex_s='regex-s', exact='exact')
_search_type_l = [str(_b) for _b in _search_type_d.keys()]
_input_d = dict(
i=dict(h='Required. Name of input file generated by capture.py, combine.py, or multi_capture.py. ".json" is '
'automatically appended if not present.'),
initiator=dict(r=False, d=None,
h='Optional. A CSV list of plain text files containing the initiators to migrate. It is assumed '
'that initiators can be migrated without consideration. The initiator is removed from any zones '
'it is a member of. If the resulting zone has fewer than two members, the zone will be removed '
'from any configuration it is a member of and then deleted. If an alias was specified, the alias '
'is also deleted. Regex and wild card searching is only applied to aliases. This file can contain '
'WWNs but regex and wild card searching is not applied to WWNs. Any entry in this file that looks '
'like a properly formatted WWN is considered to be a WWN. All other entries are assumed to be '
'aliases. All devices are treated as initiators regardless of the actual device type.'),
target=dict(r=False, d=None,
h='Optional. Same as -initiator, except targets are assumed to have dependencies. A target cannot be '
'removed if it is a member of a zone that contains other members that are not in -initiator or '
'-ignore.'),
ignore=dict(r=False, d=None,
h='Optional. Similar to -initiator, except these are only used to determine target dependencies. '
'Typically, this file contains all the storage WWNs and aliases used in zoning that are not for '
'peer-to-peer storage mirroring. Note that it is not always possible to determine if a device is a '
'target or initiator. If a zone contained one initiator and two targets and the initiator removed, '
'the result would be a zone with two targets. Since there is more than one member in the resulting '
'zone, the migration would fail. Specifying WWNs and aliases to ignore gets around this problem by '
'not counting them as zone members.'),
zone=dict(r=False, d=None,
h='Optional. Similar to -ignore, but for zones. This gets around the potential problem that can occur '
'when targets are in multiple zones, but you don\'t want to ignore dependencies on a target in all '
'zones.'),
fid=dict(r=False, d=None,
h='Optional. When set, commands to move all ports found that are associated with -initiator and -target '
'are generated.'),
fabric_wwn=dict(r=False, d=None,
h='Optional with -scan. Otherwise, required. Fabric WWN whose zone DB is to be read from. When '
'-switch_file is used, it is also used to determine the switches and chassis.'),
asearch=dict(r=False, d='exact', v=_search_type_l,
h='Optional. Search method to be applied to aliases (-initiator, -target, and -ignore). Options '
'are: ' + ', '.join(_search_type_l) + '. The default is "exact".'),
zsearch=dict(r=False, d='exact', v=_search_type_l,
h='Optional. Search method to be applied to zones (-zone). Options '
'are: ' + ', '.join(_search_type_l) + '. The default is "exact".'),
scan=dict(r=False, d=False, t='bool',
h='Optional. No parameters. Scan input file, -i, for fabric information. No other actions are taken.'),
)
_input_d.update(gen_util.parseargs_log_d.copy())
_MAX_LINE_LEN = 72 # Used to control how long CLI can be.
def psuedo_main(fab_obj, dest_d, parameter_d):
"""Basically the main().
:param fab_obj: Fabric object
:type fab_obj: brcdapi.class.fabric.FabricObj
:param dest_d: Destination switch parameters. Search for dest_d in _get_input() for details
:type dest_d: dict
:param parameter_d: Search for parameter_d in _get_input() for details
:type parameter_d: dict
:return: Status code.
:rtype: int
"""
alias_d = dict() # Alias to be deleted. Key is the alias name. Value is True
""" zone_d is a dictionary. The key is the zone name. The value is a dictionary as follows:
+-----------+-----------------------------------------------------------+
| key | Description |
+===========+===========================================================+
| mem_l | Member to be removed from the member list |
+-----------+-----------------------------------------------------------+
| pmem_l | Principal member to be removed the the pmember list |
+-----------+-----------------------------------------------------------+
"""
zone_d = dict() # See comments above
ec = brcddb_common.EXIT_STATUS_OK
el, cli_alias_add_l, cli_alias_del_l, cli_zone_add_l, cli_zone_del_l = list(), list(), list(), list(), list()
# t_fab_obj is for the new logical switch. c_fab_obj is a copy of the current fabric object to modify
t_fab_obj = None if dest_d['fid'] is None else fab_obj.r_proj_obj().s_add_fabric(fab_obj.r_obj_key() + str('_t'))
c_fab_obj = brcddb_fabric.copy_fab_obj(fab_obj)
# Build a dictionary of aliases and WWNs to ignore for easy lookup
ignore_d = dict()
for key in parameter_d['ignore']['wwn_l'] + parameter_d['ignore']['obj_l']:
ignore_d[key] = True
# Alias. Remove them from zones, delete the alias in the fabric and create the alias in the new fabric
for alias in parameter_d['initiator']['obj_l'] + parameter_d['target']['obj_l']: # The aliases
alias_obj = c_fab_obj.r_alias_obj(alias)
if alias_obj is None:
continue # It can't be None. This is just future proofing.
t_fab_obj.s_add_alias(alias, c_fab_obj.r_alias_obj(alias).r_members())
c_fab_obj.s_del_alias(alias)
for zone_obj in c_fab_obj.r_zone_objects():
zone_obj.s_del_member(alias)
zone_obj.s_del_member(alias)
# WWN. Aliases were determined in _get_input and added to the alias list, so just remove the WWNs from the zones.
for wwn in parameter_d['initiator']['wwn_l'] + parameter_d['target']['wwn_l']: # The WWNs
for zone_obj in c_fab_obj.r_zone_objects():
zone_obj.s_del_member(wwn)
zone_obj.s_del_member(wwn)
print('Left off here')
# Make sure we didn't delete any targets that have dependencies
return ec
def _get_input():
"""Parses the module load command line
:return ec: Status code.
:rtype ec: int
"""
global __version__, _input_d, _version_d, _search_type_d, _search_type_l
# Get command line input
buf = 'Searches a project file for devices and zone members to migrate to another fabric. Typically used for '\
'"what if" scenarios. Direct actions on switches are not taken. Wild card and regex matching/searching is '\
'supported for -initiator, -target, -ignore, and -zone. To simplify copy and paste, duplicates for '\
'-initiator, -target, -ignore, and -zone options are removed.'
try:
args_d = gen_util.get_input(buf, _input_d)
except TypeError:
return brcddb_common.EXIT_STATUS_INPUT_ERROR # gen_util.get_input() already posted the error message.
# Set up logging
brcdapi_log.open_log(folder=args_d['log'], supress=args_d['sup'], no_log=args_d['nl'], version_d=_version_d)
"""dest_d is a dictionary passed to pseudo_main() as follows:
+-----------+-------+-------------------------------------------------------------------------------------------+
| Key | type | Description |
+===========+=======+===========================================================================================+
| ip | str | IP address of the destination switch. Not yet implemented |
+-----------+-------+-------------------------------------------------------------------------------------------+
| id | str | Login ID of the destination switch. Not yet implemented |
+-----------+-------+-------------------------------------------------------------------------------------------+
| pw | str | Password of the destination switch. Not yet implemented |
+-----------+-------+-------------------------------------------------------------------------------------------+
| sec | str | Security type, 'none' or 'self'. Not yet implemented |
+-----------+-------+-------------------------------------------------------------------------------------------+
| fid | int | Fabric ID (FID) to create in the destination switch. |
+-----------+-------+-------------------------------------------------------------------------------------------+
parameter_d is a dictionary passed to pseudo_main(). The keys are initiator, target, ignore, zone. The value
is a list of dictionaries as follows:
+-----------+-------+-------------------------------------------------------------------------------------------+
| Key | Type | Description |
+===========+=======+===========================================================================================+
| obj_l | list | List of brcddb class object (alias or zone) names matching the search criteria. |
+-----------+-------+-------------------------------------------------------------------------------------------+
| wwn_l | list | List of WWNs matching the search criteria. |
+-----------+-------+-------------------------------------------------------------------------------------------+
"""
ec, parameter_d = brcddb_common.EXIT_STATUS_OK, dict()
dest_d = dict(ip=args_d.get('ip'),
id=args_d.get('id'),
pw=args_d.get('pw'),
sec=args_d.get('sec'),
fid=args_d['fid'])
# Read in the project file
proj_obj, args_i_help = None, ''
try:
proj_obj = brcddb_project.read_from(brcdapi_file.full_file_name(args_d['i'], '.json'))
if proj_obj is None: # Error messages are sent to the log in brcddb_project.read_from() if proj_obj is None
return brcddb_common.EXIT_STATUS_INPUT_ERROR
brcddb_project.build_xref(proj_obj)
except FileNotFoundError:
args_i_help = ' **ERROR**: not found'
ec = brcddb_common.EXIT_STATUS_INPUT_ERROR
except FileExistsError:
args_i_help = ' **ERROR**: folder does not exist.'
ec = brcddb_common.EXIT_STATUS_INPUT_ERROR
# Validate the inputs
args_fabric_wwn_help, fab_obj = '', None
if not args_d['scan']:
if args_d['fabric_wwn'] is None:
args_fabric_wwn_help = ' **ERROR**: Required input missing.'
ec = brcddb_common.EXIT_STATUS_INPUT_ERROR
else:
fab_obj = proj_obj.r_fabric_obj(args_d['fabric_wwn'])
if fab_obj is None:
args_fabric_wwn_help = ' **ERROR**: ' + args_d['fabric_wwn'] + ' not found in ' + args_d['i']
ec = brcddb_common.EXIT_STATUS_INPUT_ERROR
# Command line feedback
ml = ['',
os.path.basename(__file__) + ', ' + __version__,
'Project, -i: ' + args_d['i'] + args_i_help,
'Initiator file, -initiator: ' + str(args_d['initiator']),
'Target file, -target: ' + str(args_d['target']),
'Ignore file, -ignore: ' + str(args_d['ignore']),
'Zone file, -zone: ' + str(args_d['zone']),
'Fabric ID, -fid: ' + str(args_d['fid']),
'Fabric WWN, -fabric_wwn: ' + str(args_d['fabric_wwn']) + args_fabric_wwn_help,
'Alias search type, -asearch: ' + str(args_d['asearch']),
'Zone search type, -zsearch: ' + str(args_d['zsearch']),
'Scan, -scan: ' + str(args_d['scan']),
'Log, -log: ' + str(args_d['log']),
'No log, -nl: ' + str(args_d['nl']),
'Suppress, -sup: ' + str(args_d['sup']),
'',]
if ec == brcddb_common.EXIT_STATUS_OK:
proj_obj.s_description('\n'.join(ml))
if bool(args_d['scan']):
ml.extend(brcddb_project.scan(proj_obj))
ec = brcddb_common.EXIT_STATUS_INPUT_ERROR
"""Find all the aliases and zones. The dictionary embedded in the code below is defined as follows:
+-------+-------+---------------------------------------------------------------+
| Key | Type | Description |
+=======+=======+===============================================================+
| i | str | The command line parameter identifier. |
+-------+-------+---------------------------------------------------------------+
| l | list | List of brcddb class objects to search through |
+-------+-------+---------------------------------------------------------------+
| s | str | Search type. One of the types in _search_type_l |
+-------+-------+---------------------------------------------------------------+
"""
alias_obj_l = fab_obj.r_alias_objects()
for d in (dict(i='initiator', l=alias_obj_l, s=args_d['asearch']), # See last dict in above block comments
dict(i='target', l=alias_obj_l, s=args_d['asearch']),
dict(i='ignore', l=alias_obj_l, s=args_d['asearch']),
dict(i='zone', l=fab_obj.r_zone_objects(), s=args_d['zsearch'])):
parameter_d[d['i']] = dict(obj_l=list(), wwn_l=list())
for file in list() if args_d[d['i']] is None else gen_util.convert_to_list(args_d[d['i']].split(',')):
try:
for buf in brcdapi_file.read_file(brcdapi_file.full_file_name(file.strip(), '.txt')):
if gen_util.is_wwn(buf, full_check=True):
parameter_d[d['i']]['wwn_l'].append(buf)
parameter_d[d['i']]['obj_l'].extend(fab_obj.r_alias_for_wwn(buf))
else:
obj_l = brcddb_search.match(d['l'], '_obj_key', buf, stype=_search_type_d[d['s']])
parameter_d[d['i']]['obj_l'].extend([obj.r_obj_key() for obj in obj_l])
if d['i'] != 'zone':
for alias_obj in obj_l:
parameter_d[d['i']]['wwn_l'].extend(alias_obj.r_members())
except FileNotFoundError:
ml.append(file + ' does not exist')
ec = brcddb_common.EXIT_STATUS_INPUT_ERROR
except FileExistsError:
ml.append('A folder in ' + file + ' does not exist')
ec = brcddb_common.EXIT_STATUS_INPUT_ERROR
for d in parameter_d.values():
d['obj_l'] = gen_util.remove_duplicates(d['obj_l'])
d['wwn_l'] = gen_util.remove_duplicates(d['wwn_l'])
# Command line feedback and process
brcdapi_log.log(ml, echo=True)
return ec if ec != brcddb_common.EXIT_STATUS_OK else \
psuedo_main(fab_obj, dest_d, parameter_d)
##################################################################
#
# Main Entry Point
#
###################################################################
if _DOC_STRING:
print('_DOC_STRING is True. No processing')
exit(0)
if _STAND_ALONE:
_ec = _get_input()
brcdapi_log.close_log(['', 'Processing Complete. Exit code: ' + str(_ec)], echo=True)
exit(_ec)