Skip to content

Commit c54c8c3

Browse files
authored
Allow target deletes for compareModel tool (#737)
* JIRA WDT-474 - Use aliases to determine location and attribute name * JIRA WDT-474 - Set list values to delta between old and new models; rename some variables * JIRA WDT-474 - Check type for comment
1 parent cd728d4 commit c54c8c3

File tree

4 files changed

+160
-83
lines changed

4 files changed

+160
-83
lines changed

core/src/main/python/compare_model.py

Lines changed: 144 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
from oracle.weblogic.deploy.validate import ValidateException
3333

3434
import oracle.weblogic.deploy.util.TranslateException as TranslateException
35+
from wlsdeploy.aliases import alias_utils
36+
from wlsdeploy.aliases.alias_constants import ALIAS_LIST_TYPES
3537
from wlsdeploy.aliases.aliases import Aliases
3638
from wlsdeploy.aliases.location_context import LocationContext
3739
from wlsdeploy.aliases.wlst_modes import WlstModes
@@ -42,6 +44,8 @@
4244
from wlsdeploy.logging.platform_logger import PlatformLogger
4345
from wlsdeploy.tool.validate.validator import Validator
4446
from wlsdeploy.util import cla_helper
47+
from wlsdeploy.util import dictionary_utils
48+
from wlsdeploy.util import model_helper
4549
from wlsdeploy.util import variables
4650
from wlsdeploy.util.cla_utils import CommandLineArgUtil
4751
from wlsdeploy.util.model_context import ModelContext
@@ -87,7 +91,8 @@ def __process_args(args):
8791

8892
class ModelDiffer:
8993

90-
def __init__(self, current_dict, past_dict):
94+
def __init__(self, current_dict, past_dict, aliases):
95+
self.aliases = aliases
9196
self.final_changed_model = PyOrderedDict()
9297
self.current_dict = current_dict
9398
self.past_dict = past_dict
@@ -136,7 +141,7 @@ def recursive_changed_detail(self, key, token, root):
136141
"""
137142
debug("DEBUG: Entering recursive_changed_detail key=%s token=%s root=%s", key, token, root)
138143

139-
a = ModelDiffer(self.current_dict[key], self.past_dict[key])
144+
a = ModelDiffer(self.current_dict[key], self.past_dict[key], self.aliases)
140145
diff = a.changed()
141146
added = a.added()
142147
removed = a.removed()
@@ -243,112 +248,130 @@ def calculate_changed_model(self):
243248
_logger.throwing(ex, class_name=_class_name, method_name=_method_name)
244249
raise ex
245250

246-
def _is_alias_folder(self, path):
251+
def _parse_change_path(self, path):
247252
"""
248-
Check if the delimited path is a folder or attribute
249-
:param path: '|' delimited path
250-
:return: true if it is a folder otherwise false
253+
Determine the location and attribute name (if specified) for the specified change path
254+
:param path: delimited change path, such as "resources|JDBCSystemResource|Generic2|JdbcResource"
255+
:return: tuple - location for path, attribute name from path or None
251256
"""
252-
debug("DEBUG: Entering is_alias_folder %s", path)
253-
path_tokens = path.split(PATH_TOKEN)
254-
model_context = ModelContext("test", {})
255-
location = LocationContext()
256-
last_token = path_tokens[-1]
257-
aliases = Aliases(model_context, wlst_mode=WlstModes.OFFLINE, exception_type=ExceptionType.COMPARE)
257+
_method_name = '_parse_change_path'
258258

259-
found = True
259+
location = LocationContext()
260+
attribute_name = None
260261
name_token_next = False
262+
263+
path_tokens = path.split(PATH_TOKEN)
264+
folder_names = self.aliases.get_model_section_top_level_folder_names(path_tokens[0])
265+
261266
for path_token in path_tokens[1:]:
267+
attribute_names = self.aliases.get_model_attribute_names(location)
268+
262269
if name_token_next:
263-
token_name = aliases.get_name_token(location)
270+
token_name = self.aliases.get_name_token(location)
264271
location.add_name_token(token_name, path_token)
265272
name_token_next = False
266-
else:
273+
elif path_token in folder_names:
267274
location.append_location(path_token)
268-
if last_token == path_token:
269-
break
270-
name_token_next = aliases.supports_multiple_mbean_instances(location)
271-
attrib_names = aliases.get_model_attribute_names(location)
272-
if last_token in attrib_names:
273-
found = False
274-
275-
debug("DEBUG: is_alias_folder %s %s", path, found)
275+
folder_names = self.aliases.get_model_subfolder_names(location)
276+
regular_type = not self.aliases.is_artificial_type_folder(location)
277+
security_type = regular_type and self.aliases.is_security_provider_type(location)
278+
multiple_type = regular_type and self.aliases.supports_multiple_mbean_instances(location)
279+
if multiple_type or security_type:
280+
name_token_next = True
281+
else:
282+
token_name = self.aliases.get_name_token(location)
283+
if not location.get_name_for_token(token_name):
284+
location.add_name_token(token_name, "TOKEN")
285+
elif path_token in attribute_names:
286+
attribute_name = path_token
287+
name_token_next = False
288+
else:
289+
ex = exception_helper.create_compare_exception('WLSDPLY-05712', path_token, path)
290+
_logger.throwing(ex, class_name=_class_name, method_name=_method_name)
291+
raise ex
276292

277-
return found
293+
return location, attribute_name
278294

279-
def _add_results(self, ar_changes, is_delete=False, is_change=False):
295+
def _add_results(self, change_paths, is_delete=False):
280296
"""
281297
Update the differences in the final model dictionary with the changes
282-
:param ar_changes: Array of changes in delimited format
298+
:param change_paths: Array of changes in delimited format
299+
:param is_delete: flag indicating to delete paths
283300
"""
284-
# The ar_changes is the keys of changes in the piped format
285-
# 'resources|JDBCSystemResource|Generic2|JdbcResource|JDBCConnectionPoolParams|TestConnectionsOnReserve
286-
#
287301
parent_index = -2
288-
for item in ar_changes:
289-
if is_delete:
290-
# Skipp adding if it is a delete of an attribute
291-
found_in_allowable_delete = self._is_alias_folder(item)
292-
if not found_in_allowable_delete:
293-
compare_msgs.add(('WLSDPLY-05701', item))
294-
continue
295-
splitted = item.split(PATH_TOKEN, 1)
296-
n = len(splitted)
297-
result = PyOrderedDict()
298-
walked = []
299-
300-
while n > 1:
301-
tmp = PyOrderedDict()
302-
tmp[splitted[0]] = PyOrderedDict()
303-
if len(result) > 0:
304-
# traverse to the leaf
305-
leaf = result
306-
for k in walked:
307-
leaf = leaf[k]
308-
leaf[splitted[0]] = PyOrderedDict()
309-
walked.append(splitted[0])
302+
for change_path in change_paths:
303+
# change_path is the keys of changes in the piped format, such as:
304+
# resources|JDBCSystemResource|Generic2|JdbcResource|JDBCConnectionPoolParams|TestConnectionsOnReserve
305+
location, attribute_name = self._parse_change_path(change_path)
306+
is_folder_path = attribute_name is None
307+
308+
if is_delete and not is_folder_path:
309+
# Skip adding if it is a delete of an attribute
310+
compare_msgs.add(('WLSDPLY-05701', change_path))
311+
continue
312+
313+
# splitted is a tuple containing the next token, and a delimited string of remaining tokens
314+
splitted = change_path.split(PATH_TOKEN, 1)
315+
316+
# change_tree will be a nested dictionary containing the change path parent elements.
317+
# change_tokens is a list of parent tokens in change_tree.
318+
change_tree = PyOrderedDict()
319+
change_tokens = []
320+
321+
while len(splitted) > 1:
322+
tmp_folder = PyOrderedDict()
323+
tmp_folder[splitted[0]] = PyOrderedDict()
324+
if len(change_tree) > 0:
325+
# traverse to the leaf folder
326+
change_folder = change_tree
327+
for token in change_tokens:
328+
change_folder = change_folder[token]
329+
change_folder[splitted[0]] = PyOrderedDict()
330+
change_tokens.append(splitted[0])
310331
else:
311-
result = tmp
312-
walked.append(splitted[0])
332+
change_tree = tmp_folder
333+
change_tokens.append(splitted[0])
313334
splitted = splitted[1].split(PATH_TOKEN, 1)
314-
n = len(splitted)
315-
#
316-
# result is the dictionary format
317-
#
318-
leaf = result
319-
if is_change:
320-
value_tree = self.past_dict
321-
else:
322-
value_tree = self.current_dict
323-
for k in walked:
324-
leaf = leaf[k]
325-
value_tree = value_tree[k]
326-
#
327-
# walk the current dictionary and set the value
328-
#
329-
if value_tree:
330-
if is_change:
331-
leaf[COMMENT_MATCH + splitted[0]] = value_tree[splitted[0]]
332-
else:
333-
if value_tree[splitted[0]] is not None and not isinstance(value_tree[splitted[0]], PyOrderedDict):
334-
self._add_results(ar_changes, is_delete, is_change=True)
335-
leaf[splitted[0]] = value_tree[splitted[0]]
335+
336+
# key is the last name in the change path
337+
key = splitted[0]
338+
339+
# find the specified folder in the change tree and in the current and previous models
340+
change_folder = change_tree
341+
current_folder = self.current_dict
342+
previous_folder = self.past_dict
343+
for token in change_tokens:
344+
change_folder = change_folder[token]
345+
current_folder = current_folder[token]
346+
previous_folder = dictionary_utils.get_dictionary_element(previous_folder, token)
347+
348+
# set the value in the change folder if present.
349+
# merge new and previous values if relevant.
350+
# add a comment if the previous value was found.
351+
if current_folder:
352+
current_value = current_folder[key]
353+
previous_value = dictionary_utils.get_element(previous_folder, key)
354+
change_value, comment = self._get_change_info(current_value, previous_value, location, attribute_name)
355+
356+
if comment:
357+
change_folder[COMMENT_MATCH] = comment
358+
change_folder[key] = change_value
336359
else:
337-
leaf[splitted[0]] = None
360+
change_folder[key] = None
338361

339-
self.merge_dictionaries(self.final_changed_model, result)
362+
# merge the change tree into the final model
363+
self.merge_dictionaries(self.final_changed_model, change_tree)
340364

341365
# if it is a deletion then go back and update with '!'
342366

343367
if is_delete:
344-
is_folder_path = self._is_alias_folder(item)
345-
split_delete = item.split(PATH_TOKEN)
368+
split_delete = change_path.split(PATH_TOKEN)
346369
# allowable_delete_length = len(allowable_delete.split(PATH_TOKEN))
347370
split_delete_length = len(split_delete)
348371
if is_folder_path:
349372
app_key = split_delete[split_delete_length - 1]
350373
parent_key = split_delete[parent_index]
351-
debug("DEBUG: deleting folder %s from the model: key %s ", item, app_key)
374+
debug("DEBUG: deleting folder %s from the model: key %s ", change_path, app_key)
352375
pointer_dict = self.final_changed_model
353376
for k_item in split_delete:
354377
if k_item == parent_key:
@@ -364,6 +387,44 @@ def _add_results(self, ar_changes, is_delete=False, is_change=False):
364387
else:
365388
pointer_dict[parent_key]['!' + app_key] = PyOrderedDict()
366389

390+
def _get_change_info(self, current_value, previous_value, location, attribute_name):
391+
"""
392+
Determine the value and comment to put in the change model based on the supplied arguments.
393+
:param current_value: the current value from the new model
394+
:param previous_value: the previous value from the old model
395+
:param location: the location of the value in the model
396+
:param attribute_name: the name of the attribute, or None if this is a folder path
397+
:return: a tuple with the change value and comment, either can be None
398+
"""
399+
change_value = current_value
400+
comment = None
401+
402+
if attribute_name and (previous_value is not None):
403+
attribute_type = self.aliases.get_model_attribute_type(location, attribute_name)
404+
if attribute_type in ALIAS_LIST_TYPES:
405+
current_list = alias_utils.create_list(current_value, 'WLSDPLY-08001')
406+
previous_list = alias_utils.create_list(previous_value, 'WLSDPLY-08000')
407+
408+
change_list = list(previous_list)
409+
for item in current_list:
410+
if item in previous_list:
411+
change_list.remove(item)
412+
else:
413+
change_list.append(item)
414+
for item in previous_list:
415+
if item not in current_list:
416+
change_list.remove(item)
417+
change_list.append(model_helper.get_delete_name(item))
418+
change_value = ','.join(change_list)
419+
420+
current_text = ','.join(current_list)
421+
previous_text = ','.join(previous_list)
422+
comment = attribute_name + ": '" + previous_text + "' -> '" + current_text + "'"
423+
elif not isinstance(previous_value, dict):
424+
comment = attribute_name + ": '" + str(previous_value) + "'"
425+
426+
return change_value, comment
427+
367428
def merge_dictionaries(self, dictionary, new_dictionary):
368429
"""
369430
Merge the values from the new dictionary to the existing one.
@@ -432,7 +493,8 @@ def compare(self):
432493

433494
self.model_context.set_validation_method('lax')
434495

435-
aliases = Aliases(model_context=self.model_context, wlst_mode=WlstModes.OFFLINE)
496+
aliases = Aliases(model_context=self.model_context, wlst_mode=WlstModes.OFFLINE,
497+
exception_type=ExceptionType.COMPARE)
436498

437499
validator = Validator(self.model_context, aliases, wlst_mode=WlstModes.OFFLINE)
438500

@@ -498,7 +560,7 @@ def compare(self):
498560
_logger.throwing(ex, class_name=_class_name, method_name=_method_name)
499561
return VALIDATION_FAIL
500562

501-
obj = ModelDiffer(current_dict, past_dict)
563+
obj = ModelDiffer(current_dict, past_dict, aliases)
502564
obj.calculate_changed_model()
503565
net_diff = obj.get_final_changed_model()
504566

core/src/main/python/wlsdeploy/util/model_helper.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,14 @@ def get_delete_item_name(name):
3636
ex = exception_helper.create_deploy_exception('WLSDPLY-09111', name)
3737
_logger.throwing(ex, class_name=_class_name, method_name=_method_name)
3838
raise ex
39+
40+
41+
def get_delete_name(name):
42+
"""
43+
Returns the delete name for the specified name by adding a "!" prefix.
44+
:param name: the name be adjusted
45+
:return: the delete name for the name
46+
"""
47+
_method_name = 'get_delete_name'
48+
49+
return "!" + name

core/src/main/python/wlsdeploy/yaml/yaml_translator.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import oracle.weblogic.deploy.yaml.YamlTranslator as JYamlTranslator
1818

1919
from wlsdeploy.exception import exception_helper
20+
from wlsdeploy.json.json_translator import COMMENT_MATCH
2021
from wlsdeploy.logging.platform_logger import PlatformLogger
2122
from wlsdeploy.yaml.dictionary_list import DictionaryList
2223

@@ -166,7 +167,9 @@ def _write_dictionary_to_yaml_file(self, dictionary, writer, indent=''):
166167

167168
for key, value in dictionary.iteritems():
168169
quoted_key = self._quotify_string(key)
169-
if isinstance(value, DictionaryList):
170+
if key.startswith(COMMENT_MATCH):
171+
writer.println(indent + "# " + str(value))
172+
elif isinstance(value, DictionaryList):
170173
writer.println(indent + quoted_key + ':')
171174
self._write_dictionary_list_to_yaml_file(value, writer, indent)
172175
elif isinstance(value, dict):

core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ WLSDPLY-05708=An error occurred while trying to write file {0}: {1}
482482
WLSDPLY-05709=Fatal compare model error {0}
483483
WLSDPLY-05710=The models are identical
484484
WLSDPLY-05711=The model differences and output is written to the directory {0}
485+
WLSDPLY-05712=Unrecognized token {0} in path {1}
485486

486487
# prepare_model.py
487488
WLSDPLY-05801=Error in prepare model {0}

0 commit comments

Comments
 (0)