Skip to content

Commit 4bcaa60

Browse files
authored
[generic-config-updater] Adding logging to config-{replace, rollback, checkpoint, list-checkpoints} (sonic-net#1885)
#### What I did Adding more logging to other CLI commands `config replace`, `config rollback`, `config checkpoint`, `config list-checkpoints` and `config delete-checkpoint`
1 parent 5e95fc3 commit 4bcaa60

File tree

2 files changed

+73
-20
lines changed

2 files changed

+73
-20
lines changed

generic_config_updater/generic_updater.py

+68-16
Original file line numberDiff line numberDiff line change
@@ -29,118 +29,170 @@ def __init__(self,
2929
changeapplier=None,
3030
config_wrapper=None,
3131
patch_wrapper=None):
32-
self.logger = genericUpdaterLogging.get_logger(title="Patch Applier")
32+
self.logger = genericUpdaterLogging.get_logger(title="Patch Applier", print_all_to_console=True)
3333
self.config_wrapper = config_wrapper if config_wrapper is not None else ConfigWrapper()
3434
self.patch_wrapper = patch_wrapper if patch_wrapper is not None else PatchWrapper()
3535
self.patchsorter = patchsorter if patchsorter is not None else PatchSorter(self.config_wrapper, self.patch_wrapper)
3636
self.changeapplier = changeapplier if changeapplier is not None else ChangeApplier()
3737

3838
def apply(self, patch):
39-
print_to_console=True
40-
self.logger.log_notice("Patch application starting.", print_to_console)
41-
self.logger.log_notice(f"Patch: {patch}", print_to_console)
39+
self.logger.log_notice("Patch application starting.")
40+
self.logger.log_notice(f"Patch: {patch}")
4241

4342
# validate patch is only updating tables with yang models
44-
self.logger.log_notice("Validating patch is not making changes to tables without YANG models.", print_to_console)
43+
self.logger.log_notice("Validating patch is not making changes to tables without YANG models.")
4544
if not(self.patch_wrapper.validate_config_db_patch_has_yang_models(patch)):
4645
raise ValueError(f"Given patch is not valid because it has changes to tables without YANG models")
4746

4847
# Get old config
49-
self.logger.log_notice("Getting current config db.", print_to_console)
48+
self.logger.log_notice("Getting current config db.")
5049
old_config = self.config_wrapper.get_config_db_as_json()
5150

5251
# Generate target config
53-
self.logger.log_notice("Simulating the target full config after applying the patch.", print_to_console)
52+
self.logger.log_notice("Simulating the target full config after applying the patch.")
5453
target_config = self.patch_wrapper.simulate_patch(patch, old_config)
5554

5655
# Validate target config
57-
self.logger.log_notice("Validating target config according to YANG models.", print_to_console)
56+
self.logger.log_notice("Validating target config according to YANG models.")
5857
if not(self.config_wrapper.validate_config_db_config(target_config)):
5958
raise ValueError(f"Given patch is not valid because it will result in an invalid config")
6059

6160
# Generate list of changes to apply
62-
self.logger.log_notice("Sorting patch updates.", print_to_console)
61+
self.logger.log_notice("Sorting patch updates.")
6362
changes = self.patchsorter.sort(patch)
6463
changes_len = len(changes)
6564
self.logger.log_notice(f"The patch was sorted into {changes_len} " \
66-
f"change{'s' if changes_len != 1 else ''}{':' if changes_len > 0 else '.'}",
67-
print_to_console)
65+
f"change{'s' if changes_len != 1 else ''}{':' if changes_len > 0 else '.'}")
6866
for change in changes:
69-
self.logger.log_notice(f" * {change}", print_to_console)
67+
self.logger.log_notice(f" * {change}")
7068

7169
# Apply changes in order
72-
self.logger.log_notice("Applying changes in order.", print_to_console)
70+
self.logger.log_notice("Applying changes in order.")
7371
for change in changes:
7472
self.changeapplier.apply(change)
7573

