Skip to content

Commit 41d8ddc

Browse files
authored
[config][generic-update] Adding apply-patch, rollback, checkpoints commands (sonic-net#1536)
#### What I did Adding apply-patch, rollback, replace, checkpoint, delete-checkpoint, list-checkpoints functionality. #### How I did it This PR is implementing the first step in in README.md in the design document: sonic-net/SONiC#736 #### How to verify it Using unit-tests #### Previous command output (if the output of a command-line utility has changed) #### New command output (if the output of a command-line utility has changed) ```sh admin@sonic:~$ sudo config apply-patch --help Usage: config apply-patch [OPTIONS] PATCH_FILE_PATH Apply given patch of updates to Config. A patch is a JsonPatch which follows rfc6902. This command can be used do partial updates to the config with minimum disruption to running processes. It allows addition as well as deletion of configs. The patch file represents a diff of ConfigDb(ABNF) format or SonicYang format. <patch-file-path>: Path to the patch file on the file-system. Options: -f, --format [CONFIGDB|SONICYANG] format of config of the patch is either ConfigDb(ABNF) or SonicYang -d, --dry-run test out the command without affecting config state -v, --verbose print additional details of what the operation is doing -h, -?, --help Show this message and exit. admin@sonic:~$ sudo config replace --help Usage: config replace [OPTIONS] TARGET_FILE_PATH Replace the whole config with the specified config. The config is replaced with minimum disruption e.g. if ACL config is different between current and target config only ACL config is updated, and other config/services such as DHCP will not be affected. **WARNING** The target config file should be the whole config, not just the part intended to be updated. <target-file-path>: Path to the target file on the file-system. Options: -f, --format [CONFIGDB|SONICYANG] format of target config is either ConfigDb(ABNF) or SonicYang -d, --dry-run test out the command without affecting config state -v, --verbose print additional details of what the operation is doing -h, -?, --help Show this message and exit. admin@sonic:~$ sudo config rollback --help Usage: config rollback [OPTIONS] CHECKPOINT_NAME Rollback the whole config to the specified checkpoint. The config is rolled back with minimum disruption e.g. if ACL config is different between current and checkpoint config only ACL config is updated, and other config/services such as DHCP will not be affected. <checkpoint-name>: The checkpoint name, use `config list-checkpoints` command to see available checkpoints. Options: -d, --dry-run test out the command without affecting config state -v, --verbose print additional details of what the operation is doing -?, -h, --help Show this message and exit. admin@sonic:~$ sudo config checkpoint --help Usage: config checkpoint [OPTIONS] CHECKPOINT_NAME Take a checkpoint of the whole current config with the specified checkpoint name. <checkpoint-name>: The checkpoint name, use `config list-checkpoints` command to see available checkpoints. Options: -v, --verbose print additional details of what the operation is doing -h, -?, --help Show this message and exit. admin@sonic:~$ sudo config delete-checkpoint --help Usage: config delete-checkpoint [OPTIONS] CHECKPOINT_NAME Delete a checkpoint with the specified checkpoint name. <checkpoint-name>: The checkpoint name, use `config list-checkpoints` command to see available checkpoints. Options: -v, --verbose print additional details of what the operation is doing -h, -?, --help Show this message and exit. admin@sonic:~$ sudo config list-checkpoints --help Usage: config list-checkpoints [OPTIONS] List the config checkpoints available. Options: -v, --verbose print additional details of what the operation is doing -?, -h, --help Show this message and exit. ```
1 parent a3d37f1 commit 41d8ddc

24 files changed

+3437
-6
lines changed

.azure-pipelines/docker-sonic-vs/Dockerfile

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ ARG docker_container_name
44

55
ADD ["wheels", "/wheels"]
66

7-
RUN pip3 install --no-deps --force-reinstall /wheels/sonic_utilities-1.2-py3-none-any.whl
7+
# Uninstalls only sonic-utilities and does not impact its dependencies
8+
RUN pip3 uninstall -y sonic-utilities
9+
10+
# Installs sonic-utilities, adds missing dependencies, upgrades out-dated depndencies
11+
RUN pip3 install /wheels/sonic_utilities-1.2-py3-none-any.whl

config/main.py

+128-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import click
44
import ipaddress
55
import json
6+
import jsonpatch
67
import netaddr
78
import netifaces
89
import os
@@ -11,6 +12,7 @@
1112
import sys
1213
import time
1314

15+
from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat
1416
from socket import AF_INET, AF_INET6
1517
from minigraph import parse_device_desc_xml
1618
from portconfig import get_child_ports
@@ -826,7 +828,7 @@ def cache_arp_entries():
826828
if filter_err:
827829
click.echo("Could not filter FDB entries prior to reloading")
828830
success = False
829-
831+
830832
# If we are able to successfully cache ARP table info, signal SWSS to restore from our cache
831833
# by creating /host/config-reload/needs-restore
832834
if success:
@@ -987,6 +989,129 @@ def load(filename, yes):
987989
log.log_info("'load' executing...")
988990
clicommon.run_command(command, display_cmd=True)
989991

992+
@config.command('apply-patch')
993+
@click.argument('patch-file-path', type=str, required=True)
994+
@click.option('-f', '--format', type=click.Choice([e.name for e in ConfigFormat]),
995+
default=ConfigFormat.CONFIGDB.name,
996+
help='format of config of the patch is either ConfigDb(ABNF) or SonicYang')
997+
@click.option('-d', '--dry-run', is_flag=True, default=False, help='test out the command without affecting config state')
998+
@click.option('-v', '--verbose', is_flag=True, default=False, help='print additional details of what the operation is doing')
999+
@click.pass_context
1000+
def apply_patch(ctx, patch_file_path, format, dry_run, verbose):
1001+
"""Apply given patch of updates to Config. A patch is a JsonPatch which follows rfc6902.
1002+
This command can be used do partial updates to the config with minimum disruption to running processes.
1003+
It allows addition as well as deletion of configs. The patch file represents a diff of ConfigDb(ABNF)
1004+
format or SonicYang format.
1005+
1006+
<patch-file-path>: Path to the patch file on the file-system."""
1007+
try:
1008+
with open(patch_file_path, 'r') as fh:
1009+
text = fh.read()
1010+
patch_as_json = json.loads(text)
1011+
patch = jsonpatch.JsonPatch(patch_as_json)
1012+
1013+
config_format = ConfigFormat[format.upper()]
1014+
1015+
GenericUpdater().apply_patch(patch, config_format, verbose, dry_run)
1016+
1017+
click.secho("Patch applied successfully.", fg="cyan", underline=True)
1018+
except Exception as ex:
1019+
click.secho("Failed to apply patch", fg="red", underline=True, err=True)
1020+
ctx.fail(ex)
1021+
1022+
@config.command()
1023+
@click.argument('target-file-path', type=str, required=True)
1024+
@click.option('-f', '--format', type=click.Choice([e.name for e in ConfigFormat]),
1025+
default=ConfigFormat.CONFIGDB.name,
1026+
help='format of target config is either ConfigDb(ABNF) or SonicYang')
1027+
@click.option('-d', '--dry-run', is_flag=True, default=False, help='test out the command without affecting config state')
1028+
@click.option('-v', '--verbose', is_flag=True, default=False, help='print additional details of what the operation is doing')
1029+
@click.pass_context
1030+
def replace(ctx, target_file_path, format, dry_run, verbose):
1031+
"""Replace the whole config with the specified config. The config is replaced with minimum disruption e.g.
1032+
if ACL config is different between current and target config only ACL config is updated, and other config/services
1033+
such as DHCP will not be affected.
1034+
1035+
**WARNING** The target config file should be the whole config, not just the part intended to be updated.
1036+
1037+
<target-file-path>: Path to the target file on the file-system."""
1038+
try:
1039+
with open(target_file_path, 'r') as fh:
1040+
target_config_as_text = fh.read()
1041+
target_config = json.loads(target_config_as_text)
1042+
1043+
config_format = ConfigFormat[format.upper()]
1044+
1045+
GenericUpdater().replace(target_config, config_format, verbose, dry_run)
1046+
1047+
click.secho("Config replaced successfully.", fg="cyan", underline=True)
1048+
except Exception as ex:
1049+
click.secho("Failed to replace config", fg="red", underline=True, err=True)
1050+
ctx.fail(ex)
1051+
1052+
@config.command()
1053+
@click.argument('checkpoint-name', type=str, required=True)
1054+
@click.option('-d', '--dry-run', is_flag=True, default=False, help='test out the command without affecting config state')
1055+
@click.option('-v', '--verbose', is_flag=True, default=False, help='print additional details of what the operation is doing')
1056+
@click.pass_context
1057+
def rollback(ctx, checkpoint_name, dry_run, verbose):
1058+
"""Rollback the whole config to the specified checkpoint. The config is rolled back with minimum disruption e.g.
1059+
if ACL config is different between current and checkpoint config only ACL config is updated, and other config/services
1060+
such as DHCP will not be affected.
1061+
1062+
<checkpoint-name>: The checkpoint name, use `config list-checkpoints` command to see available checkpoints."""
1063+
try:
1064+
GenericUpdater().rollback(checkpoint_name, verbose, dry_run)
1065+
1066+
click.secho("Config rolled back successfully.", fg="cyan", underline=True)
1067+
except Exception as ex:
1068+
click.secho("Failed to rollback config", fg="red", underline=True, err=True)
1069+
ctx.fail(ex)
1070+
1071+
@config.command()
1072+
@click.argument('checkpoint-name', type=str, required=True)
1073+
@click.option('-v', '--verbose', is_flag=True, default=False, help='print additional details of what the operation is doing')
1074+
@click.pass_context
1075+
def checkpoint(ctx, checkpoint_name, verbose):
1076+
"""Take a checkpoint of the whole current config with the specified checkpoint name.
1077+
1078+
<checkpoint-name>: The checkpoint name, use `config list-checkpoints` command to see available checkpoints."""
1079+
try:
1080+
GenericUpdater().checkpoint(checkpoint_name, verbose)
1081+
1082+
click.secho("Checkpoint created successfully.", fg="cyan", underline=True)
1083+
except Exception as ex:
1084+
click.secho("Failed to create a config checkpoint", fg="red", underline=True, err=True)
1085+
ctx.fail(ex)
1086+
1087+
@config.command('delete-checkpoint')
1088+
@click.argument('checkpoint-name', type=str, required=True)
1089+
@click.option('-v', '--verbose', is_flag=True, default=False, help='print additional details of what the operation is doing')
1090+
@click.pass_context
1091+
def delete_checkpoint(ctx, checkpoint_name, verbose):
1092+
"""Delete a checkpoint with the specified checkpoint name.
1093+
1094+
<checkpoint-name>: The checkpoint name, use `config list-checkpoints` command to see available checkpoints."""
1095+
try:
1096+
GenericUpdater().delete_checkpoint(checkpoint_name, verbose)
1097+
1098+
click.secho("Checkpoint deleted successfully.", fg="cyan", underline=True)
1099+
except Exception as ex:
1100+
click.secho("Failed to delete config checkpoint", fg="red", underline=True, err=True)
1101+
ctx.fail(ex)
1102+
1103+
@config.command('list-checkpoints')
1104+
@click.option('-v', '--verbose', is_flag=True, default=False, help='print additional details of what the operation is doing')
1105+
@click.pass_context
1106+
def list_checkpoints(ctx, verbose):
1107+
"""List the config checkpoints available."""
1108+
try:
1109+
checkpoints_list = GenericUpdater().list_checkpoints(verbose)
1110+
formatted_output = json.dumps(checkpoints_list, indent=4)
1111+
click.echo(formatted_output)
1112+
except Exception as ex:
1113+
click.secho("Failed to list config checkpoints", fg="red", underline=True, err=True)
1114+
ctx.fail(ex)
9901115

9911116
@config.command()
9921117
@click.option('-y', '--yes', is_flag=True)
@@ -2581,8 +2706,8 @@ def add(ctx, interface_name, ip_addr, gw):
25812706
if interface_name is None:
25822707
ctx.fail("'interface_name' is None!")
25832708

2584-
# Add a validation to check this interface is not a member in vlan before
2585-
# changing it to a router port
2709+
# Add a validation to check this interface is not a member in vlan before
2710+
# changing it to a router port
25862711
vlan_member_table = config_db.get_table('VLAN_MEMBER')
25872712
if (interface_is_in_vlan(vlan_member_table, interface_name)):
25882713
click.echo("Interface {} is a member of vlan\nAborting!".format(interface_name))

generic_config_updater/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)