|
3 | 3 | import click
|
4 | 4 | import ipaddress
|
5 | 5 | import json
|
| 6 | +import jsonpatch |
6 | 7 | import netaddr
|
7 | 8 | import netifaces
|
8 | 9 | import os
|
|
11 | 12 | import sys
|
12 | 13 | import time
|
13 | 14 |
|
| 15 | +from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat |
14 | 16 | from socket import AF_INET, AF_INET6
|
15 | 17 | from minigraph import parse_device_desc_xml
|
16 | 18 | from portconfig import get_child_ports
|
@@ -826,7 +828,7 @@ def cache_arp_entries():
|
826 | 828 | if filter_err:
|
827 | 829 | click.echo("Could not filter FDB entries prior to reloading")
|
828 | 830 | success = False
|
829 |
| - |
| 831 | + |
830 | 832 | # If we are able to successfully cache ARP table info, signal SWSS to restore from our cache
|
831 | 833 | # by creating /host/config-reload/needs-restore
|
832 | 834 | if success:
|
@@ -987,6 +989,129 @@ def load(filename, yes):
|
987 | 989 | log.log_info("'load' executing...")
|
988 | 990 | clicommon.run_command(command, display_cmd=True)
|
989 | 991 |
|
| 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) |
990 | 1115 |
|
991 | 1116 | @config.command()
|
992 | 1117 | @click.option('-y', '--yes', is_flag=True)
|
@@ -2581,8 +2706,8 @@ def add(ctx, interface_name, ip_addr, gw):
|
2581 | 2706 | if interface_name is None:
|
2582 | 2707 | ctx.fail("'interface_name' is None!")
|
2583 | 2708 |
|
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 |
2586 | 2711 | vlan_member_table = config_db.get_table('VLAN_MEMBER')
|
2587 | 2712 | if (interface_is_in_vlan(vlan_member_table, interface_name)):
|
2588 | 2713 | click.echo("Interface {} is a member of vlan\nAborting!".format(interface_name))
|
|
0 commit comments