Skip to content

Commit

Permalink
Merge pull request #64 from xincunli-sonic/xincun/gcu-multiasic
Browse files Browse the repository at this point in the history
Add Multi ASIC support for apply-patch
  • Loading branch information
gechiang authored Apr 26, 2024
2 parents 26f49f4 + bbc154a commit 8d815da
Show file tree
Hide file tree
Showing 8 changed files with 682 additions and 98 deletions.
58 changes: 55 additions & 3 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import tempfile

from collections import OrderedDict
from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat
from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat, extract_scope
from minigraph import parse_device_desc_xml, minigraph_encoder
from natsort import natsorted
from portconfig import get_child_ports
Expand Down Expand Up @@ -1081,6 +1081,23 @@ def validate_gre_type(ctx, _, value):
except ValueError:
raise click.UsageError("{} is not a valid GRE type".format(value))

# Function to apply patch for a single ASIC.
def apply_patch_for_scope(scope_changes, results, config_format, verbose, dry_run, ignore_non_yang_tables, ignore_path):
scope, changes = scope_changes
# Replace localhost to DEFAULT_NAMESPACE which is db definition of Host
if scope.lower() == "localhost" or scope == "":
scope = multi_asic.DEFAULT_NAMESPACE

scope_for_log = scope if scope else "localhost"
try:
# Call apply_patch with the ASIC-specific changes and predefined parameters
GenericUpdater(namespace=scope).apply_patch(jsonpatch.JsonPatch(changes), config_format, verbose, dry_run, ignore_non_yang_tables, ignore_path)
results[scope_for_log] = {"success": True, "message": "Success"}
log.log_notice(f"'apply-patch' executed successfully for {scope_for_log} by {changes}")
except Exception as e:
results[scope_for_log] = {"success": False, "message": str(e)}
log.log_error(f"'apply-patch' executed failed for {scope_for_log} by {changes} due to {str(e)}")

# This is our main entrypoint - the main 'config' command
@click.group(cls=clicommon.AbbreviationGroup, context_settings=CONTEXT_SETTINGS)
@click.pass_context
Expand Down Expand Up @@ -1279,12 +1296,47 @@ def apply_patch(ctx, patch_file_path, format, dry_run, ignore_non_yang_tables, i
patch_as_json = json.loads(text)
patch = jsonpatch.JsonPatch(patch_as_json)

results = {}
config_format = ConfigFormat[format.upper()]
GenericUpdater().apply_patch(patch, config_format, verbose, dry_run, ignore_non_yang_tables, ignore_path)
# Initialize a dictionary to hold changes categorized by scope
changes_by_scope = {}

# Iterate over each change in the JSON Patch
for change in patch:
scope, modified_path = extract_scope(change["path"])

# Modify the 'path' in the change to remove the scope
change["path"] = modified_path

# Check if the scope is already in our dictionary, if not, initialize it
if scope not in changes_by_scope:
changes_by_scope[scope] = []

# Add the modified change to the appropriate list based on scope
changes_by_scope[scope].append(change)

# Empty case to force validate YANG model.
if not changes_by_scope:
asic_list = [multi_asic.DEFAULT_NAMESPACE]
asic_list.extend(multi_asic.get_namespace_list())
for asic in asic_list:
changes_by_scope[asic] = []

# Apply changes for each scope
for scope_changes in changes_by_scope.items():
apply_patch_for_scope(scope_changes, results, config_format, verbose, dry_run, ignore_non_yang_tables, ignore_path)

# Check if any updates failed
failures = [scope for scope, result in results.items() if not result['success']]

if failures:
failure_messages = '\n'.join([f"- {failed_scope}: {results[failed_scope]['message']}" for failed_scope in failures])
raise Exception(f"Failed to apply patch on the following scopes:\n{failure_messages}")

log.log_notice(f"Patch applied successfully for {patch}.")
click.secho("Patch applied successfully.", fg="cyan", underline=True)
except Exception as ex:
click.secho("Failed to apply patch", fg="red", underline=True, err=True)
click.secho("Failed to apply patch due to: {}".format(ex), fg="red", underline=True, err=True)
ctx.fail(ex)

@config.command()
Expand Down
44 changes: 30 additions & 14 deletions generic_config_updater/change_applier.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import jsondiff
import importlib
import os
import subprocess
import tempfile
from collections import defaultdict
from swsscommon.swsscommon import ConfigDBConnector
from .gu_common import genericUpdaterLogging
from sonic_py_common import multi_asic
from .gu_common import GenericConfigUpdaterError, genericUpdaterLogging

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
UPDATER_CONF_FILE = f"{SCRIPT_DIR}/gcu_services_validator.conf.json"
Expand All @@ -32,12 +34,11 @@ def log_error(m):
logger.log(logger.LOG_PRIORITY_ERROR, m, print_to_console)


def get_config_db():
config_db = ConfigDBConnector()
def get_config_db(namespace=multi_asic.DEFAULT_NAMESPACE):
config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace)
config_db.connect()
return config_db


def set_config(config_db, tbl, key, data):
config_db.set_entry(tbl, key, data)

Expand Down Expand Up @@ -73,8 +74,9 @@ class ChangeApplier:

updater_conf = None

def __init__(self):
self.config_db = get_config_db()
def __init__(self, namespace=multi_asic.DEFAULT_NAMESPACE):
self.namespace = namespace
self.config_db = get_config_db(self.namespace)
self.backend_tables = [
"BUFFER_PG",
"BUFFER_PROFILE",
Expand Down Expand Up @@ -160,18 +162,32 @@ def apply(self, change):
log_error("Failed to apply Json change")
return ret


def remove_backend_tables_from_config(self, data):
for key in self.backend_tables:
data.pop(key, None)


def _get_running_config(self):
(_, fname) = tempfile.mkstemp(suffix="_changeApplier")
os.system("sonic-cfggen -d --print-data > {}".format(fname))
run_data = {}
with open(fname, "r") as s:
run_data = json.load(s)
if os.path.isfile(fname):
_, fname = tempfile.mkstemp(suffix="_changeApplier")

if self.namespace:
cmd = ['sonic-cfggen', '-d', '--print-data', '-n', self.namespace]
else:
cmd = ['sonic-cfggen', '-d', '--print-data']

with open(fname, "w") as file:
result = subprocess.Popen(cmd, stdout=file, stderr=subprocess.PIPE, text=True)
_, err = result.communicate()

return_code = result.returncode
if return_code:
os.remove(fname)
raise GenericConfigUpdaterError(f"Failed to get running config for namespace: {self.namespace}, Return code: {return_code}, Error: {err}")

run_data = {}
try:
with open(fname, "r") as file:
run_data = json.load(file)
finally:
if os.path.isfile(fname):
os.remove(fname)
return run_data
Loading

0 comments on commit 8d815da

Please sign in to comment.