7674
# Validate config updated successfully
77-
self.logger.log_notice("Verifying patch updates are reflected on ConfigDB.", print_to_console)
75+
self.logger.log_notice("Verifying patch updates are reflected on ConfigDB.")
7876
new_config = self.config_wrapper.get_config_db_as_json()
7977
if not(self.patch_wrapper.verify_same_json(target_config, new_config)):
8078
raise GenericConfigUpdaterError(f"After applying patch to config, there are still some parts not updated")
8179

82-
self.logger.log_notice("Patch application completed.", print_to_console)
80+
self.logger.log_notice("Patch application completed.")
8381

8482
class ConfigReplacer:
8583
def __init__(self, patch_applier=None, config_wrapper=None, patch_wrapper=None):
84+
self.logger = genericUpdaterLogging.get_logger(title="Config Replacer", print_all_to_console=True)
8685
self.patch_applier = patch_applier if patch_applier is not None else PatchApplier()
8786
self.config_wrapper = config_wrapper if config_wrapper is not None else ConfigWrapper()
8887
self.patch_wrapper = patch_wrapper if patch_wrapper is not None else PatchWrapper()
8988

9089
def replace(self, target_config):
90+
self.logger.log_notice("Config replacement starting.")
91+
self.logger.log_notice(f"Target config length: {len(json.dumps(target_config))}.")
92+
93+
self.logger.log_notice("Validating target config according to YANG models.")
9194
if not(self.config_wrapper.validate_config_db_config(target_config)):
9295
raise ValueError(f"The given target config is not valid")
9396

97+
self.logger.log_notice("Getting current config db.")
9498
old_config = self.config_wrapper.get_config_db_as_json()
99+
100+
self.logger.log_notice("Generating patch between target config and current config db.")
95101
patch = self.patch_wrapper.generate_patch(old_config, target_config)
102+
self.logger.log_debug(f"Generated patch: {patch}.") # debug since the patch will printed again in 'patch_applier.apply'
96103

104+
self.logger.log_notice("Applying patch using 'Patch Applier'.")
97105
self.patch_applier.apply(patch)
98106

107+
self.logger.log_notice("Verifying config replacement is reflected on ConfigDB.")
99108
new_config = self.config_wrapper.get_config_db_as_json()
100109
if not(self.patch_wrapper.verify_same_json(target_config, new_config)):
101110
raise GenericConfigUpdaterError(f"After replacing config, there is still some parts not updated")
102111

112+
self.logger.log_notice("Config replacement completed.")
113+
103114
class FileSystemConfigRollbacker:
104115
def __init__(self,
105116
checkpoints_dir=CHECKPOINTS_DIR,
106117
config_replacer=None,
107118
config_wrapper=None):
119+
self.logger = genericUpdaterLogging.get_logger(title="Config Rollbacker", print_all_to_console=True)
108120
self.checkpoints_dir = checkpoints_dir
109121
self.config_replacer = config_replacer if config_replacer is not None else ConfigReplacer()
110122
self.config_wrapper = config_wrapper if config_wrapper is not None else ConfigWrapper()
111123

112124
def rollback(self, checkpoint_name):
125+
self.logger.log_notice("Config rollbacking starting.")
126+
self.logger.log_notice(f"Checkpoint name: {checkpoint_name}.")
127+
128+
self.logger.log_notice(f"Verifying '{checkpoint_name}' exists.")
113129
if not self._check_checkpoint_exists(checkpoint_name):
114130
raise ValueError(f"Checkpoint '{checkpoint_name}' does not exist")
115131

132+
self.logger.log_notice(f"Loading checkpoint into memory.")
116133
target_config = self._get_checkpoint_content(checkpoint_name)
117134

135+
self.logger.log_notice(f"Replacing config using 'Config Replacer'.")
118136
self.config_replacer.replace(target_config)
119137

138+
self.logger.log_notice("Config rollbacking completed.")
139+
120140
def checkpoint(self, checkpoint_name):
141+
self.logger.log_notice("Config checkpoint starting.")
142+
self.logger.log_notice(f"Checkpoint name: {checkpoint_name}.")
143+
144+
self.logger.log_notice("Getting current config db.")
121145
json_content = self.config_wrapper.get_config_db_as_json()
122146

