diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6899bdc26..b048a96fd 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -72,6 +72,7 @@ stages: artifact: sonic-swss-common.bullseye.amd64 runVersion: 'latestFromBranch' runBranch: 'refs/heads/$(sourceBranch)' + allowPartiallySucceededBuilds: true displayName: "Download sonic swss common deb packages" - script: | diff --git a/config/main.py b/config/main.py index 27621683b..42d677086 100644 --- a/config/main.py +++ b/config/main.py @@ -13,6 +13,7 @@ import time import itertools import copy +import tempfile from collections import OrderedDict from generic_config_updater.generic_updater import GenericUpdater, ConfigFormat @@ -131,6 +132,14 @@ def read_json_file(fileName): raise Exception(str(e)) return result +# write given JSON file +def write_json_file(json_input, fileName): + try: + with open(fileName, 'w') as f: + json.dump(json_input, f, indent=4) + except Exception as e: + raise Exception(str(e)) + def _get_breakout_options(ctx, args, incomplete): """ Provides dynamic mode option as per user argument i.e. interface name """ all_mode_options = [] @@ -1452,6 +1461,12 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, force, file_form # Get the file from user input, else take the default file /etc/sonic/config_db{NS_id}.json if cfg_files: file = cfg_files[inst+1] + # Save to tmpfile in case of stdin input which can only be read once + if file == "/dev/stdin": + file_input = read_json_file(file) + (_, tmpfname) = tempfile.mkstemp(dir="/tmp", suffix="_configReloadStdin") + write_json_file(file_input, tmpfname) + file = tmpfname else: if file_format == 'config_db': if namespace is None: @@ -1467,6 +1482,19 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, force, file_form click.echo("The config file {} doesn't exist".format(file)) continue + if file_format == 'config_db': + file_input = read_json_file(file) + + platform = file_input.get("DEVICE_METADATA", {}).\ + get("localhost", {}).get("platform") + mac = file_input.get("DEVICE_METADATA", {}).\ + get("localhost", {}).get("mac") + + if not platform or not mac: + log.log_warning("Input file does't have platform or mac. platform: {}, mac: {}" + .format(None if platform is None else platform, None if mac is None else mac)) + load_sysinfo = True + if load_sysinfo: try: command = "{} -j {} -v DEVICE_METADATA.localhost.hwsku".format(SONIC_CFGGEN_PATH, file) @@ -1528,6 +1556,13 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart, force, file_form clicommon.run_command(command, display_cmd=True) client.set(config_db.INIT_INDICATOR, 1) + if os.path.exists(file) and file.endswith("_configReloadStdin"): + # Remove tmpfile + try: + os.remove(file) + except OSError as e: + click.echo("An error occurred while removing the temporary file: {}".format(str(e)), err=True) + # Migrate DB contents to latest version db_migrator='/usr/local/bin/db_migrator.py' if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): @@ -4058,28 +4093,12 @@ def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load click.secho("[ERROR] port_dict is None!", fg='red') raise click.Abort() - """ Special Case: Dont delete those ports where the current mode and speed of the parent port - remains unchanged to limit the traffic impact """ - - click.secho("\nAfter running Logic to limit the impact", fg="cyan", underline=True) - matched_items = [intf for intf in del_intf_dict if intf in add_intf_dict and del_intf_dict[intf] == add_intf_dict[intf]] - - # Remove the interface which remains unchanged from both del_intf_dict and add_intf_dict - for item in matched_items: - del_intf_dict.pop(item) - add_intf_dict.pop(item) - # validate all del_ports before calling breakOutPort for intf in del_intf_dict.keys(): if not interface_name_is_valid(config_db, intf): click.secho("[ERROR] Interface name {} is invalid".format(intf)) raise click.Abort() - click.secho("\nFinal list of ports to be deleted : \n {} \nFinal list of ports to be added : \n {}".format(json.dumps(del_intf_dict, indent=4), json.dumps(add_intf_dict, indent=4), fg='green', blink=True)) - if not add_intf_dict: - click.secho("[ERROR] add_intf_dict is None or empty! No interfaces are there to be added", fg='red') - raise click.Abort() - port_dict = {} for intf in add_intf_dict: if intf in add_ports: @@ -6350,7 +6369,7 @@ def disable(ctx): def polling_int(ctx, interval): """Set polling-interval for counter-sampling (0 to disable)""" if interval not in range(5, 301) and interval != 0: - click.echo("Polling interval must be between 5-300 (0 to disable)") + ctx.fail("Polling interval must be between 5-300 (0 to disable)") config_db = ctx.obj['db'] sflow_tbl = config_db.get_table('SFLOW') diff --git a/scripts/fast-reboot b/scripts/fast-reboot index b89592e27..30b0c787d 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -30,6 +30,7 @@ TAG_LATEST=yes DETACH=no LOG_PATH="/var/log/${REBOOT_TYPE}.txt" UIMAGE_HDR_SIZE=64 +REQUIRE_TEAMD_RETRY_COUNT=no # Require 100M available on the hard drive for warm reboot temp files, # Size is in 1K blocks: @@ -47,6 +48,7 @@ EXIT_DB_INTEGRITY_FAILURE=15 EXIT_NO_CONTROL_PLANE_ASSISTANT=20 EXIT_SONIC_INSTALLER_VERIFY_REBOOT=21 EXIT_PLATFORM_FW_AU_FAILURE=22 +EXIT_TEAMD_RETRY_COUNT_FAILURE=23 function error() { @@ -77,6 +79,8 @@ function showHelpAndExit() echo " - control plane assistant IP list." echo " -t : Don't tag the current kube images as latest" echo " -D : detached mode - closing terminal will not cause stopping reboot" + echo " -n : don't require peer devices to be running SONiC with retry count feature [default]" + echo " -N : require peer devices to be running SONiC with retry count feature" exit "${EXIT_SUCCESS}" } @@ -121,6 +125,12 @@ function parseOptions() D ) DETACH=yes ;; + n ) + REQUIRE_TEAMD_RETRY_COUNT=no + ;; + N ) + REQUIRE_TEAMD_RETRY_COUNT=yes + ;; esac done } @@ -605,6 +615,22 @@ init_warm_reboot_states setup_control_plane_assistant +TEAMD_INCREASE_RETRY_COUNT=0 +if [[ "${REBOOT_TYPE}" = "warm-reboot" || "${REBOOT_TYPE}" = "fastfast-reboot" ]]; then + TEAMD_RETRY_COUNT_PROBE_RC=0 + /usr/local/bin/teamd_increase_retry_count.py --probe-only || TEAMD_RETRY_COUNT_PROBE_RC=$? + if [[ ${TEAMD_RETRY_COUNT_PROBE_RC} -ne 0 ]]; then + if [[ "${REQUIRE_TEAMD_RETRY_COUNT}" = "yes" ]]; then + error "Could not confirm that all neighbor devices are running SONiC with the retry count feature" + exit "${EXIT_TEAMD_RETRY_COUNT_FAILURE}" + else + debug "Warning: Retry count feature support unknown for one or more neighbor devices; assuming that it's not available" + fi + else + TEAMD_INCREASE_RETRY_COUNT=1 + fi +fi + if [[ "$REBOOT_TYPE" = "warm-reboot" || "$REBOOT_TYPE" = "fastfast-reboot" || "$REBOOT_TYPE" = "fast-reboot" ]]; then # Freeze orchagent for warm restart # Ask orchagent_restart_check to try freeze 5 times with interval of 2 seconds, @@ -633,6 +659,10 @@ if [[ "$REBOOT_TYPE" = "fast-reboot" ]]; then fi fi +if [[ ( "${REBOOT_TYPE}" = "warm-reboot" || "${REBOOT_TYPE}" = "fastfast-reboot" ) && "${TEAMD_INCREASE_RETRY_COUNT}" -eq 1 ]]; then + /usr/local/bin/teamd_increase_retry_count.py +fi + # We are fully committed to reboot from this point on because critical # service will go down and we cannot recover from it. set +e diff --git a/scripts/teamd_increase_retry_count.py b/scripts/teamd_increase_retry_count.py new file mode 100755 index 000000000..d5151b69b --- /dev/null +++ b/scripts/teamd_increase_retry_count.py @@ -0,0 +1,330 @@ +#!/usr/bin/python3 + +import subprocess +import json +from scapy.config import conf +conf.ipv6_enabled = False +conf.verb = False +from scapy.fields import ByteField, ShortField, MACField, XStrFixedLenField, ConditionalField +from scapy.layers.l2 import Ether +from scapy.sendrecv import sendp, sniff +from scapy.packet import Packet, split_layers, bind_layers +import scapy.contrib.lacp +import os +import re +import sys +from threading import Thread, Event +import time +import argparse +import signal + +from sonic_py_common import logger +from swsscommon.swsscommon import DBConnector, Table + +log = logger.Logger() +revertTeamdRetryCountChanges = False +DEFAULT_RETRY_COUNT = 3 +EXTENDED_RETRY_COUNT = 5 +SLOW_PROTOCOL_MAC_ADDRESS = "01:80:c2:00:00:02" +LACP_ETHERTYPE = 0x8809 + +class LACPRetryCount(Packet): + name = "LACPRetryCount" + fields_desc = [ + ByteField("version", 0xf1), + ByteField("actor_type", 1), + ByteField("actor_length", 20), + ShortField("actor_system_priority", 0), + MACField("actor_system", None), + ShortField("actor_key", 0), + ShortField("actor_port_priority", 0), + ShortField("actor_port_number", 0), + ByteField("actor_state", 0), + XStrFixedLenField("actor_reserved", "", 3), + ByteField("partner_type", 2), + ByteField("partner_length", 20), + ShortField("partner_system_priority", 0), + MACField("partner_system", None), + ShortField("partner_key", 0), + ShortField("partner_port_priority", 0), + ShortField("partner_port_number", 0), + ByteField("partner_state", 0), + XStrFixedLenField("partner_reserved", "", 3), + ByteField("collector_type", 3), + ByteField("collector_length", 16), + ShortField("collector_max_delay", 0), + XStrFixedLenField("collector_reserved", "", 12), + ConditionalField(ByteField("actor_retry_count_type", 0x80), lambda pkt:pkt.version == 0xf1), + ConditionalField(ByteField("actor_retry_count_length", 4), lambda pkt:pkt.version == 0xf1), + ConditionalField(ByteField("actor_retry_count", 0), lambda pkt:pkt.version == 0xf1), + ConditionalField(XStrFixedLenField("actor_retry_count_reserved", "", 1), lambda pkt:pkt.version == 0xf1), + ConditionalField(ByteField("partner_retry_count_type", 0x81), lambda pkt:pkt.version == 0xf1), + ConditionalField(ByteField("partner_retry_count_length", 4), lambda pkt:pkt.version == 0xf1), + ConditionalField(ByteField("partner_retry_count", 0), lambda pkt:pkt.version == 0xf1), + ConditionalField(XStrFixedLenField("partner_retry_count_reserved", "", 1), lambda pkt:pkt.version == 0xf1), + ByteField("terminator_type", 0), + ByteField("terminator_length", 0), + ConditionalField(XStrFixedLenField("reserved", "", 42), lambda pkt:pkt.version == 0xf1), + ConditionalField(XStrFixedLenField("reserved", "", 50), lambda pkt:pkt.version != 0xf1), + ] + +split_layers(scapy.contrib.lacp.SlowProtocol, scapy.contrib.lacp.LACP, subtype=1) +bind_layers(scapy.contrib.lacp.SlowProtocol, LACPRetryCount, subtype=1) + +class LacpPacketListenThread(Thread): + def __init__(self, port, targetMacAddress, sendReadyEvent): + Thread.__init__(self) + self.port = port + self.targetMacAddress = targetMacAddress + self.sendReadyEvent = sendReadyEvent + self.detectedNewVersion = False + + def lacpPacketCallback(self, pkt): + if pkt["LACPRetryCount"].version == 0xf1: + self.detectedNewVersion = True + return self.detectedNewVersion + + def run(self): + sniff(stop_filter=self.lacpPacketCallback, iface=self.port, filter="ether proto {} and ether src {}".format(LACP_ETHERTYPE, self.targetMacAddress), + store=0, timeout=30, started_callback=self.sendReadyEvent.set) + +def getPortChannels(): + applDb = DBConnector("APPL_DB", 0) + configDb = DBConnector("CONFIG_DB", 0) + portChannelTable = Table(applDb, "LAG_TABLE") + portChannels = portChannelTable.getKeys() + activePortChannels = [] + for portChannel in portChannels: + state = portChannelTable.get(portChannel) + if not state or not state[0]: + continue + isAdminUp = False + isOperUp = False + for key, value in state[1]: + if key == "admin_status": + isAdminUp = value == "up" + elif key == "oper_status": + isOperUp = value == "up" + if isAdminUp and isOperUp: + activePortChannels.append(portChannel) + + # Now find out which BGP sessions on these port channels are admin up. This needs to go + # through a circuitious sequence of steps. + # + # 1. Get the local IPv4/IPv6 address assigned to each port channel. + # 2. Find out which BGP session (in CONFIG_DB) has a local_addr attribute of the local + # IPv4/IPv6 address. + # 3. Check the admin_status field of that table in CONFIG_DB. + portChannelData = {} + portChannelInterfaceTable = Table(configDb, "PORTCHANNEL_INTERFACE") + portChannelInterfaces = portChannelInterfaceTable.getKeys() + for portChannelInterface in portChannelInterfaces: + if "|" not in portChannelInterface: + continue + portChannel = portChannelInterface.split("|")[0] + ipAddress = portChannelInterface.split("|")[1].split("/")[0].lower() + if portChannel not in activePortChannels: + continue + portChannelData[ipAddress] = { + "portChannel": portChannel, + "adminUp": False + } + + deviceMetadataTable = Table(configDb, "DEVICE_METADATA") + metadata = deviceMetadataTable.get("localhost") + defaultBgpStatus = True + for key, value in metadata[1]: + if key == "default_bgp_status": + defaultBgpStatus = value == "up" + break + + bgpTable = Table(configDb, "BGP_NEIGHBOR") + bgpNeighbors = bgpTable.getKeys() + for bgpNeighbor in bgpNeighbors: + neighborData = bgpTable.get(bgpNeighbor) + if not neighborData[0]: + continue + localAddr = None + isAdminUp = defaultBgpStatus + for key, value in neighborData[1]: + if key == "local_addr": + if value not in portChannelData: + break + localAddr = value.lower() + elif key == "admin_status": + isAdminUp = value == "up" + if not localAddr: + continue + portChannelData[localAddr]["adminUp"] = isAdminUp + + return set([portChannelData[x]["portChannel"] for x in portChannelData.keys() if portChannelData[x]["adminUp"]]) + +def getPortChannelConfig(portChannelName): + (processStdout, _) = getCmdOutput(["teamdctl", portChannelName, "state", "dump"]) + return json.loads(processStdout) + +def getLldpNeighbors(): + (processStdout, _) = getCmdOutput(["lldpctl", "-f", "json"]) + return json.loads(processStdout) + +def craftLacpPacket(portChannelConfig, portName, isResetPacket=False, newVersion=True): + portConfig = portChannelConfig["ports"][portName] + actorConfig = portConfig["runner"]["actor_lacpdu_info"] + partnerConfig = portConfig["runner"]["partner_lacpdu_info"] + l2 = Ether(dst=SLOW_PROTOCOL_MAC_ADDRESS, src=portConfig["ifinfo"]["dev_addr"], type=LACP_ETHERTYPE) + l3 = scapy.contrib.lacp.SlowProtocol(subtype=0x01) + l4 = LACPRetryCount() + if newVersion: + l4.version = 0xf1 + else: + l4.version = 0x1 + l4.actor_system_priority = actorConfig["system_priority"] + l4.actor_system = actorConfig["system"] + l4.actor_key = actorConfig["key"] + l4.actor_port_priority = actorConfig["port_priority"] + l4.actor_port_number = actorConfig["port"] + l4.actor_state = actorConfig["state"] + l4.partner_system_priority = partnerConfig["system_priority"] + l4.partner_system = partnerConfig["system"] + l4.partner_key = partnerConfig["key"] + l4.partner_port_priority = partnerConfig["port_priority"] + l4.partner_port_number = partnerConfig["port"] + l4.partner_state = partnerConfig["state"] + if newVersion: + l4.actor_retry_count = EXTENDED_RETRY_COUNT if not isResetPacket else DEFAULT_RETRY_COUNT + l4.partner_retry_count = DEFAULT_RETRY_COUNT + packet = l2 / l3 / l4 + return packet + +def sendLacpPackets(packets, revertPackets): + global revertTeamdRetryCountChanges + while not revertTeamdRetryCountChanges: + for port, packet in packets: + sendp(packet, iface=port) + time.sleep(15) + if revertTeamdRetryCountChanges: + for port, packet in revertPackets: + sendp(packet, iface=port) + +def abortTeamdChanges(signum, frame): + global revertTeamdRetryCountChanges + log.log_info("Got signal {}, reverting teamd retry count change".format(signum)) + revertTeamdRetryCountChanges = True + +def getCmdOutput(cmd): + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + return proc.communicate()[0], proc.returncode + +def main(probeOnly=False): + if os.geteuid() != 0: + log.log_error("Root privileges required for this operation", also_print_to_console=True) + sys.exit(1) + portChannels = getPortChannels() + if not portChannels: + log.log_info("No port channels retrieved; exiting") + return + failedPortChannels = [] + if probeOnly: + for portChannel in portChannels: + config = getPortChannelConfig(portChannel) + lldpInfo = getLldpNeighbors() + portChannelChecked = False + for portName in config["ports"].keys(): + if not "runner" in config["ports"][portName] or \ + not "partner_lacpdu_info" in config["ports"][portName]["runner"] or \ + not "actor_lacpdu_info" in config["ports"][portName]["runner"]: + log.log_error("ERROR: Missing information from teamd about {}; skipping".format(portName)) + failedPortChannels.append(portChannel) + break + + interfaceLldpInfo = [k for k in lldpInfo["lldp"]["interface"] if portName in k] + if not interfaceLldpInfo: + log.log_warning("WARNING: No LLDP info available for {}; skipping".format(portName)) + continue + interfaceLldpInfo = interfaceLldpInfo[0][portName] + peerName = list(interfaceLldpInfo["chassis"].keys())[0] + peerInfo = interfaceLldpInfo["chassis"][peerName] + if "descr" not in peerInfo: + log.log_warning("WARNING: No peer description available via LLDP for {}; skipping".format(portName)) + continue + portChannelChecked = True + if "sonic" not in peerInfo["descr"].lower(): + log.log_warning("WARNING: Peer device is not a SONiC device; skipping") + failedPortChannels.append(portChannel) + break + + sendReadyEvent = Event() + + # Start sniffing thread + lacpThread = LacpPacketListenThread(portName, config["ports"][portName]["runner"]["partner_lacpdu_info"]["system"], sendReadyEvent) + lacpThread.start() + + # Generate and send probe packet after sniffing has started + probePacket = craftLacpPacket(config, portName) + sendReadyEvent.wait() + sendp(probePacket, iface=portName) + + lacpThread.join() + + resetProbePacket = craftLacpPacket(config, portName, newVersion=False) + # 2-second sleep for making sure all processing is done on the peer device + time.sleep(2) + sendp(resetProbePacket, iface=portName, count=2, inter=0.5) + + if lacpThread.detectedNewVersion: + log.log_notice("SUCCESS: Peer device {} is running version of SONiC with teamd retry count feature".format(peerName), also_print_to_console=True) + break + else: + log.log_warning("WARNING: Peer device {} is running version of SONiC without teamd retry count feature".format(peerName), also_print_to_console=True) + failedPortChannels.append(portChannel) + break + if not portChannelChecked: + log.log_warning("WARNING: No information available about peer device on port channel {}".format(portChannel), also_print_to_console=True) + failedPortChannels.append(portChannel) + if failedPortChannels: + log.log_error("ERROR: There are port channels/peer devices that failed the probe: {}".format(failedPortChannels), also_print_to_console=True) + sys.exit(2) + else: + global revertTeamdRetryCountChanges + signal.signal(signal.SIGUSR1, abortTeamdChanges) + signal.signal(signal.SIGTERM, abortTeamdChanges) + (_, rc) = getCmdOutput(["config", "portchannel", "retry-count", "get", list(portChannels)[0]]) + if rc == 0: + # Currently running on SONiC version with teamd retry count feature + for portChannel in portChannels: + getCmdOutput(["config", "portchannel", "retry-count", "set", portChannel, str(EXTENDED_RETRY_COUNT)]) + pid = os.fork() + if pid == 0: + # Running in a new process, detached from parent process + while not revertTeamdRetryCountChanges: + time.sleep(15) + if revertTeamdRetryCountChanges: + for portChannel in portChannels: + getCmdOutput(["config", "portchannel", "retry-count", "set", portChannel, str(DEFAULT_RETRY_COUNT)]) + else: + lacpPackets = [] + revertLacpPackets = [] + for portChannel in portChannels: + config = getPortChannelConfig(portChannel) + for portName in config["ports"].keys(): + if not "runner" in config["ports"][portName] or \ + not "partner_lacpdu_info" in config["ports"][portName]["runner"] or \ + not "actor_lacpdu_info" in config["ports"][portName]["runner"]: + log.log_error("ERROR: Missing information from teamd about {}; skipping".format(portName)) + break + + packet = craftLacpPacket(config, portName) + lacpPackets.append((portName, packet)) + packet = craftLacpPacket(config, portName, isResetPacket=True) + revertLacpPackets.append((portName, packet)) + pid = os.fork() + if pid == 0: + # Running in a new process, detached from parent process + sendLacpPackets(lacpPackets, revertLacpPackets) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Teamd retry count changer.') + parser.add_argument('--probe-only', action='store_true', + help='Probe the peer devices only, to verify that they support the teamd retry count feature') + args = parser.parse_args() + main(args.probe_only) diff --git a/setup.py b/setup.py index f2f63da73..4e7634c0c 100644 --- a/setup.py +++ b/setup.py @@ -140,6 +140,7 @@ 'scripts/soft-reboot', 'scripts/storyteller', 'scripts/syseeprom-to-json', + 'scripts/teamd_increase_retry_count.py', 'scripts/tempershow', 'scripts/tunnelstat', 'scripts/update_json.py', diff --git a/tests/config_test.py b/tests/config_test.py index 00e06874c..6cd0a9fa6 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -7,6 +7,7 @@ import sys import unittest import ipaddress +import shutil from unittest import mock import click @@ -256,6 +257,46 @@ def test_config_reload_untriggered_timer(self, get_cmd_module, setup_single_broa assert "\n".join([l.rstrip() for l in result.output.split('\n')][:2]) == reload_config_with_untriggered_timer_output + def test_config_reload_stdin(self, get_cmd_module, setup_single_broadcom_asic): + def mock_json_load(f): + device_metadata = { + "DEVICE_METADATA": { + "localhost": { + "docker_routing_config_mode": "split", + "hostname": "sonic", + "hwsku": "Seastone-DX010-25-50", + "mac": "00:e0:ec:89:6e:48", + "platform": "x86_64-cel_seastone-r0", + "type": "ToRRouter" + } + } + } + return device_metadata + with mock.patch("utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect)) as mock_run_command,\ + mock.patch("json.load", mock.MagicMock(side_effect=mock_json_load)): + (config, show) = get_cmd_module + + dev_stdin = "/dev/stdin" + jsonfile_init_cfg = os.path.join(mock_db_path, "init_cfg.json") + + # create object + config.INIT_CFG_FILE = jsonfile_init_cfg + + db = Db() + runner = CliRunner() + obj = {'config_db': db.cfgdb} + + # simulate 'config reload' to provoke load_sys_info option + result = runner.invoke(config.config.commands["reload"], [dev_stdin, "-l", "-n", "-y"], obj=obj) + + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + + assert result.exit_code == 0 + + assert "\n".join([l.rstrip() for l in result.output.split('\n')][:1]) == reload_config_with_sys_info_command_output + @classmethod def teardown_class(cls): print("TEARDOWN") @@ -461,9 +502,66 @@ def setup_class(cls): print("SETUP") import config.main importlib.reload(config.main) - open(cls.dummy_cfg_file, 'w').close() + + def add_sysinfo_to_cfg_file(self): + with open(self.dummy_cfg_file, 'w') as f: + device_metadata = { + "DEVICE_METADATA": { + "localhost": { + "platform": "some_platform", + "mac": "02:42:f0:7f:01:05" + } + } + } + f.write(json.dumps(device_metadata)) + + def test_reload_config_invalid_input(self, get_cmd_module, setup_single_broadcom_asic): + open(self.dummy_cfg_file, 'w').close() + with mock.patch( + "utilities_common.cli.run_command", + mock.MagicMock(side_effect=mock_run_command_side_effect) + ) as mock_run_command: + (config, show) = get_cmd_module + runner = CliRunner() + + result = runner.invoke( + config.config.commands["reload"], + [self.dummy_cfg_file, '-y', '-f']) + + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + + def test_reload_config_no_sysinfo(self, get_cmd_module, setup_single_broadcom_asic): + with open(self.dummy_cfg_file, 'w') as f: + device_metadata = { + "DEVICE_METADATA": { + "localhost": { + "hwsku": "some_hwsku" + } + } + } + f.write(json.dumps(device_metadata)) + + with mock.patch( + "utilities_common.cli.run_command", + mock.MagicMock(side_effect=mock_run_command_side_effect) + ) as mock_run_command: + (config, show) = get_cmd_module + runner = CliRunner() + + result = runner.invoke( + config.config.commands["reload"], + [self.dummy_cfg_file, '-y', '-f']) + + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 def test_reload_config(self, get_cmd_module, setup_single_broadcom_asic): + self.add_sysinfo_to_cfg_file() with mock.patch( "utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect) @@ -483,6 +581,7 @@ def test_reload_config(self, get_cmd_module, setup_single_broadcom_asic): == RELOAD_CONFIG_DB_OUTPUT def test_config_reload_disabled_service(self, get_cmd_module, setup_single_broadcom_asic): + self.add_sysinfo_to_cfg_file() with mock.patch( "utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect_disabled_timer) @@ -502,6 +601,7 @@ def test_config_reload_disabled_service(self, get_cmd_module, setup_single_broad assert "\n".join([l.rstrip() for l in result.output.split('\n')]) == reload_config_with_disabled_service_output def test_reload_config_masic(self, get_cmd_module, setup_multi_broadcom_masic): + self.add_sysinfo_to_cfg_file() with mock.patch( "utilities_common.cli.run_command", mock.MagicMock(side_effect=mock_run_command_side_effect) diff --git a/tests/sflow_test.py b/tests/sflow_test.py index ecb278253..ad6c53462 100644 --- a/tests/sflow_test.py +++ b/tests/sflow_test.py @@ -182,6 +182,13 @@ def test_config_sflow_polling_interval(self): runner = CliRunner() obj = {'db':db.cfgdb} + # set to 500 out of range + result = runner.invoke(config.config.commands["sflow"]. + commands["polling-interval"], ["500"], obj=obj) + print(result.exit_code, result.output) + assert result.exit_code != 0 + assert "Polling interval must be between 5-300" in result.output + # set to 20 result = runner.invoke(config.config.commands["sflow"]. commands["polling-interval"], ["20"], obj=obj)