147+
# if current config are not valid, we might not be able to rollback to it. So fail early by not taking checkpoint at all.
148+
self.logger.log_notice("Validating current config according to YANG models.")
123149
if not self.config_wrapper.validate_config_db_config(json_content):
124150
raise ValueError(f"Running configs on the device are not valid.")
125151

152+
self.logger.log_notice("Getting checkpoint full-path.")
126153
path = self._get_checkpoint_full_path(checkpoint_name)
127154

155+
self.logger.log_notice("Ensuring checkpoint directory exist.")
128156
self._ensure_checkpoints_dir_exists()
129157

158+
self.logger.log_notice(f"Saving config db content to {path}.")
130159
self._save_json_file(path, json_content)
131160

161+
self.logger.log_notice("Config checkpoint completed.")
162+
132163
def list_checkpoints(self):
164+
self.logger.log_info("Listing checkpoints starting.")
165+
166+
self.logger.log_info(f"Verifying checkpoints directory '{self.checkpoints_dir}' exists.")
133167
if not self._checkpoints_dir_exist():
168+
self.logger.log_info("Checkpoints directory is empty, returning empty checkpoints list.")
134169
return []
135170

136-
return self._get_checkpoint_names()
171+
self.logger.log_info("Getting checkpoints in checkpoints directory.")
172+
checkpoint_names = self._get_checkpoint_names()
173+
174+
checkpoints_len = len(checkpoint_names)
175+
self.logger.log_info(f"Found {checkpoints_len} checkpoint{'s' if checkpoints_len != 1 else ''}{':' if checkpoints_len > 0 else '.'}")
176+
for checkpoint_name in checkpoint_names:
177+
self.logger.log_info(f" * {checkpoint_name}")
178+
179+
self.logger.log_info("Listing checkpoints completed.")
180+
181+
return checkpoint_names
137182

138183
def delete_checkpoint(self, checkpoint_name):
184+
self.logger.log_notice("Deleting checkpoint starting.")
185+
self.logger.log_notice(f"Checkpoint name: {checkpoint_name}.")
186+
187+
self.logger.log_notice(f"Checking checkpoint exists.")
139188
if not self._check_checkpoint_exists(checkpoint_name):
140189
raise ValueError(f"Checkpoint '{checkpoint_name}' does not exist")
141190

191+
self.logger.log_notice(f"Deleting checkpoint.")
142192
self._delete_checkpoint(checkpoint_name)
143193

194+
self.logger.log_notice("Deleting checkpoint completed.")
195+
144196
def _ensure_checkpoints_dir_exists(self):
145197
os.makedirs(self.checkpoints_dir, exist_ok=True)
146198

generic_config_updater/gu_common.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -695,15 +695,16 @@ def _get_model(self, model, name):
695695
return None
696696

697697
class TitledLogger(logger.Logger):
698-
def __init__(self, syslog_identifier, title, verbose):
698+
def __init__(self, syslog_identifier, title, verbose, print_all_to_console):
699699
super().__init__(syslog_identifier)
700700
self._title = title
701701
if verbose:
702702
self.set_min_log_priority_debug()
703+
self.print_all_to_console = print_all_to_console
703704

704705
def log(self, priority, msg, also_print_to_console=False):
705706
combined_msg = f"{self._title}: {msg}"
706-
super().log(priority, combined_msg, also_print_to_console)
707+
super().log(priority, combined_msg, self.print_all_to_console or also_print_to_console)
707708

708709
class GenericUpdaterLogging:
709710
def __init__(self):
@@ -712,7 +713,7 @@ def __init__(self):
712713
def set_verbose(self, verbose):
713714
self._verbose = verbose
714715

715-
def get_logger(self, title):
716-
return TitledLogger(SYSLOG_IDENTIFIER, title, self._verbose)
716+
def get_logger(self, title, print_all_to_console=False):
717+
return TitledLogger(SYSLOG_IDENTIFIER, title, self._verbose, print_all_to_console)
717718

718719
genericUpdaterLogging = GenericUpdaterLogging()

0 commit comments

Comments
 (0)