From 3909818ba04f998ce168c363ccc98adbc8281ada Mon Sep 17 00:00:00 2001 From: Olivier Singla Date: Sat, 11 Jul 2020 19:58:59 -0400 Subject: [PATCH 01/48] Use /var/tmp instead of /tmp for sonic_installer and generate_dump sonic_installer and generate_dump utilities use currently /tmp directory to store temporary files. Since the amount of data stored can be rather large (especially in the case of the sonic_installer), we are switching to use /var/tmp instead. We are doing these changes in anticipation that at some point /tmp will being mounted over TMPFS /tmp is typically mounted on TMPFS, which means it's fast and does not produce wear on SSD or Hard-Drive. /var/tmp is mounted on the rootfs partition, therefore not in the TMPFS partition. We also noticed that both sonic_installer and generate_dump produce temporary files which are not deleted once thse programes are done. We are then making changes to peform a clean-up and remove the temporary files not needed anymore. # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Sat Jul 11 19:58:59 2020 -0400 # # On branch var_tmp # Changes to be committed: # modified: scripts/generate_dump # modified: sonic_installer/bootloader/aboot.py # modified: sonic_installer/bootloader/grub.py # modified: sonic_installer/bootloader/onie.py # modified: sonic_installer/bootloader/uboot.py # modified: sonic_installer/common.py # modified: sonic_installer/main.py # --- scripts/generate_dump | 16 ++++++++++------ sonic_installer/bootloader/aboot.py | 7 ++++--- sonic_installer/bootloader/grub.py | 1 + sonic_installer/bootloader/onie.py | 3 ++- sonic_installer/bootloader/uboot.py | 1 + sonic_installer/common.py | 7 ++++++- sonic_installer/main.py | 26 +++++++++++++++++++++++--- 7 files changed, 47 insertions(+), 14 deletions(-) diff --git a/scripts/generate_dump b/scripts/generate_dump index 01e45f801a..579c52cb7b 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -24,7 +24,8 @@ NOOP=false DO_COMPRESS=true CMD_PREFIX= SINCE_DATE="@0" # default is set to January 1, 1970 at 00:00:00 GMT -REFERENCE_FILE=/tmp/reference +TMP_PREFIX=/var/tmp +REFERENCE_FILE=${TMP_PREFIX}/reference BASE=sonic_dump_`hostname`_`date +%Y%m%d_%H%M%S` DUMPDIR=/var/dump TARDIR=$DUMPDIR/$BASE @@ -423,12 +424,12 @@ main() { local asic="$(/usr/local/bin/sonic-cfggen -y /etc/sonic/sonic_version.yml -v asic_type)" if [[ "$asic" = "mellanox" ]]; then - local sai_dump_filename="/tmp/sai_sdk_dump_$(date +"%m_%d_%Y_%I_%M_%p")" + local sai_dump_filename="${TMP_PREFIX}/sai_sdk_dump_$(date +"%m_%d_%Y_%I_%M_%p")" ${CMD_PREFIX}docker exec -it syncd saisdkdump -f $sai_dump_filename - ${CMD_PREFIX}docker exec syncd tar Ccf $(dirname $sai_dump_filename) - $(basename $sai_dump_filename) | tar Cxf /tmp/ - + ${CMD_PREFIX}docker exec syncd tar Ccf $(dirname $sai_dump_filename) - $(basename $sai_dump_filename) | tar Cxf ${TMP_PREFIX}/ - save_file $sai_dump_filename sai_sdk_dump true - local mst_dump_filename="/tmp/mstdump" + local mst_dump_filename="${TMP_PREFIX}/mstdump" local max_dump_count="3" for i in $(seq 1 $max_dump_count); do ${CMD_PREFIX}/usr/bin/mstdump /dev/mst/mt*conf0 > "${mst_dump_filename}${i}" @@ -519,8 +520,8 @@ main() { # run 'hw-management-generate-dump.sh' script and save the result file /usr/bin/hw-management-generate-dump.sh - save_file "/tmp/hw-mgmt-dump*" "hw-mgmt" false - rm -f /tmp/hw-mgmt-dump* + save_file "${TMP_PREFIX}/hw-mgmt-dump*" "hw-mgmt" false + rm -f ${TMP_PREFIX}/hw-mgmt-dump* # clean up working tar dir before compressing $RM $V -rf $TARDIR @@ -535,6 +536,9 @@ main() { fi echo ${TARFILE} + + # Cleanup + rm -f ${REFERENCE_FILE} } ############################################################################### diff --git a/sonic_installer/bootloader/aboot.py b/sonic_installer/bootloader/aboot.py index 1933921512..2e06dff394 100644 --- a/sonic_installer/bootloader/aboot.py +++ b/sonic_installer/bootloader/aboot.py @@ -18,6 +18,7 @@ HOST_PATH, IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, run_command, ) from .bootloader import Bootloader @@ -41,7 +42,7 @@ class AbootBootloader(Bootloader): NAME = 'aboot' BOOT_CONFIG_PATH = os.path.join(HOST_PATH, 'boot-config') - DEFAULT_IMAGE_PATH = '/tmp/sonic_image.swi' + DEFAULT_IMAGE_PATH = TMP_PREFIX+'/sonic_image.swi' def _boot_config_read(self, path=BOOT_CONFIG_PATH): config = collections.OrderedDict() @@ -99,8 +100,8 @@ def set_next_image(self, image): return True def install_image(self, image_path): - run_command("/usr/bin/unzip -od /tmp %s boot0" % image_path) - run_command("swipath=%s target_path=/host sonic_upgrade=1 . /tmp/boot0" % image_path) + run_command("/usr/bin/unzip -od %s %s boot0" % (TMP_PREFIX, image_path)) + run_command("swipath=%s target_path=/host sonic_upgrade=1 . %s/boot0" % (image_path, TMP_PREFIX)) def remove_image(self, image): nextimage = self.get_next_image() diff --git a/sonic_installer/bootloader/grub.py b/sonic_installer/bootloader/grub.py index 1d111f4191..f3e2834c5a 100644 --- a/sonic_installer/bootloader/grub.py +++ b/sonic_installer/bootloader/grub.py @@ -12,6 +12,7 @@ HOST_PATH, IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, run_command, ) from .onie import OnieInstallerBootloader diff --git a/sonic_installer/bootloader/onie.py b/sonic_installer/bootloader/onie.py index ca16172efa..eedfb1c912 100644 --- a/sonic_installer/bootloader/onie.py +++ b/sonic_installer/bootloader/onie.py @@ -10,6 +10,7 @@ from ..common import ( IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, ) from .bootloader import Bootloader @@ -20,7 +21,7 @@ def default_sigpipe(): class OnieInstallerBootloader(Bootloader): # pylint: disable=abstract-method - DEFAULT_IMAGE_PATH = '/tmp/sonic_image' + DEFAULT_IMAGE_PATH = TMP_PREFIX+'/sonic_image' def get_current_image(self): cmdline = open('/proc/cmdline', 'r') diff --git a/sonic_installer/bootloader/uboot.py b/sonic_installer/bootloader/uboot.py index 47252dd6af..5bc10c6fa2 100644 --- a/sonic_installer/bootloader/uboot.py +++ b/sonic_installer/bootloader/uboot.py @@ -11,6 +11,7 @@ HOST_PATH, IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, run_command, ) from .onie import OnieInstallerBootloader diff --git a/sonic_installer/common.py b/sonic_installer/common.py index f12454042a..ef37b8d313 100644 --- a/sonic_installer/common.py +++ b/sonic_installer/common.py @@ -5,15 +5,17 @@ import subprocess import sys +import os import click HOST_PATH = '/host' IMAGE_PREFIX = 'SONiC-OS-' IMAGE_DIR_PREFIX = 'image-' +TMP_PREFIX = '/var/tmp' # Run bash command and print output to stdout -def run_command(command): +def run_command(command, img_to_delete=None): click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green')) proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) @@ -22,4 +24,7 @@ def run_command(command): click.echo(out) if proc.returncode != 0: + if img_to_delete: + if os.path.exists(img_to_delete): + os.remove(img_to_delete) sys.exit(proc.returncode) diff --git a/sonic_installer/main.py b/sonic_installer/main.py index 3c68bcd843..8e528386a4 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -10,7 +10,11 @@ from swsssdk import SonicV2Connector from .bootloader import get_bootloader -from .common import run_command + +from .common import ( + TMP_PREFIX, + run_command, +) # # Helper functions @@ -162,6 +166,8 @@ def install(url, force, skip_migration=False): binary_image_version = bootloader.get_binary_image_version(image_path) if not binary_image_version: click.echo("Image file does not exist or is not a valid SONiC image file") + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() # Is this version already installed? @@ -169,6 +175,8 @@ def install(url, force, skip_migration=False): click.echo("Image {} is already installed. Setting it as default...".format(binary_image_version)) if not bootloader.set_default_image(binary_image_version): click.echo('Error: Failed to set image as default') + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() else: # Verify that the binary image is of the same type as the running image @@ -176,6 +184,8 @@ def install(url, force, skip_migration=False): click.echo("Image file '{}' is of a different type than running image.\n" "If you are sure you want to install this image, use -f|--force.\n" "Aborting...".format(image_path)) + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() click.echo("Installing image {} and setting it as default...".format(binary_image_version)) @@ -186,6 +196,10 @@ def install(url, force, skip_migration=False): else: run_command('config-setup backup') + # Clean-up by deleting downloaded file + if os.path.exists(image_path): + os.remove(image_path) + # Finally, sync filesystem run_command("sync;sync;sync") run_command("sleep 3") # wait 3 seconds after sync @@ -298,7 +312,7 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): image_latest = image_name + ":latest" image_id_previous = get_container_image_id(image_latest) - DEFAULT_IMAGE_PATH = os.path.join("/tmp/", image_name) + DEFAULT_IMAGE_PATH = os.path.join(TMP_PREFIX+"/", image_name) if url.startswith('http://') or url.startswith('https://'): click.echo('Downloading image...') validate_url_or_abort(url) @@ -315,6 +329,8 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): # TODO: Verify the file is a *proper Docker image file* if not os.path.isfile(image_path): click.echo("Image file '{}' does not exist or is not a regular file. Aborting...".format(image_path)) + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() warm_configured = False @@ -335,7 +351,7 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): # Fetch tag of current running image tag_previous = get_docker_tag_name(image_latest) # Load the new image beforehand to shorten disruption time - run_command("docker load < %s" % image_path) + run_command("docker load < %s" % image_path, image_path) warm_app_names = [] # warm restart specific procssing for swss, bgp and teamd dockers. if warm_configured == True or warm: @@ -438,6 +454,10 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): if container_name == "swss" or container_name == "bgp" or container_name == "teamd": run_command("config warm_restart disable %s" % container_name) + # Clean-up by deleting downloaded file + if os.path.exists(image_path): + os.remove(image_path) + if state == exp_state: click.echo('Done') else: From b4b5fd37094807e6105dfb5b1824ab5f9b32de3f Mon Sep 17 00:00:00 2001 From: xumia <59720581+xumia@users.noreply.github.com> Date: Mon, 13 Jul 2020 14:19:22 +0800 Subject: [PATCH 02/48] Support to verify reboot for secure boot (#979) * Support to verify reboot for secure boot * Support to verify reboot for secure boot * Fix type issue * Change the verification command to verify-next-image * Fix python function error * Fix Host_PATH case issue * Fix some messages issue * Remove not neccessary message * not to import no use module * Change command to use hyphens * Simplify the command verify-next-image * Add fast-reboot exit code * Improve the verification message --- scripts/fast-reboot | 11 +++++++---- scripts/reboot | 10 ++++++---- sonic_installer/bootloader/aboot.py | 12 ++++++++++-- sonic_installer/bootloader/bootloader.py | 20 ++++++++++++++++++++ sonic_installer/main.py | 10 ++++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 644955533b..bfce5add29 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -33,6 +33,7 @@ EXIT_SYNCD_SHUTDOWN=11 EXIT_FAST_REBOOT_DUMP_FAILURE=12 EXIT_FILTER_FDB_ENTRIES_FAILURE=13 EXIT_NO_CONTROL_PLANE_ASSISTANT=20 +EXIT_SONIC_INSTALLER_VERIFY_REBOOT=21 function error() { @@ -305,10 +306,12 @@ function reboot_pre_check() exit ${EXIT_FILE_SYSTEM_FULL} fi - # Make sure that the next image exists - if [[ ! -d ${IMAGE_PATH} ]]; then - debug "Next image ${NEXT_SONIC_IMAGE} doesn't exist ..." - exit ${EXIT_NEXT_IMAGE_NOT_EXISTS} + # Verify the next image by sonic_installer + INSTALLER_VERIFY_RC=0 + sonic_installer verify-next-image > /dev/null || INSTALLER_VERIFY_RC=$? + if [[ INSTALLER_VERIFY_RC -ne 0 ]]; then + error "Failed to verify next image. Exit code: $INSTALLER_VERIFY_RC" + exit ${EXIT_SONIC_INSTALLER_VERIFY_REBOOT} fi # Make sure ASIC configuration has not changed between images diff --git a/scripts/reboot b/scripts/reboot index 6a030c15c9..889634bbf4 100755 --- a/scripts/reboot +++ b/scripts/reboot @@ -16,6 +16,7 @@ PLAT_REBOOT="platform_reboot" REBOOT_CAUSE_FILE="/host/reboot-cause/reboot-cause.txt" VERBOSE=no EXIT_NEXT_IMAGE_NOT_EXISTS=4 +EXIT_SONIC_INSTALLER_VERIFY_REBOOT=21 function debug() { @@ -79,10 +80,11 @@ function reboot_pre_check() fi rm ${filename} - # Make sure that the next image exists - if [[ ! -d ${IMAGE_PATH} ]]; then - VERBOSE=yes debug "Next image ${NEXT_SONIC_IMAGE} doesn't exist ..." - exit ${EXIT_NEXT_IMAGE_NOT_EXISTS} + # Verify the next image by sonic_installer + local message=$(sonic_installer verify-next-image 2>&1) + if [ $? -ne 0 ]; then + VERBOSE=yes debug "Failed to verify next image: ${message}" + exit ${EXIT_SONIC_INSTALLER_VERIFY_REBOOT} fi } diff --git a/sonic_installer/bootloader/aboot.py b/sonic_installer/bootloader/aboot.py index 1933921512..3cc43a457f 100644 --- a/sonic_installer/bootloader/aboot.py +++ b/sonic_installer/bootloader/aboot.py @@ -23,6 +23,7 @@ from .bootloader import Bootloader _secureboot = None +DEFAULT_SWI_IMAGE = 'sonic.swi' # For the signature format, see: https://github.com/aristanetworks/swi-tools/tree/master/switools SWI_SIG_FILE_NAME = 'swi-signature' @@ -110,9 +111,9 @@ def remove_image(self, image): self._boot_config_set(SWI=image_path, SWI_DEFAULT=image_path) click.echo("Set next and default boot to current image %s" % current) - image_dir = image.replace(IMAGE_PREFIX, IMAGE_DIR_PREFIX) + image_path = self.get_image_path(image) click.echo('Removing image root filesystem...') - subprocess.call(['rm','-rf', os.path.join(HOST_PATH, image_dir)]) + subprocess.call(['rm','-rf', image_path]) click.echo('Image removed') def get_binary_image_version(self, image_path): @@ -129,6 +130,13 @@ def verify_binary_image(self, image_path): except subprocess.CalledProcessError: return False + def verify_next_image(self): + if not super(AbootBootloader, self).verify_next_image(): + return False + image = self.get_next_image() + image_path = os.path.join(self.get_image_path(image), DEFAULT_SWI_IMAGE) + return self._verify_secureboot_image(image_path) + def _verify_secureboot_image(self, image_path): if isSecureboot(): cert = self.getCert(image_path) diff --git a/sonic_installer/bootloader/bootloader.py b/sonic_installer/bootloader/bootloader.py index 78bd05c61c..54054d4c55 100644 --- a/sonic_installer/bootloader/bootloader.py +++ b/sonic_installer/bootloader/bootloader.py @@ -2,6 +2,14 @@ Abstract Bootloader class """ +from os import path + +from ..common import ( + HOST_PATH, + IMAGE_DIR_PREFIX, + IMAGE_PREFIX, +) + class Bootloader(object): NAME = None @@ -43,8 +51,20 @@ def verify_binary_image(self, image_path): """verify that the image is supported by the bootloader""" raise NotImplementedError + def verify_next_image(self): + """verify the next image for reboot""" + image = self.get_next_image() + image_path = self.get_image_path(image) + return path.exists(image_path) + @classmethod def detect(cls): """returns True if the bootloader is in use""" return False + @classmethod + def get_image_path(cls, image): + """returns the image path""" + prefix = path.join(HOST_PATH, IMAGE_DIR_PREFIX) + return image.replace(IMAGE_PREFIX, prefix) + diff --git a/sonic_installer/main.py b/sonic_installer/main.py index 3c68bcd843..ddd0bffdba 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -476,5 +476,15 @@ def rollback_docker(container_name): click.echo('Done') +# verify the next image +@cli.command('verify-next-image') +def verify_next_image(): + """ Verify the next image for reboot""" + bootloader = get_bootloader() + if not bootloader.verify_next_image(): + click.echo('Image verification failed') + sys.exit(1) + click.echo('Image successfully verified') + if __name__ == '__main__': cli() From da27247b21dbad8efe4447b703efd7305b9e03b4 Mon Sep 17 00:00:00 2001 From: abdosi <58047199+abdosi@users.noreply.github.com> Date: Mon, 13 Jul 2020 08:53:19 -0700 Subject: [PATCH 03/48] Fix the None Type Exception when Interface Table does not exist (cold boot) as part of db migration (#986) * Fix for command. show interface transceiver eeprom -d Ethernet Make sure key is present in dom_info_dict and then only parse else skip. * Fix the None Type Exception when Interface Table does not exist (cold boot) as part of db migration --- scripts/db_migrator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index c6c5f90945..17ec3ea6e6 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -115,8 +115,12 @@ def migrate_intf_table(self): if self.appDB is None: return - if_db = [] data = self.appDB.keys(self.appDB.APPL_DB, "INTF_TABLE:*") + + if data is None: + return + + if_db = [] for key in data: if_name = key.split(":")[1] if if_name == "lo": From a23479e8cf792c9c821329dc374c3ce0f9bd8348 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Mon, 13 Jul 2020 20:57:41 -0700 Subject: [PATCH 04/48] [sonic-installer] Hyphens instead of underscores in command and subcommands (#983) Replace underscores with hyphens in all sonic-installer subcommands as well as the root command itself. - Install two entry points for sonic-installer: `sonic-installer` and `sonic_installer`. This allows for backward compatibility and time for users to transition from using the underscore versions to the hyphenated versions. - Replace underscores with hyphens in the following subcommands: ``` set-default set-next-boot binary-version upgrade-docker rollback-docker ``` - Also add aliases from the underscore versions of these subcommnds to the new hyphenated subcommands to allow for backward compatibility and time for users to transition from using the underscore versions to the hyphenated versions. - Print deprecation warnings to stderr if issued command line contains any deprecated versions with underscores - Bonus: Addition of the AliasedGroup base class also adds support for abbreviated subcommands - Fixed some style issues which did not align with PEP8 standards - Eliminate duplicate code by adding DOCKER_CONTAINER_LIST; Remove unknown 'amon' container from the list --- data/etc/bash_completion.d/sonic-installer | 8 + doc/Command-Reference.md | 40 ++-- fwutil/lib.py | 4 +- scripts/asic_config_check | 4 +- scripts/fast-reboot | 2 +- scripts/reboot | 2 +- scripts/sonic-kdump-config | 4 +- setup.py | 3 +- show/main.py | 2 +- sonic_installer/aliases.ini | 6 + sonic_installer/common.py | 2 +- sonic_installer/main.py | 202 +++++++++++++++++---- 12 files changed, 212 insertions(+), 67 deletions(-) create mode 100644 data/etc/bash_completion.d/sonic-installer create mode 100644 sonic_installer/aliases.ini diff --git a/data/etc/bash_completion.d/sonic-installer b/data/etc/bash_completion.d/sonic-installer new file mode 100644 index 0000000000..f1e9be7a01 --- /dev/null +++ b/data/etc/bash_completion.d/sonic-installer @@ -0,0 +1,8 @@ +_sonic_installer_completion() { + COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \ + COMP_CWORD=$COMP_CWORD \ + _SONIC_INSTALLER_COMPLETE=complete $1 ) ) + return 0 +} + +complete -F _sonic_installer_completion -o default sonic-installer; diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 8971445418..30df5c57bb 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -4645,7 +4645,7 @@ Supported options: 2. -f|--force - install FW regardless the current version 3. -i|--image - update FW using current/next SONiC image -Note: the default option is --image=current (current/next values are taken from `sonic_installer list`) +Note: the default option is --image=current (current/next values are taken from `sonic-installer list`) ### Platform Component Firmware vendor specific behaviour @@ -6604,7 +6604,7 @@ Go Back To [Beginning of the document](#) or [Beginning of this section](#waterm ## Software Installation and Management -SONiC software can be installed in two methods, viz, "using sonic_installer tool", "ONIE Installer". +SONiC software can be installed in two methods, viz, "using sonic-installer tool", "ONIE Installer". ### SONiC Installer @@ -6612,18 +6612,18 @@ This is a command line tool available as part of the SONiC software; If the devi This tool has facility to install an alternate image, list the available images and to set the next reboot image. This command requires elevated (root) privileges to run. -**sonic_installer list** +**sonic-installer list** This command displays information about currently installed images. It displays a list of installed images, currently running image and image set to be loaded in next reboot. - Usage: ``` - sonic_installer list + sonic-installer list ``` - Example: ``` - admin@sonic:~$ sudo sonic_installer list + admin@sonic:~$ sudo sonic-installer list Current: SONiC-OS-HEAD.XXXX Next: SONiC-OS-HEAD.XXXX Available: @@ -6633,18 +6633,18 @@ This command displays information about currently installed images. It displays TIP: This output can be obtained without evelated privileges by running the `show boot` command. See [here](#show-system-status) for details. -**sonic_installer install** +**sonic-installer install** This command is used to install a new image on the alternate image partition. This command takes a path to an installable SONiC image or URL and installs the image. - Usage: ``` - sonic_installer install + sonic-installer install ``` - Example: ``` - admin@sonic:~$ sudo sonic_installer install https://sonic-jenkins.westus.cloudapp.azure.com/job/xxxx/job/buildimage-xxxx-all/xxx/artifact/target/sonic-xxxx.bin + admin@sonic:~$ sudo sonic-installer install https://sonic-jenkins.westus.cloudapp.azure.com/job/xxxx/job/buildimage-xxxx-all/xxx/artifact/target/sonic-xxxx.bin New image will be installed, continue? [y/N]: y Downloading image... ...100%, 480 MB, 3357 KB/s, 146 seconds passed @@ -6676,46 +6676,46 @@ This command is used to install a new image on the alternate image partition. T Done ``` -**sonic_installer set_default** +**sonic-installer set_default** This command is be used to change the image which can be loaded by default in all the subsequent reboots. - Usage: ``` - sonic_installer set_default + sonic-installer set_default ``` - Example: ``` - admin@sonic:~$ sudo sonic_installer set_default SONiC-OS-HEAD.XXXX + admin@sonic:~$ sudo sonic-installer set_default SONiC-OS-HEAD.XXXX ``` -**sonic_installer set_next_boot** +**sonic-installer set_next_boot** This command is used to change the image that can be loaded in the *next* reboot only. Note that it will fallback to current image in all other subsequent reboots after the next reboot. - Usage: ``` - sonic_installer set_next_boot + sonic-installer set_next_boot ``` - Example: ``` - admin@sonic:~$ sudo sonic_installer set_next_boot SONiC-OS-HEAD.XXXX + admin@sonic:~$ sudo sonic-installer set_next_boot SONiC-OS-HEAD.XXXX ``` -**sonic_installer remove** +**sonic-installer remove** This command is used to remove the unused SONiC image from the disk. Note that it's *not* allowed to remove currently running image. - Usage: ``` - sonic_installer remove [-y|--yes] + sonic-installer remove [-y|--yes] ``` - Example: ``` - admin@sonic:~$ sudo sonic_installer remove SONiC-OS-HEAD.YYYY + admin@sonic:~$ sudo sonic-installer remove SONiC-OS-HEAD.YYYY Image will be removed, continue? [y/N]: y Updating GRUB... Done @@ -6726,18 +6726,18 @@ This command is used to remove the unused SONiC image from the disk. Note that i Image removed ``` -**sonic_installer cleanup** +**sonic-installer cleanup** This command removes all unused images from the device, leaving only the currently active image and the image which will be booted into next (if different) installed. If there are no images which can be removed, the command will output `No image(s) to remove` - Usage: ``` - sonic_installer cleanup [-y|--yes] + sonic-installer cleanup [-y|--yes] ``` - Example: ``` - admin@sonic:~$ sudo sonic_installer cleanup + admin@sonic:~$ sudo sonic-installer cleanup Remove images which are not current and next, continue? [y/N]: y No image(s) to remove ``` diff --git a/fwutil/lib.py b/fwutil/lib.py index 9bfc229276..eff432c630 100755 --- a/fwutil/lib.py +++ b/fwutil/lib.py @@ -221,13 +221,13 @@ def __init__(self): self.overlay_mountpoint = self.OVERLAY_MOUNTPOINT_TEMPLATE.format(image_stem) def get_current_image(self): - cmd = "sonic_installer list | grep 'Current: ' | cut -f2 -d' '" + cmd = "sonic-installer list | grep 'Current: ' | cut -f2 -d' '" output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) return output.rstrip(NEWLINE) def get_next_image(self): - cmd = "sonic_installer list | grep 'Next: ' | cut -f2 -d' '" + cmd = "sonic-installer list | grep 'Next: ' | cut -f2 -d' '" output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=True) return output.rstrip(NEWLINE) diff --git a/scripts/asic_config_check b/scripts/asic_config_check index b2b156b80f..06988da2d7 100755 --- a/scripts/asic_config_check +++ b/scripts/asic_config_check @@ -73,8 +73,8 @@ function ConfirmASICConfigChecksumsMatch() # Main starts here debug "Checking that ASIC configuration has not changed" -CURR_SONIC_IMAGE="$(sonic_installer list | grep "Current: " | cut -f2 -d' ')" -DST_SONIC_IMAGE="$(sonic_installer list | grep "Next: " | cut -f2 -d' ')" +CURR_SONIC_IMAGE="$(sonic-installer list | grep "Current: " | cut -f2 -d' ')" +DST_SONIC_IMAGE="$(sonic-installer list | grep "Next: " | cut -f2 -d' ')" if [[ "${CURR_SONIC_IMAGE}" == "${DST_SONIC_IMAGE}" ]]; then debug "ASIC config unchanged, current and destination SONiC version are the same" exit "${ASIC_CONFIG_UNCHANGED}" diff --git a/scripts/fast-reboot b/scripts/fast-reboot index bfce5add29..e18af200c3 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -274,7 +274,7 @@ function teardown_control_plane_assistant() function setup_reboot_variables() { # Kernel and initrd image - NEXT_SONIC_IMAGE=$(sonic_installer list | grep "Next: " | cut -d ' ' -f 2) + NEXT_SONIC_IMAGE=$(sonic-installer list | grep "Next: " | cut -d ' ' -f 2) IMAGE_PATH="/host/image-${NEXT_SONIC_IMAGE#SONiC-OS-}" if grep -q aboot_platform= /host/machine.conf; then KERNEL_IMAGE="$(ls $IMAGE_PATH/boot/vmlinuz-*)" diff --git a/scripts/reboot b/scripts/reboot index 889634bbf4..0ad25836da 100755 --- a/scripts/reboot +++ b/scripts/reboot @@ -64,7 +64,7 @@ function show_help_and_exit() function setup_reboot_variables() { - NEXT_SONIC_IMAGE=$(sonic_installer list | grep "Next: " | cut -d ' ' -f 2) + NEXT_SONIC_IMAGE=$(sonic-installer list | grep "Next: " | cut -d ' ' -f 2) IMAGE_PATH="/host/image-${NEXT_SONIC_IMAGE#SONiC-OS-}" } diff --git a/scripts/sonic-kdump-config b/scripts/sonic-kdump-config index 5f91979834..4a0bba6273 100755 --- a/scripts/sonic-kdump-config +++ b/scripts/sonic-kdump-config @@ -77,7 +77,7 @@ def run_command(cmd, use_shell=False): ## Search which SONiC image is the Current image def get_current_image(): - (rc, img, err_str) = run_command("sonic_installer list | grep 'Current: ' | cut -d '-' -f 3-", use_shell=True); + (rc, img, err_str) = run_command("sonic-installer list | grep 'Current: ' | cut -d '-' -f 3-", use_shell=True); if type(img) == list and len(img) == 1: return img[0] print_err("Unable to locate current SONiC image") @@ -85,7 +85,7 @@ def get_current_image(): ## Search which SONiC image is the Next image def get_next_image(): - (rc, img, err_str) = run_command("sonic_installer list | grep 'Next: ' | cut -d '-' -f 3-", use_shell=True); + (rc, img, err_str) = run_command("sonic-installer list | grep 'Next: ' | cut -d '-' -f 3-", use_shell=True); if type(img) == list and len(img) == 1: return img[0] print_err("Unable to locate current SONiC image") diff --git a/setup.py b/setup.py index 38090071b0..b593eedd48 100644 --- a/setup.py +++ b/setup.py @@ -135,7 +135,8 @@ 'pddf_ledutil = pddf_ledutil.main:cli', 'show = show.main:cli', 'sonic-clear = clear.main:cli', - 'sonic_installer = sonic_installer.main:cli', + 'sonic-installer = sonic_installer.main:sonic_installer', + 'sonic_installer = sonic_installer.main:sonic_installer', # Deprecated 'undebug = undebug.main:cli', 'watchdogutil = watchdogutil.main:watchdogutil', ] diff --git a/show/main.py b/show/main.py index 95993aca8c..b670596466 100755 --- a/show/main.py +++ b/show/main.py @@ -2722,7 +2722,7 @@ def ecn(): @cli.command('boot') def boot(): """Show boot configuration""" - cmd = "sudo sonic_installer list" + cmd = "sudo sonic-installer list" proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) click.echo(proc.stdout.read()) diff --git a/sonic_installer/aliases.ini b/sonic_installer/aliases.ini new file mode 100644 index 0000000000..ebe917057b --- /dev/null +++ b/sonic_installer/aliases.ini @@ -0,0 +1,6 @@ +[aliases] +set_default=set-default +set_next_boot=set-next-boot +binary_version=binary-version +upgrade_docker=upgrade-docker +rollback_docker=rollback-docker diff --git a/sonic_installer/common.py b/sonic_installer/common.py index f12454042a..e9bfeac04f 100644 --- a/sonic_installer/common.py +++ b/sonic_installer/common.py @@ -1,5 +1,5 @@ """ -Module holding common functions and constants used by sonic_installer and its +Module holding common functions and constants used by sonic-installer and its subpackages. """ diff --git a/sonic_installer/main.py b/sonic_installer/main.py index ddd0bffdba..8197e12069 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -1,17 +1,85 @@ #! /usr/bin/python -u +try: + import ConfigParser as configparser +except ImportError: + import configparser + import os +import subprocess import sys +import syslog import time -import click import urllib -import syslog -import subprocess + +import click from swsssdk import SonicV2Connector from .bootloader import get_bootloader from .common import run_command + +# Global Config object +_config = None + + +# This is from the aliases example: +# https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py +class Config(object): + """Object to hold CLI config""" + + def __init__(self): + self.path = os.getcwd() + self.aliases = {} + + def read_config(self, filename): + parser = configparser.RawConfigParser() + parser.read([filename]) + try: + self.aliases.update(parser.items('aliases')) + except configparser.NoSectionError: + pass + + +class AliasedGroup(click.Group): + """This subclass of click.Group supports abbreviations and + looking up aliases in a config file with a bit of magic. + """ + + def get_command(self, ctx, cmd_name): + global _config + + # If we haven't instantiated our global config, do it now and load current config + if _config is None: + _config = Config() + + # Load our config file + cfg_file = os.path.join(os.path.dirname(__file__), 'aliases.ini') + _config.read_config(cfg_file) + + # Try to get builtin commands as normal + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # No builtin found. Look up an explicit command alias in the config + if cmd_name in _config.aliases: + actual_cmd = _config.aliases[cmd_name] + return click.Group.get_command(self, ctx, actual_cmd) + + # Alternative option: if we did not find an explicit alias we + # allow automatic abbreviation of the command. "status" for + # instance will match "st". We only allow that however if + # there is only one command. + matches = [x for x in self.list_commands(ctx) + if x.lower().startswith(cmd_name.lower())] + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + + # # Helper functions # @@ -37,9 +105,10 @@ def reporthook(count, block_size, total_size): percent = int(count * block_size * 100 / total_size) time_left = (total_size - progress_size) / speed / 1024 sys.stdout.write("\r...%d%%, %d MB, %d KB/s, %d seconds left... " % - (percent, progress_size / (1024 * 1024), speed, time_left)) + (percent, progress_size / (1024 * 1024), speed, time_left)) sys.stdout.flush() + # TODO: Embed tag name info into docker image meta data at build time, # and extract tag name from docker image file. def get_docker_tag_name(image): @@ -54,6 +123,7 @@ def get_docker_tag_name(image): return "unknown" return tag + # Function which validates whether a given URL specifies an existent file # on a reachable remote machine. Will abort the current operation if not def validate_url_or_abort(url): @@ -74,11 +144,13 @@ def validate_url_or_abort(url): click.echo("Image file not found on remote machine. Aborting...") raise click.Abort() + # Callback for confirmation prompt. Aborts if user enters "n" def abort_if_false(ctx, param, value): if not value: ctx.abort() + def get_container_image_name(container_name): # example image: docker-lldp-sv2:latest cmd = "docker inspect --format '{{.Config.Image}}' " + container_name @@ -94,6 +166,7 @@ def get_container_image_name(container_name): image_name = proc.stdout.read().rstrip() return image_name + def get_container_image_id(image_tag): # TODO: extract commond docker info fetching functions # this is image_id for image with tag, like 'docker-teamd:latest' @@ -102,6 +175,7 @@ def get_container_image_id(image_tag): image_id = proc.stdout.read().rstrip() return image_id + def get_container_image_id_all(image_name): # All images id under the image name like 'docker-teamd' cmd = "docker images --format '{{.ID}}' " + image_name @@ -111,6 +185,7 @@ def get_container_image_id_all(image_name): image_id_all = set(image_id_all) return image_id_all + def hget_warm_restart_table(db_name, table_name, warm_app_name, key): db = SonicV2Connector() db.connect(db_name, False) @@ -118,29 +193,42 @@ def hget_warm_restart_table(db_name, table_name, warm_app_name, key): client = db.get_redis_client(db_name) return client.hget(_hash, key) + def hdel_warm_restart_table(db_name, table_name, warm_app_name, key): db = SonicV2Connector() db.connect(db_name, False) _hash = table_name + db.get_db_separator(db_name) + warm_app_name client = db.get_redis_client(db_name) - return client.hdel(_hash, key) + return client.hdel(_hash, key) + + +def print_deprecation_warning(deprecated_cmd_or_subcmd, new_cmd_or_subcmd): + click.secho("Warning: '{}' {}command is deprecated and will be removed in the future" + .format(deprecated_cmd_or_subcmd, "" if deprecated_cmd_or_subcmd == "sonic_installer" else "sub"), + fg="red", err=True) + click.secho("Please use '{}' instead".format(new_cmd_or_subcmd), fg="red", err=True) + # Main entrypoint -@click.group() -def cli(): +@click.group(cls=AliasedGroup) +def sonic_installer(): """ SONiC image installation manager """ if os.geteuid() != 0: exit("Root privileges required for this operation") + # Warn the user if they are calling the deprecated version of the command (with an underscore instead of a hyphen) + if os.path.basename(sys.argv[0]) == "sonic_installer": + print_deprecation_warning("sonic_installer", "sonic-installer") + # Install image -@cli.command() +@sonic_installer.command('install') @click.option('-y', '--yes', is_flag=True, callback=abort_if_false, - expose_value=False, prompt='New image will be installed, continue?') + expose_value=False, prompt='New image will be installed, continue?') @click.option('-f', '--force', is_flag=True, - help="Force installation of an image of a type which differs from that of the current running image") + help="Force installation of an image of a type which differs from that of the current running image") @click.option('--skip_migration', is_flag=True, - help="Do not migrate current configuration to the newly installed image") + help="Do not migrate current configuration to the newly installed image") @click.argument('url') def install(url, force, skip_migration=False): """ Install image from local binary or URL""" @@ -188,12 +276,12 @@ def install(url, force, skip_migration=False): # Finally, sync filesystem run_command("sync;sync;sync") - run_command("sleep 3") # wait 3 seconds after sync + run_command("sleep 3") # wait 3 seconds after sync click.echo('Done') # List installed images -@cli.command('list') +@sonic_installer.command('list') def list_command(): """ Print installed images """ bootloader = get_bootloader() @@ -206,32 +294,43 @@ def list_command(): for image in images: click.echo(image) + # Set default image for boot -@cli.command('set_default') +@sonic_installer.command('set-default') @click.argument('image') def set_default(image): """ Choose image to boot from by default """ + # Warn the user if they are calling the deprecated version of the subcommand (with an underscore instead of a hyphen) + if "set_default" in sys.argv: + print_deprecation_warning("set_default", "set-default") + bootloader = get_bootloader() if image not in bootloader.get_installed_images(): click.echo('Error: Image does not exist') raise click.Abort() bootloader.set_default_image(image) + # Set image for next boot -@cli.command('set_next_boot') +@sonic_installer.command('set-next-boot') @click.argument('image') def set_next_boot(image): """ Choose image for next reboot (one time action) """ + # Warn the user if they are calling the deprecated version of the subcommand (with underscores instead of hyphens) + if "set_next_boot" in sys.argv: + print_deprecation_warning("set_next_boot", "set-next-boot") + bootloader = get_bootloader() if image not in bootloader.get_installed_images(): click.echo('Error: Image does not exist') sys.exit(1) bootloader.set_next_image(image) + # Uninstall image -@cli.command() +@sonic_installer.command('remove') @click.option('-y', '--yes', is_flag=True, callback=abort_if_false, - expose_value=False, prompt='Image will be removed, continue?') + expose_value=False, prompt='Image will be removed, continue?') @click.argument('image') def remove(image): """ Uninstall image """ @@ -247,11 +346,16 @@ def remove(image): # TODO: check if image is next boot or default boot and fix these bootloader.remove_image(image) + # Retrieve version from binary image file and print to screen -@cli.command('binary_version') +@sonic_installer.command('binary-version') @click.argument('binary_image_path') def binary_version(binary_image_path): """ Get version from local binary image file """ + # Warn the user if they are calling the deprecated version of the subcommand (with an underscore instead of a hyphen) + if "binary_version" in sys.argv: + print_deprecation_warning("binary_version", "binary-version") + bootloader = get_bootloader() version = bootloader.get_binary_image_version(binary_image_path) if not version: @@ -260,10 +364,11 @@ def binary_version(binary_image_path): else: click.echo(version) + # Remove installed images which are not current and next -@cli.command() +@sonic_installer.command('cleanup') @click.option('-y', '--yes', is_flag=True, callback=abort_if_false, - expose_value=False, prompt='Remove images which are not current and next, continue?') + expose_value=False, prompt='Remove images which are not current and next, continue?') def cleanup(): """ Remove installed images which are not current and next """ bootloader = get_bootloader() @@ -280,19 +385,39 @@ def cleanup(): if image_removed == 0: click.echo("No image(s) to remove") + +DOCKER_CONTAINER_LIST = [ + "bgp", + "dhcp_relay", + "lldp", + "nat", + "pmon", + "radv", + "restapi", + "sflow", + "snmp", + "swss", + "syncd", + "teamd", + "telemetry" +] + # Upgrade docker image -@cli.command('upgrade_docker') +@sonic_installer.command('upgrade-docker') @click.option('-y', '--yes', is_flag=True, callback=abort_if_false, - expose_value=False, prompt='New docker image will be installed, continue?') + expose_value=False, prompt='New docker image will be installed, continue?') @click.option('--cleanup_image', is_flag=True, help="Clean up old docker image") @click.option('--skip_check', is_flag=True, help="Skip task check for docker upgrade") @click.option('--tag', type=str, help="Tag for the new docker image") @click.option('--warm', is_flag=True, help="Perform warm upgrade") @click.argument('container_name', metavar='', required=True, - type=click.Choice(["swss", "snmp", "lldp", "bgp", "pmon", "dhcp_relay", "telemetry", "teamd", "radv", "amon", "sflow"])) + type=click.Choice(DOCKER_CONTAINER_LIST)) @click.argument('url') def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): """ Upgrade docker image from local binary or URL""" + # Warn the user if they are calling the deprecated version of the subcommand (with an underscore instead of a hyphen) + if "upgrade_docker" in sys.argv: + print_deprecation_warning("upgrade_docker", "upgrade-docker") image_name = get_container_image_name(container_name) image_latest = image_name + ":latest" @@ -329,8 +454,8 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): state_db.close(state_db.STATE_DB) if container_name == "swss" or container_name == "bgp" or container_name == "teamd": - if warm_configured == False and warm: - run_command("config warm_restart enable %s" % container_name) + if warm_configured is False and warm: + run_command("config warm_restart enable %s" % container_name) # Fetch tag of current running image tag_previous = get_docker_tag_name(image_latest) @@ -338,7 +463,7 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): run_command("docker load < %s" % image_path) warm_app_names = [] # warm restart specific procssing for swss, bgp and teamd dockers. - if warm_configured == True or warm: + if warm_configured is True or warm: # make sure orchagent is in clean state if swss is to be upgraded if container_name == "swss": skipPendingTaskCheck = "" @@ -353,7 +478,7 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): if not skip_check: click.echo("Orchagent is not in clean state, RESTARTCHECK failed") # Restore orignal config before exit - if warm_configured == False and warm: + if warm_configured is False and warm: run_command("config warm_restart disable %s" % container_name) # Clean the image loaded earlier image_id_latest = get_container_image_id(image_latest) @@ -413,7 +538,7 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): exp_state = "reconciled" state = "" # post warm restart specific procssing for swss, bgp and teamd dockers, wait for reconciliation state. - if warm_configured == True or warm: + if warm_configured is True or warm: count = 0 for warm_app_name in warm_app_names: state = "" @@ -425,16 +550,16 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): count += 1 time.sleep(2) state = hget_warm_restart_table("STATE_DB", "WARM_RESTART_TABLE", warm_app_name, "state") - syslog.syslog("%s reached %s state"%(warm_app_name, state)) + syslog.syslog("%s reached %s state" % (warm_app_name, state)) sys.stdout.write("]\n\r") if state != exp_state: - click.echo("%s failed to reach %s state"%(warm_app_name, exp_state)) - syslog.syslog(syslog.LOG_ERR, "%s failed to reach %s state"%(warm_app_name, exp_state)) + click.echo("%s failed to reach %s state" % (warm_app_name, exp_state)) + syslog.syslog(syslog.LOG_ERR, "%s failed to reach %s state" % (warm_app_name, exp_state)) else: exp_state = "" # this is cold upgrade # Restore to previous cold restart setting - if warm_configured == False and warm: + if warm_configured is False and warm: if container_name == "swss" or container_name == "bgp" or container_name == "teamd": run_command("config warm_restart disable %s" % container_name) @@ -444,14 +569,19 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): click.echo('Failed') sys.exit(1) + # rollback docker image -@cli.command('rollback_docker') +@sonic_installer.command('rollback-docker') @click.option('-y', '--yes', is_flag=True, callback=abort_if_false, - expose_value=False, prompt='Docker image will be rolled back, continue?') + expose_value=False, prompt='Docker image will be rolled back, continue?') @click.argument('container_name', metavar='', required=True, - type=click.Choice(["swss", "snmp", "lldp", "bgp", "pmon", "dhcp_relay", "telemetry", "teamd", "radv", "amon", "sflow"])) + type=click.Choice(DOCKER_CONTAINER_LIST)) def rollback_docker(container_name): """ Rollback docker image to previous version""" + # Warn the user if they are calling the deprecated version of the subcommand (with an underscore instead of a hyphen) + if "rollback_docker" in sys.argv: + print_deprecation_warning("rollback_docker", "rollback-docker") + image_name = get_container_image_name(container_name) # All images id under the image name image_id_all = get_container_image_id_all(image_name) @@ -487,4 +617,4 @@ def verify_next_image(): click.echo('Image successfully verified') if __name__ == '__main__': - cli() + sonic_installer() From 527860d67fb3c1d6c85207eeb5ef3765d367fc85 Mon Sep 17 00:00:00 2001 From: shlomibitton <60430976+shlomibitton@users.noreply.github.com> Date: Thu, 16 Jul 2020 20:26:56 +0300 Subject: [PATCH 05/48] [show] Add support for QSFP-DD cables on 'show' command (#989) - What I did Add support for QSFP-DD cables on 'sfpshow' script Adapt sfp_test to new eeprom output. - How I did it Added a new label "application_advertisement" to print from DB. Exclude "specification compliance" print from QSFP-DD cables. Signed-off-by: Shlomi Bitton --- scripts/sfpshow | 16 ++++++++++------ sonic-utilities-tests/mock_tables/state_db.json | 3 ++- sonic-utilities-tests/sfp_test.py | 3 +++ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/scripts/sfpshow b/scripts/sfpshow index bf0b90408a..a4a5e78c02 100755 --- a/scripts/sfpshow +++ b/scripts/sfpshow @@ -38,7 +38,8 @@ qsfp_data_map = {'model': 'Vendor PN', 'nominal_bit_rate': 'Nominal Bit Rate(100Mbs)', 'specification_compliance': 'Specification compliance', 'encoding': 'Encoding', - 'connector': 'Connector' + 'connector': 'Connector', + 'application_advertisement': 'Application Advertisement' } sfp_dom_channel_monitor_map = {'rx1power': 'RXPower', @@ -261,11 +262,14 @@ class SFPShow(object): elif key1 == 'cable_length': pass elif key1 == 'specification_compliance': - spefic_compliance_dict = eval(sfp_info_dict['specification_compliance']) - sorted_compliance_key_table = natsorted(spefic_compliance_dict) - out_put = out_put + ident + qsfp_data_map['specification_compliance'] + ': ' + '\n' - for compliance_key in sorted_compliance_key_table: - out_put = out_put + ident + ident + compliance_key + ': ' + spefic_compliance_dict[compliance_key] + '\n' + if sfp_info_dict['type'] == "QSFP-DD Double Density 8X Pluggable Transceiver": + out_put = out_put + ident + qsfp_data_map[key1] + ': ' + sfp_info_dict[key1] + '\n' + else: + out_put = out_put + ident + qsfp_data_map['specification_compliance'] + ': ' + '\n' + spefic_compliance_dict = eval(sfp_info_dict['specification_compliance']) + sorted_compliance_key_table = natsorted(spefic_compliance_dict) + for compliance_key in sorted_compliance_key_table: + out_put = out_put + ident + ident + compliance_key + ': ' + spefic_compliance_dict[compliance_key] + '\n' else: out_put = out_put + ident + qsfp_data_map[key1] + ': ' + sfp_info_dict[key1] + '\n' diff --git a/sonic-utilities-tests/mock_tables/state_db.json b/sonic-utilities-tests/mock_tables/state_db.json index b44b60df9f..ccc9f83eb8 100644 --- a/sonic-utilities-tests/mock_tables/state_db.json +++ b/sonic-utilities-tests/mock_tables/state_db.json @@ -14,7 +14,8 @@ "cable_type": "Length Cable Assembly(m)", "cable_length": "3", "specification_compliance": "{'10/40G Ethernet Compliance Code': '40G Active Cable (XLPPI)'}", - "nominal_bit_rate": "255" + "nominal_bit_rate": "255", + "application_advertisement": "N/A" }, "TRANSCEIVER_DOM_SENSOR|Ethernet0": { "temperature": "30.9258", diff --git a/sonic-utilities-tests/sfp_test.py b/sonic-utilities-tests/sfp_test.py index 695258d60b..c9f5784f3f 100644 --- a/sonic-utilities-tests/sfp_test.py +++ b/sonic-utilities-tests/sfp_test.py @@ -1,6 +1,7 @@ import sys import os from click.testing import CliRunner +import mock_tables.dbconnector test_path = os.path.dirname(os.path.abspath(__file__)) modules_path = os.path.dirname(test_path) @@ -36,6 +37,7 @@ def test_sfp_eeprom_with_dom(self): runner = CliRunner() result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["eeprom"], ["Ethernet0 -d"]) expected = """Ethernet0: SFP EEPROM detected + Application Advertisement: N/A Connector: No separable connector Encoding: 64B66B Extended Identifier: Power Class 3(2.5W max), CDR present in Rx Tx @@ -89,6 +91,7 @@ def test_sfp_eeprom(self): runner = CliRunner() result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["eeprom"], ["Ethernet0"]) expected = """Ethernet0: SFP EEPROM detected + Application Advertisement: N/A Connector: No separable connector Encoding: 64B66B Extended Identifier: Power Class 3(2.5W max), CDR present in Rx Tx From e3d6ba069a6ae858305f7f866d9d881ebb5af5ea Mon Sep 17 00:00:00 2001 From: Danny Allen Date: Thu, 16 Jul 2020 16:34:54 -0700 Subject: [PATCH 06/48] [acl_loader] Fix bugs in acl_loader (#991) - Support protocol number == 0 - Emit warning if --table_name not found in Config DB Signed-off-by: Danny Allen --- acl_loader/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/acl_loader/main.py b/acl_loader/main.py index c719100fd9..f23aefc2e4 100644 --- a/acl_loader/main.py +++ b/acl_loader/main.py @@ -244,6 +244,9 @@ def set_table_name(self, table_name): :param table_name: Table name :return: """ + if not self.is_table_valid(table_name): + warning("Table \"%s\" not found" % table_name) + self.current_table = table_name def set_session_name(self, session_name): @@ -412,7 +415,7 @@ def convert_l2(self, table_name, rule_idx, rule): def convert_ip(self, table_name, rule_idx, rule): rule_props = {} - if rule.ip.config.protocol: + if rule.ip.config.protocol or rule.ip.config.protocol == 0: # 0 is a valid protocol number if self.ip_protocol_map.has_key(rule.ip.config.protocol): rule_props["IP_PROTOCOL"] = self.ip_protocol_map[rule.ip.config.protocol] else: From 82dfe50578dac1d86951ff32edb0b8b3912a8df7 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Sun, 19 Jul 2020 13:40:19 -0700 Subject: [PATCH 07/48] [sonic-installer] Update group name for 'verify-next-image' subcommand (#995) Root group name was changed from `cli` to `sonic_installer` in https://github.com/Azure/sonic-utilities/pull/983. However, `verify-next-image` subcommand was being added in an already-open PR https://github.com/Azure/sonic-utilities/pull/979/ at the time. It did not get updated before merge. This aligns it with the new group name. --- sonic_installer/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonic_installer/main.py b/sonic_installer/main.py index 8197e12069..8f016037f7 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -607,7 +607,7 @@ def rollback_docker(container_name): click.echo('Done') # verify the next image -@cli.command('verify-next-image') +@sonic_installer.command('verify-next-image') def verify_next_image(): """ Verify the next image for reboot""" bootloader = get_bootloader() From 995cf3988c0754a1627b98e90c7e6f3ac4fd106e Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Tue, 21 Jul 2020 15:53:06 -0700 Subject: [PATCH 08/48] [config] Restart telemetry service upon config (re)load (#992) Add telemetry service to the list of services to stop/reset-failed/restart during config load/reload operations. --- config/main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/config/main.py b/config/main.py index e639e4106f..ef0b60966d 100755 --- a/config/main.py +++ b/config/main.py @@ -548,8 +548,10 @@ def _get_disabled_services_list(): def _stop_services(): + # This list is order-dependent. Please add services in the order they should be stopped # on Mellanox platform pmon is stopped by syncd services_to_stop = [ + 'telemetry', 'restapi', 'swss', 'lldp', @@ -566,6 +568,7 @@ def _stop_services(): def _reset_failed_services(): + # This list is order-independent. Please keep list in alphabetical order services_to_reset = [ 'bgp', 'dhcp_relay', @@ -573,23 +576,25 @@ def _reset_failed_services(): 'hostname-config', 'interfaces-config', 'lldp', + 'nat', 'ntp-config', 'pmon', 'radv', + 'restapi', 'rsyslog-config', + 'sflow', 'snmp', 'swss', 'syncd', 'teamd', - 'nat', - 'sflow', - 'restapi' + 'telemetry' ] execute_systemctl(services_to_reset, SYSTEMCTL_ACTION_RESET_FAILED) def _restart_services(): + # This list is order-dependent. Please add services in the order they should be started # on Mellanox platform pmon is started by syncd services_to_restart = [ 'hostname-config', @@ -603,7 +608,8 @@ def _restart_services(): 'hostcfgd', 'nat', 'sflow', - 'restapi' + 'restapi', + 'telemetry' ] disable_services = _get_disabled_services_list() From aa1b072aa712e7e6663998f19c88b90048febb12 Mon Sep 17 00:00:00 2001 From: Sangita Maity Date: Thu, 23 Jul 2020 00:29:06 -0700 Subject: [PATCH 09/48] [config] Add Initial draft of Breakout Mode subcommand (#766) Added breakout subcommand in config command Signed-off-by: Sangita Maity --- config/main.py | 275 +++++++++++++++++++++++++++++++++++++++ doc/Command-Reference.md | 31 +++++ 2 files changed, 306 insertions(+) diff --git a/config/main.py b/config/main.py index ef0b60966d..f838fed653 100755 --- a/config/main.py +++ b/config/main.py @@ -15,7 +15,10 @@ import ipaddress from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig from minigraph import parse_device_desc_xml +from config_mgmt import ConfigMgmtDPB from utilities_common.intf_filter import parse_interface_in_filter +from utilities_common.util_base import UtilHelper +from portconfig import get_child_ports, get_port_config_file_name import aaa import mlnx @@ -30,6 +33,7 @@ ASIC_CONF_FILENAME = 'asic.conf' DEFAULT_CONFIG_DB_FILE = '/etc/sonic/config_db.json' NAMESPACE_PREFIX = 'asic' +INTF_KEY = "interfaces" INIT_CFG_FILE = '/etc/sonic/init_cfg.json' @@ -118,6 +122,159 @@ def get_command(self, ctx, cmd_name): except (KeyError, TypeError): raise click.Abort() +# +# Load breakout config file for Dynamic Port Breakout +# + +try: + # Load the helper class + helper = UtilHelper() + (platform, hwsku) = helper.get_platform_and_hwsku() +except Exception as e: + click.secho("Failed to get platform and hwsku with error:{}".format(str(e)), fg='red') + raise click.Abort() + +try: + breakout_cfg_file = get_port_config_file_name(hwsku, platform) +except Exception as e: + click.secho("Breakout config file not found with error:{}".format(str(e)), fg='red') + raise click.Abort() + +# +# Breakout Mode Helper functions +# + +# Read given JSON file +def readJsonFile(fileName): + try: + with open(fileName) as f: + result = json.load(f) + except Exception as e: + raise Exception(str(e)) + return result + +def _get_option(ctx,args,incomplete): + """ Provides dynamic mode option as per user argument i.e. interface name """ + all_mode_options = [] + interface_name = args[-1] + + if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'): + return [] + else: + breakout_file_input = readJsonFile(breakout_cfg_file) + if interface_name in breakout_file_input[INTF_KEY]: + breakout_mode_list = [v["breakout_modes"] for i ,v in breakout_file_input[INTF_KEY].items() if i == interface_name][0] + breakout_mode_options = [] + for i in breakout_mode_list.split(','): + breakout_mode_options.append(i) + all_mode_options = [str(c) for c in breakout_mode_options if incomplete in c] + return all_mode_options + +def shutdown_interfaces(ctx, del_intf_dict): + """ shut down all the interfaces before deletion """ + for intf in del_intf_dict.keys(): + config_db = ctx.obj['config_db'] + if get_interface_naming_mode() == "alias": + interface_name = interface_alias_to_name(intf) + if interface_name is None: + click.echo("[ERROR] interface name is None!") + return False + + if interface_name_is_valid(intf) is False: + click.echo("[ERROR] Interface name is invalid. Please enter a valid interface name!!") + return False + + port_dict = config_db.get_table('PORT') + if not port_dict: + click.echo("port_dict is None!") + return False + + if intf in port_dict.keys(): + config_db.mod_entry("PORT", intf, {"admin_status": "down"}) + else: + click.secho("[ERROR] Could not get the correct interface name, exiting", fg='red') + return False + return True + +def _validate_interface_mode(ctx, breakout_cfg_file, interface_name, target_brkout_mode, cur_brkout_mode): + """ Validate Parent interface and user selected mode before starting deletion or addition process """ + breakout_file_input = readJsonFile(breakout_cfg_file)["interfaces"] + + if interface_name not in breakout_file_input: + click.secho("[ERROR] {} is not a Parent port. So, Breakout Mode is not available on this port".format(interface_name), fg='red') + return False + + # Check whether target breakout mode is available for the user-selected interface or not + if target_brkout_mode not in breakout_file_input[interface_name]["breakout_modes"]: + click.secho('[ERROR] Target mode {} is not available for the port {}'. format(target_brkout_mode, interface_name), fg='red') + return False + + # Get config db context + config_db = ctx.obj['config_db'] + port_dict = config_db.get_table('PORT') + + # Check whether there is any port in config db. + if not port_dict: + click.echo("port_dict is None!") + return False + + # Check whether the user-selected interface is part of 'port' table in config db. + if interface_name not in port_dict.keys(): + click.secho("[ERROR] {} is not in port_dict".format(interface_name)) + return False + click.echo("\nRunning Breakout Mode : {} \nTarget Breakout Mode : {}".format(cur_brkout_mode, target_brkout_mode)) + if (cur_brkout_mode == target_brkout_mode): + click.secho("[WARNING] No action will be taken as current and desired Breakout Mode are same.", fg='magenta') + sys.exit(0) + return True + +def load_ConfigMgmt(verbose): + """ Load config for the commands which are capable of change in config DB. """ + try: + cm = ConfigMgmtDPB(debug=verbose) + return cm + except Exception as e: + raise Exception("Failed to load the config. Error: {}".format(str(e))) + +def breakout_warnUser_extraTables(cm, final_delPorts, confirm=True): + """ + Function to warn user about extra tables while Dynamic Port Breakout(DPB). + confirm: re-confirm from user to proceed. + Config Tables Without Yang model considered extra tables. + cm = instance of config MGMT class. + """ + try: + # check if any extra tables exist + eTables = cm.tablesWithOutYang() + if len(eTables): + # find relavent tables in extra tables, i.e. one which can have deleted + # ports + tables = cm.configWithKeys(configIn=eTables, keys=final_delPorts) + click.secho("Below Config can not be verified, It may cause harm "\ + "to the system\n {}".format(json.dumps(tables, indent=2))) + click.confirm('Do you wish to Continue?', abort=True) + except Exception as e: + raise Exception("Failed in breakout_warnUser_extraTables. Error: {}".format(str(e))) + return + +def breakout_Ports(cm, delPorts=list(), portJson=dict(), force=False, \ + loadDefConfig=False, verbose=False): + + deps, ret = cm.breakOutPort(delPorts=delPorts, portJson=portJson, \ + force=force, loadDefConfig=loadDefConfig) + # check if DPB failed + if ret == False: + if not force and deps: + click.echo("Dependecies Exist. No further action will be taken") + click.echo("*** Printing dependecies ***") + for dep in deps: + click.echo(dep) + sys.exit(0) + else: + click.echo("[ERROR] Port breakout Failed!!! Opting Out") + raise click.Abort() + return + # # Helper functions # @@ -2186,6 +2343,124 @@ def speed(ctx, interface_name, interface_speed, verbose): command += " -vv" run_command(command, display_cmd=verbose) +# +# 'breakout' subcommand +# + +@interface.command() +@click.argument('interface_name', metavar='', required=True) +@click.argument('mode', required=True, type=click.STRING, autocompletion=_get_option) +@click.option('-f', '--force-remove-dependencies', is_flag=True, help='Clear all depenedecies internally first.') +@click.option('-l', '--load-predefined-config', is_flag=True, help='load predefied user configuration (alias, lanes, speed etc) first.') +@click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Do you want to Breakout the port, continue?') +@click.option('-v', '--verbose', is_flag=True, help="Enable verbose output") +@click.pass_context +def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load_predefined_config): + """ Set interface breakout mode """ + if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'): + click.secho("[ERROR] Breakout feature is not available without platform.json file", fg='red') + raise click.Abort() + + # Connect to config db and get the context + config_db = ConfigDBConnector() + config_db.connect() + ctx.obj['config_db'] = config_db + + target_brkout_mode = mode + + # Get current breakout mode + cur_brkout_dict = config_db.get_table('BREAKOUT_CFG') + cur_brkout_mode = cur_brkout_dict[interface_name]["brkout_mode"] + + # Validate Interface and Breakout mode + if not _validate_interface_mode(ctx, breakout_cfg_file, interface_name, mode, cur_brkout_mode): + raise click.Abort() + + """ Interface Deletion Logic """ + # Get list of interfaces to be deleted + del_ports = get_child_ports(interface_name, cur_brkout_mode, breakout_cfg_file) + del_intf_dict = {intf: del_ports[intf]["speed"] for intf in del_ports} + + if del_intf_dict: + """ shut down all the interface before deletion """ + ret = shutdown_interfaces(ctx, del_intf_dict) + if not ret: + raise click.Abort() + click.echo("\nPorts to be deleted : \n {}".format(json.dumps(del_intf_dict, indent=4))) + + else: + click.secho("[ERROR] del_intf_dict is None! No interfaces are there to be deleted", fg='red') + raise click.Abort() + + """ Interface Addition Logic """ + # Get list of interfaces to be added + add_ports = get_child_ports(interface_name, target_brkout_mode, breakout_cfg_file) + add_intf_dict = {intf: add_ports[intf]["speed"] for intf in add_ports} + + if add_intf_dict: + click.echo("Ports to be added : \n {}".format(json.dumps(add_intf_dict, indent=4))) + else: + 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_item = [intf for intf, speed in del_intf_dict.items() if intf in add_intf_dict.keys() and speed == add_intf_dict[intf]] + + # Remove the interface which remains unchanged from both del_intf_dict and add_intf_dict + map(del_intf_dict.pop, matched_item) + map(add_intf_dict.pop, matched_item) + + 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 len(add_intf_dict.keys()) == 0: + click.secho("[ERROR] add_intf_dict is None! 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.keys(): + port_dict[intf] = add_ports[intf] + + # writing JSON object + with open('new_port_config.json', 'w') as f: + json.dump(port_dict, f, indent=4) + + # Start Interation with Dy Port BreakOut Config Mgmt + try: + """ Load config for the commands which are capable of change in config DB """ + cm = load_ConfigMgmt(verbose) + + """ Delete all ports if forced else print dependencies using ConfigMgmt API """ + final_delPorts = [intf for intf in del_intf_dict.keys()] + """ Warn user if tables without yang models exist and have final_delPorts """ + breakout_warnUser_extraTables(cm, final_delPorts, confirm=True) + + # Create a dictionary containing all the added ports with its capabilities like alias, lanes, speed etc. + portJson = dict(); portJson['PORT'] = port_dict + + # breakout_Ports will abort operation on failure, So no need to check return + breakout_Ports(cm, delPorts=final_delPorts, portJson=portJson, force=force_remove_dependencies, \ + loadDefConfig=load_predefined_config, verbose=verbose) + + # Set Current Breakout mode in config DB + brkout_cfg_keys = config_db.get_keys('BREAKOUT_CFG') + if interface_name.decode("utf-8") not in brkout_cfg_keys: + click.secho("[ERROR] {} is not present in 'BREAKOUT_CFG' Table!".\ + format(interface_name), fg='red') + raise click.Abort() + config_db.set_entry("BREAKOUT_CFG", interface_name,\ + {'brkout_mode': target_brkout_mode}) + click.secho("Breakout process got successfully completed.".\ + format(interface_name), fg="cyan", underline=True) + click.echo("Please note loaded setting will be lost after system reboot. To preserve setting, run `config save`.") + + except Exception as e: + click.secho("Failed to break out Port. Error: {}".format(str(e)), \ + fg='magenta') + sys.exit(0) + def _get_all_mgmtinterface_keys(): """Returns list of strings containing mgmt interface keys """ diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index 30df5c57bb..fcf08821d2 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -2660,6 +2660,7 @@ This sub-section explains the following list of configuration on the interfaces. 3) shutdown - to administratively shut down the interface 4) speed - to set the interface speed 5) startup - to bring up the administratively shutdown interface +6) breakout - to set interface breakout mode From 201904 release onwards, the “config interface” command syntax is changed and the format is as follows: @@ -2962,6 +2963,36 @@ This command is used to configure the mtu for the Physical interface. Use the va admin@sonic:~$ sudo config interface mtu Ethernet64 1500 ``` +**config interface breakout** + +This command is used to set breakout mode available for user-specified interface. +kindly use, double tab i.e. to see the available breakout option customized for each interface provided by the user. + +- Usage: + ``` + sudo config interface breakout --help + Usage: config interface breakout [OPTIONS] MODE + + Set interface breakout mode + + Options: + -f, --force-remove-dependencies + Clear all depenedecies internally first. + -l, --load-predefined-config load predefied user configuration (alias, + lanes, speed etc) first. + -y, --yes + -v, --verbose Enable verbose output + -?, -h, --help Show this message and exit. + ``` +- Example : + ``` + admin@sonic:~$ sudo config interface breakout Ethernet0 + + 1x100G[40G] 2x50G 4x25G[10G] + + admin@sonic:~$ sudo config interface breakout Ethernet0 4x25G[10G] -f -l -v -y + ``` + Go Back To [Beginning of the document](#) or [Beginning of this section](#interfaces) From ef9ac2a7d5516b04f68583d9bc8f44d21d4886c3 Mon Sep 17 00:00:00 2001 From: Sangita Maity Date: Wed, 29 Jul 2020 23:51:55 -0700 Subject: [PATCH 10/48] importing json needed for DPB CLI (#1010) --- config/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config/main.py b/config/main.py index f838fed653..aad6b3227a 100755 --- a/config/main.py +++ b/config/main.py @@ -10,6 +10,7 @@ import time import netifaces import threading +import json import sonic_device_util import ipaddress From c6c5be127b26776f4748c39b4903a34dcd6a561c Mon Sep 17 00:00:00 2001 From: shlomibitton <60430976+shlomibitton@users.noreply.github.com> Date: Thu, 30 Jul 2020 19:57:00 +0300 Subject: [PATCH 11/48] [show] Fix for 'trunk' PortChannel reported as 'routed' port (#1002) Adding a PortChannel to a Vlan group, will change the 'Vlan' tag to 'trunk'. Signed-off-by: Shlomi Bitton --- scripts/intfutil | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/intfutil b/scripts/intfutil index f51c945936..c37a68e962 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -290,7 +290,7 @@ def po_speed_dict(po_int_dict, appl_db): po_speed_dict = {} return po_speed_dict -def appl_db_portchannel_status_get(appl_db, config_db, po_name, status_type, portchannel_speed_dict): +def appl_db_portchannel_status_get(appl_db, config_db, po_name, status_type, portchannel_speed_dict, combined_int_to_vlan_po_dict=None): """ Get the port status """ @@ -301,7 +301,10 @@ def appl_db_portchannel_status_get(appl_db, config_db, po_name, status_type, por status = portchannel_speed_dict[po_name] return status if status_type == "vlan": - status = "routed" + if combined_int_to_vlan_po_dict and po_name in combined_int_to_vlan_po_dict.keys(): + status = "trunk" + else: + status = "routed" return status if status_type == "mtu": status = config_db.get(config_db.CONFIG_DB, po_table_id, status_type) @@ -388,7 +391,7 @@ class IntfStatus(object): appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_MTU_STATUS, self.portchannel_speed_dict), appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_FEC, self.portchannel_speed_dict), appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_ALIAS, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, "vlan", self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.appl_db, self.config_db, po, "vlan", self.portchannel_speed_dict, self.combined_int_to_vlan_po_dict), appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_OPER_STATUS, self.portchannel_speed_dict), appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_ADMIN_STATUS, self.portchannel_speed_dict), appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_OPTICS_TYPE, self.portchannel_speed_dict), From c0c3cce6bee0e335cf1b5528f1172b511d48ee12 Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Thu, 30 Jul 2020 10:02:56 -0700 Subject: [PATCH 12/48] [config/config_mgmt.py]: Fix typo and enable test for tablesWithOutYang() (#1012) Fix typo and enable test for tablesWithOutYang() Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- config/config_mgmt.py | 2 +- sonic-utilities-tests/config_mgmt_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config_mgmt.py b/config/config_mgmt.py index c9db79ea90..5e35f299ae 100644 --- a/config/config_mgmt.py +++ b/config/config_mgmt.py @@ -83,7 +83,7 @@ def __init__(self, source="configDB", debug=False, allowTablesWithoutYang=True): def __del__(self): pass - def tablesWithoutYang(self): + def tablesWithOutYang(self): ''' Return tables loaded in config for which YANG model does not exist. diff --git a/sonic-utilities-tests/config_mgmt_test.py b/sonic-utilities-tests/config_mgmt_test.py index aec7f75e30..4133ab2194 100644 --- a/sonic-utilities-tests/config_mgmt_test.py +++ b/sonic-utilities-tests/config_mgmt_test.py @@ -32,7 +32,7 @@ def test_table_without_yang(self): self.updateConfig(curConfig, unknown) self.writeJson(curConfig, config_mgmt.CONFIG_DB_JSON_FILE) cm = config_mgmt.ConfigMgmt(source=config_mgmt.CONFIG_DB_JSON_FILE) - #assert "unknown_table" in cm.tablesWithoutYang() + assert "unknown_table" in cm.tablesWithOutYang() return def test_search_keys(self): From 216688e99cdb5b9ac57a01b703d48d03c78b8962 Mon Sep 17 00:00:00 2001 From: lguohan Date: Sun, 2 Aug 2020 23:33:06 -0700 Subject: [PATCH 13/48] [tests]: rename sonic-utilitie-tests to tests (#1022) --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- scripts/dropconfig | 2 +- scripts/dropstat | 2 +- scripts/gearboxutil | 2 +- scripts/intfstat | 2 +- scripts/intfutil | 2 +- scripts/psushow | 2 +- scripts/sfpshow | 2 +- setup.py | 4 ++-- {sonic-utilities-tests => tests}/__init__.py | 0 .../acl_input/acl1.json | 0 .../acl_input/acl2.json | 0 .../acl_input/empty_acl.json | 0 .../acl_loader_test.py | 0 {sonic-utilities-tests => tests}/aclshow_test.py | 0 .../config_mgmt_test.py | 0 .../drops_group_test.py | 0 .../filter_fdb_entries_test.py | 0 .../filter_fdb_input/__init__.py | 0 .../filter_fdb_input/arp.json | 0 .../filter_fdb_input/config_db.json | 0 .../filter_fdb_input/expected_fdb.json | 0 .../filter_fdb_input/fdb.json | 0 .../filter_fdb_input/test_vectors.py | 16 ++++++++-------- {sonic-utilities-tests => tests}/gearbox_test.py | 2 +- .../intfstat_test.py | 0 .../intfutil_test.py | 0 .../mock_tables/__init__.py | 0 .../mock_tables/appl_db.json | 0 .../mock_tables/asic_db.json | 0 .../mock_tables/config_db.json | 0 .../mock_tables/counters_db.json | 0 .../mock_tables/dbconnector.py | 0 .../mock_tables/state_db.json | 0 .../port2alias_test.py | 0 {sonic-utilities-tests => tests}/psu_test.py | 0 {sonic-utilities-tests => tests}/pytest.ini | 0 {sonic-utilities-tests => tests}/sfp_test.py | 0 .../show_breakout_test.py | 0 39 files changed, 19 insertions(+), 19 deletions(-) rename {sonic-utilities-tests => tests}/__init__.py (100%) rename {sonic-utilities-tests => tests}/acl_input/acl1.json (100%) rename {sonic-utilities-tests => tests}/acl_input/acl2.json (100%) rename {sonic-utilities-tests => tests}/acl_input/empty_acl.json (100%) rename {sonic-utilities-tests => tests}/acl_loader_test.py (100%) rename {sonic-utilities-tests => tests}/aclshow_test.py (100%) rename {sonic-utilities-tests => tests}/config_mgmt_test.py (100%) rename {sonic-utilities-tests => tests}/drops_group_test.py (100%) rename {sonic-utilities-tests => tests}/filter_fdb_entries_test.py (100%) rename {sonic-utilities-tests => tests}/filter_fdb_input/__init__.py (100%) rename {sonic-utilities-tests => tests}/filter_fdb_input/arp.json (100%) rename {sonic-utilities-tests => tests}/filter_fdb_input/config_db.json (100%) rename {sonic-utilities-tests => tests}/filter_fdb_input/expected_fdb.json (100%) rename {sonic-utilities-tests => tests}/filter_fdb_input/fdb.json (100%) rename {sonic-utilities-tests => tests}/filter_fdb_input/test_vectors.py (92%) rename {sonic-utilities-tests => tests}/gearbox_test.py (96%) rename {sonic-utilities-tests => tests}/intfstat_test.py (100%) rename {sonic-utilities-tests => tests}/intfutil_test.py (100%) rename {sonic-utilities-tests => tests}/mock_tables/__init__.py (100%) rename {sonic-utilities-tests => tests}/mock_tables/appl_db.json (100%) rename {sonic-utilities-tests => tests}/mock_tables/asic_db.json (100%) rename {sonic-utilities-tests => tests}/mock_tables/config_db.json (100%) rename {sonic-utilities-tests => tests}/mock_tables/counters_db.json (100%) rename {sonic-utilities-tests => tests}/mock_tables/dbconnector.py (100%) rename {sonic-utilities-tests => tests}/mock_tables/state_db.json (100%) rename {sonic-utilities-tests => tests}/port2alias_test.py (100%) rename {sonic-utilities-tests => tests}/psu_test.py (100%) rename {sonic-utilities-tests => tests}/pytest.ini (100%) rename {sonic-utilities-tests => tests}/sfp_test.py (100%) rename {sonic-utilities-tests => tests}/show_breakout_test.py (100%) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 15ea2f31b5..8dbc11e0a0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,7 +9,7 @@ If this is a bug fix, make sure your description includes "closes #xxxx", issue when the PR is merged. If you are adding/modifying/removing any command or utility script, please also -make sure to add/modify/remove any unit tests from the sonic-utilities-tests +make sure to add/modify/remove any unit tests from the tests directory as appropriate. If you are modifying or removing an existing 'show', 'config' or 'sonic-clear' diff --git a/scripts/dropconfig b/scripts/dropconfig index c9bbd34418..292214a31f 100755 --- a/scripts/dropconfig +++ b/scripts/dropconfig @@ -21,7 +21,7 @@ from tabulate import tabulate try: if os.environ["UTILITIES_UNIT_TESTING"] == "1": modules_path = os.path.join(os.path.dirname(__file__), "..") - test_path = os.path.join(modules_path, "sonic-utilities-tests") + test_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector diff --git a/scripts/dropstat b/scripts/dropstat index 7baedd6fd5..808913805d 100755 --- a/scripts/dropstat +++ b/scripts/dropstat @@ -26,7 +26,7 @@ from natsort import natsorted try: if os.environ["UTILITIES_UNIT_TESTING"] == "1": modules_path = os.path.join(os.path.dirname(__file__), "..") - test_path = os.path.join(modules_path, "sonic-utilities-tests") + test_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector diff --git a/scripts/gearboxutil b/scripts/gearboxutil index b6b2818dee..c143ff744d 100755 --- a/scripts/gearboxutil +++ b/scripts/gearboxutil @@ -11,7 +11,7 @@ import os try: if os.environ["UTILITIES_UNIT_TESTING"] == "1": modules_path = os.path.join(os.path.dirname(__file__), "..") - tests_path = os.path.join(modules_path, "sonic-utilities-tests") + tests_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, tests_path) import mock_tables.dbconnector diff --git a/scripts/intfstat b/scripts/intfstat index 86e628f8e5..3591016b7e 100755 --- a/scripts/intfstat +++ b/scripts/intfstat @@ -19,7 +19,7 @@ import time try: if os.environ["UTILITIES_UNIT_TESTING"] == "1": modules_path = os.path.join(os.path.dirname(__file__), "..") - test_path = os.path.join(modules_path, "sonic-utilities-tests") + test_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector diff --git a/scripts/intfutil b/scripts/intfutil index c37a68e962..f35702aa0c 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -16,7 +16,7 @@ import os try: if os.environ["UTILITIES_UNIT_TESTING"] == "1": modules_path = os.path.join(os.path.dirname(__file__), "..") - tests_path = os.path.join(modules_path, "sonic-utilities-tests") + tests_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, tests_path) import mock_tables.dbconnector diff --git a/scripts/psushow b/scripts/psushow index 60d7123346..f61d8b405f 100755 --- a/scripts/psushow +++ b/scripts/psushow @@ -10,7 +10,7 @@ from tabulate import tabulate try: if os.environ["UTILITIES_UNIT_TESTING"] == "1": modules_path = os.path.join(os.path.dirname(__file__), "..") - test_path = os.path.join(modules_path, "sonic-utilities-tests") + test_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector diff --git a/scripts/sfpshow b/scripts/sfpshow index a4a5e78c02..875b64d8df 100755 --- a/scripts/sfpshow +++ b/scripts/sfpshow @@ -17,7 +17,7 @@ from tabulate import tabulate try: if os.environ["UTILITIES_UNIT_TESTING"] == "1": modules_path = os.path.join(os.path.dirname(__file__), "..") - test_path = os.path.join(modules_path, "sonic-utilities-tests") + test_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector diff --git a/setup.py b/setup.py index b593eedd48..bece679708 100644 --- a/setup.py +++ b/setup.py @@ -50,14 +50,14 @@ 'show', 'sonic_installer', 'sonic_installer.bootloader', - 'sonic-utilities-tests', + 'tests', 'undebug', 'utilities_common', 'watchdogutil', ], package_data={ 'show': ['aliases.ini'], - 'sonic-utilities-tests': ['acl_input/*', 'mock_tables/*.py', 'mock_tables/*.json', 'filter_fdb_input/*'] + 'tests': ['acl_input/*', 'mock_tables/*.py', 'mock_tables/*.json', 'filter_fdb_input/*'] }, scripts=[ 'scripts/aclshow', diff --git a/sonic-utilities-tests/__init__.py b/tests/__init__.py similarity index 100% rename from sonic-utilities-tests/__init__.py rename to tests/__init__.py diff --git a/sonic-utilities-tests/acl_input/acl1.json b/tests/acl_input/acl1.json similarity index 100% rename from sonic-utilities-tests/acl_input/acl1.json rename to tests/acl_input/acl1.json diff --git a/sonic-utilities-tests/acl_input/acl2.json b/tests/acl_input/acl2.json similarity index 100% rename from sonic-utilities-tests/acl_input/acl2.json rename to tests/acl_input/acl2.json diff --git a/sonic-utilities-tests/acl_input/empty_acl.json b/tests/acl_input/empty_acl.json similarity index 100% rename from sonic-utilities-tests/acl_input/empty_acl.json rename to tests/acl_input/empty_acl.json diff --git a/sonic-utilities-tests/acl_loader_test.py b/tests/acl_loader_test.py similarity index 100% rename from sonic-utilities-tests/acl_loader_test.py rename to tests/acl_loader_test.py diff --git a/sonic-utilities-tests/aclshow_test.py b/tests/aclshow_test.py similarity index 100% rename from sonic-utilities-tests/aclshow_test.py rename to tests/aclshow_test.py diff --git a/sonic-utilities-tests/config_mgmt_test.py b/tests/config_mgmt_test.py similarity index 100% rename from sonic-utilities-tests/config_mgmt_test.py rename to tests/config_mgmt_test.py diff --git a/sonic-utilities-tests/drops_group_test.py b/tests/drops_group_test.py similarity index 100% rename from sonic-utilities-tests/drops_group_test.py rename to tests/drops_group_test.py diff --git a/sonic-utilities-tests/filter_fdb_entries_test.py b/tests/filter_fdb_entries_test.py similarity index 100% rename from sonic-utilities-tests/filter_fdb_entries_test.py rename to tests/filter_fdb_entries_test.py diff --git a/sonic-utilities-tests/filter_fdb_input/__init__.py b/tests/filter_fdb_input/__init__.py similarity index 100% rename from sonic-utilities-tests/filter_fdb_input/__init__.py rename to tests/filter_fdb_input/__init__.py diff --git a/sonic-utilities-tests/filter_fdb_input/arp.json b/tests/filter_fdb_input/arp.json similarity index 100% rename from sonic-utilities-tests/filter_fdb_input/arp.json rename to tests/filter_fdb_input/arp.json diff --git a/sonic-utilities-tests/filter_fdb_input/config_db.json b/tests/filter_fdb_input/config_db.json similarity index 100% rename from sonic-utilities-tests/filter_fdb_input/config_db.json rename to tests/filter_fdb_input/config_db.json diff --git a/sonic-utilities-tests/filter_fdb_input/expected_fdb.json b/tests/filter_fdb_input/expected_fdb.json similarity index 100% rename from sonic-utilities-tests/filter_fdb_input/expected_fdb.json rename to tests/filter_fdb_input/expected_fdb.json diff --git a/sonic-utilities-tests/filter_fdb_input/fdb.json b/tests/filter_fdb_input/fdb.json similarity index 100% rename from sonic-utilities-tests/filter_fdb_input/fdb.json rename to tests/filter_fdb_input/fdb.json diff --git a/sonic-utilities-tests/filter_fdb_input/test_vectors.py b/tests/filter_fdb_input/test_vectors.py similarity index 92% rename from sonic-utilities-tests/filter_fdb_input/test_vectors.py rename to tests/filter_fdb_input/test_vectors.py index 2321da47af..01d92240e0 100644 --- a/sonic-utilities-tests/filter_fdb_input/test_vectors.py +++ b/tests/filter_fdb_input/test_vectors.py @@ -243,14 +243,14 @@ ], }, { - "arp": "sonic-utilities-tests/filter_fdb_input/arp.json", - "fdb": "sonic-utilities-tests/filter_fdb_input/fdb.json", - "config_db": "sonic-utilities-tests/filter_fdb_input/config_db.json", - "expected_fdb": "sonic-utilities-tests/filter_fdb_input/expected_fdb.json" + "arp": "tests/filter_fdb_input/arp.json", + "fdb": "tests/filter_fdb_input/fdb.json", + "config_db": "tests/filter_fdb_input/config_db.json", + "expected_fdb": "tests/filter_fdb_input/expected_fdb.json" }, { - "arp": "sonic-utilities-tests/filter_fdb_input/arp.json", - "fdb": "sonic-utilities-tests/filter_fdb_input/fdb.json", + "arp": "tests/filter_fdb_input/arp.json", + "fdb": "tests/filter_fdb_input/fdb.json", "config_db": { "VLAN": { "Vlan1": {} @@ -263,8 +263,8 @@ ], }, { - "arp": "sonic-utilities-tests/filter_fdb_input/arp.json", - "fdb": "sonic-utilities-tests/filter_fdb_input/fdb.json", + "arp": "tests/filter_fdb_input/arp.json", + "fdb": "tests/filter_fdb_input/fdb.json", "config_db": { }, "expected_fdb": [ diff --git a/sonic-utilities-tests/gearbox_test.py b/tests/gearbox_test.py similarity index 96% rename from sonic-utilities-tests/gearbox_test.py rename to tests/gearbox_test.py index 2d0cef041e..ba7975fed3 100644 --- a/sonic-utilities-tests/gearbox_test.py +++ b/tests/gearbox_test.py @@ -9,7 +9,7 @@ sys.path.insert(0, test_path) sys.path.insert(0, modules_path) -import mock_tables.dbconnector # required by sonic-utilities-tests +import mock_tables.dbconnector # required by tests import show.main as show diff --git a/sonic-utilities-tests/intfstat_test.py b/tests/intfstat_test.py similarity index 100% rename from sonic-utilities-tests/intfstat_test.py rename to tests/intfstat_test.py diff --git a/sonic-utilities-tests/intfutil_test.py b/tests/intfutil_test.py similarity index 100% rename from sonic-utilities-tests/intfutil_test.py rename to tests/intfutil_test.py diff --git a/sonic-utilities-tests/mock_tables/__init__.py b/tests/mock_tables/__init__.py similarity index 100% rename from sonic-utilities-tests/mock_tables/__init__.py rename to tests/mock_tables/__init__.py diff --git a/sonic-utilities-tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json similarity index 100% rename from sonic-utilities-tests/mock_tables/appl_db.json rename to tests/mock_tables/appl_db.json diff --git a/sonic-utilities-tests/mock_tables/asic_db.json b/tests/mock_tables/asic_db.json similarity index 100% rename from sonic-utilities-tests/mock_tables/asic_db.json rename to tests/mock_tables/asic_db.json diff --git a/sonic-utilities-tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json similarity index 100% rename from sonic-utilities-tests/mock_tables/config_db.json rename to tests/mock_tables/config_db.json diff --git a/sonic-utilities-tests/mock_tables/counters_db.json b/tests/mock_tables/counters_db.json similarity index 100% rename from sonic-utilities-tests/mock_tables/counters_db.json rename to tests/mock_tables/counters_db.json diff --git a/sonic-utilities-tests/mock_tables/dbconnector.py b/tests/mock_tables/dbconnector.py similarity index 100% rename from sonic-utilities-tests/mock_tables/dbconnector.py rename to tests/mock_tables/dbconnector.py diff --git a/sonic-utilities-tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json similarity index 100% rename from sonic-utilities-tests/mock_tables/state_db.json rename to tests/mock_tables/state_db.json diff --git a/sonic-utilities-tests/port2alias_test.py b/tests/port2alias_test.py similarity index 100% rename from sonic-utilities-tests/port2alias_test.py rename to tests/port2alias_test.py diff --git a/sonic-utilities-tests/psu_test.py b/tests/psu_test.py similarity index 100% rename from sonic-utilities-tests/psu_test.py rename to tests/psu_test.py diff --git a/sonic-utilities-tests/pytest.ini b/tests/pytest.ini similarity index 100% rename from sonic-utilities-tests/pytest.ini rename to tests/pytest.ini diff --git a/sonic-utilities-tests/sfp_test.py b/tests/sfp_test.py similarity index 100% rename from sonic-utilities-tests/sfp_test.py rename to tests/sfp_test.py diff --git a/sonic-utilities-tests/show_breakout_test.py b/tests/show_breakout_test.py similarity index 100% rename from sonic-utilities-tests/show_breakout_test.py rename to tests/show_breakout_test.py From dfaae69678da80a0e5bc7b8c2d9b846ab80b6b19 Mon Sep 17 00:00:00 2001 From: Arun Saravanan Balachandran <52521751+ArunSaravananBalachandran@users.noreply.github.com> Date: Mon, 3 Aug 2020 09:50:21 +0000 Subject: [PATCH 14/48] [lldpshow]: Fix input device is not a TTY error (#1016) Fix Azure/sonic-buildimage#5019 Fix "the input device is not a TTY" error reported by 'show lldp' commands when executed via pytest scripts in sonic-mgmt. --- scripts/lldpshow | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lldpshow b/scripts/lldpshow index 42d5fa39ac..3d53df13db 100755 --- a/scripts/lldpshow +++ b/scripts/lldpshow @@ -74,7 +74,7 @@ class Lldpshow(object): lldp_interface_list = lldp_port if lldp_port is not None else self.lldp_interface[lldp_instace_num] # In detail mode we will pass interface list (only front ports) and get O/P as plain text # and in table format we will get xml output - lldp_cmd = 'sudo docker exec -it lldp{} lldpctl '.format(self.lldp_instance[lldp_instace_num]) + ('-f xml' if not lldp_detail_info else lldp_interface_list) + lldp_cmd = 'sudo docker exec -i lldp{} lldpctl '.format(self.lldp_instance[lldp_instace_num]) + ('-f xml' if not lldp_detail_info else lldp_interface_list) p = subprocess.Popen(lldp_cmd, stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() ## Wait for end of command. Get return returncode ## From c9d3550d9372541e3a58688b8e01a7ffe92071e3 Mon Sep 17 00:00:00 2001 From: lguohan Date: Mon, 3 Aug 2020 08:23:23 -0700 Subject: [PATCH 15/48] [tests]: fix drops_group_test failure on second run (#1023) a few tests in drops_group_test fails on second run. The reason is that /tmp/dropstat is not cleaned, the first run leave some state in the folder which cause the subsequent run to fail. The fix is the always clean up the folder. Signed-off-by: Guohan Lu --- tests/drops_group_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/drops_group_test.py b/tests/drops_group_test.py index df3c6c4f93..0076e7b477 100644 --- a/tests/drops_group_test.py +++ b/tests/drops_group_test.py @@ -1,5 +1,7 @@ -import sys import os +import sys + +import shutil from click.testing import CliRunner test_path = os.path.dirname(os.path.abspath(__file__)) @@ -8,7 +10,6 @@ sys.path.insert(0, test_path) sys.path.insert(0, modules_path) -import mock_tables.dbconnector import show.main as show import clear.main as clear @@ -75,10 +76,14 @@ sonic_drops_test 0 """ +dropstat_path = "/tmp/dropstat" + class TestDropCounters(object): @classmethod def setup_class(cls): print("SETUP") + if os.path.exists(dropstat_path): + shutil.rmtree(dropstat_path) os.environ["PATH"] += os.pathsep + scripts_path os.environ["UTILITIES_UNIT_TESTING"] = "1" From 9700e45322a4a58076e5cef604ed15803ffb9314 Mon Sep 17 00:00:00 2001 From: lguohan Date: Mon, 3 Aug 2020 11:47:14 -0700 Subject: [PATCH 16/48] [show/config]: combine feature and container feature cli (#1015) merge container feature cli into feature cli config command: ``` admin@vlab-01:~$ sudo config feature Usage: config feature [OPTIONS] COMMAND [ARGS]... Modify configuration of features Options: -?, -h, --help Show this message and exit. Commands: autorestart Configure autorestart status for a feature state Configure status of feature ``` show command: ``` admin@vlab-01:~$ show feature Usage: show feature [OPTIONS] COMMAND [ARGS]... Show feature status Options: -?, -h, --help Show this message and exit. Commands: autorestart Show auto-restart status for a feature status Show feature status ``` output: ``` admin@vlab-01:~$ show feature status Feature Status AutoRestart ---------- -------- ------------- lldp enabled enabled pmon enabled enabled sflow disabled enabled database enabled disabled restapi disabled enabled telemetry enabled enabled snmp enabled enabled bgp enabled enabled radv enabled enabled dhcp_relay enabled enabled nat enabled enabled teamd enabled enabled syncd enabled enabled swss enabled enabled admin@vlab-01:~$ show feature autorestart Feature AutoRestart ---------- ------------- lldp enabled pmon enabled sflow enabled database disabled restapi enabled telemetry enabled snmp enabled bgp enabled radv enabled dhcp_relay enabled nat enabled teamd enabled syncd enabled swss enabled ``` Signed-off-by: Guohan Lu --- config/main.py | 144 +++++++++++----------- show/main.py | 94 ++++++++------- sonic-utilities-tests/feature_test.py | 166 ++++++++++++++++++++++++++ tests/mock_tables/config_db.json | 72 ++++++++++- 4 files changed, 356 insertions(+), 120 deletions(-) create mode 100644 sonic-utilities-tests/feature_test.py diff --git a/config/main.py b/config/main.py index aad6b3227a..128152e6cd 100755 --- a/config/main.py +++ b/config/main.py @@ -48,6 +48,10 @@ CFG_LOOPBACK_NAME_TOTAL_LEN_MAX = 11 CFG_LOOPBACK_ID_MAX_VAL = 999 CFG_LOOPBACK_NO="<0-999>" + +asic_type = None +config_db = None + # ========================== Syslog wrappers ========================== def log_debug(msg): @@ -112,17 +116,6 @@ def get_command(self, ctx, cmd_name): ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) - -# -# Load asic_type for further use -# - -try: - version_info = sonic_device_util.get_sonic_version_info() - asic_type = version_info['asic_type'] -except (KeyError, TypeError): - raise click.Abort() - # # Load breakout config file for Dynamic Port Breakout # @@ -552,7 +545,7 @@ def _is_neighbor_ipaddress(config_db, ipaddress): def _get_all_neighbor_ipaddresses(config_db, ignore_local_hosts=False): """Returns list of strings containing IP addresses of all BGP neighbors - if the flag ignore_local_hosts is set to True, additional check to see if + if the flag ignore_local_hosts is set to True, additional check to see if if the BGP neighbor AS number is same as local BGP AS number, if so ignore that neigbor. """ addrs = [] @@ -692,12 +685,12 @@ def _get_disabled_services_list(): log_warning("Feature is None") continue - status = feature_table[feature_name]['status'] - if not status: - log_warning("Status of feature '{}' is None".format(feature_name)) + state = feature_table[feature_name]['state'] + if not state: + log_warning("Enable state of feature '{}' is None".format(feature_name)) continue - if status == "disabled": + if state == "disabled": disabled_services_list.append(feature_name) else: log_warning("Unable to retreive FEATURE table") @@ -890,6 +883,19 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port, @click.group(cls=AbbreviationGroup, context_settings=CONTEXT_SETTINGS) def config(): """SONiC command line - 'config' command""" + # + # Load asic_type for further use + # + global asic_type + + try: + version_info = sonic_device_util.get_sonic_version_info() + asic_type = version_info['asic_type'] + except KeyError, TypeError: + raise click.Abort() + + if asic_type == 'mellanox': + platform.add_command(mlnx.mlnx) # Load the global config file database_global.json once. SonicDBConfig.load_sonic_global_db_config() @@ -899,6 +905,10 @@ def config(): SonicDBConfig.load_sonic_global_db_config() + global config_db + + config_db = ConfigDBConnector() + config_db.connect() config.add_command(aaa.aaa) config.add_command(aaa.tacacs) @@ -1007,7 +1017,7 @@ def load(filename, yes): # if any of the config files in linux host OR namespace is not present, return if not os.path.isfile(file): click.echo("The config_db file {} doesn't exist".format(file)) - return + return if namespace is None: command = "{} -j {} --write-to-db".format(SONIC_CFGGEN_PATH, file) @@ -1225,7 +1235,7 @@ def load_minigraph(no_service_restart): if os.path.isfile('/etc/sonic/acl.json'): run_command("acl-loader update full /etc/sonic/acl.json", display_cmd=True) - + # Write latest db version string into db db_migrator='/usr/bin/db_migrator.py' if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): @@ -1235,7 +1245,7 @@ def load_minigraph(no_service_restart): else: cfggen_namespace_option = " -n {}".format(namespace) run_command(db_migrator + ' -o set_version' + cfggen_namespace_option) - + # We first run "systemctl reset-failed" to remove the "failed" # status from all services before we attempt to restart them if not no_service_restart: @@ -1891,7 +1901,7 @@ def add_snmp_agent_address(ctx, agentip, port, vrf): #Construct SNMP_AGENT_ADDRESS_CONFIG table key in the format ip|| key = agentip+'|' if port: - key = key+port + key = key+port key = key+'|' if vrf: key = key+vrf @@ -1912,7 +1922,7 @@ def del_snmp_agent_address(ctx, agentip, port, vrf): key = agentip+'|' if port: - key = key+port + key = key+port key = key+'|' if vrf: key = key+vrf @@ -2174,7 +2184,7 @@ def all(verbose): config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) config_db.connect() bgp_neighbor_ip_list = _get_all_neighbor_ipaddresses(config_db, ignore_local_hosts) - for ipaddress in bgp_neighbor_ip_list: + for ipaddress in bgp_neighbor_ip_list: _change_bgp_session_status_by_addr(config_db, ipaddress, 'up', verbose) # 'neighbor' subcommand @@ -2463,7 +2473,7 @@ def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load sys.exit(0) def _get_all_mgmtinterface_keys(): - """Returns list of strings containing mgmt interface keys + """Returns list of strings containing mgmt interface keys """ config_db = ConfigDBConnector() config_db.connect() @@ -3191,9 +3201,9 @@ def priority(ctx, interface_name, priority, status): interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") - + run_command("pfc config priority {0} {1} {2}".format(status, interface_name, priority)) - + # # 'platform' group ('config platform ...') # @@ -3202,9 +3212,6 @@ def priority(ctx, interface_name, priority, status): def platform(): """Platform-related configuration tasks""" -if asic_type == 'mellanox': - platform.add_command(mlnx.mlnx) - # 'firmware' subgroup ("config platform firmware ...") @platform.group(cls=AbbreviationGroup) def firmware(): @@ -3292,10 +3299,10 @@ def naming_mode_alias(): def is_loopback_name_valid(loopback_name): """Loopback name validation """ - + if loopback_name[:CFG_LOOPBACK_PREFIX_LEN] != CFG_LOOPBACK_PREFIX : return False - if (loopback_name[CFG_LOOPBACK_PREFIX_LEN:].isdigit() is False or + if (loopback_name[CFG_LOOPBACK_PREFIX_LEN:].isdigit() is False or int(loopback_name[CFG_LOOPBACK_PREFIX_LEN:]) > CFG_LOOPBACK_ID_MAX_VAL) : return False if len(loopback_name) > CFG_LOOPBACK_NAME_TOTAL_LEN_MAX: @@ -3462,7 +3469,7 @@ def add_ntp_server(ctx, ntp_ip_address): if ntp_ip_address in ntp_servers: click.echo("NTP server {} is already configured".format(ntp_ip_address)) return - else: + else: db.set_entry('NTP_SERVER', ntp_ip_address, {'NULL': 'NULL'}) click.echo("NTP server {} added to configuration".format(ntp_ip_address)) try: @@ -3483,7 +3490,7 @@ def del_ntp_server(ctx, ntp_ip_address): if ntp_ip_address in ntp_servers: db.set_entry('NTP_SERVER', '{}'.format(ntp_ip_address), None) click.echo("NTP server {} removed from configuration".format(ntp_ip_address)) - else: + else: ctx.fail("NTP server {} is not configured.".format(ntp_ip_address)) try: click.echo("Restarting ntp-config service...") @@ -3770,58 +3777,47 @@ def delete(ctx): config_db.set_entry('SFLOW', 'global', sflow_tbl['global']) # -# 'feature' command ('config feature name state') -# -@config.command('feature') -@click.argument('name', metavar='', required=True) -@click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -def feature_status(name, state): - """ Configure status of feature""" - config_db = ConfigDBConnector() - config_db.connect() - status_data = config_db.get_entry('FEATURE', name) - - if not status_data: - click.echo(" Feature '{}' doesn't exist".format(name)) - return - - config_db.mod_entry('FEATURE', name, {'status': state}) - -# -# 'container' group ('config container ...') +# 'feature' group ('config feature ...') # -@config.group(cls=AbbreviationGroup, name='container', invoke_without_command=False) -def container(): - """Modify configuration of containers""" +@config.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) +def feature(): + """Modify configuration of features""" pass # -# 'feature' group ('config container feature ...') +# 'state' command ('config feature state ...') # -@container.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) -def feature(): - """Modify configuration of container features""" - pass +@feature.command('state', short_help="Enable/disable a feature") +@click.argument('name', metavar='', required=True) +@click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) +def feature_state(name, state): + """Enable/disable a feature""" + state_data = config_db.get_entry('FEATURE', name) + + if not state_data: + click.echo("Feature '{}' doesn't exist".format(name)) + sys.exit(1) + + config_db.mod_entry('FEATURE', name, {'state': state}) # -# 'autorestart' subcommand ('config container feature autorestart ...') +# 'autorestart' command ('config feature autorestart ...') # -@feature.command(name='autorestart', short_help="Configure the status of autorestart feature for specific container") -@click.argument('container_name', metavar='', required=True) -@click.argument('autorestart_status', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -def autorestart(container_name, autorestart_status): - config_db = ConfigDBConnector() - config_db.connect() - container_feature_table = config_db.get_table('CONTAINER_FEATURE') - if not container_feature_table: - click.echo("Unable to retrieve container feature table from Config DB.") - return +@feature.command(name='autorestart', short_help="Enable/disable autosrestart of a feature") +@click.argument('name', metavar='', required=True) +@click.argument('autorestart', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) +def feature_autorestart(name, autorestart): + """Enable/disable autorestart of a feature""" + feature_table = config_db.get_table('FEATURE') + if not feature_table: + click.echo("Unable to retrieve feature table from Config DB.") + sys.exit(1) - if not container_feature_table.has_key(container_name): - click.echo("Unable to retrieve features for container '{}'".format(container_name)) - return + if not feature_table.has_key(name): + click.echo("Unable to retrieve feature '{}'".format(name)) + sys.exit(1) - config_db.mod_entry('CONTAINER_FEATURE', container_name, {'auto_restart': autorestart_status}) + config_db.mod_entry('FEATURE', name, {'auto_restart': autorestart}) if __name__ == '__main__': config() diff --git a/show/main.py b/show/main.py index b670596466..26c5f280e9 100755 --- a/show/main.py +++ b/show/main.py @@ -31,6 +31,8 @@ VLAN_SUB_INTERFACE_SEPARATOR = '.' +config_db = None + try: # noinspection PyPep8Naming import ConfigParser as configparser @@ -557,7 +559,10 @@ def get_bgp_neighbor_ip_to_name(ip, static_neighbors, dynamic_neighbors): @click.group(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS) def cli(): """SONiC command line - 'show' command""" - pass + global config_db + + config_db = ConfigDBConnector() + config_db.connect() # # 'vrf' command ("show vrf") @@ -1179,8 +1184,8 @@ def priority(interface): cmd = 'pfc show priority' if interface is not None and get_interface_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) - - if interface is not None: + + if interface is not None: cmd += ' {0}'.format(interface) run_command(cmd) @@ -1192,8 +1197,8 @@ def asymmetric(interface): cmd = 'pfc show asymmetric' if interface is not None and get_interface_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) - - if interface is not None: + + if interface is not None: cmd += ' {0}'.format(interface) run_command(cmd) @@ -1727,7 +1732,7 @@ def protocol(verbose): from .bgp_quagga_v6 import bgp ipv6.add_command(bgp) elif routing_stack == "frr": - from .bgp_frr_v4 import bgp + from .bgp_frr_v4 import bgp ip.add_command(bgp) from .bgp_frr_v6 import bgp ipv6.add_command(bgp) @@ -2173,7 +2178,7 @@ def ntp(ctx, verbose): ntpstat_cmd = "ntpstat" ntpcmd = "ntpq -p -n" if is_mgmt_vrf_enabled(ctx) is True: - #ManagementVRF is enabled. Call ntpq using "ip vrf exec" or cgexec based on linux version + #ManagementVRF is enabled. Call ntpq using "ip vrf exec" or cgexec based on linux version os_info = os.uname() release = os_info[2].split('-') if parse_version(release[0]) > parse_version("4.9.0"): @@ -2971,7 +2976,7 @@ def pool(verbose): # Define GEARBOX commands only if GEARBOX is configured app_db = SonicV2Connector(host='127.0.0.1') -app_db.connect(app_db.APPL_DB) +app_db.connect(app_db.APPL_DB) if app_db.keys(app_db.APPL_DB, '_GEARBOX_TABLE:phy:*'): @cli.group(cls=AliasedGroup) @@ -3051,53 +3056,52 @@ def ztp(status, verbose): run_command(cmd, display_cmd=verbose) # -# show features -# -@cli.command('features') -def features(): - """Show status of optional features""" - config_db = ConfigDBConnector() - config_db.connect() - header = ['Feature', 'Status'] - body = [] - status_data = config_db.get_table('FEATURE') - for key in status_data.keys(): - body.append([key, status_data[key]['status']]) - click.echo(tabulate(body, header)) - -# -# 'container' group (show container ...) +# 'feature' group (show feature ...) # -@cli.group(name='container', invoke_without_command=False) -def container(): - """Show container""" +@cli.group(name='feature', invoke_without_command=False) +def feature(): + """Show feature status""" pass # -# 'feature' group (show container feature ...) +# 'state' subcommand (show feature status) # -@container.group(name='feature', invoke_without_command=False) -def feature(): - """Show container feature""" - pass +@feature.command('status', short_help="Show feature state") +@click.argument('feature_name', required=False) +def autorestart(feature_name): + header = ['Feature', 'State', 'AutoRestart'] + body = [] + feature_table = config_db.get_table('FEATURE') + if feature_name: + if feature_table and feature_table.has_key(feature_name): + body.append([feature_name, feature_table[feature_name]['state'], \ + feature_table[feature_name]['auto_restart']]) + else: + click.echo("Can not find feature {}".format(feature_name)) + sys.exit(1) + else: + for key in natsorted(feature_table.keys()): + body.append([key, feature_table[key]['state'], feature_table[key]['auto_restart']]) + click.echo(tabulate(body, header)) # -# 'autorestart' subcommand (show container feature autorestart) +# 'autorestart' subcommand (show feature autorestart) # -@feature.command('autorestart', short_help="Show whether the auto-restart feature for container(s) is enabled or disabled") -@click.argument('container_name', required=False) -def autorestart(container_name): - config_db = ConfigDBConnector() - config_db.connect() - header = ['Container Name', 'Status'] +@feature.command('autorestart', short_help="Show auto-restart state for a feature") +@click.argument('feature_name', required=False) +def autorestart(feature_name): + header = ['Feature', 'AutoRestart'] body = [] - container_feature_table = config_db.get_table('CONTAINER_FEATURE') - if container_name: - if container_feature_table and container_feature_table.has_key(container_name): - body.append([container_name, container_feature_table[container_name]['auto_restart']]) + feature_table = config_db.get_table('FEATURE') + if feature_name: + if feature_table and feature_table.has_key(feature_name): + body.append([feature_name, feature_table[feature_name]['auto_restart']]) + else: + click.echo("Can not find feature {}".format(feature_name)) + sys.exit(1) else: - for name in container_feature_table.keys(): - body.append([name, container_feature_table[name]['auto_restart']]) + for name in natsorted(feature_table.keys()): + body.append([name, feature_table[name]['auto_restart']]) click.echo(tabulate(body, header)) # diff --git a/sonic-utilities-tests/feature_test.py b/sonic-utilities-tests/feature_test.py new file mode 100644 index 0000000000..01aa387fe0 --- /dev/null +++ b/sonic-utilities-tests/feature_test.py @@ -0,0 +1,166 @@ +import os +import sys + +import mock +from click.testing import CliRunner +from swsssdk import ConfigDBConnector + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +import show.main as show +import config.main as config + +config.asic_type = mock.MagicMock(return_value = "broadcom") +config._get_device_type = mock.MagicMock(return_value = "ToRRouter") + +config_db = ConfigDBConnector() +config_db.connect() + +show.config_db = config_db +config.config_db = config_db + +show_feature_status_output="""\ +Feature State AutoRestart +---------- -------- ------------- +bgp enabled enabled +database enabled disabled +dhcp_relay enabled enabled +lldp enabled enabled +nat enabled enabled +pmon enabled enabled +radv enabled enabled +restapi disabled enabled +sflow disabled enabled +snmp enabled enabled +swss enabled enabled +syncd enabled enabled +teamd enabled enabled +telemetry enabled enabled +""" + +show_feature_bgp_status_output="""\ +Feature State AutoRestart +--------- ------- ------------- +bgp enabled enabled +""" + +show_feature_bgp_disabled_status_output="""\ +Feature State AutoRestart +--------- -------- ------------- +bgp disabled enabled +""" + +show_feature_autorestart_output="""\ +Feature AutoRestart +---------- ------------- +bgp enabled +database disabled +dhcp_relay enabled +lldp enabled +nat enabled +pmon enabled +radv enabled +restapi enabled +sflow enabled +snmp enabled +swss enabled +syncd enabled +teamd enabled +telemetry enabled +""" + +show_feature_bgp_autorestart_output="""\ +Feature AutoRestart +--------- ------------- +bgp enabled +""" + + +show_feature_bgp_disabled_autorestart_output="""\ +Feature AutoRestart +--------- ------------- +bgp disabled +""" + +class TestFeature(object): + @classmethod + def setup_class(cls): + print("SETUP") + + def test_show_feature_status(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"].commands["status"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_status_output + + def test_show_bgp_feature_status(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"].commands["status"], ["bgp"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_bgp_status_output + + def test_show_unknown_feature_status(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"].commands["status"], ["foo"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 1 + + def test_show_feature_autorestart(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_autorestart_output + + def test_show_bgp_autorestart_status(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], ["bgp"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_bgp_autorestart_output + + def test_show_unknown_autorestart_status(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], ["foo"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 1 + + def test_config_bgp_feature_state(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["feature"].commands["state"], ["bgp", "disabled"]) + print(result.exit_code) + print(result.output) + result = runner.invoke(show.cli.commands["feature"].commands["status"], ["bgp"]) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_bgp_disabled_status_output + + def test_config_bgp_autorestart(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["feature"].commands["autorestart"], ["bgp", "disabled"]) + print(result.exit_code) + print(result.output) + result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], ["bgp"]) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_bgp_disabled_autorestart_output + + def test_config_unknown_feature(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["feature"].commands['state'], ["foo", "enabled"]) + print(result.output) + assert result.exit_code == 1 + + @classmethod + def teardown_class(cls): + print("TEARDOWN") diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index ec278c3450..f60ee3ef12 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -150,5 +150,75 @@ "DEBUG_COUNTER_DROP_REASON|DEBUG_0|IP_HEADER_ERROR": {}, "DEBUG_COUNTER_DROP_REASON|DEBUG_1|ACL_ANY": {}, "DEBUG_COUNTER_DROP_REASON|DEBUG_2|IP_HEADER_ERROR": {}, - "DEBUG_COUNTER_DROP_REASON|DEBUG_2|NO_L3_HEADER": {} + "DEBUG_COUNTER_DROP_REASON|DEBUG_2|NO_L3_HEADER": {}, + "FEATURE|bgp": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|database": { + "state": "enabled", + "auto_restart": "disabled", + "high_mem_alert": "disabled" + }, + "FEATURE|dhcp_relay": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|lldp": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|nat": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|pmon": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|radv": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|restapi": { + "state": "disabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|sflow": { + "state": "disabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|snmp": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|swss": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|syncd": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|teamd": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + }, + "FEATURE|telemetry": { + "state": "enabled", + "auto_restart": "enabled", + "high_mem_alert": "disabled" + } } From 5d26391365218f3cf90fad843a062db499996546 Mon Sep 17 00:00:00 2001 From: lguohan Date: Tue, 4 Aug 2020 02:07:15 -0700 Subject: [PATCH 17/48] [tests]: move feature_test.py into tests folder (#1027) Signed-off-by: Guohan Lu --- {sonic-utilities-tests => tests}/feature_test.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {sonic-utilities-tests => tests}/feature_test.py (100%) diff --git a/sonic-utilities-tests/feature_test.py b/tests/feature_test.py similarity index 100% rename from sonic-utilities-tests/feature_test.py rename to tests/feature_test.py From 092ebd23461a3c1cdf67b7da3d9c00ea0e07cb9a Mon Sep 17 00:00:00 2001 From: lguohan Date: Tue, 4 Aug 2020 17:53:57 -0700 Subject: [PATCH 18/48] [config load]: do not stop/reset/reload service if it is disabled (#1028) also introduce fixtures to setup the cmd as well as asic context Signed-off-by: Guohan Lu --- config/main.py | 52 +++++++++++++------- tests/config_test.py | 72 ++++++++++++++++++++++++++++ tests/conftest.py | 81 ++++++++++++++++++++++++++++++++ tests/feature_test.py | 48 +++++++------------ tests/mock_tables/dbconnector.py | 2 + 5 files changed, 209 insertions(+), 46 deletions(-) create mode 100644 tests/config_test.py create mode 100644 tests/conftest.py diff --git a/config/main.py b/config/main.py index 128152e6cd..169ce46657 100755 --- a/config/main.py +++ b/config/main.py @@ -337,6 +337,24 @@ def run_command(command, display_cmd=False, ignore_error=False): if proc.returncode != 0 and not ignore_error: sys.exit(proc.returncode) +def _get_device_type(): + """ + Get device type + + TODO: move to sonic-py-common + """ + + command = "{} -m -v DEVICE_METADATA.localhost.type".format(SONIC_CFGGEN_PATH) + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + device_type, err = proc.communicate() + if err: + click.echo("Could not get the device type from minigraph, setting device type to Unknown") + device_type = 'Unknown' + else: + device_type = device_type.strip() + + return device_type + # Validate whether a given namespace name is valid in the device. def validate_namespace(namespace): if not sonic_device_util.is_multi_npu(): @@ -676,8 +694,6 @@ def _abort_if_false(ctx, param, value): def _get_disabled_services_list(): disabled_services_list = [] - config_db = ConfigDBConnector() - config_db.connect() feature_table = config_db.get_table('FEATURE') if feature_table is not None: for feature_name in feature_table.keys(): @@ -697,7 +713,6 @@ def _get_disabled_services_list(): return disabled_services_list - def _stop_services(): # This list is order-dependent. Please add services in the order they should be stopped # on Mellanox platform pmon is stopped by syncd @@ -715,6 +730,12 @@ def _stop_services(): if asic_type == 'mellanox' and 'pmon' in services_to_stop: services_to_stop.remove('pmon') + disabled_services = _get_disabled_services_list() + + for service in disabled_services: + if service in services_to_stop: + services_to_stop.remove(service) + execute_systemctl(services_to_stop, SYSTEMCTL_ACTION_STOP) @@ -741,6 +762,12 @@ def _reset_failed_services(): 'telemetry' ] + disabled_services = _get_disabled_services_list() + + for service in disabled_services: + if service in services_to_reset: + services_to_reset.remove(service) + execute_systemctl(services_to_reset, SYSTEMCTL_ACTION_RESET_FAILED) @@ -763,9 +790,9 @@ def _restart_services(): 'telemetry' ] - disable_services = _get_disabled_services_list() + disabled_services = _get_disabled_services_list() - for service in disable_services: + for service in disabled_services: if service in services_to_restart: services_to_restart.remove(service) @@ -1185,16 +1212,6 @@ def load_minigraph(no_service_restart): """Reconfigure based on minigraph.""" log_info("'load_minigraph' executing...") - # get the device type - command = "{} -m -v DEVICE_METADATA.localhost.type".format(SONIC_CFGGEN_PATH) - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - device_type, err = proc.communicate() - if err: - click.echo("Could not get the device type from minigraph, setting device type to Unknown") - device_type = 'Unknown' - else: - device_type = device_type.strip() - #Stop services before config push if not no_service_restart: log_info("'load_minigraph' stopping services...") @@ -1223,10 +1240,13 @@ def load_minigraph(no_service_restart): if os.path.isfile('/etc/sonic/init_cfg.json'): command = "{} -H -m -j /etc/sonic/init_cfg.json {} --write-to-db".format(SONIC_CFGGEN_PATH, cfggen_namespace_option) else: - command = "{} -H -m --write-to-db {} ".format(SONIC_CFGGEN_PATH,cfggen_namespace_option) + command = "{} -H -m --write-to-db {}".format(SONIC_CFGGEN_PATH, cfggen_namespace_option) run_command(command, display_cmd=True) client.set(config_db.INIT_INDICATOR, 1) + # get the device type + device_type = _get_device_type() + # These commands are not run for host on multi asic platform if num_npus == 1 or namespace is not DEFAULT_NAMESPACE: if device_type != 'MgmtToRRouter': diff --git a/tests/config_test.py b/tests/config_test.py new file mode 100644 index 0000000000..5e8a983486 --- /dev/null +++ b/tests/config_test.py @@ -0,0 +1,72 @@ +from click.testing import CliRunner + +load_minigraph_command_output="""\ +Executing stop of service telemetry... +Executing stop of service swss... +Executing stop of service lldp... +Executing stop of service pmon... +Executing stop of service bgp... +Executing stop of service hostcfgd... +Executing stop of service nat... +Running command: /usr/local/bin/sonic-cfggen -H -m --write-to-db +Running command: pfcwd start_default +Running command: config qos reload +Executing reset-failed of service bgp... +Executing reset-failed of service dhcp_relay... +Executing reset-failed of service hostcfgd... +Executing reset-failed of service hostname-config... +Executing reset-failed of service interfaces-config... +Executing reset-failed of service lldp... +Executing reset-failed of service nat... +Executing reset-failed of service ntp-config... +Executing reset-failed of service pmon... +Executing reset-failed of service radv... +Executing reset-failed of service rsyslog-config... +Executing reset-failed of service snmp... +Executing reset-failed of service swss... +Executing reset-failed of service syncd... +Executing reset-failed of service teamd... +Executing reset-failed of service telemetry... +Executing restart of service hostname-config... +Executing restart of service interfaces-config... +Executing restart of service ntp-config... +Executing restart of service rsyslog-config... +Executing restart of service swss... +Executing restart of service bgp... +Executing restart of service pmon... +Executing restart of service lldp... +Executing restart of service hostcfgd... +Executing restart of service nat... +Executing restart of service telemetry... +Please note setting loaded from minigraph will be lost after system reboot. To preserve setting, run `config save`. +""" + +class TestLoadMinigraph(object): + @classmethod + def setup_class(cls): + print("SETUP") + + def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic): + (config, show) = get_cmd_module + runner = CliRunner() + result = runner.invoke(config.config.commands["load_minigraph"], ["-y"]) + print result.exit_code + print result.output + assert result.exit_code == 0 + assert "\n".join([ l.rstrip() for l in result.output.split('\n')]) == load_minigraph_command_output + + def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_single_broacom_asic): + (config, show) = get_cmd_module + runner = CliRunner() + runner.invoke(config.config.commands["feature"].commands["state"], ["telemetry", "disabled"]) + result = runner.invoke(show.cli.commands["feature"].commands["status"], ["telemetry"]) + print result.output + result = runner.invoke(config.config.commands["load_minigraph"], ["-y"]) + print result.exit_code + print result.output + assert result.exit_code == 0 + assert "telemetry" not in result.output + + @classmethod + def teardown_class(cls): + print("TEARDOWN") diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..44260c25e1 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,81 @@ +import os +import sys + +import mock +import click +import pytest + +import mock_tables.dbconnector + +import sonic_device_util +from swsssdk import ConfigDBConnector + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +generated_services_list = [ + 'ntp-config.service', + 'warmboot-finalizer.service', + 'watchdog-control.service', + 'rsyslog-config.service', + 'interfaces-config.service', + 'hostcfgd.service', + 'hostname-config.service', + 'topology.service', + 'updategraph.service', + 'config-setup.service', + 'caclmgrd.service', + 'procdockerstatsd.service', + 'pcie-check.service', + 'process-reboot-cause.service', + 'dhcp_relay.service', + 'snmp.service', + 'sflow.service', + 'bgp.service', + 'telemetry.service', + 'swss.service', + 'database.service', + 'database.service', + 'lldp.service', + 'lldp.service', + 'pmon.service', + 'radv.service', + 'mgmt-framework.service', + 'nat.service', + 'teamd.service', + 'syncd.service', + 'snmp.timer', + 'telemetry.timer'] + + +def _dummy_run_command(command, display_cmd=False, return_cmd=False): + if display_cmd == True: + click.echo(click.style("Running command: ", fg='cyan') + click.style(command, fg='green')) + +@pytest.fixture +def get_cmd_module(): + import config.main as config + import show.main as show + + config_db = ConfigDBConnector() + config_db.connect() + + config.config_db = config_db + show.config_db = config_db + + config.run_command = _dummy_run_command + + return (config, show) + +@pytest.fixture +def setup_single_broacom_asic(): + import config.main as config + import show.main as show + + sonic_device_util.get_num_npus = mock.MagicMock(return_value = 1) + config._get_sonic_generated_services = \ + mock.MagicMock(return_value = (generated_services_list, [])) + + config.asic_type = mock.MagicMock(return_value = "broadcom") + config._get_device_type = mock.MagicMock(return_value = "ToRRouter") diff --git a/tests/feature_test.py b/tests/feature_test.py index 01aa387fe0..f6c748acf7 100644 --- a/tests/feature_test.py +++ b/tests/feature_test.py @@ -1,25 +1,4 @@ -import os -import sys - -import mock from click.testing import CliRunner -from swsssdk import ConfigDBConnector - -test_path = os.path.dirname(os.path.abspath(__file__)) -modules_path = os.path.dirname(test_path) -sys.path.insert(0, modules_path) - -import show.main as show -import config.main as config - -config.asic_type = mock.MagicMock(return_value = "broadcom") -config._get_device_type = mock.MagicMock(return_value = "ToRRouter") - -config_db = ConfigDBConnector() -config_db.connect() - -show.config_db = config_db -config.config_db = config_db show_feature_status_output="""\ Feature State AutoRestart @@ -89,7 +68,8 @@ class TestFeature(object): def setup_class(cls): print("SETUP") - def test_show_feature_status(self): + def test_show_feature_status(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(show.cli.commands["feature"].commands["status"], []) print(result.exit_code) @@ -97,7 +77,8 @@ def test_show_feature_status(self): assert result.exit_code == 0 assert result.output == show_feature_status_output - def test_show_bgp_feature_status(self): + def test_show_bgp_feature_status(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(show.cli.commands["feature"].commands["status"], ["bgp"]) print(result.exit_code) @@ -105,14 +86,16 @@ def test_show_bgp_feature_status(self): assert result.exit_code == 0 assert result.output == show_feature_bgp_status_output - def test_show_unknown_feature_status(self): + def test_show_unknown_feature_status(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(show.cli.commands["feature"].commands["status"], ["foo"]) print(result.exit_code) print(result.output) assert result.exit_code == 1 - def test_show_feature_autorestart(self): + def test_show_feature_autorestart(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], []) print(result.exit_code) @@ -120,7 +103,8 @@ def test_show_feature_autorestart(self): assert result.exit_code == 0 assert result.output == show_feature_autorestart_output - def test_show_bgp_autorestart_status(self): + def test_show_bgp_autorestart_status(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], ["bgp"]) print(result.exit_code) @@ -128,14 +112,16 @@ def test_show_bgp_autorestart_status(self): assert result.exit_code == 0 assert result.output == show_feature_bgp_autorestart_output - def test_show_unknown_autorestart_status(self): + def test_show_unknown_autorestart_status(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], ["foo"]) print(result.exit_code) print(result.output) assert result.exit_code == 1 - def test_config_bgp_feature_state(self): + def test_config_bgp_feature_state(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(config.config.commands["feature"].commands["state"], ["bgp", "disabled"]) print(result.exit_code) @@ -145,7 +131,8 @@ def test_config_bgp_feature_state(self): assert result.exit_code == 0 assert result.output == show_feature_bgp_disabled_status_output - def test_config_bgp_autorestart(self): + def test_config_bgp_autorestart(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(config.config.commands["feature"].commands["autorestart"], ["bgp", "disabled"]) print(result.exit_code) @@ -155,7 +142,8 @@ def test_config_bgp_autorestart(self): assert result.exit_code == 0 assert result.output == show_feature_bgp_disabled_autorestart_output - def test_config_unknown_feature(self): + def test_config_unknown_feature(self, get_cmd_module): + (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(config.config.commands["feature"].commands['state'], ["foo", "enabled"]) print(result.output) diff --git a/tests/mock_tables/dbconnector.py b/tests/mock_tables/dbconnector.py index 5a67337e6d..dc1a64bc87 100644 --- a/tests/mock_tables/dbconnector.py +++ b/tests/mock_tables/dbconnector.py @@ -32,6 +32,8 @@ def listen(self): def punsubscribe(self, *args, **kwargs): pass + def clear(self): + pass INPUT_DIR = os.path.dirname(os.path.abspath(__file__)) From d1cf75ffeebde0f753932c661c550b9e82cc9a7a Mon Sep 17 00:00:00 2001 From: Tamer Ahmed Date: Tue, 4 Aug 2020 20:31:08 -0700 Subject: [PATCH 19/48] [sonic-installer] Create Envvars File for Incoming Image (#1011) Create environment files for SONiC immutable attribute. The file will reside in the incoming image dir under sonic-config dir. Incoming image will then move the file to /etc/sonic. The file will be used to avoid calls into cfggen for those attributes. signed-off-by: Tamer Ahmed --- setup.py | 1 + sonic_installer/common.py | 14 +++++ sonic_installer/exception.py | 8 +++ sonic_installer/main.py | 53 ++++++++++++++++++- .../templates/sonic-environment.j2 | 5 ++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 sonic_installer/exception.py create mode 100644 sonic_installer/templates/sonic-environment.j2 diff --git a/setup.py b/setup.py index bece679708..7da80ef286 100644 --- a/setup.py +++ b/setup.py @@ -112,6 +112,7 @@ ], data_files=[ ('/etc/bash_completion.d', glob.glob('data/etc/bash_completion.d/*')), + ('/usr/share/sonic/templates', ['sonic_installer/templates/sonic-environment.j2']), ], entry_points={ 'console_scripts': [ diff --git a/sonic_installer/common.py b/sonic_installer/common.py index e9bfeac04f..a9df312a85 100644 --- a/sonic_installer/common.py +++ b/sonic_installer/common.py @@ -8,6 +8,8 @@ import click +from .exception import SonicRuntimeException + HOST_PATH = '/host' IMAGE_PREFIX = 'SONiC-OS-' IMAGE_DIR_PREFIX = 'image-' @@ -23,3 +25,15 @@ def run_command(command): if proc.returncode != 0: sys.exit(proc.returncode) + +# Run bash command and return output, raise if it fails +def run_command_or_raise(argv): + click.echo(click.style("Command: ", fg='cyan') + click.style(' '.join(argv), fg='green')) + + proc = subprocess.Popen(argv, stdout=subprocess.PIPE) + out, _ = proc.communicate() + + if proc.returncode != 0: + raise SonicRuntimeException("Failed to run command '{0}'".format(argv)) + + return out.rstrip("\n") diff --git a/sonic_installer/exception.py b/sonic_installer/exception.py new file mode 100644 index 0000000000..b0806269f0 --- /dev/null +++ b/sonic_installer/exception.py @@ -0,0 +1,8 @@ +""" +Module sonic_installer exceptions +""" + +class SonicRuntimeException(Exception): + """SONiC Runtime Excpetion class used to report SONiC related errors + """ + pass diff --git a/sonic_installer/main.py b/sonic_installer/main.py index 8f016037f7..cc8db16616 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -16,7 +16,8 @@ from swsssdk import SonicV2Connector from .bootloader import get_bootloader -from .common import run_command +from .common import run_command, run_command_or_raise +from .exception import SonicRuntimeException # Global Config object @@ -208,6 +209,54 @@ def print_deprecation_warning(deprecated_cmd_or_subcmd, new_cmd_or_subcmd): fg="red", err=True) click.secho("Please use '{}' instead".format(new_cmd_or_subcmd), fg="red", err=True) +def update_sonic_environment(click, binary_image_version): + """Prepare sonic environment variable using incoming image template file. If incoming image template does not exist + use current image template file. + """ + def mount_next_image_fs(squashfs_path, mount_point): + run_command_or_raise(["mkdir", "-p", mount_point]) + run_command_or_raise(["mount", "-t", "squashfs", squashfs_path, mount_point]) + + def umount_next_image_fs(mount_point): + run_command_or_raise(["umount", "-rf", mount_point]) + run_command_or_raise(["rm", "-rf", mount_point]) + + SONIC_ENV_TEMPLATE_FILE = os.path.join("usr", "share", "sonic", "templates", "sonic-environment.j2") + SONIC_VERSION_YML_FILE = os.path.join("etc", "sonic", "sonic_version.yml") + + sonic_version = re.sub("SONiC-OS-", '', binary_image_version) + new_image_dir = os.path.join('/', "host", "image-{0}".format(sonic_version)) + new_image_squashfs_path = os.path.join(new_image_dir, "fs.squashfs") + new_image_mount = os.path.join('/', "tmp", "image-{0}-fs".format(sonic_version)) + env_dir = os.path.join(new_image_dir, "sonic-config") + env_file = os.path.join(env_dir, "sonic-environment") + + try: + mount_next_image_fs(new_image_squashfs_path, new_image_mount) + + next_sonic_env_template_file = os.path.join(new_image_mount, SONIC_ENV_TEMPLATE_FILE) + next_sonic_version_yml_file = os.path.join(new_image_mount, SONIC_VERSION_YML_FILE) + + sonic_env = run_command_or_raise([ + "sonic-cfggen", + "-d", + "-y", + next_sonic_version_yml_file, + "-t", + next_sonic_env_template_file, + ]) + os.mkdir(env_dir, 0o755) + with open(env_file, "w+") as ef: + print >>ef, sonic_env + os.chmod(env_file, 0o644) + except SonicRuntimeException as ex: + click.secho("Warning: SONiC environment variables are not supported for this image: {0}".format(str(ex)), + fg="red", err=True) + if os.path.exists(env_file): + os.remove(env_file) + os.rmdir(env_dir) + finally: + umount_next_image_fs(new_image_mount) # Main entrypoint @click.group(cls=AliasedGroup) @@ -274,6 +323,8 @@ def install(url, force, skip_migration=False): else: run_command('config-setup backup') + update_sonic_environment(click, binary_image_version) + # Finally, sync filesystem run_command("sync;sync;sync") run_command("sleep 3") # wait 3 seconds after sync diff --git a/sonic_installer/templates/sonic-environment.j2 b/sonic_installer/templates/sonic-environment.j2 new file mode 100644 index 0000000000..220de2dd19 --- /dev/null +++ b/sonic_installer/templates/sonic-environment.j2 @@ -0,0 +1,5 @@ +SONIC_VERSION={{ build_version }} +PLATFORM={{ DEVICE_METADATA.localhost.platform }} +HWSKU={{ DEVICE_METADATA.localhost.hwsku }} +DEVICE_TYPE={{ DEVICE_METADATA.localhost.type }} +ASIC_TYPE={{ asic_type }} From 8e64106f179d98e97a6e8e9974802b0037c3a0c2 Mon Sep 17 00:00:00 2001 From: Samuel Angebault Date: Tue, 4 Aug 2020 23:51:26 -0700 Subject: [PATCH 20/48] Add secure fast/warm-reboot support for Aboot (#994) Instead of having multiple implementation of preparing a SWI image for secureboot, fast-reboot now reuses boot0. SWI images booting in regular mode will keep using the old behavior. --- scripts/fast-reboot | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/scripts/fast-reboot b/scripts/fast-reboot index e18af200c3..27c22f2497 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -271,14 +271,23 @@ function teardown_control_plane_assistant() fi } +function is_secureboot() { + grep -Eq 'secure_boot_enable=[1y]' /proc/cmdline +} + function setup_reboot_variables() { # Kernel and initrd image NEXT_SONIC_IMAGE=$(sonic-installer list | grep "Next: " | cut -d ' ' -f 2) IMAGE_PATH="/host/image-${NEXT_SONIC_IMAGE#SONiC-OS-}" if grep -q aboot_platform= /host/machine.conf; then - KERNEL_IMAGE="$(ls $IMAGE_PATH/boot/vmlinuz-*)" - BOOT_OPTIONS="$(cat "$IMAGE_PATH/kernel-cmdline" | tr '\n' ' ') SONIC_BOOT_TYPE=${BOOT_TYPE_ARG}" + if is_secureboot; then + KERNEL_IMAGE="" + BOOT_OPTIONS="SONIC_BOOT_TYPE=${BOOT_TYPE_ARG} secure_boot_enable=1" + else + KERNEL_IMAGE="$(ls $IMAGE_PATH/boot/vmlinuz-*)" + BOOT_OPTIONS="$(cat "$IMAGE_PATH/kernel-cmdline" | tr '\n' ' ') SONIC_BOOT_TYPE=${BOOT_TYPE_ARG}" + fi elif grep -q onie_platform= /host/machine.conf; then KERNEL_OPTIONS=$(cat /host/grub/grub.cfg | sed "/$NEXT_SONIC_IMAGE'/,/}/"'!'"g" | grep linux) KERNEL_IMAGE="/host$(echo $KERNEL_OPTIONS | cut -d ' ' -f 2)" @@ -332,6 +341,18 @@ function reboot_pre_check() fi } +function load_aboot_secureboot_kernel() { + local next_image="$IMAGE_PATH/sonic.swi" + echo "Loading next image from $next_image" + unzip -qp "$next_image" boot0 | \ + swipath=$next_image kexec=true loadonly=true ENV_EXTRA_CMDLINE="$BOOT_OPTIONS" bash - +} + +function load_kernel() { + # Load kernel into the memory + /sbin/kexec -l "$KERNEL_IMAGE" --initrd="$INITRD" --append="$BOOT_OPTIONS" +} + function unload_kernel() { # Unload the previously loaded kernel if any loaded @@ -412,8 +433,12 @@ if [[ "$sonic_asic_type" == "mellanox" ]]; then fi fi -# Load kernel into the memory -/sbin/kexec -l "$KERNEL_IMAGE" --initrd="$INITRD" --append="$BOOT_OPTIONS" + +if is_secureboot && grep -q aboot_machine= /host/machine.conf; then + load_aboot_secureboot_kernel +else + load_kernel +fi if [[ "$REBOOT_TYPE" = "fast-reboot" ]]; then # Dump the ARP and FDB tables to files also as default routes for both IPv4 and IPv6 From f9d3ee0abbfa2e375c88ebb54573e097fed07ce5 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Wed, 5 Aug 2020 00:48:20 -0700 Subject: [PATCH 21/48] [tests] Add unit tests for 'show platform ...' commands (#1021) Add unit tests for 'show platform ...' commands --- tests/show_platform_test.py | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/show_platform_test.py diff --git a/tests/show_platform_test.py b/tests/show_platform_test.py new file mode 100644 index 0000000000..028a90fb69 --- /dev/null +++ b/tests/show_platform_test.py @@ -0,0 +1,58 @@ +import os +import sys +import textwrap + +import mock +from click.testing import CliRunner + +test_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(test_path) +sys.path.insert(0, modules_path) + +import show.main as show + + +TEST_PLATFORM = "x86_64-mlnx_msn2700-r0" +TEST_HWSKU = "Mellanox-SN2700" +TEST_ASIC_TYPE = "mellanox" + + +""" + Note: The following 'show platform' commands simply call other SONiC + CLI utilities, so the unit tests for the other utilities are expected + to cover testing their functionality: + + show platform fan + show platform firmware + show platform mlnx + show platform psustatus + show platform ssdhealth + show platform syseeprom + show platform temperature +""" + +class TestShowPlatform(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + # Test 'show platform summary' + def test_summary(self): + expected_output = """\ + Platform: {} + HwSKU: {} + ASIC: {} + """.format(TEST_PLATFORM, TEST_HWSKU, TEST_ASIC_TYPE) + + with mock.patch("show.main.get_hw_info_dict", + return_value={"platform": TEST_PLATFORM, "hwsku": TEST_HWSKU, "asic_type": TEST_ASIC_TYPE}): + runner = CliRunner() + result = runner.invoke(show.cli.commands["platform"].commands["summary"], []) + assert result.output == textwrap.dedent(expected_output) + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) + os.environ["UTILITIES_UNIT_TESTING"] = "0" From e17383a66c57404266716035084b089049e28066 Mon Sep 17 00:00:00 2001 From: fk410167 <51665572+fk410167@users.noreply.github.com> Date: Thu, 6 Aug 2020 23:39:37 +0530 Subject: [PATCH 22/48] [PDDF] Make utilities compatible to platform API 2.0, in accordance with pddf_2.0 (#940) Added PDDF utilities changes to make them compatible with 2.0 platform APIs. Since PDDF 2.0 is supporting platform 2.0 APIs, we needed to move the utilities to support the same. Signed-off-by: Fuzail Khan --- pddf_fanutil/main.py | 160 ++++++++++++++++++++++------ pddf_ledutil/main.py | 55 +++++++--- pddf_psuutil/main.py | 190 +++++++++++++++++++++++++++------- pddf_thermalutil/main.py | 97 ++++++++++++++--- utilities_common/util_base.py | 9 +- 5 files changed, 411 insertions(+), 100 deletions(-) diff --git a/pddf_fanutil/main.py b/pddf_fanutil/main.py index 5c081f0124..04ca4ea66a 100644 --- a/pddf_fanutil/main.py +++ b/pddf_fanutil/main.py @@ -14,7 +14,7 @@ except ImportError as e: raise ImportError("%s - required module not found" % str(e)) -VERSION = '1.0' +VERSION = '2.0' SYSLOG_IDENTIFIER = "fanutil" PLATFORM_SPECIFIC_MODULE_NAME = "fanutil" @@ -22,14 +22,93 @@ # Global platform-specific fanutil class instance platform_fanutil = None +platform_chassis = None #logger = UtilLogger(SYSLOG_IDENTIFIER) +def _wrapper_get_num_fans(): + if platform_chassis is not None: + try: + return platform_chassis.get_num_fans() + except NotImplementedError: + pass + return platform_fanutil.get_num_fans() + +def _wrapper_get_fan_name(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_fan(idx-1).get_name() + except NotImplementedError: + pass + return "FAN {}".format(idx) + +def _wrapper_get_fan_presence(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_fan(idx-1).get_presence() + except NotImplementedError: + pass + return platform_fanutil.get_presence(idx) + +def _wrapper_get_fan_status(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_fan(idx-1).get_status() + except NotImplementedError: + pass + return platform_fanutil.get_status(idx) + +def _wrapper_get_fan_direction(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_fan(idx-1).get_direction() + except NotImplementedError: + pass + return platform_fanutil.get_direction(idx) + +def _wrapper_get_fan_speed(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_fan(idx-1).get_speed_rpm() + except NotImplementedError: + pass + return platform_fanutil.get_speed(idx) + +def _wrapper_get_fan_speed_rear(idx): + if platform_chassis is not None: + # This wrapper API is invalid for Pl API 2.0 as every fan + # is treated as a separate fan + return 0 + return platform_fanutil.get_speed_rear(idx) + +def _wrapper_set_fan_speed(idx, percent): + if platform_chassis is not None: + try: + return platform_chassis.get_fan(idx-1).set_speed(percent) + except NotImplementedError: + pass + return platform_fanutil.set_speed(percent) + +def _wrapper_dump_sysfs(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_fan(idx).dump_sysfs() + except NotImplementedError: + pass + return platform_fanutil.dump_sysfs() + + + + + # This is our main entrypoint - the main 'fanutil' command @click.group() def cli(): """pddf_fanutil - Command line utility for providing FAN information""" + global platform_fanutil + global platform_chassis + if os.geteuid() != 0: click.echo("Root privileges are required for this operation") sys.exit(1) @@ -41,13 +120,21 @@ def cli(): click.echo("PDDF mode should be supported and enabled for this platform for this operation") sys.exit(1) - # Load platform-specific fanutil class - global platform_fanutil + # Load new platform api class try: - platform_fanutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + import sonic_platform.platform + platform_chassis = sonic_platform.platform.Platform().get_chassis() except Exception as e: - click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) - sys.exit(2) + click.echo("Failed to load chassis due to {}".format(str(e))) + + + # Load platform-specific fanutil class if new platform object class is not found + if platform_chassis is None: + try: + platform_fanutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + except Exception as e: + click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) + sys.exit(2) # 'version' subcommand @cli.command() @@ -59,14 +146,14 @@ def version(): @cli.command() def numfans(): """Display number of FANs installed on device""" - click.echo(str(platform_fanutil.get_num_fans())) + click.echo(_wrapper_get_num_fans()) # 'status' subcommand @cli.command() @click.option('-i', '--index', default=-1, type=int, help="the index of FAN") def status(index): """Display FAN status""" - supported_fan = range(1, platform_fanutil.get_num_fans() + 1) + supported_fan = range(1, _wrapper_get_num_fans()+1) fan_ids = [] if (index < 0): fan_ids = supported_fan @@ -78,14 +165,14 @@ def status(index): for fan in fan_ids: msg = "" - fan_name = "FAN {}".format(fan) + fan_name = _wrapper_get_fan_name(fan) if fan not in supported_fan: click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported FAN - {}.".format(fan_name, platform_fanutil.get_num_fans())) + "Number of supported FAN - {}.".format(fan_name, len(supported_fan))) continue - presence = platform_fanutil.get_presence(fan) + presence = _wrapper_get_fan_presence(fan) if presence: - oper_status = platform_fanutil.get_status(fan) + oper_status = _wrapper_get_fan_status(fan) msg = 'OK' if oper_status else "NOT OK" else: msg = 'NOT PRESENT' @@ -99,7 +186,7 @@ def status(index): @click.option('-i', '--index', default=-1, type=int, help="the index of FAN") def direction(index): """Display FAN airflow direction""" - supported_fan = range(1, platform_fanutil.get_num_fans() + 1) + supported_fan = range(1, _wrapper_get_num_fans() + 1) fan_ids = [] if (index < 0): fan_ids = supported_fan @@ -110,13 +197,13 @@ def direction(index): status_table = [] for fan in fan_ids: - fan_name = "FAN {}".format(fan) + fan_name = _wrapper_get_fan_name(fan) if fan not in supported_fan: click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported FAN - {}.".format(fan_name, platform_fanutil.get_num_fans())) + "Number of supported FAN - {}.".format(fan_name, len(supported_fan))) continue - direction = platform_fanutil.get_direction(fan) - status_table.append([fan_name, direction]) + direction = _wrapper_get_fan_direction(fan) + status_table.append([fan_name, direction.capitalize()]) if status_table: click.echo(tabulate(status_table, header, tablefmt="simple")) @@ -126,25 +213,33 @@ def direction(index): @click.option('-i', '--index', default=-1, type=int, help="the index of FAN") def getspeed(index): """Display FAN speed in RPM""" - supported_fan = range(1, platform_fanutil.get_num_fans() + 1) + supported_fan = range(1, _wrapper_get_num_fans() + 1) fan_ids = [] if (index < 0): fan_ids = supported_fan else: fan_ids = [index] - header = ['FAN', 'Front Fan RPM', 'Rear Fan RPM'] + if platform_chassis is not None: + header = ['FAN', 'SPEED (RPM)'] + else: + header = ['FAN', 'Front Fan RPM', 'Rear Fan RPM'] + status_table = [] for fan in fan_ids: - fan_name = "FAN {}".format(fan) + fan_name = _wrapper_get_fan_name(fan) if fan not in supported_fan: click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported FAN - {}.".format(fan_name, platform_fanutil.get_num_fans())) + "Number of supported FAN - {}.".format(fan_name, len(supported_fan))) continue - front = platform_fanutil.get_speed(fan) - rear = platform_fanutil.get_speed_rear(fan) - status_table.append([fan_name, front, rear]) + front = _wrapper_get_fan_speed(fan) + rear = _wrapper_get_fan_speed_rear(fan) + + if platform_chassis is not None: + status_table.append([fan_name, front]) + else: + status_table.append([fan_name, front, rear]) if status_table: click.echo(tabulate(status_table, header, tablefmt="simple")) @@ -158,21 +253,24 @@ def setspeed(speed): click.echo("speed value is required") raise click.Abort() - status = platform_fanutil.set_speed(speed) - if status: - click.echo("Successful") - else: - click.echo("Failed") + for fan in range(_wrapper_get_num_fans()): + status = _wrapper_set_fan_speed(fan, speed) + if not status: + click.echo("Failed") + sys.exit(1) + + click.echo("Successful") @cli.group() def debug(): """pddf_fanutil debug commands""" pass -@debug.command('dump-sysfs') +@debug.command() def dump_sysfs(): """Dump all Fan related SysFS paths""" - status = platform_fanutil.dump_sysfs() + for fan in range(_wrapper_get_num_fans()): + status = _wrapper_dump_sysfs(fan) if status: for i in status: diff --git a/pddf_ledutil/main.py b/pddf_ledutil/main.py index 217781abf0..5f8e74c55f 100644 --- a/pddf_ledutil/main.py +++ b/pddf_ledutil/main.py @@ -13,7 +13,7 @@ except ImportError as e: raise ImportError("%s - required module not found" % str(e)) -VERSION = '1.0' +VERSION = '2.0' SYSLOG_IDENTIFIER = "ledutil" PLATFORM_SPECIFIC_MODULE_NAME = "ledutil" @@ -21,6 +21,25 @@ # Global platform-specific ledutil class instance platform_ledutil = None +platform_chassis = None + + +# ==================== Wrapper APIs ==================== +def _wrapper_getstatusled(device_name): + if platform_chassis is not None: + outputs=platform_chassis.get_system_led(device_name) + else: + outputs = platform_ledutil.get_status_led(device_name) + click.echo(outputs) + +def _wrapper_setstatusled(device_name, color, color_state): + if platform_chassis is not None: + outputs=platform_chassis.set_system_led(device_name, color) + else: + outputs = platform_ledutil.set_status_led(device_name, color, color_state) + click.echo(outputs) + + #logger = UtilLogger(SYSLOG_IDENTIFIER) @@ -30,7 +49,7 @@ # This is our main entrypoint - the main 'ledutil' command @click.group() def cli(): - """pddf_ledutil - Command line utility for providing FAN information""" + """pddf_ledutil - Command line utility for providing System LED information""" if os.geteuid() != 0: click.echo("Root privileges are required for this operation") @@ -45,11 +64,22 @@ def cli(): # Load platform-specific fanutil class global platform_ledutil + global platform_chassis + + # Load new platform api class try: - platform_ledutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + import sonic_platform.platform + platform_chassis = sonic_platform.platform.Platform().get_chassis() except Exception as e: - click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) - sys.exit(2) + click.echo("Failed to load chassis due to {}".format(str(e))) + + # Load platform-specific psuutil class if 2.0 implementation is not present + if platform_chassis is None: + try: + platform_ledutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + except Exception as e: + click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) + sys.exit(2) # 'version' subcommand @cli.command() @@ -60,29 +90,26 @@ def version(): # 'getstatusled' subcommand @cli.command() @click.argument('device_name', type=click.STRING) -@click.argument('index', type=click.STRING) -def getstatusled(device_name, index): +def getstatusled(device_name): if device_name is None: click.echo("device_name is required") raise click.Abort() - outputs = platform_ledutil.get_status_led(device_name, index) - click.echo(outputs) + _wrapper_getstatusled(device_name) # 'setstatusled' subcommand @cli.command() @click.argument('device_name', type=click.STRING) -@click.argument('index', type=click.STRING) @click.argument('color', type=click.STRING) -@click.argument('color_state', type=click.STRING) -def setstatusled(device_name, index, color, color_state): +@click.argument('color_state', default='SOLID', type=click.STRING) +def setstatusled(device_name, color, color_state): if device_name is None: click.echo("device_name is required") raise click.Abort() - outputs = platform_ledutil.set_status_led(device_name, index, color, color_state) - click.echo(outputs) + _wrapper_setstatusled(device_name, color, color_state) + if __name__ == '__main__': cli() diff --git a/pddf_psuutil/main.py b/pddf_psuutil/main.py index 0209d7592c..54bd0ccc52 100644 --- a/pddf_psuutil/main.py +++ b/pddf_psuutil/main.py @@ -14,7 +14,7 @@ except ImportError as e: raise ImportError("%s - required module not found" % str(e)) -VERSION = '1.0' +VERSION = '2.0' SYSLOG_IDENTIFIER = "psuutil" PLATFORM_SPECIFIC_MODULE_NAME = "psuutil" @@ -22,9 +22,115 @@ # Global platform-specific psuutil class instance platform_psuutil = None +platform_chassis = None #logger = UtilLogger(SYSLOG_IDENTIFIER) +# Wrapper APIs so that this util is suited to both 1.0 and 2.0 platform APIs +def _wrapper_get_num_psus(): + if platform_chassis is not None: + try: + return platform_chassis.get_num_psus() + except NotImplementedError: + pass + return platform_psuutil.get_num_psus() + +def _wrapper_get_psu_name(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_name() + except NotImplementedError: + pass + return "PSU {}".format(idx) + +def _wrapper_get_psu_presence(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_presence() + except NotImplementedError: + pass + return platform_psuutil.get_psu_presence(idx) + +def _wrapper_get_psu_status(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_status() + except NotImplementedError: + pass + return platform_psuutil.get_psu_status(idx) + +def _wrapper_get_psu_model(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_model() + except NotImplementedError: + pass + return platform_psuutil.get_model(idx) + +def _wrapper_get_psu_mfr_id(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_mfr_id() + except NotImplementedError: + pass + return platform_psuutil.get_mfr_id(idx) + +def _wrapper_get_psu_serial(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_serial() + except NotImplementedError: + pass + return platform_psuutil.get_serial(idx) + +def _wrapper_get_psu_direction(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1)._fan_list[0].get_direction() + except NotImplementedError: + pass + return platform_psuutil.get_direction(idx) + +def _wrapper_get_output_voltage(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_voltage() + except NotImplementedError: + pass + return platform_psuutil.get_output_voltage(idx) + +def _wrapper_get_output_current(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_current() + except NotImplementedError: + pass + return platform_psuutil.get_output_current(idx) + +def _wrapper_get_output_power(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1).get_power() + except NotImplementedError: + pass + return platform_psuutil.get_output_power(idx) + +def _wrapper_get_fan_rpm(idx, fan_idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx-1)._fan_list[fan_idx-1].get_speed_rpm() + except NotImplementedError: + pass + return platform_psuutil.get_fan_rpm(idx, fan_idx) + +def _wrapper_dump_sysfs(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_psu(idx).dump_sysfs() + except NotImplementedError: + pass + return platform_psuutil.dump_sysfs() + # ==================== CLI commands and groups ==================== @@ -33,6 +139,9 @@ def cli(): """psuutil - Command line utility for providing PSU status""" + global platform_psuutil + global platform_chassis + if os.geteuid() != 0: click.echo("Root privileges are required for this operation") sys.exit(1) @@ -44,32 +153,40 @@ def cli(): click.echo("PDDF mode should be supported and enabled for this platform for this operation") sys.exit(1) - # Load platform-specific fanutil class - global platform_psuutil + # Load new platform api class try: - platform_psuutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + import sonic_platform.platform + platform_chassis = sonic_platform.platform.Platform().get_chassis() except Exception as e: - click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) - sys.exit(2) + click.echo("Failed to load chassis due to {}".format(str(e))) + + + # Load platform-specific psuutil class if 2.0 implementation is not present + if platform_chassis is None: + try: + platform_psuutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + except Exception as e: + click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) + sys.exit(2) # 'version' subcommand @cli.command() def version(): """Display version info""" - click.echo("psuutil version {0}".format(VERSION)) + click.echo("PDDF psuutil version {0}".format(VERSION)) # 'numpsus' subcommand @cli.command() def numpsus(): """Display number of supported PSUs on device""" - click.echo(str(platform_psuutil.get_num_psus())) + click.echo(_wrapper_get_num_psus()) # 'status' subcommand @cli.command() @click.option('-i', '--index', default=-1, type=int, help="the index of PSU") def status(index): """Display PSU status""" - supported_psu = range(1, platform_psuutil.get_num_psus() + 1) + supported_psu = range(1, _wrapper_get_num_psus() + 1) psu_ids = [] if (index < 0): psu_ids = supported_psu @@ -81,14 +198,14 @@ def status(index): for psu in psu_ids: msg = "" - psu_name = "PSU {}".format(psu) + psu_name = _wrapper_get_psu_name(psu) if psu not in supported_psu: click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported PSU - {}.".format(psu_name, platform_psuutil.get_num_psus())) + "Number of supported PSU - {}.".format(psu_name, len(supported_psu))) continue - presence = platform_psuutil.get_psu_presence(psu) + presence = _wrapper_get_psu_presence(psu) if presence: - oper_status = platform_psuutil.get_psu_status(psu) + oper_status = _wrapper_get_psu_status(psu) msg = 'OK' if oper_status else "NOT OK" else: msg = 'NOT PRESENT' @@ -102,7 +219,7 @@ def status(index): @click.option('-i', '--index', default=-1, type=int, help="the index of PSU") def mfrinfo(index): """Display PSU manufacturer info""" - supported_psu = range(1, platform_psuutil.get_num_psus() + 1) + supported_psu = range(1, _wrapper_get_num_psus() + 1) psu_ids = [] if (index < 0): psu_ids = supported_psu @@ -110,24 +227,24 @@ def mfrinfo(index): psu_ids = [index] for psu in psu_ids: - psu_name = "PSU {}".format(psu) + psu_name = _wrapper_get_psu_name(psu) if psu not in supported_psu: click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported PSU - {}.".format(psu_name, platform_psuutil.get_num_psus())) + "Number of supported PSU - {}.".format(psu_name, len(supported_psu))) continue - status = platform_psuutil.get_psu_status(psu) + status = _wrapper_get_psu_status(psu) if not status: click.echo("{} is Not OK\n".format(psu_name)) continue - model_name = platform_psuutil.get_model(psu) - mfr_id = platform_psuutil.get_mfr_id(psu) - serial_num = platform_psuutil.get_serial(psu) - airflow_dir = platform_psuutil.get_direction(psu) + model_name = _wrapper_get_psu_model(psu) + mfr_id = _wrapper_get_psu_mfr_id(psu) + serial_num = _wrapper_get_psu_serial(psu) + airflow_dir = _wrapper_get_psu_direction(psu) click.echo("{} is OK\nManufacture Id: {}\n" \ "Model: {}\nSerial Number: {}\n" \ - "Fan Direction: {}\n".format(psu_name, mfr_id, model_name, serial_num, airflow_dir)) + "Fan Direction: {}\n".format(psu_name, mfr_id, model_name, serial_num, airflow_dir.capitalize())) # 'seninfo' subcommand @@ -135,7 +252,7 @@ def mfrinfo(index): @click.option('-i', '--index', default=-1, type=int, help="the index of PSU") def seninfo(index): """Display PSU sensor info""" - supported_psu = range(1, platform_psuutil.get_num_psus() + 1) + supported_psu = range(1, _wrapper_get_num_psus() + 1) psu_ids = [] if (index < 0): psu_ids = supported_psu @@ -143,24 +260,22 @@ def seninfo(index): psu_ids = [index] for psu in psu_ids: - psu_name = "PSU {}".format(psu) + psu_name = _wrapper_get_psu_name(psu) if psu not in supported_psu: click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported PSU - {}.".format(psu_name, platform_psuutil.get_num_psus())) + "Number of supported PSU - {}.".format(psu_name, len(supported_psu))) continue - oper_status = platform_psuutil.get_psu_status(psu) + oper_status = _wrapper_get_psu_status(psu) if not oper_status: click.echo("{} is Not OK\n".format(psu_name)) continue - v_out = platform_psuutil.get_output_voltage(psu) - i_out = platform_psuutil.get_output_current(psu) - p_out = platform_psuutil.get_output_power(psu) - # p_out would be in micro watts, convert it into milli watts - p_out = p_out/1000 + v_out = _wrapper_get_output_voltage(psu) * 1000 + i_out = _wrapper_get_output_current(psu) * 1000 + p_out = _wrapper_get_output_power(psu) * 1000 - fan1_rpm = platform_psuutil.get_fan_speed(psu, 1) + fan1_rpm = _wrapper_get_fan_rpm(psu, 1) click.echo("{} is OK\nOutput Voltage: {} mv\n" \ "Output Current: {} ma\nOutput Power: {} mw\n" \ "Fan1 Speed: {} rpm\n".format(psu_name, v_out, i_out, p_out, fan1_rpm)) @@ -170,14 +285,15 @@ def debug(): """pddf_psuutil debug commands""" pass -@debug.command('dump-sysfs') +@debug.command() def dump_sysfs(): """Dump all PSU related SysFS paths""" - status = platform_psuutil.dump_sysfs() + for psu in range(_wrapper_get_num_psus()): + status = _wrapper_dump_sysfs(psu) - if status: - for i in status: - click.echo(i) + if status: + for i in status: + click.echo(i) if __name__ == '__main__': diff --git a/pddf_thermalutil/main.py b/pddf_thermalutil/main.py index 077d30b08d..11b2aea1ff 100644 --- a/pddf_thermalutil/main.py +++ b/pddf_thermalutil/main.py @@ -14,7 +14,7 @@ except ImportError as e: raise ImportError("%s - required module not found" % str(e)) -VERSION = '1.0' +VERSION = '2.0' SYSLOG_IDENTIFIER = "thermalutil" PLATFORM_SPECIFIC_MODULE_NAME = "thermalutil" @@ -22,9 +22,28 @@ # Global platform-specific thermalutil class instance platform_thermalutil = None +platform_chassis = None #logger = UtilLogger(SYSLOG_IDENTIFIER) +# Wrapper APIs so that this util is suited to both 1.0 and 2.0 platform APIs +def _wrapper_get_num_thermals(): + if platform_chassis is not None: + try: + return platform_chassis.get_num_thermals() + except NotImplementedError: + pass + return platform_thermalutil.get_num_thermals() + +def _wrapper_get_thermal_name(idx): + if platform_chassis is not None: + try: + return platform_chassis.get_thermal(idx-1).get_name() + except NotImplementedError: + pass + return "TEMP{}".format(idx) + + # ==================== CLI commands and groups ==================== @@ -33,6 +52,9 @@ def cli(): """pddf_thermalutil - Command line utility for providing Temp Sensors information""" + global platform_thermalutil + global platform_chassis + if os.geteuid() != 0: click.echo("Root privileges are required for this operation") sys.exit(1) @@ -44,13 +66,21 @@ def cli(): click.echo("PDDF mode should be supported and enabled for this platform for this operation") sys.exit(1) - # Load platform-specific fanutil class - global platform_thermalutil + # Load new platform api class try: - platform_thermalutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + import sonic_platform.platform + platform_chassis = sonic_platform.platform.Platform().get_chassis() except Exception as e: - click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) - sys.exit(2) + click.echo("Failed to load chassis due to {}".format(str(e))) + + + # Load platform-specific fanutil class + if platform_chassis is None: + try: + platform_thermalutil = helper.load_platform_util(PLATFORM_SPECIFIC_MODULE_NAME, PLATFORM_SPECIFIC_CLASS_NAME) + except Exception as e: + click.echo("Failed to load {}: {}".format(PLATFORM_SPECIFIC_MODULE_NAME, str(e))) + sys.exit(2) # 'version' subcommand @@ -63,33 +93,63 @@ def version(): @cli.command() def numthermals(): """Display number of Thermal Sensors installed """ - click.echo(str(platform_thermalutil.get_num_thermals())) + click.echo(_wrapper_get_num_thermals()) # 'gettemp' subcommand @cli.command() @click.option('-i', '--index', default=-1, type=int, help="the index of Temp Sensor") def gettemp(index): """Display Temperature values of thermal sensors""" - supported_thermal = range(1, platform_thermalutil.get_num_thermals() + 1) + supported_thermal = range(1, _wrapper_get_num_thermals()+ 1) thermal_ids = [] if (index < 0): thermal_ids = supported_thermal else: thermal_ids = [index] - header = ['Temp Sensor', 'Label', 'Value'] + header=[] status_table = [] for thermal in thermal_ids: - thermal_name = "TEMP{}".format(thermal) + thermal_name = _wrapper_get_thermal_name(thermal) if thermal not in supported_thermal: click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported Temp - {}.".format(thermal_name, platform_thermalutil.get_num_thermals())) - ##continue - label, value = platform_thermalutil.show_thermal_temp_values(thermal) - status_table.append([thermal_name, label, value]) + "Number of supported Temp - {}.".format(thermal_name, len(supported_thermal))) + continue + # TODO: Provide a wrapper API implementation for the below function + if platform_chassis is not None: + try: + temp = platform_chassis.get_thermal(thermal-1).get_temperature() + if temp: + value = "temp1\t %+.1f C ("%temp + high = platform_chassis.get_thermal(thermal-1).get_high_threshold() + if high: + value += "high = %+.1f C"%high + crit = platform_chassis.get_thermal(thermal-1).get_high_critical_threshold() + if high and crit: + value += ", " + if crit: + value += "crit = %+.1f C"%crit + + + label = platform_chassis.get_thermal(thermal-1).get_temp_label() + value +=")" + + except NotImplementedError: + pass + else: + label, value = platform_thermalutil.show_thermal_temp_values(thermal) + + if label is None: + status_table.append([thermal_name, value]) + else: + status_table.append([thermal_name, label, value]) if status_table: + if label is None: + header = ['Temp Sensor', 'Value'] + else: + header = ['Temp Sensor', 'Label', 'Value'] click.echo(tabulate(status_table, header, tablefmt="simple")) @cli.group() @@ -97,10 +157,15 @@ def debug(): """pddf_thermalutil debug commands""" pass -@debug.command('dump-sysfs') +@debug.command() def dump_sysfs(): """Dump all Temp Sensor related SysFS paths""" - status = platform_thermalutil.dump_sysfs() + if platform_chassis is not None: + supported_thermal = range(1, _wrapper_get_num_thermals()+ 1) + for index in supported_thermal: + status = platform_chassis.get_thermal(index-1).dump_sysfs() + else: + status = platform_thermalutil.dump_sysfs() if status: for i in status: diff --git a/utilities_common/util_base.py b/utilities_common/util_base.py index 2659824982..64c17e6d52 100644 --- a/utilities_common/util_base.py +++ b/utilities_common/util_base.py @@ -11,7 +11,8 @@ # # Constants ==================================================================== # -# Platform root directory inside docker +# Platform root directory +PLATFORM_ROOT_PATH = '/usr/share/sonic/device' PLATFORM_ROOT_DOCKER = '/usr/share/sonic/platform' SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku' @@ -98,7 +99,11 @@ def get_path_to_platform_and_hwsku(self): (platform, hwsku) = self.get_platform_and_hwsku() # Load platform module from source - platform_path = PLATFORM_ROOT_DOCKER + platform_path = '' + if len(platform) != 0: + platform_path = "/".join([PLATFORM_ROOT_PATH, platform]) + else: + platform_path = PLATFORM_ROOT_PATH_DOCKER hwsku_path = "/".join([platform_path, hwsku]) return (platform_path, hwsku_path) From ed3d7cb0b24a28c7b88a47c482402e5926b680b4 Mon Sep 17 00:00:00 2001 From: lguohan Date: Fri, 7 Aug 2020 03:25:12 -0700 Subject: [PATCH 23/48] [cli]: pass db connector as click context (#1029) it is better not let every command to have its own connector and connect to db. it is also not good to have a global db variable. Here, the idea is to have a single db connector and pass the connector as a click context Signed-off-by: Guohan Lu --- config/main.py | 56 ++++++++++++++++++++++-------------------- show/main.py | 24 +++++++++--------- tests/config_test.py | 14 ++++++++--- tests/conftest.py | 6 ----- tests/feature_test.py | 14 ++++++++--- utilities_common/db.py | 6 +++++ 6 files changed, 70 insertions(+), 50 deletions(-) create mode 100644 utilities_common/db.py diff --git a/config/main.py b/config/main.py index 169ce46657..dba28bec23 100755 --- a/config/main.py +++ b/config/main.py @@ -19,6 +19,7 @@ from config_mgmt import ConfigMgmtDPB from utilities_common.intf_filter import parse_interface_in_filter from utilities_common.util_base import UtilHelper +from utilities_common.db import Db from portconfig import get_child_ports, get_port_config_file_name import aaa @@ -50,7 +51,6 @@ CFG_LOOPBACK_NO="<0-999>" asic_type = None -config_db = None # ========================== Syslog wrappers ========================== @@ -691,7 +691,7 @@ def _abort_if_false(ctx, param, value): ctx.abort() -def _get_disabled_services_list(): +def _get_disabled_services_list(config_db): disabled_services_list = [] feature_table = config_db.get_table('FEATURE') @@ -713,7 +713,7 @@ def _get_disabled_services_list(): return disabled_services_list -def _stop_services(): +def _stop_services(config_db): # This list is order-dependent. Please add services in the order they should be stopped # on Mellanox platform pmon is stopped by syncd services_to_stop = [ @@ -730,7 +730,7 @@ def _stop_services(): if asic_type == 'mellanox' and 'pmon' in services_to_stop: services_to_stop.remove('pmon') - disabled_services = _get_disabled_services_list() + disabled_services = _get_disabled_services_list(config_db) for service in disabled_services: if service in services_to_stop: @@ -739,7 +739,7 @@ def _stop_services(): execute_systemctl(services_to_stop, SYSTEMCTL_ACTION_STOP) -def _reset_failed_services(): +def _reset_failed_services(config_db): # This list is order-independent. Please keep list in alphabetical order services_to_reset = [ 'bgp', @@ -762,7 +762,7 @@ def _reset_failed_services(): 'telemetry' ] - disabled_services = _get_disabled_services_list() + disabled_services = _get_disabled_services_list(config_db) for service in disabled_services: if service in services_to_reset: @@ -771,7 +771,7 @@ def _reset_failed_services(): execute_systemctl(services_to_reset, SYSTEMCTL_ACTION_RESET_FAILED) -def _restart_services(): +def _restart_services(config_db): # This list is order-dependent. Please add services in the order they should be started # on Mellanox platform pmon is started by syncd services_to_restart = [ @@ -790,7 +790,7 @@ def _restart_services(): 'telemetry' ] - disabled_services = _get_disabled_services_list() + disabled_services = _get_disabled_services_list(config_db) for service in disabled_services: if service in services_to_restart: @@ -908,7 +908,8 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port, # This is our main entrypoint - the main 'config' command @click.group(cls=AbbreviationGroup, context_settings=CONTEXT_SETTINGS) -def config(): +@click.pass_context +def config(ctx): """SONiC command line - 'config' command""" # # Load asic_type for further use @@ -932,10 +933,9 @@ def config(): SonicDBConfig.load_sonic_global_db_config() - global config_db + ctx.obj = Db() - config_db = ConfigDBConnector() - config_db.connect() +pass_db = click.make_pass_decorator(Db, ensure=True) config.add_command(aaa.aaa) config.add_command(aaa.tacacs) @@ -1060,7 +1060,8 @@ def load(filename, yes): @click.option('-l', '--load-sysinfo', is_flag=True, help='load system default information (mac, portmap etc) first.') @click.option('-n', '--no_service_restart', default=False, is_flag=True, help='Do not restart docker services') @click.argument('filename', required=False) -def reload(filename, yes, load_sysinfo, no_service_restart): +@pass_db +def reload(db, filename, yes, load_sysinfo, no_service_restart): """Clear current configuration and import a previous saved config DB dump file. : Names of configuration file(s) to load, separated by comma with no spaces in between """ @@ -1102,7 +1103,7 @@ def reload(filename, yes, load_sysinfo, no_service_restart): #Stop services before config push if not no_service_restart: log_info("'reload' stopping services...") - _stop_services() + _stop_services(db.cfgdb) """ In Single AISC platforms we have single DB service. In multi-ASIC platforms we have a global DB service running in the host + DB services running in each ASIC namespace created per ASIC. @@ -1175,9 +1176,9 @@ def reload(filename, yes, load_sysinfo, no_service_restart): # We first run "systemctl reset-failed" to remove the "failed" # status from all services before we attempt to restart them if not no_service_restart: - _reset_failed_services() + _reset_failed_services(db.cfgdb) log_info("'reload' restarting services...") - _restart_services() + _restart_services(db.cfgdb) @config.command("load_mgmt_config") @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, @@ -1208,14 +1209,15 @@ def load_mgmt_config(filename): @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Reload config from minigraph?') @click.option('-n', '--no_service_restart', default=False, is_flag=True, help='Do not restart docker services') -def load_minigraph(no_service_restart): +@pass_db +def load_minigraph(db, no_service_restart): """Reconfigure based on minigraph.""" log_info("'load_minigraph' executing...") #Stop services before config push if not no_service_restart: log_info("'load_minigraph' stopping services...") - _stop_services() + _stop_services(db.cfgdb) # For Single Asic platform the namespace list has the empty string # for mulit Asic platform the empty string to generate the config @@ -1269,10 +1271,10 @@ def load_minigraph(no_service_restart): # We first run "systemctl reset-failed" to remove the "failed" # status from all services before we attempt to restart them if not no_service_restart: - _reset_failed_services() + _reset_failed_services(db.cfgdb) #FIXME: After config DB daemon is implemented, we'll no longer need to restart every service. log_info("'load_minigraph' restarting services...") - _restart_services() + _restart_services(db.cfgdb) click.echo("Please note setting loaded from minigraph will be lost after system reboot. To preserve setting, run `config save`.") @@ -3810,15 +3812,16 @@ def feature(): @feature.command('state', short_help="Enable/disable a feature") @click.argument('name', metavar='', required=True) @click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -def feature_state(name, state): +@pass_db +def feature_state(db, name, state): """Enable/disable a feature""" - state_data = config_db.get_entry('FEATURE', name) + state_data = db.cfgdb.get_entry('FEATURE', name) if not state_data: click.echo("Feature '{}' doesn't exist".format(name)) sys.exit(1) - config_db.mod_entry('FEATURE', name, {'state': state}) + db.cfgdb.mod_entry('FEATURE', name, {'state': state}) # # 'autorestart' command ('config feature autorestart ...') @@ -3826,9 +3829,10 @@ def feature_state(name, state): @feature.command(name='autorestart', short_help="Enable/disable autosrestart of a feature") @click.argument('name', metavar='', required=True) @click.argument('autorestart', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -def feature_autorestart(name, autorestart): +@pass_db +def feature_autorestart(db, name, autorestart): """Enable/disable autorestart of a feature""" - feature_table = config_db.get_table('FEATURE') + feature_table = db.cfgdb.get_table('FEATURE') if not feature_table: click.echo("Unable to retrieve feature table from Config DB.") sys.exit(1) @@ -3837,7 +3841,7 @@ def feature_autorestart(name, autorestart): click.echo("Unable to retrieve feature '{}'".format(name)) sys.exit(1) - config_db.mod_entry('FEATURE', name, {'auto_restart': autorestart}) + db.cfgdb.mod_entry('FEATURE', name, {'auto_restart': autorestart}) if __name__ == '__main__': config() diff --git a/show/main.py b/show/main.py index 26c5f280e9..8193e1f611 100755 --- a/show/main.py +++ b/show/main.py @@ -19,6 +19,7 @@ from swsssdk import ConfigDBConnector from swsssdk import SonicV2Connector from portconfig import get_child_ports +from utilities_common.db import Db import mlnx @@ -31,8 +32,6 @@ VLAN_SUB_INTERFACE_SEPARATOR = '.' -config_db = None - try: # noinspection PyPep8Naming import ConfigParser as configparser @@ -557,12 +556,13 @@ def get_bgp_neighbor_ip_to_name(ip, static_neighbors, dynamic_neighbors): # This is our entrypoint - the main "show" command # TODO: Consider changing function name to 'show' for better understandability @click.group(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS) -def cli(): +@click.pass_context +def cli(ctx): """SONiC command line - 'show' command""" - global config_db - config_db = ConfigDBConnector() - config_db.connect() + ctx.obj = Db() + +pass_db = click.make_pass_decorator(Db, ensure=True) # # 'vrf' command ("show vrf") @@ -3064,14 +3064,15 @@ def feature(): pass # -# 'state' subcommand (show feature status) +# 'status' subcommand (show feature status) # @feature.command('status', short_help="Show feature state") @click.argument('feature_name', required=False) -def autorestart(feature_name): +@pass_db +def feature_status(db, feature_name): header = ['Feature', 'State', 'AutoRestart'] body = [] - feature_table = config_db.get_table('FEATURE') + feature_table = db.cfgdb.get_table('FEATURE') if feature_name: if feature_table and feature_table.has_key(feature_name): body.append([feature_name, feature_table[feature_name]['state'], \ @@ -3089,10 +3090,11 @@ def autorestart(feature_name): # @feature.command('autorestart', short_help="Show auto-restart state for a feature") @click.argument('feature_name', required=False) -def autorestart(feature_name): +@pass_db +def feature_autorestart(db, feature_name): header = ['Feature', 'AutoRestart'] body = [] - feature_table = config_db.get_table('FEATURE') + feature_table = db.cfgdb.get_table('FEATURE') if feature_name: if feature_table and feature_table.has_key(feature_name): body.append([feature_name, feature_table[feature_name]['auto_restart']]) diff --git a/tests/config_test.py b/tests/config_test.py index 5e8a983486..3100848dbb 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -1,5 +1,9 @@ +import traceback + from click.testing import CliRunner +from utilities_common.db import Db + load_minigraph_command_output="""\ Executing stop of service telemetry... Executing stop of service swss... @@ -52,16 +56,20 @@ def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic): result = runner.invoke(config.config.commands["load_minigraph"], ["-y"]) 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')]) == load_minigraph_command_output def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_single_broacom_asic): (config, show) = get_cmd_module + db = Db() runner = CliRunner() - runner.invoke(config.config.commands["feature"].commands["state"], ["telemetry", "disabled"]) - result = runner.invoke(show.cli.commands["feature"].commands["status"], ["telemetry"]) + result = runner.invoke(config.config.commands["feature"].commands["state"], ["telemetry", "disabled"], obj=db) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["feature"].commands["status"], ["telemetry"], obj=db) print result.output - result = runner.invoke(config.config.commands["load_minigraph"], ["-y"]) + assert result.exit_code == 0 + result = runner.invoke(config.config.commands["load_minigraph"], ["-y"], obj=db) print result.exit_code print result.output assert result.exit_code == 0 diff --git a/tests/conftest.py b/tests/conftest.py index 44260c25e1..03dbfdb38e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -58,12 +58,6 @@ def get_cmd_module(): import config.main as config import show.main as show - config_db = ConfigDBConnector() - config_db.connect() - - config.config_db = config_db - show.config_db = config_db - config.run_command = _dummy_run_command return (config, show) diff --git a/tests/feature_test.py b/tests/feature_test.py index f6c748acf7..c928b9657e 100644 --- a/tests/feature_test.py +++ b/tests/feature_test.py @@ -1,5 +1,7 @@ from click.testing import CliRunner +from utilities_common.db import Db + show_feature_status_output="""\ Feature State AutoRestart ---------- -------- ------------- @@ -122,22 +124,26 @@ def test_show_unknown_autorestart_status(self, get_cmd_module): def test_config_bgp_feature_state(self, get_cmd_module): (config, show) = get_cmd_module + db = Db() runner = CliRunner() - result = runner.invoke(config.config.commands["feature"].commands["state"], ["bgp", "disabled"]) + result = runner.invoke(config.config.commands["feature"].commands["state"], ["bgp", "disabled"], obj=db) print(result.exit_code) print(result.output) - result = runner.invoke(show.cli.commands["feature"].commands["status"], ["bgp"]) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["feature"].commands["status"], ["bgp"], obj=db) print(result.output) assert result.exit_code == 0 assert result.output == show_feature_bgp_disabled_status_output def test_config_bgp_autorestart(self, get_cmd_module): (config, show) = get_cmd_module + db = Db() runner = CliRunner() - result = runner.invoke(config.config.commands["feature"].commands["autorestart"], ["bgp", "disabled"]) + result = runner.invoke(config.config.commands["feature"].commands["autorestart"], ["bgp", "disabled"], obj=db) print(result.exit_code) print(result.output) - result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], ["bgp"]) + assert result.exit_code == 0 + result = runner.invoke(show.cli.commands["feature"].commands["autorestart"], ["bgp"], obj=db) print(result.output) assert result.exit_code == 0 assert result.output == show_feature_bgp_disabled_autorestart_output diff --git a/utilities_common/db.py b/utilities_common/db.py new file mode 100644 index 0000000000..462c5d690e --- /dev/null +++ b/utilities_common/db.py @@ -0,0 +1,6 @@ +from swsssdk import ConfigDBConnector + +class Db(object): + def __init__(self): + self.cfgdb = ConfigDBConnector() + self.cfgdb.connect() From a80826d92fffcaae572e2eb51655f6619a1f5b12 Mon Sep 17 00:00:00 2001 From: Mahesh Maddikayala <10645050+smaheshm@users.noreply.github.com> Date: Fri, 7 Aug 2020 18:00:11 -0700 Subject: [PATCH 24/48] [config qos] QoS and Buffer config genration for multi ASIC platforms (#978) * QoS and Buffer config genration for multi ASIC platforms. In case of multi ASIC platforms each ASIC has its own buffer and QoS configuration template that is used to populate corresponding ASIC's config DB. Made changes so that QoS config commands generates QoS and buffer configs for all ASICs. Also the 'config qos clear' acts on all ASIC instances. --- config/main.py | 117 ++++++++++++++++++++++++++++++++++--------- tests/config_test.py | 4 +- 2 files changed, 94 insertions(+), 27 deletions(-) diff --git a/config/main.py b/config/main.py index dba28bec23..6bfd4da9b6 100755 --- a/config/main.py +++ b/config/main.py @@ -663,10 +663,21 @@ def _clear_qos(): 'BUFFER_PROFILE', 'BUFFER_PG', 'BUFFER_QUEUE'] - config_db = ConfigDBConnector() - config_db.connect() - for qos_table in QOS_TABLE_NAMES: - config_db.delete_table(qos_table) + + namespace_list = [DEFAULT_NAMESPACE] + if sonic_device_util.get_num_npus() > 1: + namespace_list = sonic_device_util.get_namespaces() + + for ns in namespace_list: + if ns is DEFAULT_NAMESPACE: + config_db = ConfigDBConnector() + else: + config_db = ConfigDBConnector( + use_unix_socket_path=True, namespace=ns + ) + config_db.connect() + for qos_table in QOS_TABLE_NAMES: + config_db.delete_table(qos_table) def _get_sonic_generated_services(num_asic): if not os.path.isfile(SONIC_GENERATED_SERVICE_PATH): @@ -1231,11 +1242,11 @@ def load_minigraph(db, no_service_restart): if namespace is DEFAULT_NAMESPACE: config_db = ConfigDBConnector() cfggen_namespace_option = " " - ns_cmd_prefix = " " + ns_cmd_prefix = "" else: config_db = ConfigDBConnector(use_unix_socket_path=True, namespace=namespace) cfggen_namespace_option = " -n {}".format(namespace) - ns_cmd_prefix = "sudo ip netns exec {}".format(namespace) + ns_cmd_prefix = "sudo ip netns exec {} ".format(namespace) config_db.connect() client = config_db.get_redis_client(config_db.CONFIG_DB) client.flushdb() @@ -1252,12 +1263,14 @@ def load_minigraph(db, no_service_restart): # These commands are not run for host on multi asic platform if num_npus == 1 or namespace is not DEFAULT_NAMESPACE: if device_type != 'MgmtToRRouter': - run_command('{} pfcwd start_default'.format(ns_cmd_prefix), display_cmd=True) - run_command("{} config qos reload".format(ns_cmd_prefix), display_cmd=True) + run_command('{}pfcwd start_default'.format(ns_cmd_prefix), display_cmd=True) if os.path.isfile('/etc/sonic/acl.json'): run_command("acl-loader update full /etc/sonic/acl.json", display_cmd=True) + # generate QoS and Buffer configs + run_command("config qos reload", display_cmd=True) + # Write latest db version string into db db_migrator='/usr/bin/db_migrator.py' if os.path.isfile(db_migrator) and os.access(db_migrator, os.X_OK): @@ -1642,26 +1655,80 @@ def reload(): _clear_qos() platform = sonic_device_util.get_platform() hwsku = sonic_device_util.get_hwsku() - buffer_template_file = os.path.join('/usr/share/sonic/device/', platform, hwsku, 'buffers.json.j2') - if os.path.isfile(buffer_template_file): - command = "{} -d -t {} >/tmp/buffers.json".format(SONIC_CFGGEN_PATH, buffer_template_file) - run_command(command, display_cmd=True) - - qos_template_file = os.path.join('/usr/share/sonic/device/', platform, hwsku, 'qos.json.j2') - sonic_version_file = os.path.join('/etc/sonic/', 'sonic_version.yml') - if os.path.isfile(qos_template_file): - command = "{} -d -t {} -y {} >/tmp/qos.json".format(SONIC_CFGGEN_PATH, qos_template_file, sonic_version_file) - run_command(command, display_cmd=True) + namespace_list = [DEFAULT_NAMESPACE] + if sonic_device_util.get_num_npus() > 1: + namespace_list = sonic_device_util.get_namespaces() - # Apply the configurations only when both buffer and qos configuration files are presented - command = "{} -j /tmp/buffers.json --write-to-db".format(SONIC_CFGGEN_PATH) - run_command(command, display_cmd=True) - command = "{} -j /tmp/qos.json --write-to-db".format(SONIC_CFGGEN_PATH) + for ns in namespace_list: + if ns is DEFAULT_NAMESPACE: + asic_id_suffix = "" + else: + asic_id = sonic_device_util.get_npu_id_from_name(ns) + if asic_id is None: + click.secho( + "Command 'qos reload' failed with invalid namespace '{}'". + format(ns), + fg='yellow' + ) + raise click.Abort() + asic_id_suffix = str(asic_id) + + buffer_template_file = os.path.join( + '/usr/share/sonic/device/', + platform, + hwsku, + asic_id_suffix, + 'buffers.json.j2' + ) + buffer_output_file = "/tmp/buffers{}.json".format(asic_id_suffix) + qos_output_file = "/tmp/qos{}.json".format(asic_id_suffix) + + cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns) + if os.path.isfile(buffer_template_file): + command = "{} {} -d -t {} > {}".format( + SONIC_CFGGEN_PATH, + cmd_ns, + buffer_template_file, + buffer_output_file + ) run_command(command, display_cmd=True) + qos_template_file = os.path.join( + '/usr/share/sonic/device/', + platform, + hwsku, + asic_id_suffix, + 'qos.json.j2' + ) + sonic_version_file = os.path.join( + '/etc/sonic/', 'sonic_version.yml' + ) + if os.path.isfile(qos_template_file): + command = "{} {} -d -t {} -y {} > {}".format( + SONIC_CFGGEN_PATH, + cmd_ns, + qos_template_file, + sonic_version_file, + qos_output_file + ) + run_command(command, display_cmd=True) + # Apply the configurations only when both buffer and qos + # configuration files are presented + command = "{} {} -j {} --write-to-db".format( + SONIC_CFGGEN_PATH, cmd_ns, buffer_output_file + ) + run_command(command, display_cmd=True) + command = "{} {} -j {} --write-to-db".format( + SONIC_CFGGEN_PATH, cmd_ns, qos_output_file + ) + run_command(command, display_cmd=True) + else: + click.secho('QoS definition template not found at {}'.format( + qos_template_file + ), fg='yellow') else: - click.secho('QoS definition template not found at {}'.format(qos_template_file), fg='yellow') - else: - click.secho('Buffer definition template not found at {}'.format(buffer_template_file), fg='yellow') + click.secho('Buffer definition template not found at {}'.format( + buffer_template_file + ), fg='yellow') # # 'warm_restart' group ('config warm_restart ...') diff --git a/tests/config_test.py b/tests/config_test.py index 3100848dbb..ebc0b55fdf 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -13,8 +13,8 @@ Executing stop of service hostcfgd... Executing stop of service nat... Running command: /usr/local/bin/sonic-cfggen -H -m --write-to-db -Running command: pfcwd start_default -Running command: config qos reload +Running command: pfcwd start_default +Running command: config qos reload Executing reset-failed of service bgp... Executing reset-failed of service dhcp_relay... Executing reset-failed of service hostcfgd... From 621aad0151b6a64ab5c64e81f73f93b2c6b827bd Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Sat, 8 Aug 2020 11:08:14 -0700 Subject: [PATCH 25/48] [Python] Migrate applications/scripts to import sonic-py-common package (#1008) As part of consolidating all common Python-based functionality into the new sonic-py-common package, this pull request redirects all Python applications/scripts in sonic-utilities repo to point to sonic-py-common and removes multiple duplicate definitions of common functions in the process. This is the next step toward resolving https://github.com/Azure/sonic-buildimage/issues/4999. - Also reorganize imports for consistency - Also align some style --- acl_loader/main.py | 12 +-- config/main.py | 195 +++++++++++++++------------------- config/mlnx.py | 36 ++----- fwutil/lib.py | 21 ++-- fwutil/log.py | 47 ++------ pcieutil/main.py | 118 +++++--------------- pddf_fanutil/main.py | 4 - pddf_ledutil/main.py | 3 - pddf_psuutil/main.py | 1 - pddf_thermalutil/main.py | 2 - psuutil/main.py | 94 ++++------------ scripts/db_migrator.py | 51 ++++----- scripts/decode-syseeprom | 12 +-- scripts/lldpshow | 49 +++++---- scripts/neighbor_advertiser | 65 +++++------- scripts/port2alias | 21 +--- setup.py | 3 +- sfputil/main.py | 98 +++-------------- show/main.py | 85 ++++++--------- sonic_installer/main.py | 9 +- ssdutil/main.py | 72 ++----------- tests/conftest.py | 4 +- utilities_common/util_base.py | 110 +------------------ watchdogutil/main.py | 40 ++----- 24 files changed, 323 insertions(+), 829 deletions(-) diff --git a/acl_loader/main.py b/acl_loader/main.py index f23aefc2e4..97c5afa46c 100644 --- a/acl_loader/main.py +++ b/acl_loader/main.py @@ -4,15 +4,13 @@ import ipaddr import json import syslog -import tabulate -from natsort import natsorted -import sonic_device_util import openconfig_acl +import tabulate import pyangbind.lib.pybindJSON as pybindJSON -from swsssdk import ConfigDBConnector -from swsssdk import SonicV2Connector -from swsssdk import SonicDBConfig +from natsort import natsorted +from sonic_py_common import device_info +from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig def info(msg): @@ -142,7 +140,7 @@ def __init__(self): # Getting all front asic namespace and correspding config and state DB connector - namespaces = sonic_device_util.get_all_namespaces() + namespaces = device_info.get_all_namespaces() for front_asic_namespaces in namespaces['front_ns']: self.per_npu_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces) self.per_npu_configdb[front_asic_namespaces].connect() diff --git a/config/main.py b/config/main.py index 6bfd4da9b6..43f07a0bd2 100755 --- a/config/main.py +++ b/config/main.py @@ -1,30 +1,28 @@ #!/usr/sbin/env python -import sys -import os import click -import subprocess +import ipaddress +import json import netaddr -import re -import syslog -import time import netifaces +import os +import re +import subprocess +import sys import threading -import json +import time -import sonic_device_util -import ipaddress -from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig from minigraph import parse_device_desc_xml -from config_mgmt import ConfigMgmtDPB -from utilities_common.intf_filter import parse_interface_in_filter -from utilities_common.util_base import UtilHelper -from utilities_common.db import Db from portconfig import get_child_ports, get_port_config_file_name +from sonic_py_common import device_info, logger +from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig +from utilities_common.db import Db +from utilities_common.intf_filter import parse_interface_in_filter import aaa import mlnx import nat +from config_mgmt import ConfigMgmtDPB CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) @@ -50,32 +48,11 @@ CFG_LOOPBACK_ID_MAX_VAL = 999 CFG_LOOPBACK_NO="<0-999>" -asic_type = None - -# ========================== Syslog wrappers ========================== - -def log_debug(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_DEBUG, msg) - syslog.closelog() +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) -def log_info(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() - - -def log_warning(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_WARNING, msg) - syslog.closelog() - - -def log_error(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() +asic_type = None class AbbreviationGroup(click.Group): @@ -116,14 +93,13 @@ def get_command(self, ctx, cmd_name): ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + # # Load breakout config file for Dynamic Port Breakout # try: - # Load the helper class - helper = UtilHelper() - (platform, hwsku) = helper.get_platform_and_hwsku() + (platform, hwsku) = device_info.get_platform_and_hwsku() except Exception as e: click.secho("Failed to get platform and hwsku with error:{}".format(str(e)), fg='red') raise click.Abort() @@ -279,17 +255,17 @@ def execute_systemctl_per_asic_instance(inst, event, service, action): click.echo("Executing {} of service {}@{}...".format(action, service, inst)) run_command("systemctl {} {}@{}.service".format(action, service, inst)) except SystemExit as e: - log_error("Failed to execute {} of service {}@{} with error {}".format(action, service, inst, e)) + log.log_error("Failed to execute {} of service {}@{} with error {}".format(action, service, inst, e)) # Set the event object if there is a failure and exception was raised. event.set() # Execute action on list of systemd services def execute_systemctl(list_of_services, action): - num_asic = sonic_device_util.get_num_npus() + num_asic = device_info.get_num_npus() generated_services_list, generated_multi_instance_services = _get_sonic_generated_services(num_asic) if ((generated_services_list == []) and (generated_multi_instance_services == [])): - log_error("Failed to get generated services") + log.log_error("Failed to get generated services") return for service in list_of_services: @@ -298,12 +274,12 @@ def execute_systemctl(list_of_services, action): click.echo("Executing {} of service {}...".format(action, service)) run_command("systemctl {} {}".format(action, service)) except SystemExit as e: - log_error("Failed to execute {} of service {} with error {}".format(action, service, e)) + log.log_error("Failed to execute {} of service {} with error {}".format(action, service, e)) raise if (service + '.service' in generated_multi_instance_services): # With Multi NPU, Start a thread per instance to do the "action" on multi instance services. - if sonic_device_util.is_multi_npu(): + if device_info.is_multi_npu(): threads = [] # Use this event object to co-ordinate if any threads raised exception e = threading.Event() @@ -357,10 +333,10 @@ def _get_device_type(): # Validate whether a given namespace name is valid in the device. def validate_namespace(namespace): - if not sonic_device_util.is_multi_npu(): + if not device_info.is_multi_npu(): return True - namespaces = sonic_device_util.get_all_namespaces() + namespaces = device_info.get_all_namespaces() if namespace in namespaces['front_ns'] + namespaces['back_ns']: return True else: @@ -665,8 +641,8 @@ def _clear_qos(): 'BUFFER_QUEUE'] namespace_list = [DEFAULT_NAMESPACE] - if sonic_device_util.get_num_npus() > 1: - namespace_list = sonic_device_util.get_namespaces() + if device_info.get_num_npus() > 1: + namespace_list = device_info.get_namespaces() for ns in namespace_list: if ns is DEFAULT_NAMESPACE: @@ -709,18 +685,18 @@ def _get_disabled_services_list(config_db): if feature_table is not None: for feature_name in feature_table.keys(): if not feature_name: - log_warning("Feature is None") + log.log_warning("Feature is None") continue state = feature_table[feature_name]['state'] if not state: - log_warning("Enable state of feature '{}' is None".format(feature_name)) + log.log_warning("Enable state of feature '{}' is None".format(feature_name)) continue if state == "disabled": disabled_services_list.append(feature_name) else: - log_warning("Unable to retreive FEATURE table") + log.log_warning("Unable to retreive FEATURE table") return disabled_services_list @@ -928,7 +904,7 @@ def config(ctx): global asic_type try: - version_info = sonic_device_util.get_sonic_version_info() + version_info = device_info.get_sonic_version_info() asic_type = version_info['asic_type'] except KeyError, TypeError: raise click.Abort() @@ -961,11 +937,11 @@ def save(filename): """Export current config DB to a file on disk.\n : Names of configuration file(s) to save, separated by comma with no spaces in between """ - num_asic = sonic_device_util.get_num_npus() + num_asic = device_info.get_num_npus() cfg_files = [] num_cfg_file = 1 - if sonic_device_util.is_multi_npu(): + if device_info.is_multi_npu(): num_cfg_file += num_asic # If the user give the filename[s], extract the file names. @@ -1000,7 +976,7 @@ def save(filename): else: command = "{} -n {} -d --print-data > {}".format(SONIC_CFGGEN_PATH, namespace, file) - log_info("'save' executing...") + log.log_info("'save' executing...") run_command(command, display_cmd=True) @config.command() @@ -1018,11 +994,11 @@ def load(filename, yes): if not yes: click.confirm(message, abort=True) - num_asic = sonic_device_util.get_num_npus() + num_asic = device_info.get_num_npus() cfg_files = [] num_cfg_file = 1 - if sonic_device_util.is_multi_npu(): + if device_info.is_multi_npu(): num_cfg_file += num_asic # If the user give the filename[s], extract the file names. @@ -1062,7 +1038,7 @@ def load(filename, yes): else: command = "{} -n {} -j {} --write-to-db".format(SONIC_CFGGEN_PATH, namespace, file) - log_info("'load' executing...") + log.log_info("'load' executing...") run_command(command, display_cmd=True) @@ -1084,13 +1060,13 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart): if not yes: click.confirm(message, abort=True) - log_info("'reload' executing...") + log.log_info("'reload' executing...") - num_asic = sonic_device_util.get_num_npus() + num_asic = device_info.get_num_npus() cfg_files = [] num_cfg_file = 1 - if sonic_device_util.is_multi_npu(): + if device_info.is_multi_npu(): num_cfg_file += num_asic # If the user give the filename[s], extract the file names. @@ -1113,7 +1089,7 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart): #Stop services before config push if not no_service_restart: - log_info("'reload' stopping services...") + log.log_info("'reload' stopping services...") _stop_services(db.cfgdb) """ In Single AISC platforms we have single DB service. In multi-ASIC platforms we have a global DB @@ -1188,7 +1164,7 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart): # status from all services before we attempt to restart them if not no_service_restart: _reset_failed_services(db.cfgdb) - log_info("'reload' restarting services...") + log.log_info("'reload' restarting services...") _restart_services(db.cfgdb) @config.command("load_mgmt_config") @@ -1197,7 +1173,7 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart): @click.argument('filename', default='/etc/sonic/device_desc.xml', type=click.Path(exists=True)) def load_mgmt_config(filename): """Reconfigure hostname and mgmt interface based on device description file.""" - log_info("'load_mgmt_config' executing...") + log.log_info("'load_mgmt_config' executing...") command = "{} -M {} --write-to-db".format(SONIC_CFGGEN_PATH, filename) run_command(command, display_cmd=True) #FIXME: After config DB daemon for hostname and mgmt interface is implemented, we'll no longer need to do manual configuration here @@ -1223,20 +1199,20 @@ def load_mgmt_config(filename): @pass_db def load_minigraph(db, no_service_restart): """Reconfigure based on minigraph.""" - log_info("'load_minigraph' executing...") + log.log_info("'load_minigraph' executing...") #Stop services before config push if not no_service_restart: - log_info("'load_minigraph' stopping services...") + log.log_info("'load_minigraph' stopping services...") _stop_services(db.cfgdb) # For Single Asic platform the namespace list has the empty string # for mulit Asic platform the empty string to generate the config # for host namespace_list = [DEFAULT_NAMESPACE] - num_npus = sonic_device_util.get_num_npus() + num_npus = device_info.get_num_npus() if num_npus > 1: - namespace_list += sonic_device_util.get_namespaces() + namespace_list += device_info.get_namespaces() for namespace in namespace_list: if namespace is DEFAULT_NAMESPACE: @@ -1286,7 +1262,7 @@ def load_minigraph(db, no_service_restart): if not no_service_restart: _reset_failed_services(db.cfgdb) #FIXME: After config DB daemon is implemented, we'll no longer need to restart every service. - log_info("'load_minigraph' restarting services...") + log.log_info("'load_minigraph' restarting services...") _restart_services(db.cfgdb) click.echo("Please note setting loaded from minigraph will be lost after system reboot. To preserve setting, run `config save`.") @@ -1460,7 +1436,7 @@ def add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer """ For multi-npu platforms we need to program all front asic namespaces """ - namespaces = sonic_device_util.get_all_namespaces() + namespaces = device_info.get_all_namespaces() if not namespaces['front_ns']: config_db = ConfigDBConnector() config_db.connect() @@ -1510,7 +1486,7 @@ def add_span(session_name, dst_port, src_port, direction, queue, policer): """ For multi-npu platforms we need to program all front asic namespaces """ - namespaces = sonic_device_util.get_all_namespaces() + namespaces = device_info.get_all_namespaces() if not namespaces['front_ns']: config_db = ConfigDBConnector() config_db.connect() @@ -1535,7 +1511,7 @@ def remove(session_name): """ For multi-npu platforms we need to program all front asic namespaces """ - namespaces = sonic_device_util.get_all_namespaces() + namespaces = device_info.get_all_namespaces() if not namespaces['front_ns']: config_db = ConfigDBConnector() config_db.connect() @@ -1645,25 +1621,26 @@ def qos(ctx): @qos.command('clear') def clear(): """Clear QoS configuration""" - log_info("'qos clear' executing...") + log.log_info("'qos clear' executing...") _clear_qos() @qos.command('reload') def reload(): """Reload QoS configuration""" - log_info("'qos reload' executing...") + log.log_info("'qos reload' executing...") _clear_qos() - platform = sonic_device_util.get_platform() - hwsku = sonic_device_util.get_hwsku() + + _, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs() + namespace_list = [DEFAULT_NAMESPACE] - if sonic_device_util.get_num_npus() > 1: - namespace_list = sonic_device_util.get_namespaces() + if device_info.get_num_npus() > 1: + namespace_list = device_info.get_namespaces() for ns in namespace_list: if ns is DEFAULT_NAMESPACE: asic_id_suffix = "" else: - asic_id = sonic_device_util.get_npu_id_from_name(ns) + asic_id = device_info.get_npu_id_from_name(ns) if asic_id is None: click.secho( "Command 'qos reload' failed with invalid namespace '{}'". @@ -1674,9 +1651,7 @@ def reload(): asic_id_suffix = str(asic_id) buffer_template_file = os.path.join( - '/usr/share/sonic/device/', - platform, - hwsku, + hwsku_path, asic_id_suffix, 'buffers.json.j2' ) @@ -1693,9 +1668,7 @@ def reload(): ) run_command(command, display_cmd=True) qos_template_file = os.path.join( - '/usr/share/sonic/device/', - platform, - hwsku, + hwsku_path, asic_id_suffix, 'qos.json.j2' ) @@ -1838,7 +1811,7 @@ def add_vlan(ctx, vid): @click.pass_context def del_vlan(ctx, vid): """Delete VLAN""" - log_info("'vlan del {}' executing...".format(vid)) + log.log_info("'vlan del {}' executing...".format(vid)) db = ctx.obj['db'] keys = [ (k, v) for k, v in db.get_table('VLAN_MEMBER') if k == 'Vlan{}'.format(vid) ] for k in keys: @@ -1862,7 +1835,7 @@ def vlan_member(ctx): @click.pass_context def add_vlan_member(ctx, vid, interface_name, untagged): """Add VLAN member""" - log_info("'vlan member add {} {}' executing...".format(vid, interface_name)) + log.log_info("'vlan member add {} {}' executing...".format(vid, interface_name)) db = ctx.obj['db'] vlan_name = 'Vlan{}'.format(vid) vlan = db.get_entry('VLAN', vlan_name) @@ -1905,7 +1878,7 @@ def add_vlan_member(ctx, vid, interface_name, untagged): @click.pass_context def del_vlan_member(ctx, vid, interface_name): """Delete VLAN member""" - log_info("'vlan member del {} {}' executing...".format(vid, interface_name)) + log.log_info("'vlan member del {} {}' executing...".format(vid, interface_name)) db = ctx.obj['db'] vlan_name = 'Vlan{}'.format(vid) vlan = db.get_entry('VLAN', vlan_name) @@ -2201,12 +2174,12 @@ def all(verbose): """Shut down all BGP sessions In the case of Multi-Asic platform, we shut only the EBGP sessions with external neighbors. """ - log_info("'bgp shutdown all' executing...") + log.log_info("'bgp shutdown all' executing...") namespaces = [DEFAULT_NAMESPACE] ignore_local_hosts = False - if sonic_device_util.is_multi_npu(): - ns_list = sonic_device_util.get_all_namespaces() + if device_info.is_multi_npu(): + ns_list = device_info.get_all_namespaces() namespaces = ns_list['front_ns'] ignore_local_hosts = True @@ -2227,12 +2200,12 @@ def neighbor(ipaddr_or_hostname, verbose): """Shut down BGP session by neighbor IP address or hostname. User can specify either internal or external BGP neighbor to shutdown """ - log_info("'bgp shutdown neighbor {}' executing...".format(ipaddr_or_hostname)) + log.log_info("'bgp shutdown neighbor {}' executing...".format(ipaddr_or_hostname)) namespaces = [DEFAULT_NAMESPACE] found_neighbor = False - if sonic_device_util.is_multi_npu(): - ns_list = sonic_device_util.get_all_namespaces() + if device_info.is_multi_npu(): + ns_list = device_info.get_all_namespaces() namespaces = ns_list['front_ns'] + ns_list['back_ns'] # Connect to CONFIG_DB in linux host (in case of single ASIC) or CONFIG_DB in all the @@ -2258,12 +2231,12 @@ def all(verbose): """Start up all BGP sessions In the case of Multi-Asic platform, we startup only the EBGP sessions with external neighbors. """ - log_info("'bgp startup all' executing...") + log.log_info("'bgp startup all' executing...") namespaces = [DEFAULT_NAMESPACE] ignore_local_hosts = False - if sonic_device_util.is_multi_npu(): - ns_list = sonic_device_util.get_all_namespaces() + if device_info.is_multi_npu(): + ns_list = device_info.get_all_namespaces() namespaces = ns_list['front_ns'] ignore_local_hosts = True @@ -2281,15 +2254,15 @@ def all(verbose): @click.argument('ipaddr_or_hostname', metavar='', required=True) @click.option('-v', '--verbose', is_flag=True, help="Enable verbose output") def neighbor(ipaddr_or_hostname, verbose): - log_info("'bgp startup neighbor {}' executing...".format(ipaddr_or_hostname)) + log.log_info("'bgp startup neighbor {}' executing...".format(ipaddr_or_hostname)) """Start up BGP session by neighbor IP address or hostname. User can specify either internal or external BGP neighbor to startup """ namespaces = [DEFAULT_NAMESPACE] found_neighbor = False - if sonic_device_util.is_multi_npu(): - ns_list = sonic_device_util.get_all_namespaces() + if device_info.is_multi_npu(): + ns_list = device_info.get_all_namespaces() namespaces = ns_list['front_ns'] + ns_list['back_ns'] # Connect to CONFIG_DB in linux host (in case of single ASIC) or CONFIG_DB in all the @@ -2321,8 +2294,8 @@ def remove_neighbor(neighbor_ip_or_hostname): namespaces = [DEFAULT_NAMESPACE] removed_neighbor = False - if sonic_device_util.is_multi_npu(): - ns_list = sonic_device_util.get_all_namespaces() + if device_info.is_multi_npu(): + ns_list = device_info.get_all_namespaces() namespaces = ns_list['front_ns'] + ns_list['back_ns'] # Connect to CONFIG_DB in linux host (in case of single ASIC) or CONFIG_DB in all the @@ -2369,7 +2342,7 @@ def startup(ctx, interface_name): if len(intf_fs) == 1 and interface_name_is_valid(interface_name) is False: ctx.fail("Interface name is invalid. Please enter a valid interface name!!") - log_info("'interface startup {}' executing...".format(interface_name)) + log.log_info("'interface startup {}' executing...".format(interface_name)) port_dict = config_db.get_table('PORT') for port_name in port_dict.keys(): if port_name in intf_fs: @@ -2394,7 +2367,7 @@ def startup(ctx, interface_name): @click.pass_context def shutdown(ctx, interface_name): """Shut down interface""" - log_info("'interface shutdown {}' executing...".format(interface_name)) + log.log_info("'interface shutdown {}' executing...".format(interface_name)) config_db = ctx.obj['config_db'] if get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) @@ -2436,7 +2409,7 @@ def speed(ctx, interface_name, interface_speed, verbose): if interface_name is None: ctx.fail("'interface_name' is None!") - log_info("'interface speed {} {}' executing...".format(interface_name, interface_speed)) + log.log_info("'interface speed {} {}' executing...".format(interface_name, interface_speed)) command = "portconfig -p {} -s {}".format(interface_name, interface_speed) if verbose: @@ -3131,7 +3104,7 @@ def update(): @click.argument('file_name', required=True) def full(file_name): """Full update of ACL rules configuration.""" - log_info("'acl update full {}' executing...".format(file_name)) + log.log_info("'acl update full {}' executing...".format(file_name)) command = "acl-loader update full {}".format(file_name) run_command(command) @@ -3144,7 +3117,7 @@ def full(file_name): @click.argument('file_name', required=True) def incremental(file_name): """Incremental update of ACL rule configuration.""" - log_info("'acl update incremental {}' executing...".format(file_name)) + log.log_info("'acl update incremental {}' executing...".format(file_name)) command = "acl-loader update incremental {}".format(file_name) run_command(command) @@ -3235,7 +3208,7 @@ def remove_reasons(counter_name, reasons, verbose): @click.option('-v', '--verbose', is_flag=True, help="Enable verbose output") def ecn(profile, rmax, rmin, ymax, ymin, gmax, gmin, verbose): """ECN-related configuration tasks""" - log_info("'ecn -profile {}' executing...".format(profile)) + log.log_info("'ecn -profile {}' executing...".format(profile)) command = "ecnconfig -p %s" % profile if rmax is not None: command += " -rmax %d" % rmax if rmin is not None: command += " -rmin %d" % rmin @@ -3622,7 +3595,7 @@ def enable(ctx): ctx.fail("Unable to check sflow status {}".format(e)) if out != "active": - log_info("sflow service is not enabled. Starting sflow docker...") + log.log_info("sflow service is not enabled. Starting sflow docker...") run_command("sudo systemctl enable sflow") run_command("sudo systemctl start sflow") diff --git a/config/mlnx.py b/config/mlnx.py index 9d7810a6a9..54775312ff 100644 --- a/config/mlnx.py +++ b/config/mlnx.py @@ -6,12 +6,13 @@ # try: - import sys import os import subprocess - import click - import syslog + import sys import time + + import click + from sonic_py_common import logger except ImportError as e: raise ImportError("%s - required module not found" % str(e)) @@ -36,32 +37,9 @@ # Command to restart swss service COMMAND_RESTART_SWSS = 'systemctl restart swss.service' -# ========================== Syslog wrappers ========================== -def log_info(msg, syslog_identifier, also_print_to_console=False): - syslog.openlog(syslog_identifier) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() - - if also_print_to_console: - print msg - - -def log_warning(msg, syslog_identifier, also_print_to_console=False): - syslog.openlog(syslog_identifier) - syslog.syslog(syslog.LOG_WARNING, msg) - syslog.closelog() - - if also_print_to_console: - print msg - - -def log_error(msg, syslog_identifier, also_print_to_console=False): - syslog.openlog(syslog_identifier) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() - if also_print_to_console: - print msg +# Global logger instance +log = logger.Logger(SNIFFER_SYSLOG_IDENTIFIER) # run command @@ -166,7 +144,7 @@ def restart_swss(): try: run_command(COMMAND_RESTART_SWSS) except OSError as e: - log_error("Not able to restart swss service, %s" % str(e), SNIFFER_SYSLOG_IDENTIFIER, True) + log.log_error("Not able to restart swss service, %s" % str(e), True) return 1 return 0 diff --git a/fwutil/lib.py b/fwutil/lib.py index eff432c630..71f4d1ae85 100755 --- a/fwutil/lib.py +++ b/fwutil/lib.py @@ -7,19 +7,19 @@ try: import os - import time import json import socket - import urllib import subprocess + import time + import urllib + from collections import OrderedDict import click - import sonic_device_util - - from collections import OrderedDict - from urlparse import urlparse - from tabulate import tabulate from log import LogHelper + from sonic_py_common import device_info + from tabulate import tabulate + from urlparse import urlparse + from . import Platform except ImportError as e: raise ImportError("Required module not found: {}".format(str(e))) @@ -296,15 +296,10 @@ def __init__(self, is_modular_chassis): self.__chassis_component_map = OrderedDict() self.__module_component_map = OrderedDict() - def __get_platform_type(self): - return sonic_device_util.get_platform_info( - sonic_device_util.get_machine_info() - ) - def __get_platform_components_path(self, root_path): return self.PLATFORM_COMPONENTS_PATH_TEMPLATE.format( root_path, - self.__get_platform_type(), + device_info.get_platform(), self.PLATFORM_COMPONENTS_FILE ) diff --git a/fwutil/log.py b/fwutil/log.py index 69d60a28f5..95973b0ddc 100755 --- a/fwutil/log.py +++ b/fwutil/log.py @@ -6,8 +6,8 @@ # try: - import syslog import click + from sonic_py_common import logger except ImportError as e: raise ImportError("Required module not found: {}".format(str(e))) @@ -15,42 +15,11 @@ SYSLOG_IDENTIFIER = "fwutil" -# ========================= Helper classes ===================================== - -class SyslogLogger(object): - """ - SyslogLogger - """ - def __init__(self, identifier): - self.__syslog = syslog - - self.__syslog.openlog( - ident=identifier, - logoption=self.__syslog.LOG_NDELAY, - facility=self.__syslog.LOG_USER - ) - - def __del__(self): - self.__syslog.closelog() +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) - def log_error(self, msg): - self.__syslog.syslog(self.__syslog.LOG_ERR, msg) - - def log_warning(self, msg): - self.__syslog.syslog(self.__syslog.LOG_WARNING, msg) - - def log_notice(self, msg): - self.__syslog.syslog(self.__syslog.LOG_NOTICE, msg) - - def log_info(self, msg): - self.__syslog.syslog(self.__syslog.LOG_INFO, msg) - - def log_debug(self, msg): - self.__syslog.syslog(self.__syslog.LOG_DEBUG, msg) - - -logger = SyslogLogger(SYSLOG_IDENTIFIER) +# ========================= Helper classes ===================================== class LogHelper(object): """ @@ -67,7 +36,7 @@ def __log_fw_action_start(self, action, component, firmware): caption = "Firmware {} started".format(action) template = "{}: component={}, firmware={}" - logger.log_info( + log.log_info( template.format( caption, component, @@ -82,7 +51,7 @@ def __log_fw_action_end(self, action, component, firmware, status, exception=Non exception_template = "{}: component={}, firmware={}, status={}, exception={}" if status: - logger.log_info( + log.log_info( status_template.format( caption, component, @@ -92,7 +61,7 @@ def __log_fw_action_end(self, action, component, firmware, status, exception=Non ) else: if exception is None: - logger.log_error( + log.log_error( status_template.format( caption, component, @@ -101,7 +70,7 @@ def __log_fw_action_end(self, action, component, firmware, status, exception=Non ) ) else: - logger.log_error( + log.log_error( exception_template.format( caption, component, diff --git a/pcieutil/main.py b/pcieutil/main.py index 8fddce7979..5eedd678f1 100644 --- a/pcieutil/main.py +++ b/pcieutil/main.py @@ -6,14 +6,11 @@ # try: - import sys import os - import subprocess + import sys + import click - import imp - import syslog - import types - import traceback + from sonic_py_common import device_info, logger from tabulate import tabulate except ImportError as e: raise ImportError("%s - required module not found" % str(e)) @@ -23,100 +20,43 @@ SYSLOG_IDENTIFIER = "pcieutil" PLATFORM_SPECIFIC_MODULE_NAME = "pcieutil" -PLATFORM_ROOT_PATH = '/usr/share/sonic/device' -PLATFORM_ROOT_PATH_DOCKER = '/usr/share/sonic/platform' -SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' -HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku' -PLATFORM_KEY = 'DEVICE_METADATA.localhost.platform' - -#from pcieutil import PcieUtil - # Global platform-specific psuutil class instance platform_pcieutil = None -hwsku_path = None - -# ========================== Syslog wrappers ========================== - - -def log_info(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) - - -def log_warning(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_WARNING, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) +platform_plugins_path = None +log = logger.Logger(SYSLOG_IDENTIFIER) -def log_error(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() - if also_print_to_console: - click.echo(msg) - -def log_out(name, result): +def print_result(name, result): string = "PCI Device: {} ".format(name) length = 105-len(string) - sys.stdout.write(string) + sys.stdout.write(string) for i in xrange(int(length)): sys.stdout.write("-") - print ' [%s]' % result - + print(' [%s]' % result) + # ==================== Methods for initialization ==================== -# Returns platform and HW SKU -def get_platform_and_hwsku(): - try: - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - platform = stdout.rstrip('\n') - - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-d', '-v', HWSKU_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - hwsku = stdout.rstrip('\n') - except OSError, e: - raise OSError("Cannot detect platform") - - return (platform, hwsku) +# Loads platform specific psuutil module from source -# Loads platform specific psuutil module from source def load_platform_pcieutil(): global platform_pcieutil - global hwsku_plugins_path - # Get platform and hwsku - (platform, hwsku) = get_platform_and_hwsku() + global platform_plugins_path # Load platform module from source try: - hwsku_plugins_path = "/".join([PLATFORM_ROOT_PATH, platform, "plugins"]) - sys.path.append(os.path.abspath(hwsku_plugins_path)) + platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs() + platform_plugins_path = os.path.join(platform_path, "plugins") + sys.path.append(os.path.abspath(platform_plugins_path)) from pcieutil import PcieUtil except ImportError as e: - log_warning("Fail to load specific PcieUtil moudle. Falling down to the common implementation") + log.log_warning("Failed to load platform-specific PcieUtil module. Falling back to the common implementation") try: from sonic_platform_base.sonic_pcie.pcie_common import PcieUtil - platform_pcieutil = PcieUtil(hwsku_plugins_path) + platform_pcieutil = PcieUtil(platform_plugins_path) except ImportError as e: - log_error("Fail to load default PcieUtil moudle. Error :{}".format(str(e)), True) + log.log_error("Failed to load default PcieUtil module. Error : {}".format(str(e)), True) raise e @@ -135,18 +75,22 @@ def cli(): load_platform_pcieutil() # 'version' subcommand + + @cli.command() def version(): """Display version info""" click.echo("pcieutil version {0}".format(VERSION)) -#show the platform PCIE info +# show the platform PCIE info def print_test_title(testname): click.echo("{name:=^80s}".format(name=testname)) # Show PCIE lnkSpeed + + @cli.command() def pcie_show(): '''Display PCIe Device ''' @@ -159,10 +103,7 @@ def pcie_show(): Fn = item["fn"] Name = item["name"] Id = item["id"] - print "bus:dev.fn %s:%s.%s - dev_id=0x%s, %s" % (Bus,Dev,Fn,Id,Name) - - - + print "bus:dev.fn %s:%s.%s - dev_id=0x%s, %s" % (Bus, Dev, Fn, Id, Name) # Show PCIE Vender ID and Device ID @@ -175,17 +116,15 @@ def pcie_check(): resultInfo = platform_pcieutil.get_pcie_check() for item in resultInfo: if item["result"] == "Passed": - log_out(item["name"], "Passed") + print_result(item["name"], "Passed") else: - log_out(item["name"], "Failed") - log_warning("PCIe Device: " + item["name"] + " Not Found") - err+=1 + print_result(item["name"], "Failed") + log.log_warning("PCIe Device: " + item["name"] + " Not Found") + err += 1 if err: print "PCIe Device Checking All Test ----------->>> FAILED" else: print "PCIe Device Checking All Test ----------->>> PASSED" - - @cli.command() @@ -193,7 +132,8 @@ def pcie_check(): def pcie_generate(): '''Generate config file with current pci device''' platform_pcieutil.dump_conf_yaml() - print "Generate config file pcie.yaml under path %s" %hwsku_plugins_path + print "Generate config file pcie.yaml under path %s" % platform_plugins_path + if __name__ == '__main__': cli() diff --git a/pddf_fanutil/main.py b/pddf_fanutil/main.py index 04ca4ea66a..1aa90b9424 100644 --- a/pddf_fanutil/main.py +++ b/pddf_fanutil/main.py @@ -24,7 +24,6 @@ platform_fanutil = None platform_chassis = None -#logger = UtilLogger(SYSLOG_IDENTIFIER) def _wrapper_get_num_fans(): if platform_chassis is not None: @@ -98,9 +97,6 @@ def _wrapper_dump_sysfs(idx): return platform_fanutil.dump_sysfs() - - - # This is our main entrypoint - the main 'fanutil' command @click.group() def cli(): diff --git a/pddf_ledutil/main.py b/pddf_ledutil/main.py index 5f8e74c55f..6ff08ffa97 100644 --- a/pddf_ledutil/main.py +++ b/pddf_ledutil/main.py @@ -40,9 +40,6 @@ def _wrapper_setstatusled(device_name, color, color_state): click.echo(outputs) - -#logger = UtilLogger(SYSLOG_IDENTIFIER) - # ==================== CLI commands and groups ==================== diff --git a/pddf_psuutil/main.py b/pddf_psuutil/main.py index 54bd0ccc52..f916883828 100644 --- a/pddf_psuutil/main.py +++ b/pddf_psuutil/main.py @@ -24,7 +24,6 @@ platform_psuutil = None platform_chassis = None -#logger = UtilLogger(SYSLOG_IDENTIFIER) # Wrapper APIs so that this util is suited to both 1.0 and 2.0 platform APIs def _wrapper_get_num_psus(): diff --git a/pddf_thermalutil/main.py b/pddf_thermalutil/main.py index 11b2aea1ff..1be6381eec 100644 --- a/pddf_thermalutil/main.py +++ b/pddf_thermalutil/main.py @@ -24,8 +24,6 @@ platform_thermalutil = None platform_chassis = None -#logger = UtilLogger(SYSLOG_IDENTIFIER) - # Wrapper APIs so that this util is suited to both 1.0 and 2.0 platform APIs def _wrapper_get_num_thermals(): if platform_chassis is not None: diff --git a/psuutil/main.py b/psuutil/main.py index 180cbecfa7..efea550962 100644 --- a/psuutil/main.py +++ b/psuutil/main.py @@ -6,12 +6,12 @@ # try: - import sys + import imp import os - import subprocess + import sys + import click - import imp - import syslog + from sonic_py_common import device_info, logger from tabulate import tabulate except ImportError as e: raise ImportError("%s - required module not found" % str(e)) @@ -22,98 +22,35 @@ PLATFORM_SPECIFIC_MODULE_NAME = "psuutil" PLATFORM_SPECIFIC_CLASS_NAME = "PsuUtil" -PLATFORM_ROOT_PATH = '/usr/share/sonic/device' -PLATFORM_ROOT_PATH_DOCKER = '/usr/share/sonic/platform' -SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' -HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku' -PLATFORM_KEY = 'DEVICE_METADATA.localhost.platform' - # Global platform-specific psuutil class instance platform_psuutil = None -# ========================== Syslog wrappers ========================== - - -def log_info(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) - - -def log_warning(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_WARNING, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) - - -def log_error(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) # ==================== Methods for initialization ==================== -# Returns platform and HW SKU -def get_platform_and_hwsku(): - try: - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - platform = stdout.rstrip('\n') - - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-d', '-v', HWSKU_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - hwsku = stdout.rstrip('\n') - except OSError as e: - raise OSError("Cannot detect platform") - - return (platform, hwsku) - - # Loads platform specific psuutil module from source def load_platform_psuutil(): global platform_psuutil - # Get platform and hwsku - (platform, hwsku) = get_platform_and_hwsku() - # Load platform module from source - platform_path = '' - if len(platform) != 0: - platform_path = "/".join([PLATFORM_ROOT_PATH, platform]) - else: - platform_path = PLATFORM_ROOT_PATH_DOCKER + platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs() try: - module_file = "/".join([platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py"]) + module_file = os.path.join(platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py") module = imp.load_source(PLATFORM_SPECIFIC_MODULE_NAME, module_file) except IOError as e: - log_error("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True) + log.log_error("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True) return -1 try: platform_psuutil_class = getattr(module, PLATFORM_SPECIFIC_CLASS_NAME) platform_psuutil = platform_psuutil_class() except AttributeError as e: - log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True) + log.log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True) return -2 return 0 @@ -137,18 +74,24 @@ def cli(): sys.exit(2) # 'version' subcommand + + @cli.command() def version(): """Display version info""" click.echo("psuutil version {0}".format(VERSION)) # 'numpsus' subcommand + + @cli.command() def numpsus(): """Display number of supported PSUs on device""" click.echo(str(platform_psuutil.get_num_psus())) # 'status' subcommand + + @cli.command() @click.option('-i', '--index', default=-1, type=int, help="the index of PSU") def status(index): @@ -167,8 +110,8 @@ def status(index): msg = "" psu_name = "PSU {}".format(psu) if psu not in supported_psu: - click.echo("Error! The {} is not available on the platform.\n" \ - "Number of supported PSU - {}.".format(psu_name, platform_psuutil.get_num_psus())) + click.echo("Error! The {} is not available on the platform.\n" + "Number of supported PSU - {}.".format(psu_name, platform_psuutil.get_num_psus())) continue presence = platform_psuutil.get_psu_presence(psu) if presence: @@ -181,5 +124,6 @@ def status(index): if status_table: click.echo(tabulate(status_table, header, tablefmt="simple")) + if __name__ == '__main__': cli() diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index 17ec3ea6e6..82c9982516 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -1,27 +1,18 @@ #!/usr/bin/env python -import traceback -import sys import argparse -import syslog -from swsssdk import ConfigDBConnector, SonicDBConfig -from swsssdk import SonicV2Connector -import sonic_device_util - +import sys +import traceback -SYSLOG_IDENTIFIER = 'db_migrator' +from sonic_py_common import device_info, logger +from swsssdk import ConfigDBConnector, SonicDBConfig, SonicV2Connector -def log_info(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() +SYSLOG_IDENTIFIER = 'db_migrator' -def log_error(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) class DBMigrator(): @@ -100,7 +91,7 @@ def migrate_interface_table(self): for key in data.keys(): if not self.is_ip_prefix_in_key(key) or key[0] in if_db: continue - log_info('Migrating interface table for ' + key[0]) + log.log_info('Migrating interface table for ' + key[0]) self.configDB.set_entry(table, key[0], data[key]) if_db.append(key[0]) @@ -126,7 +117,7 @@ def migrate_intf_table(self): if if_name == "lo": self.appDB.delete(self.appDB.APPL_DB, key) key = key.replace(if_name, "Loopback0") - log_info('Migrating lo entry to ' + key) + log.log_info('Migrating lo entry to ' + key) self.appDB.set(self.appDB.APPL_DB, key, 'NULL', 'NULL') if '/' not in key: @@ -138,7 +129,7 @@ def migrate_intf_table(self): if_name = key.split(":")[1] if if_name in if_db: continue - log_info('Migrating intf table for ' + if_name) + log.log_info('Migrating intf table for ' + if_name) table = "INTF_TABLE:" + if_name self.appDB.set(self.appDB.APPL_DB, table, 'NULL', 'NULL') if_db.append(if_name) @@ -193,7 +184,7 @@ def mlnx_migrate_buffer_pool_size(self): hwsku = device_data['localhost']['hwsku'] platform = device_data['localhost']['platform'] else: - log_error("Trying to get DEVICE_METADATA from DB but doesn't exist, skip migration") + log.log_error("Trying to get DEVICE_METADATA from DB but doesn't exist, skip migration") return False buffer_pool_conf = self.configDB.get_table('BUFFER_POOL') @@ -230,12 +221,12 @@ def mlnx_migrate_buffer_pool_size(self): new_buffer_pool_conf = spc2_t1_default_config else: # It's not using default buffer pool configuration, no migration needed. - log_info("buffer pool size is not old default value, no need to migrate") + log.log_info("buffer pool size is not old default value, no need to migrate") return True # Migrate old buffer conf to latest. for pool in buffer_pools: self.configDB.set_entry('BUFFER_POOL', pool, new_buffer_pool_conf[pool]) - log_info("Successfully migrate mlnx buffer pool size to the latest.") + log.log_info("Successfully migrate mlnx buffer pool size to the latest.") return True def version_unknown(self): @@ -248,7 +239,7 @@ def version_unknown(self): before migrating date to the next version. """ - log_info('Handling version_unknown') + log.log_info('Handling version_unknown') # NOTE: Uncomment next 3 lines of code when the migration code is in # place. Note that returning specific string is intentional, @@ -265,7 +256,7 @@ def version_1_0_1(self): """ Version 1_0_1. """ - log_info('Handling version_1_0_1') + log.log_info('Handling version_1_0_1') self.migrate_interface_table() self.migrate_intf_table() @@ -276,9 +267,9 @@ def version_1_0_2(self): """ Version 1_0_2. """ - log_info('Handling version_1_0_2') + log.log_info('Handling version_1_0_2') # Check ASIC type, if Mellanox platform then need DB migration - version_info = sonic_device_util.get_sonic_version_info() + version_info = device_info.get_sonic_version_info() if version_info['asic_type'] == "mellanox": if self.mlnx_migrate_buffer_pool_size(): self.set_version('version_1_0_3') @@ -290,7 +281,7 @@ def version_1_0_3(self): """ Current latest version. Nothing to do here. """ - log_info('Handling version_1_0_3') + log.log_info('Handling version_1_0_3') return None @@ -305,14 +296,14 @@ def get_version(self): def set_version(self, version=None): if not version: version = self.CURRENT_VERSION - log_info('Setting version to ' + version) + log.log_info('Setting version to ' + version) entry = { self.TABLE_FIELD : version } self.configDB.set_entry(self.TABLE_NAME, self.TABLE_KEY, entry) def migrate(self): version = self.get_version() - log_info('Upgrading from version ' + version) + log.log_info('Upgrading from version ' + version) while version: next_version = getattr(self, version)() if next_version == version: @@ -364,7 +355,7 @@ def main(): print(str(result)) except Exception as e: - log_error('Caught exception: ' + str(e)) + log.log_error('Caught exception: ' + str(e)) traceback.print_exc() print(str(e)) parser.print_help() diff --git a/scripts/decode-syseeprom b/scripts/decode-syseeprom index e687f4fb68..bb542fd4a4 100755 --- a/scripts/decode-syseeprom +++ b/scripts/decode-syseeprom @@ -5,14 +5,14 @@ # This is the main script that handles eeprom encoding and decoding # try: + import glob + import imp import optparse - import warnings import os import sys - import imp - import glob - from sonic_device_util import get_machine_info - from sonic_device_util import get_platform_info + import warnings + + from sonic_py_common import device_info except ImportError as e: raise ImportError (str(e) + "- required module not found") @@ -26,7 +26,7 @@ def main(): raise RuntimeError("must be root to run") # Get platform name - platform = get_platform_info(get_machine_info()) + platform = device_info.get_platform() platform_path = '/'.join([PLATFORM_ROOT, platform]) diff --git a/scripts/lldpshow b/scripts/lldpshow index 3d53df13db..ba826a3f57 100755 --- a/scripts/lldpshow +++ b/scripts/lldpshow @@ -20,14 +20,17 @@ """ from __future__ import print_function -import subprocess + +import argparse import re +import subprocess import sys import xml.etree.ElementTree as ET -from tabulate import tabulate -import argparse -import sonic_device_util + +from sonic_py_common import device_info from swsssdk import ConfigDBConnector, SonicDBConfig +from tabulate import tabulate + BACKEND_ASIC_INTERFACE_NAME_PREFIX = 'Ethernet-BP' LLDP_INTERFACE_LIST_IN_HOST_NAMESPACE = '' @@ -35,6 +38,7 @@ LLDP_INSTANCE_IN_HOST_NAMESPACE = '' LLDP_DEFAULT_INTERFACE_LIST_IN_ASIC_NAMESPACE = '' SPACE_TOKEN = ' ' + class Lldpshow(object): def __init__(self): self.lldpraw = [] @@ -42,16 +46,17 @@ class Lldpshow(object): self.lldp_interface = [] self.lldp_instance = [] self.err = None - ### So far only find Router and Bridge two capabilities in lldpctl, so any other capacility types will be read as Other - ### if further capability type is supported like WLAN, can just add the tag definition here + # So far only find Router and Bridge two capabilities in lldpctl, so any other capacility types will be read as Other + # if further capability type is supported like WLAN, can just add the tag definition here self.ctags = {'Router': 'R', 'Bridge': 'B'} SonicDBConfig.load_sonic_global_db_config() # For multi-asic platforms we will get only front-panel interface to display - namespaces = sonic_device_util.get_all_namespaces() + namespaces = device_info.get_all_namespaces() per_asic_configdb = {} for instance_num, front_asic_namespaces in enumerate(namespaces['front_ns']): - per_asic_configdb[front_asic_namespaces] = ConfigDBConnector(use_unix_socket_path=True, namespace=front_asic_namespaces) + per_asic_configdb[front_asic_namespaces] = ConfigDBConnector( + use_unix_socket_path=True, namespace=front_asic_namespaces) per_asic_configdb[front_asic_namespaces].connect() # Initalize Interface list to be ''. We will do string append of the interfaces below. self.lldp_interface.append(LLDP_DEFAULT_INTERFACE_LIST_IN_ASIC_NAMESPACE) @@ -72,14 +77,15 @@ class Lldpshow(object): """ for lldp_instace_num in range(len(self.lldp_instance)): lldp_interface_list = lldp_port if lldp_port is not None else self.lldp_interface[lldp_instace_num] - # In detail mode we will pass interface list (only front ports) and get O/P as plain text + # In detail mode we will pass interface list (only front ports) and get O/P as plain text # and in table format we will get xml output - lldp_cmd = 'sudo docker exec -i lldp{} lldpctl '.format(self.lldp_instance[lldp_instace_num]) + ('-f xml' if not lldp_detail_info else lldp_interface_list) + lldp_cmd = 'sudo docker exec -i lldp{} lldpctl '.format(self.lldp_instance[lldp_instace_num]) + ( + '-f xml' if not lldp_detail_info else lldp_interface_list) p = subprocess.Popen(lldp_cmd, stdout=subprocess.PIPE, shell=True) (output, err) = p.communicate() ## Wait for end of command. Get return returncode ## returncode = p.wait() - ### if no error, get the lldpctl result + # if no error, get the lldpctl result if returncode == 0: # ignore the output if given port is not present if lldp_port is not None and lldp_port not in output: @@ -89,7 +95,7 @@ class Lldpshow(object): break else: self.err = err - + if self.err: self.lldpraw = [] @@ -140,10 +146,10 @@ class Lldpshow(object): def sort_sum(self, summary): """ Sort the summary information in the way that is expected(natural string).""" - alphanum_key = lambda key: [re.findall('[A-Za-z]+',key) + [int(port_num) for port_num in re.findall('\d+',key)]] + def alphanum_key(key): return [re.findall('[A-Za-z]+', key) + [int(port_num) + for port_num in re.findall('\d+', key)]] return sorted(summary, key=alphanum_key) - def display_sum(self, lldp_detail_info): """ print out summary result of lldp neighbors @@ -160,18 +166,20 @@ class Lldpshow(object): header = ['LocalPort', 'RemoteDevice', 'RemotePortID', 'Capability', 'RemotePortDescr'] sortedsum = self.sort_sum(self.lldpsum) for key in sortedsum: - lldpstatus.append([ key, self.lldpsum[key]['r_name'], self.lldpsum[key]['r_portid'], self.lldpsum[key]['capability'], self.lldpsum[key]['r_portname']]) + lldpstatus.append([key, self.lldpsum[key]['r_name'], self.lldpsum[key]['r_portid'], + self.lldpsum[key]['capability'], self.lldpsum[key]['r_portname']]) print (tabulate(lldpstatus, header)) print ('-'.rjust(50, '-')) print ('Total entries displayed: ', len(self.lldpsum)) elif self.err is not None: - print ('Error:',self.err) + print ('Error:', self.err) + def main(): - parser = argparse.ArgumentParser(description='Display the LLDP neighbors', - version='1.0.0', - formatter_class=argparse.RawTextHelpFormatter, - epilog=""" + parser = argparse.ArgumentParser(description='Display the LLDP neighbors', + version='1.0.0', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" Examples: lldpshow lldpshow -d @@ -198,5 +206,6 @@ def main(): print(e.message, file=sys.stderr) sys.exit(1) + if __name__ == "__main__": main() diff --git a/scripts/neighbor_advertiser b/scripts/neighbor_advertiser index cf36febb16..7805dd9a6b 100644 --- a/scripts/neighbor_advertiser +++ b/scripts/neighbor_advertiser @@ -7,16 +7,17 @@ ######################################## -import os -import sys +import argparse import json +import os import requests -import argparse -import syslog -import traceback import subprocess +import sys import time +import traceback import warnings + +from sonic_py_common import logger from swsssdk import ConfigDBConnector from netaddr import IPAddress, IPNetwork @@ -58,25 +59,9 @@ FERRET_NEIGHBOR_ADVERTISER_API_PREFIX = '/Ferret/NeighborAdvertiser/Slices/' # -# Syslog functions +# Global logger instance # - -def log_info(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() - - -def log_warning(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_WARNING, msg) - syslog.closelog() - - -def log_error(msg): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() +log = logger.Logger() # @@ -110,13 +95,13 @@ def is_dip_in_device_vlan(ferret_dip): for vlan_interface in vlan_interface_query.iterkeys(): if not is_ip_prefix_in_key(vlan_interface): - log_info('{} does not have a subnet, skipping...'.format(vlan_interface)) + log.log_info('{} does not have a subnet, skipping...'.format(vlan_interface)) continue vlan_subnet = IPNetwork(vlan_interface[1]) if ferret_dip.version != vlan_subnet.version: - log_info('{} version (IPv{}) does not match provided DIP version (IPv{}), skipping...'.format(vlan_interface[0], vlan_subnet.version, ferret_dip.version)) + log.log_info('{} version (IPv{}) does not match provided DIP version (IPv{}), skipping...'.format(vlan_interface[0], vlan_subnet.version, ferret_dip.version)) continue if ferret_dip in vlan_subnet: @@ -263,7 +248,7 @@ def get_vlan_addresses(vlan_interface): elif keys[0] == 'link/ether': mac_addr = keys[1] except Exception: - log_error('failed to get %s addresses from o.s.' % vlan_interface) + log.log_error('failed to get %s addresses from o.s.' % vlan_interface) if not mac_addr: mac_addr = get_vlan_interface_mac_address(vlan_interface) @@ -300,7 +285,7 @@ def construct_neighbor_advertiser_slice(): vlan_id, vxlan_id, ipv4_addr, ipv6_addr, mac_addr = get_vlan_addresses(vlan_interface) if not mac_addr: - log_warning('Cannot find mac addr of vlan interface {}'.format(vlan_interface)) + log.log_warning('Cannot find mac addr of vlan interface {}'.format(vlan_interface)) continue ipv4_mappings = [] @@ -370,7 +355,7 @@ def post_neighbor_advertiser_slice(ferret_service_vip): try: response = wrapped_ferret_request(request_slice, https_endpoint) except Exception as e: - log_error("The request failed, vip: {}, error: {}".format(ferret_service_vip, e)) + log.log_error("The request failed, vip: {}, error: {}".format(ferret_service_vip, e)) return None neighbor_advertiser_configuration = json.loads(response.content) @@ -378,15 +363,15 @@ def post_neighbor_advertiser_slice(ferret_service_vip): # Retry the request if the provided DIP is in the device VLAN if is_dip_in_device_vlan(ferret_server_ipv4_addr): - log_info("Failed to set up neighbor advertiser slice, vip: {}, dip {} is in device VLAN (attempt {}/{})".format(ferret_service_vip, ferret_server_ipv4_addr, retry + 1, DEFAULT_FERRET_QUERY_RETRIES)) + log.log_info("Failed to set up neighbor advertiser slice, vip: {}, dip {} is in device VLAN (attempt {}/{})".format(ferret_service_vip, ferret_server_ipv4_addr, retry + 1, DEFAULT_FERRET_QUERY_RETRIES)) continue # If all the proceeding checks pass, return the provided DIP save_as_json(neighbor_advertiser_configuration, NEIGHBOR_ADVERTISER_RESPONSE_CONFIG_PATH) - log_info("Successfully set up neighbor advertiser slice, vip: {}, dip: {}".format(ferret_service_vip, ferret_server_ipv4_addr)) + log.log_info("Successfully set up neighbor advertiser slice, vip: {}, dip: {}".format(ferret_service_vip, ferret_server_ipv4_addr)) return ferret_server_ipv4_addr - log_error("Failed to set up neighbor advertiser slice, vip: {}, returned dips were in device VLAN".format(ferret_service_vip)) + log.log_error("Failed to set up neighbor advertiser slice, vip: {}, returned dips were in device VLAN".format(ferret_service_vip)) return None @@ -412,9 +397,9 @@ def find_mirror_table_name(): MIRROR_ACL_TABLE_PREFIX + MIRROR_ACL_TABLEV6_NAME == table: v6_table = table if not v4_table: - log_error(MIRROR_ACL_TABLE_NAME + " table does not exist") + log.log_error(MIRROR_ACL_TABLE_NAME + " table does not exist") if not v6_table: - log_error(MIRROR_ACL_TABLEV6_NAME + " table does not exist") + log.log_error(MIRROR_ACL_TABLEV6_NAME + " table does not exist") return (v4_table, v6_table) @@ -459,7 +444,7 @@ def set_mirror_tunnel(ferret_server_ip): # Ensure the mirror session is created before creating the rules time.sleep(DEFAULT_CONFIG_DB_WAIT_TIME) add_mirror_acl_rule() - log_info('Finish setting mirror tunnel; Ferret: {}'.format(ferret_server_ip)) + log.log_info('Finish setting mirror tunnel; Ferret: {}'.format(ferret_server_ip)) # @@ -485,7 +470,7 @@ def reset_mirror_tunnel(): # Ensure the rules are removed before removing the mirror session time.sleep(DEFAULT_CONFIG_DB_WAIT_TIME) remove_mirror_session() - log_info('Finish resetting mirror tunnel') + log.log_info('Finish resetting mirror tunnel') # @@ -514,7 +499,7 @@ def add_vxlan_tunnel_map(): def set_vxlan_tunnel(ferret_server_ip): add_vxlan_tunnel(ferret_server_ip) add_vxlan_tunnel_map() - log_info('Finish setting vxlan tunnel; Ferret: {}'.format(ferret_server_ip)) + log.log_info('Finish setting vxlan tunnel; Ferret: {}'.format(ferret_server_ip)) # @@ -533,7 +518,7 @@ def remove_vxlan_tunnel_map(): def reset_vxlan_tunnel(): remove_vxlan_tunnel_map() remove_vxlan_tunnel() - log_info('Finish resetting vxlan tunnel') + log.log_info('Finish resetting vxlan tunnel') # @@ -550,7 +535,7 @@ def main(): operation_mode = args.mode if operation_mode == 'set' and ferret_service_vips is None: - log_warning('ferret service vip is required in set mode') + log.log_warning('ferret service vip is required in set mode') sys.exit(1) connect_config_db() @@ -568,7 +553,7 @@ def main(): break if not set_success: - log_error('Failed to set up neighbor advertiser slice, tried all vips in {}'.format(ferret_service_vips)) + log.log_error('Failed to set up neighbor advertiser slice, tried all vips in {}'.format(ferret_service_vips)) sys.exit(1) if operation_mode == 'reset': @@ -582,6 +567,6 @@ if __name__ == '__main__': try: main() except Exception as e: - log_error('! [Failure] {} {}'.format(e, traceback.format_exc())) + log.log_error('! [Failure] {} {}'.format(e, traceback.format_exc())) sys.exit(1) diff --git a/scripts/port2alias b/scripts/port2alias index 6c8be6b093..426971479c 100755 --- a/scripts/port2alias +++ b/scripts/port2alias @@ -1,26 +1,11 @@ #!/usr/bin/env python import sys -import subprocess from cStringIO import StringIO -from portconfig import get_port_config +from portconfig import get_port_config +from sonic_py_common import device_info -def get_platform_hwsku(): - hwsku = None - platform = None - command = "show platform summary" - p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - output = p.stdout.readlines() - for line in output: - tokens = line.split() - if not tokens: - continue - if tokens[0].lower() == 'hwsku:': - hwsku = tokens[1] - elif tokens[0].lower() == 'platform:': - platform = tokens[1] - return (platform, hwsku) def translate_line(line, ports): allowed_symbols = ['-', '_'] @@ -49,7 +34,7 @@ def translate_line(line, ports): return sb.getvalue() def main(): - (platform, hwsku) = get_platform_hwsku() + (platform, hwsku) = device_info.get_platform_and_hwsku() (ports, _) = get_port_config(hwsku, platform) for line in sys.stdin: sys.stdout.write(translate_line(line, ports)) diff --git a/setup.py b/setup.py index 7da80ef286..af5cf6e251 100644 --- a/setup.py +++ b/setup.py @@ -149,7 +149,8 @@ # therefore all dependencies will be assumed to also be available as .debs. # These unlistable dependencies are as follows: # - sonic-config-engine - # - swsssdk + # - sonic-py-common + # - sonic-py-swsssdk # - tabulate install_requires=[ 'click', diff --git a/sfputil/main.py b/sfputil/main.py index c70c4486b1..75a5f741eb 100644 --- a/sfputil/main.py +++ b/sfputil/main.py @@ -6,12 +6,12 @@ # try: - import sys + import imp import os - import subprocess + import sys + import click - import imp - import syslog + from sonic_py_common import device_info, logger from tabulate import tabulate except ImportError as e: raise ImportError("%s - required module not found" % str(e)) @@ -23,44 +23,14 @@ PLATFORM_SPECIFIC_MODULE_NAME = "sfputil" PLATFORM_SPECIFIC_CLASS_NAME = "SfpUtil" -PLATFORM_ROOT_PATH = '/usr/share/sonic/device' -SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' -HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku' -PLATFORM_KEY = 'DEVICE_METADATA.localhost.platform' - # Global platform-specific sfputil class instance platform_sfputil = None PLATFORM_JSON = 'platform.json' PORT_CONFIG_INI = 'port_config.ini' -# ========================== Syslog wrappers ========================== - - -def log_info(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() - - if also_print_to_console: - print msg - - -def log_warning(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_WARNING, msg) - syslog.closelog() - if also_print_to_console: - print msg - - -def log_error(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() - - if also_print_to_console: - print msg +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) # ========================== Methods for printing ========================== @@ -292,69 +262,25 @@ def port_eeprom_data_raw_string_pretty(logical_port_name): # ==================== Methods for initialization ==================== -# Returns platform and HW SKU -def get_platform_and_hwsku(): - try: - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - platform = stdout.rstrip('\n') - - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-d', '-v', HWSKU_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - hwsku = stdout.rstrip('\n') - except OSError as e: - raise OSError("Cannot detect platform") - - return (platform, hwsku) - - -# Returns path to port config file -def get_path_to_port_config_file(): - # Get platform and hwsku - (platform, hwsku) = get_platform_and_hwsku() - - # Load platform module from source - platform_path = "/".join([PLATFORM_ROOT_PATH, platform]) - hwsku_path = "/".join([platform_path, hwsku]) - - # First check for the presence of the new 'port_config.ini' file - port_config_file_path = "/".join([platform_path, PLATFORM_JSON]) - if not os.path.isfile(port_config_file_path): - # platform.json doesn't exist. Try loading the legacy 'port_config.ini' file - port_config_file_path = "/".join([hwsku_path, PORT_CONFIG_INI]) - - return port_config_file_path - # Loads platform specific sfputil module from source def load_platform_sfputil(): global platform_sfputil - # Get platform and hwsku - (platform, hwsku) = get_platform_and_hwsku() - # Load platform module from source - platform_path = "/".join([PLATFORM_ROOT_PATH, platform]) + platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs() try: - module_file = "/".join([platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py"]) + module_file = os.path.join(platform_path, "plugins", PLATFORM_SPECIFIC_MODULE_NAME + ".py") module = imp.load_source(PLATFORM_SPECIFIC_MODULE_NAME, module_file) except IOError as e: - log_error("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True) + log.log_error("Failed to load platform module '%s': %s" % (PLATFORM_SPECIFIC_MODULE_NAME, str(e)), True) return -1 try: platform_sfputil_class = getattr(module, PLATFORM_SPECIFIC_CLASS_NAME) platform_sfputil = platform_sfputil_class() except AttributeError as e: - log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True) + log.log_error("Failed to instantiate '%s' class: %s" % (PLATFORM_SPECIFIC_CLASS_NAME, str(e)), True) return -2 return 0 @@ -379,10 +305,10 @@ def cli(): # Load port info try: - port_config_file_path = get_path_to_port_config_file() + port_config_file_path = device_info.get_path_to_port_config_file() platform_sfputil.read_porttab_mappings(port_config_file_path) except Exception as e: - log_error("Error reading port info (%s)" % str(e), True) + log.log_error("Error reading port info (%s)" % str(e), True) sys.exit(3) diff --git a/show/main.py b/show/main.py index 8193e1f611..0158706cb2 100755 --- a/show/main.py +++ b/show/main.py @@ -8,26 +8,23 @@ import subprocess import sys import ipaddress -from pkg_resources import parse_version from collections import OrderedDict import click from natsort import natsorted -from tabulate import tabulate - -import sonic_device_util +from pkg_resources import parse_version +from portconfig import get_child_ports +from sonic_py_common import device_info from swsssdk import ConfigDBConnector from swsssdk import SonicV2Connector -from portconfig import get_child_ports +from tabulate import tabulate from utilities_common.db import Db import mlnx -# Global Variable -PLATFORM_ROOT_PATH = "/usr/share/sonic/device" +# Global Variables PLATFORM_JSON = 'platform.json' HWSKU_JSON = 'hwsku.json' -SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' PORT_STR = "Ethernet" VLAN_SUB_INTERFACE_SEPARATOR = '.' @@ -824,41 +821,36 @@ def breakout(ctx): ctx.obj = {'db': config_db} try: - curBrkout_tbl = config_db.get_table('BREAKOUT_CFG') + cur_brkout_tbl = config_db.get_table('BREAKOUT_CFG') except Exception as e: click.echo("Breakout table is not present in Config DB") raise click.Abort() if ctx.invoked_subcommand is None: - - # Get HWSKU and Platform information - hw_info_dict = get_hw_info_dict() - platform = hw_info_dict['platform'] - hwsku = hw_info_dict['hwsku'] - # Get port capability from platform and hwsku related files - platformFile = "{}/{}/{}".format(PLATFORM_ROOT_PATH, platform, PLATFORM_JSON) - platformDict = readJsonFile(platformFile)['interfaces'] - hwskuDict = readJsonFile("{}/{}/{}/{}".format(PLATFORM_ROOT_PATH, platform, hwsku, HWSKU_JSON))['interfaces'] + platform_path, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs() + platform_file = os.path.join(platform_path, PLATFORM_JSON) + platform_dict = readJsonFile(platform_file)['interfaces'] + hwsku_dict = readJsonFile(os.path.join(hwsku_path, HWSKU_JSON))['interfaces'] - if not platformDict or not hwskuDict: + if not platform_dict or not hwsku_dict: click.echo("Can not load port config from {} or {} file".format(PLATFORM_JSON, HWSKU_JSON)) raise click.Abort() - for port_name in platformDict.keys(): - curBrkout_mode = curBrkout_tbl[port_name]["brkout_mode"] + for port_name in platform_dict.keys(): + cur_brkout_mode = cur_brkout_tbl[port_name]["brkout_mode"] - # Update deafult breakout mode and current breakout mode to platformDict - platformDict[port_name].update(hwskuDict[port_name]) - platformDict[port_name]["Current Breakout Mode"] = curBrkout_mode + # Update deafult breakout mode and current breakout mode to platform_dict + platform_dict[port_name].update(hwsku_dict[port_name]) + platform_dict[port_name]["Current Breakout Mode"] = cur_brkout_mode # List all the child ports if present - child_portDict = get_child_ports(port_name, curBrkout_mode, platformFile) - if not child_portDict: + child_port_dict = get_child_ports(port_name, cur_brkout_mode, platformFile) + if not child_port_dict: click.echo("Cannot find ports from {} file ".format(PLATFORM_JSON)) raise click.Abort() - child_ports = natsorted(child_portDict.keys()) + child_ports = natsorted(child_port_dict.keys()) children, speeds = [], [] # Update portname and speed of child ports if present @@ -868,11 +860,11 @@ def breakout(ctx): speeds.append(str(int(speed)//1000)+'G') children.append(port) - platformDict[port_name]["child ports"] = ",".join(children) - platformDict[port_name]["child port speeds"] = ",".join(speeds) + platform_dict[port_name]["child ports"] = ",".join(children) + platform_dict[port_name]["child port speeds"] = ",".join(speeds) # Sorted keys by name in natural sort Order for human readability - parsed = OrderedDict((k, platformDict[k]) for k in natsorted(platformDict.keys())) + parsed = OrderedDict((k, platform_dict[k]) for k in natsorted(platform_dict.keys())) click.echo(json.dumps(parsed, indent=4)) # 'breakout current-mode' subcommand ("show interfaces breakout current-mode") @@ -888,20 +880,20 @@ def currrent_mode(ctx, interface): body = [] try: - curBrkout_tbl = config_db.get_table('BREAKOUT_CFG') + cur_brkout_tbl = config_db.get_table('BREAKOUT_CFG') except Exception as e: click.echo("Breakout table is not present in Config DB") raise click.Abort() # Show current Breakout Mode of user prompted interface if interface is not None: - body.append([interface, str(curBrkout_tbl[interface]['brkout_mode'])]) + body.append([interface, str(cur_brkout_tbl[interface]['brkout_mode'])]) click.echo(tabulate(body, header, tablefmt="grid")) return # Show current Breakout Mode for all interfaces - for name in natsorted(curBrkout_tbl.keys()): - body.append([name, str(curBrkout_tbl[name]['brkout_mode'])]) + for name in natsorted(cur_brkout_tbl.keys()): + body.append([name, str(cur_brkout_tbl[name]['brkout_mode'])]) click.echo(tabulate(body, header, tablefmt="grid")) @@ -1779,20 +1771,13 @@ def get_hw_info_dict(): This function is used to get the HW info helper function """ hw_info_dict = {} - machine_info = sonic_device_util.get_machine_info() - platform = sonic_device_util.get_platform_info(machine_info) - config_db = ConfigDBConnector() - config_db.connect() - data = config_db.get_table('DEVICE_METADATA') - try: - hwsku = data['localhost']['hwsku'] - except KeyError: - hwsku = "Unknown" - version_info = sonic_device_util.get_sonic_version_info() - asic_type = version_info['asic_type'] - hw_info_dict['platform'] = platform - hw_info_dict['hwsku'] = hwsku - hw_info_dict['asic_type'] = asic_type + + version_info = device_info.get_sonic_version_info() + + hw_info_dict['platform'] = device_info.get_platform() + hw_info_dict['hwsku'] = device_info.get_hwsku() + hw_info_dict['asic_type'] = version_info['asic_type'] + return hw_info_dict @cli.group(cls=AliasedGroup) @@ -1800,7 +1785,7 @@ def platform(): """Show platform-specific hardware info""" pass -version_info = sonic_device_util.get_sonic_version_info() +version_info = device_info.get_sonic_version_info() if (version_info and version_info.get('asic_type') == 'mellanox'): platform.add_command(mlnx.mlnx) @@ -1927,7 +1912,7 @@ def logging(process, lines, follow, verbose): @click.option("--verbose", is_flag=True, help="Enable verbose output") def version(verbose): """Show version information""" - version_info = sonic_device_util.get_sonic_version_info() + version_info = device_info.get_sonic_version_info() hw_info_dict = get_hw_info_dict() serial_number_cmd = "sudo decode-syseeprom -s" serial_number = subprocess.Popen(serial_number_cmd, shell=True, stdout=subprocess.PIPE) diff --git a/sonic_installer/main.py b/sonic_installer/main.py index cc8db16616..fc5984d031 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -8,21 +8,24 @@ import os import subprocess import sys -import syslog import time import urllib import click +from sonic_py_common import logger from swsssdk import SonicV2Connector from .bootloader import get_bootloader from .common import run_command, run_command_or_raise from .exception import SonicRuntimeException +SYSLOG_IDENTIFIER = "sonic-installer" # Global Config object _config = None +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) # This is from the aliases example: # https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py @@ -601,11 +604,11 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): count += 1 time.sleep(2) state = hget_warm_restart_table("STATE_DB", "WARM_RESTART_TABLE", warm_app_name, "state") - syslog.syslog("%s reached %s state" % (warm_app_name, state)) + log.log_notice("%s reached %s state" % (warm_app_name, state)) sys.stdout.write("]\n\r") if state != exp_state: click.echo("%s failed to reach %s state" % (warm_app_name, exp_state)) - syslog.syslog(syslog.LOG_ERR, "%s failed to reach %s state" % (warm_app_name, exp_state)) + log.log_error("%s failed to reach %s state" % (warm_app_name, exp_state)) else: exp_state = "" # this is cold upgrade diff --git a/ssdutil/main.py b/ssdutil/main.py index 7accba63a5..94939d3480 100755 --- a/ssdutil/main.py +++ b/ssdutil/main.py @@ -6,68 +6,20 @@ # try: - import sys - import os - import subprocess import argparse - import syslog + import os + import sys + + from sonic_py_common import device_info, logger except ImportError as e: raise ImportError("%s - required module not found" % str(e)) DEFAULT_DEVICE="/dev/sda" SYSLOG_IDENTIFIER = "ssdutil" -PLATFORM_ROOT_PATH = '/usr/share/sonic/device' -SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' -HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku' -PLATFORM_KEY = 'DEVICE_METADATA.localhost.platform' - -def syslog_msg(severity, msg, stdout=False): - """ - Prints to syslog (and stdout if needed) message with specified severity - - Args: - severity : message severity - msg : message - stdout : also primt message to stdout - - """ - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(severity, msg) - syslog.closelog() +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) - if stdout: - print msg - -def get_platform_and_hwsku(): - """ - Retrieves current platform name and hwsku - Raises an OSError exception when failed to fetch - - Returns: - tuple of strings platform and hwsku - e.g. ("x86_64-mlnx_msn2700-r0", "ACS-MSN2700") - """ - try: - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - platform = stdout.rstrip('\n') - - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-d', '-v', HWSKU_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - hwsku = stdout.rstrip('\n') - except OSError as e: - raise OSError("Cannot detect platform") - - return (platform, hwsku) def import_ssd_api(diskdev): """ @@ -78,20 +30,18 @@ def import_ssd_api(diskdev): Instance of the class with SSD API implementation (vendor or generic) """ - # Get platform and hwsku - (platform, hwsku) = get_platform_and_hwsku() - # try to load platform specific module try: - hwsku_plugins_path = "/".join([PLATFORM_ROOT_PATH, platform, "plugins"]) - sys.path.append(os.path.abspath(hwsku_plugins_path)) + platform_path, _ = device_info.get_paths_to_platform_and_hwsku_dirs() + platform_plugins_path = os.path.join(platform_path, "plugins") + sys.path.append(os.path.abspath(platform_plugins_path)) from ssd_util import SsdUtil except ImportError as e: - syslog_msg(syslog.LOG_WARNING, "Platform specific SsdUtil module not found. Falling down to the generic implementation") + log.log_warning("Platform specific SsdUtil module not found. Falling down to the generic implementation") try: from sonic_platform_base.sonic_ssd.ssd_generic import SsdUtil except ImportError as e: - syslog_msg(syslog.LOG_ERR, "Failed to import default SsdUtil. Error: {}".format(str(e)), True) + log.log_error("Failed to import default SsdUtil. Error: {}".format(str(e)), True) raise e return SsdUtil(diskdev) diff --git a/tests/conftest.py b/tests/conftest.py index 03dbfdb38e..fae575fdc7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,7 +7,7 @@ import mock_tables.dbconnector -import sonic_device_util +from sonic_py_common import device_info from swsssdk import ConfigDBConnector test_path = os.path.dirname(os.path.abspath(__file__)) @@ -67,7 +67,7 @@ def setup_single_broacom_asic(): import config.main as config import show.main as show - sonic_device_util.get_num_npus = mock.MagicMock(return_value = 1) + device_info.get_num_npus = mock.MagicMock(return_value = 1) config._get_sonic_generated_services = \ mock.MagicMock(return_value = (generated_services_list, [])) diff --git a/utilities_common/util_base.py b/utilities_common/util_base.py index 64c17e6d52..74646e098c 100644 --- a/utilities_common/util_base.py +++ b/utilities_common/util_base.py @@ -2,136 +2,34 @@ try: import imp - import subprocess import os - import syslog + + from sonic_py_common import device_info except ImportError as e: raise ImportError (str(e) + " - required module not found") # # Constants ==================================================================== # -# Platform root directory -PLATFORM_ROOT_PATH = '/usr/share/sonic/device' -PLATFORM_ROOT_DOCKER = '/usr/share/sonic/platform' -SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' -HWSKU_KEY = 'DEVICE_METADATA.localhost.hwsku' -PLATFORM_KEY = 'DEVICE_METADATA.localhost.platform' PDDF_FILE_PATH = '/usr/share/sonic/platform/pddf_support' -# Port config information -PORT_CONFIG = 'port_config.ini' -PORTMAP = 'portmap.ini' - - EEPROM_MODULE_NAME = 'eeprom' EEPROM_CLASS_NAME = 'board' -class UtilLogger(object): - def __init__(self, syslog_identifier): - self.syslog = syslog - self.syslog.openlog(ident=syslog_identifier, logoption=self.syslog.LOG_NDELAY, facility=self.syslog.LOG_DAEMON) - - def __del__(self): - self.syslog.closelog() - - def log_error(self, msg, print_to_console=False): - self.syslog.syslog(self.syslog.LOG_ERR, msg) - - if print_to_console: - print msg - - def log_warning(self, msg, print_to_console=False): - self.syslog.syslog(self.syslog.LOG_WARNING, msg) - - if print_to_console: - print msg - - def log_notice(self, msg, print_to_console=False): - self.syslog.syslog(self.syslog.LOG_NOTICE, msg) - - if print_to_console: - print msg - - def log_info(self, msg, print_to_console=False): - self.syslog.syslog(self.syslog.LOG_INFO, msg) - - if print_to_console: - print msg - - def log_debug(self, msg, print_to_console=False): - self.syslog.syslog(self.syslog.LOG_DEBUG, msg) - - if print_to_console: - print msg - class UtilHelper(object): def __init__(self): pass - # Returns platform and hwsku - def get_platform_and_hwsku(self): - try: - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-H', '-v', PLATFORM_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - platform = stdout.rstrip('\n') - - proc = subprocess.Popen([SONIC_CFGGEN_PATH, '-d', '-v', HWSKU_KEY], - stdout=subprocess.PIPE, - shell=False, - stderr=subprocess.STDOUT) - stdout = proc.communicate()[0] - proc.wait() - hwsku = stdout.rstrip('\n') - except OSError as e: - raise OSError("Failed to detect platform: %s" % (str(e))) - - return (platform, hwsku) - - # Returns path to platform and hwsku - def get_path_to_platform_and_hwsku(self): - # Get platform and hwsku - (platform, hwsku) = self.get_platform_and_hwsku() - - # Load platform module from source - platform_path = '' - if len(platform) != 0: - platform_path = "/".join([PLATFORM_ROOT_PATH, platform]) - else: - platform_path = PLATFORM_ROOT_PATH_DOCKER - hwsku_path = "/".join([platform_path, hwsku]) - - return (platform_path, hwsku_path) - - # Returns path to port config file - def get_path_to_port_config_file(self): - # Get platform and hwsku path - (platform_path, hwsku_path) = self.get_path_to_platform_and_hwsku() - - # First check for the presence of the new 'port_config.ini' file - port_config_file_path = "/".join([hwsku_path, PORT_CONFIG]) - if not os.path.isfile(port_config_file_path): - # port_config.ini doesn't exist. Try loading the legacy 'portmap.ini' file - port_config_file_path = "/".join([hwsku_path, PORTMAP]) - if not os.path.isfile(port_config_file_path): - raise IOError("Failed to detect port config file: %s" % (port_config_file_path)) - - return port_config_file_path - # Loads platform specific psuutil module from source def load_platform_util(self, module_name, class_name): platform_util = None # Get path to platform and hwsku - (platform_path, hwsku_path) = self.get_path_to_platform_and_hwsku() + (platform_path, hwsku_path) = device_info.get_paths_to_platform_and_hwsku_dirs() try: - module_file = "/".join([platform_path, "plugins", module_name + ".py"]) + module_file = os.path.join(platform_path, "plugins", module_name + ".py") module = imp.load_source(module_name, module_file) except IOError as e: raise IOError("Failed to load platform module '%s': %s" % (module_name, str(e))) diff --git a/watchdogutil/main.py b/watchdogutil/main.py index df0f72c780..27d61ff768 100644 --- a/watchdogutil/main.py +++ b/watchdogutil/main.py @@ -6,11 +6,12 @@ # try: - import sys import os + import sys + import click - import syslog import sonic_platform + from sonic_py_common import logger except ImportError as e: raise ImportError("%s - required module not found" % str(e)) @@ -24,35 +25,8 @@ # Global platform-specific watchdog class instance platform_watchdog = None - -# ========================== Syslog wrappers ========================== - - -def log_info(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_INFO, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) - - -def log_warning(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_WARNING, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) - - -def log_error(msg, also_print_to_console=False): - syslog.openlog(SYSLOG_IDENTIFIER) - syslog.syslog(syslog.LOG_ERR, msg) - syslog.closelog() - - if also_print_to_console: - click.echo(msg) +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) # ==================== Methods for initialization ==================== @@ -65,12 +39,12 @@ def load_platform_watchdog(): chassis = platform.get_chassis() if not chassis: - log_error("Failed to get chassis") + log.log_error("Failed to get chassis") return CHASSIS_LOAD_ERROR platform_watchdog = chassis.get_watchdog() if not platform_watchdog: - log_error("Failed to get watchdog module") + log.log_error("Failed to get watchdog module") return WATCHDOG_LOAD_ERROR return 0 From e722cb02d6b7157bdd36549aa7e1c1c59459b90e Mon Sep 17 00:00:00 2001 From: lguohan Date: Sat, 8 Aug 2020 13:27:29 -0700 Subject: [PATCH 26/48] [cli/feature]: split feature command into a separate file (#1034) create new feature.py for both config and show, move the code into feature.py move common code into sonic-utiltities.common.cli.py Signed-off-by: Guohan Lu --- config/feature.py | 48 ++++++++++++++++++++++ config/main.py | 90 ++--------------------------------------- show/feature.py | 56 +++++++++++++++++++++++++ show/main.py | 54 +------------------------ tests/feature_test.py | 9 +++++ utilities_common/cli.py | 43 ++++++++++++++++++++ 6 files changed, 161 insertions(+), 139 deletions(-) create mode 100644 config/feature.py create mode 100644 show/feature.py create mode 100644 utilities_common/cli.py diff --git a/config/feature.py b/config/feature.py new file mode 100644 index 0000000000..55bbaaf921 --- /dev/null +++ b/config/feature.py @@ -0,0 +1,48 @@ +import click + +from utilities_common.cli import AbbreviationGroup, pass_db + +# +# 'feature' group ('config feature ...') +# +@click.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) +def feature(): + """Configure features""" + pass + +# +# 'state' command ('config feature state ...') +# +@feature.command('state', short_help="Enable/disable a feature") +@click.argument('name', metavar='', required=True) +@click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) +@pass_db +def feature_state(db, name, state): + """Enable/disable a feature""" + state_data = db.cfgdb.get_entry('FEATURE', name) + + if not state_data: + click.echo("Feature '{}' doesn't exist".format(name)) + sys.exit(1) + + db.cfgdb.mod_entry('FEATURE', name, {'state': state}) + +# +# 'autorestart' command ('config feature autorestart ...') +# +@feature.command(name='autorestart', short_help="Enable/disable autosrestart of a feature") +@click.argument('name', metavar='', required=True) +@click.argument('autorestart', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) +@pass_db +def feature_autorestart(db, name, autorestart): + """Enable/disable autorestart of a feature""" + feature_table = db.cfgdb.get_table('FEATURE') + if not feature_table: + click.echo("Unable to retrieve feature table from Config DB.") + sys.exit(1) + + if not feature_table.has_key(name): + click.echo("Unable to retrieve feature '{}'".format(name)) + sys.exit(1) + + db.cfgdb.mod_entry('FEATURE', name, {'auto_restart': autorestart}) diff --git a/config/main.py b/config/main.py index 43f07a0bd2..2d917dd417 100755 --- a/config/main.py +++ b/config/main.py @@ -18,10 +18,12 @@ from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig from utilities_common.db import Db from utilities_common.intf_filter import parse_interface_in_filter +from utilities_common.cli import AbbreviationGroup, pass_db import aaa import mlnx import nat +import feature from config_mgmt import ConfigMgmtDPB CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) @@ -54,46 +56,6 @@ asic_type = None - -class AbbreviationGroup(click.Group): - """This subclass of click.Group supports abbreviated subgroup/subcommand names - """ - - def get_command(self, ctx, cmd_name): - # Try to get builtin commands as normal - rv = click.Group.get_command(self, ctx, cmd_name) - if rv is not None: - return rv - - # Allow automatic abbreviation of the command. "status" for - # instance will match "st". We only allow that however if - # there is only one command. - # If there are multiple matches and the shortest one is the common prefix of all the matches, return - # the shortest one - matches = [] - shortest = None - for x in self.list_commands(ctx): - if x.lower().startswith(cmd_name.lower()): - matches.append(x) - if not shortest: - shortest = x - elif len(shortest) > len(x): - shortest = x - - if not matches: - return None - elif len(matches) == 1: - return click.Group.get_command(self, ctx, matches[0]) - else: - for x in matches: - if not x.startswith(shortest): - break - else: - return click.Group.get_command(self, ctx, shortest) - - ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) - - # # Load breakout config file for Dynamic Port Breakout # @@ -922,10 +884,9 @@ def config(ctx): ctx.obj = Db() -pass_db = click.make_pass_decorator(Db, ensure=True) - config.add_command(aaa.aaa) config.add_command(aaa.tacacs) +config.add_command(feature.feature) # === Add NAT Configuration ========== config.add_command(nat.nat) @@ -3838,50 +3799,5 @@ def delete(ctx): sflow_tbl['global'].pop('agent_id') config_db.set_entry('SFLOW', 'global', sflow_tbl['global']) -# -# 'feature' group ('config feature ...') -# -@config.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) -def feature(): - """Modify configuration of features""" - pass - -# -# 'state' command ('config feature state ...') -# -@feature.command('state', short_help="Enable/disable a feature") -@click.argument('name', metavar='', required=True) -@click.argument('state', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -@pass_db -def feature_state(db, name, state): - """Enable/disable a feature""" - state_data = db.cfgdb.get_entry('FEATURE', name) - - if not state_data: - click.echo("Feature '{}' doesn't exist".format(name)) - sys.exit(1) - - db.cfgdb.mod_entry('FEATURE', name, {'state': state}) - -# -# 'autorestart' command ('config feature autorestart ...') -# -@feature.command(name='autorestart', short_help="Enable/disable autosrestart of a feature") -@click.argument('name', metavar='', required=True) -@click.argument('autorestart', metavar='', required=True, type=click.Choice(["enabled", "disabled"])) -@pass_db -def feature_autorestart(db, name, autorestart): - """Enable/disable autorestart of a feature""" - feature_table = db.cfgdb.get_table('FEATURE') - if not feature_table: - click.echo("Unable to retrieve feature table from Config DB.") - sys.exit(1) - - if not feature_table.has_key(name): - click.echo("Unable to retrieve feature '{}'".format(name)) - sys.exit(1) - - db.cfgdb.mod_entry('FEATURE', name, {'auto_restart': autorestart}) - if __name__ == '__main__': config() diff --git a/show/feature.py b/show/feature.py new file mode 100644 index 0000000000..c3b13abaf8 --- /dev/null +++ b/show/feature.py @@ -0,0 +1,56 @@ +import click +from natsort import natsorted +from tabulate import tabulate + +from utilities_common.cli import AbbreviationGroup, pass_db + +# +# 'feature' group (show feature ...) +# +@click.group(cls=AbbreviationGroup, name='feature', invoke_without_command=False) +def feature(): + """Show feature status""" + pass + +# +# 'status' subcommand (show feature status) +# +@feature.command('status', short_help="Show feature state") +@click.argument('feature_name', required=False) +@pass_db +def feature_status(db, feature_name): + header = ['Feature', 'State', 'AutoRestart'] + body = [] + feature_table = db.cfgdb.get_table('FEATURE') + if feature_name: + if feature_table and feature_table.has_key(feature_name): + body.append([feature_name, feature_table[feature_name]['state'], \ + feature_table[feature_name]['auto_restart']]) + else: + click.echo("Can not find feature {}".format(feature_name)) + sys.exit(1) + else: + for key in natsorted(feature_table.keys()): + body.append([key, feature_table[key]['state'], feature_table[key]['auto_restart']]) + click.echo(tabulate(body, header)) + +# +# 'autorestart' subcommand (show feature autorestart) +# +@feature.command('autorestart', short_help="Show auto-restart state for a feature") +@click.argument('feature_name', required=False) +@pass_db +def feature_autorestart(db, feature_name): + header = ['Feature', 'AutoRestart'] + body = [] + feature_table = db.cfgdb.get_table('FEATURE') + if feature_name: + if feature_table and feature_table.has_key(feature_name): + body.append([feature_name, feature_table[feature_name]['auto_restart']]) + else: + click.echo("Can not find feature {}".format(feature_name)) + sys.exit(1) + else: + for name in natsorted(feature_table.keys()): + body.append([name, feature_table[name]['auto_restart']]) + click.echo(tabulate(body, header)) diff --git a/show/main.py b/show/main.py index 0158706cb2..ba7e912dde 100755 --- a/show/main.py +++ b/show/main.py @@ -20,6 +20,7 @@ from tabulate import tabulate from utilities_common.db import Db +import feature import mlnx # Global Variables @@ -559,7 +560,7 @@ def cli(ctx): ctx.obj = Db() -pass_db = click.make_pass_decorator(Db, ensure=True) +cli.add_command(feature.feature) # # 'vrf' command ("show vrf") @@ -3040,57 +3041,6 @@ def ztp(status, verbose): cmd = cmd + " --verbose" run_command(cmd, display_cmd=verbose) -# -# 'feature' group (show feature ...) -# -@cli.group(name='feature', invoke_without_command=False) -def feature(): - """Show feature status""" - pass - -# -# 'status' subcommand (show feature status) -# -@feature.command('status', short_help="Show feature state") -@click.argument('feature_name', required=False) -@pass_db -def feature_status(db, feature_name): - header = ['Feature', 'State', 'AutoRestart'] - body = [] - feature_table = db.cfgdb.get_table('FEATURE') - if feature_name: - if feature_table and feature_table.has_key(feature_name): - body.append([feature_name, feature_table[feature_name]['state'], \ - feature_table[feature_name]['auto_restart']]) - else: - click.echo("Can not find feature {}".format(feature_name)) - sys.exit(1) - else: - for key in natsorted(feature_table.keys()): - body.append([key, feature_table[key]['state'], feature_table[key]['auto_restart']]) - click.echo(tabulate(body, header)) - -# -# 'autorestart' subcommand (show feature autorestart) -# -@feature.command('autorestart', short_help="Show auto-restart state for a feature") -@click.argument('feature_name', required=False) -@pass_db -def feature_autorestart(db, feature_name): - header = ['Feature', 'AutoRestart'] - body = [] - feature_table = db.cfgdb.get_table('FEATURE') - if feature_name: - if feature_table and feature_table.has_key(feature_name): - body.append([feature_name, feature_table[feature_name]['auto_restart']]) - else: - click.echo("Can not find feature {}".format(feature_name)) - sys.exit(1) - else: - for name in natsorted(feature_table.keys()): - body.append([name, feature_table[name]['auto_restart']]) - click.echo(tabulate(body, header)) - # # 'vnet' command ("show vnet") # diff --git a/tests/feature_test.py b/tests/feature_test.py index c928b9657e..cf54b89d7b 100644 --- a/tests/feature_test.py +++ b/tests/feature_test.py @@ -79,6 +79,15 @@ def test_show_feature_status(self, get_cmd_module): assert result.exit_code == 0 assert result.output == show_feature_status_output + def test_show_feature_status_abbrev_cmd(self, get_cmd_module): + (config, show) = get_cmd_module + runner = CliRunner() + result = runner.invoke(show.cli.commands["feature"], ["st"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_feature_status_output + def test_show_bgp_feature_status(self, get_cmd_module): (config, show) = get_cmd_module runner = CliRunner() diff --git a/utilities_common/cli.py b/utilities_common/cli.py new file mode 100644 index 0000000000..dd09a444c5 --- /dev/null +++ b/utilities_common/cli.py @@ -0,0 +1,43 @@ +import click + +from utilities_common.db import Db + +pass_db = click.make_pass_decorator(Db, ensure=True) + +class AbbreviationGroup(click.Group): + """This subclass of click.Group supports abbreviated subgroup/subcommand names + """ + + def get_command(self, ctx, cmd_name): + # Try to get builtin commands as normal + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # Allow automatic abbreviation of the command. "status" for + # instance will match "st". We only allow that however if + # there is only one command. + # If there are multiple matches and the shortest one is the common prefix of all the matches, return + # the shortest one + matches = [] + shortest = None + for x in self.list_commands(ctx): + if x.lower().startswith(cmd_name.lower()): + matches.append(x) + if not shortest: + shortest = x + elif len(shortest) > len(x): + shortest = x + + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + else: + for x in matches: + if not x.startswith(shortest): + break + else: + return click.Group.get_command(self, ctx, shortest) + + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) From fec442e9078a5427199dd3012dcdb6c0b6932008 Mon Sep 17 00:00:00 2001 From: lguohan Date: Sun, 9 Aug 2020 03:20:40 -0700 Subject: [PATCH 27/48] [tests]:add coverage for config/main/utilities_common module (#1039) generate xml, html and term report Signed-off-by: Guohan Lu --- pytest.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/pytest.ini b/pytest.ini index c24fe5bb9e..81221a9147 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] filterwarnings = ignore::DeprecationWarning +addopts = --cov=config --cov=show --cov=utilities_common --cov-report html --cov-report term --cov-report xml From 33ba594cc9a16b2f21c8cef9c26f53e4b3984708 Mon Sep 17 00:00:00 2001 From: Sujin Kang Date: Sun, 9 Aug 2020 11:20:19 -0700 Subject: [PATCH 28/48] enable watchdog before running platform specific reboot plugin (#1037) --- scripts/fast-reboot | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 27c22f2497..18d9b0724c 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -633,17 +633,18 @@ if [ -x /sbin/hwclock ]; then /sbin/hwclock -w || /bin/true fi -if [ -x ${DEVPATH}/${PLATFORM}/${PLATFORM_PLUGIN} ]; then - debug "Running ${PLATFORM} specific plugin..." - ${DEVPATH}/${PLATFORM}/${PLATFORM_PLUGIN} -fi - # Enable Watchdog Timer if [ -x ${WATCHDOG_UTIL} ]; then debug "Enabling Watchdog before ${REBOOT_TYPE}" ${WATCHDOG_UTIL} arm fi +# Run platform specific reboot plugin +if [ -x ${DEVPATH}/${PLATFORM}/${PLATFORM_PLUGIN} ]; then + debug "Running ${PLATFORM} specific plugin..." + ${DEVPATH}/${PLATFORM}/${PLATFORM_PLUGIN} +fi + # Reboot: explicity call Linux native reboot under sbin debug "Rebooting with ${REBOOT_METHOD} to ${NEXT_SONIC_IMAGE} ..." exec ${REBOOT_METHOD} From fa7fb3a84cbdaf4b90c492b2e03479621187ea5c Mon Sep 17 00:00:00 2001 From: lguohan Date: Sun, 9 Aug 2020 16:49:31 -0700 Subject: [PATCH 29/48] [pytest/coverage]: add coverage for all scripts (#1041) Signed-off-by: Guohan Lu --- pytest.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 81221a9147..d1975890b9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] filterwarnings = ignore::DeprecationWarning -addopts = --cov=config --cov=show --cov=utilities_common --cov-report html --cov-report term --cov-report xml +addopts = --cov=acl_loader --cov=clear --cov=config --cov=connect --cov=consutil --cov=counterpoll --cov=crm --cov=debug --cov=fwutil --cov=pcieutil --cov=pfcwd --cov=psuutil --cov=pddf_fanutil --cov=pddf_ledutil --cov=pddf_psuutil --cov=pddf_thermalutil --cov=scripts --cov=sfputil --cov=show --cov=sonic_installer --cov=ssdutil --cov=utilities_common --cov=watchdogutil --cov-report html --cov-report term --cov-report xml From 0225c09ca05de2c75cd89bfed90351443bcc0a52 Mon Sep 17 00:00:00 2001 From: lguohan Date: Sun, 9 Aug 2020 23:20:31 -0700 Subject: [PATCH 30/48] [config/show]: split vlan into separate file (#1038) - add vlan.py in config and show - add extensive unit tests for vlan Signed-off-by: Guohan Lu --- config/aaa.py | 19 +- config/main.py | 513 ++++++++----------------------- config/mlnx.py | 29 +- config/utils.py | 6 + config/vlan.py | 203 ++++++++++++ show/bgp_frr_v4.py | 7 +- show/bgp_frr_v6.py | 6 +- show/main.py | 383 +++-------------------- show/vlan.py | 133 ++++++++ tests/config_test.py | 3 + tests/conftest.py | 8 - tests/intfutil_test.py | 26 +- tests/mock_tables/appl_db.json | 60 ++++ tests/mock_tables/config_db.json | 461 +++++++++++++++++++++++---- tests/vlan_test.py | 461 +++++++++++++++++++++++++++ utilities_common/cli.py | 257 ++++++++++++++++ 16 files changed, 1748 insertions(+), 827 deletions(-) create mode 100644 config/utils.py create mode 100644 config/vlan.py create mode 100644 show/vlan.py create mode 100644 tests/vlan_test.py diff --git a/config/aaa.py b/config/aaa.py index 251ada2652..8cdd818ac0 100644 --- a/config/aaa.py +++ b/config/aaa.py @@ -2,19 +2,9 @@ # -*- coding: utf-8 -*- import click -import netaddr -from swsssdk import ConfigDBConnector - - -def is_ipaddress(val): - if not val: - return False - try: - netaddr.IPAddress(str(val)) - except ValueError: - return False - return True +from swsssdk import ConfigDBConnector +import utilities_common.cli as clicommon def add_table_kv(table, entry, key, val): config_db = ConfigDBConnector() @@ -31,7 +21,6 @@ def del_table_key(table, entry, key): del data[key] config_db.set_entry(table, entry, data) - @click.group() def aaa(): """AAA command line""" @@ -164,7 +153,7 @@ def passkey(ctx, secret): @click.option('-m', '--use-mgmt-vrf', help="Management vrf, default is no vrf", is_flag=True) def add(address, timeout, key, auth_type, port, pri, use_mgmt_vrf): """Specify a TACACS+ server""" - if not is_ipaddress(address): + if not clicommon.is_ipaddress(address): click.echo('Invalid ip address') return @@ -196,7 +185,7 @@ def add(address, timeout, key, auth_type, port, pri, use_mgmt_vrf): @click.argument('address', metavar='') def delete(address): """Delete a TACACS+ server""" - if not is_ipaddress(address): + if not clicommon.is_ipaddress(address): click.echo('Invalid ip address') return diff --git a/config/main.py b/config/main.py index 2d917dd417..fc451f37b7 100755 --- a/config/main.py +++ b/config/main.py @@ -14,23 +14,25 @@ from minigraph import parse_device_desc_xml from portconfig import get_child_ports, get_port_config_file_name -from sonic_py_common import device_info, logger +from sonic_py_common import device_info from swsssdk import ConfigDBConnector, SonicV2Connector, SonicDBConfig from utilities_common.db import Db from utilities_common.intf_filter import parse_interface_in_filter -from utilities_common.cli import AbbreviationGroup, pass_db +import utilities_common.cli as clicommon +from .utils import log + import aaa import mlnx import nat import feature +import vlan from config_mgmt import ConfigMgmtDPB CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help', '-?']) SONIC_GENERATED_SERVICE_PATH = '/etc/sonic/generated_services.conf' SONIC_CFGGEN_PATH = '/usr/local/bin/sonic-cfggen' -SYSLOG_IDENTIFIER = "config" VLAN_SUB_INTERFACE_SEPARATOR = '.' ASIC_CONF_FILENAME = 'asic.conf' DEFAULT_CONFIG_DB_FILE = '/etc/sonic/config_db.json' @@ -51,9 +53,6 @@ CFG_LOOPBACK_NO="<0-999>" -# Global logger instance -log = logger.Logger(SYSLOG_IDENTIFIER) - asic_type = None # @@ -106,7 +105,7 @@ def shutdown_interfaces(ctx, del_intf_dict): """ shut down all the interfaces before deletion """ for intf in del_intf_dict.keys(): config_db = ctx.obj['config_db'] - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(intf) if interface_name is None: click.echo("[ERROR] interface name is None!") @@ -215,7 +214,7 @@ def breakout_Ports(cm, delPorts=list(), portJson=dict(), force=False, \ def execute_systemctl_per_asic_instance(inst, event, service, action): try: click.echo("Executing {} of service {}@{}...".format(action, service, inst)) - run_command("systemctl {} {}@{}.service".format(action, service, inst)) + clicommon.run_command("systemctl {} {}@{}.service".format(action, service, inst)) except SystemExit as e: log.log_error("Failed to execute {} of service {}@{} with error {}".format(action, service, inst, e)) # Set the event object if there is a failure and exception was raised. @@ -234,7 +233,7 @@ def execute_systemctl(list_of_services, action): if (service + '.service' in generated_services_list): try: click.echo("Executing {} of service {}...".format(action, service)) - run_command("systemctl {} {}".format(action, service)) + clicommon.run_command("systemctl {} {}".format(action, service)) except SystemExit as e: log.log_error("Failed to execute {} of service {} with error {}".format(action, service, e)) raise @@ -260,21 +259,6 @@ def execute_systemctl(list_of_services, action): if e.is_set(): sys.exit(1) -def run_command(command, display_cmd=False, ignore_error=False): - """Run bash command and print output to stdout - """ - if display_cmd == True: - click.echo(click.style("Running command: ", fg='cyan') + click.style(command, fg='green')) - - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - (out, err) = proc.communicate() - - if len(out) > 0: - click.echo(out) - - if proc.returncode != 0 and not ignore_error: - sys.exit(proc.returncode) - def _get_device_type(): """ Get device type @@ -342,7 +326,7 @@ def interface_name_is_valid(interface_name): port_channel_dict = config_db.get_table('PORTCHANNEL') sub_port_intf_dict = config_db.get_table('VLAN_SUB_INTERFACE') - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is not None: @@ -482,12 +466,6 @@ def set_interface_naming_mode(mode): click.echo("Please logout and log back in for changes take effect.") -def get_interface_naming_mode(): - mode = os.getenv('SONIC_CLI_IFACE_MODE') - if mode is None: - mode = "default" - return mode - # Get the local BGP ASN from DEVICE_METADATA def get_local_bgp_asn(config_db): metadata = config_db.get_table('DEVICE_METADATA') @@ -580,10 +558,10 @@ def _remove_bgp_neighbor_config(config_db, neighbor_ip_or_hostname): def _change_hostname(hostname): current_hostname = os.uname()[1] if current_hostname != hostname: - run_command('echo {} > /etc/hostname'.format(hostname), display_cmd=True) - run_command('hostname -F /etc/hostname', display_cmd=True) - run_command('sed -i "/\s{}$/d" /etc/hosts'.format(current_hostname), display_cmd=True) - run_command('echo "127.0.0.1 {}" >> /etc/hosts'.format(hostname), display_cmd=True) + clicommon.run_command('echo {} > /etc/hostname'.format(hostname), display_cmd=True) + clicommon.run_command('hostname -F /etc/hostname', display_cmd=True) + clicommon.run_command('sed -i "/\s{}$/d" /etc/hosts'.format(current_hostname), display_cmd=True) + clicommon.run_command('echo "127.0.0.1 {}" >> /etc/hosts'.format(hostname), display_cmd=True) def _clear_qos(): QOS_TABLE_NAMES = [ @@ -751,16 +729,6 @@ def _restart_services(config_db): execute_systemctl(services_to_restart, SYSTEMCTL_ACTION_RESTART) -def is_ipaddress(val): - """ Validate if an entry is a valid IP """ - if not val: - return False - try: - netaddr.IPAddress(str(val)) - except ValueError: - return False - return True - def interface_is_in_vlan(vlan_member_table, interface_name): """ Check if an interface is in a vlan """ for _,intf in vlan_member_table.keys(): @@ -777,23 +745,6 @@ def interface_is_in_portchannel(portchannel_member_table, interface_name): return False -def interface_is_router_port(interface_table, interface_name): - """ Check if an interface has router config """ - for intf in interface_table.keys(): - if (interface_name == intf[0]): - return True - - return False - -def interface_is_mirror_dst_port(config_db, interface_name): - """ Check if port is already configured as mirror destination port """ - mirror_table = config_db.get_table('MIRROR_SESSION') - for _,v in mirror_table.items(): - if 'dst_port' in v and v['dst_port'] == interface_name: - return True - - return False - def interface_has_mirror_config(mirror_table, interface_name): """ Check if port is already configured with mirror config """ for _,v in mirror_table.items(): @@ -813,7 +764,6 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port, vlan_member_table = config_db.get_table('VLAN_MEMBER') mirror_table = config_db.get_table('MIRROR_SESSION') portchannel_member_table = config_db.get_table('PORTCHANNEL_MEMBER') - interface_table = config_db.get_table('INTERFACE') if dst_port: if not interface_name_is_valid(dst_port): @@ -832,7 +782,7 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port, click.echo("Error: Destination Interface {} has portchannel config".format(dst_port)) return False - if interface_is_router_port(interface_table, dst_port): + if clicommon.is_port_router_interface(config_db, dst_port): click.echo("Error: Destination Interface {} is a L3 interface".format(dst_port)) return False @@ -856,7 +806,7 @@ def validate_mirror_session_config(config_db, session_name, dst_port, src_port, return True # This is our main entrypoint - the main 'config' command -@click.group(cls=AbbreviationGroup, context_settings=CONTEXT_SETTINGS) +@click.group(cls=clicommon.AbbreviationGroup, context_settings=CONTEXT_SETTINGS) @click.pass_context def config(ctx): """SONiC command line - 'config' command""" @@ -889,6 +839,7 @@ def config(ctx): config.add_command(feature.feature) # === Add NAT Configuration ========== config.add_command(nat.nat) +config.add_command(vlan.vlan) @config.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, @@ -938,7 +889,7 @@ def save(filename): command = "{} -n {} -d --print-data > {}".format(SONIC_CFGGEN_PATH, namespace, file) log.log_info("'save' executing...") - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) @config.command() @click.option('-y', '--yes', is_flag=True) @@ -1000,7 +951,7 @@ def load(filename, yes): command = "{} -n {} -j {} --write-to-db".format(SONIC_CFGGEN_PATH, namespace, file) log.log_info("'load' executing...") - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) @config.command() @@ -1008,7 +959,7 @@ def load(filename, yes): @click.option('-l', '--load-sysinfo', is_flag=True, help='load system default information (mac, portmap etc) first.') @click.option('-n', '--no_service_restart', default=False, is_flag=True, help='Do not restart docker services') @click.argument('filename', required=False) -@pass_db +@clicommon.pass_db def reload(db, filename, yes, load_sysinfo, no_service_restart): """Clear current configuration and import a previous saved config DB dump file. : Names of configuration file(s) to load, separated by comma with no spaces in between @@ -1092,7 +1043,7 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart): command = "{} -H -k {} --write-to-db".format(SONIC_CFGGEN_PATH, cfg_hwsku) else: command = "{} -H -k {} -n {} --write-to-db".format(SONIC_CFGGEN_PATH, cfg_hwsku, namespace) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) # For the database service running in linux host we use the file user gives as input # or by default DEFAULT_CONFIG_DB_FILE. In the case of database service running in namespace, @@ -1109,7 +1060,7 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart): else: command = "{} -j {} -n {} --write-to-db".format(SONIC_CFGGEN_PATH, file, namespace) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) client.set(config_db.INIT_INDICATOR, 1) # Migrate DB contents to latest version @@ -1119,7 +1070,7 @@ def reload(db, filename, yes, load_sysinfo, no_service_restart): command = "{} -o migrate".format(db_migrator) else: command = "{} -o migrate -n {}".format(db_migrator, namespace) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) # We first run "systemctl reset-failed" to remove the "failed" # status from all services before we attempt to restart them @@ -1136,7 +1087,7 @@ def load_mgmt_config(filename): """Reconfigure hostname and mgmt interface based on device description file.""" log.log_info("'load_mgmt_config' executing...") command = "{} -M {} --write-to-db".format(SONIC_CFGGEN_PATH, filename) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) #FIXME: After config DB daemon for hostname and mgmt interface is implemented, we'll no longer need to do manual configuration here config_data = parse_device_desc_xml(filename) hostname = config_data['DEVICE_METADATA']['localhost']['hostname'] @@ -1144,20 +1095,20 @@ def load_mgmt_config(filename): mgmt_conf = netaddr.IPNetwork(config_data['MGMT_INTERFACE'].keys()[0][1]) gw_addr = config_data['MGMT_INTERFACE'].values()[0]['gwaddr'] command = "ifconfig eth0 {} netmask {}".format(str(mgmt_conf.ip), str(mgmt_conf.netmask)) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) command = "ip route add default via {} dev eth0 table default".format(gw_addr) - run_command(command, display_cmd=True, ignore_error=True) + clicommon.run_command(command, display_cmd=True, ignore_error=True) command = "ip rule add from {} table default".format(str(mgmt_conf.ip)) - run_command(command, display_cmd=True, ignore_error=True) + clicommon.run_command(command, display_cmd=True, ignore_error=True) command = "[ -f /var/run/dhclient.eth0.pid ] && kill `cat /var/run/dhclient.eth0.pid` && rm -f /var/run/dhclient.eth0.pid" - run_command(command, display_cmd=True, ignore_error=True) + clicommon.run_command(command, display_cmd=True, ignore_error=True) click.echo("Please note loaded setting will be lost after system reboot. To preserve setting, run `config save`.") @config.command("load_minigraph") @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Reload config from minigraph?') @click.option('-n', '--no_service_restart', default=False, is_flag=True, help='Do not restart docker services') -@pass_db +@clicommon.pass_db def load_minigraph(db, no_service_restart): """Reconfigure based on minigraph.""" log.log_info("'load_minigraph' executing...") @@ -1191,7 +1142,7 @@ def load_minigraph(db, no_service_restart): command = "{} -H -m -j /etc/sonic/init_cfg.json {} --write-to-db".format(SONIC_CFGGEN_PATH, cfggen_namespace_option) else: command = "{} -H -m --write-to-db {}".format(SONIC_CFGGEN_PATH, cfggen_namespace_option) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) client.set(config_db.INIT_INDICATOR, 1) # get the device type @@ -1200,13 +1151,13 @@ def load_minigraph(db, no_service_restart): # These commands are not run for host on multi asic platform if num_npus == 1 or namespace is not DEFAULT_NAMESPACE: if device_type != 'MgmtToRRouter': - run_command('{}pfcwd start_default'.format(ns_cmd_prefix), display_cmd=True) + clicommon.run_command('{}pfcwd start_default'.format(ns_cmd_prefix), display_cmd=True) if os.path.isfile('/etc/sonic/acl.json'): - run_command("acl-loader update full /etc/sonic/acl.json", display_cmd=True) + clicommon.run_command("acl-loader update full /etc/sonic/acl.json", display_cmd=True) # generate QoS and Buffer configs - run_command("config qos reload", display_cmd=True) + clicommon.run_command("config qos reload", display_cmd=True) # Write latest db version string into db db_migrator='/usr/bin/db_migrator.py' @@ -1216,7 +1167,7 @@ def load_minigraph(db, no_service_restart): cfggen_namespace_option = " " else: cfggen_namespace_option = " -n {}".format(namespace) - run_command(db_migrator + ' -o set_version' + cfggen_namespace_option) + clicommon.run_command(db_migrator + ' -o set_version' + cfggen_namespace_option) # We first run "systemctl reset-failed" to remove the "failed" # status from all services before we attempt to restart them @@ -1241,7 +1192,7 @@ def hostname(new_hostname): config_db.mod_entry('DEVICE_METADATA' , 'localhost', {"hostname" : new_hostname}) try: command = "service hostname-config restart" - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) except SystemExit as e: click.echo("Restarting hostname-config service failed with error {}".format(e)) raise @@ -1250,7 +1201,7 @@ def hostname(new_hostname): # # 'portchannel' group ('config portchannel ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def portchannel(ctx): config_db = ConfigDBConnector() @@ -1281,7 +1232,7 @@ def remove_portchannel(ctx, portchannel_name): db = ctx.obj['db'] db.set_entry('PORTCHANNEL', portchannel_name, None) -@portchannel.group(cls=AbbreviationGroup, name='member') +@portchannel.group(cls=clicommon.AbbreviationGroup, name='member') @click.pass_context def portchannel_member(ctx): pass @@ -1293,7 +1244,7 @@ def portchannel_member(ctx): def add_portchannel_member(ctx, portchannel_name, port_name): """Add member to port channel""" db = ctx.obj['db'] - if interface_is_mirror_dst_port(db, port_name): + if clicommon.is_port_mirror_dst_port(db, port_name): ctx.fail("{} is configured as mirror destination port".format(port_name)) db.set_entry('PORTCHANNEL_MEMBER', (portchannel_name, port_name), {'NULL': 'NULL'}) @@ -1312,7 +1263,7 @@ def del_portchannel_member(ctx, portchannel_name, port_name): # # 'mirror_session' group ('config mirror_session ...') # -@config.group(cls=AbbreviationGroup, name='mirror_session') +@config.group(cls=clicommon.AbbreviationGroup, name='mirror_session') def mirror_session(): pass @@ -1333,7 +1284,7 @@ def add(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer): """ Add ERSPAN mirror session.(Legacy support) """ add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer) -@mirror_session.group(cls=AbbreviationGroup, name='erspan') +@mirror_session.group(cls=clicommon.AbbreviationGroup, name='erspan') @click.pass_context def erspan(ctx): """ ERSPAN mirror_session """ @@ -1367,7 +1318,7 @@ def gather_session_info(session_info, policer, queue, src_port, direction): session_info['queue'] = queue if src_port: - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": src_port_list = [] for port in src_port.split(","): src_port_list.append(interface_alias_to_name(port)) @@ -1413,7 +1364,7 @@ def add_erspan(session_name, src_ip, dst_ip, dscp, ttl, gre_type, queue, policer return per_npu_configdb[front_asic_namespaces].set_entry("MIRROR_SESSION", session_name, session_info) -@mirror_session.group(cls=AbbreviationGroup, name='span') +@mirror_session.group(cls=clicommon.AbbreviationGroup, name='span') @click.pass_context def span(ctx): """ SPAN mirror session """ @@ -1431,7 +1382,7 @@ def add(session_name, dst_port, src_port, direction, queue, policer): add_span(session_name, dst_port, src_port, direction, queue, policer) def add_span(session_name, dst_port, src_port, direction, queue, policer): - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": dst_port = interface_alias_to_name(dst_port) if dst_port is None: click.echo("Error: Destination Interface {} is invalid".format(dst_port)) @@ -1487,7 +1438,7 @@ def remove(session_name): # # 'pfcwd' group ('config pfcwd ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def pfcwd(): """Configure pfc watchdog """ pass @@ -1520,7 +1471,7 @@ def start(action, restoration_time, ports, detection_time, verbose): if restoration_time: cmd += " --restoration-time {}".format(restoration_time) - run_command(cmd, display_cmd=verbose) + clicommon.run_command(cmd, display_cmd=verbose) @pfcwd.command() @click.option('--verbose', is_flag=True, help="Enable verbose output") @@ -1529,7 +1480,7 @@ def stop(verbose): cmd = "pfcwd stop" - run_command(cmd, display_cmd=verbose) + clicommon.run_command(cmd, display_cmd=verbose) @pfcwd.command() @click.option('--verbose', is_flag=True, help="Enable verbose output") @@ -1539,7 +1490,7 @@ def interval(poll_interval, verbose): cmd = "pfcwd interval {}".format(poll_interval) - run_command(cmd, display_cmd=verbose) + clicommon.run_command(cmd, display_cmd=verbose) @pfcwd.command('counter_poll') @click.option('--verbose', is_flag=True, help="Enable verbose output") @@ -1549,7 +1500,7 @@ def counter_poll(counter_poll, verbose): cmd = "pfcwd counter_poll {}".format(counter_poll) - run_command(cmd, display_cmd=verbose) + clicommon.run_command(cmd, display_cmd=verbose) @pfcwd.command('big_red_switch') @click.option('--verbose', is_flag=True, help="Enable verbose output") @@ -1559,7 +1510,7 @@ def big_red_switch(big_red_switch, verbose): cmd = "pfcwd big_red_switch {}".format(big_red_switch) - run_command(cmd, display_cmd=verbose) + clicommon.run_command(cmd, display_cmd=verbose) @pfcwd.command('start_default') @click.option('--verbose', is_flag=True, help="Enable verbose output") @@ -1568,12 +1519,12 @@ def start_default(verbose): cmd = "pfcwd start_default" - run_command(cmd, display_cmd=verbose) + clicommon.run_command(cmd, display_cmd=verbose) # # 'qos' group ('config qos ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def qos(ctx): """QoS-related configuration tasks""" @@ -1627,7 +1578,7 @@ def reload(): buffer_template_file, buffer_output_file ) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) qos_template_file = os.path.join( hwsku_path, asic_id_suffix, @@ -1644,17 +1595,17 @@ def reload(): sonic_version_file, qos_output_file ) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) # Apply the configurations only when both buffer and qos # configuration files are presented command = "{} {} -j {} --write-to-db".format( SONIC_CFGGEN_PATH, cmd_ns, buffer_output_file ) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) command = "{} {} -j {} --write-to-db".format( SONIC_CFGGEN_PATH, cmd_ns, qos_output_file ) - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) else: click.secho('QoS definition template not found at {}'.format( qos_template_file @@ -1667,7 +1618,7 @@ def reload(): # # 'warm_restart' group ('config warm_restart ...') # -@config.group(cls=AbbreviationGroup, name='warm_restart') +@config.group(cls=clicommon.AbbreviationGroup, name='warm_restart') @click.pass_context @click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') def warm_restart(ctx, redis_unix_socket_path): @@ -1739,135 +1690,6 @@ def warm_restart_bgp_eoiu(ctx, enable): db = ctx.obj['db'] db.mod_entry('WARM_RESTART', 'bgp', {'bgp_eoiu': enable}) -# -# 'vlan' group ('config vlan ...') -# -@config.group(cls=AbbreviationGroup) -@click.pass_context -@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') -def vlan(ctx, redis_unix_socket_path): - """VLAN-related configuration tasks""" - kwargs = {} - if redis_unix_socket_path: - kwargs['unix_socket_path'] = redis_unix_socket_path - config_db = ConfigDBConnector(**kwargs) - config_db.connect(wait_for_init=False) - ctx.obj = {'db': config_db} - -@vlan.command('add') -@click.argument('vid', metavar='', required=True, type=int) -@click.pass_context -def add_vlan(ctx, vid): - if vid >= 1 and vid <= 4094: - db = ctx.obj['db'] - vlan = 'Vlan{}'.format(vid) - if len(db.get_entry('VLAN', vlan)) != 0: - ctx.fail("{} already exists".format(vlan)) - db.set_entry('VLAN', vlan, {'vlanid': vid}) - else : - ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) - -@vlan.command('del') -@click.argument('vid', metavar='', required=True, type=int) -@click.pass_context -def del_vlan(ctx, vid): - """Delete VLAN""" - log.log_info("'vlan del {}' executing...".format(vid)) - db = ctx.obj['db'] - keys = [ (k, v) for k, v in db.get_table('VLAN_MEMBER') if k == 'Vlan{}'.format(vid) ] - for k in keys: - db.set_entry('VLAN_MEMBER', k, None) - db.set_entry('VLAN', 'Vlan{}'.format(vid), None) - - -# -# 'member' group ('config vlan member ...') -# -@vlan.group(cls=AbbreviationGroup, name='member') -@click.pass_context -def vlan_member(ctx): - pass - - -@vlan_member.command('add') -@click.argument('vid', metavar='', required=True, type=int) -@click.argument('interface_name', metavar='', required=True) -@click.option('-u', '--untagged', is_flag=True) -@click.pass_context -def add_vlan_member(ctx, vid, interface_name, untagged): - """Add VLAN member""" - log.log_info("'vlan member add {} {}' executing...".format(vid, interface_name)) - db = ctx.obj['db'] - vlan_name = 'Vlan{}'.format(vid) - vlan = db.get_entry('VLAN', vlan_name) - interface_table = db.get_table('INTERFACE') - - if get_interface_naming_mode() == "alias": - interface_name = interface_alias_to_name(interface_name) - if interface_name is None: - ctx.fail("'interface_name' is None!") - - if len(vlan) == 0: - ctx.fail("{} doesn't exist".format(vlan_name)) - if interface_is_mirror_dst_port(db, interface_name): - ctx.fail("{} is configured as mirror destination port".format(interface_name)) - - members = vlan.get('members', []) - if interface_name in members: - if get_interface_naming_mode() == "alias": - interface_name = interface_name_to_alias(interface_name) - if interface_name is None: - ctx.fail("'interface_name' is None!") - ctx.fail("{} is already a member of {}".format(interface_name, - vlan_name)) - else: - ctx.fail("{} is already a member of {}".format(interface_name, - vlan_name)) - for entry in interface_table: - if (interface_name == entry[0]): - ctx.fail("{} is a L3 interface!".format(interface_name)) - - members.append(interface_name) - vlan['members'] = members - db.set_entry('VLAN', vlan_name, vlan) - db.set_entry('VLAN_MEMBER', (vlan_name, interface_name), {'tagging_mode': "untagged" if untagged else "tagged" }) - - -@vlan_member.command('del') -@click.argument('vid', metavar='', required=True, type=int) -@click.argument('interface_name', metavar='', required=True) -@click.pass_context -def del_vlan_member(ctx, vid, interface_name): - """Delete VLAN member""" - log.log_info("'vlan member del {} {}' executing...".format(vid, interface_name)) - db = ctx.obj['db'] - vlan_name = 'Vlan{}'.format(vid) - vlan = db.get_entry('VLAN', vlan_name) - - if get_interface_naming_mode() == "alias": - interface_name = interface_alias_to_name(interface_name) - if interface_name is None: - ctx.fail("'interface_name' is None!") - - if len(vlan) == 0: - ctx.fail("{} doesn't exist".format(vlan_name)) - members = vlan.get('members', []) - if interface_name not in members: - if get_interface_naming_mode() == "alias": - interface_name = interface_name_to_alias(interface_name) - if interface_name is None: - ctx.fail("'interface_name' is None!") - ctx.fail("{} is not a member of {}".format(interface_name, vlan_name)) - else: - ctx.fail("{} is not a member of {}".format(interface_name, vlan_name)) - members.remove(interface_name) - if len(members) == 0: - del vlan['members'] - else: - vlan['members'] = members - db.set_entry('VLAN', vlan_name, vlan) - db.set_entry('VLAN_MEMBER', (vlan_name, interface_name), None) - def mvrf_restart_services(): """Restart interfaces-config service and NTP service when mvrf is changed""" """ @@ -1905,7 +1727,7 @@ def vrf_delete_management_vrf(config_db): config_db.mod_entry('MGMT_VRF_CONFIG',"vrf_global",{"mgmtVrfEnabled": "false"}) mvrf_restart_services() -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def snmpagentaddress(ctx): """SNMP agent listening IP address, port, vrf configuration""" @@ -1954,7 +1776,7 @@ def del_snmp_agent_address(ctx, agentip, port, vrf): cmd="systemctl restart snmp" os.system (cmd) -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def snmptrap(ctx): """SNMP Trap server configuration to send traps""" @@ -2001,76 +1823,11 @@ def delete_snmptrap_server(ctx, ver): cmd="systemctl restart snmp" os.system (cmd) -@vlan.group(cls=AbbreviationGroup, name='dhcp_relay') -@click.pass_context -def vlan_dhcp_relay(ctx): - pass - -@vlan_dhcp_relay.command('add') -@click.argument('vid', metavar='', required=True, type=int) -@click.argument('dhcp_relay_destination_ip', metavar='', required=True) -@click.pass_context -def add_vlan_dhcp_relay_destination(ctx, vid, dhcp_relay_destination_ip): - """ Add a destination IP address to the VLAN's DHCP relay """ - if not is_ipaddress(dhcp_relay_destination_ip): - ctx.fail('Invalid IP address') - db = ctx.obj['db'] - vlan_name = 'Vlan{}'.format(vid) - vlan = db.get_entry('VLAN', vlan_name) - - if len(vlan) == 0: - ctx.fail("{} doesn't exist".format(vlan_name)) - dhcp_relay_dests = vlan.get('dhcp_servers', []) - if dhcp_relay_destination_ip in dhcp_relay_dests: - click.echo("{} is already a DHCP relay destination for {}".format(dhcp_relay_destination_ip, vlan_name)) - return - else: - dhcp_relay_dests.append(dhcp_relay_destination_ip) - vlan['dhcp_servers'] = dhcp_relay_dests - db.set_entry('VLAN', vlan_name, vlan) - click.echo("Added DHCP relay destination address {} to {}".format(dhcp_relay_destination_ip, vlan_name)) - try: - click.echo("Restarting DHCP relay service...") - run_command("systemctl restart dhcp_relay", display_cmd=False) - except SystemExit as e: - ctx.fail("Restart service dhcp_relay failed with error {}".format(e)) - -@vlan_dhcp_relay.command('del') -@click.argument('vid', metavar='', required=True, type=int) -@click.argument('dhcp_relay_destination_ip', metavar='', required=True) -@click.pass_context -def del_vlan_dhcp_relay_destination(ctx, vid, dhcp_relay_destination_ip): - """ Remove a destination IP address from the VLAN's DHCP relay """ - if not is_ipaddress(dhcp_relay_destination_ip): - ctx.fail('Invalid IP address') - db = ctx.obj['db'] - vlan_name = 'Vlan{}'.format(vid) - vlan = db.get_entry('VLAN', vlan_name) - - if len(vlan) == 0: - ctx.fail("{} doesn't exist".format(vlan_name)) - dhcp_relay_dests = vlan.get('dhcp_servers', []) - if dhcp_relay_destination_ip in dhcp_relay_dests: - dhcp_relay_dests.remove(dhcp_relay_destination_ip) - if len(dhcp_relay_dests) == 0: - del vlan['dhcp_servers'] - else: - vlan['dhcp_servers'] = dhcp_relay_dests - db.set_entry('VLAN', vlan_name, vlan) - click.echo("Removed DHCP relay destination address {} from {}".format(dhcp_relay_destination_ip, vlan_name)) - try: - click.echo("Restarting DHCP relay service...") - run_command("systemctl restart dhcp_relay", display_cmd=False) - except SystemExit as e: - ctx.fail("Restart service dhcp_relay failed with error {}".format(e)) - else: - ctx.fail("{} is not a DHCP relay destination for {}".format(dhcp_relay_destination_ip, vlan_name)) - # # 'bgp' group ('config bgp ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def bgp(): """BGP-related configuration tasks""" pass @@ -2079,12 +1836,12 @@ def bgp(): # 'shutdown' subgroup ('config bgp shutdown ...') # -@bgp.group(cls=AbbreviationGroup) +@bgp.group(cls=clicommon.AbbreviationGroup) def shutdown(): """Shut down BGP session(s)""" pass -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def kdump(): """ Configure kdump """ if os.geteuid() != 0: @@ -2097,7 +1854,7 @@ def disable(): if config_db is not None: config_db.connect() config_db.mod_entry("KDUMP", "config", {"enabled": "false"}) - run_command("sonic-kdump-config --disable") + clicommon.run_command("sonic-kdump-config --disable") @kdump.command() def enable(): @@ -2106,7 +1863,7 @@ def enable(): if config_db is not None: config_db.connect() config_db.mod_entry("KDUMP", "config", {"enabled": "true"}) - run_command("sonic-kdump-config --enable") + clicommon.run_command("sonic-kdump-config --enable") @kdump.command() @click.argument('kdump_memory', metavar='', required=True) @@ -2116,7 +1873,7 @@ def memory(kdump_memory): if config_db is not None: config_db.connect() config_db.mod_entry("KDUMP", "config", {"memory": kdump_memory}) - run_command("sonic-kdump-config --memory %s" % kdump_memory) + clicommon.run_command("sonic-kdump-config --memory %s" % kdump_memory) @kdump.command('num-dumps') @click.argument('kdump_num_dumps', metavar='', required=True, type=int) @@ -2126,7 +1883,7 @@ def num_dumps(kdump_num_dumps): if config_db is not None: config_db.connect() config_db.mod_entry("KDUMP", "config", {"num_dumps": kdump_num_dumps}) - run_command("sonic-kdump-config --num_dumps %d" % kdump_num_dumps) + clicommon.run_command("sonic-kdump-config --num_dumps %d" % kdump_num_dumps) # 'all' subcommand @shutdown.command() @@ -2180,7 +1937,7 @@ def neighbor(ipaddr_or_hostname, verbose): if not found_neighbor: click.get_current_context().fail("Could not locate neighbor '{}'".format(ipaddr_or_hostname)) -@bgp.group(cls=AbbreviationGroup) +@bgp.group(cls=clicommon.AbbreviationGroup) def startup(): """Start up BGP session(s)""" pass @@ -2241,7 +1998,7 @@ def neighbor(ipaddr_or_hostname, verbose): # 'remove' subgroup ('config bgp remove ...') # -@bgp.group(cls=AbbreviationGroup) +@bgp.group(cls=clicommon.AbbreviationGroup) def remove(): "Remove BGP neighbor configuration from the device" pass @@ -2274,7 +2031,7 @@ def remove_neighbor(neighbor_ip_or_hostname): # 'interface' group ('config interface ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def interface(ctx): """Interface-related configuration tasks""" @@ -2294,7 +2051,7 @@ def startup(ctx, interface_name): """Start up interface""" config_db = ctx.obj['config_db'] - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2330,7 +2087,7 @@ def shutdown(ctx, interface_name): """Shut down interface""" log.log_info("'interface shutdown {}' executing...".format(interface_name)) config_db = ctx.obj['config_db'] - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2365,7 +2122,7 @@ def shutdown(ctx, interface_name): @click.option('-v', '--verbose', is_flag=True, help="Enable verbose output") def speed(ctx, interface_name, interface_speed, verbose): """Set interface speed""" - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2375,7 +2132,7 @@ def speed(ctx, interface_name, interface_speed, verbose): command = "portconfig -p {} -s {}".format(interface_name, interface_speed) if verbose: command += " -vv" - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) # # 'breakout' subcommand @@ -2528,7 +2285,7 @@ def mgmt_ip_restart_services(): @click.option('-v', '--verbose', is_flag=True, help="Enable verbose output") def mtu(ctx, interface_name, interface_mtu, verbose): """Set interface mtu""" - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2536,7 +2293,7 @@ def mtu(ctx, interface_name, interface_mtu, verbose): command = "portconfig -p {} -m {}".format(interface_name, interface_mtu) if verbose: command += " -vv" - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) @interface.command() @click.pass_context @@ -2547,7 +2304,7 @@ def fec(ctx, interface_name, interface_fec, verbose): """Set interface fec""" if interface_fec not in ["rs", "fc", "none"]: ctx.fail("'fec not in ['rs', 'fc', 'none']!") - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2555,13 +2312,13 @@ def fec(ctx, interface_name, interface_fec, verbose): command = "portconfig -p {} -f {}".format(interface_name, interface_fec) if verbose: command += " -vv" - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) # # 'ip' subgroup ('config interface ip ...') # -@interface.group(cls=AbbreviationGroup) +@interface.group(cls=clicommon.AbbreviationGroup) @click.pass_context def ip(ctx): """Add or remove IP address""" @@ -2579,7 +2336,7 @@ def ip(ctx): def add(ctx, interface_name, ip_addr, gw): """Add an IP address towards the interface""" config_db = ctx.obj["config_db"] - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2638,7 +2395,7 @@ def add(ctx, interface_name, ip_addr, gw): def remove(ctx, interface_name, ip_addr): """Remove an IP address from the interface""" config_db = ctx.obj["config_db"] - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2662,7 +2419,7 @@ def remove(ctx, interface_name, ip_addr): config_db.set_entry(table_name, interface_name, None) command = "ip neigh flush dev {} {}".format(interface_name, ip_addr) - run_command(command) + clicommon.run_command(command) except ValueError: ctx.fail("'ip_addr' is not valid.") @@ -2670,7 +2427,7 @@ def remove(ctx, interface_name, ip_addr): # 'transceiver' subgroup ('config interface transceiver ...') # -@interface.group(cls=AbbreviationGroup) +@interface.group(cls=clicommon.AbbreviationGroup) @click.pass_context def transceiver(ctx): """SFP transceiver configuration""" @@ -2686,7 +2443,7 @@ def transceiver(ctx): @click.pass_context def lpmode(ctx, interface_name, state): """Enable/disable low-power mode for SFP transceiver module""" - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2695,7 +2452,7 @@ def lpmode(ctx, interface_name, state): ctx.fail("Interface name is invalid. Please enter a valid interface name!!") cmd = "sudo sfputil lpmode {} {}".format("on" if state == "enable" else "off", interface_name) - run_command(cmd) + clicommon.run_command(cmd) # # 'reset' subcommand ('config interface reset ...') @@ -2706,7 +2463,7 @@ def lpmode(ctx, interface_name, state): @click.pass_context def reset(ctx, interface_name): """Reset SFP transceiver module""" - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2715,14 +2472,14 @@ def reset(ctx, interface_name): ctx.fail("Interface name is invalid. Please enter a valid interface name!!") cmd = "sudo sfputil reset {}".format(interface_name) - run_command(cmd) + clicommon.run_command(cmd) # # 'vrf' subgroup ('config interface vrf ...') # -@interface.group(cls=AbbreviationGroup) +@interface.group(cls=clicommon.AbbreviationGroup) @click.pass_context def vrf(ctx): """Bind or unbind VRF""" @@ -2738,7 +2495,7 @@ def vrf(ctx): def bind(ctx, interface_name, vrf_name): """Bind the interface to VRF""" config_db = ctx.obj["config_db"] - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") @@ -2773,7 +2530,7 @@ def bind(ctx, interface_name, vrf_name): def unbind(ctx, interface_name): """Unbind the interface to VRF""" config_db = ctx.obj["config_db"] - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("interface is None!") @@ -2793,7 +2550,7 @@ def unbind(ctx, interface_name): # 'vrf' group ('config vrf ...') # -@config.group(cls=AbbreviationGroup, name='vrf') +@config.group(cls=clicommon.AbbreviationGroup, name='vrf') @click.pass_context def vrf(ctx): """VRF-related configuration tasks""" @@ -2838,7 +2595,7 @@ def del_vrf(ctx, vrf_name): # 'route' group ('config route ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def route(ctx): """route-related configuration tasks""" @@ -2894,7 +2651,7 @@ def add_route(ctx, command_str): else: ctx.fail("nexthop is not in pattern!") cmd += '"' - run_command(cmd) + clicommon.run_command(cmd) @route.command('del',context_settings={"ignore_unknown_options":True}) @click.argument('command_str', metavar='prefix [vrf ] nexthop <[vrf ] >|>', nargs=-1, type=click.Path()) @@ -2946,13 +2703,13 @@ def del_route(ctx, command_str): else: ctx.fail("nexthop is not in pattern!") cmd += '"' - run_command(cmd) + clicommon.run_command(cmd) # # 'acl' group ('config acl ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def acl(): """ACL-related configuration tasks""" pass @@ -2961,7 +2718,7 @@ def acl(): # 'add' subgroup ('config acl add ...') # -@acl.group(cls=AbbreviationGroup) +@acl.group(cls=clicommon.AbbreviationGroup) def add(): """ Add ACL configuration. @@ -3025,7 +2782,7 @@ def table(table_name, table_type, description, ports, stage): # 'remove' subgroup ('config acl remove ...') # -@acl.group(cls=AbbreviationGroup) +@acl.group(cls=clicommon.AbbreviationGroup) def remove(): """ Remove ACL configuration. @@ -3051,7 +2808,7 @@ def table(table_name): # 'acl update' group # -@acl.group(cls=AbbreviationGroup) +@acl.group(cls=clicommon.AbbreviationGroup) def update(): """ACL-related configuration tasks""" pass @@ -3067,7 +2824,7 @@ def full(file_name): """Full update of ACL rules configuration.""" log.log_info("'acl update full {}' executing...".format(file_name)) command = "acl-loader update full {}".format(file_name) - run_command(command) + clicommon.run_command(command) # @@ -3080,14 +2837,14 @@ def incremental(file_name): """Incremental update of ACL rule configuration.""" log.log_info("'acl update incremental {}' executing...".format(file_name)) command = "acl-loader update incremental {}".format(file_name) - run_command(command) + clicommon.run_command(command) # # 'dropcounters' group ('config dropcounters ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def dropcounters(): """Drop counter related configuration tasks""" pass @@ -3114,7 +2871,7 @@ def install(counter_name, alias, group, counter_type, desc, reasons, verbose): if desc: command += " -d '{}'".format(desc) - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) # @@ -3126,7 +2883,7 @@ def install(counter_name, alias, group, counter_type, desc, reasons, verbose): def delete(counter_name, verbose): """Delete an existing drop counter""" command = "dropconfig -c uninstall -n {}".format(counter_name) - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) # @@ -3139,7 +2896,7 @@ def delete(counter_name, verbose): def add_reasons(counter_name, reasons, verbose): """Add reasons to an existing drop counter""" command = "dropconfig -c add -n {} -r {}".format(counter_name, reasons) - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) # @@ -3152,7 +2909,7 @@ def add_reasons(counter_name, reasons, verbose): def remove_reasons(counter_name, reasons, verbose): """Remove reasons from an existing drop counter""" command = "dropconfig -c remove -n {} -r {}".format(counter_name, reasons) - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) # @@ -3178,14 +2935,14 @@ def ecn(profile, rmax, rmin, ymax, ymin, gmax, gmin, verbose): if gmax is not None: command += " -gmax %d" % gmax if gmin is not None: command += " -gmin %d" % gmin if verbose: command += " -vv" - run_command(command, display_cmd=verbose) + clicommon.run_command(command, display_cmd=verbose) # # 'pfc' group ('config interface pfc ...') # -@interface.group(cls=AbbreviationGroup) +@interface.group(cls=clicommon.AbbreviationGroup) @click.pass_context def pfc(ctx): """Set PFC configuration.""" @@ -3202,12 +2959,12 @@ def pfc(ctx): @click.pass_context def asymmetric(ctx, interface_name, status): """Set asymmetric PFC configuration.""" - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") - run_command("pfc config asymmetric {0} {1}".format(status, interface_name)) + clicommon.run_command("pfc config asymmetric {0} {1}".format(status, interface_name)) # # 'pfc priority' command ('config interface pfc priority ...') @@ -3220,23 +2977,23 @@ def asymmetric(ctx, interface_name, status): @click.pass_context def priority(ctx, interface_name, priority, status): """Set PFC priority configuration.""" - if get_interface_naming_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interface_name = interface_alias_to_name(interface_name) if interface_name is None: ctx.fail("'interface_name' is None!") - run_command("pfc config priority {0} {1} {2}".format(status, interface_name, priority)) + clicommon.run_command("pfc config priority {0} {1} {2}".format(status, interface_name, priority)) # # 'platform' group ('config platform ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def platform(): """Platform-related configuration tasks""" # 'firmware' subgroup ("config platform firmware ...") -@platform.group(cls=AbbreviationGroup) +@platform.group(cls=clicommon.AbbreviationGroup) def firmware(): """Firmware configuration tasks""" pass @@ -3281,12 +3038,12 @@ def update(args): # 'watermark' group ("show watermark telemetry interval") # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def watermark(): """Configure watermark """ pass -@watermark.group(cls=AbbreviationGroup) +@watermark.group(cls=clicommon.AbbreviationGroup) def telemetry(): """Configure watermark telemetry""" pass @@ -3296,14 +3053,14 @@ def telemetry(): def interval(interval): """Configure watermark telemetry interval""" command = 'watermarkcfg --config-interval ' + interval - run_command(command) + clicommon.run_command(command) # # 'interface_naming_mode' subgroup ('config interface_naming_mode ...') # -@config.group(cls=AbbreviationGroup, name='interface_naming_mode') +@config.group(cls=clicommon.AbbreviationGroup, name='interface_naming_mode') def interface_naming_mode(): """Modify interface naming mode for interacting with SONiC CLI""" pass @@ -3383,7 +3140,7 @@ def del_loopback(ctx, loopback_name): config_db.set_entry('LOOPBACK_INTERFACE', loopback_name, None) -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) def ztp(): """ Configure Zero Touch Provisioning """ if os.path.isfile('/usr/bin/ztp') is False: @@ -3399,7 +3156,7 @@ def ztp(): def run(run): """Restart ZTP of the device.""" command = "ztp run -y" - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) @ztp.command() @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, @@ -3408,19 +3165,19 @@ def run(run): def disable(disable): """Administratively Disable ZTP.""" command = "ztp disable -y" - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) @ztp.command() @click.argument('enable', required=False, type=click.Choice(["enable"])) def enable(enable): """Administratively Enable ZTP.""" command = "ztp enable" - run_command(command, display_cmd=True) + clicommon.run_command(command, display_cmd=True) # # 'syslog' group ('config syslog ...') # -@config.group(cls=AbbreviationGroup, name='syslog') +@config.group(cls=clicommon.AbbreviationGroup, name='syslog') @click.pass_context def syslog_group(ctx): """Syslog server configuration tasks""" @@ -3433,7 +3190,7 @@ def syslog_group(ctx): @click.pass_context def add_syslog_server(ctx, syslog_ip_address): """ Add syslog server IP """ - if not is_ipaddress(syslog_ip_address): + if not clicommon.is_ipaddress(syslog_ip_address): ctx.fail('Invalid ip address') db = ctx.obj['db'] syslog_servers = db.get_table("SYSLOG_SERVER") @@ -3445,7 +3202,7 @@ def add_syslog_server(ctx, syslog_ip_address): click.echo("Syslog server {} added to configuration".format(syslog_ip_address)) try: click.echo("Restarting rsyslog-config service...") - run_command("systemctl restart rsyslog-config", display_cmd=False) + clicommon.run_command("systemctl restart rsyslog-config", display_cmd=False) except SystemExit as e: ctx.fail("Restart service rsyslog-config failed with error {}".format(e)) @@ -3454,7 +3211,7 @@ def add_syslog_server(ctx, syslog_ip_address): @click.pass_context def del_syslog_server(ctx, syslog_ip_address): """ Delete syslog server IP """ - if not is_ipaddress(syslog_ip_address): + if not clicommon.is_ipaddress(syslog_ip_address): ctx.fail('Invalid IP address') db = ctx.obj['db'] syslog_servers = db.get_table("SYSLOG_SERVER") @@ -3465,14 +3222,14 @@ def del_syslog_server(ctx, syslog_ip_address): ctx.fail("Syslog server {} is not configured.".format(syslog_ip_address)) try: click.echo("Restarting rsyslog-config service...") - run_command("systemctl restart rsyslog-config", display_cmd=False) + clicommon.run_command("systemctl restart rsyslog-config", display_cmd=False) except SystemExit as e: ctx.fail("Restart service rsyslog-config failed with error {}".format(e)) # # 'ntp' group ('config ntp ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def ntp(ctx): """NTP server configuration tasks""" @@ -3485,7 +3242,7 @@ def ntp(ctx): @click.pass_context def add_ntp_server(ctx, ntp_ip_address): """ Add NTP server IP """ - if not is_ipaddress(ntp_ip_address): + if not clicommon.is_ipaddress(ntp_ip_address): ctx.fail('Invalid ip address') db = ctx.obj['db'] ntp_servers = db.get_table("NTP_SERVER") @@ -3497,7 +3254,7 @@ def add_ntp_server(ctx, ntp_ip_address): click.echo("NTP server {} added to configuration".format(ntp_ip_address)) try: click.echo("Restarting ntp-config service...") - run_command("systemctl restart ntp-config", display_cmd=False) + clicommon.run_command("systemctl restart ntp-config", display_cmd=False) except SystemExit as e: ctx.fail("Restart service ntp-config failed with error {}".format(e)) @@ -3506,7 +3263,7 @@ def add_ntp_server(ctx, ntp_ip_address): @click.pass_context def del_ntp_server(ctx, ntp_ip_address): """ Delete NTP server IP """ - if not is_ipaddress(ntp_ip_address): + if not clicommon.is_ipaddress(ntp_ip_address): ctx.fail('Invalid IP address') db = ctx.obj['db'] ntp_servers = db.get_table("NTP_SERVER") @@ -3517,14 +3274,14 @@ def del_ntp_server(ctx, ntp_ip_address): ctx.fail("NTP server {} is not configured.".format(ntp_ip_address)) try: click.echo("Restarting ntp-config service...") - run_command("systemctl restart ntp-config", display_cmd=False) + clicommon.run_command("systemctl restart ntp-config", display_cmd=False) except SystemExit as e: ctx.fail("Restart service ntp-config failed with error {}".format(e)) # # 'sflow' group ('config sflow ...') # -@config.group(cls=AbbreviationGroup) +@config.group(cls=clicommon.AbbreviationGroup) @click.pass_context def sflow(ctx): """sFlow-related configuration tasks""" @@ -3557,8 +3314,8 @@ def enable(ctx): if out != "active": log.log_info("sflow service is not enabled. Starting sflow docker...") - run_command("sudo systemctl enable sflow") - run_command("sudo systemctl start sflow") + clicommon.run_command("sudo systemctl enable sflow") + clicommon.run_command("sudo systemctl start sflow") # # 'sflow' command ('config sflow disable') @@ -3605,7 +3362,7 @@ def is_valid_sample_rate(rate): # # 'sflow interface' group # -@sflow.group(cls=AbbreviationGroup) +@sflow.group(cls=clicommon.AbbreviationGroup) @click.pass_context def interface(ctx): """Configure sFlow settings for an interface""" @@ -3680,7 +3437,7 @@ def sample_rate(ctx, ifname, rate): # # 'sflow collector' group # -@sflow.group(cls=AbbreviationGroup) +@sflow.group(cls=clicommon.AbbreviationGroup) @click.pass_context def collector(ctx): """Add/Delete a sFlow collector""" @@ -3695,7 +3452,7 @@ def is_valid_collector_info(name, ip, port): click.echo("Collector port number must be between 0 and 65535") return False - if not is_ipaddress(ip): + if not clicommon.is_ipaddress(ip): click.echo("Invalid IP address") return False @@ -3748,7 +3505,7 @@ def del_collector(ctx, name): # # 'sflow agent-id' group # -@sflow.group(cls=AbbreviationGroup, name='agent-id') +@sflow.group(cls=clicommon.AbbreviationGroup, name='agent-id') @click.pass_context def agent_id(ctx): """Add/Delete a sFlow agent""" diff --git a/config/mlnx.py b/config/mlnx.py index 54775312ff..bd8c8c9425 100644 --- a/config/mlnx.py +++ b/config/mlnx.py @@ -7,12 +7,11 @@ try: import os - import subprocess - import sys import time import click from sonic_py_common import logger + import utilities_common.cli as clicommon except ImportError as e: raise ImportError("%s - required module not found" % str(e)) @@ -41,24 +40,6 @@ # Global logger instance log = logger.Logger(SNIFFER_SYSLOG_IDENTIFIER) - -# run command -def run_command(command, display_cmd=False, ignore_error=False): - """Run bash command and print output to stdout - """ - if display_cmd == True: - click.echo(click.style("Running command: ", fg='cyan') + click.style(command, fg='green')) - - proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - (out, err) = proc.communicate() - - if len(out) > 0: - click.echo(out) - - if proc.returncode != 0 and not ignore_error: - sys.exit(proc.returncode) - - # generate sniffer target file name include a time stamp. def sniffer_filename_generate(path, filename_prefix, filename_ext): time_stamp = time.strftime("%Y%m%d%H%M%S") @@ -99,12 +80,12 @@ def env_variable_delete(delete_line): def conf_file_copy(src, dest): command = 'docker cp ' + src + ' ' + dest - run_command(command) + clicommon.run_command(command) def conf_file_receive(): command = "docker exec {} bash -c 'touch {}'".format(CONTAINER_NAME, SNIFFER_CONF_FILE) - run_command(command) + clicommon.run_command(command) conf_file_copy(SNIFFER_CONF_FILE_IN_CONTAINER, TMP_SNIFFER_CONF_FILE) @@ -134,7 +115,7 @@ def sniffer_env_variable_set(enable, env_variable_name, env_variable_string=""): config_file_send() command = 'rm -rf {}'.format(TMP_SNIFFER_CONF_FILE) - run_command(command) + clicommon.run_command(command) return ignore @@ -142,7 +123,7 @@ def sniffer_env_variable_set(enable, env_variable_name, env_variable_string=""): # restart the swss service with command 'service swss restart' def restart_swss(): try: - run_command(COMMAND_RESTART_SWSS) + clicommon.run_command(COMMAND_RESTART_SWSS) except OSError as e: log.log_error("Not able to restart swss service, %s" % str(e), True) return 1 diff --git a/config/utils.py b/config/utils.py new file mode 100644 index 0000000000..3cfcc37b43 --- /dev/null +++ b/config/utils.py @@ -0,0 +1,6 @@ +from sonic_py_common import logger + +SYSLOG_IDENTIFIER = "config" + +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) diff --git a/config/vlan.py b/config/vlan.py new file mode 100644 index 0000000000..a43a374495 --- /dev/null +++ b/config/vlan.py @@ -0,0 +1,203 @@ +import click + +import utilities_common.cli as clicommon +from .utils import log + +# +# 'vlan' group ('config vlan ...') +# +@click.group(cls=clicommon.AbbreviationGroup, name='vlan') +def vlan(): + """VLAN-related configuration tasks""" + pass + +@vlan.command('add') +@click.argument('vid', metavar='', required=True, type=int) +@clicommon.pass_db +def add_vlan(db, vid): + """Add VLAN""" + + ctx = click.get_current_context() + + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) + + vlan = 'Vlan{}'.format(vid) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan): + ctx.fail("{} already exists".format(vlan)) + + db.cfgdb.set_entry('VLAN', vlan, {'vlanid': vid}) + +@vlan.command('del') +@click.argument('vid', metavar='', required=True, type=int) +@clicommon.pass_db +def del_vlan(db, vid): + """Delete VLAN""" + + log.log_info("'vlan del {}' executing...".format(vid)) + + ctx = click.get_current_context() + + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) + + vlan = 'Vlan{}'.format(vid) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: + ctx.fail("{} does not exist".format(vlan)) + + keys = [ (k, v) for k, v in db.cfgdb.get_table('VLAN_MEMBER') if k == 'Vlan{}'.format(vid) ] + for k in keys: + db.cfgdb.set_entry('VLAN_MEMBER', k, None) + db.cfgdb.set_entry('VLAN', 'Vlan{}'.format(vid), None) + +# +# 'member' group ('config vlan member ...') +# +@vlan.group(cls=clicommon.AbbreviationGroup, name='member') +def vlan_member(): + pass + +@vlan_member.command('add') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('port', metavar='port', required=True) +@click.option('-u', '--untagged', is_flag=True) +@clicommon.pass_db +def add_vlan_member(db, vid, port, untagged): + """Add VLAN member""" + + ctx = click.get_current_context() + + log.log_info("'vlan member add {} {}' executing...".format(vid, port)) + + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) + + vlan = 'Vlan{}'.format(vid) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: + ctx.fail("{} does not exist".format(vlan)) + + if clicommon.get_interface_naming_mode() == "alias": + alias = port + iface_alias_converter = clicommon.InterfaceAliasConverter(db) + port = iface_alias_converter.alias_to_name(alias) + if port is None: + ctx.fail("cannot find port name for alias {}".format(alias)) + + if clicommon.is_port_mirror_dst_port(db.cfgdb, port): + ctx.fail("{} is configured as mirror destination port".format(port)) + + if clicommon.is_port_vlan_member(db.cfgdb, port, vlan): + ctx.fail("{} is already a member of {}".format(port, vlan)) + + if clicommon.is_valid_port(db.cfgdb, port): + is_port = True + elif clicommon.is_valid_portchannel(db.cfgdb, port): + is_port = False + else: + ctx.fail("{} does not exist".format(port)) + + if (is_port and clicommon.is_port_router_interface(db.cfgdb, port)) or \ + (not is_port and clicommon.is_pc_router_interface(db.cfgdb, port)): + ctx.fail("{} is a router interface!".format(port)) + + db.cfgdb.set_entry('VLAN_MEMBER', (vlan, port), {'tagging_mode': "untagged" if untagged else "tagged" }) + +@vlan_member.command('del') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('port', metavar='', required=True) +@clicommon.pass_db +def del_vlan_member(db, vid, port): + """Delete VLAN member""" + + ctx = click.get_current_context() + + log.log_info("'vlan member del {} {}' executing...".format(vid, port)) + + if not clicommon.is_vlanid_in_range(vid): + ctx.fail("Invalid VLAN ID {} (1-4094)".format(vid)) + + vlan = 'Vlan{}'.format(vid) + if clicommon.check_if_vlanid_exist(db.cfgdb, vlan) == False: + ctx.fail("{} does not exist".format(vlan)) + + if clicommon.get_interface_naming_mode() == "alias": + alias = port + iface_alias_converter = clicommon.InterfaceAliasConverter(db) + port = iface_alias_converter.alias_to_name(alias) + if port is None: + ctx.fail("cannot find port name for alias {}".format(alias)) + + if not clicommon.is_port_vlan_member(db.cfgdb, port, vlan): + ctx.fail("{} is not a member of {}".format(port, vlan)) + + db.cfgdb.set_entry('VLAN_MEMBER', (vlan, port), None) + +@vlan.group(cls=clicommon.AbbreviationGroup, name='dhcp_relay') +def vlan_dhcp_relay(): + pass + +@vlan_dhcp_relay.command('add') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('dhcp_relay_destination_ip', metavar='', required=True) +@clicommon.pass_db +def add_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ip): + """ Add a destination IP address to the VLAN's DHCP relay """ + + ctx = click.get_current_context() + + if not clicommon.is_ipaddress(dhcp_relay_destination_ip): + ctx.fail('{} is invalid IP address'.format(dhcp_relay_destination_ip)) + + vlan_name = 'Vlan{}'.format(vid) + vlan = db.cfgdb.get_entry('VLAN', vlan_name) + if len(vlan) == 0: + ctx.fail("{} doesn't exist".format(vlan_name)) + + dhcp_relay_dests = vlan.get('dhcp_servers', []) + if dhcp_relay_destination_ip in dhcp_relay_dests: + click.echo("{} is already a DHCP relay destination for {}".format(dhcp_relay_destination_ip, vlan_name)) + return + + dhcp_relay_dests.append(dhcp_relay_destination_ip) + vlan['dhcp_servers'] = dhcp_relay_dests + db.cfgdb.set_entry('VLAN', vlan_name, vlan) + click.echo("Added DHCP relay destination address {} to {}".format(dhcp_relay_destination_ip, vlan_name)) + try: + click.echo("Restarting DHCP relay service...") + clicommon.run_command("systemctl restart dhcp_relay", display_cmd=False) + except SystemExit as e: + ctx.fail("Restart service dhcp_relay failed with error {}".format(e)) + +@vlan_dhcp_relay.command('del') +@click.argument('vid', metavar='', required=True, type=int) +@click.argument('dhcp_relay_destination_ip', metavar='', required=True) +@clicommon.pass_db +def del_vlan_dhcp_relay_destination(db, vid, dhcp_relay_destination_ip): + """ Remove a destination IP address from the VLAN's DHCP relay """ + + ctx = click.get_current_context() + + if not clicommon.is_ipaddress(dhcp_relay_destination_ip): + ctx.fail('{} is invalid IP address'.format(dhcp_relay_destination_ip)) + + vlan_name = 'Vlan{}'.format(vid) + vlan = db.cfgdb.get_entry('VLAN', vlan_name) + if len(vlan) == 0: + ctx.fail("{} doesn't exist".format(vlan_name)) + + dhcp_relay_dests = vlan.get('dhcp_servers', []) + if not dhcp_relay_destination_ip in dhcp_relay_dests: + ctx.fail("{} is not a DHCP relay destination for {}".format(dhcp_relay_destination_ip, vlan_name)) + + dhcp_relay_dests.remove(dhcp_relay_destination_ip) + if len(dhcp_relay_dests) == 0: + del vlan['dhcp_servers'] + else: + vlan['dhcp_servers'] = dhcp_relay_dests + db.cfgdb.set_entry('VLAN', vlan_name, vlan) + click.echo("Removed DHCP relay destination address {} from {}".format(dhcp_relay_destination_ip, vlan_name)) + try: + click.echo("Restarting DHCP relay service...") + clicommon.run_command("systemctl restart dhcp_relay", display_cmd=False) + except SystemExit as e: + ctx.fail("Restart service dhcp_relay failed with error {}".format(e)) diff --git a/show/bgp_frr_v4.py b/show/bgp_frr_v4.py index 5f2831c05a..9d2fc57dd4 100644 --- a/show/bgp_frr_v4.py +++ b/show/bgp_frr_v4.py @@ -1,5 +1,8 @@ import click -from show.main import AliasedGroup, ip, run_command, get_bgp_summary_extended + +import utilities_common.cli as clicommon + +from show.main import ip, run_command, get_bgp_summary_extended ############################################################################### @@ -9,7 +12,7 @@ ############################################################################### -@ip.group(cls=AliasedGroup) +@ip.group(cls=clicommon.AliasedGroup) def bgp(): """Show IPv4 BGP (Border Gateway Protocol) information""" pass diff --git a/show/bgp_frr_v6.py b/show/bgp_frr_v6.py index f199ac60b9..37d13a8690 100644 --- a/show/bgp_frr_v6.py +++ b/show/bgp_frr_v6.py @@ -1,5 +1,7 @@ import click -from show.main import AliasedGroup, ipv6, run_command, get_bgp_summary_extended + +import utilities_common.cli as clicommon +from show.main import ipv6, run_command, get_bgp_summary_extended ############################################################################### @@ -9,7 +11,7 @@ ############################################################################### -@ipv6.group(cls=AliasedGroup) +@ipv6.group(cls=clicommon.AliasedGroup) def bgp(): """Show IPv6 BGP (Border Gateway Protocol) information""" pass diff --git a/show/main.py b/show/main.py index ba7e912dde..44399729ce 100755 --- a/show/main.py +++ b/show/main.py @@ -19,9 +19,11 @@ from swsssdk import SonicV2Connector from tabulate import tabulate from utilities_common.db import Db +import utilities_common.cli as clicommon -import feature import mlnx +import vlan +import feature # Global Variables PLATFORM_JSON = 'platform.json' @@ -30,139 +32,6 @@ VLAN_SUB_INTERFACE_SEPARATOR = '.' -try: - # noinspection PyPep8Naming - import ConfigParser as configparser -except ImportError: - # noinspection PyUnresolvedReferences - import configparser - - -# This is from the aliases example: -# https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py -class Config(object): - """Object to hold CLI config""" - - def __init__(self): - self.path = os.getcwd() - self.aliases = {} - - def read_config(self, filename): - parser = configparser.RawConfigParser() - parser.read([filename]) - try: - self.aliases.update(parser.items('aliases')) - except configparser.NoSectionError: - pass - -class InterfaceAliasConverter(object): - """Class which handles conversion between interface name and alias""" - - def __init__(self): - self.alias_max_length = 0 - - config_db = ConfigDBConnector() - config_db.connect() - self.port_dict = config_db.get_table('PORT') - - if not self.port_dict: - click.echo(message="Warning: failed to retrieve PORT table from ConfigDB!", err=True) - self.port_dict = {} - - for port_name in self.port_dict.keys(): - try: - if self.alias_max_length < len( - self.port_dict[port_name]['alias']): - self.alias_max_length = len( - self.port_dict[port_name]['alias']) - except KeyError: - break - - def name_to_alias(self, interface_name): - """Return vendor interface alias if SONiC - interface name is given as argument - """ - vlan_id = '' - sub_intf_sep_idx = -1 - if interface_name is not None: - sub_intf_sep_idx = interface_name.find(VLAN_SUB_INTERFACE_SEPARATOR) - if sub_intf_sep_idx != -1: - vlan_id = interface_name[sub_intf_sep_idx + 1:] - # interface_name holds the parent port name - interface_name = interface_name[:sub_intf_sep_idx] - - for port_name in self.port_dict.keys(): - if interface_name == port_name: - return self.port_dict[port_name]['alias'] if sub_intf_sep_idx == -1 \ - else self.port_dict[port_name]['alias'] + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id - - # interface_name not in port_dict. Just return interface_name - return interface_name if sub_intf_sep_idx == -1 else interface_name + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id - - def alias_to_name(self, interface_alias): - """Return SONiC interface name if vendor - port alias is given as argument - """ - vlan_id = '' - sub_intf_sep_idx = -1 - if interface_alias is not None: - sub_intf_sep_idx = interface_alias.find(VLAN_SUB_INTERFACE_SEPARATOR) - if sub_intf_sep_idx != -1: - vlan_id = interface_alias[sub_intf_sep_idx + 1:] - # interface_alias holds the parent port alias - interface_alias = interface_alias[:sub_intf_sep_idx] - - for port_name in self.port_dict.keys(): - if interface_alias == self.port_dict[port_name]['alias']: - return port_name if sub_intf_sep_idx == -1 else port_name + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id - - # interface_alias not in port_dict. Just return interface_alias - return interface_alias if sub_intf_sep_idx == -1 else interface_alias + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id - - -# Global Config object -_config = None - - -class AliasedGroup(click.Group): - """This subclass of click.Group supports abbreviations and - looking up aliases in a config file with a bit of magic. - """ - - def get_command(self, ctx, cmd_name): - global _config - - # If we haven't instantiated our global config, do it now and load current config - if _config is None: - _config = Config() - - # Load our config file - cfg_file = os.path.join(os.path.dirname(__file__), 'aliases.ini') - _config.read_config(cfg_file) - - # Try to get builtin commands as normal - rv = click.Group.get_command(self, ctx, cmd_name) - if rv is not None: - return rv - - # No builtin found. Look up an explicit command alias in the config - if cmd_name in _config.aliases: - actual_cmd = _config.aliases[cmd_name] - return click.Group.get_command(self, ctx, actual_cmd) - - # Alternative option: if we did not find an explicit alias we - # allow automatic abbreviation of the command. "status" for - # instance will match "st". We only allow that however if - # there is only one command. - matches = [x for x in self.list_commands(ctx) - if x.lower().startswith(cmd_name.lower())] - if not matches: - return None - elif len(matches) == 1: - return click.Group.get_command(self, ctx, matches[0]) - ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) - - # To be enhanced. Routing-stack information should be collected from a global # location (configdb?), so that we prevent the continous execution of this # bash oneliner. To be revisited once routing-stack info is tracked somewhere. @@ -203,7 +72,7 @@ def run_command(command, display_cmd=False, return_cmd=False): # No conversion needed for intfutil commands as it already displays # both SONiC interface name and alias name for all interfaces. - if get_interface_mode() == "alias" and not command.startswith("intfutil"): + if clicommon.get_interface_naming_mode() == "alias" and not command.startswith("intfutil"): run_command_in_alias_mode(command) raise sys.exit(0) @@ -223,26 +92,8 @@ def run_command(command, display_cmd=False, return_cmd=False): if rc != 0: sys.exit(rc) - -def get_interface_mode(): - mode = os.getenv('SONIC_CLI_IFACE_MODE') - if mode is None: - mode = "default" - return mode - - -def is_ip_prefix_in_key(key): - ''' - Function to check if IP address is present in the key. If it - is present, then the key would be a tuple or else, it shall be - be string - ''' - return (isinstance(key, tuple)) - - # Global class instance for SONiC interface name to alias conversion -iface_alias_converter = InterfaceAliasConverter() - +iface_alias_converter = clicommon.InterfaceAliasConverter() def print_output_in_alias_mode(output, index): """Convert and print all instances of SONiC interface @@ -553,7 +404,7 @@ def get_bgp_neighbor_ip_to_name(ip, static_neighbors, dynamic_neighbors): # This is our entrypoint - the main "show" command # TODO: Consider changing function name to 'show' for better understandability -@click.group(cls=AliasedGroup, context_settings=CONTEXT_SETTINGS) +@click.group(cls=clicommon.AliasedGroup, context_settings=CONTEXT_SETTINGS) @click.pass_context def cli(ctx): """SONiC command line - 'show' command""" @@ -561,6 +412,7 @@ def cli(ctx): ctx.obj = Db() cli.add_command(feature.feature) +cli.add_command(vlan.vlan) # # 'vrf' command ("show vrf") @@ -620,7 +472,7 @@ def arp(ipaddress, iface, verbose): cmd += " -ip {}".format(ipaddress) if iface is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": if not ((iface.startswith("PortChannel")) or (iface.startswith("eth"))): iface = iface_alias_converter.alias_to_name(iface) @@ -700,7 +552,7 @@ def mgmt_vrf(ctx,routes): # 'management_interface' group ("show management_interface ...") # -@cli.group(name='management_interface', cls=AliasedGroup) +@cli.group(name='management_interface', cls=clicommon.AliasedGroup) def management_interface(): """Show management interface parameters""" pass @@ -766,7 +618,7 @@ def snmptrap (ctx): # 'interfaces' group ("show interfaces ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def interfaces(): """Show details of the network interfaces""" pass @@ -786,7 +638,7 @@ def alias(interfacename): body = [] if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) # If we're given an interface name, output name and alias for that interface only @@ -901,7 +753,7 @@ def currrent_mode(ctx, interface): # # 'neighbor' group ### # -@interfaces.group(cls=AliasedGroup) +@interfaces.group(cls=clicommon.AliasedGroup) def neighbor(): """Show neighbor related information""" pass @@ -931,7 +783,7 @@ def expected(interfacename): device2interface_dict = {} for port in natsorted(neighbor_dict['DEVICE_NEIGHBOR'].keys()): temp_port = port - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": port = iface_alias_converter.name_to_alias(port) neighbor_dict['DEVICE_NEIGHBOR'][port] = neighbor_dict['DEVICE_NEIGHBOR'].pop(temp_port) device2interface_dict[neighbor_dict['DEVICE_NEIGHBOR'][port]['name']] = {'localPort': port, 'neighborPort': neighbor_dict['DEVICE_NEIGHBOR'][port]['port']} @@ -958,7 +810,7 @@ def expected(interfacename): click.echo(tabulate(body, header)) -@interfaces.group(cls=AliasedGroup) +@interfaces.group(cls=clicommon.AliasedGroup) def transceiver(): """Show SFP Transceiver information""" pass @@ -977,7 +829,7 @@ def eeprom(interfacename, dump_dom, verbose): cmd += " --dom" if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) cmd += " -p {}".format(interfacename) @@ -994,7 +846,7 @@ def lpmode(interfacename, verbose): cmd = "sudo sfputil show lpmode" if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) cmd += " -p {}".format(interfacename) @@ -1010,7 +862,7 @@ def presence(interfacename, verbose): cmd = "sfpshow presence" if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) cmd += " -p {}".format(interfacename) @@ -1027,7 +879,7 @@ def description(interfacename, verbose): cmd = "intfutil description" if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) cmd += " {}".format(interfacename) @@ -1044,7 +896,7 @@ def status(interfacename, verbose): cmd = "intfutil status" if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) cmd += " {}".format(interfacename) @@ -1124,7 +976,7 @@ def portchannel(verbose): # 'subinterfaces' group ("show subinterfaces ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def subinterfaces(): """Show details of the sub port interfaces""" pass @@ -1143,7 +995,7 @@ def status(subinterfacename, verbose): print("Invalid sub port interface name") return - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": subinterfacename = iface_alias_converter.alias_to_name(subinterfacename) cmd += subinterfacename @@ -1155,7 +1007,7 @@ def status(subinterfacename, verbose): # 'pfc' group ("show pfc ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def pfc(): """Show details of the priority-flow-control (pfc) """ pass @@ -1175,7 +1027,7 @@ def counters(verbose): def priority(interface): """Show pfc priority""" cmd = 'pfc show priority' - if interface is not None and get_interface_mode() == "alias": + if interface is not None and clicommon.get_interface_naming_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) if interface is not None: @@ -1188,7 +1040,7 @@ def priority(interface): def asymmetric(interface): """Show asymmetric pfc""" cmd = 'pfc show asymmetric' - if interface is not None and get_interface_mode() == "alias": + if interface is not None and clicommon.get_interface_naming_mode() == "alias": interface = iface_alias_converter.alias_to_name(interface) if interface is not None: @@ -1197,7 +1049,7 @@ def asymmetric(interface): run_command(cmd) # 'pfcwd' subcommand ("show pfcwd...") -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def pfcwd(): """Show details of the pfc watchdog """ pass @@ -1226,14 +1078,14 @@ def stats(verbose): def naming_mode(verbose): """Show interface naming_mode status""" - click.echo(get_interface_mode()) + click.echo(clicommon.get_interface_naming_mode()) # # 'watermark' group ("show watermark telemetry interval") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def watermark(): """Show details of watermark """ pass @@ -1254,7 +1106,7 @@ def show_tm_interval(): # 'queue' group ("show queue ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def queue(): """Show details of the queues """ pass @@ -1269,7 +1121,7 @@ def counters(interfacename, verbose): cmd = "queuestat" if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) if interfacename is not None: @@ -1328,7 +1180,7 @@ def pwm_q_multi(): # 'priority-group' group ("show priority-group ...") # -@cli.group(name='priority-group', cls=AliasedGroup) +@cli.group(name='priority-group', cls=clicommon.AliasedGroup) def priority_group(): """Show details of the PGs """ @@ -1371,7 +1223,7 @@ def pwm_pg_shared(): # 'buffer_pool' group ("show buffer_pool ...") # -@cli.group(name='buffer_pool', cls=AliasedGroup) +@cli.group(name='buffer_pool', cls=clicommon.AliasedGroup) def buffer_pool(): """Show details of the buffer pools""" @@ -1429,7 +1281,7 @@ def route_map(route_map_name, verbose): # # This group houses IP (i.e., IPv4) commands and subgroups -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def ip(): """Show IP (IPv4) commands""" pass @@ -1534,7 +1386,7 @@ def interfaces(): else: oper = "down" master = get_if_master(iface) - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": iface = iface_alias_converter.name_to_alias(iface) data.append([iface, master, ifaddresses[0][1], admin + "/" + oper, neighbor_name, neighbor_ip]) @@ -1612,7 +1464,7 @@ def protocol(verbose): # # This group houses IPv6-related commands and subgroups -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def ipv6(): """Show IPv6 commands""" pass @@ -1676,7 +1528,7 @@ def interfaces(): else: oper = "down" master = get_if_master(iface) - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": iface = iface_alias_converter.name_to_alias(iface) data.append([iface, master, ifaddresses[0][1], admin + "/" + oper, neighbor_info[0][0], neighbor_info[0][1]]) neighbor_info.pop(0) @@ -1734,7 +1586,7 @@ def protocol(verbose): # 'lldp' group ("show lldp ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def lldp(): """LLDP (Link Layer Discovery Protocol) information""" pass @@ -1748,7 +1600,7 @@ def neighbors(interfacename, verbose): cmd = "sudo lldpshow -d" if interfacename is not None: - if get_interface_mode() == "alias": + if clicommon.get_interface_naming_mode() == "alias": interfacename = iface_alias_converter.alias_to_name(interfacename) cmd += " -p {}".format(interfacename) @@ -1781,7 +1633,7 @@ def get_hw_info_dict(): return hw_info_dict -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def platform(): """Show platform-specific hardware info""" pass @@ -1951,7 +1803,7 @@ def environment(verbose): # 'processes' group ("show processes ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def processes(): """Display process information""" pass @@ -2014,7 +1866,7 @@ def techsupport(since, verbose): # 'runningconfiguration' group ("show runningconfiguration") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def runningconfiguration(): """Show current running configuration information""" pass @@ -2128,7 +1980,7 @@ def syslog(verbose): # 'startupconfiguration' group ("show startupconfiguration ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def startupconfiguration(): """Show startup configuration information""" pass @@ -2202,15 +2054,10 @@ def system_memory(verbose): cmd = "free -m" run_command(cmd, display_cmd=verbose) -@cli.group(cls=AliasedGroup) -def vlan(): - """Show VLAN information""" - pass - # # 'kdump command ("show kdump ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def kdump(): """Show kdump configuration, status and information """ pass @@ -2288,134 +2135,6 @@ def log(record, lines): else: run_command("sonic-kdump-config --file %s --lines %s" % (record, lines)) -@vlan.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def brief(verbose): - """Show all bridge information""" - config_db = ConfigDBConnector() - config_db.connect() - header = ['VLAN ID', 'IP Address', 'Ports', 'Port Tagging', 'DHCP Helper Address'] - body = [] - vlan_keys = [] - - # Fetching data from config_db for VLAN, VLAN_INTERFACE and VLAN_MEMBER - vlan_dhcp_helper_data = config_db.get_table('VLAN') - vlan_ip_data = config_db.get_table('VLAN_INTERFACE') - vlan_ports_data = config_db.get_table('VLAN_MEMBER') - - # Defining dictionaries for DHCP Helper address, Interface Gateway IP, - # VLAN ports and port tagging - vlan_dhcp_helper_dict = {} - vlan_ip_dict = {} - vlan_ports_dict = {} - vlan_tagging_dict = {} - - # Parsing DHCP Helpers info - for key in natsorted(vlan_dhcp_helper_data.keys()): - try: - if vlan_dhcp_helper_data[key]['dhcp_servers']: - vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = vlan_dhcp_helper_data[key]['dhcp_servers'] - except KeyError: - vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = " " - - # Parsing VLAN Gateway info - for key in natsorted(vlan_ip_data.keys()): - if not is_ip_prefix_in_key(key): - continue - interface_key = str(key[0].strip("Vlan")) - interface_value = str(key[1]) - if interface_key in vlan_ip_dict: - vlan_ip_dict[interface_key].append(interface_value) - else: - vlan_ip_dict[interface_key] = [interface_value] - - # Parsing VLAN Ports info - for key in natsorted(vlan_ports_data.keys()): - ports_key = str(key[0].strip("Vlan")) - ports_value = str(key[1]) - ports_tagging = vlan_ports_data[key]['tagging_mode'] - if ports_key in vlan_ports_dict: - if get_interface_mode() == "alias": - ports_value = iface_alias_converter.name_to_alias(ports_value) - vlan_ports_dict[ports_key].append(ports_value) - else: - if get_interface_mode() == "alias": - ports_value = iface_alias_converter.name_to_alias(ports_value) - vlan_ports_dict[ports_key] = [ports_value] - if ports_key in vlan_tagging_dict: - vlan_tagging_dict[ports_key].append(ports_tagging) - else: - vlan_tagging_dict[ports_key] = [ports_tagging] - - # Printing the following dictionaries in tablular forms: - # vlan_dhcp_helper_dict={}, vlan_ip_dict = {}, vlan_ports_dict = {} - # vlan_tagging_dict = {} - for key in natsorted(vlan_dhcp_helper_dict.keys()): - if key not in vlan_ip_dict: - ip_address = "" - else: - ip_address = ','.replace(',', '\n').join(vlan_ip_dict[key]) - if key not in vlan_ports_dict: - vlan_ports = "" - else: - vlan_ports = ','.replace(',', '\n').join((vlan_ports_dict[key])) - if key not in vlan_dhcp_helper_dict: - dhcp_helpers = "" - else: - dhcp_helpers = ','.replace(',', '\n').join(vlan_dhcp_helper_dict[key]) - if key not in vlan_tagging_dict: - vlan_tagging = "" - else: - vlan_tagging = ','.replace(',', '\n').join((vlan_tagging_dict[key])) - body.append([key, ip_address, vlan_ports, vlan_tagging, dhcp_helpers]) - click.echo(tabulate(body, header, tablefmt="grid")) - -@vlan.command() -@click.option('-s', '--redis-unix-socket-path', help='unix socket path for redis connection') -def config(redis_unix_socket_path): - kwargs = {} - if redis_unix_socket_path: - kwargs['unix_socket_path'] = redis_unix_socket_path - config_db = ConfigDBConnector(**kwargs) - config_db.connect(wait_for_init=False) - data = config_db.get_table('VLAN') - keys = data.keys() - - def tablelize(keys, data): - table = [] - - for k in natsorted(keys): - if 'members' not in data[k] : - r = [] - r.append(k) - r.append(data[k]['vlanid']) - table.append(r) - continue - - for m in data[k].get('members', []): - r = [] - r.append(k) - r.append(data[k]['vlanid']) - if get_interface_mode() == "alias": - alias = iface_alias_converter.name_to_alias(m) - r.append(alias) - else: - r.append(m) - - entry = config_db.get_entry('VLAN_MEMBER', (k, m)) - mode = entry.get('tagging_mode') - if mode is None: - r.append('?') - else: - r.append(mode) - - table.append(r) - - return table - - header = ['Name', 'VID', 'Member', 'Mode'] - click.echo(tabulate(tablelize(keys, data), header)) - @cli.command('services') def services(): """Show all daemon services""" @@ -2606,7 +2325,7 @@ def show_sflow_global(config_db): # 'acl' group ### # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def acl(): """Show ACL related information""" pass @@ -2648,7 +2367,7 @@ def table(table_name, verbose): # 'dropcounters' group ### # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def dropcounters(): """Show drop counter related information""" pass @@ -2760,7 +2479,7 @@ def line(verbose): return -@cli.group(name='warm_restart', cls=AliasedGroup) +@cli.group(name='warm_restart', cls=clicommon.AliasedGroup) def warm_restart(): """Show warm restart configuration and state""" pass @@ -2886,7 +2605,7 @@ def tablelize(keys, data, enable_table_keys, prefix): # 'nat' group ("show nat ...") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def nat(): """Show details of the nat """ pass @@ -2965,13 +2684,13 @@ def pool(verbose): app_db.connect(app_db.APPL_DB) if app_db.keys(app_db.APPL_DB, '_GEARBOX_TABLE:phy:*'): - @cli.group(cls=AliasedGroup) + @cli.group(cls=clicommon.AliasedGroup) def gearbox(): """Show gearbox info""" pass # 'phys' subcommand ("show gearbox phys") - @gearbox.group(cls=AliasedGroup) + @gearbox.group(cls=clicommon.AliasedGroup) def phys(): """Show external PHY information""" pass @@ -2985,7 +2704,7 @@ def status(ctx): return # 'interfaces' subcommand ("show gearbox interfaces") - @gearbox.group(cls=AliasedGroup) + @gearbox.group(cls=clicommon.AliasedGroup) def interfaces(): """Show gearbox interfaces information""" pass @@ -3044,7 +2763,7 @@ def ztp(status, verbose): # # 'vnet' command ("show vnet") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def vnet(): """Show vnet related information""" pass @@ -3301,7 +3020,7 @@ def tunnel(): # # 'vxlan' command ("show vxlan") # -@cli.group(cls=AliasedGroup) +@cli.group(cls=clicommon.AliasedGroup) def vxlan(): """Show vxlan related information""" pass diff --git a/show/vlan.py b/show/vlan.py new file mode 100644 index 0000000000..d40f93cd29 --- /dev/null +++ b/show/vlan.py @@ -0,0 +1,133 @@ +import click +from natsort import natsorted +from tabulate import tabulate + +import utilities_common.cli as clicommon + +@click.group(cls=clicommon.AliasedGroup) +def vlan(): + """Show VLAN information""" + pass + +@vlan.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def brief(db, verbose): + """Show all bridge information""" + header = ['VLAN ID', 'IP Address', 'Ports', 'Port Tagging', 'DHCP Helper Address'] + body = [] + + # Fetching data from config db for VLAN, VLAN_INTERFACE and VLAN_MEMBER + vlan_dhcp_helper_data = db.cfgdb.get_table('VLAN') + vlan_ip_data = db.cfgdb.get_table('VLAN_INTERFACE') + vlan_ports_data = db.cfgdb.get_table('VLAN_MEMBER') + + # Defining dictionaries for DHCP Helper address, Interface Gateway IP, + # VLAN ports and port tagging + vlan_dhcp_helper_dict = {} + vlan_ip_dict = {} + vlan_ports_dict = {} + vlan_tagging_dict = {} + + # Parsing DHCP Helpers info + for key in natsorted(vlan_dhcp_helper_data.keys()): + try: + if vlan_dhcp_helper_data[key]['dhcp_servers']: + vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = vlan_dhcp_helper_data[key]['dhcp_servers'] + except KeyError: + vlan_dhcp_helper_dict[str(key.strip('Vlan'))] = " " + + # Parsing VLAN Gateway info + for key in natsorted(vlan_ip_data.keys()): + if not clicommon.is_ip_prefix_in_key(key): + continue + interface_key = str(key[0].strip("Vlan")) + interface_value = str(key[1]) + if interface_key in vlan_ip_dict: + vlan_ip_dict[interface_key].append(interface_value) + else: + vlan_ip_dict[interface_key] = [interface_value] + + iface_alias_converter = clicommon.InterfaceAliasConverter(db) + + # Parsing VLAN Ports info + for key in natsorted(vlan_ports_data.keys()): + ports_key = str(key[0].strip("Vlan")) + ports_value = str(key[1]) + ports_tagging = vlan_ports_data[key]['tagging_mode'] + if ports_key in vlan_ports_dict: + if clicommon.get_interface_naming_mode() == "alias": + ports_value = iface_alias_converter.name_to_alias(ports_value) + vlan_ports_dict[ports_key].append(ports_value) + else: + if clicommon.get_interface_naming_mode() == "alias": + ports_value = iface_alias_converter.name_to_alias(ports_value) + vlan_ports_dict[ports_key] = [ports_value] + if ports_key in vlan_tagging_dict: + vlan_tagging_dict[ports_key].append(ports_tagging) + else: + vlan_tagging_dict[ports_key] = [ports_tagging] + + # Printing the following dictionaries in tablular forms: + # vlan_dhcp_helper_dict={}, vlan_ip_dict = {}, vlan_ports_dict = {} + # vlan_tagging_dict = {} + for key in natsorted(vlan_dhcp_helper_dict.keys()): + if key not in vlan_ip_dict: + ip_address = "" + else: + ip_address = ','.replace(',', '\n').join(vlan_ip_dict[key]) + if key not in vlan_ports_dict: + vlan_ports = "" + else: + vlan_ports = ','.replace(',', '\n').join((vlan_ports_dict[key])) + if key not in vlan_dhcp_helper_dict: + dhcp_helpers = "" + else: + dhcp_helpers = ','.replace(',', '\n').join(vlan_dhcp_helper_dict[key]) + if key not in vlan_tagging_dict: + vlan_tagging = "" + else: + vlan_tagging = ','.replace(',', '\n').join((vlan_tagging_dict[key])) + body.append([key, ip_address, vlan_ports, vlan_tagging, dhcp_helpers]) + click.echo(tabulate(body, header, tablefmt="grid")) + +@vlan.command() +@clicommon.pass_db +def config(db): + data = db.cfgdb.get_table('VLAN') + keys = data.keys() + + def tablelize(keys, data): + table = [] + + for k in natsorted(keys): + if 'members' not in data[k] : + r = [] + r.append(k) + r.append(data[k]['vlanid']) + table.append(r) + continue + + for m in data[k].get('members', []): + r = [] + r.append(k) + r.append(data[k]['vlanid']) + if clicommon.get_interface_naming_mode() == "alias": + alias = iface_alias_converter.name_to_alias(m) + r.append(alias) + else: + r.append(m) + + entry = db.cfgdb.get_entry('VLAN_MEMBER', (k, m)) + mode = entry.get('tagging_mode') + if mode is None: + r.append('?') + else: + r.append(mode) + + table.append(r) + + return table + + header = ['Name', 'VID', 'Member', 'Mode'] + click.echo(tabulate(tablelize(keys, data), header)) diff --git a/tests/config_test.py b/tests/config_test.py index ebc0b55fdf..e3c519da6a 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -1,3 +1,4 @@ +import os import traceback from click.testing import CliRunner @@ -48,6 +49,7 @@ class TestLoadMinigraph(object): @classmethod def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" print("SETUP") def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic): @@ -77,4 +79,5 @@ def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_sing @classmethod def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" print("TEARDOWN") diff --git a/tests/conftest.py b/tests/conftest.py index fae575fdc7..e46e6605fd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,7 +2,6 @@ import sys import mock -import click import pytest import mock_tables.dbconnector @@ -48,18 +47,11 @@ 'snmp.timer', 'telemetry.timer'] - -def _dummy_run_command(command, display_cmd=False, return_cmd=False): - if display_cmd == True: - click.echo(click.style("Running command: ", fg='cyan') + click.style(command, fg='green')) - @pytest.fixture def get_cmd_module(): import config.main as config import show.main as show - config.run_command = _dummy_run_command - return (config, show) @pytest.fixture diff --git a/tests/intfutil_test.py b/tests/intfutil_test.py index 67f6f9b6a8..1fd5fe777e 100644 --- a/tests/intfutil_test.py +++ b/tests/intfutil_test.py @@ -10,6 +10,22 @@ modules_path = os.path.dirname(root_path) scripts_path = os.path.join(modules_path, "scripts") +show_interface_status_output="""\ + Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC +--------------- --------------- ------- ----- ----- --------- --------------- ------ ------- --------------- ---------- + Ethernet0 0 25G 9100 rs Ethernet0 routed down up QSFP28 or later off + Ethernet32 13,14,15,16 40G 9100 rs etp9 PortChannel1001 up up N/A off + Ethernet112 93,94,95,96 40G 9100 rs etp29 PortChannel0001 up up N/A off + Ethernet116 89,90,91,92 40G 9100 rs etp30 PortChannel0002 up up N/A off + Ethernet120 101,102,103,104 40G 9100 rs etp31 PortChannel0003 up up N/A off + Ethernet124 97,98,99,100 40G 9100 rs etp32 PortChannel0004 up up N/A off +PortChannel0001 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A +PortChannel0002 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A +PortChannel0003 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A +PortChannel0004 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A +PortChannel1001 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A +""" + class TestIntfutil(TestCase): @classmethod def setup_class(cls): @@ -26,17 +42,12 @@ def test_intf_status(self): # Test 'show interfaces status' result = self.runner.invoke(show.cli.commands["interfaces"].commands["status"], []) print >> sys.stderr, result.output - expected_output = ( - "Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC\n" - "----------- ------- ------- ----- ----- --------- ------ ------ ------- --------------- ----------\n" - " Ethernet0 0 25G 9100 rs Ethernet0 routed down up QSFP28 or later off" - ) - self.assertEqual(result.output.strip(), expected_output) + assert result.output == show_interface_status_output # Test 'intfutil status' output = subprocess.check_output('intfutil status', stderr=subprocess.STDOUT, shell=True) print >> sys.stderr, output - self.assertEqual(output.strip(), expected_output) + assert result.output == show_interface_status_output # Test 'show interfaces status --verbose' def test_intf_status_verbose(self): @@ -45,7 +56,6 @@ def test_intf_status_verbose(self): expected_output = "Command: intfutil status" self.assertEqual(result.output.split('\n')[0], expected_output) - # Test 'show subinterfaces status' / 'intfutil status subport' def test_subintf_status(self): # Test 'show subinterfaces status' diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index 4239cf949d..44bd9dd3f6 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -11,6 +11,66 @@ "fec": "rs", "admin_status": "up" }, + "PORT_TABLE:Ethernet32": { + "index": "8", + "lanes": "13,14,15,16", + "alias": "etp9", + "description": "Servers7:eth0", + "speed": "40000", + "oper_status": "up", + "pfc_asym": "off", + "mtu": "9100", + "fec": "rs", + "admin_status": "up" + }, + "PORT_TABLE:Ethernet112": { + "index": "28", + "lanes": "93,94,95,96", + "alias": "etp29", + "description": "ARISTA01T1:Ethernet1", + "speed": "40000", + "oper_status": "up", + "pfc_asym": "off", + "mtu": "9100", + "fec": "rs", + "admin_status": "up" + }, + "PORT_TABLE:Ethernet116": { + "index": "29", + "lanes": "89,90,91,92", + "alias": "etp30", + "description": "ARISTA02T1:Ethernet1", + "speed": "40000", + "oper_status": "up", + "pfc_asym": "off", + "mtu": "9100", + "fec": "rs", + "admin_status": "up" + }, + "PORT_TABLE:Ethernet120": { + "index": "30", + "lanes": "101,102,103,104", + "alias": "etp31", + "description": "ARISTA03T1:Ethernet1", + "speed": "40000", + "oper_status": "up", + "pfc_asym": "off", + "mtu": "9100", + "fec": "rs", + "admin_status": "up" + }, + "PORT_TABLE:Ethernet124": { + "index": "31", + "lanes": "97,98,99,100", + "alias": "etp32", + "description": "ARISTA04T1:Ethernet1", + "speed": "40000", + "oper_status": "up", + "pfc_asym": "off", + "mtu": "9100", + "fec": "rs", + "admin_status": "up" + }, "PORT_TABLE:Ethernet200": { "index": "200", "lanes": "200,201,202,203", diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index f60ee3ef12..0dc8939c12 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -10,130 +10,475 @@ }, "PORT|Ethernet0": { "alias": "etp1", - "lanes": "0,1,2,3", + "description": "etp1", + "index": "0", + "lanes": "25,26,27,28", "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet4": { + "admin_status": "up", + "alias": "etp2", + "description": "Servers0:eth0", + "index": "1", + "lanes": "29,30,31,32", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet8": { + "admin_status": "up", + "alias": "etp3", + "description": "Servers1:eth0", + "index": "2", + "lanes": "33,34,35,36", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet12": { + "admin_status": "up", + "alias": "etp4", + "description": "Servers2:eth0", + "index": "3", + "lanes": "37,38,39,40", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet16": { + "admin_status": "up", + "alias": "etp5", + "description": "Servers3:eth0", + "index": "4", + "lanes": "45,46,47,48", + "mtu": "9100", + "pfc_asym": "off", "speed": "40000" }, "PORT|Ethernet20": { + "admin_status": "up", "alias": "etp6", - "lanes": "20,21,22,23", + "description": "Servers4:eth0", + "index": "5", + "lanes": "41,42,43,44", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet24": { + "admin_status": "up", + "alias": "etp7", + "description": "Servers5:eth0", + "index": "6", + "lanes": "1,2,3,4", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet28": { + "admin_status": "up", + "alias": "etp8", + "description": "Servers6:eth0", + "index": "7", + "lanes": "5,6,7,8", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet32": { + "admin_status": "up", + "alias": "etp9", + "description": "Servers7:eth0", + "index": "8", + "lanes": "13,14,15,16", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet36": { + "admin_status": "up", + "alias": "etp10", + "description": "Servers8:eth0", + "index": "9", + "lanes": "9,10,11,12", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet40": { + "admin_status": "up", + "alias": "etp11", + "description": "Servers9:eth0", + "index": "10", + "lanes": "17,18,19,20", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet44": { + "admin_status": "up", + "alias": "etp12", + "description": "Servers10:eth0", + "index": "11", + "lanes": "21,22,23,24", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet48": { + "admin_status": "up", + "alias": "etp13", + "description": "Servers11:eth0", + "index": "12", + "lanes": "53,54,55,56", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet52": { + "admin_status": "up", + "alias": "etp14", + "description": "Servers12:eth0", + "index": "13", + "lanes": "49,50,51,52", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet56": { + "admin_status": "up", + "alias": "etp15", + "description": "Servers13:eth0", + "index": "14", + "lanes": "57,58,59,60", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet60": { + "admin_status": "up", + "alias": "etp16", + "description": "Servers14:eth0", + "index": "15", + "lanes": "61,62,63,64", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet64": { + "admin_status": "up", + "alias": "etp17", + "description": "Servers15:eth0", + "index": "16", + "lanes": "69,70,71,72", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet68": { + "admin_status": "up", + "alias": "etp18", + "description": "Servers16:eth0", + "index": "17", + "lanes": "65,66,67,68", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet72": { + "admin_status": "up", + "alias": "etp19", + "description": "Servers17:eth0", + "index": "18", + "lanes": "73,74,75,76", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet76": { + "admin_status": "up", + "alias": "etp20", + "description": "Servers18:eth0", + "index": "19", + "lanes": "77,78,79,80", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet80": { + "admin_status": "up", + "alias": "etp21", + "description": "Servers19:eth0", + "index": "20", + "lanes": "109,110,111,112", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet84": { + "admin_status": "up", + "alias": "etp22", + "description": "Servers20:eth0", + "index": "21", + "lanes": "105,106,107,108", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet88": { + "admin_status": "up", + "alias": "etp23", + "description": "Servers21:eth0", + "index": "22", + "lanes": "113,114,115,116", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet92": { + "admin_status": "up", + "alias": "etp24", + "description": "Servers22:eth0", + "index": "23", + "lanes": "117,118,119,120", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet96": { + "admin_status": "up", + "alias": "etp25", + "description": "Servers23:eth0", + "index": "24", + "lanes": "125,126,127,128", "mtu": "9100", + "pfc_asym": "off", "speed": "40000" }, "PORT|Ethernet100": { "alias": "etp26", - "lanes": "100,101,102,103", + "description": "fortyGigE0/100", + "index": "25", + "lanes": "121,122,123,124", "mtu": "9100", + "pfc_asym": "off", "speed": "40000" }, "PORT|Ethernet104": { "alias": "etp27", - "lanes": "104,105,106,107", - "mtu": "9100" + "description": "fortyGigE0/104", + "index": "26", + "lanes": "81,82,83,84", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" }, "PORT|Ethernet108": { "alias": "etp28", - "lanes": "108,109,110,111", + "description": "fortyGigE0/108", + "index": "27", + "lanes": "85,86,87,88", "mtu": "9100", + "pfc_asym": "off", "speed": "40000" }, "PORT|Ethernet112": { "admin_status": "up", "alias": "etp29", - "lanes": "112,113,114,115", + "description": "ARISTA01T1:PORT|Ethernet1", + "index": "28", + "lanes": "93,94,95,96", "mtu": "9100", + "pfc_asym": "off", "speed": "40000" }, "PORT|Ethernet116": { "admin_status": "up", "alias": "etp30", - "lanes": "116,117,118,119", + "description": "ARISTA02T1:PORT|Ethernet1", + "index": "29", + "lanes": "89,90,91,92", "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet120": { + "admin_status": "up", + "alias": "etp31", + "description": "ARISTA03T1:PORT|Ethernet1", + "index": "30", + "lanes": "101,102,103,104", + "mtu": "9100", + "pfc_asym": "off", + "speed": "40000" + }, + "PORT|Ethernet124": { + "admin_status": "up", + "alias": "etp32", + "description": "ARISTA04T1:PORT|Ethernet1", + "index": "31", + "lanes": "97,98,99,100", + "mtu": "9100", + "pfc_asym": "off", "speed": "40000" }, "VLAN_SUB_INTERFACE|Ethernet0.10": { "admin_status": "up" }, "ACL_RULE|DATAACL|DEFAULT_RULE": { - "PACKET_ACTION": "DROP", - "PRIORITY": "1" + "PACKET_ACTION": "DROP", + "PRIORITY": "1" }, "ACL_RULE|DATAACL|RULE_1": { - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9999", - "SRC_IP": "10.0.0.2/32" + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9999", + "SRC_IP": "10.0.0.2/32" }, "ACL_RULE|DATAACL|RULE_2": { - "DST_IP": "192.168.0.16/32", - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9998" + "DST_IP": "192.168.0.16/32", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9998" }, "ACL_RULE|DATAACL|RULE_3": { - "DST_IP": "172.16.2.0/32", - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9997" + "DST_IP": "172.16.2.0/32", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9997" }, "ACL_RULE|DATAACL|RULE_4": { - "L4_SRC_PORT": "4661", - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9996" + "L4_SRC_PORT": "4661", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9996" }, "ACL_RULE|DATAACL|RULE_05": { - "IP_PROTOCOL": "126", - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9995" + "IP_PROTOCOL": "126", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9995" }, "ACL_RULE|EVERFLOW|RULE_6": { - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9994", - "TCP_FLAGS": "0x12/0x12" + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9994", + "TCP_FLAGS": "0x12/0x12" }, "ACL_RULE|DATAACL|RULE_7": { - "PACKET_ACTION": "DROP", - "PRIORITY": "9993", - "SRC_IP": "10.0.0.3/32" + "PACKET_ACTION": "DROP", + "PRIORITY": "9993", + "SRC_IP": "10.0.0.3/32" }, "ACL_RULE|EVERFLOW|RULE_08": { - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9992", - "SRC_IP": "10.0.0.3/32" + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9992", + "SRC_IP": "10.0.0.3/32" }, "ACL_RULE|DATAACL|RULE_9": { - "L4_DST_PORT": "4661", - "PACKET_ACTION": "FORWARD", - "PRIORITY": "9991" + "L4_DST_PORT": "4661", + "PACKET_ACTION": "FORWARD", + "PRIORITY": "9991" }, "ACL_RULE|DATAACL|RULE_10": { - "PACKET_ACTION": "DROP", - "priority": "9989", - "SRC_IP": "10.0.0.3/32" + "PACKET_ACTION": "DROP", + "priority": "9989", + "SRC_IP": "10.0.0.3/32" }, - "ACL_TABLE|DATAACL": { - "policy_desc": "DATAACL", - "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124", - "type": "L3" + "ACL_TABLE|DATAACL": { + "policy_desc": "DATAACL", + "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet64,Ethernet68,Ethernet72,Ethernet76,Ethernet80,Ethernet84,Ethernet88,Ethernet92,Ethernet96,Ethernet100,Ethernet104,Ethernet108,Ethernet112,Ethernet116,Ethernet120,Ethernet124", + "type": "L3" }, "ACL_TABLE|EVERFLOW": { - "policy_desc": "EVERFLOW", - "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet100,Ethernet104,Ethernet92,Ethernet96,Ethernet84,Ethernet88,Ethernet76,Ethernet80,Ethernet108,Ethernet112,Ethernet64,Ethernet120,Ethernet116,Ethernet124,Ethernet72,Ethernet68", - "type": "MIRROR" + "policy_desc": "EVERFLOW", + "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet100,Ethernet104,Ethernet92,Ethernet96,Ethernet84,Ethernet88,Ethernet76,Ethernet80,Ethernet108,Ethernet112,Ethernet64,Ethernet120,Ethernet116,Ethernet124,Ethernet72,Ethernet68", + "type": "MIRROR" }, "ACL_TABLE|EVERFLOW_EGRESS": { - "policy_desc": "EGRESS EVERFLOW", - "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet100,Ethernet104,Ethernet92,Ethernet96,Ethernet84,Ethernet88,Ethernet76,Ethernet80,Ethernet108,Ethernet112,Ethernet64,Ethernet120,Ethernet116,Ethernet124,Ethernet72,Ethernet68", - "type": "MIRROR", - "stage": "egress" + "policy_desc": "EGRESS EVERFLOW", + "ports@": "PortChannel0002,PortChannel0005,PortChannel0008,PortChannel0011,PortChannel0014,PortChannel0017,PortChannel0020,PortChannel0023,Ethernet100,Ethernet104,Ethernet92,Ethernet96,Ethernet84,Ethernet88,Ethernet76,Ethernet80,Ethernet108,Ethernet112,Ethernet64,Ethernet120,Ethernet116,Ethernet124,Ethernet72,Ethernet68", + "type": "MIRROR", + "stage": "egress" }, "ACL_TABLE|SNMP_ACL": { - "policy_desc": "SNMP_ACL", - "services@": "SNMP", - "type": "CTRLPLANE" + "policy_desc": "SNMP_ACL", + "services@": "SNMP", + "type": "CTRLPLANE" }, "ACL_TABLE|SSH_ONLY": { - "policy_desc": "SSH_ONLY", - "services@": "SSH", - "type": "CTRLPLANE" - }, + "policy_desc": "SSH_ONLY", + "services@": "SSH", + "type": "CTRLPLANE" + }, + "VLAN|Vlan1000": { + "dhcp_servers@": "192.0.0.1,192.0.0.2,192.0.0.3,192.0.0.4", + "vlanid": "1000" + }, + "VLAN_INTERFACE|Vlan1000": { + "NULL": "NULL" + }, + "VLAN_INTERFACE|Vlan1000|192.168.0.1/21": { + "NULL": "NULL" + }, + "VLAN_INTERFACE|Vlan1000|fc02:1000::1/64": { + "NULL": "NULL" + }, + "VLAN_MEMBER|Vlan1000|Ethernet4": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan1000|Ethernet8": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan1000|Ethernet12": { + "tagging_mode": "untagged" + }, + "VLAN_MEMBER|Vlan1000|Ethernet16": { + "tagging_mode": "untagged" + }, + "PORTCHANNEL|PortChannel1001": { + "admin_status": "up", + "members@": "Ethernet32", + "min_links": "1", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0001": { + "admin_status": "up", + "members@": "Ethernet112", + "min_links": "1", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0002": { + "admin_status": "up", + "members@": "Ethernet116", + "min_links": "1", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0003": { + "admin_status": "up", + "members@": "Ethernet120", + "min_links": "1", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel0004": { + "admin_status": "up", + "members@": "Ethernet124", + "min_links": "1", + "mtu": "9100" + }, + "PORTCHANNEL_MEMBER|PortChannel1001|Ethernet32": {"NULL": "NULL"}, + "PORTCHANNEL_MEMBER|PortChannel0001|Ethernet112": {"NULL": "NULL"}, + "PORTCHANNEL_MEMBER|PortChannel0002|Ethernet116": {"NULL": "NULL"}, + "PORTCHANNEL_MEMBER|PortChannel0003|Ethernet120": {"NULL": "NULL"}, + "PORTCHANNEL_MEMBER|PortChannel0004|Ethernet124": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0001": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0002": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0003": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0004": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0001|10.0.0.56/31": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0001|FC00::71/126": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0002|10.0.0.58/31": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0002|FC00::75/126": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0003|10.0.0.60/31": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0003|FC00::79/126": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0004|10.0.0.62/31": {"NULL": "NULL"}, + "PORTCHANNEL_INTERFACE|PortChannel0004|FC00::7D/126": {"NULL": "NULL"}, "DEBUG_COUNTER|DEBUG_0": { "type": "PORT_INGRESS_DROPS" }, diff --git a/tests/vlan_test.py b/tests/vlan_test.py new file mode 100644 index 0000000000..04aa16fa74 --- /dev/null +++ b/tests/vlan_test.py @@ -0,0 +1,461 @@ +import os +import traceback + +from click.testing import CliRunner + +import config.main as config +import show.main as show +from utilities_common.db import Db + +show_vlan_brief_output="""\ ++-----------+-----------------+------------+----------------+-----------------------+ +| VLAN ID | IP Address | Ports | Port Tagging | DHCP Helper Address | ++===========+=================+============+================+=======================+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | 192.0.0.1 | +| | fc02:1000::1/64 | Ethernet8 | untagged | 192.0.0.2 | +| | | Ethernet12 | untagged | 192.0.0.3 | +| | | Ethernet16 | untagged | 192.0.0.4 | ++-----------+-----------------+------------+----------------+-----------------------+ +""" + +show_vlan_brief_in_alias_mode_output="""\ ++-----------+-----------------+---------+----------------+-----------------------+ +| VLAN ID | IP Address | Ports | Port Tagging | DHCP Helper Address | ++===========+=================+=========+================+=======================+ +| 1000 | 192.168.0.1/21 | etp2 | untagged | 192.0.0.1 | +| | fc02:1000::1/64 | etp3 | untagged | 192.0.0.2 | +| | | etp4 | untagged | 192.0.0.3 | +| | | etp5 | untagged | 192.0.0.4 | ++-----------+-----------------+---------+----------------+-----------------------+ +""" + +show_vlan_brief_empty_output="""\ ++-----------+--------------+---------+----------------+-----------------------+ +| VLAN ID | IP Address | Ports | Port Tagging | DHCP Helper Address | ++===========+==============+=========+================+=======================+ ++-----------+--------------+---------+----------------+-----------------------+ +""" + +show_vlan_brief_with_portchannel_output="""\ ++-----------+-----------------+-----------------+----------------+-----------------------+ +| VLAN ID | IP Address | Ports | Port Tagging | DHCP Helper Address | ++===========+=================+=================+================+=======================+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | 192.0.0.1 | +| | fc02:1000::1/64 | Ethernet8 | untagged | 192.0.0.2 | +| | | Ethernet12 | untagged | 192.0.0.3 | +| | | Ethernet16 | untagged | 192.0.0.4 | +| | | PortChannel1001 | untagged | | ++-----------+-----------------+-----------------+----------------+-----------------------+ +""" + +show_vlan_config_output="""\ +Name VID +-------- ----- +Vlan1000 1000 +""" + +config_vlan_add_dhcp_relay_output="""\ +Added DHCP relay destination address 192.0.0.100 to Vlan1000 +Restarting DHCP relay service... +""" + +config_vlan_del_dhcp_relay_output="""\ +Removed DHCP relay destination address 192.0.0.100 from Vlan1000 +Restarting DHCP relay service... +""" + +show_vlan_brief_output_with_new_dhcp_relay_address="""\ ++-----------+-----------------+------------+----------------+-----------------------+ +| VLAN ID | IP Address | Ports | Port Tagging | DHCP Helper Address | ++===========+=================+============+================+=======================+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | 192.0.0.1 | +| | fc02:1000::1/64 | Ethernet8 | untagged | 192.0.0.2 | +| | | Ethernet12 | untagged | 192.0.0.3 | +| | | Ethernet16 | untagged | 192.0.0.4 | +| | | | | 192.0.0.100 | ++-----------+-----------------+------------+----------------+-----------------------+ +""" + +config_add_del_vlan_and_vlan_member_output="""\ ++-----------+-----------------+------------+----------------+-----------------------+ +| VLAN ID | IP Address | Ports | Port Tagging | DHCP Helper Address | ++===========+=================+============+================+=======================+ +| 1000 | 192.168.0.1/21 | Ethernet4 | untagged | 192.0.0.1 | +| | fc02:1000::1/64 | Ethernet8 | untagged | 192.0.0.2 | +| | | Ethernet12 | untagged | 192.0.0.3 | +| | | Ethernet16 | untagged | 192.0.0.4 | ++-----------+-----------------+------------+----------------+-----------------------+ +| 1001 | | Ethernet20 | untagged | | ++-----------+-----------------+------------+----------------+-----------------------+ +""" + +config_add_del_vlan_and_vlan_member_in_alias_mode_output="""\ ++-----------+-----------------+---------+----------------+-----------------------+ +| VLAN ID | IP Address | Ports | Port Tagging | DHCP Helper Address | ++===========+=================+=========+================+=======================+ +| 1000 | 192.168.0.1/21 | etp2 | untagged | 192.0.0.1 | +| | fc02:1000::1/64 | etp3 | untagged | 192.0.0.2 | +| | | etp4 | untagged | 192.0.0.3 | +| | | etp5 | untagged | 192.0.0.4 | ++-----------+-----------------+---------+----------------+-----------------------+ +| 1001 | | etp6 | untagged | | ++-----------+-----------------+---------+----------------+-----------------------+ +""" +class TestVlan(object): + @classmethod + def setup_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "1" + print("SETUP") + + def test_show_vlan(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vlan"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + def test_show_vlan_brief(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output + + def test_show_vlan_brief_verbose(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], ["--verbose"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output + + def test_show_vlan_brief_in_alias_mode(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["vlan"].commands["brief"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_in_alias_mode_output + os.environ['SONIC_CLI_IFACE_MODE'] = "" + + def test_show_vlan_config(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["vlan"].commands["config"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_config_output + + def test_config_vlan_add_vlan_with_invalid_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["4096"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + + def test_config_vlan_add_vlan_with_exist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1000"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1000 already exists" in result.output + + def test_config_vlan_del_vlan_with_invalid_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["4096"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + + def test_config_vlan_del_vlan_with_nonexist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1001 does not exist" in result.output + + def test_config_vlan_add_member_with_invalid_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["4096", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid VLAN ID 4096 (1-4094)" in result.output + + def test_config_vlan_add_member_with_nonexist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1001", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Vlan1001 does not exist" in result.output + + def test_config_vlan_add_exist_port_member(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1000", "Ethernet4"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet4 is already a member of Vlan1000" in result.output + + def test_config_vlan_add_nonexist_port_member(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], ["1000", "Ethernet3"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet3 does not exist" in result.output + + def test_config_vlan_add_nonexist_portchannel_member(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ + ["1000", "PortChannel1011"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel1011 does not exist" in result.output + + def test_config_vlan_add_portchannel_member(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ + ["1000", "PortChannel1001", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_with_portchannel_output + + def test_config_vlan_add_rif_portchannel_member(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], \ + ["1000", "PortChannel0001", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: PortChannel0001 is a router interface!" in result.output + + def test_config_vlan_del_vlan(self): + runner = CliRunner() + db = Db() + + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1000"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_empty_output + + def test_config_vlan_del_nonexist_vlan_member(self): + runner = CliRunner() + + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], \ + ["1000", "Ethernet0"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Ethernet0 is not a member of Vlan1000" in result.output + + def test_config_add_del_vlan_and_vlan_member(self): + runner = CliRunner() + db = Db() + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add Ethernet20 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "Ethernet20", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == config_add_del_vlan_and_vlan_member_output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001", "Ethernet20"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add del 1001 + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_output + + def test_config_add_del_vlan_and_vlan_member_in_alias_mode(self): + runner = CliRunner() + db = Db() + + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + + # add vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["add"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add etp6 to vlan 1001 + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["add"], + ["1001", "etp6", "--untagged"], obj=db) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == config_add_del_vlan_and_vlan_member_in_alias_mode_output + + # remove vlan member + result = runner.invoke(config.config.commands["vlan"].commands["member"].commands["del"], + ["1001", "etp6"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # add del 1001 + result = runner.invoke(config.config.commands["vlan"].commands["del"], ["1001"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_brief_in_alias_mode_output + + os.environ['SONIC_CLI_IFACE_MODE'] = "" + + def test_config_vlan_add_dhcp_relay_with_nonexist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["add"], + ["1001", "192.0.0.100"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Error: Vlan1001 doesn't exist" in result.output + + def test_config_vlan_add_dhcp_relay_with_invalid_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["add"], + ["4096", "192.0.0.100"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Error: Vlan4096 doesn't exist" in result.output + + def test_config_vlan_add_dhcp_relay_with_invalid_ip(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["add"], + ["1000", "192.0.0.1000"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Error: 192.0.0.1000 is invalid IP address" in result.output + + def test_config_vlan_add_dhcp_relay_with_exist_ip(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["add"], + ["1000", "192.0.0.1"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert "192.0.0.1 is already a DHCP relay destination for Vlan1000" in result.output + + def test_config_vlan_add_del_dhcp_relay_dest(self): + runner = CliRunner() + db = Db() + + # add new relay dest + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["add"], + ["1000", "192.0.0.100"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_vlan_add_dhcp_relay_output + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == show_vlan_brief_output_with_new_dhcp_relay_address + + # del relay dest + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["del"], + ["1000", "192.0.0.100"], obj=db) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == config_vlan_del_dhcp_relay_output + + # show output + result = runner.invoke(show.cli.commands["vlan"].commands["brief"], [], obj=db) + print(result.output) + assert result.output == show_vlan_brief_output + + def test_config_vlan_remove_nonexist_dhcp_relay_dest(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["del"], + ["1000", "192.0.0.100"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Error: 192.0.0.100 is not a DHCP relay destination for Vlan1000" in result.output + + def test_config_vlan_remove_dhcp_relay_dest_with_nonexist_vlanid(self): + runner = CliRunner() + result = runner.invoke(config.config.commands["vlan"].commands["dhcp_relay"].commands["del"], + ["1001", "192.0.0.1"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code != 0 + assert "Error: Vlan1001 doesn't exist" in result.output + + @classmethod + def teardown_class(cls): + os.environ['UTILITIES_UNIT_TESTING'] = "0" + print("TEARDOWN") diff --git a/utilities_common/cli.py b/utilities_common/cli.py index dd09a444c5..bbbb6df471 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -1,7 +1,16 @@ +import os +import sys +import netaddr +import subprocess + import click from utilities_common.db import Db +from swsssdk import ConfigDBConnector + +VLAN_SUB_INTERFACE_SEPARATOR = '.' + pass_db = click.make_pass_decorator(Db, ensure=True) class AbbreviationGroup(click.Group): @@ -41,3 +50,251 @@ def get_command(self, ctx, cmd_name): return click.Group.get_command(self, ctx, shortest) ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + +try: + import ConfigParser as configparser +except ImportError: + import configparser + +# This is from the aliases example: +# https://github.com/pallets/click/blob/57c6f09611fc47ca80db0bd010f05998b3c0aa95/examples/aliases/aliases.py +class Config(object): + """Object to hold CLI config""" + + def __init__(self): + self.path = os.getcwd() + self.aliases = {} + + def read_config(self, filename): + parser = configparser.RawConfigParser() + parser.read([filename]) + try: + self.aliases.update(parser.items('aliases')) + except configparser.NoSectionError: + pass + +# Global Config object +_config = None + +class AliasedGroup(click.Group): + """This subclass of click.Group supports abbreviations and + looking up aliases in a config file with a bit of magic. + """ + + def get_command(self, ctx, cmd_name): + global _config + + # If we haven't instantiated our global config, do it now and load current config + if _config is None: + _config = Config() + + # Load our config file + cfg_file = os.path.join(os.path.dirname(__file__), 'aliases.ini') + _config.read_config(cfg_file) + + # Try to get builtin commands as normal + rv = click.Group.get_command(self, ctx, cmd_name) + if rv is not None: + return rv + + # No builtin found. Look up an explicit command alias in the config + if cmd_name in _config.aliases: + actual_cmd = _config.aliases[cmd_name] + return click.Group.get_command(self, ctx, actual_cmd) + + # Alternative option: if we did not find an explicit alias we + # allow automatic abbreviation of the command. "status" for + # instance will match "st". We only allow that however if + # there is only one command. + matches = [x for x in self.list_commands(ctx) + if x.lower().startswith(cmd_name.lower())] + if not matches: + return None + elif len(matches) == 1: + return click.Group.get_command(self, ctx, matches[0]) + ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) + +class InterfaceAliasConverter(object): + """Class which handles conversion between interface name and alias""" + + def __init__(self, db=None): + + if db is None: + self.config_db = ConfigDBConnector() + self.config_db.connect() + else: + self.config_db = db.cfgdb + + self.alias_max_length = 0 + self.port_dict = self.config_db.get_table('PORT') + + if not self.port_dict: + click.echo(message="Warning: failed to retrieve PORT table from ConfigDB!", err=True) + self.port_dict = {} + + for port_name in self.port_dict.keys(): + try: + if self.alias_max_length < len( + self.port_dict[port_name]['alias']): + self.alias_max_length = len( + self.port_dict[port_name]['alias']) + except KeyError: + break + + def name_to_alias(self, interface_name): + """Return vendor interface alias if SONiC + interface name is given as argument + """ + vlan_id = '' + sub_intf_sep_idx = -1 + if interface_name is not None: + sub_intf_sep_idx = interface_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + if sub_intf_sep_idx != -1: + vlan_id = interface_name[sub_intf_sep_idx + 1:] + # interface_name holds the parent port name + interface_name = interface_name[:sub_intf_sep_idx] + + for port_name in self.port_dict.keys(): + if interface_name == port_name: + return self.port_dict[port_name]['alias'] if sub_intf_sep_idx == -1 \ + else self.port_dict[port_name]['alias'] + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id + + # interface_name not in port_dict. Just return interface_name + return interface_name if sub_intf_sep_idx == -1 else interface_name + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id + + def alias_to_name(self, interface_alias): + """Return SONiC interface name if vendor + port alias is given as argument + """ + vlan_id = '' + sub_intf_sep_idx = -1 + if interface_alias is not None: + sub_intf_sep_idx = interface_alias.find(VLAN_SUB_INTERFACE_SEPARATOR) + if sub_intf_sep_idx != -1: + vlan_id = interface_alias[sub_intf_sep_idx + 1:] + # interface_alias holds the parent port alias + interface_alias = interface_alias[:sub_intf_sep_idx] + + for port_name in self.port_dict.keys(): + if interface_alias == self.port_dict[port_name]['alias']: + return port_name if sub_intf_sep_idx == -1 else port_name + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id + + # interface_alias not in port_dict. Just return interface_alias + return interface_alias if sub_intf_sep_idx == -1 else interface_alias + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id + +def get_interface_naming_mode(): + mode = os.getenv('SONIC_CLI_IFACE_MODE') + if mode is None: + mode = "default" + return mode + +def is_ipaddress(val): + """ Validate if an entry is a valid IP """ + if not val: + return False + try: + netaddr.IPAddress(str(val)) + except netaddr.core.AddrFormatError: + return False + return True + + +def is_ip_prefix_in_key(key): + ''' + Function to check if IP address is present in the key. If it + is present, then the key would be a tuple or else, it shall be + be string + ''' + return (isinstance(key, tuple)) + +def is_valid_port(config_db, port): + """Check if port is in PORT table""" + + port_table = config_db.get_table('PORT') + if port in port_table.keys(): + return True + + return False + +def is_valid_portchannel(config_db, port): + """Check if port is in PORT_CHANNEL table""" + + pc_table = config_db.get_table('PORTCHANNEL') + if port in pc_table.keys(): + return True + + return False + +def is_vlanid_in_range(vid): + """Check if vlan id is valid or not""" + + if vid >= 1 and vid <= 4094: + return True + + return False + +def check_if_vlanid_exist(config_db, vlan): + """Check if vlan id exits in the config db or ot""" + + if len(config_db.get_entry('VLAN', vlan)) != 0: + return True + + return False + +def is_port_vlan_member(config_db, port, vlan): + """Check if port is a member of vlan""" + + vlan_ports_data = config_db.get_table('VLAN_MEMBER') + for key in vlan_ports_data.keys(): + if key[0] == vlan and key[1] == port: + return True + + return False + +def is_port_router_interface(config_db, port): + """Check if port is a router interface""" + + interface_table = config_db.get_table('INTERFACE') + for intf in interface_table.keys(): + if port == intf[0]: + return True + + return False + +def is_pc_router_interface(config_db, pc): + """Check if portchannel is a router interface""" + + pc_interface_table = config_db.get_table('PORTCHANNEL_INTERFACE') + for intf in pc_interface_table.keys(): + if pc == intf[0]: + return True + + return False + +def is_port_mirror_dst_port(config_db, port): + """ Check if port is already configured as mirror destination port """ + mirror_table = config_db.get_table('MIRROR_SESSION') + for _,v in mirror_table.items(): + if 'dst_port' in v and v['dst_port'] == port: + return True + + return False + +def run_command(command, display_cmd=False, ignore_error=False): + """Run bash command and print output to stdout + """ + + if display_cmd == True: + click.echo(click.style("Running command: ", fg='cyan') + click.style(command, fg='green')) + + if os.environ["UTILITIES_UNIT_TESTING"] == "1": + return + + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + (out, err) = proc.communicate() + + if len(out) > 0: + click.echo(out) + + if proc.returncode != 0 and not ignore_error: + sys.exit(proc.returncode) From 7ae8024af02109e6aee2b82cc3bc4d101207450c Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Tue, 11 Aug 2020 01:04:40 -0700 Subject: [PATCH 31/48] Update all references to new 'sonic-installer' file name (#1033) `sonic_installer` has been renamed `sonic-installer`. Update the application name everywhere it is used. --- scripts/fast-reboot | 4 ++-- scripts/reboot | 4 ++-- sonic_installer/exception.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 18d9b0724c..302cdf7f1f 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -315,9 +315,9 @@ function reboot_pre_check() exit ${EXIT_FILE_SYSTEM_FULL} fi - # Verify the next image by sonic_installer + # Verify the next image by sonic-installer INSTALLER_VERIFY_RC=0 - sonic_installer verify-next-image > /dev/null || INSTALLER_VERIFY_RC=$? + sonic-installer verify-next-image > /dev/null || INSTALLER_VERIFY_RC=$? if [[ INSTALLER_VERIFY_RC -ne 0 ]]; then error "Failed to verify next image. Exit code: $INSTALLER_VERIFY_RC" exit ${EXIT_SONIC_INSTALLER_VERIFY_REBOOT} diff --git a/scripts/reboot b/scripts/reboot index 0ad25836da..ee5cff2df4 100755 --- a/scripts/reboot +++ b/scripts/reboot @@ -80,8 +80,8 @@ function reboot_pre_check() fi rm ${filename} - # Verify the next image by sonic_installer - local message=$(sonic_installer verify-next-image 2>&1) + # Verify the next image by sonic-installer + local message=$(sonic-installer verify-next-image 2>&1) if [ $? -ne 0 ]; then VERBOSE=yes debug "Failed to verify next image: ${message}" exit ${EXIT_SONIC_INSTALLER_VERIFY_REBOOT} diff --git a/sonic_installer/exception.py b/sonic_installer/exception.py index b0806269f0..08eab7d2ff 100644 --- a/sonic_installer/exception.py +++ b/sonic_installer/exception.py @@ -1,5 +1,5 @@ """ -Module sonic_installer exceptions +Module sonic-installer exceptions """ class SonicRuntimeException(Exception): From fdda5aca66af8ebcc9a8ab597851f134090b61a0 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Tue, 11 Aug 2020 15:09:49 -0700 Subject: [PATCH 32/48] Python 3 compliance (#1043) As part of the migration from Python 2 to Python 3, this PR ensures that all Python files in sonic-utilities are Python 3-compliant, and work with both Python 2 and Python 3. Once this is merged, we can begin building a Python 3-based version of the sonic-utilities package. --- config/aaa.py | 8 ++--- config/main.py | 2 +- config/mlnx.py | 14 ++++----- consutil/main.py | 2 +- counterpoll/main.py | 2 +- crm/main.py | 32 ++++++++++---------- pcieutil/main.py | 10 +++---- pddf_thermalutil/main.py | 28 +++++++++--------- pfc/main.py | 20 ++++++------- pfcwd/main.py | 5 ++-- scripts/boot_part | 4 +-- scripts/decode-syseeprom | 18 ++++++------ scripts/fast-reboot-dump.py | 4 +-- scripts/fdbclear | 2 +- scripts/fdbshow | 8 ++--- scripts/gearboxutil | 10 +++---- scripts/intfstat | 34 +++++++++++----------- scripts/intfutil | 8 ++--- scripts/natclear | 8 ++--- scripts/natconfig | 58 ++++++++++++++++++------------------- scripts/natshow | 38 ++++++++++++------------ scripts/nbrshow | 10 +++---- scripts/pcmping | 18 ++++++------ scripts/pfcstat | 27 ++++++++--------- scripts/portstat | 26 ++++++++--------- scripts/psushow | 10 +++---- scripts/queuestat | 38 ++++++++++++------------ scripts/sfpshow | 2 +- scripts/sonic-kdump-config | 4 +-- scripts/teamshow | 4 +-- scripts/watermarkcfg | 4 +-- scripts/watermarkstat | 24 +++++++-------- sfputil/main.py | 50 ++++++++++++++++---------------- show/main.py | 18 ++++++------ show/mlnx.py | 10 +++---- ssdutil/main.py | 14 ++++----- tests/config_test.py | 10 +++---- utilities_common/cli.py | 2 +- 38 files changed, 292 insertions(+), 294 deletions(-) diff --git a/config/aaa.py b/config/aaa.py index 8cdd818ac0..fdb85c3f27 100644 --- a/config/aaa.py +++ b/config/aaa.py @@ -70,7 +70,7 @@ def fallback(option): def login(auth_protocol): """Switch login authentication [ {tacacs+, local} | default ]""" if len(auth_protocol) is 0: - print 'Not support empty argument' + click.echo('Argument "auth_protocol" is required') return if 'default' in auth_protocol: @@ -107,7 +107,7 @@ def timeout(ctx, second): elif second: add_table_kv('TACPLUS', 'global', 'timeout', second) else: - click.echo('Not support empty argument') + click.echo('Argument "second" is required') tacacs.add_command(timeout) default.add_command(timeout) @@ -122,7 +122,7 @@ def authtype(ctx, type): elif type: add_table_kv('TACPLUS', 'global', 'auth_type', type) else: - click.echo('Not support empty argument') + click.echo('Argument "type" is required') tacacs.add_command(authtype) default.add_command(authtype) @@ -137,7 +137,7 @@ def passkey(ctx, secret): elif secret: add_table_kv('TACPLUS', 'global', 'passkey', secret) else: - click.echo('Not support empty argument') + click.echo('Argument "secret" is required') tacacs.add_command(passkey) default.add_command(passkey) diff --git a/config/main.py b/config/main.py index fc451f37b7..6822077099 100755 --- a/config/main.py +++ b/config/main.py @@ -818,7 +818,7 @@ def config(ctx): try: version_info = device_info.get_sonic_version_info() asic_type = version_info['asic_type'] - except KeyError, TypeError: + except (KeyError, TypeError): raise click.Abort() if asic_type == 'mellanox': diff --git a/config/mlnx.py b/config/mlnx.py index bd8c8c9425..8c97d3b382 100644 --- a/config/mlnx.py +++ b/config/mlnx.py @@ -100,7 +100,7 @@ def sniffer_env_variable_set(enable, env_variable_name, env_variable_string=""): env_variable_exist_string = env_variable_read(env_variable_name) if env_variable_exist_string: if enable is True: - print "sniffer is already enabled, do nothing" + click.echo("sniffer is already enabled, do nothing") ignore = True else: env_variable_delete(env_variable_exist_string) @@ -108,7 +108,7 @@ def sniffer_env_variable_set(enable, env_variable_name, env_variable_string=""): if enable is True: env_variable_write(env_variable_string) else: - print "sniffer is already disabled, do nothing" + click.echo("sniffer is already disabled, do nothing") ignore = True if not ignore: @@ -164,9 +164,9 @@ def sdk(): prompt='Swss service will be restarted, continue?') def enable(): """Enable SDK Sniffer""" - print "Enabling SDK sniffer" + click.echo("Enabling SDK sniffer") sdk_sniffer_enable() - print "Note: the sniffer file may exhaust the space on /var/log, please disable it when you are done with this sniffering." + click.echo("Note: the sniffer file may exhaust the space on /var/log, please disable it when you are done with this sniffering.") @sdk.command() @@ -174,7 +174,7 @@ def enable(): prompt='Swss service will be restarted, continue?') def disable(): """Disable SDK Sniffer""" - print "Disabling SDK sniffer" + click.echo("Disabling SDK sniffer") sdk_sniffer_disable() @@ -198,7 +198,7 @@ def sdk_sniffer_enable(): err = restart_swss() if err is not 0: return - print 'SDK sniffer is Enabled, recording file is %s.' % sdk_sniffer_filename + click.echo('SDK sniffer is Enabled, recording file is %s.' % sdk_sniffer_filename) else: pass @@ -211,7 +211,7 @@ def sdk_sniffer_disable(): err = restart_swss() if err is not 0: return - print "SDK sniffer is Disabled." + click.echo("SDK sniffer is Disabled.") else: pass diff --git a/consutil/main.py b/consutil/main.py index c9a914d478..faddeb33e9 100644 --- a/consutil/main.py +++ b/consutil/main.py @@ -20,7 +20,7 @@ def consutil(): """consutil - Command-line utility for interacting with switches via console device""" if os.geteuid() != 0: - print "Root privileges are required for this operation" + click.echo("Root privileges are required for this operation") sys.exit(1) # 'show' subcommand diff --git a/counterpoll/main.py b/counterpoll/main.py index 55f1aefeba..1f56fadaa2 100644 --- a/counterpoll/main.py +++ b/counterpoll/main.py @@ -186,5 +186,5 @@ def show(): if buffer_pool_wm_info: data.append(["BUFFER_POOL_WATERMARK_STAT", buffer_pool_wm_info.get("POLL_INTERVAL", DEFLT_10_SEC), buffer_pool_wm_info.get("FLEX_COUNTER_STATUS", DISABLE)]) - print tabulate(data, headers=header, tablefmt="simple", missingval="") + click.echo(tabulate(data, headers=header, tablefmt="simple", missingval="")) diff --git a/crm/main.py b/crm/main.py index 241874e6e8..33fbfb5de5 100644 --- a/crm/main.py +++ b/crm/main.py @@ -29,9 +29,9 @@ def show_summary(self): crm_info = configdb.get_entry('CRM', 'Config') if crm_info: - print '\nPolling Interval: ' + crm_info['polling_interval'] + ' second(s)\n' + click.echo('\nPolling Interval: ' + crm_info['polling_interval'] + ' second(s)\n') else: - print '\nError! Could not get CRM configuration.\n' + click.echo('\nError! Could not get CRM configuration.\n') def show_thresholds(self, resource): """ @@ -54,11 +54,11 @@ def show_thresholds(self, resource): else: data.append([resource, crm_info[resource + "_threshold_type"], crm_info[resource + "_low_threshold"], crm_info[resource + "_high_threshold"]]) else: - print '\nError! Could not get CRM configuration.' + click.echo('\nError! Could not get CRM configuration.') - print '\n' - print tabulate(data, headers=header, tablefmt="simple", missingval="") - print '\n' + click.echo() + click.echo(tabulate(data, headers=header, tablefmt="simple", missingval="")) + click.echo() def show_resources(self, resource): """ @@ -80,11 +80,11 @@ def show_resources(self, resource): else: data.append([resource, crm_stats['crm_stats_' + resource + "_used"], crm_stats['crm_stats_' + resource + "_available"]]) else: - print '\nCRM counters are not ready. They would be populated after the polling interval.' + click.echo('\nCRM counters are not ready. They would be populated after the polling interval.') - print '\n' - print tabulate(data, headers=header, tablefmt="simple", missingval="") - print '\n' + click.echo() + click.echo(tabulate(data, headers=header, tablefmt="simple", missingval="")) + click.echo() def show_acl_resources(self): """ @@ -108,9 +108,9 @@ def show_acl_resources(self): crm_stats['crm_stats_' + res + "_available"] ]) - print '\n' - print tabulate(data, headers=header, tablefmt="simple", missingval="") - print '\n' + click.echo() + click.echo(tabulate(data, headers=header, tablefmt="simple", missingval="")) + click.echo() def show_acl_table_resources(self): """ @@ -136,9 +136,9 @@ def show_acl_table_resources(self): if ('crm_stats_' + res + '_used' in crm_stats) and ('crm_stats_' + res + '_available' in crm_stats): data.append([id, res, crm_stats['crm_stats_' + res + '_used'], crm_stats['crm_stats_' + res + '_available']]) - print '\n' - print tabulate(data, headers=header, tablefmt="simple", missingval="") - print '\n' + click.echo() + click.echo(tabulate(data, headers=header, tablefmt="simple", missingval="")) + click.echo() @click.group() diff --git a/pcieutil/main.py b/pcieutil/main.py index 5eedd678f1..ca1dd0fb96 100644 --- a/pcieutil/main.py +++ b/pcieutil/main.py @@ -33,7 +33,7 @@ def print_result(name, result): sys.stdout.write(string) for i in xrange(int(length)): sys.stdout.write("-") - print(' [%s]' % result) + click.echo(' [%s]' % result) # ==================== Methods for initialization ==================== @@ -103,7 +103,7 @@ def pcie_show(): Fn = item["fn"] Name = item["name"] Id = item["id"] - print "bus:dev.fn %s:%s.%s - dev_id=0x%s, %s" % (Bus, Dev, Fn, Id, Name) + click.echo("bus:dev.fn %s:%s.%s - dev_id=0x%s, %s" % (Bus, Dev, Fn, Id, Name)) # Show PCIE Vender ID and Device ID @@ -122,9 +122,9 @@ def pcie_check(): log.log_warning("PCIe Device: " + item["name"] + " Not Found") err += 1 if err: - print "PCIe Device Checking All Test ----------->>> FAILED" + click.echo("PCIe Device Checking All Test ----------->>> FAILED") else: - print "PCIe Device Checking All Test ----------->>> PASSED" + click.echo("PCIe Device Checking All Test ----------->>> PASSED") @cli.command() @@ -132,7 +132,7 @@ def pcie_check(): def pcie_generate(): '''Generate config file with current pci device''' platform_pcieutil.dump_conf_yaml() - print "Generate config file pcie.yaml under path %s" % platform_plugins_path + click.echo("Generate config file pcie.yaml under path %s" % platform_plugins_path) if __name__ == '__main__': diff --git a/pddf_thermalutil/main.py b/pddf_thermalutil/main.py index 1be6381eec..ee32769635 100644 --- a/pddf_thermalutil/main.py +++ b/pddf_thermalutil/main.py @@ -135,19 +135,19 @@ def gettemp(index): except NotImplementedError: pass - else: - label, value = platform_thermalutil.show_thermal_temp_values(thermal) + else: + label, value = platform_thermalutil.show_thermal_temp_values(thermal) - if label is None: - status_table.append([thermal_name, value]) - else: - status_table.append([thermal_name, label, value]) + if label is None: + status_table.append([thermal_name, value]) + else: + status_table.append([thermal_name, label, value]) if status_table: - if label is None: - header = ['Temp Sensor', 'Value'] - else: - header = ['Temp Sensor', 'Label', 'Value'] + if label is None: + header = ['Temp Sensor', 'Value'] + else: + header = ['Temp Sensor', 'Label', 'Value'] click.echo(tabulate(status_table, header, tablefmt="simple")) @cli.group() @@ -159,11 +159,11 @@ def debug(): def dump_sysfs(): """Dump all Temp Sensor related SysFS paths""" if platform_chassis is not None: - supported_thermal = range(1, _wrapper_get_num_thermals()+ 1) - for index in supported_thermal: - status = platform_chassis.get_thermal(index-1).dump_sysfs() + supported_thermal = range(1, _wrapper_get_num_thermals()+ 1) + for index in supported_thermal: + status = platform_chassis.get_thermal(index-1).dump_sysfs() else: - status = platform_thermalutil.dump_sysfs() + status = platform_thermalutil.dump_sysfs() if status: for i in status: diff --git a/pfc/main.py b/pfc/main.py index 9da2147070..856c16ce95 100644 --- a/pfc/main.py +++ b/pfc/main.py @@ -45,16 +45,16 @@ def showPfcAsym(interface): sorted_table = natsorted(table) - print '\n' - print tabulate(sorted_table, headers=header, tablefmt="simple", missingval="") - print '\n' + click.echo() + click.echo(tabulate(sorted_table, headers=header, tablefmt="simple", missingval="")) + click.echo() def configPfcPrio(status, interface, priority): configdb = swsssdk.ConfigDBConnector() configdb.connect() if interface not in configdb.get_keys('PORT_QOS_MAP'): - print 'Cannot find interface {0}'.format(interface) + click.echo('Cannot find interface {0}'.format(interface)) return """Current lossless priorities on the interface""" @@ -65,11 +65,11 @@ def configPfcPrio(status, interface, priority): enable_prio = [x.strip() for x in enable_prio if x.strip()] if status == 'on' and priority in enable_prio: - print 'Priority {0} has already been enabled on {1}'.format(priority, interface) + click.echo('Priority {0} has already been enabled on {1}'.format(priority, interface)) return if status == 'off' and priority not in enable_prio: - print 'Priority {0} is not enabled on {1}'.format(priority, interface) + click.echo('Priority {0} is not enabled on {1}'.format(priority, interface)) return if status == 'on': @@ -99,7 +99,7 @@ def showPfcPrio(interface): """The user specifies an interface but we cannot find it""" if interface and interface not in intfs: - print 'Cannot find interface {0}'.format(interface) + click.echo('Cannot find interface {0}'.format(interface)) return if interface: @@ -110,9 +110,9 @@ def showPfcPrio(interface): table.append([intf, entry.get('pfc_enable', 'N/A')]) sorted_table = natsorted(table) - print '\n' - print tabulate(sorted_table, headers=header, tablefmt="simple", missingval="") - print '\n' + click.echo() + click.echo(tabulate(sorted_table, headers=header, tablefmt="simple", missingval="")) + click.echo() @click.group() def cli(): diff --git a/pfcwd/main.py b/pfcwd/main.py index fbb944009c..1856ff0f45 100644 --- a/pfcwd/main.py +++ b/pfcwd/main.py @@ -3,7 +3,6 @@ import click import swsssdk import os -import sys from tabulate import tabulate from natsort import natsorted @@ -171,7 +170,7 @@ def start(action, restoration_time, ports, detection_time): pfcwd_info['restoration_time'] = restoration_time else: pfcwd_info['restoration_time'] = 2 * detection_time - print "restoration time not defined; default to 2 times detection time: %d ms" % (2 * detection_time) + click.echo("restoration time not defined; default to 2 times detection time: %d ms" % (2 * detection_time)) for port in ports: if port == "all": @@ -209,7 +208,7 @@ def interval(poll_interval): entry_min = restoration_time_entry_value entry_min_str = "restoration time" if entry_min < poll_interval: - print >> sys.stderr, "unable to use polling interval = {}ms, value is bigger than one of the configured {} values, please choose a smaller polling_interval".format(poll_interval,entry_min_str) + click.echo("unable to use polling interval = {}ms, value is bigger than one of the configured {} values, please choose a smaller polling_interval".format(poll_interval,entry_min_str), err=True) exit(1) pfcwd_info['POLL_INTERVAL'] = poll_interval diff --git a/scripts/boot_part b/scripts/boot_part index 5bcae549c2..e9e51a9362 100755 --- a/scripts/boot_part +++ b/scripts/boot_part @@ -52,7 +52,7 @@ def print_partitions(blkdev): index = m.group(3) if name != blkdev or not index: continue - print index, label + print(index, label) ## Get the current boot partition index def get_boot_partition(blkdev): @@ -117,7 +117,7 @@ def main(): if cur is None: logger.error('Failed to get boot partition') return -1 - print 'Current rootfs partition is: {0}'.format(cur) + print('Current rootfs partition is: {0}'.format(cur)) ## Handle the command line if args.index is None: diff --git a/scripts/decode-syseeprom b/scripts/decode-syseeprom index bb542fd4a4..2dc892f2aa 100755 --- a/scripts/decode-syseeprom +++ b/scripts/decode-syseeprom @@ -92,7 +92,7 @@ def run(target, opts, args, support_eeprom_db): return 0 status = target.check_status() - if status <> 'ok': + if status != 'ok': sys.stderr.write("Device is not ready: " + status + "\n") exit(0) @@ -126,30 +126,30 @@ def run(target, opts, args, support_eeprom_db): if opts.init: err = target.update_eeprom_db(e) if err: - print "Failed to update eeprom database" + print("Failed to update eeprom database") return -1 elif opts.mgmtmac: mm = target.mgmtaddrstr(e) if mm != None: - print mm + print(mm) elif opts.serial: try: serial = target.serial_number_str(e) except NotImplementedError as e: - print e + print(e) else: - print serial or "Undefined." + print(serial or "Undefined.") elif opts.modelstr: mm = target.modelstr(e) if mm != None: - print mm + print(mm) else: target.decode_eeprom(e) (is_valid, valid_crc) = target.is_checksum_valid(e) if is_valid: - print '(checksum valid)' + print('(checksum valid)') else: - print '(*** checksum invalid)' + print('(*** checksum invalid)') # + ', should be 0x' + binascii.b2a_hex(array('I', [valid_crc])).upper() + ')' return 0 @@ -172,6 +172,6 @@ if __name__ == "__main__": except KeyboardInterrupt: sys.stderr.write("\nInterrupted\n") exit(1) - except (RuntimeError, OSError, IOError), errstr: + except (RuntimeError, OSError, IOError) as errstr: sys.stderr.write("%s : ERROR : %s\n" % (sys.argv[0], str(errstr))) exit(1) diff --git a/scripts/fast-reboot-dump.py b/scripts/fast-reboot-dump.py index 83387ad755..4a6a356d13 100644 --- a/scripts/fast-reboot-dump.py +++ b/scripts/fast-reboot-dump.py @@ -94,7 +94,7 @@ def get_map_bridge_port_id_2_iface_name(db): if port_id in port_id_2_iface: bridge_port_id_2_iface_name[bridge_port_id] = port_id_2_iface[port_id] else: - print "Not found" + print("Not found") return bridge_port_id_2_iface_name @@ -268,7 +268,7 @@ def main(): args = parser.parse_args() root_dir = args.target if not os.path.isdir(root_dir): - print "Target directory '%s' not found" % root_dir + print("Target directory '%s' not found" % root_dir) return 3 all_available_macs, map_mac_ip_per_vlan = generate_fdb_entries(root_dir + '/fdb.json') arp_entries = generate_arp_entries(root_dir + '/arp.json', all_available_macs) diff --git a/scripts/fdbclear b/scripts/fdbclear index de5d380bef..4b723cca55 100644 --- a/scripts/fdbclear +++ b/scripts/fdbclear @@ -49,7 +49,7 @@ def main(): fdb.send_notification("ALL", "ALL") print("FDB entries are cleared.") except Exception as e: - print e.message + print(e.message) sys.exit(1) if __name__ == "__main__": diff --git a/scripts/fdbshow b/scripts/fdbshow index 6743caf3cd..383a26ae9b 100755 --- a/scripts/fdbshow +++ b/scripts/fdbshow @@ -87,7 +87,7 @@ class FdbShow(object): vlan_id = port_util.get_vlan_id_from_bvid(self.db, fdb["bvid"]) except Exception: vlan_id = fdb["bvid"] - print "Failed to get Vlan id for bvid {}\n".format(fdb["bvid"]) + print("Failed to get Vlan id for bvid {}\n".format(fdb["bvid"])) self.bridge_mac_list.append((int(vlan_id),) + (fdb["mac"],) + (if_name,) + (fdb_type,)) self.bridge_mac_list.sort(key = lambda x: x[0]) @@ -129,8 +129,8 @@ class FdbShow(object): self.FDB_COUNT += 1 output.append([self.FDB_COUNT, fdb[0], fdb[1], fdb[2], fdb[3]]) - print tabulate(output, self.HEADER) - print "Total number of entries {0} ".format(self.FDB_COUNT) + print(tabulate(output, self.HEADER)) + print("Total number of entries {0}".format(self.FDB_COUNT)) def main(): @@ -145,7 +145,7 @@ def main(): fdb = FdbShow() fdb.display(args.vlan, args.port) except Exception as e: - print e.message + print(e.message) sys.exit(1) if __name__ == "__main__": diff --git a/scripts/gearboxutil b/scripts/gearboxutil index c143ff744d..678b6b6484 100755 --- a/scripts/gearboxutil +++ b/scripts/gearboxutil @@ -123,7 +123,7 @@ class PhyStatus(object): # Sorting and tabulating the result table. sorted_table = natsorted(table) - print tabulate(sorted_table, phy_header_status, tablefmt="simple", stralign='right') + print(tabulate(sorted_table, phy_header_status, tablefmt="simple", stralign='right')) def __init__(self): self.appl_db = db_connect_appl() @@ -180,7 +180,7 @@ class InterfaceStatus(object): # Sorting and tabulating the result table. sorted_table = natsorted(table) - print tabulate(sorted_table, intf_header_status, tablefmt="simple", stralign='right') + print(tabulate(sorted_table, intf_header_status, tablefmt="simple", stralign='right')) def __init__(self): self.appl_db = db_connect_appl() @@ -201,17 +201,17 @@ def main(args): """ if len(args) == 0: - print "No valid arguments provided" + print("No valid arguments provided") return cmd1 = args[0] if cmd1 != "phys" and cmd1 != "interfaces": - print "No valid command provided" + print("No valid command provided") return cmd2 = args[1] if cmd2 != "status" and cmd2 != "counters": - print "No valid command provided" + print("No valid command provided") return if cmd1 == "phys" and cmd2 == "status": diff --git a/scripts/intfstat b/scripts/intfstat index 3591016b7e..6b5d8215f4 100755 --- a/scripts/intfstat +++ b/scripts/intfstat @@ -93,11 +93,11 @@ class Intfstat(object): if counter_rif_name_map is None: - print "No %s in the DB!" % COUNTERS_RIF_NAME_MAP + print("No %s in the DB!" % COUNTERS_RIF_NAME_MAP) sys.exit(1) if rif and not rif in counter_rif_name_map: - print "Interface %s missing from %s! Make sure it exists" % (rif, COUNTERS_RIF_NAME_MAP) + print("Interface %s missing from %s! Make sure it exists" % (rif, COUNTERS_RIF_NAME_MAP)) sys.exit(2) if rif: @@ -140,10 +140,10 @@ class Intfstat(object): data.tx_p_ok, STATUS_NA, STATUS_NA, data.tx_p_err)) if use_json: - print table_as_json(table, header) + print(table_as_json(table, header)) else: - print tabulate(table, header, tablefmt='simple', stralign='right') + print(tabulate(table, header, tablefmt='simple', stralign='right')) def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, use_json): """ @@ -182,9 +182,9 @@ class Intfstat(object): STATUS_NA, cntr.tx_p_err)) if use_json: - print table_as_json(table, header) + print(table_as_json(table, header)) else: - print tabulate(table, header, tablefmt='simple', stralign='right') + print(tabulate(table, header, tablefmt='simple', stralign='right')) def cnstat_single_interface(self, rif, cnstat_new_dict, cnstat_old_dict): @@ -218,8 +218,8 @@ class Intfstat(object): body = body % (cntr.rx_p_ok, cntr.rx_b_ok, cntr.rx_p_err,cntr.rx_b_err, cntr.tx_p_ok, cntr.tx_b_ok, cntr.tx_p_err, cntr.tx_b_err) - print header - print body + print(header) + print(body) def main(): @@ -276,7 +276,7 @@ def main(): os.rmdir(cnstat_dir) sys.exit(0) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(e) if delete_saved_stats: @@ -284,7 +284,7 @@ def main(): os.remove(cnstat_fqn_file) except IOError as e: if e.errno != ENOENT: - print e.errno, e + print(e.errno, e) sys.exit(1) finally: if os.listdir(cnstat_dir) == []: @@ -299,7 +299,7 @@ def main(): try: os.makedirs(cnstat_dir) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(1) if save_fresh_stats: @@ -308,24 +308,24 @@ def main(): except IOError as e: sys.exit(e.errno) else: - print "Cleared counters" + print("Cleared counters") sys.exit(0) if wait_time_in_seconds == 0: if os.path.isfile(cnstat_fqn_file): try: cnstat_cached_dict = pickle.load(open(cnstat_fqn_file, 'r')) - print "Last cached time was " + str(cnstat_cached_dict.get('time')) + print("Last cached time was " + str(cnstat_cached_dict.get('time'))) if interface_name: intfstat.cnstat_single_interface(interface_name, cnstat_dict, cnstat_cached_dict) else: intfstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, use_json) except IOError as e: - print e.errno, e + print(e.errno, e) else: if tag_name: - print "\nFile '%s' does not exist" % cnstat_fqn_file - print "Did you run 'intfstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name) + print("\nFile '%s' does not exist" % cnstat_fqn_file) + print("Did you run 'intfstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name)) else: if interface_name: intfstat.cnstat_single_interface(interface_name, cnstat_dict, None) @@ -334,7 +334,7 @@ def main(): else: #wait for the specified time and then gather the new stats and output the difference. time.sleep(wait_time_in_seconds) - print "The rates are calculated within %s seconds period" % wait_time_in_seconds + print("The rates are calculated within %s seconds period" % wait_time_in_seconds) cnstat_new_dict = intfstat.get_cnstat(rif=interface_name) if interface_name: intfstat.cnstat_single_interface(interface_name, cnstat_new_dict, cnstat_dict) diff --git a/scripts/intfutil b/scripts/intfutil index f35702aa0c..af11c1322a 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -409,7 +409,7 @@ class IntfStatus(object): # Sorting and tabulating the result table. sorted_table = natsorted(table) - print tabulate(sorted_table, header_stat if not sub_intf_only else header_stat_sub_intf, tablefmt="simple", stralign='right') + print(tabulate(sorted_table, header_stat if not sub_intf_only else header_stat_sub_intf, tablefmt="simple", stralign='right')) def __init__(self, intf_name): @@ -494,7 +494,7 @@ class IntfDescription(object): # Sorting and tabulating the result table. sorted_table = natsorted(table) - print tabulate(sorted_table, header_desc, tablefmt="simple", stralign='right') + print(tabulate(sorted_table, header_desc, tablefmt="simple", stralign='right')) def __init__(self, intf_name): @@ -519,12 +519,12 @@ class IntfDescription(object): def main(args): if len(args) == 0: - print "No valid arguments provided" + print("No valid arguments provided") return command = args[0] if command != "status" and command != "description": - print "No valid command provided" + print("No valid command provided") return intf_name = args[1] if len(args) == 2 else None diff --git a/scripts/natclear b/scripts/natclear index 1a22f7f100..c18ec7ba58 100644 --- a/scripts/natclear +++ b/scripts/natclear @@ -57,14 +57,12 @@ def main(): nat = NatClear() if clear_translations: nat.send_entries_notification("ENTRIES", "ALL") - print "" - print("Dynamic NAT entries are cleared.") + print("\nDynamic NAT entries are cleared.") elif clear_statistics: nat.send_statistics_notification("STATISTICS", "ALL") - print "" - print("NAT statistics are cleared.") + print("\nNAT statistics are cleared.") except Exception as e: - print e.message + print(e.message) sys.exit(1) if __name__ == "__main__": diff --git a/scripts/natconfig b/scripts/natconfig index b9f7869e55..6a2467e21b 100644 --- a/scripts/natconfig +++ b/scripts/natconfig @@ -238,9 +238,9 @@ class NatConfig(object): for napt in self.static_napt_data: output.append([napt[0], napt[1], napt[2], napt[3], napt[4], napt[5], napt[6]]) - print "" - print tabulate(output, HEADER) - print "" + print() + print(tabulate(output, HEADER)) + print() def display_pool(self): """ @@ -253,9 +253,9 @@ class NatConfig(object): for nat in self.nat_pool_data: output.append([nat[0], nat[1], nat[2]]) - print "" - print tabulate(output, HEADER) - print "" + print() + print(tabulate(output, HEADER)) + print() def display_binding(self): """ @@ -268,9 +268,9 @@ class NatConfig(object): for nat in self.nat_binding_data: output.append([nat[0], nat[1], nat[2], nat[3], nat[4]]) - print "" - print tabulate(output, HEADER) - print "" + print() + print(tabulate(output, HEADER)) + print() def display_global(self): """ @@ -280,31 +280,31 @@ class NatConfig(object): global_data = self.config_db.get_entry('NAT_GLOBAL', 'Values') if global_data: - print "" + print() if 'admin_mode' in global_data: - print "Admin Mode :", global_data['admin_mode'] + print("Admin Mode :", global_data['admin_mode']) else: - print "Admin Mode : disabled" + print("Admin Mode : disabled") if 'nat_timeout' in global_data: - print "Global Timeout :", global_data['nat_timeout'], "secs" + print("Global Timeout :", global_data['nat_timeout'], "secs") else: - print "Global Timeout : 600 secs" + print("Global Timeout : 600 secs") if 'nat_tcp_timeout' in global_data: - print "TCP Timeout :", global_data['nat_tcp_timeout'], "secs" + print("TCP Timeout :", global_data['nat_tcp_timeout'], "secs") else: - print "TCP Timeout : 86400 secs" + print("TCP Timeout : 86400 secs") if 'nat_udp_timeout' in global_data: - print "UDP Timeout :", global_data['nat_udp_timeout'], "secs" + print("UDP Timeout :", global_data['nat_udp_timeout'], "secs") else: - print "UDP Timeout : 300 secs" - print "" + print("UDP Timeout : 300 secs") + print() else: - print "" - print "Admin Mode : disabled" - print "Global Timeout : 600 secs" - print "TCP Timeout : 86400 secs" - print "UDP Timeout : 300 secs" - print "" + print() + print("Admin Mode : disabled") + print("Global Timeout : 600 secs") + print("TCP Timeout : 86400 secs") + print("UDP Timeout : 300 secs") + print() return def display_nat_zone(self): @@ -318,9 +318,9 @@ class NatConfig(object): for nat in self.nat_zone_data: output.append([nat[0], nat[1]]) - print "" - print tabulate(output, HEADER) - print "" + print() + print(tabulate(output, HEADER)) + print() def main(): parser = argparse.ArgumentParser(description='Display the nat configuration information', @@ -366,7 +366,7 @@ def main(): nat.fetch_nat_zone() nat.display_nat_zone() except Exception as e: - print e.message + print(e.message) sys.exit(1) if __name__ == "__main__": diff --git a/scripts/natshow b/scripts/natshow index 02945a524b..74888f0a7f 100644 --- a/scripts/natshow +++ b/scripts/natshow @@ -329,19 +329,19 @@ class NatShow(object): totalEntries = int(self.static_nat_entries) + int(self.dynamic_nat_entries) + int(self.static_napt_entries) + int(self.dynamic_napt_entries) totalEntries += (int(self.static_twice_nat_entries) + int(self.dynamic_twice_nat_entries) + int(self.static_twice_napt_entries) + int(self.dynamic_twice_napt_entries)) - print "" - print "Static NAT Entries ..................... {}".format(self.static_nat_entries) - print "Static NAPT Entries ..................... {}".format(self.static_napt_entries) - print "Dynamic NAT Entries ..................... {}".format(self.dynamic_nat_entries) - print "Dynamic NAPT Entries ..................... {}".format(self.dynamic_napt_entries) - print "Static Twice NAT Entries ..................... {}".format(self.static_twice_nat_entries) - print "Static Twice NAPT Entries ..................... {}".format(self.static_twice_napt_entries) - print "Dynamic Twice NAT Entries ..................... {}".format(self.dynamic_twice_nat_entries) - print "Dynamic Twice NAPT Entries ..................... {}".format(self.dynamic_twice_napt_entries) - print "Total SNAT/SNAPT Entries ..................... {}".format(self.snat_entries) - print "Total DNAT/DNAPT Entries ..................... {}".format(self.dnat_entries) - print "Total Entries ..................... {}".format(totalEntries) - print "" + print() + print("Static NAT Entries ..................... {}".format(self.static_nat_entries)) + print("Static NAPT Entries ..................... {}".format(self.static_napt_entries)) + print("Dynamic NAT Entries ..................... {}".format(self.dynamic_nat_entries)) + print("Dynamic NAPT Entries ..................... {}".format(self.dynamic_napt_entries)) + print("Static Twice NAT Entries ..................... {}".format(self.static_twice_nat_entries)) + print("Static Twice NAPT Entries ..................... {}".format(self.static_twice_napt_entries)) + print("Dynamic Twice NAT Entries ..................... {}".format(self.dynamic_twice_nat_entries)) + print("Dynamic Twice NAPT Entries ..................... {}".format(self.dynamic_twice_napt_entries)) + print("Total SNAT/SNAPT Entries ..................... {}".format(self.snat_entries)) + print("Total DNAT/DNAPT Entries ..................... {}".format(self.dnat_entries)) + print("Total Entries ..................... {}".format(totalEntries)) + print() def display_translations(self): """ @@ -354,8 +354,8 @@ class NatShow(object): for nat in self.nat_entries_list: output.append([nat[0], nat[1], nat[2], nat[3], nat[4]]) - print tabulate(output, HEADER) - print "" + print(tabulate(output, HEADER)) + print() def display_statistics(self): """ @@ -368,9 +368,9 @@ class NatShow(object): for nat in self.nat_statistics_list: output.append([nat[0], nat[1], nat[2], nat[3], nat[4]]) - print "" - print tabulate(output, HEADER) - print "" + print() + print(tabulate(output, HEADER)) + print() def main(): parser = argparse.ArgumentParser(description='Display the nat information', @@ -409,7 +409,7 @@ def main(): nat.display_count() except Exception as e: - print e.message + print(e.message) sys.exit(1) if __name__ == "__main__": diff --git a/scripts/nbrshow b/scripts/nbrshow index b607070150..8d1abe7562 100644 --- a/scripts/nbrshow +++ b/scripts/nbrshow @@ -94,7 +94,7 @@ class NbrBase(object): vlan_id = port_util.get_vlan_id_from_bvid(self.db, fdb["bvid"]) except Exception: vlan_id = fdb["bvid"] - print "Failed to get Vlan id for bvid {}\n".format(fdb["bvid"]) + print("Failed to get Vlan id for bvid {}\n".format(fdb["bvid"])) self.bridge_mac_list.append((int(vlan_id),) + (fdb["mac"],) + (if_name,)) return @@ -141,11 +141,11 @@ class NbrBase(object): self.nbrdata = natsorted(output, key=lambda x: x[0]) - print tabulate(self.nbrdata, self.HEADER) - print "Total number of entries {0} ".format(self.NBR_COUNT) + print(tabulate(self.nbrdata, self.HEADER)) + print("Total number of entries {0} ".format(self.NBR_COUNT)) def display_err(self): - print "Error fetching Neighbors: {} ".format(self.err) + print("Error fetching Neighbors: {} ".format(self.err)) class ArpShow(NbrBase): @@ -258,7 +258,7 @@ def main(): arp.display() except Exception as e: - print e.message + print(e.message) sys.exit(1) diff --git a/scripts/pcmping b/scripts/pcmping index 2ec42712d6..06fd61bfd1 100755 --- a/scripts/pcmping +++ b/scripts/pcmping @@ -48,7 +48,7 @@ def find_probe_packet(interface, dst_out, dst_in, sockets, exp_socket, max_iter) src_out = dst_in decap_valid = False for x in range(0, max_iter): - print "---------- Attempting to create probe packet that goes through %s, iteration: %d ---------" % (interface, x) + print("---------- Attempting to create probe packet that goes through %s, iteration: %d ---------" % (interface, x)) ip_src = socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) ip_src =ipaddress.IPv4Address(unicode(ip_src,'utf-8')) while ip_src == ipaddress.IPv4Address(unicode(dst_in,'utf-8')) or \ @@ -70,10 +70,10 @@ def find_probe_packet(interface, dst_out, dst_in, sockets, exp_socket, max_iter) decap_valid = True if decap_valid: - print ("Decap works properly. link %s is unreachable, Please check!\n" % interface) + print("Decap works properly. link %s is unreachable, Please check!\n" % interface) sys.exit(0) else: - print ("Decap using %s doesn't work properly, Please Check!\n" % (dst_out)) + print("Decap using %s doesn't work properly, Please Check!\n" % (dst_out)) sys.exit(0) def get_portchannel(interface): @@ -126,8 +126,8 @@ def create_socket(interface, portchannel): return sockets, exp_socket def print_statistics(interface, total_count, recv_count): - print "\n--- %s ping statistics ---" % interface - print ("%d packets transmitted, %d received, %0.2f%% packet loss" % (total_count, recv_count, (total_count-recv_count)*1.0/total_count*100)) + print("\n--- %s ping statistics ---" % interface) + print("%d packets transmitted, %d received, %0.2f%% packet loss" % (total_count, recv_count, (total_count-recv_count)*1.0/total_count*100)) def main(): parser = argparse.ArgumentParser(description='Check portchannel member link state', @@ -156,8 +156,8 @@ def main(): portchannel_ip = get_portchannel_ipv4(portchannel_name) sockets, exp_socket = create_socket(interface, portchannel) pkt, exp_pkt = find_probe_packet(interface, decap_ip, portchannel_ip, sockets, exp_socket, max_trial) - print "Preparation done! Start probing ............" - print "PORTCHANNEL Member PING %d btyes of data. PORTCHANNEL: %s, INTERFACE: %s" % (PROBE_PACKET_LEN, portchannel_name, interface) + print("Preparation done! Start probing ............") + print("PORTCHANNEL Member PING %d btyes of data. PORTCHANNEL: %s, INTERFACE: %s" % (PROBE_PACKET_LEN, portchannel_name, interface)) recv_count = 0 total_count = 0 try: @@ -167,10 +167,10 @@ def main(): have_received, delay, recv_socket = receive(sockets, SELECT_TIMEOUT, exp_pkt, time_sent) total_count = total_count + 1 if have_received: - print "%d bytes from %s: time=%0.4fms" % (PROBE_PACKET_LEN, interface, delay) + print("%d bytes from %s: time=%0.4fms" % (PROBE_PACKET_LEN, interface, delay)) recv_count = recv_count + 1 else: - print "packet not received!" + print("packet not received!") if total_count == exp_count: break; time.sleep(1) diff --git a/scripts/pfcstat b/scripts/pfcstat index 106cdda84b..7649c07f4d 100755 --- a/scripts/pfcstat +++ b/scripts/pfcstat @@ -105,9 +105,9 @@ class Pfcstat(object): data.pfc6, data.pfc7)) if rx: - print tabulate(table, header_Rx, tablefmt='simple', stralign='right') + print(tabulate(table, header_Rx, tablefmt='simple', stralign='right')) else: - print tabulate(table, header_Tx, tablefmt='simple', stralign='right') + print(tabulate(table, header_Tx, tablefmt='simple', stralign='right')) def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, rx): """ @@ -150,9 +150,9 @@ class Pfcstat(object): cntr.pfc6, cntr.pfc7)) if rx: - print tabulate(table, header_Rx, tablefmt='simple', stralign='right') + print(tabulate(table, header_Rx, tablefmt='simple', stralign='right')) else: - print tabulate(table, header_Tx, tablefmt='simple', stralign='right') + print(tabulate(table, header_Tx, tablefmt='simple', stralign='right')) def main(): parser = argparse.ArgumentParser(description='Display the pfc counters', @@ -189,7 +189,7 @@ Examples: os.rmdir(cnstat_dir) sys.exit(0) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(e) """ @@ -207,7 +207,7 @@ Examples: try: os.makedirs(cnstat_dir) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(1) if save_fresh_stats: @@ -215,10 +215,10 @@ Examples: pickle.dump(cnstat_dict_rx, open(cnstat_fqn_file_rx, 'w')) pickle.dump(cnstat_dict_tx, open(cnstat_fqn_file_tx, 'w')) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(e.errno) else: - print "Clear saved counters" + print("Clear saved counters") sys.exit(0) @@ -228,24 +228,25 @@ Examples: if os.path.isfile(cnstat_fqn_file_rx): try: cnstat_cached_dict = pickle.load(open(cnstat_fqn_file_rx, 'r')) - print "Last cached time was " + str(cnstat_cached_dict.get('time')) + print("Last cached time was " + str(cnstat_cached_dict.get('time'))) pfcstat.cnstat_diff_print(cnstat_dict_rx, cnstat_cached_dict, True) except IOError as e: - print e.errno, e + print(e.errno, e) else: pfcstat.cnstat_print(cnstat_dict_rx, True) - print + print() + """ Print the counters of pfc tx counter """ if os.path.isfile(cnstat_fqn_file_tx): try: cnstat_cached_dict = pickle.load(open(cnstat_fqn_file_tx, 'r')) - print "Last cached time was " + str(cnstat_cached_dict.get('time')) + print("Last cached time was " + str(cnstat_cached_dict.get('time'))) pfcstat.cnstat_diff_print(cnstat_dict_tx, cnstat_cached_dict, False) except IOError as e: - print e.errno, e + print(e.errno, e) else: pfcstat.cnstat_print(cnstat_dict_tx, False) diff --git a/scripts/portstat b/scripts/portstat index 42757d3677..b34191d0cd 100644 --- a/scripts/portstat +++ b/scripts/portstat @@ -167,9 +167,9 @@ class Portstat(object): data.tx_drop, data.tx_ovr)) if use_json: - print table_as_json(table, header) + print(table_as_json(table, header)) else: - print tabulate(table, header, tablefmt='simple', stralign='right') + print(tabulate(table, header, tablefmt='simple', stralign='right')) def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, intf_list, use_json, print_all, errors_only, rates_only): """ @@ -277,9 +277,9 @@ class Portstat(object): cntr.tx_ovr)) if use_json: - print table_as_json(table, header) + print(table_as_json(table, header)) else: - print tabulate(table, header, tablefmt='simple', stralign='right') + print(tabulate(table, header, tablefmt='simple', stralign='right')) def main(): @@ -341,7 +341,7 @@ Examples: os.rmdir(cnstat_dir) sys.exit(0) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(e) if delete_saved_stats: @@ -349,7 +349,7 @@ Examples: os.remove(cnstat_fqn_file) except IOError as e: if e.errno != ENOENT: - print e.errno, e + print(e.errno, e) sys.exit(1) finally: if os.listdir(cnstat_dir) == []: @@ -372,7 +372,7 @@ Examples: try: os.makedirs(cnstat_dir) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(1) @@ -382,7 +382,7 @@ Examples: except IOError as e: sys.exit(e.errno) else: - print "Cleared counters" + print("Cleared counters") sys.exit(0) if wait_time_in_seconds == 0: @@ -390,20 +390,20 @@ Examples: if os.path.isfile(cnstat_fqn_file): try: cnstat_cached_dict = pickle.load(open(cnstat_fqn_file, 'r')) - print "Last cached time was " + str(cnstat_cached_dict.get('time')) + print("Last cached time was " + str(cnstat_cached_dict.get('time'))) portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, intf_list, use_json, print_all, errors_only, rates_only) except IOError as e: - print e.errno, e + print(e.errno, e) else: if tag_name: - print "\nFile '%s' does not exist" % cnstat_fqn_file - print "Did you run 'portstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name) + print("\nFile '%s' does not exist" % cnstat_fqn_file) + print("Did you run 'portstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name)) else: portstat.cnstat_print(cnstat_dict, intf_list, use_json, print_all, errors_only, rates_only) else: #wait for the specified time and then gather the new stats and output the difference. time.sleep(wait_time_in_seconds) - print "The rates are calculated within %s seconds period" % wait_time_in_seconds + print("The rates are calculated within %s seconds period" % wait_time_in_seconds) cnstat_new_dict = portstat.get_cnstat() portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, intf_list, use_json, print_all, errors_only, rates_only) diff --git a/scripts/psushow b/scripts/psushow index f61d8b405f..d74aee3a88 100755 --- a/scripts/psushow +++ b/scripts/psushow @@ -26,7 +26,7 @@ def psu_status_show(index): chassis_name = "chassis {}".format(chassis_num) num_psus = db.get(db.STATE_DB, 'CHASSIS_INFO|{}'.format(chassis_name), 'psu_num') if not num_psus: - print "Error! Failed to get the number of PSUs!" + print("Error! Failed to get the number of PSUs!") return -1 supported_psu = range(1, int(num_psus) + 1) @@ -42,8 +42,8 @@ def psu_status_show(index): msg = "" psu_name = "PSU {}".format(psu) if psu not in supported_psu: - print "Error! The {} is not available on the platform.\n" \ - "Number of supported PSU - {}.".format(psu_name, num_psus) + print("Error! The {} is not available on the platform.\n" + "Number of supported PSU - {}.".format(psu_name, num_psus)) continue presence = db.get(db.STATE_DB, 'PSU_INFO|{}'.format(psu_name), 'presence') if presence == 'true': @@ -55,7 +55,7 @@ def psu_status_show(index): status_table.append([psu_name, msg, led_status]) if status_table: - print tabulate(status_table, header, tablefmt="simple") + print(tabulate(status_table, header, tablefmt="simple")) return 0 def main(): @@ -76,7 +76,7 @@ Examples: if status_show: err = psu_status_show(psu_index) if err: - print "Error: fail to get psu status from state DB" + print("Error: fail to get psu status from state DB") sys.exit(1) if __name__ == "__main__": diff --git a/scripts/queuestat b/scripts/queuestat index 6b14581afd..9ce450a85d 100755 --- a/scripts/queuestat +++ b/scripts/queuestat @@ -56,7 +56,7 @@ class Queuestat(object): def get_queue_port(table_id): port_table_id = self.db.get(self.db.COUNTERS_DB, COUNTERS_QUEUE_PORT_MAP, table_id) if port_table_id is None: - print "Port is not available!", table_id + print("Port is not available!", table_id) sys.exit(1) return port_table_id @@ -64,7 +64,7 @@ class Queuestat(object): # Get all ports self.counter_port_name_map = self.db.get_all(self.db.COUNTERS_DB, COUNTERS_PORT_NAME_MAP) if self.counter_port_name_map is None: - print "COUNTERS_PORT_NAME_MAP is empty!" + print("COUNTERS_PORT_NAME_MAP is empty!") sys.exit(1) self.port_queues_map = {} @@ -77,7 +77,7 @@ class Queuestat(object): # Get Queues for each port counter_queue_name_map = self.db.get_all(self.db.COUNTERS_DB, COUNTERS_QUEUE_NAME_MAP) if counter_queue_name_map is None: - print "COUNTERS_QUEUE_NAME_MAP is empty!" + print("COUNTERS_QUEUE_NAME_MAP is empty!") sys.exit(1) for queue in counter_queue_name_map: @@ -95,7 +95,7 @@ class Queuestat(object): def get_queue_index(table_id): queue_index = self.db.get(self.db.COUNTERS_DB, COUNTERS_QUEUE_INDEX_MAP, table_id) if queue_index is None: - print "Queue index is not available!", table_id + print("Queue index is not available!", table_id) sys.exit(1) return queue_index @@ -103,7 +103,7 @@ class Queuestat(object): def get_queue_type(table_id): queue_type = self.db.get(self.db.COUNTERS_DB, COUNTERS_QUEUE_TYPE_MAP, table_id) if queue_type is None: - print "Queue Type is not available!", table_id + print("Queue Type is not available!", table_id) sys.exit(1) elif queue_type == SAI_QUEUE_TYPE_MULTICAST: return QUEUE_TYPE_MC @@ -112,7 +112,7 @@ class Queuestat(object): elif queue_type == SAI_QUEUE_TYPE_ALL: return QUEUE_TYPE_ALL else: - print "Queue Type is invalid:", table_id, queue_type + print("Queue Type is invalid:", table_id, queue_type) sys.exit(1) fields = ["0","0","0","0","0","0"] @@ -151,8 +151,8 @@ class Queuestat(object): data.totalpacket, data.totalbytes, data.droppacket, data.dropbytes)) - print tabulate(table, header, tablefmt='simple', stralign='right') - print + print(tabulate(table, header, tablefmt='simple', stralign='right')) + print() def cnstat_diff_print(self, port, cnstat_new_dict, cnstat_old_dict): """ @@ -188,8 +188,8 @@ class Queuestat(object): cntr.totalpacket, cntr.totalbytes, cntr.droppacket, cntr.dropbytes)) - print tabulate(table, header, tablefmt='simple', stralign='right') - print + print(tabulate(table, header, tablefmt='simple', stralign='right')) + print() def get_print_all_stat(self): # Get stat for each port @@ -200,16 +200,16 @@ class Queuestat(object): if os.path.isfile(cnstat_fqn_file_name): try: cnstat_cached_dict = pickle.load(open(cnstat_fqn_file_name, 'r')) - print port + " Last cached time was " + str(cnstat_cached_dict.get('time')) + print(port + " Last cached time was " + str(cnstat_cached_dict.get('time'))) self.cnstat_diff_print(port, cnstat_dict, cnstat_cached_dict) except IOError as e: - print e.errno, e + print(e.errno, e) else: self.cnstat_print(port, cnstat_dict) def get_print_port_stat(self, port): if not port in self.port_queues_map: - print "Port doesn't exist!", port + print("Port doesn't exist!", port) sys.exit(1) # Get stat for the port queried @@ -218,10 +218,10 @@ class Queuestat(object): if os.path.isfile(cnstat_fqn_file_name): try: cnstat_cached_dict = pickle.load(open(cnstat_fqn_file_name, 'r')) - print "Last cached time was " + str(cnstat_cached_dict.get('time')) + print("Last cached time was " + str(cnstat_cached_dict.get('time'))) self.cnstat_diff_print(port, cnstat_dict, cnstat_cached_dict) except IOError as e: - print e.errno, e + print(e.errno, e) else: self.cnstat_print(port, cnstat_dict) @@ -230,7 +230,7 @@ class Queuestat(object): try: os.makedirs(cnstat_dir) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(1) # Get stat for each port and save @@ -239,10 +239,10 @@ class Queuestat(object): try: pickle.dump(cnstat_dict, open(cnstat_fqn_file + port, 'w')) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(e.errno) else: - print "Clear and update saved counters for " + port + print("Clear and update saved counters for " + port) def main(): global cnstat_dir @@ -283,7 +283,7 @@ Examples: os.rmdir(cnstat_dir) sys.exit(0) except IOError as e: - print e.errno, e + print(e.errno, e) sys.exit(e) queuestat = Queuestat() diff --git a/scripts/sfpshow b/scripts/sfpshow index 875b64d8df..f77740ab2c 100755 --- a/scripts/sfpshow +++ b/scripts/sfpshow @@ -314,7 +314,7 @@ class SFPShow(object): out_put = out_put + '\n' - print out_put + click.echo(out_put) def display_presence(self, interfacename): port_table = [] diff --git a/scripts/sonic-kdump-config b/scripts/sonic-kdump-config index 4a0bba6273..696a87adc8 100755 --- a/scripts/sonic-kdump-config +++ b/scripts/sonic-kdump-config @@ -410,14 +410,14 @@ def cmd_kdump_num_dumps(verbose, num_dumps): ## Command: Display kdump status def cmd_kdump_status(): - print 'Kdump Administrative Mode: ', + print('Kdump Administrative Mode: ', end='') kdump_enabled = get_kdump_administrative_mode() if kdump_enabled: print('Enabled') else: print('Disabled') - print 'Kdump Operational State: ', + print('Kdump Operational State: ', end='') (rc, lines, err_str) = run_command("/usr/sbin/kdump-config status", use_shell=False); if len(lines) >= 1 and ": ready to kdump" in lines[0]: use_kdump_in_cfg = read_use_kdump() diff --git a/scripts/teamshow b/scripts/teamshow index e38bf5affc..54199895be 100755 --- a/scripts/teamshow +++ b/scripts/teamshow @@ -81,7 +81,7 @@ class Teamshow(object): p = subprocess.Popen(teamdctl_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) (output, err) = p.communicate() rc = p.wait() - if rc == 0: + if rc == 0: self.teamsraw[self.get_team_id(team)] = output else: self.err = err @@ -138,7 +138,7 @@ class Teamshow(object): output = [] for team_id in natsorted(self.summary): output.append([team_id, 'PortChannel'+team_id, self.summary[team_id]['protocol'], self.summary[team_id]['ports']]) - print tabulate(output, header) + print(tabulate(output, header)) def main(): if os.geteuid() != 0: diff --git a/scripts/watermarkcfg b/scripts/watermarkcfg index dec6665d0b..9d2c6be35e 100644 --- a/scripts/watermarkcfg +++ b/scripts/watermarkcfg @@ -28,9 +28,9 @@ class Watermarkcfg(object): wm_info = configdb.get_entry('WATERMARK_TABLE', 'TELEMETRY_INTERVAL') if wm_info: - print '\nTelemetry interval: ' + wm_info['interval'] + ' second(s)\n' + print('\nTelemetry interval: ' + wm_info['interval'] + ' second(s)\n') else: - print '\nTelemetry interval 120 second(s)\n' + print('\nTelemetry interval 120 second(s)\n') def main(): diff --git a/scripts/watermarkstat b/scripts/watermarkstat index 8b567ea3b5..f306cd62e9 100644 --- a/scripts/watermarkstat +++ b/scripts/watermarkstat @@ -56,7 +56,7 @@ class Watermarkstat(object): def get_queue_type(table_id): queue_type = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_TYPE_MAP, table_id) if queue_type is None: - print >> sys.stderr, "Queue Type is not available!", table_id + print("Queue Type is not available in table '{}'".format(table_id), file=sys.stderr) sys.exit(1) elif queue_type == SAI_QUEUE_TYPE_MULTICAST: return QUEUE_TYPE_MC @@ -65,13 +65,13 @@ class Watermarkstat(object): elif queue_type == SAI_QUEUE_TYPE_ALL: return QUEUE_TYPE_ALL else: - print >> sys.stderr, "Queue Type is invalid:", table_id, queue_type + print("Queue Type '{} in table '{}' is invalid".format(queue_type, table_id), file=sys.stderr) sys.exit(1) def get_queue_port(table_id): port_table_id = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_PORT_MAP, table_id) if port_table_id is None: - print >> sys.stderr, "Port is not available!", table_id + print("Port is not available in table '{}'".format(table_id), file=sys.stderr) sys.exit(1) return port_table_id @@ -79,7 +79,7 @@ class Watermarkstat(object): def get_pg_port(table_id): port_table_id = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_PG_PORT_MAP, table_id) if port_table_id is None: - print >> sys.stderr, "Port is not available!", table_id + print("Port is not available in table '{}'".format(table_id), file=sys.stderr) sys.exit(1) return port_table_id @@ -87,7 +87,7 @@ class Watermarkstat(object): # Get all ports self.counter_port_name_map = self.counters_db.get_all(self.counters_db.COUNTERS_DB, COUNTERS_PORT_NAME_MAP) if self.counter_port_name_map is None: - print >> sys.stderr, "COUNTERS_PORT_NAME_MAP is empty!" + print("COUNTERS_PORT_NAME_MAP is empty!", file=sys.stderr) sys.exit(1) self.port_uc_queues_map = {} @@ -104,7 +104,7 @@ class Watermarkstat(object): # Get Queues for each port counter_queue_name_map = self.counters_db.get_all(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_NAME_MAP) if counter_queue_name_map is None: - print >> sys.stderr, "COUNTERS_QUEUE_NAME_MAP is empty!" + print("COUNTERS_QUEUE_NAME_MAP is empty!", file=sys.stderr) sys.exit(1) for queue in counter_queue_name_map: @@ -118,7 +118,7 @@ class Watermarkstat(object): # Get PGs for each port counter_pg_name_map = self.counters_db.get_all(self.counters_db.COUNTERS_DB, COUNTERS_PG_NAME_MAP) if counter_pg_name_map is None: - print >> sys.stderr, "COUNTERS_PG_NAME_MAP is empty!" + print("COUNTERS_PG_NAME_MAP is empty!", file=sys.stderr) sys.exit(1) for pg in counter_pg_name_map: @@ -128,7 +128,7 @@ class Watermarkstat(object): # Get all buffer pools self.buffer_pool_name_to_oid_map = self.counters_db.get_all(self.counters_db.COUNTERS_DB, COUNTERS_BUFFER_POOL_NAME_MAP) if self.buffer_pool_name_to_oid_map is None: - print >> sys.stderr, "COUNTERS_BUFFER_POOL_NAME_MAP is empty!" + print("COUNTERS_BUFFER_POOL_NAME_MAP is empty!", file=sys.stderr) sys.exit(1) self.watermark_types = { @@ -160,7 +160,7 @@ class Watermarkstat(object): def get_queue_index(self, table_id): queue_index = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_INDEX_MAP, table_id) if queue_index is None: - print >> sys.stderr, "Queue index is not available!", table_id + print("Queue index is not available in table '{}'".format(table_id), file=sys.stderr) sys.exit(1) return queue_index @@ -168,14 +168,14 @@ class Watermarkstat(object): def get_pg_index(self, table_id): pg_index = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_PG_INDEX_MAP, table_id) if pg_index is None: - print >> sys.stderr, "Priority group index is not available!", table_id + print("Priority group index is not available in table '{}'".format(table_id), file=sys.stderr) sys.exit(1) return pg_index def build_header(self, wm_type): if wm_type is None: - print >> sys.stderr, "Header info is not available!" + print("Header info is not available!", file=sys.stderr) sys.exit(1) self.header_list = ['Port'] @@ -234,7 +234,7 @@ class Watermarkstat(object): table.append(tuple(row_data)) print(type["message"]) - print tabulate(table, self.header_list, tablefmt='simple', stralign='right') + print(tabulate(table, self.header_list, tablefmt='simple', stralign='right')) def send_clear_notification(self, data): msg = json.dumps(data, separators=(',', ':')) diff --git a/sfputil/main.py b/sfputil/main.py index 75a5f741eb..7dfd56b1a5 100644 --- a/sfputil/main.py +++ b/sfputil/main.py @@ -126,14 +126,14 @@ def logical_port_name_to_physical_port_list(port_name): if platform_sfputil.is_logical_port(port_name): return platform_sfputil.get_logical_to_physical(port_name) else: - print "Error: Invalid port '%s'" % port_name + click.echo("Error: Invalid port '%s'" % port_name) return None else: return [int(port_name)] def print_all_valid_port_values(): - print "Valid values for port: %s\n" % str(platform_sfputil.logical) + click.echo("Valid values for port: %s\n" % str(platform_sfputil.logical)) # Returns multi-line string of pretty SFP port EEPROM data @@ -144,7 +144,7 @@ def port_eeprom_data_string_pretty(logical_port_name, dump_dom): physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) if physical_port_list is None: - print "Error: No physical ports found for logical port '%s'" % logical_port_name + click.echo("Error: No physical ports found for logical port '%s'" % logical_port_name) return "" if len(physical_port_list) > 1: @@ -192,7 +192,7 @@ def port_eeprom_data_string_pretty_oneline(logical_port_name, physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) if physical_port_list is None: - print "Error: No physical ports found for logical port '%s'" % logical_port_name + click.echo("Error: No physical ports found for logical port '%s'" % logical_port_name) return "" if len(physical_port_list) > 1: @@ -232,7 +232,7 @@ def port_eeprom_data_raw_string_pretty(logical_port_name): physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) if physical_port_list is None: - print "Error: No physical ports found for logical port '%s'" % logical_port_name + click.echo("Error: No physical ports found for logical port '%s'" % logical_port_name) return "" if len(physical_port_list) > 1: @@ -295,7 +295,7 @@ def cli(): """sfputil - Command line utility for managing SFP transceivers""" if os.geteuid() != 0: - print "Root privileges are required for this operation" + click.echo("Root privileges are required for this operation") sys.exit(1) # Load platform-specific sfputil class @@ -335,7 +335,7 @@ def eeprom(port, dump_dom, oneline, raw): logical_port_list = platform_sfputil.logical else: if platform_sfputil.is_valid_sfputil_port(port) == 0: - print "Error: invalid port '%s'\n" % port + click.echo("Error: invalid port '%s'\n" % port) print_all_valid_port_values() sys.exit(4) @@ -360,7 +360,7 @@ def eeprom(port, dump_dom, oneline, raw): for logical_port_name in logical_port_list: output += port_eeprom_data_string_pretty(logical_port_name, dump_dom) - print output + click.echo(output) # 'presence' subcommand @@ -377,7 +377,7 @@ def presence(port): logical_port_list = platform_sfputil.logical else: if platform_sfputil.is_valid_sfputil_port(port) == 0: - print "Error: invalid port '%s'\n" % port + click.echo("Error: invalid port '%s'\n" % port) print_all_valid_port_values() sys.exit(4) @@ -389,7 +389,7 @@ def presence(port): physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) if physical_port_list is None: - print "Error: No physical ports found for logical port '%s'" % logical_port_name + click.echo("Error: No physical ports found for logical port '%s'" % logical_port_name) return if len(physical_port_list) > 1: @@ -411,7 +411,7 @@ def presence(port): i += 1 - print tabulate(output_table, table_header, tablefmt="simple") + click.echo(tabulate(output_table, table_header, tablefmt="simple")) # 'lpmode' subcommand @@ -428,7 +428,7 @@ def lpmode(port): logical_port_list = platform_sfputil.logical else: if platform_sfputil.is_valid_sfputil_port(port) == 0: - print "Error: invalid port '%s'\n" % port + click.echo("Error: invalid port '%s'\n" % port) print_all_valid_port_values() sys.exit(4) @@ -440,7 +440,7 @@ def lpmode(port): physical_port_list = logical_port_name_to_physical_port_list(logical_port_name) if physical_port_list is None: - print "Error: No physical ports found for logical port '%s'" % logical_port_name + click.echo("Error: No physical ports found for logical port '%s'" % logical_port_name) return if len(physical_port_list) > 1: @@ -462,7 +462,7 @@ def lpmode(port): i += 1 - print tabulate(output_table, table_header, tablefmt='simple') + click.echo(tabulate(output_table, table_header, tablefmt='simple')) # 'lpmode' subgroup @@ -478,22 +478,22 @@ def set_lpmode(logical_port, enable): i = 1 if platform_sfputil.is_valid_sfputil_port(logical_port) == 0: - print "Error: invalid port '%s'\n" % logical_port + click.echo("Error: invalid port '%s'\n" % logical_port) print_all_valid_port_values() sys.exit(4) physical_port_list = logical_port_name_to_physical_port_list(logical_port) if physical_port_list is None: - print "Error: No physical ports found for logical port '%s'" % logical_port + click.echo("Error: No physical ports found for logical port '%s'" % logical_port) return if len(physical_port_list) > 1: ganged = True for physical_port in physical_port_list: - print "%s low-power mode for port %s... " % ( + click.echo("{} low-power mode for port {}... ".format( "Enabling" if enable else "Disabling", - get_physical_port_name(logical_port, i, ganged)), + get_physical_port_name(logical_port, i, ganged)), nl=False) try: result = platform_sfputil.set_low_power_mode(physical_port, enable) @@ -502,9 +502,9 @@ def set_lpmode(logical_port, enable): sys.exit(5) if result: - print "OK" + click.echo("OK") else: - print "Failed" + click.echo("Failed") i += 1 @@ -534,20 +534,20 @@ def reset(port_name): i = 1 if platform_sfputil.is_valid_sfputil_port(port_name) == 0: - print "Error: invalid port '%s'\n" % port_name + click.echo("Error: invalid port '%s'\n" % port_name) print_all_valid_port_values() sys.exit(4) physical_port_list = logical_port_name_to_physical_port_list(port_name) if physical_port_list is None: - print "Error: No physical ports found for logical port '%s'" % port_name + click.echo("Error: No physical ports found for logical port '%s'" % port_name) return if len(physical_port_list) > 1: ganged = True for physical_port in physical_port_list: - print "Resetting port %s... " % get_physical_port_name(port_name, i, ganged), + click.echo("Resetting port %s... " % get_physical_port_name(port_name, i, ganged), nl=False) try: result = platform_sfputil.reset(physical_port) @@ -556,9 +556,9 @@ def reset(port_name): sys.exit(5) if result: - print "OK" + click.echo("OK") else: - print "Failed" + click.echo("Failed") i += 1 diff --git a/show/main.py b/show/main.py index 44399729ce..7cd562fd4d 100755 --- a/show/main.py +++ b/show/main.py @@ -1298,7 +1298,7 @@ def get_if_admin_state(iface): try: state_file = open(admin_file.format(iface), "r") except IOError as e: - print "Error: unable to open file: %s" % str(e) + print("Error: unable to open file: %s" % str(e)) return "error" content = state_file.readline().rstrip() @@ -1321,7 +1321,7 @@ def get_if_oper_state(iface): try: state_file = open(oper_file.format(iface), "r") except IOError as e: - print "Error: unable to open file: %s" % str(e) + print("Error: unable to open file: %s" % str(e)) return "error" oper_state = state_file.readline().rstrip() @@ -1394,7 +1394,7 @@ def interfaces(): for ifaddr in ifaddresses[1:]: data.append(["", "", ifaddr[1], ""]) - print tabulate(data, header, tablefmt="simple", stralign='left', missingval="") + print(tabulate(data, header, tablefmt="simple", stralign='left', missingval="")) # get bgp peering info def get_bgp_peer(): @@ -1536,7 +1536,7 @@ def interfaces(): data.append(["", "", ifaddr[1], admin + "/" + oper, neighbor_info[0][0], neighbor_info[0][1]]) neighbor_info.pop(0) - print tabulate(data, header, tablefmt="simple", stralign='left', missingval="") + print(tabulate(data, header, tablefmt="simple", stralign='left', missingval="")) # @@ -1955,7 +1955,7 @@ def ntp(verbose): ntp_server = line.split(" ")[1] ntp_servers.append(ntp_server) ntp_dict['NTP Servers'] = ntp_servers - print tabulate(ntp_dict, headers=ntp_dict.keys(), tablefmt="simple", stralign='left', missingval="") + print(tabulate(ntp_dict, headers=ntp_dict.keys(), tablefmt="simple", stralign='left', missingval="")) # 'syslog' subcommand ("show runningconfiguration syslog") @@ -1973,7 +1973,7 @@ def syslog(verbose): server = line[0][5:] syslog_servers.append(server) syslog_dict['Syslog Servers'] = syslog_servers - print tabulate(syslog_dict, headers=syslog_dict.keys(), tablefmt="simple", stralign='left', missingval="") + print(tabulate(syslog_dict, headers=syslog_dict.keys(), tablefmt="simple", stralign='left', missingval="")) # @@ -2147,7 +2147,7 @@ def services(): print("---------------------------") cmd = "sudo docker exec {} ps aux | sed '$d'".format(line.rstrip()) proc1 = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) - print proc1.stdout.read() + print(proc1.stdout.read()) else: break @@ -2940,7 +2940,7 @@ def neighbors(): r = ["", ip, mac, intf] table.append(r) click.echo(tabulate(table, header)) - click.echo("\n") + click.echo() if not bool(vnet_intfs): click.echo(tabulate(table, header)) @@ -2973,7 +2973,7 @@ def all(): click.echo(tabulate(table, header)) - click.echo("\n") + click.echo() header = ['vnet name', 'prefix', 'endpoint', 'mac address', 'vni'] diff --git a/show/mlnx.py b/show/mlnx.py index 6b0d304a27..3dae4e906f 100644 --- a/show/mlnx.py +++ b/show/mlnx.py @@ -95,7 +95,7 @@ def is_issu_status_enabled(): try: sai_xml_path = sai_profile_kvs['SAI_INIT_CONFIG_FILE'] except KeyError: - print >> sys.stderr, "Failed to get SAI XML from sai profile" + click.echo("Failed to get SAI XML from sai profile", err=True) sys.exit(1) # Get ISSU from SAI XML @@ -105,7 +105,7 @@ def is_issu_status_enabled(): try: root = ET.fromstring(sai_xml_content) except ET.ParseError: - print >> sys.stderr, "Failed to parse SAI xml" + click.echo("Failed to parse SAI xml", err=True) sys.exit(1) el = root.find('platform_info').find('issu-enabled') @@ -124,9 +124,9 @@ def sniffer_status(): for index in range(len(components)): enabled = sniffer_status_get(env_variable_strings[index]) if enabled is True: - print components[index] + " sniffer is enabled" + click.echo(components[index] + " sniffer is enabled") else: - print components[index] + " sniffer is disabled" + click.echo(components[index] + " sniffer is disabled") @mlnx.command('issu') @@ -135,5 +135,5 @@ def issu_status(): res = is_issu_status_enabled() - print 'ISSU is enabled' if res else 'ISSU is disabled' + click.echo('ISSU is enabled' if res else 'ISSU is disabled') diff --git a/ssdutil/main.py b/ssdutil/main.py index 94939d3480..7edd8ab66a 100755 --- a/ssdutil/main.py +++ b/ssdutil/main.py @@ -56,7 +56,7 @@ def is_number(s): # ==================== Entry point ==================== def ssdutil(): if os.geteuid() != 0: - print "Root privileges are required for this operation" + print("Root privileges are required for this operation") sys.exit(1) parser = argparse.ArgumentParser() @@ -67,14 +67,14 @@ def ssdutil(): ssd = import_ssd_api(args.device) - print "Device Model : {}".format(ssd.get_model()) + print("Device Model : {}".format(ssd.get_model())) if args.verbose: - print "Firmware : {}".format(ssd.get_firmware()) - print "Serial : {}".format(ssd.get_serial()) - print "Health : {}{}".format(ssd.get_health(), "%" if is_number(ssd.get_health()) else "") - print "Temperature : {}{}".format(ssd.get_temperature(), "C" if is_number(ssd.get_temperature()) else "") + print("Firmware : {}".format(ssd.get_firmware())) + print("Serial : {}".format(ssd.get_serial())) + print("Health : {}{}".format(ssd.get_health(), "%" if is_number(ssd.get_health()) else "")) + print("Temperature : {}{}".format(ssd.get_temperature(), "C" if is_number(ssd.get_temperature()) else "")) if args.vendor: - print ssd.get_vendor_output() + print(ssd.get_vendor_output()) if __name__ == '__main__': ssdutil() diff --git a/tests/config_test.py b/tests/config_test.py index e3c519da6a..023f41337a 100644 --- a/tests/config_test.py +++ b/tests/config_test.py @@ -56,8 +56,8 @@ def test_load_minigraph(self, get_cmd_module, setup_single_broacom_asic): (config, show) = get_cmd_module runner = CliRunner() result = runner.invoke(config.config.commands["load_minigraph"], ["-y"]) - print result.exit_code - print result.output + 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')]) == load_minigraph_command_output @@ -69,11 +69,11 @@ def test_load_minigraph_with_disabled_telemetry(self, get_cmd_module, setup_sing result = runner.invoke(config.config.commands["feature"].commands["state"], ["telemetry", "disabled"], obj=db) assert result.exit_code == 0 result = runner.invoke(show.cli.commands["feature"].commands["status"], ["telemetry"], obj=db) - print result.output + print(result.output) assert result.exit_code == 0 result = runner.invoke(config.config.commands["load_minigraph"], ["-y"], obj=db) - print result.exit_code - print result.output + print(result.exit_code) + print(result.output) assert result.exit_code == 0 assert "telemetry" not in result.output diff --git a/utilities_common/cli.py b/utilities_common/cli.py index bbbb6df471..9b9c2693c1 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -259,7 +259,7 @@ def is_port_router_interface(config_db, port): if port == intf[0]: return True - return False + return False def is_pc_router_interface(config_db, pc): """Check if portchannel is a router interface""" From 0e96c99eb26af20f3fe8066949e981e806d06f82 Mon Sep 17 00:00:00 2001 From: lguohan Date: Tue, 11 Aug 2020 17:00:01 -0700 Subject: [PATCH 33/48] [show]: split show interfaces commands into separate file (#1044) add extensive test coverage for show interfaces commands. Signed-off-by: Guohan Lu --- scripts/intfstat | 18 +- scripts/intfutil | 38 +-- scripts/sfpshow | 18 +- show/interfaces.py | 391 ++++++++++++++++++++++ show/main.py | 536 +------------------------------ tests/interfaces_test.py | 175 ++++++++++ tests/intfstat_test.py | 43 ++- tests/intfutil_test.py | 107 +++++- tests/mock_tables/config_db.json | 136 ++++++++ tests/sfp_test.py | 91 +++--- utilities_common/cli.py | 231 ++++++++++++- 11 files changed, 1144 insertions(+), 640 deletions(-) create mode 100644 show/interfaces.py create mode 100644 tests/interfaces_test.py diff --git a/scripts/intfstat b/scripts/intfstat index 6b5d8215f4..f38f67e55a 100755 --- a/scripts/intfstat +++ b/scripts/intfstat @@ -2,7 +2,7 @@ ##################################################################### # -# intfstat is a tool for summarizing l3 network statistics. +# intfstat is a tool for summarizing l3 network statistics. # ##################################################################### @@ -17,7 +17,7 @@ import time # mock the redis for unit test purposes # try: - if os.environ["UTILITIES_UNIT_TESTING"] == "1": + if os.environ["UTILITIES_UNIT_TESTING"] == "2": modules_path = os.path.join(os.path.dirname(__file__), "..") test_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) @@ -34,7 +34,7 @@ from utilities_common.netstat import ns_diff, ns_brate, ns_prate, table_as_json, NStats = namedtuple("NStats", "rx_b_ok, rx_p_ok, tx_b_ok, tx_p_ok,\ rx_b_err, rx_p_err, tx_b_err, tx_p_err,") -header = ['IFACE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_ERR', +header = ['IFACE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_ERR', 'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_ERR'] counter_names = ( @@ -95,7 +95,7 @@ class Intfstat(object): if counter_rif_name_map is None: print("No %s in the DB!" % COUNTERS_RIF_NAME_MAP) sys.exit(1) - + if rif and not rif in counter_rif_name_map: print("Interface %s missing from %s! Make sure it exists" % (rif, COUNTERS_RIF_NAME_MAP)) sys.exit(2) @@ -105,7 +105,7 @@ class Intfstat(object): return cnstat_dict for rif in natsorted(counter_rif_name_map): - cnstat_dict[rif] = get_counters(counter_rif_name_map[rif]) + cnstat_dict[rif] = get_counters(counter_rif_name_map[rif]) return cnstat_dict def get_intf_state(self, port_name): @@ -187,12 +187,12 @@ class Intfstat(object): print(tabulate(table, header, tablefmt='simple', stralign='right')) def cnstat_single_interface(self, rif, cnstat_new_dict, cnstat_old_dict): - + header = rif + '\n' + '-'*len(rif) body = """ RX: - %10s packets - %10s bytes + %10s packets + %10s bytes %10s error packets %10s error bytes TX: @@ -202,7 +202,7 @@ class Intfstat(object): %10s error bytes""" cntr = cnstat_new_dict.get(rif) - + if cnstat_old_dict: old_cntr = cnstat_old_dict.get(rif) if old_cntr: diff --git a/scripts/intfutil b/scripts/intfutil index af11c1322a..1cedf8d65b 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -14,7 +14,7 @@ import os # mock the redis for unit test purposes # try: - if os.environ["UTILITIES_UNIT_TESTING"] == "1": + if os.environ["UTILITIES_UNIT_TESTING"] == "2": modules_path = os.path.join(os.path.dirname(__file__), "..") tests_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) @@ -49,10 +49,10 @@ def db_connect_configdb(): Connect to configdb """ config_db = ConfigDBConnector() - if config_db is None: - return None + if config_db is None: + return None config_db.connect() - return config_db + return config_db def get_frontpanel_port_list(config_db): ports_dict = config_db.get_table('PORT') @@ -96,7 +96,7 @@ def config_db_vlan_port_keys_get(int_to_vlan_dict, front_panel_ports_list, intf_ if intf_name in int_to_vlan_dict.keys(): vlan = int_to_vlan_dict[intf_name] if "Vlan" in vlan: - vlan = "trunk" + vlan = "trunk" return vlan @@ -169,9 +169,9 @@ def state_db_port_optics_get(state_db, intf_name, type): """ full_table_id = PORT_TRANSCEIVER_TABLE_PREFIX + intf_name optics_type = state_db.get(state_db.STATE_DB, full_table_id, type) - if optics_type is None: - return "N/A" - return optics_type + if optics_type is None: + return "N/A" + return optics_type def merge_dicts(x,y): # store a copy of x, but overwrite with y's values where applicable @@ -188,7 +188,7 @@ def merge_dicts(x,y): def tuple_to_dict(tup, new_dict): """ From a tuple create a dictionary that uses the first item in the tuple as a key - and the 2nd item in the tuple as a value. + and the 2nd item in the tuple as a value. """ for a, b in tup: new_dict.setdefault(a, []).append(b) @@ -216,7 +216,7 @@ def get_portchannel_list(get_raw_po_int_configdb_info): >>> portchannel_list = get_portchannel_list(get_raw_po_int_configdb_info) >>> pprint(portchannel_list) ['PortChannel0001', 'PortChannel0002', 'PortChannel0003', 'PortChannel0004'] - >>> + >>> """ portchannel_list = [] for po in get_raw_po_int_configdb_info: @@ -234,7 +234,7 @@ def create_po_int_tuple_list(get_raw_po_int_configdb_info): ('PortChannel0004', 'Ethernet124'), ('PortChannel0003', 'Ethernet120'), ('PortChannel0001', 'Ethernet112')] - >>> + >>> """ po_int_tuple_list = get_raw_po_int_configdb_info.keys() return po_int_tuple_list @@ -262,11 +262,11 @@ def create_int_to_portchannel_dict(po_int_tuple_list): def po_speed_dict(po_int_dict, appl_db): """ - This function takes the portchannel to interface dictionary + This function takes the portchannel to interface dictionary and the appl_db and then creates a portchannel to speed - dictionary. + dictionary. """ - if po_int_dict: + if po_int_dict: po_list = [] for key, value in po_int_dict.iteritems(): agg_speed_list = [] @@ -311,9 +311,9 @@ def appl_db_portchannel_status_get(appl_db, config_db, po_name, status_type, por return status status = appl_db.get(appl_db.APPL_DB, full_table_id, status_type) #print(status) - if status is None: + if status is None: return "N/A" - return status + return status def appl_db_sub_intf_status_get(appl_db, config_db, front_panel_ports_list, portchannel_speed_dict, sub_intf_name, status_type): sub_intf_sep_idx = sub_intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR) @@ -415,16 +415,16 @@ class IntfStatus(object): def __init__(self, intf_name): """ Class constructor method - :param self: + :param self: :param intf_name: string of interface - :return: + :return: """ self.appl_db = db_connect_appl() self.state_db = db_connect_state() self.config_db = db_connect_configdb() if self.appl_db is None: return - if self.state_db is None: + if self.state_db is None: return if self.config_db is None: return diff --git a/scripts/sfpshow b/scripts/sfpshow index f77740ab2c..188f21de83 100755 --- a/scripts/sfpshow +++ b/scripts/sfpshow @@ -1,7 +1,7 @@ #!/usr/bin/python """ Script to show sfp eeprom and presence status. - Not like sfputil this scripts get the sfp data from DB directly. + Not like sfputil this scripts get the sfp data from DB directly. """ import sys import click @@ -15,14 +15,14 @@ from tabulate import tabulate # Mock the redis for unit test purposes # try: - if os.environ["UTILITIES_UNIT_TESTING"] == "1": + if os.environ["UTILITIES_UNIT_TESTING"] == "2": modules_path = os.path.join(os.path.dirname(__file__), "..") test_path = os.path.join(modules_path, "tests") sys.path.insert(0, modules_path) sys.path.insert(0, test_path) import mock_tables.dbconnector except KeyError: - pass + pass qsfp_data_map = {'model': 'Vendor PN', 'vendor_oui': 'Vendor OUI', @@ -159,7 +159,7 @@ class SFPShow(object): dom_unit_map[key]) out_put = out_put + current_val + '\n' return out_put - + # Convert dom sensor info in DB to cli output string def convert_dom_to_output_string(self, sfp_type, dom_info_dict): ident = ' ' @@ -253,7 +253,7 @@ class SFPShow(object): def convert_sfp_info_to_output_string(self, sfp_info_dict): ident = ' ' out_put = '' - + sorted_qsfp_data_map = sorted(qsfp_data_map.items(), key=operator.itemgetter(1)) for key in sorted_qsfp_data_map: key1 = key[0] @@ -299,7 +299,7 @@ class SFPShow(object): if presence: out_put = self.convert_interface_sfp_info_to_cli_output_string(self.sdb, interfacename, dump_dom) else: - out_put = out_put + interfacename + ': ' + 'SFP EEPROM Not detected' + '\n' + out_put = out_put + interfacename + ': ' + 'SFP EEPROM Not detected' + '\n' else: port_table_keys = self.adb.keys(self.adb.APPL_DB, "PORT_TABLE:*") sorted_table_keys = natsorted(port_table_keys) @@ -310,9 +310,9 @@ class SFPShow(object): if presence: out_put = out_put + self.convert_interface_sfp_info_to_cli_output_string(self.sdb, interface, dump_dom) else: - out_put = out_put + interface + ': ' + 'SFP EEPROM Not detected' + '\n' + out_put = out_put + interface + ': ' + 'SFP EEPROM Not detected' + '\n' - out_put = out_put + '\n' + out_put = out_put + '\n' click.echo(out_put) @@ -336,7 +336,7 @@ class SFPShow(object): port_table.append((key,'Present')) else: port_table.append((key,'Not present')) - + sorted_port_table = natsorted(port_table) click.echo(tabulate(sorted_port_table, header)) diff --git a/show/interfaces.py b/show/interfaces.py new file mode 100644 index 0000000000..0154bcb45a --- /dev/null +++ b/show/interfaces.py @@ -0,0 +1,391 @@ +import json + +import click +from natsort import natsorted +from tabulate import tabulate + +import utilities_common.cli as clicommon + +def try_convert_interfacename_from_alias(ctx, db, interfacename): + """try to convert interface name from alias""" + + if clicommon.get_interface_naming_mode() == "alias": + alias = interfacename + interfacename = clicommon.InterfaceAliasConverter(db).alias_to_name(alias) + # TODO: ideally alias_to_name should return None when it cannot find + # the port name for the alias + if interfacename == alias: + ctx.fail("cannot find interface name for alias {}".format(alias)) + + return interfacename + +# +# 'interfaces' group ("show interfaces ...") +# +@click.group(cls=clicommon.AliasedGroup) +def interfaces(): + """Show details of the network interfaces""" + pass + +# 'alias' subcommand ("show interfaces alias") +@interfaces.command() +@click.argument('interfacename', required=False) +@clicommon.pass_db +def alias(db, interfacename): + """Show Interface Name/Alias Mapping""" + + ctx = click.get_current_context() + + port_dict = db.cfgdb.get_table("PORT") + + header = ['Name', 'Alias'] + body = [] + + if interfacename is not None: + interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + + # If we're given an interface name, output name and alias for that interface only + if interfacename in port_dict.keys(): + if port_dict[interfacename].has_key('alias'): + body.append([interfacename, port_dict[interfacename]['alias']]) + else: + body.append([interfacename, interfacename]) + else: + ctx.fail("Invalid interface name {}".format(interfacename)) + else: + # Output name and alias for all interfaces + for port_name in natsorted(port_dict.keys()): + if 'alias' in port_dict[port_name]: + body.append([port_name, port_dict[port_name]['alias']]) + else: + body.append([port_name, port_name]) + + click.echo(tabulate(body, header)) + +@interfaces.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def description(db, interfacename, verbose): + """Show interface status, protocol and description""" + + ctx = click.get_current_context() + + cmd = "intfutil description" + + if interfacename is not None: + interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + + cmd += " {}".format(interfacename) + + clicommon.run_command(cmd, display_cmd=verbose) + +# 'naming_mode' subcommand ("show interfaces naming_mode") +@interfaces.command('naming_mode') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def naming_mode(verbose): + """Show interface naming_mode status""" + + click.echo(clicommon.get_interface_naming_mode()) + +# 'portchannel' subcommand ("show interfaces portchannel") +@interfaces.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def portchannel(verbose): + """Show PortChannel information""" + cmd = "sudo teamshow" + clicommon.run_command(cmd, display_cmd=verbose) + +@interfaces.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def status(db, interfacename, verbose): + """Show Interface status information""" + + ctx = click.get_current_context() + + cmd = "intfutil status" + + if interfacename is not None: + interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + + cmd += " {}".format(interfacename) + + clicommon.run_command(cmd, display_cmd=verbose) + +# +# 'breakout' group ### +# +@interfaces.group(invoke_without_command=True) +@click.pass_context +def breakout(ctx): + """Show Breakout Mode information by interfaces""" + # Reading data from Redis configDb + config_db = ConfigDBConnector() + config_db.connect() + ctx.obj = {'db': config_db} + + try: + cur_brkout_tbl = config_db.get_table('BREAKOUT_CFG') + except Exception as e: + click.echo("Breakout table is not present in Config DB") + raise click.Abort() + + if ctx.invoked_subcommand is None: + # Get port capability from platform and hwsku related files + platform_path, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs() + platform_file = os.path.join(platform_path, PLATFORM_JSON) + platform_dict = readJsonFile(platform_file)['interfaces'] + hwsku_dict = readJsonFile(os.path.join(hwsku_path, HWSKU_JSON))['interfaces'] + + if not platform_dict or not hwsku_dict: + click.echo("Can not load port config from {} or {} file".format(PLATFORM_JSON, HWSKU_JSON)) + raise click.Abort() + + for port_name in platform_dict.keys(): + cur_brkout_mode = cur_brkout_tbl[port_name]["brkout_mode"] + + # Update deafult breakout mode and current breakout mode to platform_dict + platform_dict[port_name].update(hwsku_dict[port_name]) + platform_dict[port_name]["Current Breakout Mode"] = cur_brkout_mode + + # List all the child ports if present + child_port_dict = get_child_ports(port_name, cur_brkout_mode, platformFile) + if not child_port_dict: + click.echo("Cannot find ports from {} file ".format(PLATFORM_JSON)) + raise click.Abort() + + child_ports = natsorted(child_port_dict.keys()) + + children, speeds = [], [] + # Update portname and speed of child ports if present + for port in child_ports: + speed = config_db.get_entry('PORT', port).get('speed') + if speed is not None: + speeds.append(str(int(speed)//1000)+'G') + children.append(port) + + platform_dict[port_name]["child ports"] = ",".join(children) + platform_dict[port_name]["child port speeds"] = ",".join(speeds) + + # Sorted keys by name in natural sort Order for human readability + parsed = OrderedDict((k, platform_dict[k]) for k in natsorted(platform_dict.keys())) + click.echo(json.dumps(parsed, indent=4)) + +# 'breakout current-mode' subcommand ("show interfaces breakout current-mode") +@breakout.command('current-mode') +@click.argument('interface', metavar='', required=False, type=str) +@click.pass_context +def currrent_mode(ctx, interface): + """Show current Breakout mode Info by interface(s)""" + + config_db = ctx.obj['db'] + + header = ['Interface', 'Current Breakout Mode'] + body = [] + + try: + cur_brkout_tbl = config_db.get_table('BREAKOUT_CFG') + except Exception as e: + click.echo("Breakout table is not present in Config DB") + raise click.Abort() + + # Show current Breakout Mode of user prompted interface + if interface is not None: + body.append([interface, str(cur_brkout_tbl[interface]['brkout_mode'])]) + click.echo(tabulate(body, header, tablefmt="grid")) + return + + # Show current Breakout Mode for all interfaces + for name in natsorted(cur_brkout_tbl.keys()): + body.append([name, str(cur_brkout_tbl[name]['brkout_mode'])]) + click.echo(tabulate(body, header, tablefmt="grid")) + +# +# 'neighbor' group ### +# +@interfaces.group(cls=clicommon.AliasedGroup) +def neighbor(): + """Show neighbor related information""" + pass + +# 'expected' subcommand ("show interface neighbor expected") +@neighbor.command() +@click.argument('interfacename', required=False) +@clicommon.pass_db +def expected(db, interfacename): + """Show expected neighbor information by interfaces""" + + neighbor_dict = db.cfgdb.get_table("DEVICE_NEIGHBOR") + if neighbor_dict is None: + click.echo("DEVICE_NEIGHBOR information is not present.") + return + + neighbor_metadata_dict = db.cfgdb.get_table("DEVICE_NEIGHBOR_METADATA") + if neighbor_metadata_dict is None: + click.echo("DEVICE_NEIGHBOR_METADATA information is not present.") + return + + #Swap Key and Value from interface: name to name: interface + device2interface_dict = {} + for port in natsorted(neighbor_dict.keys()): + temp_port = port + if clicommon.get_interface_naming_mode() == "alias": + port = clicommon.InterfaceAliasConverter(db).name_to_alias(port) + neighbor_dict[port] = neighbor_dict.pop(temp_port) + device2interface_dict[neighbor_dict[port]['name']] = {'localPort': port, 'neighborPort': neighbor_dict[port]['port']} + + header = ['LocalPort', 'Neighbor', 'NeighborPort', 'NeighborLoopback', 'NeighborMgmt', 'NeighborType'] + body = [] + if interfacename: + for device in natsorted(neighbor_metadata_dict.keys()): + if device2interface_dict[device]['localPort'] == interfacename: + body.append([device2interface_dict[device]['localPort'], + device, + device2interface_dict[device]['neighborPort'], + neighbor_metadata_dict[device]['lo_addr'], + neighbor_metadata_dict[device]['mgmt_addr'], + neighbor_metadata_dict[device]['type']]) + if len(body) == 0: + click.echo("No neighbor information available for interface {}".format(interfacename)) + return + else: + for device in natsorted(neighbor_metadata_dict.keys()): + body.append([device2interface_dict[device]['localPort'], + device, + device2interface_dict[device]['neighborPort'], + neighbor_metadata_dict[device]['lo_addr'], + neighbor_metadata_dict[device]['mgmt_addr'], + neighbor_metadata_dict[device]['type']]) + + click.echo(tabulate(body, header)) + +# +# transceiver group (show interfaces trasceiver ...) +# +@interfaces.group(cls=clicommon.AliasedGroup) +def transceiver(): + """Show SFP Transceiver information""" + pass + +@transceiver.command() +@click.argument('interfacename', required=False) +@click.option('-d', '--dom', 'dump_dom', is_flag=True, help="Also display Digital Optical Monitoring (DOM) data") +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def eeprom(db, interfacename, dump_dom, verbose): + """Show interface transceiver EEPROM information""" + + ctx = click.get_current_context() + + cmd = "sfpshow eeprom" + + if dump_dom: + cmd += " --dom" + + if interfacename is not None: + interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + + cmd += " -p {}".format(interfacename) + + clicommon.run_command(cmd, display_cmd=verbose) + +@transceiver.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def lpmode(db, interfacename, verbose): + """Show interface transceiver low-power mode status""" + + ctx = click.get_current_context() + + cmd = "sudo sfputil show lpmode" + + if interfacename is not None: + interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + + cmd += " -p {}".format(interfacename) + + clicommon.run_command(cmd, display_cmd=verbose) + +@transceiver.command() +@click.argument('interfacename', required=False) +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def presence(db, interfacename, verbose): + """Show interface transceiver presence""" + + ctx = click.get_current_context() + + cmd = "sfpshow presence" + + if interfacename is not None: + interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + + cmd += " -p {}".format(interfacename) + + clicommon.run_command(cmd, display_cmd=verbose) + + +# +# counters group ("show interfaces counters ...") +# +@interfaces.group(invoke_without_command=True) +@click.option('-a', '--printall', is_flag=True) +@click.option('-p', '--period') +@click.option('-i', '--interface') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@click.pass_context +def counters(ctx, verbose, period, interface, printall): + """Show interface counters""" + + if ctx.invoked_subcommand is None: + cmd = "portstat" + + if printall: + cmd += " -a" + if period is not None: + cmd += " -p {}".format(period) + if interface is not None: + cmd += " -i {}".format(interface) + + clicommon.run_command(cmd, display_cmd=verbose) + +# 'errors' subcommand ("show interfaces counters errors") +@counters.command() +@click.option('-p', '--period') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def errors(verbose, period): + """Show interface counters errors""" + cmd = "portstat -e" + if period is not None: + cmd += " -p {}".format(period) + clicommon.run_command(cmd, display_cmd=verbose) + +# 'rates' subcommand ("show interfaces counters rates") +@counters.command() +@click.option('-p', '--period') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def rates(verbose, period): + """Show interface counters rates""" + cmd = "portstat -R" + if period is not None: + cmd += " -p {}".format(period) + clicommon.run_command(cmd, display_cmd=verbose) + +# 'counters' subcommand ("show interfaces counters rif") +@counters.command() +@click.argument('interface', metavar='', required=False, type=str) +@click.option('-p', '--period') +@click.option('--verbose', is_flag=True, help="Enable verbose output") +def rif(interface, period, verbose): + """Show interface counters""" + + cmd = "intfstat" + if period is not None: + cmd += " -p {}".format(period) + if interface is not None: + cmd += " -i {}".format(interface) + + clicommon.run_command(cmd, display_cmd=verbose) diff --git a/show/main.py b/show/main.py index 7cd562fd4d..30e1bed6f2 100755 --- a/show/main.py +++ b/show/main.py @@ -8,12 +8,10 @@ import subprocess import sys import ipaddress -from collections import OrderedDict import click from natsort import natsorted from pkg_resources import parse_version -from portconfig import get_child_ports from sonic_py_common import device_info from swsssdk import ConfigDBConnector from swsssdk import SonicV2Connector @@ -22,8 +20,9 @@ import utilities_common.cli as clicommon import mlnx -import vlan import feature +import interfaces +import vlan # Global Variables PLATFORM_JSON = 'platform.json' @@ -73,7 +72,7 @@ def run_command(command, display_cmd=False, return_cmd=False): # No conversion needed for intfutil commands as it already displays # both SONiC interface name and alias name for all interfaces. if clicommon.get_interface_naming_mode() == "alias" and not command.startswith("intfutil"): - run_command_in_alias_mode(command) + clicommon.run_command_in_alias_mode(command) raise sys.exit(0) proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) @@ -95,166 +94,6 @@ def run_command(command, display_cmd=False, return_cmd=False): # Global class instance for SONiC interface name to alias conversion iface_alias_converter = clicommon.InterfaceAliasConverter() -def print_output_in_alias_mode(output, index): - """Convert and print all instances of SONiC interface - name to vendor-sepecific interface aliases. - """ - - alias_name = "" - interface_name = "" - - # Adjust tabulation width to length of alias name - if output.startswith("---"): - word = output.split() - dword = word[index] - underline = dword.rjust(iface_alias_converter.alias_max_length, - '-') - word[index] = underline - output = ' ' .join(word) - - # Replace SONiC interface name with vendor alias - word = output.split() - if word: - interface_name = word[index] - interface_name = interface_name.replace(':', '') - for port_name in natsorted(iface_alias_converter.port_dict.keys()): - if interface_name == port_name: - alias_name = iface_alias_converter.port_dict[port_name]['alias'] - if alias_name: - if len(alias_name) < iface_alias_converter.alias_max_length: - alias_name = alias_name.rjust( - iface_alias_converter.alias_max_length) - output = output.replace(interface_name, alias_name, 1) - - click.echo(output.rstrip('\n')) - - -def run_command_in_alias_mode(command): - """Run command and replace all instances of SONiC interface names - in output with vendor-sepecific interface aliases. - """ - - process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - - while True: - output = process.stdout.readline() - if output == '' and process.poll() is not None: - break - - if output: - index = 1 - raw_output = output - output = output.lstrip() - - if command.startswith("portstat"): - """Show interface counters""" - index = 0 - if output.startswith("IFACE"): - output = output.replace("IFACE", "IFACE".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command.startswith("intfstat"): - """Show RIF counters""" - index = 0 - if output.startswith("IFACE"): - output = output.replace("IFACE", "IFACE".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command == "pfcstat": - """Show pfc counters""" - index = 0 - if output.startswith("Port Tx"): - output = output.replace("Port Tx", "Port Tx".rjust( - iface_alias_converter.alias_max_length)) - - elif output.startswith("Port Rx"): - output = output.replace("Port Rx", "Port Rx".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif (command.startswith("sudo sfputil show eeprom")): - """show interface transceiver eeprom""" - index = 0 - print_output_in_alias_mode(raw_output, index) - - elif (command.startswith("sudo sfputil show")): - """show interface transceiver lpmode, - presence - """ - index = 0 - if output.startswith("Port"): - output = output.replace("Port", "Port".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command == "sudo lldpshow": - """show lldp table""" - index = 0 - if output.startswith("LocalPort"): - output = output.replace("LocalPort", "LocalPort".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command.startswith("queuestat"): - """show queue counters""" - index = 0 - if output.startswith("Port"): - output = output.replace("Port", "Port".rjust( - iface_alias_converter.alias_max_length)) - print_output_in_alias_mode(output, index) - - elif command == "fdbshow": - """show mac""" - index = 3 - if output.startswith("No."): - output = " " + output - output = re.sub( - 'Type', ' Type', output) - elif output[0].isdigit(): - output = " " + output - print_output_in_alias_mode(output, index) - - elif command.startswith("nbrshow"): - """show arp""" - index = 2 - if "Vlan" in output: - output = output.replace('Vlan', ' Vlan') - print_output_in_alias_mode(output, index) - - elif command.startswith("sudo teamshow"): - """ - sudo teamshow - Search for port names either at the start of a line or preceded immediately by - whitespace and followed immediately by either the end of a line or whitespace - OR followed immediately by '(D)', '(S)', '(D*)' or '(S*)' - """ - converted_output = raw_output - for port_name in iface_alias_converter.port_dict.keys(): - converted_output = re.sub(r"(^|\s){}(\([DS]\*{{0,1}}\)(?:$|\s))".format(port_name), - r"\1{}\2".format(iface_alias_converter.name_to_alias(port_name)), - converted_output) - click.echo(converted_output.rstrip('\n')) - - else: - """ - Default command conversion - Search for port names either at the start of a line or preceded immediately by - whitespace and followed immediately by either the end of a line or whitespace - or a comma followed by whitespace - """ - converted_output = raw_output - for port_name in iface_alias_converter.port_dict.keys(): - converted_output = re.sub(r"(^|\s){}($|,{{0,1}}\s)".format(port_name), - r"\1{}\2".format(iface_alias_converter.name_to_alias(port_name)), - converted_output) - click.echo(converted_output.rstrip('\n')) - - rc = process.poll() - if rc != 0: - sys.exit(rc) - def get_bgp_summary_extended(command_output): """ @@ -412,6 +251,7 @@ def cli(ctx): ctx.obj = Db() cli.add_command(feature.feature) +cli.add_command(interfaces.interfaces) cli.add_command(vlan.vlan) # @@ -613,365 +453,6 @@ def snmptrap (ctx): body.append([ver, traptable[row]['DestIp'], traptable[row]['DestPort'], traptable[row]['vrf'], traptable[row]['Community']]) click.echo(tabulate(body, header)) - -# -# 'interfaces' group ("show interfaces ...") -# - -@cli.group(cls=clicommon.AliasedGroup) -def interfaces(): - """Show details of the network interfaces""" - pass - -# 'alias' subcommand ("show interfaces alias") -@interfaces.command() -@click.argument('interfacename', required=False) -def alias(interfacename): - """Show Interface Name/Alias Mapping""" - - cmd = 'sonic-cfggen -d --var-json "PORT"' - p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) - - port_dict = json.loads(p.stdout.read()) - - header = ['Name', 'Alias'] - body = [] - - if interfacename is not None: - if clicommon.get_interface_naming_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - # If we're given an interface name, output name and alias for that interface only - if interfacename in port_dict: - if 'alias' in port_dict[interfacename]: - body.append([interfacename, port_dict[interfacename]['alias']]) - else: - body.append([interfacename, interfacename]) - else: - click.echo("Invalid interface name, '{0}'".format(interfacename)) - return - else: - # Output name and alias for all interfaces - for port_name in natsorted(port_dict.keys()): - if 'alias' in port_dict[port_name]: - body.append([port_name, port_dict[port_name]['alias']]) - else: - body.append([port_name, port_name]) - - click.echo(tabulate(body, header)) - - -# -# 'breakout' group ### -# -@interfaces.group(invoke_without_command=True) -@click.pass_context -def breakout(ctx): - """Show Breakout Mode information by interfaces""" - # Reading data from Redis configDb - config_db = ConfigDBConnector() - config_db.connect() - ctx.obj = {'db': config_db} - - try: - cur_brkout_tbl = config_db.get_table('BREAKOUT_CFG') - except Exception as e: - click.echo("Breakout table is not present in Config DB") - raise click.Abort() - - if ctx.invoked_subcommand is None: - # Get port capability from platform and hwsku related files - platform_path, hwsku_path = device_info.get_paths_to_platform_and_hwsku_dirs() - platform_file = os.path.join(platform_path, PLATFORM_JSON) - platform_dict = readJsonFile(platform_file)['interfaces'] - hwsku_dict = readJsonFile(os.path.join(hwsku_path, HWSKU_JSON))['interfaces'] - - if not platform_dict or not hwsku_dict: - click.echo("Can not load port config from {} or {} file".format(PLATFORM_JSON, HWSKU_JSON)) - raise click.Abort() - - for port_name in platform_dict.keys(): - cur_brkout_mode = cur_brkout_tbl[port_name]["brkout_mode"] - - # Update deafult breakout mode and current breakout mode to platform_dict - platform_dict[port_name].update(hwsku_dict[port_name]) - platform_dict[port_name]["Current Breakout Mode"] = cur_brkout_mode - - # List all the child ports if present - child_port_dict = get_child_ports(port_name, cur_brkout_mode, platformFile) - if not child_port_dict: - click.echo("Cannot find ports from {} file ".format(PLATFORM_JSON)) - raise click.Abort() - - child_ports = natsorted(child_port_dict.keys()) - - children, speeds = [], [] - # Update portname and speed of child ports if present - for port in child_ports: - speed = config_db.get_entry('PORT', port).get('speed') - if speed is not None: - speeds.append(str(int(speed)//1000)+'G') - children.append(port) - - platform_dict[port_name]["child ports"] = ",".join(children) - platform_dict[port_name]["child port speeds"] = ",".join(speeds) - - # Sorted keys by name in natural sort Order for human readability - parsed = OrderedDict((k, platform_dict[k]) for k in natsorted(platform_dict.keys())) - click.echo(json.dumps(parsed, indent=4)) - -# 'breakout current-mode' subcommand ("show interfaces breakout current-mode") -@breakout.command('current-mode') -@click.argument('interface', metavar='', required=False, type=str) -@click.pass_context -def currrent_mode(ctx, interface): - """Show current Breakout mode Info by interface(s)""" - - config_db = ctx.obj['db'] - - header = ['Interface', 'Current Breakout Mode'] - body = [] - - try: - cur_brkout_tbl = config_db.get_table('BREAKOUT_CFG') - except Exception as e: - click.echo("Breakout table is not present in Config DB") - raise click.Abort() - - # Show current Breakout Mode of user prompted interface - if interface is not None: - body.append([interface, str(cur_brkout_tbl[interface]['brkout_mode'])]) - click.echo(tabulate(body, header, tablefmt="grid")) - return - - # Show current Breakout Mode for all interfaces - for name in natsorted(cur_brkout_tbl.keys()): - body.append([name, str(cur_brkout_tbl[name]['brkout_mode'])]) - click.echo(tabulate(body, header, tablefmt="grid")) - - -# -# 'neighbor' group ### -# -@interfaces.group(cls=clicommon.AliasedGroup) -def neighbor(): - """Show neighbor related information""" - pass - -# 'expected' subcommand ("show interface neighbor expected") -@neighbor.command() -@click.argument('interfacename', required=False) -def expected(interfacename): - """Show expected neighbor information by interfaces""" - neighbor_cmd = 'sonic-cfggen -d --var-json "DEVICE_NEIGHBOR"' - p1 = subprocess.Popen(neighbor_cmd, shell=True, stdout=subprocess.PIPE) - try : - neighbor_dict = json.loads(p1.stdout.read()) - except ValueError: - print("DEVICE_NEIGHBOR information is not present.") - return - - neighbor_metadata_cmd = 'sonic-cfggen -d --var-json "DEVICE_NEIGHBOR_METADATA"' - p2 = subprocess.Popen(neighbor_metadata_cmd, shell=True, stdout=subprocess.PIPE) - try : - neighbor_metadata_dict = json.loads(p2.stdout.read()) - except ValueError: - print("DEVICE_NEIGHBOR_METADATA information is not present.") - return - - #Swap Key and Value from interface: name to name: interface - device2interface_dict = {} - for port in natsorted(neighbor_dict['DEVICE_NEIGHBOR'].keys()): - temp_port = port - if clicommon.get_interface_naming_mode() == "alias": - port = iface_alias_converter.name_to_alias(port) - neighbor_dict['DEVICE_NEIGHBOR'][port] = neighbor_dict['DEVICE_NEIGHBOR'].pop(temp_port) - device2interface_dict[neighbor_dict['DEVICE_NEIGHBOR'][port]['name']] = {'localPort': port, 'neighborPort': neighbor_dict['DEVICE_NEIGHBOR'][port]['port']} - - header = ['LocalPort', 'Neighbor', 'NeighborPort', 'NeighborLoopback', 'NeighborMgmt', 'NeighborType'] - body = [] - if interfacename: - for device in natsorted(neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'].keys()): - if device2interface_dict[device]['localPort'] == interfacename: - body.append([device2interface_dict[device]['localPort'], - device, - device2interface_dict[device]['neighborPort'], - neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'][device]['lo_addr'], - neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'][device]['mgmt_addr'], - neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'][device]['type']]) - else: - for device in natsorted(neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'].keys()): - body.append([device2interface_dict[device]['localPort'], - device, - device2interface_dict[device]['neighborPort'], - neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'][device]['lo_addr'], - neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'][device]['mgmt_addr'], - neighbor_metadata_dict['DEVICE_NEIGHBOR_METADATA'][device]['type']]) - - click.echo(tabulate(body, header)) - -@interfaces.group(cls=clicommon.AliasedGroup) -def transceiver(): - """Show SFP Transceiver information""" - pass - - -@transceiver.command() -@click.argument('interfacename', required=False) -@click.option('-d', '--dom', 'dump_dom', is_flag=True, help="Also display Digital Optical Monitoring (DOM) data") -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def eeprom(interfacename, dump_dom, verbose): - """Show interface transceiver EEPROM information""" - - cmd = "sfpshow eeprom" - - if dump_dom: - cmd += " --dom" - - if interfacename is not None: - if clicommon.get_interface_naming_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " -p {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -@transceiver.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def lpmode(interfacename, verbose): - """Show interface transceiver low-power mode status""" - - cmd = "sudo sfputil show lpmode" - - if interfacename is not None: - if clicommon.get_interface_naming_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " -p {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - -@transceiver.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def presence(interfacename, verbose): - """Show interface transceiver presence""" - - cmd = "sfpshow presence" - - if interfacename is not None: - if clicommon.get_interface_naming_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " -p {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -@interfaces.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def description(interfacename, verbose): - """Show interface status, protocol and description""" - - cmd = "intfutil description" - - if interfacename is not None: - if clicommon.get_interface_naming_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -@interfaces.command() -@click.argument('interfacename', required=False) -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def status(interfacename, verbose): - """Show Interface status information""" - - cmd = "intfutil status" - - if interfacename is not None: - if clicommon.get_interface_naming_mode() == "alias": - interfacename = iface_alias_converter.alias_to_name(interfacename) - - cmd += " {}".format(interfacename) - - run_command(cmd, display_cmd=verbose) - - -# 'counters' subcommand ("show interfaces counters") -@interfaces.group(invoke_without_command=True) -@click.option('-a', '--printall', is_flag=True) -@click.option('-p', '--period') -@click.option('-i', '--interface') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -@click.pass_context -def counters(ctx, verbose, period, interface, printall): - """Show interface counters""" - - if ctx.invoked_subcommand is None: - cmd = "portstat" - - if printall: - cmd += " -a" - if period is not None: - cmd += " -p {}".format(period) - if interface is not None: - cmd += " -i {}".format(interface) - - run_command(cmd, display_cmd=verbose) - -# 'errors' subcommand ("show interfaces counters errors") -@counters.command() -@click.option('-p', '--period') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def errors(verbose, period): - """Show interface counters errors""" - cmd = "portstat -e" - if period is not None: - cmd += " -p {}".format(period) - run_command(cmd, display_cmd=verbose) - -# 'rates' subcommand ("show interfaces counters rates") -@counters.command() -@click.option('-p', '--period') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def rates(verbose, period): - """Show interface counters rates""" - cmd = "portstat -R" - if period is not None: - cmd += " -p {}".format(period) - run_command(cmd, display_cmd=verbose) - -# 'counters' subcommand ("show interfaces counters rif") -@counters.command() -@click.argument('interface', metavar='', required=False, type=str) -@click.option('-p', '--period') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def rif(interface, period, verbose): - """Show interface counters""" - - cmd = "intfstat" - if period is not None: - cmd += " -p {}".format(period) - if interface is not None: - cmd += " -i {}".format(interface) - - run_command(cmd, display_cmd=verbose) - -# 'portchannel' subcommand ("show interfaces portchannel") -@interfaces.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def portchannel(verbose): - """Show PortChannel information""" - cmd = "sudo teamshow" - run_command(cmd, display_cmd=verbose) - # # 'subinterfaces' group ("show subinterfaces ...") # @@ -1072,15 +553,6 @@ def stats(verbose): run_command(cmd, display_cmd=verbose) -# 'naming_mode' subcommand ("show interfaces naming_mode") -@interfaces.command('naming_mode') -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def naming_mode(verbose): - """Show interface naming_mode status""" - - click.echo(clicommon.get_interface_naming_mode()) - - # # 'watermark' group ("show watermark telemetry interval") # diff --git a/tests/interfaces_test.py b/tests/interfaces_test.py new file mode 100644 index 0000000000..084355a1de --- /dev/null +++ b/tests/interfaces_test.py @@ -0,0 +1,175 @@ +import os + +from click.testing import CliRunner + +import show.main as show + +show_interfaces_alias_output="""\ +Name Alias +----------- ------- +Ethernet0 etp1 +Ethernet4 etp2 +Ethernet8 etp3 +Ethernet12 etp4 +Ethernet16 etp5 +Ethernet20 etp6 +Ethernet24 etp7 +Ethernet28 etp8 +Ethernet32 etp9 +Ethernet36 etp10 +Ethernet40 etp11 +Ethernet44 etp12 +Ethernet48 etp13 +Ethernet52 etp14 +Ethernet56 etp15 +Ethernet60 etp16 +Ethernet64 etp17 +Ethernet68 etp18 +Ethernet72 etp19 +Ethernet76 etp20 +Ethernet80 etp21 +Ethernet84 etp22 +Ethernet88 etp23 +Ethernet92 etp24 +Ethernet96 etp25 +Ethernet100 etp26 +Ethernet104 etp27 +Ethernet108 etp28 +Ethernet112 etp29 +Ethernet116 etp30 +Ethernet120 etp31 +Ethernet124 etp32 +""" + +show_interfaces_alias_Ethernet0_output="""\ +Name Alias +--------- ------- +Ethernet0 etp1 +""" + +show_interfaces_neighbor_expected_output="""\ +LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType +----------- ---------- -------------- ------------------ -------------- -------------- +Ethernet112 ARISTA01T1 Ethernet1 None 10.250.0.51 LeafRouter +Ethernet116 ARISTA02T1 Ethernet1 None 10.250.0.52 LeafRouter +Ethernet120 ARISTA03T1 Ethernet1 None 10.250.0.53 LeafRouter +Ethernet124 ARISTA04T1 Ethernet1 None 10.250.0.54 LeafRouter +""" + +show_interfaces_neighbor_expected_output_Ethernet112="""\ +LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType +----------- ---------- -------------- ------------------ -------------- -------------- +Ethernet112 ARISTA01T1 Ethernet1 None 10.250.0.51 LeafRouter +""" + +show_interfaces_neighbor_expected_output_etp29="""\ +LocalPort Neighbor NeighborPort NeighborLoopback NeighborMgmt NeighborType +----------- ---------- -------------- ------------------ -------------- -------------- +etp29 ARISTA01T1 Ethernet1 None 10.250.0.51 LeafRouter +""" + +class TestInterfaces(object): + @classmethod + def setup_class(cls): + print("SETUP") + + def test_show_interfaces(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + + def test_show_interfaces_alias(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["alias"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interfaces_alias_output + + def test_show_interfaces_alias_Ethernet0(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["alias"], ["Ethernet0"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interfaces_alias_Ethernet0_output + + def test_show_interfaces_alias_etp1(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["interfaces"].commands["alias"], ["etp1"]) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interfaces_alias_Ethernet0_output + + def test_show_interfaces_alias_invalid_name(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["alias"], ["Ethernet3"]) + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: Invalid interface name Ethernet3" in result.output + + def test_show_interfaces_naming_mode_default(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["naming_mode"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output.rstrip() == "default" + + def test_show_interfaces_naming_mode_alias(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["interfaces"].commands["naming_mode"], []) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output.rstrip() == "alias" + + def test_show_interfaces_neighbor_expected(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], []) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert result.output == show_interfaces_neighbor_expected_output + + def test_show_interfaces_neighbor_expected_Ethernet112(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], ["Ethernet112"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert result.output == show_interfaces_neighbor_expected_output_Ethernet112 + + def test_show_interfaces_neighbor_expected_etp29(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], ["etp29"]) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert result.output == show_interfaces_neighbor_expected_output_etp29 + + def test_show_interfaces_neighbor_expected_Ethernet0(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["neighbor"].commands["expected"], ["Ethernet0"]) + print(result.exit_code) + print(result.output) + # traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert result.output.rstrip() == "No neighbor information available for interface Ethernet0" + + @classmethod + def teardown_class(cls): + print("TEARDOWN") diff --git a/tests/intfstat_test.py b/tests/intfstat_test.py index f1d11e4f84..fcf20620e8 100644 --- a/tests/intfstat_test.py +++ b/tests/intfstat_test.py @@ -1,5 +1,7 @@ import sys import os +import traceback + from click.testing import CliRunner test_path = os.path.dirname(os.path.abspath(__file__)) @@ -9,33 +11,41 @@ sys.path.insert(0, modules_path) import mock_tables.dbconnector -import show.main as show +import show.main as show import clear.main as clear +show_interfaces_counters_rif_output="""\ + IFACE RX_OK RX_BPS RX_PPS RX_ERR TX_OK TX_BPS TX_PPS TX_ERR +--------------- ------- -------- -------- -------- ------- -------- -------- -------- + Ethernet20 4 N/A N/A 2 8 N/A N/A 6 +PortChannel0001 883 N/A N/A 0 0 N/A N/A 0 +PortChannel0002 883 N/A N/A 0 0 N/A N/A 0 +PortChannel0003 0 N/A N/A 0 0 N/A N/A 0 +PortChannel0004 883 N/A N/A 0 0 N/A N/A 0 + Vlan1000 0 N/A N/A 0 0 N/A N/A 0 +""" + class TestIntfstat(object): @classmethod def setup_class(cls): print("SETUP") os.environ["PATH"] += os.pathsep + scripts_path - os.environ["UTILITIES_UNIT_TESTING"] = "1" + os.environ["UTILITIES_UNIT_TESTING"] = "2" def test_no_param(self): runner = CliRunner() result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["rif"], []) - interfaces = ["Ethernet20", "PortChannel0001", "PortChannel0002", "PortChannel0003", "PortChannel0004", "Vlan1000"] + print(result.exit_code) print(result.output) - result_lines = result.output.split('\n') - #assert all interfaces are present in the output and in the correct order - for i, interface in enumerate(interfaces): - assert interface in result_lines[i+2] - header_lines = 3 - assert len(result_lines) == header_lines + len(interfaces) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert result.output == show_interfaces_counters_rif_output def test_verbose(self): runner = CliRunner() result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["rif"], ["--verbose"]) print(result.output) - assert result.output.split('\n')[0] == "Command: intfstat" + assert result.output.split('\n')[0] == "Running command: intfstat" def test_period(self): runner = CliRunner() @@ -56,8 +66,8 @@ def test_single_intfs(self): ---------- RX: - 4 packets - 3 bytes + 4 packets + 3 bytes 2 error packets 1128 error bytes TX: @@ -78,8 +88,8 @@ def test_clear_single_intfs(self): ---------- RX: - 0 packets - 0 bytes + 0 packets + 0 bytes 0 error packets 0 error bytes TX: @@ -116,8 +126,11 @@ def test_alias_mode(self): runner = CliRunner() result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["rif"], []) # no aliases for Portchannels and Vlans for now - interfaces = ["etp6", "PortChannel0001", "PortChannel0002", "PortChannel0003", "PortChannel0004", "Vlan1000"] + print(result.exit_code) print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + interfaces = ["etp6", "PortChannel0001", "PortChannel0002", "PortChannel0003", "PortChannel0004", "Vlan1000"] result_lines = result.output.split('\n') #assert all interfaces are present in the output and in the correct order for i, interface in enumerate(interfaces): diff --git a/tests/intfutil_test.py b/tests/intfutil_test.py index 1fd5fe777e..19c4189ed7 100644 --- a/tests/intfutil_test.py +++ b/tests/intfutil_test.py @@ -26,12 +26,48 @@ PortChannel1001 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A """ +show_interface_status_Ethernet32_output="""\ + Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC +----------- ----------- ------- ----- ----- ------- --------------- ------ ------- ------ ---------- + Ethernet32 13,14,15,16 40G 9100 rs etp9 PortChannel1001 up up N/A off +""" + +show_interface_description_output="""\ + Interface Oper Admin Alias Description +----------- ------ ------- --------- -------------------- + Ethernet0 down up Ethernet0 ARISTA01T2:Ethernet1 + Ethernet32 up up etp9 Servers7:eth0 +Ethernet112 up up etp29 ARISTA01T1:Ethernet1 +Ethernet116 up up etp30 ARISTA02T1:Ethernet1 +Ethernet120 up up etp31 ARISTA03T1:Ethernet1 +Ethernet124 up up etp32 ARISTA04T1:Ethernet1 +""" + +show_interface_description_Ethernet0_output="""\ + Interface Oper Admin Alias Description +----------- ------ ------- --------- -------------------- + Ethernet0 down up Ethernet0 ARISTA01T2:Ethernet1 +""" + +show_interface_description_Ethernet0_verbose_output="""\ +Running command: intfutil description Ethernet0 + Interface Oper Admin Alias Description +----------- ------ ------- --------- -------------------- + Ethernet0 down up Ethernet0 ARISTA01T2:Ethernet1 +""" + +show_interface_description_eth9_output="""\ + Interface Oper Admin Alias Description +----------- ------ ------- ------- ------------- + Ethernet32 up up etp9 Servers7:eth0 +""" + class TestIntfutil(TestCase): @classmethod def setup_class(cls): print("SETUP") os.environ["PATH"] += os.pathsep + scripts_path - os.environ["UTILITIES_UNIT_TESTING"] = "1" + os.environ["UTILITIES_UNIT_TESTING"] = "2" def setUp(self): self.runner = CliRunner() @@ -41,20 +77,79 @@ def setUp(self): def test_intf_status(self): # Test 'show interfaces status' result = self.runner.invoke(show.cli.commands["interfaces"].commands["status"], []) - print >> sys.stderr, result.output + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 assert result.output == show_interface_status_output # Test 'intfutil status' output = subprocess.check_output('intfutil status', stderr=subprocess.STDOUT, shell=True) - print >> sys.stderr, output + print(output) assert result.output == show_interface_status_output # Test 'show interfaces status --verbose' def test_intf_status_verbose(self): result = self.runner.invoke(show.cli.commands["interfaces"].commands["status"], ["--verbose"]) - print >> sys.stderr, result.output - expected_output = "Command: intfutil status" - self.assertEqual(result.output.split('\n')[0], expected_output) + assert result.exit_code == 0 + print(result.exit_code) + print(result.output) + expected_output = "Running command: intfutil status" + assert result.output.split('\n')[0] == expected_output + + def test_intf_status_Ethernet32(self): + result = self.runner.invoke(show.cli.commands["interfaces"].commands["status"], ["Ethernet32"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interface_status_Ethernet32_output + + def test_intf_status_etp9(self): + os.environ["SONIC_CLI_IFACE_MODE"] = "alias" + result = self.runner.invoke(show.cli.commands["interfaces"].commands["status"], ["etp9"]) + os.environ["SONIC_CLI_IFACE_MODE"] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interface_status_Ethernet32_output + + def test_show_interfaces_description(self): + result = self.runner.invoke(show.cli.commands["interfaces"].commands["description"], []) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interface_description_output + + def test_show_interfaces_description_Ethernet0(self): + result = self.runner.invoke(show.cli.commands["interfaces"].commands["description"], ["Ethernet0"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interface_description_Ethernet0_output + + def test_show_interfaces_description_etp9_in_alias_mode(self): + os.environ["SONIC_CLI_IFACE_MODE"] = "alias" + result = self.runner.invoke(show.cli.commands["interfaces"].commands["description"], ["etp9"]) + os.environ["SONIC_CLI_IFACE_MODE"] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interface_description_eth9_output + + def test_show_interfaces_description_etp33_in_alias_mode(self): + os.environ["SONIC_CLI_IFACE_MODE"] = "alias" + result = self.runner.invoke(show.cli.commands["interfaces"].commands["description"], ["etp33"]) + os.environ["SONIC_CLI_IFACE_MODE"] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code != 0 + assert "Error: cannot find interface name for alias etp33" in result.output + + def test_show_interfaces_description_Ethernet0_verbose(self): + result = self.runner.invoke(show.cli.commands["interfaces"].commands["description"], ["Ethernet0", "--verbose"]) + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_interface_description_Ethernet0_verbose_output # Test 'show subinterfaces status' / 'intfutil status subport' def test_subintf_status(self): diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 0dc8939c12..51bd7fb683 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -565,5 +565,141 @@ "state": "enabled", "auto_restart": "enabled", "high_mem_alert": "disabled" + }, + "DEVICE_NEIGHBOR|Ethernet4": { + "name": "Servers0", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet8": { + "name": "Servers1", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet12": { + "name": "Servers2", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet16": { + "name": "Servers3", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet20": { + "name": "Servers4", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet24": { + "name": "Servers5", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet28": { + "name": "Servers6", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet32": { + "name": "Servers7", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet36": { + "name": "Servers8", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet40": { + "name": "Servers9", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet44": { + "name": "Servers10", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet48": { + "name": "Servers11", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet52": { + "name": "Servers12", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet56": { + "name": "Servers13", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet60": { + "name": "Servers14", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet64": { + "name": "Servers15", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet68": { + "name": "Servers16", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet72": { + "name": "Servers17", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet76": { + "name": "Servers18", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet80": { + "name": "Servers19", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet84": { + "name": "Servers20", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet88": { + "name": "Servers21", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet92": { + "name": "Servers22", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet96": { + "name": "Servers23", + "port": "eth0" + }, + "DEVICE_NEIGHBOR|Ethernet112": { + "name": "ARISTA01T1", + "port": "Ethernet1" + }, + "DEVICE_NEIGHBOR|Ethernet116": { + "name": "ARISTA02T1", + "port": "Ethernet1" + }, + "DEVICE_NEIGHBOR|Ethernet120": { + "name": "ARISTA03T1", + "port": "Ethernet1" + }, + "DEVICE_NEIGHBOR|Ethernet124": { + "name": "ARISTA04T1", + "port": "Ethernet1" + }, + "DEVICE_NEIGHBOR_METADATA|ARISTA01T1": { + "hwsku": "Arista-VM", + "lo_addr": "None", + "mgmt_addr": "10.250.0.51", + "type": "LeafRouter" + }, + "DEVICE_NEIGHBOR_METADATA|ARISTA02T1": { + "hwsku": "Arista-VM", + "lo_addr": "None", + "mgmt_addr": "10.250.0.52", + "type": "LeafRouter" + }, + "DEVICE_NEIGHBOR_METADATA|ARISTA03T1": { + "hwsku": "Arista-VM", + "lo_addr": "None", + "mgmt_addr": "10.250.0.53", + "type": "LeafRouter" + }, + "DEVICE_NEIGHBOR_METADATA|ARISTA04T1": { + "hwsku": "Arista-VM", + "lo_addr": "None", + "mgmt_addr": "10.250.0.54", + "type": "LeafRouter" } } diff --git a/tests/sfp_test.py b/tests/sfp_test.py index c9f5784f3f..d714096808 100644 --- a/tests/sfp_test.py +++ b/tests/sfp_test.py @@ -10,33 +10,8 @@ import show.main as show -class TestSFP(object): - @classmethod - def setup_class(cls): - print("SETUP") - os.environ["PATH"] += os.pathsep + scripts_path - os.environ["UTILITIES_UNIT_TESTING"] = "1" - - def test_sfp_presence(self): - runner = CliRunner() - result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["presence"], ["Ethernet0"]) - expected = """Port Presence ---------- ---------- -Ethernet0 Present -""" - assert result.output == expected - - result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["presence"], ["Ethernet200"]) - expected = """Port Presence ------------ ----------- -Ethernet200 Not present -""" - assert result.output == expected - - def test_sfp_eeprom_with_dom(self): - runner = CliRunner() - result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["eeprom"], ["Ethernet0 -d"]) - expected = """Ethernet0: SFP EEPROM detected +test_sfp_eeprom_with_dom_output = """\ +Ethernet0: SFP EEPROM detected Application Advertisement: N/A Connector: No separable connector Encoding: 64B66B @@ -45,15 +20,15 @@ def test_sfp_eeprom_with_dom(self): Identifier: QSFP28 or later Length Cable Assembly(m): 3 Nominal Bit Rate(100Mbs): 255 - Specification compliance: + Specification compliance: 10/40G Ethernet Compliance Code: 40G Active Cable (XLPPI) - Vendor Date Code(YYYY-MM-DD Lot): 2017-01-13 + Vendor Date Code(YYYY-MM-DD Lot): 2017-01-13 Vendor Name: Mellanox Vendor OUI: 00-02-c9 Vendor PN: MFA1A00-C003 Vendor Rev: AC Vendor SN: MT1706FT02064 - ChannelMonitorValues: + ChannelMonitorValues: RX1Power: 0.3802dBm RX2Power: -0.4871dBm RX3Power: -0.0860dBm @@ -62,7 +37,7 @@ def test_sfp_eeprom_with_dom(self): TX2Bias: 6.7500mA TX3Bias: 6.7500mA TX4Bias: 6.7500mA - ChannelThresholdValues: + ChannelThresholdValues: RxPowerHighAlarm : 3.4001dBm RxPowerHighWarning: 2.4000dBm RxPowerLowAlarm : -13.5067dBm @@ -71,10 +46,10 @@ def test_sfp_eeprom_with_dom(self): TxBiasHighWarning : 9.5000mA TxBiasLowAlarm : 0.5000mA TxBiasLowWarning : 1.0000mA - ModuleMonitorValues: + ModuleMonitorValues: Temperature: 30.9258C Vcc: 3.2824Volts - ModuleThresholdValues: + ModuleThresholdValues: TempHighAlarm : 75.0000C TempHighWarning: 70.0000C TempLowAlarm : -5.0000C @@ -83,14 +58,10 @@ def test_sfp_eeprom_with_dom(self): VccHighWarning : 3.4650Volts VccLowAlarm : 2.9700Volts VccLowWarning : 3.1349Volts - """ - assert result.output == expected - def test_sfp_eeprom(self): - runner = CliRunner() - result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["eeprom"], ["Ethernet0"]) - expected = """Ethernet0: SFP EEPROM detected +test_sfp_eeprom_output = """\ +Ethernet0: SFP EEPROM detected Application Advertisement: N/A Connector: No separable connector Encoding: 64B66B @@ -99,18 +70,53 @@ def test_sfp_eeprom(self): Identifier: QSFP28 or later Length Cable Assembly(m): 3 Nominal Bit Rate(100Mbs): 255 - Specification compliance: + Specification compliance: 10/40G Ethernet Compliance Code: 40G Active Cable (XLPPI) - Vendor Date Code(YYYY-MM-DD Lot): 2017-01-13 + Vendor Date Code(YYYY-MM-DD Lot): 2017-01-13 Vendor Name: Mellanox Vendor OUI: 00-02-c9 Vendor PN: MFA1A00-C003 Vendor Rev: AC Vendor SN: MT1706FT02064 +""" +class TestSFP(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "2" + + def test_sfp_presence(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["presence"], ["Ethernet0"]) + expected = """Port Presence +--------- ---------- +Ethernet0 Present """ + assert result.exit_code == 0 assert result.output == expected - + + result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["presence"], ["Ethernet200"]) + expected = """Port Presence +----------- ----------- +Ethernet200 Not present +""" + assert result.exit_code == 0 + assert result.output == expected + + def test_sfp_eeprom_with_dom(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["eeprom"], ["Ethernet0 -d"]) + assert result.exit_code == 0 + assert "\n".join([ l.rstrip() for l in result.output.split('\n')]) == test_sfp_eeprom_with_dom_output + + def test_sfp_eeprom(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["eeprom"], ["Ethernet0"]) + assert result.exit_code == 0 + assert "\n".join([ l.rstrip() for l in result.output.split('\n')]) == test_sfp_eeprom_output + result = runner.invoke(show.cli.commands["interfaces"].commands["transceiver"].commands["eeprom"], ["Ethernet200"]) result_lines = result.output.strip('\n') expected = "Ethernet200: SFP EEPROM Not detected" @@ -121,4 +127,3 @@ def teardown_class(cls): print("TEARDOWN") os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) os.environ["UTILITIES_UNIT_TESTING"] = "0" - diff --git a/utilities_common/cli.py b/utilities_common/cli.py index 9b9c2693c1..d014b7c578 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -1,9 +1,11 @@ import os +import re import sys import netaddr import subprocess import click +from natsort import natsorted from utilities_common.db import Db @@ -182,6 +184,9 @@ def alias_to_name(self, interface_alias): # interface_alias not in port_dict. Just return interface_alias return interface_alias if sub_intf_sep_idx == -1 else interface_alias + VLAN_SUB_INTERFACE_SEPARATOR + vlan_id +# Global class instance for SONiC interface name to alias conversion +iface_alias_converter = InterfaceAliasConverter() + def get_interface_naming_mode(): mode = os.getenv('SONIC_CLI_IFACE_MODE') if mode is None: @@ -251,6 +256,22 @@ def is_port_vlan_member(config_db, port, vlan): return False +def interface_is_in_vlan(vlan_member_table, interface_name): + """ Check if an interface is in a vlan """ + for _,intf in vlan_member_table.keys(): + if intf == interface_name: + return True + + return False + +def interface_is_in_portchannel(portchannel_member_table, interface_name): + """ Check if an interface is part of portchannel """ + for _,intf in portchannel_member_table.keys(): + if intf == interface_name: + return True + + return False + def is_port_router_interface(config_db, port): """Check if port is a router interface""" @@ -272,7 +293,7 @@ def is_pc_router_interface(config_db, pc): return False def is_port_mirror_dst_port(config_db, port): - """ Check if port is already configured as mirror destination port """ + """Check if port is already configured as mirror destination port """ mirror_table = config_db.get_table('MIRROR_SESSION') for _,v in mirror_table.items(): if 'dst_port' in v and v['dst_port'] == port: @@ -280,7 +301,177 @@ def is_port_mirror_dst_port(config_db, port): return False -def run_command(command, display_cmd=False, ignore_error=False): +def interface_has_mirror_config(mirror_table, interface_name): + """Check if port is already configured with mirror config """ + for _,v in mirror_table.items(): + if 'src_port' in v and v['src_port'] == interface_name: + return True + if 'dst_port' in v and v['dst_port'] == interface_name: + return True + + return False + +def print_output_in_alias_mode(output, index): + """Convert and print all instances of SONiC interface + name to vendor-sepecific interface aliases. + """ + + alias_name = "" + interface_name = "" + + # Adjust tabulation width to length of alias name + if output.startswith("---"): + word = output.split() + dword = word[index] + underline = dword.rjust(iface_alias_converter.alias_max_length, + '-') + word[index] = underline + output = ' ' .join(word) + + # Replace SONiC interface name with vendor alias + word = output.split() + if word: + interface_name = word[index] + interface_name = interface_name.replace(':', '') + for port_name in natsorted(iface_alias_converter.port_dict.keys()): + if interface_name == port_name: + alias_name = iface_alias_converter.port_dict[port_name]['alias'] + if alias_name: + if len(alias_name) < iface_alias_converter.alias_max_length: + alias_name = alias_name.rjust( + iface_alias_converter.alias_max_length) + output = output.replace(interface_name, alias_name, 1) + + click.echo(output.rstrip('\n')) + +def run_command_in_alias_mode(command): + """Run command and replace all instances of SONiC interface names + in output with vendor-sepecific interface aliases. + """ + + process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + + if output: + index = 1 + raw_output = output + output = output.lstrip() + + if command.startswith("portstat"): + """Show interface counters""" + index = 0 + if output.startswith("IFACE"): + output = output.replace("IFACE", "IFACE".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command.startswith("intfstat"): + """Show RIF counters""" + index = 0 + if output.startswith("IFACE"): + output = output.replace("IFACE", "IFACE".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command == "pfcstat": + """Show pfc counters""" + index = 0 + if output.startswith("Port Tx"): + output = output.replace("Port Tx", "Port Tx".rjust( + iface_alias_converter.alias_max_length)) + + elif output.startswith("Port Rx"): + output = output.replace("Port Rx", "Port Rx".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif (command.startswith("sudo sfputil show eeprom")): + """show interface transceiver eeprom""" + index = 0 + print_output_in_alias_mode(raw_output, index) + + elif (command.startswith("sudo sfputil show")): + """show interface transceiver lpmode, + presence + """ + index = 0 + if output.startswith("Port"): + output = output.replace("Port", "Port".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command == "sudo lldpshow": + """show lldp table""" + index = 0 + if output.startswith("LocalPort"): + output = output.replace("LocalPort", "LocalPort".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command.startswith("queuestat"): + """show queue counters""" + index = 0 + if output.startswith("Port"): + output = output.replace("Port", "Port".rjust( + iface_alias_converter.alias_max_length)) + print_output_in_alias_mode(output, index) + + elif command == "fdbshow": + """show mac""" + index = 3 + if output.startswith("No."): + output = " " + output + output = re.sub( + 'Type', ' Type', output) + elif output[0].isdigit(): + output = " " + output + print_output_in_alias_mode(output, index) + + elif command.startswith("nbrshow"): + """show arp""" + index = 2 + if "Vlan" in output: + output = output.replace('Vlan', ' Vlan') + print_output_in_alias_mode(output, index) + + elif command.startswith("sudo teamshow"): + """ + sudo teamshow + Search for port names either at the start of a line or preceded immediately by + whitespace and followed immediately by either the end of a line or whitespace + OR followed immediately by '(D)', '(S)', '(D*)' or '(S*)' + """ + converted_output = raw_output + for port_name in iface_alias_converter.port_dict.keys(): + converted_output = re.sub(r"(^|\s){}(\([DS]\*{{0,1}}\)(?:$|\s))".format(port_name), + r"\1{}\2".format(iface_alias_converter.name_to_alias(port_name)), + converted_output) + click.echo(converted_output.rstrip('\n')) + + else: + """ + Default command conversion + Search for port names either at the start of a line or preceded immediately by + whitespace and followed immediately by either the end of a line or whitespace + or a comma followed by whitespace + """ + converted_output = raw_output + for port_name in iface_alias_converter.port_dict.keys(): + converted_output = re.sub(r"(^|\s){}($|,{{0,1}}\s)".format(port_name), + r"\1{}\2".format(iface_alias_converter.name_to_alias(port_name)), + converted_output) + click.echo(converted_output.rstrip('\n')) + + rc = process.poll() + if rc != 0: + sys.exit(rc) + + +def run_command(command, display_cmd=False, ignore_error=False, return_cmd=False, interactive_mode=False): """Run bash command and print output to stdout """ @@ -290,11 +481,37 @@ def run_command(command, display_cmd=False, ignore_error=False): if os.environ["UTILITIES_UNIT_TESTING"] == "1": return + # No conversion needed for intfutil commands as it already displays + # both SONiC interface name and alias name for all interfaces. + if get_interface_naming_mode() == "alias" and not command.startswith("intfutil"): + run_command_in_alias_mode(command) + raise sys.exit(0) + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) - (out, err) = proc.communicate() - if len(out) > 0: - click.echo(out) + if return_cmd: + output = proc.communicate()[0].decode("utf-8") + return output + + if not interactive_mode: + (out, err) = proc.communicate() + + if len(out) > 0: + click.echo(out.rstrip('\n')) + + if proc.returncode != 0 and not ignore_error: + sys.exit(proc.returncode) + + return - if proc.returncode != 0 and not ignore_error: - sys.exit(proc.returncode) + # interactive mode + while True: + output = proc.stdout.readline() + if output == "" and proc.poll() is not None: + break + if output: + click.echo(output.rstrip('\n')) + + rc = proc.poll() + if rc != 0: + sys.exit(rc) From 91986b70821fbeb50e04610ab1ff75ca9af09330 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Tue, 11 Aug 2020 21:34:17 -0700 Subject: [PATCH 34/48] [watermarkstat] Import print_function from __future__ (#1048) To support the `file=` argument in `print()` function in Python 2.7. --- scripts/watermarkstat | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/watermarkstat b/scripts/watermarkstat index f306cd62e9..ef908763f6 100644 --- a/scripts/watermarkstat +++ b/scripts/watermarkstat @@ -6,9 +6,12 @@ # ##################################################################### +from __future__ import print_function + import argparse import json import sys + import swsssdk from natsort import natsorted from tabulate import tabulate From 84b2c5bd019803de0b46c06e8537689136edde2f Mon Sep 17 00:00:00 2001 From: lguohan Date: Wed, 12 Aug 2020 11:28:04 -0700 Subject: [PATCH 35/48] [teamshow]: refactor teamshow to use state db information (#1049) previously, teamshow needs to read data from teamdctl command output. As teamdmon daemon has been developed to put data into state db. Now, teamshow can get data from state db. Also remove teamshow command and incoporate as part of show command. Also add extensive unit tests Signed-off-by: Guohan Lu --- setup.py | 2 +- .../{interfaces.py => interfaces/__init__.py} | 12 +-- .../interfaces/portchannel.py | 90 +++++++++---------- tests/interfaces_test.py | 45 ++++++++++ tests/intfutil_test.py | 8 +- tests/mock_tables/appl_db.json | 32 +++++++ tests/mock_tables/state_db.json | 88 ++++++++++++++++++ utilities_common/cli.py | 16 +--- utilities_common/db.py | 6 +- 9 files changed, 225 insertions(+), 74 deletions(-) rename show/{interfaces.py => interfaces/__init__.py} (97%) rename scripts/teamshow => show/interfaces/portchannel.py (69%) mode change 100755 => 100644 diff --git a/setup.py b/setup.py index af5cf6e251..88f3fbe848 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ 'pddf_thermalutil', 'pddf_ledutil', 'show', + 'show.interfaces', 'sonic_installer', 'sonic_installer.bootloader', 'tests', @@ -102,7 +103,6 @@ 'scripts/route_check_test.sh', 'scripts/sfpshow', 'scripts/syseeprom-to-json', - 'scripts/teamshow', 'scripts/tempershow', 'scripts/update_json.py', 'scripts/warm-reboot', diff --git a/show/interfaces.py b/show/interfaces/__init__.py similarity index 97% rename from show/interfaces.py rename to show/interfaces/__init__.py index 0154bcb45a..b2d1c17936 100644 --- a/show/interfaces.py +++ b/show/interfaces/__init__.py @@ -5,6 +5,7 @@ from tabulate import tabulate import utilities_common.cli as clicommon +import portchannel def try_convert_interfacename_from_alias(ctx, db, interfacename): """try to convert interface name from alias""" @@ -88,14 +89,6 @@ def naming_mode(verbose): click.echo(clicommon.get_interface_naming_mode()) -# 'portchannel' subcommand ("show interfaces portchannel") -@interfaces.command() -@click.option('--verbose', is_flag=True, help="Enable verbose output") -def portchannel(verbose): - """Show PortChannel information""" - cmd = "sudo teamshow" - clicommon.run_command(cmd, display_cmd=verbose) - @interfaces.command() @click.argument('interfacename', required=False) @click.option('--verbose', is_flag=True, help="Enable verbose output") @@ -261,6 +254,8 @@ def expected(db, interfacename): click.echo(tabulate(body, header)) +interfaces.add_command(portchannel.portchannel) + # # transceiver group (show interfaces trasceiver ...) # @@ -389,3 +384,4 @@ def rif(interface, period, verbose): cmd += " -i {}".format(interface) clicommon.run_command(cmd, display_cmd=verbose) + diff --git a/scripts/teamshow b/show/interfaces/portchannel.py old mode 100755 new mode 100644 similarity index 69% rename from scripts/teamshow rename to show/interfaces/portchannel.py index 54199895be..cedd16a99f --- a/scripts/teamshow +++ b/show/interfaces/portchannel.py @@ -1,4 +1,9 @@ -#!/usr/bin/python +import click + +from tabulate import tabulate +from natsort import natsorted + +import utilities_common.cli as clicommon """ Script to show LAG and LAG member status in a summary view @@ -19,31 +24,23 @@ """ -import json -import os -import swsssdk -import subprocess -import sys -from tabulate import tabulate -from natsort import natsorted PORT_CHANNEL_APPL_TABLE_PREFIX = "LAG_TABLE:" PORT_CHANNEL_CFG_TABLE_PREFIX = "PORTCHANNEL|" +PORT_CHANNEL_STATE_TABLE_PREFIX = "LAG_TABLE|" PORT_CHANNEL_STATUS_FIELD = "oper_status" PORT_CHANNEL_MEMBER_APPL_TABLE_PREFIX = "LAG_MEMBER_TABLE:" +PORT_CHANNEL_MEMBER_STATE_TABLE_PREFIX = "LAG_MEMBER_TABLE|" PORT_CHANNEL_MEMBER_STATUS_FIELD = "status" class Teamshow(object): - def __init__(self): + def __init__(self, db): self.teams = [] self.teamsraw = {} self.summary = {} - self.err = None - # setup db connection - self.db = swsssdk.SonicV2Connector(host="127.0.0.1") - self.db.connect(self.db.APPL_DB) - self.db.connect(self.db.CONFIG_DB) + self.db = db.db + self.db2 = db def get_portchannel_names(self): """ @@ -76,15 +73,15 @@ def get_teamdctl(self): Get teams raw data from teamdctl. Command: 'teamdctl state dump'. """ + + team_keys = self.db.keys(self.db.STATE_DB, PORT_CHANNEL_STATE_TABLE_PREFIX+"*") + if team_keys is None: + return + _teams = [key[len(PORT_CHANNEL_STATE_TABLE_PREFIX):] for key in team_keys] + for team in self.teams: - teamdctl_cmd = 'teamdctl ' + team + ' state dump' - p = subprocess.Popen(teamdctl_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - (output, err) = p.communicate() - rc = p.wait() - if rc == 0: - self.teamsraw[self.get_team_id(team)] = output - else: - self.err = err + if team in _teams: + self.teamsraw[self.get_team_id(team)] = self.db.get_all(self.db.STATE_DB, PORT_CHANNEL_STATE_TABLE_PREFIX+team) def get_teamshow_result(self): """ @@ -98,9 +95,10 @@ def get_teamshow_result(self): self.summary[team_id] = info self.summary[team_id]['ports'] = '' continue - json_info = json.loads(self.teamsraw[team_id]) - info['protocol'] = json_info['setup']['runner_name'].upper() - info['protocol'] += '(A)' if json_info['runner']['active'] else '(I)' + state = self.teamsraw[team_id] + info['protocol'] = "LACP" + info['protocol'] += "(A)" if state['runner.active'] == "true" else '(I)' + portchannel_status = self.get_portchannel_status(team) if portchannel_status is None: info['protocol'] += '(N/A)' @@ -112,14 +110,20 @@ def get_teamshow_result(self): info['protocol'] += '(N/A)' info['ports'] = "" - if 'ports' not in json_info: + member_keys = self.db.keys(self.db.STATE_DB, PORT_CHANNEL_MEMBER_STATE_TABLE_PREFIX+team+'|*') + if member_keys is None: info['ports'] = 'N/A' else: - for port in json_info['ports']: + ports = [key[len(PORT_CHANNEL_MEMBER_STATE_TABLE_PREFIX+team+'|'):] for key in member_keys] + for port in ports: status = self.get_portchannel_member_status(team, port) - selected = json_info["ports"][port]["runner"]["selected"] - - info["ports"] += port + "(" + pstate = self.db.get_all(self.db.STATE_DB, PORT_CHANNEL_MEMBER_STATE_TABLE_PREFIX+team+'|'+port) + selected = True if pstate['runner.aggregator.selected'] == "true" else False + if clicommon.get_interface_naming_mode() == "alias": + alias = clicommon.InterfaceAliasConverter(self.db2).name_to_alias(port) + info["ports"] += alias + "(" + else: + info["ports"] += port + "(" info["ports"] += "S" if selected else "D" if status is None or (status == "enabled" and not selected) or (status == "disabled" and selected): info["ports"] += "*" @@ -140,18 +144,14 @@ def display_summary(self): output.append([team_id, 'PortChannel'+team_id, self.summary[team_id]['protocol'], self.summary[team_id]['ports']]) print(tabulate(output, header)) -def main(): - if os.geteuid() != 0: - exit("This utility must be run as root") - - try: - team = Teamshow() - team.get_portchannel_names() - team.get_teamdctl() - team.get_teamshow_result() - team.display_summary() - except Exception as e: - sys.exit(e.message) - -if __name__ == "__main__": - main() +# 'portchannel' subcommand ("show interfaces portchannel") +@click.command() +@click.option('--verbose', is_flag=True, help="Enable verbose output") +@clicommon.pass_db +def portchannel(db, verbose): + """Show PortChannel information""" + team = Teamshow(db) + team.get_portchannel_names() + team.get_teamdctl() + team.get_teamshow_result() + team.display_summary() diff --git a/tests/interfaces_test.py b/tests/interfaces_test.py index 084355a1de..6e4ab18a9a 100644 --- a/tests/interfaces_test.py +++ b/tests/interfaces_test.py @@ -1,4 +1,5 @@ import os +import traceback from click.testing import CliRunner @@ -68,6 +69,30 @@ etp29 ARISTA01T1 Ethernet1 None 10.250.0.51 LeafRouter """ +show_interfaces_portchannel_output="""\ +Flags: A - active, I - inactive, Up - up, Dw - Down, N/A - not available, + S - selected, D - deselected, * - not synced + No. Team Dev Protocol Ports +----- --------------- ----------- -------------- + 0001 PortChannel0001 LACP(A)(Dw) Ethernet112(D) + 0002 PortChannel0002 LACP(A)(Up) Ethernet116(S) + 0003 PortChannel0003 LACP(A)(Up) Ethernet120(S) + 0004 PortChannel0004 LACP(A)(Up) N/A + 1001 PortChannel1001 N/A +""" + +show_interfaces_portchannel_in_alias_mode_output="""\ +Flags: A - active, I - inactive, Up - up, Dw - Down, N/A - not available, + S - selected, D - deselected, * - not synced + No. Team Dev Protocol Ports +----- --------------- ----------- -------- + 0001 PortChannel0001 LACP(A)(Dw) etp29(D) + 0002 PortChannel0002 LACP(A)(Up) etp30(S) + 0003 PortChannel0003 LACP(A)(Up) etp31(S) + 0004 PortChannel0004 LACP(A)(Up) N/A + 1001 PortChannel1001 N/A +""" + class TestInterfaces(object): @classmethod def setup_class(cls): @@ -170,6 +195,26 @@ def test_show_interfaces_neighbor_expected_Ethernet0(self): assert result.exit_code == 0 assert result.output.rstrip() == "No neighbor information available for interface Ethernet0" + def test_show_interfaces_portchannel(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["interfaces"].commands["portchannel"], []) + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert result.output == show_interfaces_portchannel_output + + def test_show_interfaces_portchannel_in_alias_mode(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["interfaces"].commands["portchannel"], []) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code) + print(result.output) + traceback.print_tb(result.exc_info[2]) + assert result.exit_code == 0 + assert result.output == show_interfaces_portchannel_in_alias_mode_output + @classmethod def teardown_class(cls): print("TEARDOWN") diff --git a/tests/intfutil_test.py b/tests/intfutil_test.py index 19c4189ed7..3841420f92 100644 --- a/tests/intfutil_test.py +++ b/tests/intfutil_test.py @@ -19,10 +19,10 @@ Ethernet116 89,90,91,92 40G 9100 rs etp30 PortChannel0002 up up N/A off Ethernet120 101,102,103,104 40G 9100 rs etp31 PortChannel0003 up up N/A off Ethernet124 97,98,99,100 40G 9100 rs etp32 PortChannel0004 up up N/A off -PortChannel0001 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A -PortChannel0002 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A -PortChannel0003 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A -PortChannel0004 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A +PortChannel0001 N/A 40G 9100 N/A N/A routed down up N/A N/A +PortChannel0002 N/A 40G 9100 N/A N/A routed up up N/A N/A +PortChannel0003 N/A 40G 9100 N/A N/A routed up up N/A N/A +PortChannel0004 N/A 40G 9100 N/A N/A routed up up N/A N/A PortChannel1001 N/A 40G 9100 N/A N/A routed N/A N/A N/A N/A """ diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index 44bd9dd3f6..f851712caa 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -109,5 +109,37 @@ "index": "200", "line_speed": "50000", "system_speed": "25000" + }, + "LAG_MEMBER_TABLE:PortChannel0001:Ethernet112": { + "status": "disabled" + }, + "LAG_MEMBER_TABLE:PortChannel0002:Ethernet116": { + "status": "enabled" + }, + "LAG_MEMBER_TABLE:PortChannel0003:Ethernet120": { + "status": "enabled" + }, + "LAG_MEMBER_TABLE:PortChannel0004:Ethernet124": { + "status": "enabled" + }, + "LAG_TABLE:PortChannel0001": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "down" + }, + "LAG_TABLE:PortChannel0002": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "up" + }, + "LAG_TABLE:PortChannel0003": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "up" + }, + "LAG_TABLE:PortChannel0004": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "up" } } diff --git a/tests/mock_tables/state_db.json b/tests/mock_tables/state_db.json index ccc9f83eb8..4a0c3ae509 100644 --- a/tests/mock_tables/state_db.json +++ b/tests/mock_tables/state_db.json @@ -76,5 +76,93 @@ "DEBUG_COUNTER_CAPABILITIES|SWITCH_EGRESS_DROPS": { "reasons": "[ACL_ANY,L2_ANY,L3_ANY]", "count": "2" + }, + "LAG_MEMBER_TABLE|PortChannel0001|Ethernet112": { + "runner.actor_lacpdu_info.state": "5", + "runner.state": "disabled", + "runner.partner_lacpdu_info.port": "0", + "runner.actor_lacpdu_info.port": "113", + "runner.selected": "false", + "runner.partner_lacpdu_info.state": "0", + "ifinfo.dev_addr": "52:54:00:f2:e1:23", + "runner.partner_lacpdu_info.system": "00:00:00:00:00:00", + "link_watches.list.link_watch_0.up": "false", + "runner.actor_lacpdu_info.system": "52:54:00:f2:e1:23", + "runner.aggregator.selected": "false", + "runner.aggregator.id": "0", + "link.up": "false", + "ifinfo.ifindex": "98" + }, + "LAG_MEMBER_TABLE|PortChannel0002|Ethernet116": { + "runner.actor_lacpdu_info.state": "61", + "runner.state": "current", + "runner.partner_lacpdu_info.port": "1", + "runner.actor_lacpdu_info.port": "117", + "runner.selected": "true", + "runner.partner_lacpdu_info.state": "61", + "ifinfo.dev_addr": "52:54:00:f2:e1:23", + "runner.partner_lacpdu_info.system": "1e:af:77:fc:79:ee", + "link_watches.list.link_watch_0.up": "false", + "runner.actor_lacpdu_info.system": "52:54:00:f2:e1:23", + "runner.aggregator.selected": "true", + "runner.aggregator.id": "97", + "link.up": "true", + "ifinfo.ifindex": "97" + }, + "LAG_MEMBER_TABLE|PortChannel0003|Ethernet120": { + "runner.actor_lacpdu_info.state": "61", + "runner.state": "current", + "runner.partner_lacpdu_info.port": "1", + "runner.actor_lacpdu_info.port": "121", + "runner.selected": "true", + "runner.partner_lacpdu_info.state": "61", + "ifinfo.dev_addr": "52:54:00:f2:e1:23", + "runner.partner_lacpdu_info.system": "16:0e:58:6f:3c:dd", + "link_watches.list.link_watch_0.up": "false", + "runner.actor_lacpdu_info.system": "52:54:00:f2:e1:23", + "runner.aggregator.selected": "true", + "runner.aggregator.id": "100", + "link.up": "true", + "ifinfo.ifindex": "100" + }, + "LAG_TABLE|PortChannel0001": { + "runner.fallback": "false", + "team_device.ifinfo.dev_addr": "52:54:00:f2:e1:23", + "team_device.ifinfo.ifindex": "71", + "setup.pid": "32", + "state": "ok", + "runner.fast_rate": "false", + "setup.kernel_team_mode_name": "loadbalance", + "runner.active": "true" + }, + "LAG_TABLE|PortChannel0002": { + "runner.fallback": "false", + "team_device.ifinfo.dev_addr": "52:54:00:f2:e1:23", + "team_device.ifinfo.ifindex": "72", + "setup.pid": "40", + "state": "ok", + "runner.fast_rate": "false", + "setup.kernel_team_mode_name": "loadbalance", + "runner.active": "true" + }, + "LAG_TABLE|PortChannel0003": { + "runner.fallback": "false", + "team_device.ifinfo.dev_addr": "52:54:00:f2:e1:23", + "team_device.ifinfo.ifindex": "73", + "setup.pid": "48", + "state": "ok", + "runner.fast_rate": "false", + "setup.kernel_team_mode_name": "loadbalance", + "runner.active": "true" + }, + "LAG_TABLE|PortChannel0004": { + "runner.fallback": "false", + "team_device.ifinfo.dev_addr": "52:54:00:f2:e1:23", + "team_device.ifinfo.ifindex": "74", + "setup.pid": "56", + "state": "ok", + "runner.fast_rate": "false", + "setup.kernel_team_mode_name": "loadbalance", + "runner.active": "true" } } diff --git a/utilities_common/cli.py b/utilities_common/cli.py index d014b7c578..912cfeb596 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -438,20 +438,6 @@ def run_command_in_alias_mode(command): output = output.replace('Vlan', ' Vlan') print_output_in_alias_mode(output, index) - elif command.startswith("sudo teamshow"): - """ - sudo teamshow - Search for port names either at the start of a line or preceded immediately by - whitespace and followed immediately by either the end of a line or whitespace - OR followed immediately by '(D)', '(S)', '(D*)' or '(S*)' - """ - converted_output = raw_output - for port_name in iface_alias_converter.port_dict.keys(): - converted_output = re.sub(r"(^|\s){}(\([DS]\*{{0,1}}\)(?:$|\s))".format(port_name), - r"\1{}\2".format(iface_alias_converter.name_to_alias(port_name)), - converted_output) - click.echo(converted_output.rstrip('\n')) - else: """ Default command conversion @@ -478,7 +464,7 @@ def run_command(command, display_cmd=False, ignore_error=False, return_cmd=False if display_cmd == True: click.echo(click.style("Running command: ", fg='cyan') + click.style(command, fg='green')) - if os.environ["UTILITIES_UNIT_TESTING"] == "1": + if os.getenv("UTILITIES_UNIT_TESTING") == "1": return # No conversion needed for intfutil commands as it already displays diff --git a/utilities_common/db.py b/utilities_common/db.py index 462c5d690e..9b75ed9e7b 100644 --- a/utilities_common/db.py +++ b/utilities_common/db.py @@ -1,6 +1,10 @@ -from swsssdk import ConfigDBConnector +from swsssdk import ConfigDBConnector, SonicV2Connector class Db(object): def __init__(self): self.cfgdb = ConfigDBConnector() self.cfgdb.connect() + self.db = SonicV2Connector(host="127.0.0.1") + self.db.connect(self.db.APPL_DB) + self.db.connect(self.db.CONFIG_DB) + self.db.connect(self.db.STATE_DB) From 2d9d00d897d113603635bba64c30675bbd1976a6 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Wed, 12 Aug 2020 11:50:08 -0700 Subject: [PATCH 36/48] [config] Eliminate port breakout-related globals (#1045) Port breakout-related data should be gathered on-demand, not every time `config` is executed. If the ConfigDB is not ready, the call to get hwsku will hang indefinitely, which will occur when loading config for the first time. Also, if it fails to retrieve the port breakout globals, it will abort, even if the executed command was unrelated to port breakout. This is not desirable behavior. This PR eliminates port breakout-related global variables, and encapsulates them in functions to be called on-demand, only when commands which require the data are executed. It is currently only accessed in two places. If we feel the need to cache it in the future for efficiency, we can look into adding it to the Click context. Also rename `_get_option()` to `_get_breakout_cfg_file_name()` to add more detail. --- config/main.py | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/config/main.py b/config/main.py index 6822077099..8b9443b20b 100755 --- a/config/main.py +++ b/config/main.py @@ -55,22 +55,6 @@ asic_type = None -# -# Load breakout config file for Dynamic Port Breakout -# - -try: - (platform, hwsku) = device_info.get_platform_and_hwsku() -except Exception as e: - click.secho("Failed to get platform and hwsku with error:{}".format(str(e)), fg='red') - raise click.Abort() - -try: - breakout_cfg_file = get_port_config_file_name(hwsku, platform) -except Exception as e: - click.secho("Breakout config file not found with error:{}".format(str(e)), fg='red') - raise click.Abort() - # # Breakout Mode Helper functions # @@ -84,11 +68,32 @@ def readJsonFile(fileName): raise Exception(str(e)) return result -def _get_option(ctx,args,incomplete): +def _get_breakout_cfg_file_name(): + """ + Get name of config file for Dynamic Port Breakout + """ + try: + (platform, hwsku) = device_info.get_platform_and_hwsku() + except Exception as e: + click.secho("Failed to get platform and hwsku with error:{}".format(str(e)), fg='red') + raise click.Abort() + + try: + breakout_cfg_file_name = get_port_config_file_name(hwsku, platform) + except Exception as e: + click.secho("Breakout config file not found with error:{}".format(str(e)), fg='red') + raise click.Abort() + + return breakout_cfg_file_name + + +def _get_breakout_options(ctx, args, incomplete): """ Provides dynamic mode option as per user argument i.e. interface name """ all_mode_options = [] interface_name = args[-1] + breakout_cfg_file = _get_breakout_cfg_file_name() + if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'): return [] else: @@ -2140,14 +2145,16 @@ def speed(ctx, interface_name, interface_speed, verbose): @interface.command() @click.argument('interface_name', metavar='', required=True) -@click.argument('mode', required=True, type=click.STRING, autocompletion=_get_option) -@click.option('-f', '--force-remove-dependencies', is_flag=True, help='Clear all depenedecies internally first.') +@click.argument('mode', required=True, type=click.STRING, autocompletion=_get_breakout_options) +@click.option('-f', '--force-remove-dependencies', is_flag=True, help='Clear all dependencies internally first.') @click.option('-l', '--load-predefined-config', is_flag=True, help='load predefied user configuration (alias, lanes, speed etc) first.') @click.option('-y', '--yes', is_flag=True, callback=_abort_if_false, expose_value=False, prompt='Do you want to Breakout the port, continue?') @click.option('-v', '--verbose', is_flag=True, help="Enable verbose output") @click.pass_context def breakout(ctx, interface_name, mode, verbose, force_remove_dependencies, load_predefined_config): """ Set interface breakout mode """ + breakout_cfg_file = _get_breakout_cfg_file_name() + if not os.path.isfile(breakout_cfg_file) or not breakout_cfg_file.endswith('.json'): click.secho("[ERROR] Breakout feature is not available without platform.json file", fg='red') raise click.Abort() From e741c7ca9e898ac65bb3ecaa7de104ab94474f08 Mon Sep 17 00:00:00 2001 From: Volodymyr Samotiy Date: Thu, 13 Aug 2020 02:25:54 +0300 Subject: [PATCH 37/48] [PFCWD] Fix issue with "pfcwd show stats" command during SONiC init (#1018) - What I did Fixed issue with pfcwd show stats command during SONiC init. Traceback was returned if DB was not yet initialized which is incorrect. Correct behavior would to rather return empty line if something is not yet ready to be pulled and displayed. - How I did it Returned empty dictionary instead of None if data are still not present in DB. It is because in source code data expected to be as dictionary type but if there is no expected attributes in DB None was returned. Wich is incorrect and caused following exception: 'NoneType' object has no attribute 'keys' Signed-off-by: Volodymyr Samotiy --- pfcwd/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pfcwd/main.py b/pfcwd/main.py index 1856ff0f45..6f01b63cbf 100644 --- a/pfcwd/main.py +++ b/pfcwd/main.py @@ -39,7 +39,7 @@ def cli(): def get_all_queues(db): queue_names = db.get_all(db.COUNTERS_DB, 'COUNTERS_QUEUE_NAME_MAP') - return natsorted(queue_names.keys()) + return natsorted(queue_names.keys() if queue_names else {}) def get_all_ports(db): all_port_names = db.get_all(db.COUNTERS_DB, 'COUNTERS_PORT_NAME_MAP') From 027553c7a63d0c80895d8e1db20de2b23986537a Mon Sep 17 00:00:00 2001 From: lguohan Date: Wed, 12 Aug 2020 19:37:29 -0700 Subject: [PATCH 38/48] [show/vlan]: fix show vlan config (#1050) use VLAN_MEMBER table to get vlan configuration Signed-off-by: Guohan Lu --- show/vlan.py | 15 +++++++-------- tests/vlan_test.py | 30 ++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/show/vlan.py b/show/vlan.py index d40f93cd29..fa0eb524fd 100644 --- a/show/vlan.py +++ b/show/vlan.py @@ -96,24 +96,23 @@ def brief(db, verbose): def config(db): data = db.cfgdb.get_table('VLAN') keys = data.keys() + member_data = db.cfgdb.get_table('VLAN_MEMBER') def tablelize(keys, data): table = [] for k in natsorted(keys): - if 'members' not in data[k] : - r = [] - r.append(k) - r.append(data[k]['vlanid']) - table.append(r) - continue + members = set(data[k].get('members', [])) + for (vlan, interface_name) in member_data: + if vlan == k: + members.add(interface_name) - for m in data[k].get('members', []): + for m in members: r = [] r.append(k) r.append(data[k]['vlanid']) if clicommon.get_interface_naming_mode() == "alias": - alias = iface_alias_converter.name_to_alias(m) + alias = clicommon.InterfaceAliasConverter(db).name_to_alias(m) r.append(alias) else: r.append(m) diff --git a/tests/vlan_test.py b/tests/vlan_test.py index 04aa16fa74..82f03492f7 100644 --- a/tests/vlan_test.py +++ b/tests/vlan_test.py @@ -49,9 +49,21 @@ """ show_vlan_config_output="""\ -Name VID --------- ----- -Vlan1000 1000 +Name VID Member Mode +-------- ----- ---------- -------- +Vlan1000 1000 Ethernet8 untagged +Vlan1000 1000 Ethernet12 untagged +Vlan1000 1000 Ethernet4 untagged +Vlan1000 1000 Ethernet16 untagged +""" + +show_vlan_config_in_alias_mode_output="""\ +Name VID Member Mode +-------- ----- -------- -------- +Vlan1000 1000 etp3 untagged +Vlan1000 1000 etp4 untagged +Vlan1000 1000 etp2 untagged +Vlan1000 1000 etp5 untagged """ config_vlan_add_dhcp_relay_output="""\ @@ -134,11 +146,11 @@ def test_show_vlan_brief_in_alias_mode(self): runner = CliRunner() os.environ['SONIC_CLI_IFACE_MODE'] = "alias" result = runner.invoke(show.cli.commands["vlan"].commands["brief"]) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" print(result.exit_code) print(result.output) assert result.exit_code == 0 assert result.output == show_vlan_brief_in_alias_mode_output - os.environ['SONIC_CLI_IFACE_MODE'] = "" def test_show_vlan_config(self): runner = CliRunner() @@ -148,6 +160,16 @@ def test_show_vlan_config(self): assert result.exit_code == 0 assert result.output == show_vlan_config_output + def test_show_vlan_config_in_alias_mode(self): + runner = CliRunner() + os.environ['SONIC_CLI_IFACE_MODE'] = "alias" + result = runner.invoke(show.cli.commands["vlan"].commands["config"], []) + os.environ['SONIC_CLI_IFACE_MODE'] = "default" + print(result.exit_code) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_vlan_config_in_alias_mode_output + def test_config_vlan_add_vlan_with_invalid_vlanid(self): runner = CliRunner() result = runner.invoke(config.config.commands["vlan"].commands["add"], ["4096"]) From d5fdd74d3d548b608aae849aecf4094ef27adaf0 Mon Sep 17 00:00:00 2001 From: Stephen Sun <5379172+stephenxs@users.noreply.github.com> Date: Thu, 13 Aug 2020 18:11:01 +0800 Subject: [PATCH 39/48] [db_migrator] Support migrating database regarding buffer configuration for all Mellanox switches (#993) * [db_migrator] Support migrate to single ingress buffer pool mode db_migrator supports migrating old configuration who has 2 ingress pools into the new configuration who has 1 ingress pool, including BUFFER_POOL, BUFFER_PROFILE and BUFFER_PORT_INGRESS_PROFILE_LIST Signed-off-by: Stephen Sun * Update according to MSFT comments 1. Don't need to migrate for single buffer pool mode 2. Move buffer setting migration to another file 3. Remove unnecessary code/upgrading flows Signed-off-by: Stephen Sun * Fix LGTM warning Signed-off-by: Stephen Sun * Fix an error Signed-off-by: Stephen Sun * mellanox_db_migrator => mellanox_buffer_migrator * Fix issue: after migration the lossless profiles are lost This issue can fail warm reboot because after warm reboot the buffermgr doesn't have time to generate lossless profiles and the following orchagent bake can fail due to this Signed-off-by: Stephen Sun * Update buffer configuration for version 1.0.4 Signed-off-by: Stephen Sun * Update the buffer setting for version 1.0.4 Signed-off-by: Stephen Sun * [mellanox_buffer_migrator] log identifier updated from 'db_migrator' to 'mellanox_buffer_identifier' Signed-off-by: Stephen Sun * [db_migrator] Adjust db_migrator according to the latest master change Signed-off-by: Stephen Sun Co-authored-by: Stephen Sun --- scripts/db_migrator.py | 129 +++------- scripts/mellanox_buffer_migrator.py | 365 ++++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 394 insertions(+), 101 deletions(-) create mode 100644 scripts/mellanox_buffer_migrator.py diff --git a/scripts/db_migrator.py b/scripts/db_migrator.py index 82c9982516..7ac65bb041 100755 --- a/scripts/db_migrator.py +++ b/scripts/db_migrator.py @@ -28,7 +28,7 @@ def __init__(self, namespace, socket=None): none-zero values. build: sequentially increase within a minor version domain. """ - self.CURRENT_VERSION = 'version_1_0_3' + self.CURRENT_VERSION = 'version_1_0_4' self.TABLE_NAME = 'VERSIONS' self.TABLE_KEY = 'DATABASE' @@ -48,6 +48,14 @@ def __init__(self, namespace, socket=None): if self.appDB is not None: self.appDB.connect(self.appDB.APPL_DB) + version_info = device_info.get_sonic_version_info() + asic_type = version_info.get('asic_type') + self.asic_type = asic_type + + if asic_type == "mellanox": + from mellanox_buffer_migrator import MellanoxBufferMigrator + self.mellanox_buffer_migrator = MellanoxBufferMigrator(self.configDB) + def migrate_pfc_wd_table(self): ''' Migrate all data entries from table PFC_WD_TABLE to PFC_WD @@ -134,101 +142,6 @@ def migrate_intf_table(self): self.appDB.set(self.appDB.APPL_DB, table, 'NULL', 'NULL') if_db.append(if_name) - def mlnx_migrate_buffer_pool_size(self): - """ - On Mellanox platform the buffer pool size changed since - version with new SDK 4.3.3052, SONiC to SONiC update - from version with old SDK will be broken without migration. - This migration is specifically for Mellanox platform. - """ - # Buffer pools defined in version 1_0_2 - buffer_pools = ['ingress_lossless_pool', 'egress_lossless_pool', 'ingress_lossy_pool', 'egress_lossy_pool'] - - # Old default buffer pool values on Mellanox platform - spc1_t0_default_value = [{'ingress_lossless_pool': '4194304'}, {'egress_lossless_pool': '16777152'}, {'ingress_lossy_pool': '7340032'}, {'egress_lossy_pool': '7340032'}] - spc1_t1_default_value = [{'ingress_lossless_pool': '2097152'}, {'egress_lossless_pool': '16777152'}, {'ingress_lossy_pool': '5242880'}, {'egress_lossy_pool': '5242880'}] - spc2_t0_default_value = [{'ingress_lossless_pool': '8224768'}, {'egress_lossless_pool': '35966016'}, {'ingress_lossy_pool': '8224768'}, {'egress_lossy_pool': '8224768'}] - spc2_t1_default_value = [{'ingress_lossless_pool': '12042240'}, {'egress_lossless_pool': '35966016'}, {'ingress_lossy_pool': '12042240'}, {'egress_lossy_pool': '12042240'}] - - # New default buffer pool configuration on Mellanox platform - spc1_t0_default_config = {"ingress_lossless_pool": { "size": "5029836", "type": "ingress", "mode": "dynamic" }, - "ingress_lossy_pool": { "size": "5029836", "type": "ingress", "mode": "dynamic" }, - "egress_lossless_pool": { "size": "14024599", "type": "egress", "mode": "dynamic" }, - "egress_lossy_pool": {"size": "5029836", "type": "egress", "mode": "dynamic" } } - spc1_t1_default_config = {"ingress_lossless_pool": { "size": "2097100", "type": "ingress", "mode": "dynamic" }, - "ingress_lossy_pool": { "size": "2097100", "type": "ingress", "mode": "dynamic" }, - "egress_lossless_pool": { "size": "14024599", "type": "egress", "mode": "dynamic" }, - "egress_lossy_pool": {"size": "2097100", "type": "egress", "mode": "dynamic" } } - spc2_t0_default_config = {"ingress_lossless_pool": { "size": "14983147", "type": "ingress", "mode": "dynamic" }, - "ingress_lossy_pool": { "size": "14983147", "type": "ingress", "mode": "dynamic" }, - "egress_lossless_pool": { "size": "34340822", "type": "egress", "mode": "dynamic" }, - "egress_lossy_pool": {"size": "14983147", "type": "egress", "mode": "dynamic" } } - spc2_t1_default_config = {"ingress_lossless_pool": { "size": "9158635", "type": "ingress", "mode": "dynamic" }, - "ingress_lossy_pool": { "size": "9158635", "type": "ingress", "mode": "dynamic" }, - "egress_lossless_pool": { "size": "34340822", "type": "egress", "mode": "dynamic" }, - "egress_lossy_pool": {"size": "9158635", "type": "egress", "mode": "dynamic" } } - # 3800 platform has gearbox installed so the buffer pool size is different with other Spectrum2 platform - spc2_3800_t0_default_config = {"ingress_lossless_pool": { "size": "28196784", "type": "ingress", "mode": "dynamic" }, - "ingress_lossy_pool": { "size": "28196784", "type": "ingress", "mode": "dynamic" }, - "egress_lossless_pool": { "size": "34340832", "type": "egress", "mode": "dynamic" }, - "egress_lossy_pool": {"size": "28196784", "type": "egress", "mode": "dynamic" } } - spc2_3800_t1_default_config = {"ingress_lossless_pool": { "size": "17891280", "type": "ingress", "mode": "dynamic" }, - "ingress_lossy_pool": { "size": "17891280", "type": "ingress", "mode": "dynamic" }, - "egress_lossless_pool": { "size": "34340832", "type": "egress", "mode": "dynamic" }, - "egress_lossy_pool": {"size": "17891280", "type": "egress", "mode": "dynamic" } } - - # Try to get related info from DB - buffer_pool_conf = {} - device_data = self.configDB.get_table('DEVICE_METADATA') - if 'localhost' in device_data.keys(): - hwsku = device_data['localhost']['hwsku'] - platform = device_data['localhost']['platform'] - else: - log.log_error("Trying to get DEVICE_METADATA from DB but doesn't exist, skip migration") - return False - buffer_pool_conf = self.configDB.get_table('BUFFER_POOL') - - # Get current buffer pool configuration, only migrate configuration which - # with default values, if it's not default, leave it as is. - pool_size_in_db_list = [] - pools_in_db = buffer_pool_conf.keys() - - # Buffer pool numbers is different with default, don't need migrate - if len(pools_in_db) != len(buffer_pools): - return True - - # If some buffer pool is not default ones, don't need migrate - for buffer_pool in buffer_pools: - if buffer_pool not in pools_in_db: - return True - pool_size_in_db_list.append({buffer_pool: buffer_pool_conf[buffer_pool]['size']}) - - # To check if the buffer pool size is equal to old default values - new_buffer_pool_conf = None - if pool_size_in_db_list == spc1_t0_default_value: - new_buffer_pool_conf = spc1_t0_default_config - elif pool_size_in_db_list == spc1_t1_default_value: - new_buffer_pool_conf = spc1_t1_default_config - elif pool_size_in_db_list == spc2_t0_default_value: - if platform == 'x86_64-mlnx_msn3800-r0': - new_buffer_pool_conf = spc2_3800_t0_default_config - else: - new_buffer_pool_conf = spc2_t0_default_config - elif pool_size_in_db_list == spc2_t1_default_value: - if platform == 'x86_64-mlnx_msn3800-r0': - new_buffer_pool_conf = spc2_3800_t1_default_config - else: - new_buffer_pool_conf = spc2_t1_default_config - else: - # It's not using default buffer pool configuration, no migration needed. - log.log_info("buffer pool size is not old default value, no need to migrate") - return True - # Migrate old buffer conf to latest. - for pool in buffer_pools: - self.configDB.set_entry('BUFFER_POOL', pool, new_buffer_pool_conf[pool]) - log.log_info("Successfully migrate mlnx buffer pool size to the latest.") - return True - def version_unknown(self): """ version_unknown tracks all SONiC versions that doesn't have a version @@ -269,20 +182,34 @@ def version_1_0_2(self): """ log.log_info('Handling version_1_0_2') # Check ASIC type, if Mellanox platform then need DB migration - version_info = device_info.get_sonic_version_info() - if version_info['asic_type'] == "mellanox": - if self.mlnx_migrate_buffer_pool_size(): + if self.asic_type == "mellanox": + if self.mellanox_buffer_migrator.mlnx_migrate_buffer_pool_size('version_1_0_2', 'version_1_0_3'): self.set_version('version_1_0_3') else: self.set_version('version_1_0_3') - return None + return 'version_1_0_3' def version_1_0_3(self): """ - Current latest version. Nothing to do here. + Version 1_0_3. """ log.log_info('Handling version_1_0_3') + # Check ASIC type, if Mellanox platform then need DB migration + if self.asic_type == "mellanox": + if self.mellanox_buffer_migrator.mlnx_migrate_buffer_pool_size('version_1_0_3', 'version_1_0_4') and self.mellanox_buffer_migrator.mlnx_migrate_buffer_profile('version_1_0_3', 'version_1_0_4'): + self.set_version('version_1_0_4') + else: + self.set_version('version_1_0_4') + + return 'version_1_0_4' + + def version_1_0_4(self): + """ + Current latest version. Nothing to do here. + """ + log.log_info('Handling version_1_0_4') + return None def get_version(self): diff --git a/scripts/mellanox_buffer_migrator.py b/scripts/mellanox_buffer_migrator.py new file mode 100644 index 0000000000..bc8cc6382b --- /dev/null +++ b/scripts/mellanox_buffer_migrator.py @@ -0,0 +1,365 @@ +from sonic_py_common import logger + +SYSLOG_IDENTIFIER = 'mellanox_buffer_migrator' + +# Global logger instance +log = logger.Logger(SYSLOG_IDENTIFIER) + +class MellanoxBufferMigrator(): + def __init__(self, configDB): + self.configDB = configDB + + mellanox_default_parameter = { + "version_1_0_2": { + # Buffer pool migration control info + "pool_configuration_list": ["spc1_t0_pool", "spc1_t1_pool", "spc2_t0_pool", "spc2_t1_pool"], + + # Buffer pool configuration info + "buffer_pool_list" : ['ingress_lossless_pool', 'egress_lossless_pool', 'ingress_lossy_pool', 'egress_lossy_pool'], + "spc1_t0_pool": {"ingress_lossless_pool": { "size": "4194304", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "7340032", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "16777152", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "7340032", "type": "egress", "mode": "dynamic" } }, + "spc1_t1_pool": {"ingress_lossless_pool": { "size": "2097152", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "5242880", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "16777152", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "5242880", "type": "egress", "mode": "dynamic" } }, + "spc2_t0_pool": {"ingress_lossless_pool": { "size": "8224768", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "8224768", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "35966016", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "8224768", "type": "egress", "mode": "dynamic" } }, + "spc2_t1_pool": {"ingress_lossless_pool": { "size": "12042240", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "12042240", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "35966016", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "12042240", "type": "egress", "mode": "dynamic" } }, + }, + "version_1_0_3": { + # On Mellanox platform the buffer pool size changed since + # version with new SDK 4.3.3052, SONiC to SONiC update + # from version with old SDK will be broken without migration. + # + "pool_configuration_list": ["spc1_t0_pool", "spc1_t1_pool", "spc2_t0_pool", "spc2_t1_pool", "spc2_3800_t0_pool", "spc2_3800_t1_pool"], + + # Buffer pool configuration info + "buffer_pool_list" : ['ingress_lossless_pool', 'egress_lossless_pool', 'ingress_lossy_pool', 'egress_lossy_pool'], + "spc1_t0_pool": {"ingress_lossless_pool": { "size": "5029836", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "5029836", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "14024599", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "5029836", "type": "egress", "mode": "dynamic" } }, + "spc1_t1_pool": {"ingress_lossless_pool": { "size": "2097100", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "2097100", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "14024599", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "2097100", "type": "egress", "mode": "dynamic" } }, + + "spc2_t0_pool": {"ingress_lossless_pool": { "size": "14983147", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "14983147", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34340822", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "14983147", "type": "egress", "mode": "dynamic" } }, + "spc2_t1_pool": {"ingress_lossless_pool": { "size": "9158635", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "9158635", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34340822", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "9158635", "type": "egress", "mode": "dynamic" } }, + + # 3800 platform has gearbox installed so the buffer pool size is different with other Spectrum2 platform + "spc2_3800_t0_pool": {"ingress_lossless_pool": { "size": "28196784", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "28196784", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34340832", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "28196784", "type": "egress", "mode": "dynamic" } }, + "spc2_3800_t1_pool": {"ingress_lossless_pool": { "size": "17891280", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "17891280", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34340832", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "17891280", "type": "egress", "mode": "dynamic" } }, + + # Lossless headroom info + "spc1_headroom": {"pg_lossless_10000_5m_profile": {"size": "34816", "xon": "18432"}, + "pg_lossless_25000_5m_profile": {"size": "34816", "xon": "18432"}, + "pg_lossless_40000_5m_profile": {"size": "34816", "xon": "18432"}, + "pg_lossless_50000_5m_profile": {"size": "34816", "xon": "18432"}, + "pg_lossless_100000_5m_profile": {"size": "36864", "xon": "18432"}, + "pg_lossless_10000_40m_profile": {"size": "36864", "xon": "18432"}, + "pg_lossless_25000_40m_profile": {"size": "39936", "xon": "18432"}, + "pg_lossless_40000_40m_profile": {"size": "41984", "xon": "18432"}, + "pg_lossless_50000_40m_profile": {"size": "41984", "xon": "18432"}, + "pg_lossless_100000_40m_profile": {"size": "54272", "xon": "18432"}, + "pg_lossless_10000_300m_profile": {"size": "49152", "xon": "18432"}, + "pg_lossless_25000_300m_profile": {"size": "71680", "xon": "18432"}, + "pg_lossless_40000_300m_profile": {"size": "94208", "xon": "18432"}, + "pg_lossless_50000_300m_profile": {"size": "94208", "xon": "18432"}, + "pg_lossless_100000_300m_profile": {"size": "184320", "xon": "18432"}}, + "spc2_headroom": {"pg_lossless_1000_5m_profile": {"size": "35840", "xon": "18432"}, + "pg_lossless_10000_5m_profile": {"size": "36864", "xon": "18432"}, + "pg_lossless_25000_5m_profile": {"size": "36864", "xon": "18432"}, + "pg_lossless_40000_5m_profile": {"size": "36864", "xon": "18432"}, + "pg_lossless_50000_5m_profile": {"size": "37888", "xon": "18432"}, + "pg_lossless_100000_5m_profile": {"size": "38912", "xon": "18432"}, + "pg_lossless_200000_5m_profile": {"size": "41984", "xon": "18432"}, + "pg_lossless_1000_40m_profile": {"size": "36864", "xon": "18432"}, + "pg_lossless_10000_40m_profile": {"size": "38912", "xon": "18432"}, + "pg_lossless_25000_40m_profile": {"size": "41984", "xon": "18432"}, + "pg_lossless_40000_40m_profile": {"size": "45056", "xon": "18432"}, + "pg_lossless_50000_40m_profile": {"size": "47104", "xon": "18432"}, + "pg_lossless_100000_40m_profile": {"size": "59392", "xon": "18432"}, + "pg_lossless_200000_40m_profile": {"size": "81920", "xon": "18432"}, + "pg_lossless_1000_300m_profile": {"size": "37888", "xon": "18432"}, + "pg_lossless_10000_300m_profile": {"size": "53248", "xon": "18432"}, + "pg_lossless_25000_300m_profile": {"size": "78848", "xon": "18432"}, + "pg_lossless_40000_300m_profile": {"size": "104448", "xon": "18432"}, + "pg_lossless_50000_300m_profile": {"size": "121856", "xon": "18432"}, + "pg_lossless_100000_300m_profile": {"size": "206848", "xon": "18432"}, + "pg_lossless_200000_300m_profile": {"size": "376832", "xon": "18432"}}, + "spc2_3800_headroom": {"pg_lossless_1000_5m_profile": {"size": "32768", "xon": "18432"}, + "pg_lossless_10000_5m_profile": {"size": "34816", "xon": "18432"}, + "pg_lossless_25000_5m_profile": {"size": "38912", "xon": "18432"}, + "pg_lossless_40000_5m_profile": {"size": "41984", "xon": "18432"}, + "pg_lossless_50000_5m_profile": {"size": "44032", "xon": "18432"}, + "pg_lossless_100000_5m_profile": {"size": "55296", "xon": "18432"}, + "pg_lossless_200000_5m_profile": {"size": "77824", "xon": "18432"}, + "pg_lossless_1000_40m_profile": {"size": "33792", "xon": "18432"}, + "pg_lossless_10000_40m_profile": {"size": "36864", "xon": "18432"}, + "pg_lossless_25000_40m_profile": {"size": "43008", "xon": "18432"}, + "pg_lossless_40000_40m_profile": {"size": "49152", "xon": "18432"}, + "pg_lossless_50000_40m_profile": {"size": "53248", "xon": "18432"}, + "pg_lossless_100000_40m_profile": {"size": "72704", "xon": "18432"}, + "pg_lossless_200000_40m_profile": {"size": "112640", "xon": "18432"}, + "pg_lossless_1000_300m_profile": {"size": "34816", "xon": "18432"}, + "pg_lossless_10000_300m_profile": {"size": "50176", "xon": "18432"}, + "pg_lossless_25000_300m_profile": {"size": "75776", "xon": "18432"}, + "pg_lossless_40000_300m_profile": {"size": "101376", "xon": "18432"}, + "pg_lossless_50000_300m_profile": {"size": "117760", "xon": "18432"}, + "pg_lossless_100000_300m_profile": {"size": "202752", "xon": "18432"}, + "pg_lossless_200000_300m_profile": {"size": "373760", "xon": "18432"}}, + + # Buffer profile info + "buffer_profiles": {"ingress_lossless_profile": {"dynamic_th": "0", "pool": "[BUFFER_POOL|ingress_lossless_pool]", "size": "0"}, + "ingress_lossy_profile": {"dynamic_th": "3", "pool": "[BUFFER_POOL|ingress_lossy_pool]", "size": "0"}, + "egress_lossless_profile": {"dynamic_th": "7", "pool": "[BUFFER_POOL|egress_lossless_pool]", "size": "0"}, + "egress_lossy_profile": {"dynamic_th": "3", "pool": "[BUFFER_POOL|egress_lossy_pool]", "size": "4096"}, + "q_lossy_profile": {"dynamic_th": "3", "pool": "[BUFFER_POOL|egress_lossy_pool]", "size": "0"}} + }, + "version_1_0_4": { + # version 1.0.4 is introduced for updating the buffer settings + "pool_configuration_list": ["spc1_t0_pool", "spc1_t1_pool", "spc2_t0_pool", "spc2_t1_pool", "spc2_3800_t0_pool", "spc2_3800_t1_pool"], + + # Buffer pool info for normal mode + "buffer_pool_list" : ['ingress_lossless_pool', 'ingress_lossy_pool', 'egress_lossless_pool', 'egress_lossy_pool'], + "spc1_t0_pool": {"ingress_lossless_pool": { "size": "4580864", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "4580864", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "13945824", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "4580864", "type": "egress", "mode": "dynamic" } }, + "spc1_t1_pool": {"ingress_lossless_pool": { "size": "3302912", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "3302912", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "13945824", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "3302912", "type": "egress", "mode": "dynamic" } }, + + "spc2_t0_pool": {"ingress_lossless_pool": { "size": "14542848", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "14542848", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34287552", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "14542848", "type": "egress", "mode": "dynamic" } }, + "spc2_t1_pool": {"ingress_lossless_pool": { "size": "11622400", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "11622400", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34287552", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "11622400", "type": "egress", "mode": "dynamic" } }, + + "spc2_3800_t0_pool": {"ingress_lossless_pool": { "size": "13924352", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "13924352", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34287552", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "13924352", "type": "egress", "mode": "dynamic" } }, + "spc2_3800_t1_pool": {"ingress_lossless_pool": { "size": "12457984", "type": "ingress", "mode": "dynamic" }, + "ingress_lossy_pool": { "size": "12457984", "type": "ingress", "mode": "dynamic" }, + "egress_lossless_pool": { "size": "34287552", "type": "egress", "mode": "dynamic" }, + "egress_lossy_pool": {"size": "12457984", "type": "egress", "mode": "dynamic" } }, + + # Lossless headroom info + "spc1_headroom": {"pg_lossless_10000_5m_profile": {"size": "49152", "xon":"19456"}, + "pg_lossless_25000_5m_profile": {"size": "49152", "xon":"19456"}, + "pg_lossless_40000_5m_profile": {"size": "49152", "xon":"19456"}, + "pg_lossless_50000_5m_profile": {"size": "49152", "xon":"19456"}, + "pg_lossless_100000_5m_profile": {"size": "50176", "xon":"19456"}, + "pg_lossless_10000_40m_profile": {"size": "49152", "xon":"19456"}, + "pg_lossless_25000_40m_profile": {"size": "51200", "xon":"19456"}, + "pg_lossless_40000_40m_profile": {"size": "52224", "xon":"19456"}, + "pg_lossless_50000_40m_profile": {"size": "53248", "xon":"19456"}, + "pg_lossless_100000_40m_profile": {"size": "58368", "xon":"19456"}, + "pg_lossless_10000_300m_profile": {"size": "56320", "xon":"19456"}, + "pg_lossless_25000_300m_profile": {"size": "67584", "xon":"19456"}, + "pg_lossless_40000_300m_profile": {"size": "78848", "xon":"19456"}, + "pg_lossless_50000_300m_profile": {"size": "86016", "xon":"19456"}, + "pg_lossless_100000_300m_profile": {"size": "123904", "xon":"19456"}}, + "spc2_headroom": {"pg_lossless_10000_5m_profile": {"size": "52224", "xon":"19456"}, + "pg_lossless_25000_5m_profile": {"size": "52224", "xon":"19456"}, + "pg_lossless_40000_5m_profile": {"size": "53248", "xon":"19456"}, + "pg_lossless_50000_5m_profile": {"size": "53248", "xon":"19456"}, + "pg_lossless_100000_5m_profile": {"size": "53248", "xon":"19456"}, + "pg_lossless_200000_5m_profile": {"size": "55296", "xon":"19456"}, + "pg_lossless_10000_40m_profile": {"size": "53248", "xon":"19456"}, + "pg_lossless_25000_40m_profile": {"size": "55296", "xon":"19456"}, + "pg_lossless_40000_40m_profile": {"size": "57344", "xon":"19456"}, + "pg_lossless_50000_40m_profile": {"size": "58368", "xon":"19456"}, + "pg_lossless_100000_40m_profile": {"size": "63488", "xon":"19456"}, + "pg_lossless_200000_40m_profile": {"size": "74752", "xon":"19456"}, + "pg_lossless_10000_300m_profile": {"size": "60416", "xon":"19456"}, + "pg_lossless_25000_300m_profile": {"size": "73728", "xon":"19456"}, + "pg_lossless_40000_300m_profile": {"size": "86016", "xon":"19456"}, + "pg_lossless_50000_300m_profile": {"size": "95232", "xon":"19456"}, + "pg_lossless_100000_300m_profile": {"size": "137216", "xon":"19456"}, + "pg_lossless_200000_300m_profile": {"size": "223232", "xon":"19456"}}, + "spc2_3800_headroom": {"pg_lossless_10000_5m_profile": {"size": "54272", "xon":"19456"}, + "pg_lossless_25000_5m_profile": {"size": "58368", "xon":"19456"}, + "pg_lossless_40000_5m_profile": {"size": "61440", "xon":"19456"}, + "pg_lossless_50000_5m_profile": {"size": "64512", "xon":"19456"}, + "pg_lossless_100000_5m_profile": {"size": "75776", "xon":"19456"}, + "pg_lossless_10000_40m_profile": {"size": "55296", "xon":"19456"}, + "pg_lossless_25000_40m_profile": {"size": "60416", "xon":"19456"}, + "pg_lossless_40000_40m_profile": {"size": "65536", "xon":"19456"}, + "pg_lossless_50000_40m_profile": {"size": "69632", "xon":"19456"}, + "pg_lossless_100000_40m_profile": {"size": "86016", "xon":"19456"}, + "pg_lossless_10000_300m_profile": {"size": "63488", "xon":"19456"}, + "pg_lossless_25000_300m_profile": {"size": "78848", "xon":"19456"}, + "pg_lossless_40000_300m_profile": {"size": "95232", "xon":"19456"}, + "pg_lossless_50000_300m_profile": {"size": "106496", "xon":"19456"}, + "pg_lossless_100000_300m_profile": {"size": "159744", "xon":"19456"}}, + + # Buffer profile info + "buffer_profiles": {"ingress_lossless_profile": {"dynamic_th": "7", "pool": "[BUFFER_POOL|ingress_lossless_pool]", "size": "0"}, + "ingress_lossy_profile": {"dynamic_th": "3", "pool": "[BUFFER_POOL|ingress_lossy_pool]", "size": "0"}, + "egress_lossless_profile": {"dynamic_th": "7", "pool": "[BUFFER_POOL|egress_lossless_pool]", "size": "0"}, + "egress_lossy_profile": {"dynamic_th": "7", "pool": "[BUFFER_POOL|egress_lossy_pool]", "size": "9216"}, + "q_lossy_profile": {"dynamic_th": "3", "pool": "[BUFFER_POOL|egress_lossy_pool]", "size": "0"}} + } + } + + def mlnx_default_buffer_parameters(self, db_version, table): + """ + We extract buffer configurations to a common function + so that it can be reused among different migration + The logic of buffer parameters migrating: + 1. Compare the current buffer configuration with the default settings + 2. If there is a match, migrate the old value to the new one + 3. Insert the new setting into database + Each settings defined below (except that for version_1_0_2) will be used twice: + 1. It is referenced as new setting when database is migrated to that version + 2. It is referenced as old setting when database is migrated from that version + """ + + return self.mellanox_default_parameter[db_version].get(table) + + def mlnx_migrate_buffer_pool_size(self, old_version, new_version): + """ + To migrate buffer pool configuration + """ + # Buffer pools defined in old version + old_default_buffer_pools = self.mlnx_default_buffer_parameters(old_version, "buffer_pool_list") + + # Try to get related info from DB + buffer_pool_conf_in_db = self.configDB.get_table('BUFFER_POOL') + + # Get current buffer pool configuration, only migrate configuration which + # with default values, if it's not default, leave it as is. + name_list_of_pools_in_db = buffer_pool_conf_in_db.keys() + + # Buffer pool numbers is different with default, don't need migrate + if len(name_list_of_pools_in_db) != len(old_default_buffer_pools): + log.log_notice("Pools in CONFIG_DB ({}) don't match default ({}), skip buffer pool migration".format(name_list_of_pools_in_db, old_default_buffer_pools)) + return True + + # If some buffer pool is not default ones, don't need migrate + for buffer_pool in old_default_buffer_pools: + if buffer_pool not in name_list_of_pools_in_db: + log.log_notice("Default pool {} isn't in CONFIG_DB, skip buffer pool migration".format(buffer_pool)) + return True + + old_pool_configuration_list = self.mlnx_default_buffer_parameters(old_version, "pool_configuration_list") + if not old_pool_configuration_list: + log.log_error("Trying to get pool configuration list or migration control failed, skip migration") + return False + + new_config_name = None + for old_config_name in old_pool_configuration_list: + old_config = self.mlnx_default_buffer_parameters(old_version, old_config_name) + log.log_info("Checking old pool configuration {}".format(old_config_name)) + if buffer_pool_conf_in_db == old_config: + new_config_name = old_config_name + log.log_info("Old buffer pool configuration {} will be migrate to new one".format(old_config_name)) + break + + if not new_config_name: + log.log_notice("The configuration doesn't match any default configuration, migration for pool isn't required") + return True + + new_buffer_pool_conf = self.mlnx_default_buffer_parameters(new_version, new_config_name) + if not new_buffer_pool_conf: + log.log_error("Can't find the buffer pool configuration for {} in {}".format(new_config_name, new_version)) + return False + + # Migrate old buffer conf to latest. + for pool in old_default_buffer_pools: + self.configDB.set_entry('BUFFER_POOL', pool, new_buffer_pool_conf.get(pool)) + + log.log_info("Successfully migrate mlnx buffer pool {} size to the latest.".format(pool)) + + return True + + def mlnx_migrate_buffer_profile(self, old_version, new_version): + """ + This is to migrate BUFFER_PROFILE configuration + """ + device_data = self.configDB.get_table('DEVICE_METADATA') + if 'localhost' in device_data.keys(): + platform = device_data['localhost']['platform'] + else: + log.log_error("Trying to get DEVICE_METADATA from DB but doesn't exist, skip migration") + return False + + spc1_platforms = ["x86_64-mlnx_msn2010-r0", "x86_64-mlnx_msn2100-r0", "x86_64-mlnx_msn2410-r0", "x86_64-mlnx_msn2700-r0", "x86_64-mlnx_msn2740-r0"] + spc2_platforms = ["x86_64-mlnx_msn3700-r0", "x86_64-mlnx_msn3700c-r0"] + + # get profile + buffer_profile_old_configure = self.mlnx_default_buffer_parameters(old_version, "buffer_profiles") + buffer_profile_new_configure = self.mlnx_default_buffer_parameters(new_version, "buffer_profiles") + + buffer_profile_conf = self.configDB.get_table('BUFFER_PROFILE') + + # we need to transform lossless pg profiles to new settings + # to achieve that, we just need to remove this kind of profiles, buffermgrd will generate them automatically + default_lossless_profiles = None + if platform == 'x86_64-mlnx_msn3800-r0': + default_lossless_profiles = self.mlnx_default_buffer_parameters(old_version, "spc2_3800_headroom") + new_lossless_profiles = self.mlnx_default_buffer_parameters(new_version, "spc2_3800_headroom") + elif platform in spc2_platforms: + default_lossless_profiles = self.mlnx_default_buffer_parameters(old_version, "spc2_headroom") + new_lossless_profiles = self.mlnx_default_buffer_parameters(new_version, "spc2_headroom") + elif platform in spc1_platforms: + default_lossless_profiles = self.mlnx_default_buffer_parameters(old_version, "spc1_headroom") + new_lossless_profiles = self.mlnx_default_buffer_parameters(new_version, "spc1_headroom") + + if default_lossless_profiles and new_lossless_profiles: + for name, profile in buffer_profile_conf.iteritems(): + if name in default_lossless_profiles.keys(): + default_profile = default_lossless_profiles.get(name) + new_profile = new_lossless_profiles.get(name) + if not default_profile or not new_profile: + continue + default_profile['dynamic_th'] = '0' + default_profile['xoff'] = str(int(default_profile['size']) - int(default_profile['xon'])) + default_profile['pool'] = '[BUFFER_POOL|ingress_lossless_pool]' + if profile == default_profile: + default_profile['size'] = new_profile['size'] + default_profile['xon'] = new_profile['xon'] + default_profile['xoff'] = str(int(default_profile['size']) - int(default_profile['xon'])) + self.configDB.set_entry('BUFFER_PROFILE', name, default_profile) + + if not buffer_profile_new_configure: + # Not providing new profile configure in new version means they do need to be changed + log.log_notice("No buffer profile in {}, don't need to migrate non-lossless profiles".format(new_version)) + return True + + for name, profile in buffer_profile_old_configure.iteritems(): + if name in buffer_profile_conf.keys() and profile == buffer_profile_old_configure[name]: + continue + # return if any default profile isn't in cofiguration + log.log_notice("Default profile {} isn't in database or doesn't match default value".format(name)) + return True + + for name, profile in buffer_profile_new_configure.iteritems(): + log.log_info("Successfully migrate profile {}".format(name)) + self.configDB.set_entry('BUFFER_PROFILE', name, profile) diff --git a/setup.py b/setup.py index 88f3fbe848..d0b74c2aea 100644 --- a/setup.py +++ b/setup.py @@ -85,6 +85,7 @@ 'scripts/intfstat', 'scripts/lldpshow', 'scripts/log_ssd_health', + 'scripts/mellanox_buffer_migrator.py', 'scripts/mmuconfig', 'scripts/natclear', 'scripts/natconfig', From a15b6bfc825810472deeb1fed02adba6d61df662 Mon Sep 17 00:00:00 2001 From: arlakshm <55814491+arlakshm@users.noreply.github.com> Date: Fri, 14 Aug 2020 12:07:01 -0700 Subject: [PATCH 40/48] Common functions for show CLI support on multi ASIC (#999) Common changes will be used to support SONiC CLIs for multi ASIC - New MultiAsic class to support not displaying of internal object - Common CLI options which needed for multi ASIC platforms - a new decorator to execute a function on all namespaces Signed-off-by: Arvindsrinivasan Lakshmi Narasimhan --- utilities_common/constants.py | 9 +++ utilities_common/multi_asic.py | 138 +++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 utilities_common/constants.py create mode 100644 utilities_common/multi_asic.py diff --git a/utilities_common/constants.py b/utilities_common/constants.py new file mode 100644 index 0000000000..e3bb3ddb23 --- /dev/null +++ b/utilities_common/constants.py @@ -0,0 +1,9 @@ +#All the constant used in sonic-utilities + +DEFAULT_NAMESPACE = '' +DISPLAY_ALL = 'all' +DISPLAY_EXTERNAL = 'frontend' +BGP_NEIGH_OBJ = 'BGP_NEIGH' +PORT_CHANNEL_OBJ = 'PORT_CHANNEL' +PORT_OBJ = 'PORT' + diff --git a/utilities_common/multi_asic.py b/utilities_common/multi_asic.py new file mode 100644 index 0000000000..99096bb0b7 --- /dev/null +++ b/utilities_common/multi_asic.py @@ -0,0 +1,138 @@ +import argparse +import functools + +import click +from sonic_py_common import multi_asic +from utilities_common import constants + + +class MultiAsic(object): + + def __init__(self, display_option=constants.DISPLAY_ALL, + namespace_option=None): + self.namespace_option = namespace_option + self.display_option = display_option + self.current_namespace = None + self.is_multi_asic = multi_asic.is_multi_asic() + + def is_object_internal(self, object_type, cli_object): + ''' + The function checks if a CLI object is internal and returns true or false. + Internal objects are port or portchannel which are connected to other + ports or portchannels within a multi ASIC device. + + For single asic, this function is not applicable + ''' + if object_type == constants.PORT_OBJ: + return multi_asic.is_port_internal(cli_object) + elif object_type == constants.PORT_CHANNEL_OBJ: + return multi_asic.is_port_channel_internal(cli_object) + elif object_type == constants.BGP_NEIGH_OBJ: + return multi_asic.is_bgp_session_internal(cli_object) + + def skip_display(self, object_type, cli_object): + ''' + The function determines if the passed cli_object has to be displayed or not. + returns true if the display_option is external and the cli object is internal. + returns false, if the cli option is all or if it the platform is single ASIC. + + ''' + if not self.is_multi_asic: + return False + if self.display_option == constants.DISPLAY_ALL: + return False + return self.is_object_internal(object_type, cli_object) + + def get_ns_list_based_on_options(self): + ns_list = [] + if not self.is_multi_asic: + return [constants.DEFAULT_NAMESPACE] + else: + namespaces = multi_asic.get_all_namespaces() + if self.namespace_option is None: + if self.display_option == constants.DISPLAY_ALL: + ns_list = namespaces['front_ns'] + namespaces['back_ns'] + else: + ns_list = namespaces['front_ns'] + else: + if self.namespace_option not in namespaces['front_ns'] and \ + self.namespace_option not in namespaces['back_ns']: + raise ValueError( + 'Unknown Namespace {}'.format(self.namespace_option)) + ns_list = [self.namespace_option] + return ns_list + + +def multi_asic_ns_choices(): + if not multi_asic.is_multi_asic(): + return [constants.DEFAULT_NAMESPACE] + choices = multi_asic.get_namespace_list() + return choices + + +def multi_asic_display_choices(): + if not multi_asic.is_multi_asic(): + return [constants.DISPLAY_ALL] + else: + return [constants.DISPLAY_ALL, constants.DISPLAY_EXTERNAL] + + +def multi_asic_display_default_option(): + if not multi_asic.is_multi_asic(): + return constants.DISPLAY_ALL + else: + return constants.DISPLAY_EXTERNAL + + +_multi_asic_click_options = [ + click.option('--display', + '-d', 'display', + default=multi_asic_display_default_option(), + show_default=True, + type=click.Choice(multi_asic_display_choices()), + help='Show internal interfaces'), + click.option('--namespace', + '-n', 'namespace', + default=None, + type=click.Choice(multi_asic_ns_choices()), + show_default=True, + help='Namespace name or all'), +] + + +def multi_asic_click_options(func): + for option in reversed(_multi_asic_click_options): + func = option(func) + return func + + +def run_on_multi_asic(func): + ''' + This decorator is used on the CLI functions which needs to be + run on all the namespaces in the multi ASIC platform + The decorator loops through all the required namespaces, + for every iteration, it connects to all the DBs and provides an handle + to the wrapped function. + + ''' + @functools.wraps(func) + def wrapped_run_on_all_asics(self, *args, **kwargs): + ns_list = self.multi_asic.get_ns_list_based_on_options() + for ns in ns_list: + self.multi_asic.current_namespace = ns + self.db = multi_asic.connect_to_all_dbs_for_ns(ns) + self.config_db = multi_asic.connect_config_db_for_ns(ns) + func(self, *args, **kwargs) + return wrapped_run_on_all_asics + + +def multi_asic_args(parser=None): + if parser is None: + parser = argparse.ArgumentParser( + formatter_class=argparse.RawTextHelpFormatter) + + parser.add_argument('-d', '--display', default=constants.DISPLAY_EXTERNAL, + help='Display all interfaces or only external interfaces') + parser.add_argument('-n', '--namespace', default=None, + help='Display interfaces for specific namespace') + return parser From 37f131ef0403d5b811b3eccef161edaccb30bf6c Mon Sep 17 00:00:00 2001 From: Vaibhav Hemant Dixit Date: Fri, 14 Aug 2020 15:49:49 -0700 Subject: [PATCH 41/48] Change fast-reboot script to use swss and radv service script (#1036) * Change fast-reboot script to use swss and radv service script for stopping services --- scripts/fast-reboot | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 302cdf7f1f..6ed44c8dde 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -503,9 +503,9 @@ systemctl stop nat debug "Stopped nat ..." # Kill radv before stopping BGP service to prevent annoucing our departure. -debug "Stopping radv ..." -docker kill radv &>/dev/null || [ $? == 1 ] +debug "Stopping radv service..." systemctl stop radv +debug "Stopped radv service..." # Kill bgpd to start the bgp graceful restart procedure debug "Stopping bgp ..." @@ -536,12 +536,9 @@ if [[ "$REBOOT_TYPE" = "fast-reboot" ]]; then debug "Stopped teamd ..." fi -# Kill swss Docker container -# We call `docker kill swss` to ensure the container stops as quickly as possible, -# then immediately call `systemctl stop swss` to prevent the service from -# restarting the container automatically. -docker kill swss &> /dev/null || debug "Docker swss is not running ($?) ..." +debug "Stopping swss service ..." systemctl stop swss +debug "Stopped swss service ..." # Pre-shutdown syncd if [[ "$REBOOT_TYPE" = "warm-reboot" || "$REBOOT_TYPE" = "fastfast-reboot" ]]; then From 3297b7aa233b84f1946e736d32810a9c6de6df7e Mon Sep 17 00:00:00 2001 From: Praveen Chaudhary Date: Sat, 15 Aug 2020 12:44:50 -0700 Subject: [PATCH 42/48] [sflow_test.py]: Fix show sflow display. (#1054) Changes: -- Display ipv4 address with left adjust of 25 width and with space before UDP. -- IPv6 address will be displayed as is. -- Add test data to appl_db.json and config_db.json -- add test file sflow_test.py -- use pass_db decorator for sflow_interface. -- since sflow needs ctx, create use Db() in function. Signed-off-by: Praveen Chaudhary pchaudhary@linkedin.com --- show/main.py | 14 +++---- tests/mock_tables/appl_db.json | 16 ++++++++ tests/mock_tables/config_db.json | 13 +++++++ tests/sflow_test.py | 64 ++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 8 deletions(-) create mode 100644 tests/sflow_test.py diff --git a/show/main.py b/show/main.py index 30e1bed6f2..712ae674a4 100755 --- a/show/main.py +++ b/show/main.py @@ -1714,20 +1714,18 @@ def policer(policer_name, verbose): @click.pass_context def sflow(ctx): """Show sFlow related information""" - config_db = ConfigDBConnector() - config_db.connect() - ctx.obj = {'db': config_db} if ctx.invoked_subcommand is None: - show_sflow_global(config_db) + db = Db() + show_sflow_global(db.cfgdb) # # 'sflow command ("show sflow interface ...") # @sflow.command('interface') -@click.pass_context -def sflow_interface(ctx): +@clicommon.pass_db +def sflow_interface(db): """Show sFlow interface information""" - show_sflow_interface(ctx.obj['db']) + show_sflow_interface(db.cfgdb) def sflow_appDB_connect(): db = SonicV2Connector(host='127.0.0.1') @@ -1789,7 +1787,7 @@ def show_sflow_global(config_db): click.echo("\n {} Collectors configured:".format(len(sflow_info))) for collector_name in sorted(sflow_info.keys()): click.echo(" Name: {}".format(collector_name).ljust(30) + - "IP addr: {}".format(sflow_info[collector_name]['collector_ip']).ljust(20) + + "IP addr: {} ".format(sflow_info[collector_name]['collector_ip']).ljust(25) + "UDP port: {}".format(sflow_info[collector_name]['collector_port'])) diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index f851712caa..f64914760b 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -1,4 +1,20 @@ { + "SFLOW_SESSION_TABLE:Ethernet0": { + "admin_state": "up", + "sample_rate": "2500" + }, + "SFLOW_SESSION_TABLE:Ethernet4": { + "admin_state": "up", + "sample_rate": "1000" + }, + "SFLOW_SESSION_TABLE:Ethernet112": { + "admin_state": "up", + "sample_rate": "1000" + }, + "SFLOW_SESSION_TABLE:Ethernet116": { + "admin_state": "up", + "sample_rate": "5000" + }, "PORT_TABLE:Ethernet0": { "index": "0", "lanes": "0", diff --git a/tests/mock_tables/config_db.json b/tests/mock_tables/config_db.json index 51bd7fb683..c069493216 100644 --- a/tests/mock_tables/config_db.json +++ b/tests/mock_tables/config_db.json @@ -1,4 +1,17 @@ { + "SFLOW|global": { + "admin_state": "up", + "agent_id": "eth0", + "polling_interval": "0" + }, + "SFLOW_COLLECTOR|prod": { + "collector_ip": "fe80::6e82:6aff:fe1e:cd8e", + "collector_port": "6343" + }, + "SFLOW_COLLECTOR|ser5": { + "collector_ip": "172.21.35.15", + "collector_port": "6343" + }, "BREAKOUT_CFG|Ethernet0": { "brkout_mode": "4x25G[10G]" }, diff --git a/tests/sflow_test.py b/tests/sflow_test.py new file mode 100644 index 0000000000..e54c195b3d --- /dev/null +++ b/tests/sflow_test.py @@ -0,0 +1,64 @@ +import os +import sys +import pytest +from click.testing import CliRunner +from utilities_common.db import Db + +import show.main as show +import mock_tables.dbconnector + +# Expected output for 'show sflow' +show_sflow_output = ''+ \ +""" +sFlow Global Information: + sFlow Admin State: up + sFlow Polling Interval: 0 + sFlow AgentID: eth0 + + 2 Collectors configured: + Name: prod IP addr: fe80::6e82:6aff:fe1e:cd8e UDP port: 6343 + Name: ser5 IP addr: 172.21.35.15 UDP port: 6343 +""" + +# Expected output for 'show sflow interface' +show_sflow_intf_output = ''+ \ +""" +sFlow interface configurations ++-------------+---------------+-----------------+ +| Interface | Admin State | Sampling Rate | ++=============+===============+=================+ +| Ethernet0 | up | 2500 | ++-------------+---------------+-----------------+ +| Ethernet4 | up | 1000 | ++-------------+---------------+-----------------+ +| Ethernet112 | up | 1000 | ++-------------+---------------+-----------------+ +| Ethernet116 | up | 5000 | ++-------------+---------------+-----------------+ +""" + +class TestShowSflow(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["UTILITIES_UNIT_TESTING"] = "1" + + def test_show_sflow(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["sflow"], [], obj=Db()) + print(sys.stderr, result.output) + assert result.exit_code == 0 + assert result.output == show_sflow_output + + def test_show_sflow_intf(self): + runner = CliRunner() + result = runner.invoke(show.cli.commands["sflow"].commands["interface"], [], obj=Db()) + print(sys.stderr, result.output) + assert result.exit_code == 0 + assert result.output == show_sflow_intf_output + + @classmethod + def teardown_class(cls): + print("TEARDOWN") + os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) + os.environ["UTILITIES_UNIT_TESTING"] = "0" From 8768580aa055429bed9d31036afc2129ed1c0756 Mon Sep 17 00:00:00 2001 From: Tamer Ahmed Date: Sat, 15 Aug 2020 15:04:11 -0700 Subject: [PATCH 43/48] [filter-fdb] Call Filter FDB Main From Within Test Code (#1051) Code coverage requires that python code be run with the same process. Current test code was invoking filter fdb via shell which launches new process and so coverage is not available. This PR calls the main method from within test code. signed-off-by: Tamer Ahmed --- fdbutil/__init__.py | 0 {scripts => fdbutil}/filter_fdb_entries.py | 29 ++++++---------------- pytest.ini | 2 +- scripts/fast-reboot | 2 +- setup.py | 3 ++- tests/filter_fdb_entries_test.py | 9 ++++--- 6 files changed, 17 insertions(+), 28 deletions(-) create mode 100644 fdbutil/__init__.py rename {scripts => fdbutil}/filter_fdb_entries.py (93%) diff --git a/fdbutil/__init__.py b/fdbutil/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/scripts/filter_fdb_entries.py b/fdbutil/filter_fdb_entries.py similarity index 93% rename from scripts/filter_fdb_entries.py rename to fdbutil/filter_fdb_entries.py index 31d4204ec9..23380ed0ef 100755 --- a/scripts/filter_fdb_entries.py +++ b/fdbutil/filter_fdb_entries.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python - import json import sys import os @@ -124,44 +122,33 @@ def file_exists_or_raise(filename): if not os.path.exists(filename): raise Exception("file '{0}' does not exist".format(filename)) -def main(): +def main(argv=sys.argv): parser = argparse.ArgumentParser() parser.add_argument('-f', '--fdb', type=str, default='/tmp/fdb.json', help='fdb file name') parser.add_argument('-a', '--arp', type=str, default='/tmp/arp.json', help='arp file name') parser.add_argument('-c', '--config_db', type=str, default='/tmp/config_db.json', help='config db file name') parser.add_argument('-b', '--backup_file', type=bool, default=True, help='Back up old fdb entries file') - args = parser.parse_args() + args = parser.parse_args(argv[1:]) fdb_filename = args.fdb arp_filename = args.arp config_db_filename = args.config_db backup_file = args.backup_file + res = 0 try: + syslog.openlog('filter_fdb_entries') file_exists_or_raise(fdb_filename) file_exists_or_raise(arp_filename) file_exists_or_raise(config_db_filename) except Exception as e: syslog.syslog(syslog.LOG_ERR, "Got an exception %s: Traceback: %s" % (str(e), traceback.format_exc())) - else: - filter_fdb_entries(fdb_filename, arp_filename, config_db_filename, backup_file) - - return 0 - -if __name__ == '__main__': - res = 0 - try: - syslog.openlog('filter_fdb_entries') - res = main() except KeyboardInterrupt: syslog.syslog(syslog.LOG_NOTICE, "SIGINT received. Quitting") res = 1 - except Exception as e: - syslog.syslog(syslog.LOG_ERR, "Got an exception %s: Traceback: %s" % (str(e), traceback.format_exc())) - res = 2 + else: + filter_fdb_entries(fdb_filename, arp_filename, config_db_filename, backup_file) finally: syslog.closelog() - try: - sys.exit(res) - except SystemExit: - os._exit(res) + + return res diff --git a/pytest.ini b/pytest.ini index d1975890b9..40d110e3b0 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,4 @@ [pytest] filterwarnings = ignore::DeprecationWarning -addopts = --cov=acl_loader --cov=clear --cov=config --cov=connect --cov=consutil --cov=counterpoll --cov=crm --cov=debug --cov=fwutil --cov=pcieutil --cov=pfcwd --cov=psuutil --cov=pddf_fanutil --cov=pddf_ledutil --cov=pddf_psuutil --cov=pddf_thermalutil --cov=scripts --cov=sfputil --cov=show --cov=sonic_installer --cov=ssdutil --cov=utilities_common --cov=watchdogutil --cov-report html --cov-report term --cov-report xml +addopts = --cov=acl_loader --cov=clear --cov=config --cov=connect --cov=consutil --cov=counterpoll --cov=crm --cov=debug --cov=fdbutil --cov=fwutil --cov=pcieutil --cov=pfcwd --cov=psuutil --cov=pddf_fanutil --cov=pddf_ledutil --cov=pddf_psuutil --cov=pddf_thermalutil --cov=scripts --cov=sfputil --cov=show --cov=sonic_installer --cov=ssdutil --cov=utilities_common --cov=watchdogutil --cov-report html --cov-report term --cov-report xml diff --git a/scripts/fast-reboot b/scripts/fast-reboot index 6ed44c8dde..b2333e2386 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -456,7 +456,7 @@ if [[ "$REBOOT_TYPE" = "fast-reboot" ]]; then FILTER_FDB_ENTRIES_RC=0 # Filter FDB entries using MAC addresses from ARP table - /usr/bin/filter_fdb_entries.py -f $DUMP_DIR/fdb.json -a $DUMP_DIR/arp.json -c $CONFIG_DB_FILE || FILTER_FDB_ENTRIES_RC=$? + /usr/bin/filter_fdb_entries -f $DUMP_DIR/fdb.json -a $DUMP_DIR/arp.json -c $CONFIG_DB_FILE || FILTER_FDB_ENTRIES_RC=$? if [[ FILTER_FDB_ENTRIES_RC -ne 0 ]]; then error "Failed to filter FDb entries. Exit code: $FILTER_FDB_ENTRIES_RC" unload_kernel diff --git a/setup.py b/setup.py index d0b74c2aea..37e55cba1e 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ 'ssdutil', 'pfc', 'psuutil', + 'fdbutil', 'fwutil', 'pcieutil', 'pddf_fanutil', @@ -78,7 +79,6 @@ 'scripts/fast-reboot-dump.py', 'scripts/fdbclear', 'scripts/fdbshow', - 'scripts/filter_fdb_entries.py', 'scripts/gearboxutil', 'scripts/generate_dump', 'scripts/intfutil', @@ -124,6 +124,7 @@ 'counterpoll = counterpoll.main:cli', 'crm = crm.main:cli', 'debug = debug.main:cli', + 'filter_fdb_entries = fdbutil.filter_fdb_entries:main', 'pfcwd = pfcwd.main:cli', 'sfputil = sfputil.main:cli', 'ssdutil = ssdutil.main:ssdutil', diff --git a/tests/filter_fdb_entries_test.py b/tests/filter_fdb_entries_test.py index af1f7712c3..fbbff67ea9 100644 --- a/tests/filter_fdb_entries_test.py +++ b/tests/filter_fdb_entries_test.py @@ -7,6 +7,7 @@ from collections import defaultdict from filter_fdb_input.test_vectors import filterFdbEntriesTestVector +from fdbutil.filter_fdb_entries import main as filterFdbMain class TestFilterFdbEntries(object): """ @@ -162,16 +163,16 @@ def testFilterFdbEntries(self, testData): """ try: self.__setUp(testData) - - stdout, stderr, rc = self.__runCommand([ - "scripts/filter_fdb_entries.py", + argv = [ + "filter_fdb_entries", "-a", self.ARP_FILENAME, "-f", self.FDB_FILENAME, "-c", self.CONFIG_DB_FILENAME, - ]) + ] + rc = filterFdbMain(argv) assert rc == 0, "Filter_fdb_entries.py failed with '{0}'".format(stderr) assert self.__verifyOutput(), "Test failed for test data: {0}".format(testData) finally: From 5263b544f0f3a4d87f09b7c201c87b21cea6b9e0 Mon Sep 17 00:00:00 2001 From: Tamer Ahmed Date: Tue, 18 Aug 2020 18:25:08 -0700 Subject: [PATCH 44/48] [config] Reduce Calls to SONiC Cfggen (#1052) * [config] Reduce Calls to SONiC Cfggen Calls to sonic-cfggen is CPU expensive. This PR reduces calls to sonic-cfggen during config-setup when configuring buffer/qos. singed-off-by: Tamer Ahmed * review comment, apply both buffer and qos configs or none --- config/main.py | 54 ++++++++++++-------------------------------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/config/main.py b/config/main.py index 8b9443b20b..b605d37166 100755 --- a/config/main.py +++ b/config/main.py @@ -1562,63 +1562,35 @@ def reload(): click.secho( "Command 'qos reload' failed with invalid namespace '{}'". format(ns), - fg='yellow' + fg="yellow" ) raise click.Abort() asic_id_suffix = str(asic_id) - buffer_template_file = os.path.join( - hwsku_path, - asic_id_suffix, - 'buffers.json.j2' - ) - buffer_output_file = "/tmp/buffers{}.json".format(asic_id_suffix) - qos_output_file = "/tmp/qos{}.json".format(asic_id_suffix) - - cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns) + buffer_template_file = os.path.join(hwsku_path, asic_id_suffix, "buffers.json.j2") if os.path.isfile(buffer_template_file): - command = "{} {} -d -t {} > {}".format( - SONIC_CFGGEN_PATH, - cmd_ns, - buffer_template_file, - buffer_output_file - ) - clicommon.run_command(command, display_cmd=True) - qos_template_file = os.path.join( - hwsku_path, - asic_id_suffix, - 'qos.json.j2' - ) - sonic_version_file = os.path.join( - '/etc/sonic/', 'sonic_version.yml' - ) + qos_template_file = os.path.join(hwsku_path, asic_id_suffix, "qos.json.j2") if os.path.isfile(qos_template_file): - command = "{} {} -d -t {} -y {} > {}".format( + cmd_ns = "" if ns is DEFAULT_NAMESPACE else "-n {}".format(ns) + sonic_version_file = os.path.join('/', "etc", "sonic", "sonic_version.yml") + command = "{} {} -d -t {},config-db -t {},config-db -y {} --write-to-db".format( SONIC_CFGGEN_PATH, cmd_ns, + buffer_template_file, qos_template_file, - sonic_version_file, - qos_output_file + sonic_version_file ) - clicommon.run_command(command, display_cmd=True) # Apply the configurations only when both buffer and qos - # configuration files are presented - command = "{} {} -j {} --write-to-db".format( - SONIC_CFGGEN_PATH, cmd_ns, buffer_output_file - ) - clicommon.run_command(command, display_cmd=True) - command = "{} {} -j {} --write-to-db".format( - SONIC_CFGGEN_PATH, cmd_ns, qos_output_file - ) + # configuration files are present clicommon.run_command(command, display_cmd=True) else: - click.secho('QoS definition template not found at {}'.format( + click.secho("QoS definition template not found at {}".format( qos_template_file - ), fg='yellow') + ), fg="yellow") else: - click.secho('Buffer definition template not found at {}'.format( + click.secho("Buffer definition template not found at {}".format( buffer_template_file - ), fg='yellow') + ), fg="yellow") # # 'warm_restart' group ('config warm_restart ...') From 56a97a6028a152948a2083dd4379b14b9bc70a2e Mon Sep 17 00:00:00 2001 From: kuanyu99 Date: Fri, 21 Aug 2020 14:42:59 +0800 Subject: [PATCH 45/48] [fast-reboot]: Fix fail to execute fast-reboot problem (#1047) * Fix the fast-reboot-dump.py error when it try to use inet_aton to translate ipv6 address * Add send_ndp as TODO in fast-reboot-dump.py to ipv6 target --- scripts/fast-reboot-dump.py | 43 +++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/scripts/fast-reboot-dump.py b/scripts/fast-reboot-dump.py index 4a6a356d13..eebb2a8a5e 100644 --- a/scripts/fast-reboot-dump.py +++ b/scripts/fast-reboot-dump.py @@ -11,39 +11,46 @@ import argparse import syslog import traceback +import ipaddress ARP_CHUNK = binascii.unhexlify('08060001080006040001') # defines a part of the packet for ARP Request ARP_PAD = binascii.unhexlify('00' * 18) -def generate_arp_entries(filename, all_available_macs): +def generate_neighbor_entries(filename, all_available_macs): db = swsssdk.SonicV2Connector(host='127.0.0.1') db.connect(db.APPL_DB, False) # Make one attempt only arp_output = [] - arp_entries = [] + neighbor_entries = [] keys = db.keys(db.APPL_DB, 'NEIGH_TABLE:*') keys = [] if keys is None else keys for key in keys: vlan_name = key.split(':')[1] - ip_addr = key.split(':')[2] entry = db.get_all(db.APPL_DB, key) - if (vlan_name, entry['neigh'].lower()) not in all_available_macs: + mac = entry['neigh'].lower() + if (vlan_name, mac) not in all_available_macs: # FIXME: print me to log continue obj = { key: entry, 'OP': 'SET' } - arp_entries.append((vlan_name, entry['neigh'].lower(), ip_addr)) arp_output.append(obj) + ip_addr = key.split(':')[2] + if ipaddress.ip_interface(ip_addr).ip.version != 4: + #This is ipv6 address + ip_addr = key.replace(key.split(':')[0] + ':' + key.split(':')[1] + ':', '') + neighbor_entries.append((vlan_name, mac, ip_addr)) + syslog.syslog(syslog.LOG_INFO, "Neighbor entry: [Vlan: %s, Mac: %s, Ip: %s]" % (vlan_name, mac, ip_addr)) + db.close(db.APPL_DB) with open(filename, 'w') as fp: json.dump(arp_output, fp, indent=2, separators=(',', ': ')) - return arp_entries + return neighbor_entries def is_mac_unicast(mac): first_octet = mac.split(':')[0] @@ -201,14 +208,19 @@ def send_arp(s, src_mac, src_ip, dst_mac_s, dst_ip_s): return -def garp_send(arp_entries, map_mac_ip_per_vlan): +def send_ndp(s, src_mac, src_ip, dst_mac_s, dst_ip_s): + #TODO: Implement send in neighbor solicitation format + + return + +def send_garp_nd(neighbor_entries, map_mac_ip_per_vlan): ETH_P_ALL = 0x03 # generate source ip addresses for arp packets - src_ip_addrs = {vlan_name:get_iface_ip_addr(vlan_name) for vlan_name,_,_ in arp_entries} + src_ip_addrs = {vlan_name:get_iface_ip_addr(vlan_name) for vlan_name,_,_ in neighbor_entries} # generate source mac addresses for arp packets - src_ifs = {map_mac_ip_per_vlan[vlan_name][dst_mac] for vlan_name, dst_mac, _ in arp_entries} + src_ifs = {map_mac_ip_per_vlan[vlan_name][dst_mac] for vlan_name, dst_mac, _ in neighbor_entries} src_mac_addrs = {src_if:get_iface_mac_addr(src_if) for src_if in src_ifs} # open raw sockets for all required interfaces @@ -217,10 +229,13 @@ def garp_send(arp_entries, map_mac_ip_per_vlan): sockets[src_if] = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL)) sockets[src_if].bind((src_if, 0)) - # send arp packets - for vlan_name, dst_mac, dst_ip in arp_entries: + # send arp/ndp packets + for vlan_name, dst_mac, dst_ip in neighbor_entries: src_if = map_mac_ip_per_vlan[vlan_name][dst_mac] - send_arp(sockets[src_if], src_mac_addrs[src_if], src_ip_addrs[vlan_name], dst_mac, dst_ip) + if ipaddress.ip_interface(dst_ip).ip.version == 4: + send_arp(sockets[src_if], src_mac_addrs[src_if], src_ip_addrs[vlan_name], dst_mac, dst_ip) + else: + send_ndp(sockets[src_if], src_mac_addrs[src_if], src_ip_addrs[vlan_name], dst_mac, dst_ip) # close the raw sockets for s in sockets.values(): @@ -271,9 +286,9 @@ def main(): print("Target directory '%s' not found" % root_dir) return 3 all_available_macs, map_mac_ip_per_vlan = generate_fdb_entries(root_dir + '/fdb.json') - arp_entries = generate_arp_entries(root_dir + '/arp.json', all_available_macs) + neighbor_entries = generate_neighbor_entries(root_dir + '/arp.json', all_available_macs) generate_default_route_entries(root_dir + '/default_routes.json') - garp_send(arp_entries, map_mac_ip_per_vlan) + send_garp_nd(neighbor_entries, map_mac_ip_per_vlan) return 0 if __name__ == '__main__': From 17fb3781b2cb7dfb845faa9f16bc17ccd0069649 Mon Sep 17 00:00:00 2001 From: Joe LeVeque Date: Thu, 20 Aug 2020 23:44:14 -0700 Subject: [PATCH 46/48] [sonic-installer] Import re module (#1061) The import re line was removed from the file in #953. However, it is still referenced in the update_sonic_environment() function. Adding it back. --- sonic_installer/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sonic_installer/main.py b/sonic_installer/main.py index fc5984d031..ca843c394b 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -6,6 +6,7 @@ import configparser import os +import re import subprocess import sys import time From 2c0ff9280e2890b336626b4f867af0b46604db1e Mon Sep 17 00:00:00 2001 From: arlakshm <55814491+arlakshm@users.noreply.github.com> Date: Fri, 21 Aug 2020 06:33:23 -0700 Subject: [PATCH 47/48] support show interface commands for multi ASIC platforms (#1006) Changes to support the show interface status/description for multi ASIC - Add argparse intfutil script - Change the IntfDescription and IntfStatus classes to get the information from all namespaces. - Add changes to filter out the internal ports from display - Add support for -n and -d click options - Add unit test to test mulit asic commands Signed-off-by: Arvindsrinivasan Lakshmi Narasimhan --- scripts/intfutil | 308 +++++++++---------- setup.py | 7 +- show/interfaces/__init__.py | 63 ++-- show/main.py | 6 +- tests/intfutil_test.py | 16 +- tests/mock_tables/asic0/__init__.py | 0 tests/mock_tables/asic0/appl_db.json | 72 +++++ tests/mock_tables/asic0/asic_db.json | 6 + tests/mock_tables/asic0/config_db.json | 87 ++++++ tests/mock_tables/asic0/counters_db.json | 161 ++++++++++ tests/mock_tables/asic0/database_config.json | 57 ++++ tests/mock_tables/asic1/__init__.py | 0 tests/mock_tables/asic1/appl_db.json | 35 +++ tests/mock_tables/asic1/config_db.json | 55 ++++ tests/mock_tables/asic1/database_config.json | 57 ++++ tests/mock_tables/database_config.json | 57 ++++ tests/mock_tables/database_global.json | 16 + tests/mock_tables/dbconnector.py | 73 +++-- tests/mock_tables/mock_multi_asic.py | 20 ++ tests/multi_asic_intfutil_test.py | 182 +++++++++++ utilities_common/cli.py | 10 +- 21 files changed, 1064 insertions(+), 224 deletions(-) create mode 100644 tests/mock_tables/asic0/__init__.py create mode 100644 tests/mock_tables/asic0/appl_db.json create mode 100644 tests/mock_tables/asic0/asic_db.json create mode 100644 tests/mock_tables/asic0/config_db.json create mode 100644 tests/mock_tables/asic0/counters_db.json create mode 100644 tests/mock_tables/asic0/database_config.json create mode 100644 tests/mock_tables/asic1/__init__.py create mode 100644 tests/mock_tables/asic1/appl_db.json create mode 100644 tests/mock_tables/asic1/config_db.json create mode 100644 tests/mock_tables/asic1/database_config.json create mode 100644 tests/mock_tables/database_config.json create mode 100644 tests/mock_tables/database_global.json create mode 100644 tests/mock_tables/mock_multi_asic.py create mode 100644 tests/multi_asic_intfutil_test.py diff --git a/scripts/intfutil b/scripts/intfutil index 1cedf8d65b..8ba3a9bb6b 100755 --- a/scripts/intfutil +++ b/scripts/intfutil @@ -1,17 +1,17 @@ #! /usr/bin/python -import swsssdk -import sys +import argparse +import os import re +import sys import types -from tabulate import tabulate + from natsort import natsorted -from swsssdk import ConfigDBConnector -from pprint import pprint +from tabulate import tabulate +from utilities_common import constants +from utilities_common import multi_asic as multi_asic_util from utilities_common.intf_filter import parse_interface_in_filter -import os - # mock the redis for unit test purposes # try: if os.environ["UTILITIES_UNIT_TESTING"] == "2": @@ -20,6 +20,10 @@ try: sys.path.insert(0, modules_path) sys.path.insert(0, tests_path) import mock_tables.dbconnector + if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic": + import mock_tables.mock_multi_asic + mock_tables.dbconnector.load_namespace_config() + except KeyError: pass @@ -44,16 +48,6 @@ VLAN_SUB_INTERFACE_TYPE = "802.1q-encapsulation" SUB_PORT = "subport" -def db_connect_configdb(): - """ - Connect to configdb - """ - config_db = ConfigDBConnector() - if config_db is None: - return None - config_db.connect() - return config_db - def get_frontpanel_port_list(config_db): ports_dict = config_db.get_table('PORT') front_panel_ports_list = [] @@ -100,14 +94,6 @@ def config_db_vlan_port_keys_get(int_to_vlan_dict, front_panel_ports_list, intf_ return vlan -def db_connect_appl(): - appl_db = swsssdk.SonicV2Connector(host='127.0.0.1') - if appl_db is None: - return None - appl_db.connect(appl_db.APPL_DB) - return appl_db - - def appl_db_keys_get(appl_db, front_panel_ports_list, intf_name): """ Get APPL_DB Keys @@ -151,18 +137,6 @@ def appl_db_port_status_get(appl_db, intf_name, status_type): status = '{}G'.format(status[:-3]) return status - -def db_connect_state(): - """ - Connect to REDIS STATE DB and get optics info - """ - state_db = swsssdk.SonicV2Connector(host='127.0.0.1') - if state_db is None: - return None - state_db.connect(state_db.STATE_DB, False) # Make one attempt only - return state_db - - def state_db_port_optics_get(state_db, intf_name, type): """ Get optic type info for port @@ -348,9 +322,44 @@ def appl_db_sub_intf_status_get(appl_db, config_db, front_panel_ports_list, port header_stat = ['Interface', 'Lanes', 'Speed', 'MTU', 'FEC', 'Alias', 'Vlan', 'Oper', 'Admin', 'Type', 'Asym PFC'] header_stat_sub_intf = ['Sub port interface', 'Speed', 'MTU', 'Vlan', 'Admin', 'Type'] + class IntfStatus(object): - def display_intf_status(self, intf_name, appl_db_keys, front_panel_ports_list, portchannel_speed_dict, appl_db_sub_intf_keys, sub_intf_list, sub_intf_only): + def __init__(self, intf_name, namespace_option, display_option): + """ + Class constructor method + :param self: + :param intf_name: string of interface + :return: + """ + self.db = None + self.config_db = None + self.sub_intf_only = False + self.intf_name = intf_name + self.sub_intf_name = intf_name + self.table = [] + self.multi_asic = multi_asic_util.MultiAsic( + display_option, namespace_option) + if intf_name is not None: + if intf_name == SUB_PORT: + self.intf_name = None + self.sub_intf_name = None + self.sub_intf_only = True + else: + sub_intf_sep_idx = intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR) + if sub_intf_sep_idx != -1: + self.sub_intf_only = True + self.intf_name = intf_name[:sub_intf_sep_idx] + + def display_intf_status(self): + self.get_intf_status() + sorted_table = natsorted(self.table) + print tabulate(sorted_table, + header_stat if not self.sub_intf_only else header_stat_sub_intf, + tablefmt="simple", + stralign='right') + + def generate_intf_status(self): """ Generate interface-status output """ @@ -359,91 +368,64 @@ class IntfStatus(object): table = [] key = [] - intf_fs = parse_interface_in_filter(intf_name) - + intf_fs = parse_interface_in_filter(self.intf_name) # # Iterate through all the keys and append port's associated state to # the result table. # - if not sub_intf_only: - for i in appl_db_keys: + if not self.sub_intf_only: + for i in self.appl_db_keys: key = re.split(':', i, maxsplit=1)[-1].strip() - if key in front_panel_ports_list: - if intf_name is None or key in intf_fs: + if key in self.front_panel_ports_list: + if self.multi_asic.skip_display(constants.PORT_OBJ, key): + continue + + if self.intf_name is None or key in intf_fs: table.append((key, - appl_db_port_status_get(self.appl_db, key, PORT_LANES_STATUS), - appl_db_port_status_get(self.appl_db, key, PORT_SPEED), - appl_db_port_status_get(self.appl_db, key, PORT_MTU_STATUS), - appl_db_port_status_get(self.appl_db, key, PORT_FEC), - appl_db_port_status_get(self.appl_db, key, PORT_ALIAS), + appl_db_port_status_get(self.db, key, PORT_LANES_STATUS), + appl_db_port_status_get(self.db, key, PORT_SPEED), + appl_db_port_status_get(self.db, key, PORT_MTU_STATUS), + appl_db_port_status_get(self.db, key, PORT_FEC), + appl_db_port_status_get(self.db, key, PORT_ALIAS), config_db_vlan_port_keys_get(self.combined_int_to_vlan_po_dict, self.front_panel_ports_list, key), - appl_db_port_status_get(self.appl_db, key, PORT_OPER_STATUS), - appl_db_port_status_get(self.appl_db, key, PORT_ADMIN_STATUS), - state_db_port_optics_get(self.state_db, key, PORT_OPTICS_TYPE), - appl_db_port_status_get(self.appl_db, key, PORT_PFC_ASYM_STATUS))) + appl_db_port_status_get(self.db, key, PORT_OPER_STATUS), + appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS), + state_db_port_optics_get(self.db, key, PORT_OPTICS_TYPE), + appl_db_port_status_get(self.db, key, PORT_PFC_ASYM_STATUS))) - for po, value in portchannel_speed_dict.iteritems(): + for po, value in self.portchannel_speed_dict.iteritems(): if po: - if intf_name is None or po in intf_fs: + if self.multi_asic.skip_display(constants.PORT_CHANNEL_OBJ, po): + continue + if self.intf_name is None or po in intf_fs: table.append((po, - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_LANES_STATUS, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_SPEED, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_MTU_STATUS, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_FEC, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_ALIAS, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, "vlan", self.portchannel_speed_dict, self.combined_int_to_vlan_po_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_OPER_STATUS, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_ADMIN_STATUS, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_OPTICS_TYPE, self.portchannel_speed_dict), - appl_db_portchannel_status_get(self.appl_db, self.config_db, po, PORT_PFC_ASYM_STATUS, self.portchannel_speed_dict))) + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_LANES_STATUS, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_SPEED, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_MTU_STATUS, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_FEC, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_ALIAS, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, "vlan", self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_OPER_STATUS, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_ADMIN_STATUS, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_OPTICS_TYPE, self.portchannel_speed_dict), + appl_db_portchannel_status_get(self.db, self.config_db, po, PORT_PFC_ASYM_STATUS, self.portchannel_speed_dict))) else: - for key in appl_db_sub_intf_keys: + for key in self.appl_db_sub_intf_keys: sub_intf = re.split(':', key, maxsplit=1)[-1].strip() - if sub_intf in sub_intf_list: + if sub_intf in self.sub_intf_list: table.append((sub_intf, - appl_db_sub_intf_status_get(self.appl_db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_SPEED), - appl_db_sub_intf_status_get(self.appl_db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_MTU_STATUS), - appl_db_sub_intf_status_get(self.appl_db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, "vlan"), - appl_db_sub_intf_status_get(self.appl_db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_ADMIN_STATUS), - appl_db_sub_intf_status_get(self.appl_db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_OPTICS_TYPE))) - - # Sorting and tabulating the result table. - sorted_table = natsorted(table) - print(tabulate(sorted_table, header_stat if not sub_intf_only else header_stat_sub_intf, tablefmt="simple", stralign='right')) - + appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_SPEED), + appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_MTU_STATUS), + appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, "vlan"), + appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_ADMIN_STATUS), + appl_db_sub_intf_status_get(self.db, self.config_db, self.front_panel_ports_list, self.portchannel_speed_dict, sub_intf, PORT_OPTICS_TYPE))) + return table - def __init__(self, intf_name): - """ - Class constructor method - :param self: - :param intf_name: string of interface - :return: - """ - self.appl_db = db_connect_appl() - self.state_db = db_connect_state() - self.config_db = db_connect_configdb() - if self.appl_db is None: - return - if self.state_db is None: - return - if self.config_db is None: - return - - sub_intf_only = False - sub_intf_name = intf_name - if intf_name is not None: - if intf_name == SUB_PORT: - intf_name = None - sub_intf_name = None - sub_intf_only = True - else: - sub_intf_sep_idx = intf_name.find(VLAN_SUB_INTERFACE_SEPARATOR) - if sub_intf_sep_idx != -1: - sub_intf_only = True - intf_name = intf_name[:sub_intf_sep_idx] + @multi_asic_util.run_on_multi_asic + def get_intf_status(self): self.front_panel_ports_list = get_frontpanel_port_list(self.config_db) - appl_db_keys = appl_db_keys_get(self.appl_db, self.front_panel_ports_list, None) + self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, None) self.int_to_vlan_dict = get_interface_vlan_dict(self.config_db) self.get_raw_po_int_configdb_info = get_raw_portchannel_info(self.config_db) self.portchannel_list = get_portchannel_list(self.get_raw_po_int_configdb_info) @@ -451,16 +433,13 @@ class IntfStatus(object): self.po_int_dict = create_po_int_dict(self.po_int_tuple_list) self.int_po_dict = create_int_to_portchannel_dict(self.po_int_tuple_list) self.combined_int_to_vlan_po_dict = merge_dicts(self.int_to_vlan_dict, self.int_po_dict) - self.portchannel_speed_dict = po_speed_dict(self.po_int_dict, self.appl_db) + self.portchannel_speed_dict = po_speed_dict(self.po_int_dict, self.db) self.portchannel_keys = self.portchannel_speed_dict.keys() self.sub_intf_list = get_sub_port_intf_list(self.config_db) - appl_db_sub_intf_keys = appl_db_sub_intf_keys_get(self.appl_db, self.sub_intf_list, sub_intf_name) - if appl_db_keys is None: - return - self.display_intf_status(intf_name, appl_db_keys, self.front_panel_ports_list, self.portchannel_speed_dict, appl_db_sub_intf_keys, self.sub_intf_list, sub_intf_only) - - + self.appl_db_sub_intf_keys = appl_db_sub_intf_keys_get(self.db, self.sub_intf_list, self.sub_intf_name) + if self.appl_db_keys: + self.table += self.generate_intf_status() # ========================== interface-description logic ========================== @@ -470,7 +449,27 @@ header_desc = ['Interface', 'Oper', 'Admin', 'Alias', 'Description'] class IntfDescription(object): - def display_intf_description(self, appl_db_keys, front_panel_ports_list): + def __init__(self, intf_name, namespace_option, display_option): + self.db = None + self.config_db = None + self.table = [] + self.multi_asic = multi_asic_util.MultiAsic( + display_option, namespace_option) + + if intf_name is not None and intf_name == SUB_PORT: + self.intf_name = None + else: + self.intf_name = intf_name + + def display_intf_description(self): + + self.get_intf_description() + + # Sorting and tabulating the result table. + sorted_table = natsorted(self.table) + print tabulate(sorted_table, header_desc, tablefmt="simple", stralign='right') + + def generate_intf_description(self): """ Generate interface-description output """ @@ -483,58 +482,41 @@ class IntfDescription(object): # Iterate through all the keys and append port's associated state to # the result table. # - for i in appl_db_keys: + for i in self.appl_db_keys: key = re.split(':', i, maxsplit=1)[-1].strip() - if key in front_panel_ports_list: + if key in self.front_panel_ports_list: + if self.multi_asic.skip_display(constants.PORT_OBJ, key): + continue table.append((key, - appl_db_port_status_get(self.appl_db, key, PORT_OPER_STATUS), - appl_db_port_status_get(self.appl_db, key, PORT_ADMIN_STATUS), - appl_db_port_status_get(self.appl_db, key, PORT_ALIAS), - appl_db_port_status_get(self.appl_db, key, PORT_DESCRIPTION))) - - # Sorting and tabulating the result table. - sorted_table = natsorted(table) - print(tabulate(sorted_table, header_desc, tablefmt="simple", stralign='right')) - - def __init__(self, intf_name): - - self.config_db = db_connect_configdb() - self.appl_db = db_connect_appl() - if self.appl_db is None: - return - if self.config_db is None: - return - - if intf_name is not None and intf_name == SUB_PORT: - intf_name = None - + appl_db_port_status_get(self.db, key, PORT_OPER_STATUS), + appl_db_port_status_get(self.db, key, PORT_ADMIN_STATUS), + appl_db_port_status_get(self.db, key, PORT_ALIAS), + appl_db_port_status_get(self.db, key, PORT_DESCRIPTION))) + return table + + @multi_asic_util.run_on_multi_asic + def get_intf_description(self): self.front_panel_ports_list = get_frontpanel_port_list(self.config_db) - appl_db_keys = appl_db_keys_get(self.appl_db, self.front_panel_ports_list, intf_name) - if appl_db_keys is None: - return - - self.display_intf_description(appl_db_keys, self.front_panel_ports_list) - - - -def main(args): - if len(args) == 0: - print("No valid arguments provided") - return - - command = args[0] - if command != "status" and command != "description": - print("No valid command provided") - return - - intf_name = args[1] if len(args) == 2 else None - - if command == "status": - interface_stat = IntfStatus(intf_name) - elif command == "description": - interface_desc = IntfDescription(intf_name) + self.appl_db_keys = appl_db_keys_get(self.db, self.front_panel_ports_list, self.intf_name) + if self.appl_db_keys: + self.table += self.generate_intf_description() + +def main(): + parser = argparse.ArgumentParser(description='Display Interface information', + formatter_class=argparse.RawTextHelpFormatter) + parser.add_argument('-c', '--command', type=str, help='get interface status or description', default=None) + parser.add_argument('-i', '--interface', type=str, help='interface information for specific port: Ethernet0', default=None) + parser = multi_asic_util.multi_asic_args(parser) + args = parser.parse_args() + + if args.command == "status": + interface_stat = IntfStatus(args.interface, args.namespace, args.display) + interface_stat.display_intf_status() + elif args.command == "description": + interface_desc = IntfDescription(args.interface, args.namespace, args.display) + interface_desc.display_intf_description() sys.exit(0) if __name__ == "__main__": - main(sys.argv[1:]) + main() diff --git a/setup.py b/setup.py index 37e55cba1e..434ece3f3d 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,12 @@ ], package_data={ 'show': ['aliases.ini'], - 'tests': ['acl_input/*', 'mock_tables/*.py', 'mock_tables/*.json', 'filter_fdb_input/*'] + 'tests': ['acl_input/*', + 'mock_tables/*.py', + 'mock_tables/*.json', + 'mock_tables/asic0/*.json', + 'mock_tables/asic1/*.json', + 'filter_fdb_input/*'] }, scripts=[ 'scripts/aclshow', diff --git a/show/interfaces/__init__.py b/show/interfaces/__init__.py index b2d1c17936..06c74b9b2a 100644 --- a/show/interfaces/__init__.py +++ b/show/interfaces/__init__.py @@ -4,15 +4,17 @@ from natsort import natsorted from tabulate import tabulate +from sonic_py_common import multi_asic import utilities_common.cli as clicommon +import utilities_common.multi_asic as multi_asic_util import portchannel -def try_convert_interfacename_from_alias(ctx, db, interfacename): +def try_convert_interfacename_from_alias(ctx, interfacename): """try to convert interface name from alias""" if clicommon.get_interface_naming_mode() == "alias": alias = interfacename - interfacename = clicommon.InterfaceAliasConverter(db).alias_to_name(alias) + interfacename = clicommon.InterfaceAliasConverter().alias_to_name(alias) # TODO: ideally alias_to_name should return None when it cannot find # the port name for the alias if interfacename == alias: @@ -31,19 +33,19 @@ def interfaces(): # 'alias' subcommand ("show interfaces alias") @interfaces.command() @click.argument('interfacename', required=False) -@clicommon.pass_db -def alias(db, interfacename): +@multi_asic_util.multi_asic_click_options +def alias(interfacename, namespace, display): """Show Interface Name/Alias Mapping""" ctx = click.get_current_context() - port_dict = db.cfgdb.get_table("PORT") + port_dict = multi_asic.get_port_table(namespace=namespace) header = ['Name', 'Alias'] body = [] if interfacename is not None: - interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + interfacename = try_convert_interfacename_from_alias(ctx, interfacename) # If we're given an interface name, output name and alias for that interface only if interfacename in port_dict.keys(): @@ -56,6 +58,10 @@ def alias(db, interfacename): else: # Output name and alias for all interfaces for port_name in natsorted(port_dict.keys()): + if ((display == multi_asic_util.constants.DISPLAY_EXTERNAL) and + ('role' in port_dict[port_name]) and + (port_dict[port_name]['role'] is multi_asic.INTERNAL_PORT)): + continue if 'alias' in port_dict[port_name]: body.append([port_name, port_dict[port_name]['alias']]) else: @@ -65,19 +71,25 @@ def alias(db, interfacename): @interfaces.command() @click.argument('interfacename', required=False) +@multi_asic_util.multi_asic_click_options @click.option('--verbose', is_flag=True, help="Enable verbose output") -@clicommon.pass_db -def description(db, interfacename, verbose): +def description(interfacename, namespace, display, verbose): """Show interface status, protocol and description""" ctx = click.get_current_context() - cmd = "intfutil description" + cmd = "intfutil -c description" + #ignore the display option when interface name is passed if interfacename is not None: - interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + interfacename = try_convert_interfacename_from_alias(ctx, interfacename) - cmd += " {}".format(interfacename) + cmd += " -i {}".format(interfacename) + else: + cmd += " -d {}".format(display) + + if namespace is not None: + cmd += " -n {}".format(namespace) clicommon.run_command(cmd, display_cmd=verbose) @@ -91,19 +103,24 @@ def naming_mode(verbose): @interfaces.command() @click.argument('interfacename', required=False) +@multi_asic_util.multi_asic_click_options @click.option('--verbose', is_flag=True, help="Enable verbose output") -@clicommon.pass_db -def status(db, interfacename, verbose): +def status(interfacename, namespace, display, verbose): """Show Interface status information""" ctx = click.get_current_context() - cmd = "intfutil status" + cmd = "intfutil -c status" if interfacename is not None: - interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + interfacename = try_convert_interfacename_from_alias(ctx, interfacename) - cmd += " {}".format(interfacename) + cmd += " -i {}".format(interfacename) + else: + cmd += " -d {}".format(display) + + if namespace is not None: + cmd += " -n {}".format(namespace) clicommon.run_command(cmd, display_cmd=verbose) @@ -225,7 +242,7 @@ def expected(db, interfacename): for port in natsorted(neighbor_dict.keys()): temp_port = port if clicommon.get_interface_naming_mode() == "alias": - port = clicommon.InterfaceAliasConverter(db).name_to_alias(port) + port = clicommon.InterfaceAliasConverter().name_to_alias(port) neighbor_dict[port] = neighbor_dict.pop(temp_port) device2interface_dict[neighbor_dict[port]['name']] = {'localPort': port, 'neighborPort': neighbor_dict[port]['port']} @@ -268,8 +285,7 @@ def transceiver(): @click.argument('interfacename', required=False) @click.option('-d', '--dom', 'dump_dom', is_flag=True, help="Also display Digital Optical Monitoring (DOM) data") @click.option('--verbose', is_flag=True, help="Enable verbose output") -@clicommon.pass_db -def eeprom(db, interfacename, dump_dom, verbose): +def eeprom(interfacename, dump_dom, verbose): """Show interface transceiver EEPROM information""" ctx = click.get_current_context() @@ -280,7 +296,7 @@ def eeprom(db, interfacename, dump_dom, verbose): cmd += " --dom" if interfacename is not None: - interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + interfacename = try_convert_interfacename_from_alias(ctx, interfacename) cmd += " -p {}".format(interfacename) @@ -289,8 +305,7 @@ def eeprom(db, interfacename, dump_dom, verbose): @transceiver.command() @click.argument('interfacename', required=False) @click.option('--verbose', is_flag=True, help="Enable verbose output") -@clicommon.pass_db -def lpmode(db, interfacename, verbose): +def lpmode(interfacename, verbose): """Show interface transceiver low-power mode status""" ctx = click.get_current_context() @@ -298,7 +313,7 @@ def lpmode(db, interfacename, verbose): cmd = "sudo sfputil show lpmode" if interfacename is not None: - interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + interfacename = try_convert_interfacename_from_alias(ctx, interfacename) cmd += " -p {}".format(interfacename) @@ -316,7 +331,7 @@ def presence(db, interfacename, verbose): cmd = "sfpshow presence" if interfacename is not None: - interfacename = try_convert_interfacename_from_alias(ctx, db, interfacename) + interfacename = try_convert_interfacename_from_alias(ctx, interfacename) cmd += " -p {}".format(interfacename) diff --git a/show/main.py b/show/main.py index 712ae674a4..710a4a9efe 100755 --- a/show/main.py +++ b/show/main.py @@ -468,7 +468,7 @@ def subinterfaces(): @click.option('--verbose', is_flag=True, help="Enable verbose output") def status(subinterfacename, verbose): """Show sub port interface status information""" - cmd = "intfutil status " + cmd = "intfutil -c status" if subinterfacename is not None: sub_intf_sep_idx = subinterfacename.find(VLAN_SUB_INTERFACE_SEPARATOR) @@ -479,9 +479,9 @@ def status(subinterfacename, verbose): if clicommon.get_interface_naming_mode() == "alias": subinterfacename = iface_alias_converter.alias_to_name(subinterfacename) - cmd += subinterfacename + cmd += " -i {}".format(subinterfacename) else: - cmd += "subport" + cmd += " -i subport" run_command(cmd, display_cmd=verbose) # diff --git a/tests/intfutil_test.py b/tests/intfutil_test.py index 3841420f92..53e878dcd1 100644 --- a/tests/intfutil_test.py +++ b/tests/intfutil_test.py @@ -50,7 +50,7 @@ """ show_interface_description_Ethernet0_verbose_output="""\ -Running command: intfutil description Ethernet0 +Running command: intfutil -c description -i Ethernet0 Interface Oper Admin Alias Description ----------- ------ ------- --------- -------------------- Ethernet0 down up Ethernet0 ARISTA01T2:Ethernet1 @@ -83,7 +83,7 @@ def test_intf_status(self): assert result.output == show_interface_status_output # Test 'intfutil status' - output = subprocess.check_output('intfutil status', stderr=subprocess.STDOUT, shell=True) + output = subprocess.check_output('intfutil -c status', stderr=subprocess.STDOUT, shell=True) print(output) assert result.output == show_interface_status_output @@ -93,7 +93,7 @@ def test_intf_status_verbose(self): assert result.exit_code == 0 print(result.exit_code) print(result.output) - expected_output = "Running command: intfutil status" + expected_output = "Running command: intfutil -c status -d all" assert result.output.split('\n')[0] == expected_output def test_intf_status_Ethernet32(self): @@ -164,7 +164,7 @@ def test_subintf_status(self): self.assertEqual(result.output.strip(), expected_output) # Test 'intfutil status subport' - output = subprocess.check_output('intfutil status subport', stderr=subprocess.STDOUT, shell=True) + output = subprocess.check_output('intfutil -c status -i subport', stderr=subprocess.STDOUT, shell=True) print >> sys.stderr, output self.assertEqual(output.strip(), expected_output) @@ -172,7 +172,7 @@ def test_subintf_status(self): def test_subintf_status_verbose(self): result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["--verbose"]) print >> sys.stderr, result.output - expected_output = "Command: intfutil status subport" + expected_output = "Command: intfutil -c status -i subport" self.assertEqual(result.output.split('\n')[0], expected_output) @@ -189,7 +189,7 @@ def test_single_subintf_status(self): self.assertEqual(result.output.strip(), expected_output) # Test 'intfutil status Ethernet0.10' - output = subprocess.check_output('intfutil status Ethernet0.10', stderr=subprocess.STDOUT, shell=True) + output = subprocess.check_output('intfutil -c status -i Ethernet0.10', stderr=subprocess.STDOUT, shell=True) print >> sys.stderr, output self.assertEqual(output.strip(), expected_output) @@ -197,7 +197,7 @@ def test_single_subintf_status(self): def test_single_subintf_status_verbose(self): result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["Ethernet0.10", "--verbose"]) print >> sys.stderr, result.output - expected_output = "Command: intfutil status Ethernet0.10" + expected_output = "Command: intfutil -c status -i Ethernet0.10" self.assertEqual(result.output.split('\n')[0], expected_output) @@ -222,7 +222,7 @@ def test_single_subintf_status_alias_mode_verbose(self): result = self.runner.invoke(show.cli.commands["subinterfaces"].commands["status"], ["etp1.10", "--verbose"]) print >> sys.stderr, result.output - expected_output = "Command: intfutil status Ethernet0.10" + expected_output = "Command: intfutil -c status -i Ethernet0.10" self.assertEqual(result.output.split('\n')[0], expected_output) os.environ["SONIC_CLI_IFACE_MODE"] = "default" diff --git a/tests/mock_tables/asic0/__init__.py b/tests/mock_tables/asic0/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/mock_tables/asic0/appl_db.json b/tests/mock_tables/asic0/appl_db.json new file mode 100644 index 0000000000..cfe085962f --- /dev/null +++ b/tests/mock_tables/asic0/appl_db.json @@ -0,0 +1,72 @@ +{ + "PORT_TABLE:Ethernet0": { + "lanes": "33,34,35,36", + "description": "ARISTA01T2:Ethernet3/1/1", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet1/1", + "oper_status": "up", + "admin_status": "up", + "role": "Ext", + "speed": "40000", + "asic_port_name": "Eth0-ASIC0" + }, + "PORT_TABLE:Ethernet4": { + "oper_status": "up", + "lanes": "29,30,31,32", + "description": "ARISTA01T2:Ethernet3/2/1", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet1/2", + "admin_status": "up", + "role": "Ext", + "speed": "40000", + "asic_port_name": "Eth1-ASIC0" + }, + "PORT_TABLE:Ethernet-BP0": { + "oper_status": "up", + "lanes": "93,94,95,96", + "description": "ASIC1:Eth0-ASIC1", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet-BP0", + "admin_status": "up", + "role": "Int", + "speed": "40000", + "asic_port_name": "Eth16-ASIC0" + }, + "PORT_TABLE:Ethernet-BP4": { + "oper_status": "up", + "lanes": "97,98,99,100", + "description": "ASIC1:Eth1-ASIC1", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet-BP4", + "admin_status": "up", + "role": "Int", + "speed": "40000", + "asic_port_name": "Eth17-ASIC0" + }, + "LAG_MEMBER_TABLE:PortChannel1002:Ethernet0": { + "status": "disabled" + }, + "LAG_MEMBER_TABLE:PortChannel1002:Ethernet4": { + "status": "enabled" + }, + "LAG_MEMBER_TABLE:PortChannel4001:Ethernet-BP0": { + "status": "enabled" + }, + "LAG_MEMBER_TABLE:PortChannel4001:Ethernet-BP4": { + "status": "enabled" + }, + "LAG_TABLE:PortChannel1002": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "up" + }, + "LAG_TABLE:PortChannel4001": { + "admin_status": "up", + "mtu": "9100", + "oper_status": "up" + } +} \ No newline at end of file diff --git a/tests/mock_tables/asic0/asic_db.json b/tests/mock_tables/asic0/asic_db.json new file mode 100644 index 0000000000..1a769b82b5 --- /dev/null +++ b/tests/mock_tables/asic0/asic_db.json @@ -0,0 +1,6 @@ +{ + "ASIC_STATE:SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000000": { + "SAI_SWITCH_ATTR_INIT_SWITCH": "true", + "SAI_SWITCH_ATTR_SRC_MAC_ADDRESS": "DE:AD:BE:EF:CA:FE" + } +} diff --git a/tests/mock_tables/asic0/config_db.json b/tests/mock_tables/asic0/config_db.json new file mode 100644 index 0000000000..79adcf758e --- /dev/null +++ b/tests/mock_tables/asic0/config_db.json @@ -0,0 +1,87 @@ +{ + "DEVICE_METADATA|localhost": { + "asic_id": "01.00.0", + "asic_name": "asic0", + "bgp_asn": "65100", + "cloudtype": "None", + "default_bgp_status": "down", + "default_pfcwd_status": "enable", + "deployment_id": "None", + "docker_routing_config_mode": "separated", + "hostname": "sonic", + "hwsku": "multi_asic", + "mac": "02:42:f0:7f:01:05", + "platform": "multi_asic", + "region": "None", + "sub_role": "FrontEnd", + "type": "LeafRouter" + }, + "PORT|Ethernet0": { + "admin_status": "up", + "alias": "Ethernet1/1", + "asic_port_name": "Eth0-ASIC0", + "description": "ARISTA01T2:Ethernet3/1/1", + "lanes": "33,34,35,36", + "mtu": "9100", + "pfc_asym": "off", + "role": "Ext", + "speed": "40000" + }, + "PORT|Ethernet4": { + "lanes": "29,30,31,32", + "description": "ARISTA01T2:Ethernet3/2/1", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet1/2", + "admin_status": "up", + "role": "Ext", + "speed": "40000", + "asic_port_name": "Eth1-ASIC0" + }, + "PORT|Ethernet-BP0" : { + "lanes": "93,94,95,96", + "description": "ASIC1:Eth0-ASIC1", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet-BP0", + "admin_status": "up", + "role": "Int", + "speed": "40000", + "asic_port_name": "Eth16-ASIC0" + }, + "PORT|Ethernet-BP4" : { + "lanes": "97,98,99,100", + "description": "ASIC1:Eth1-ASIC1", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet-BP4", + "admin_status": "up", + "role": "Int", + "speed": "40000", + "asic_port_name": "Eth17-ASIC0" + }, + "PORTCHANNEL|PortChannel1002": { + "admin_status": "up", + "members@": "Ethernet0,Ethernet4", + "min_links": "2", + "mtu": "9100" + }, + "PORTCHANNEL|PortChannel4001": { + "admin_status": "up", + "members@": "Ethernet-BP0,Ethernet-BP4", + "min_links": "2", + "mtu": "9100" + }, + "PORTCHANNEL_MEMBER|PortChannel1002|Ethernet0": { + "NULL": "NULL" + }, + "PORTCHANNEL_MEMBER|PortChannel1002|Ethernet4": { + "NULL": "NULL" + }, + "PORTCHANNEL_MEMBER|PortChannel4001|Ethernet-BP0": { + "NULL": "NULL" + }, + "PORTCHANNEL_MEMBER|PortChannel4001|Ethernet-BP4": { + "NULL": "NULL" + } +} \ No newline at end of file diff --git a/tests/mock_tables/asic0/counters_db.json b/tests/mock_tables/asic0/counters_db.json new file mode 100644 index 0000000000..2b2b600280 --- /dev/null +++ b/tests/mock_tables/asic0/counters_db.json @@ -0,0 +1,161 @@ +{ + "COUNTERS:oid:0x60000000005a3": { + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_PACKETS": "0" + }, + "COUNTERS:oid:0x60000000005a1": { + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_OCTETS": "608985", + "SAI_ROUTER_INTERFACE_STAT_IN_PACKETS": "883", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_PACKETS": "0" + }, + "COUNTERS:oid:0x600000000065f": { + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_OCTETS": "1128", + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_PACKETS": "2", + "SAI_ROUTER_INTERFACE_STAT_IN_OCTETS": "3", + "SAI_ROUTER_INTERFACE_STAT_IN_PACKETS": "4", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_OCTETS": "5", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS": "6", + "SAI_ROUTER_INTERFACE_STAT_OUT_OCTETS": "754", + "SAI_ROUTER_INTERFACE_STAT_OUT_PACKETS": "8" + }, + "COUNTERS:oid:0x60000000005a2": { + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_OCTETS": "608985", + "SAI_ROUTER_INTERFACE_STAT_IN_PACKETS": "883", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_PACKETS": "0" + }, + "COUNTERS:oid:0x600000000063c": { + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_PACKETS": "0" + }, + "COUNTERS:oid:0x600000000063d": { + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_IN_OCTETS": "608985", + "SAI_ROUTER_INTERFACE_STAT_IN_PACKETS": "883", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_OCTETS": "0", + "SAI_ROUTER_INTERFACE_STAT_OUT_PACKETS": "0" + }, + "COUNTERS_RIF_NAME_MAP": { + "Ethernet20": "oid:0x600000000065f", + "PortChannel0001": "oid:0x60000000005a1", + "PortChannel0002": "oid:0x60000000005a2", + "PortChannel0003": "oid:0x600000000063c", + "PortChannel0004": "oid:0x600000000063d", + "Vlan1000": "oid:0x60000000005a3" + }, + "COUNTERS_RIF_TYPE_MAP": { + "oid:0x60000000005a1": "SAI_ROUTER_INTERFACE_TYPE_PORT", + "oid:0x60000000005a2": "SAI_ROUTER_INTERFACE_TYPE_PORT", + "oid:0x60000000005a3": "SAI_ROUTER_INTERFACE_TYPE_VLAN", + "oid:0x600000000063c": "SAI_ROUTER_INTERFACE_TYPE_PORT", + "oid:0x600000000063d": "SAI_ROUTER_INTERFACE_TYPE_PORT", + "oid:0x600000000065f": "SAI_ROUTER_INTERFACE_TYPE_PORT" + }, + "COUNTERS:DATAACL:DEFAULT_RULE": { + "Bytes": "1", + "Packets": "2" + }, + "COUNTERS:DATAACL:RULE_1": { + "Bytes": "100", + "Packets": "101" + }, + "COUNTERS:DATAACL:RULE_2": { + "Bytes": "200", + "Packets": "201" + }, + "COUNTERS:DATAACL:RULE_3": { + "Bytes": "300", + "Packets": "301" + }, + "COUNTERS:DATAACL:RULE_4": { + "Bytes": "400", + "Packets": "401" + }, + "COUNTERS:DATAACL:RULE_05": { + "Bytes": "0", + "Packets": "0" + }, + "COUNTERS:EVERFLOW:RULE_6": { + "Bytes": "600", + "Packets": "601" + }, + "COUNTERS:DATAACL:RULE_7":{ + "Bytes": "700", + "Packets": "701" + }, + "COUNTERS:EVERFLOW:RULE_08": { + "Bytes": "0", + "Packets": "0" + }, + "COUNTERS:DATAACL:RULE_9": { + "Bytes": "900", + "Packets": "901" + }, + "COUNTERS:DATAACL:RULE_10": { + "Bytes": "1000", + "Packets": "1001" + }, + "COUNTERS:oid:0x1000000000002": { + "SAI_PORT_STAT_IF_IN_ERRORS": "10", + "SAI_PORT_STAT_IF_IN_DISCARDS": "100", + "SAI_PORT_STAT_IN_DROP_REASON_RANGE_BASE": "80", + "SAI_PORT_STAT_OUT_CONFIGURED_DROP_REASONS_1_DROPPED_PKTS": "20" + }, + "COUNTERS:oid:0x1000000000004": { + "SAI_PORT_STAT_IF_IN_ERRORS": "0", + "SAI_PORT_STAT_IF_IN_DISCARDS": "1000", + "SAI_PORT_STAT_IN_DROP_REASON_RANGE_BASE": "800", + "SAI_PORT_STAT_OUT_CONFIGURED_DROP_REASONS_1_DROPPED_PKTS": "100" + }, + "COUNTERS:oid:0x1000000000006": { + "SAI_PORT_STAT_IF_IN_ERRORS": "100", + "SAI_PORT_STAT_IF_IN_DISCARDS": "10", + "SAI_PORT_STAT_IN_DROP_REASON_RANGE_BASE": "10", + "SAI_PORT_STAT_OUT_CONFIGURED_DROP_REASONS_1_DROPPED_PKTS": "0" + }, + "COUNTERS:oid:0x21000000000000": { + "SAI_SWITCH_STAT_IN_DROP_REASON_RANGE_BASE": "1000" + }, + "COUNTERS_PORT_NAME_MAP": { + "Ethernet0": "oid:0x1000000000002", + "Ethernet4": "oid:0x1000000000004", + "Ethernet8": "oid:0x1000000000006" + }, + "COUNTERS_LAG_NAME_MAP": { + "PortChannel0001": "oid:0x60000000005a1", + "PortChannel0002": "oid:0x60000000005a2", + "PortChannel0003": "oid:0x600000000063c", + "PortChannel0004": "oid:0x600000000063d" + }, + "COUNTERS_DEBUG_NAME_PORT_STAT_MAP": { + "DEBUG_0": "SAI_PORT_STAT_IN_DROP_REASON_RANGE_BASE", + "DEBUG_2": "SAI_PORT_STAT_OUT_CONFIGURED_DROP_REASONS_1_DROPPED_PKTS" + }, + "COUNTERS_DEBUG_NAME_SWITCH_STAT_MAP": { + "DEBUG_1": "SAI_SWITCH_STAT_IN_DROP_REASON_RANGE_BASE" + } +} diff --git a/tests/mock_tables/asic0/database_config.json b/tests/mock_tables/asic0/database_config.json new file mode 100644 index 0000000000..d3028b0b45 --- /dev/null +++ b/tests/mock_tables/asic0/database_config.json @@ -0,0 +1,57 @@ +{ + "INSTANCES": { + "redis": { + "hostname" : "127.0.0.1", + "port" : 6379, + "unix_socket_path" : "/var/run/redis/redis.sock" + } + }, + "DATABASES" : { + "APPL_DB" : { + "id" : 0, + "separator": ":", + "instance" : "redis" + }, + "ASIC_DB" : { + "id" : 1, + "separator": ":", + "instance" : "redis" + }, + "COUNTERS_DB" : { + "id" : 2, + "separator": ":", + "instance" : "redis" + }, + "LOGLEVEL_DB" : { + "id" : 3, + "separator": ":", + "instance" : "redis" + }, + "CONFIG_DB" : { + "id" : 4, + "separator": "|", + "instance" : "redis" + }, + "PFC_WD_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "FLEX_COUNTER_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "STATE_DB" : { + "id" : 6, + "separator": "|", + "instance" : "redis" + }, + "SNMP_OVERLAY_DB" : { + "id" : 7, + "separator": "|", + "instance" : "redis" + } + }, + "VERSION" : "1.1" +} \ No newline at end of file diff --git a/tests/mock_tables/asic1/__init__.py b/tests/mock_tables/asic1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/mock_tables/asic1/appl_db.json b/tests/mock_tables/asic1/appl_db.json new file mode 100644 index 0000000000..f5f67b26ce --- /dev/null +++ b/tests/mock_tables/asic1/appl_db.json @@ -0,0 +1,35 @@ +{ + "PORT_TABLE:Ethernet-BP256": { + "oper_status": "up", + "lanes": "61,62,63,64", + "description": "ASIC0:Eth16-ASIC0", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet-BP256", + "admin_status": "up", + "speed": "40000", + "asic_port_name": "Eth0-ASIC1" + }, + "PORT_TABLE:Ethernet-BP260": { + "oper_status": "up", + "lanes": "57,58,59,60", + "description": "ASIC0:Eth17-ASIC0", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet-BP260", + "admin_status": "up", + "speed": "40000", + "asic_port_name": "Eth1-ASIC1" + }, + "LAG_TABLE:PortChannel4009": { + "admin_status": "up", + "oper_status": "up", + "mtu": "9100" + }, + "LAG_MEMBER_TABLE:PortChannel4009:Ethernet-BP256": { + "status": "enabled" + }, + "LAG_MEMBER_TABLE:PortChannel4009:Ethernet-BP260": { + "status": "enabled" + } +} \ No newline at end of file diff --git a/tests/mock_tables/asic1/config_db.json b/tests/mock_tables/asic1/config_db.json new file mode 100644 index 0000000000..c8f97c5df1 --- /dev/null +++ b/tests/mock_tables/asic1/config_db.json @@ -0,0 +1,55 @@ +{ + "DEVICE_METADATA|localhost": { + "asic_id": "08:00.0", + "asic_name": "asic1", + "bgp_asn": "65100", + "cloudtype": "None", + "default_bgp_status": "down", + "default_pfcwd_status": "enable", + "deployment_id": "None", + "docker_routing_config_mode": "separated", + "hostname": "sonic", + "hwsku": "multi_asic", + "mac": "02:42:f0:7f:01:06", + "platform": "multi_asic", + "region": "None", + "sub_role": "BackEnd", + "type": "LeafRouter" + }, + "PORT|Ethernet-BP256": { + "admin_status": "up", + "alias": "Ethernet-BP256", + "asic_port_name": "Eth0-ASIC1", + "description": "ASIC0:Eth16-ASIC0", + "lanes": "61,62,63,64", + "mtu": "9100", + "pfc_asym": "off", + "role": "Int", + "speed": "40000" + }, + "PORT|Ethernet-BP260": { + "lanes": "57,58,59,60", + "description": "ASIC0:Eth17-ASIC0", + "pfc_asym": "off", + "mtu": "9100", + "alias": "Ethernet-BP260", + "admin_status": "up", + "speed": "40000", + "role": "Int", + "asic_port_name": "Eth1-ASIC1" + }, + "PORTCHANNEL|PortChannel4009": { + "admin_status": "up", + "members": [ + "Ethernet-BP256,Ethernet-BP260" + ], + "min_links": "2", + "mtu": "9100" + }, + "PORTCHANNEL_MEMBER|PortChannel4009|Ethernet-BP256": { + "NULL": "NULL" + }, + "PORTCHANNEL_MEMBER|PortChannel4009|Ethernet-BP260" : { + "NULL": "NULL" + } +} \ No newline at end of file diff --git a/tests/mock_tables/asic1/database_config.json b/tests/mock_tables/asic1/database_config.json new file mode 100644 index 0000000000..d3028b0b45 --- /dev/null +++ b/tests/mock_tables/asic1/database_config.json @@ -0,0 +1,57 @@ +{ + "INSTANCES": { + "redis": { + "hostname" : "127.0.0.1", + "port" : 6379, + "unix_socket_path" : "/var/run/redis/redis.sock" + } + }, + "DATABASES" : { + "APPL_DB" : { + "id" : 0, + "separator": ":", + "instance" : "redis" + }, + "ASIC_DB" : { + "id" : 1, + "separator": ":", + "instance" : "redis" + }, + "COUNTERS_DB" : { + "id" : 2, + "separator": ":", + "instance" : "redis" + }, + "LOGLEVEL_DB" : { + "id" : 3, + "separator": ":", + "instance" : "redis" + }, + "CONFIG_DB" : { + "id" : 4, + "separator": "|", + "instance" : "redis" + }, + "PFC_WD_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "FLEX_COUNTER_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "STATE_DB" : { + "id" : 6, + "separator": "|", + "instance" : "redis" + }, + "SNMP_OVERLAY_DB" : { + "id" : 7, + "separator": "|", + "instance" : "redis" + } + }, + "VERSION" : "1.1" +} \ No newline at end of file diff --git a/tests/mock_tables/database_config.json b/tests/mock_tables/database_config.json new file mode 100644 index 0000000000..04c064abb3 --- /dev/null +++ b/tests/mock_tables/database_config.json @@ -0,0 +1,57 @@ +{ + "INSTANCES": { + "redis": { + "hostname" : "227.0.0.1", + "port" : 6379, + "unix_socket_path" : "/var/run/redis/redis.sock" + } + }, + "DATABASES" : { + "APPL_DB" : { + "id" : 0, + "separator": ":", + "instance" : "redis" + }, + "ASIC_DB" : { + "id" : 1, + "separator": ":", + "instance" : "redis" + }, + "COUNTERS_DB" : { + "id" : 2, + "separator": ":", + "instance" : "redis" + }, + "LOGLEVEL_DB" : { + "id" : 3, + "separator": ":", + "instance" : "redis" + }, + "CONFIG_DB" : { + "id" : 4, + "separator": "|", + "instance" : "redis" + }, + "PFC_WD_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "FLEX_COUNTER_DB" : { + "id" : 5, + "separator": ":", + "instance" : "redis" + }, + "STATE_DB" : { + "id" : 6, + "separator": "|", + "instance" : "redis" + }, + "SNMP_OVERLAY_DB" : { + "id" : 7, + "separator": "|", + "instance" : "redis" + } + }, + "VERSION" : "1.1" +} \ No newline at end of file diff --git a/tests/mock_tables/database_global.json b/tests/mock_tables/database_global.json new file mode 100644 index 0000000000..f6f35f7fde --- /dev/null +++ b/tests/mock_tables/database_global.json @@ -0,0 +1,16 @@ +{ + "INCLUDES" : [ + { + "include" : "database_config.json" + }, + { + "namespace" : "asic0", + "include" : "./asic0/database_config.json" + }, + { + "namespace" : "asic1", + "include" : "./asic1/database_config.json" + } + ], + "VERSION" : "1.0" +} \ No newline at end of file diff --git a/tests/mock_tables/dbconnector.py b/tests/mock_tables/dbconnector.py index dc1a64bc87..a06cfea202 100644 --- a/tests/mock_tables/dbconnector.py +++ b/tests/mock_tables/dbconnector.py @@ -5,8 +5,44 @@ import mock import mockredis import swsssdk.interface +from sonic_py_common import multi_asic +from swsssdk import SonicDBConfig, SonicV2Connector from swsssdk.interface import redis +def clean_up_config(): + # Set SonicDBConfig variables to initial state + # so that it can be loaded with single or multiple + # namespaces before the test begins. + SonicDBConfig._sonic_db_config = {} + SonicDBConfig._sonic_db_global_config_init = False + SonicDBConfig._sonic_db_config_init = False + +def load_namespace_config(): + # To support multi asic testing + # SonicDBConfig load_sonic_global_db_config + # is invoked to load multiple namespaces + clean_up_config() + SonicDBConfig.load_sonic_global_db_config( + global_db_file_path=os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'database_global.json')) + +def load_database_config(): + # Load local database_config.json for single namespace test scenario + clean_up_config() + SonicDBConfig.load_sonic_db_config( + sonic_db_file_path=os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'database_config.json')) + + +_old_connect_SonicV2Connector = SonicV2Connector.connect + +def connect_SonicV2Connector(self, db_name, retry_on=True): + + # add the namespace to kwargs for testing multi asic + self.dbintf.redis_kwargs['namespace'] = self.namespace + # Mock DB filename for unit-test + self.dbintf.redis_kwargs['db_name'] = db_name + _old_connect_SonicV2Connector(self, db_name, retry_on) def _subscribe_keyspace_notification(self, db_name, client): pass @@ -41,27 +77,25 @@ def clear(self): class SwssSyncClient(mockredis.MockRedis): def __init__(self, *args, **kwargs): super(SwssSyncClient, self).__init__(strict=True, *args, **kwargs) - db = kwargs.pop('db') - if db == 0: - fname = 'appl_db.json' - elif db == 1: - fname = 'asic_db.json' - elif db == 2: - fname = 'counters_db.json' - elif db == 4: - fname = 'config_db.json' - elif db == 6: - fname = 'state_db.json' - else: - raise ValueError("Invalid db") + # Namespace is added in kwargs specifically for unit-test + # to identify the file path to load the db json files. + namespace = kwargs.pop('namespace') + db_name = kwargs.pop('db_name') + fname = db_name.lower() + ".json" self.pubsub = MockPubSub() + + if namespace is not None and namespace is not multi_asic.DEFAULT_NAMESPACE: + fname = os.path.join(INPUT_DIR, namespace, fname) + else: + fname = os.path.join(INPUT_DIR, fname) + - fname = os.path.join(INPUT_DIR, fname) - with open(fname) as f: - js = json.load(f) - for h, table in js.items(): - for k, v in table.items(): - self.hset(h, k, v) + if os.path.exists(fname): + with open(fname) as f: + js = json.load(f) + for h, table in js.items(): + for k, v in table.items(): + self.hset(h, k, v) # Patch mockredis/mockredis/client.py # The official implementation will filter out keys with a slash '/' @@ -90,3 +124,4 @@ def keys(self, pattern='*'): swsssdk.interface.DBInterface._subscribe_keyspace_notification = _subscribe_keyspace_notification mockredis.MockRedis.config_set = config_set redis.StrictRedis = SwssSyncClient +SonicV2Connector.connect = connect_SonicV2Connector diff --git a/tests/mock_tables/mock_multi_asic.py b/tests/mock_tables/mock_multi_asic.py new file mode 100644 index 0000000000..79c8ebda1c --- /dev/null +++ b/tests/mock_tables/mock_multi_asic.py @@ -0,0 +1,20 @@ +# MONKEY PATCH!!! +import mock +from sonic_py_common import multi_asic + + +def mock_get_num_asics(): + return 2 + + +def mock_is_multi_asic(): + return True + + +def mock_get_namespace_list(namespace=None): + return ['asic0', 'asic1'] + + +multi_asic.get_num_asics = mock_get_num_asics +multi_asic.is_multi_asic = mock_is_multi_asic +multi_asic.get_namespace_list = mock_get_namespace_list diff --git a/tests/multi_asic_intfutil_test.py b/tests/multi_asic_intfutil_test.py new file mode 100644 index 0000000000..722ed92957 --- /dev/null +++ b/tests/multi_asic_intfutil_test.py @@ -0,0 +1,182 @@ +import os +import subprocess + +from click.testing import CliRunner + +root_path = os.path.dirname(os.path.abspath(__file__)) +modules_path = os.path.dirname(root_path) +scripts_path = os.path.join(modules_path, "scripts") + +intf_status_all = """\ + Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC +--------------- ------------ ------- ----- ----- -------------- --------------- ------ ------- ------ ---------- + Ethernet0 33,34,35,36 40G 9100 N/A Ethernet1/1 PortChannel1002 up up N/A off + Ethernet4 29,30,31,32 40G 9100 N/A Ethernet1/2 PortChannel1002 up up N/A off + Ethernet-BP0 93,94,95,96 40G 9100 N/A Ethernet-BP0 PortChannel4001 up up N/A off + Ethernet-BP4 97,98,99,100 40G 9100 N/A Ethernet-BP4 PortChannel4001 up up N/A off + Ethernet-BP256 61,62,63,64 40G 9100 N/A Ethernet-BP256 PortChannel4009 up up N/A off + Ethernet-BP260 57,58,59,60 40G 9100 N/A Ethernet-BP260 PortChannel4009 up up N/A off +PortChannel1002 N/A 80G 9100 N/A N/A routed up up N/A N/A +PortChannel4001 N/A 80G 9100 N/A N/A routed up up N/A N/A +PortChannel4009 N/A 80G 9100 N/A N/A routed up up N/A N/A +""" +intf_status = """\ + Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC +--------------- ----------- ------- ----- ----- ----------- --------------- ------ ------- ------ ---------- + Ethernet0 33,34,35,36 40G 9100 N/A Ethernet1/1 PortChannel1002 up up N/A off + Ethernet4 29,30,31,32 40G 9100 N/A Ethernet1/2 PortChannel1002 up up N/A off +PortChannel1002 N/A 80G 9100 N/A N/A routed up up N/A N/A +""" + +intf_status_asic0 = """\ + Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC +--------------- ----------- ------- ----- ----- ----------- --------------- ------ ------- ------ ---------- + Ethernet0 33,34,35,36 40G 9100 N/A Ethernet1/1 PortChannel1002 up up N/A off + Ethernet4 29,30,31,32 40G 9100 N/A Ethernet1/2 PortChannel1002 up up N/A off +PortChannel1002 N/A 80G 9100 N/A N/A routed up up N/A N/A +""" + +intf_status_asic0_all = """\ + Interface Lanes Speed MTU FEC Alias Vlan Oper Admin Type Asym PFC +--------------- ------------ ------- ----- ----- ------------ --------------- ------ ------- ------ ---------- + Ethernet0 33,34,35,36 40G 9100 N/A Ethernet1/1 PortChannel1002 up up N/A off + Ethernet4 29,30,31,32 40G 9100 N/A Ethernet1/2 PortChannel1002 up up N/A off + Ethernet-BP0 93,94,95,96 40G 9100 N/A Ethernet-BP0 PortChannel4001 up up N/A off + Ethernet-BP4 97,98,99,100 40G 9100 N/A Ethernet-BP4 PortChannel4001 up up N/A off +PortChannel1002 N/A 80G 9100 N/A N/A routed up up N/A N/A +PortChannel4001 N/A 80G 9100 N/A N/A routed up up N/A N/A +""" +intf_description = """\ + Interface Oper Admin Alias Description +----------- ------ ------- ----------- ------------------------ + Ethernet0 up up Ethernet1/1 ARISTA01T2:Ethernet3/1/1 + Ethernet4 up up Ethernet1/2 ARISTA01T2:Ethernet3/2/1 +""" + +intf_description_all = """\ + Interface Oper Admin Alias Description +-------------- ------ ------- -------------- ------------------------ + Ethernet0 up up Ethernet1/1 ARISTA01T2:Ethernet3/1/1 + Ethernet4 up up Ethernet1/2 ARISTA01T2:Ethernet3/2/1 + Ethernet-BP0 up up Ethernet-BP0 ASIC1:Eth0-ASIC1 + Ethernet-BP4 up up Ethernet-BP4 ASIC1:Eth1-ASIC1 +Ethernet-BP256 up up Ethernet-BP256 ASIC0:Eth16-ASIC0 +Ethernet-BP260 up up Ethernet-BP260 ASIC0:Eth17-ASIC0 +""" + +intf_description_asic0 = """\ + Interface Oper Admin Alias Description +----------- ------ ------- ----------- ------------------------ + Ethernet0 up up Ethernet1/1 ARISTA01T2:Ethernet3/1/1 + Ethernet4 up up Ethernet1/2 ARISTA01T2:Ethernet3/2/1 +""" + +intf_description_asic0_all = """\ + Interface Oper Admin Alias Description +------------ ------ ------- ------------ ------------------------ + Ethernet0 up up Ethernet1/1 ARISTA01T2:Ethernet3/1/1 + Ethernet4 up up Ethernet1/2 ARISTA01T2:Ethernet3/2/1 +Ethernet-BP0 up up Ethernet-BP0 ASIC1:Eth0-ASIC1 +Ethernet-BP4 up up Ethernet-BP4 ASIC1:Eth1-ASIC1 +""" + +intf_invalid_asic_error="""ValueError: Unknown Namespace asic99""" + +class TestInterfacesMultiAsic(object): + @classmethod + def setup_class(cls): + print("SETUP") + os.environ["PATH"] += os.pathsep + scripts_path + os.environ["UTILITIES_UNIT_TESTING"] = "2" + os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic" + + def setUp(self): + self.runner = CliRunner() + + def get_result_and_return_code(self, cmd): + return_code = 0 + try: + output = subprocess.check_output( + cmd, stderr=subprocess.STDOUT, shell=True) + except subprocess.CalledProcessError as e: + return_code = e.returncode + #store only the error, no need for the traceback + output = e.output.strip().split("\n")[-1] + + return(return_code, output) + + def test_multi_asic_interface_status_all(self): + return_code, result = self.get_result_and_return_code( 'intfutil -c status -d all') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_status_all + + def test_multi_asic_interface_status(self): + return_code, result = self.get_result_and_return_code('intfutil -c status') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_status + + def test_multi_asic_interface_status_asic0_all(self): + return_code, result = self.get_result_and_return_code('intfutil -c status -n asic0 -d all') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_status_asic0_all + + def test_multi_asic_interface_status_asic0(self): + return_code, result = self.get_result_and_return_code('intfutil -c status -n asic0') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_status_asic0 + + def test_multi_asic_interface_desc(self): + return_code, result = self.get_result_and_return_code('intfutil -c description') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_description + + def test_multi_asic_interface_desc_all(self): + return_code, result = self.get_result_and_return_code( 'intfutil -c description -d all') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_description_all + + def test_multi_asic_interface_asic0(self): + return_code, result = self.get_result_and_return_code( 'intfutil -c description -n asic0') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_description_asic0 + + def test_multi_asic_interface_desc_asic0_all(self): + return_code, result = self.get_result_and_return_code('intfutil -c description -n asic0 -d all') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 0 + assert result == intf_description_asic0_all + + def test_invalid_asic_name(self): + return_code, result = self.get_result_and_return_code('intfutil -c description -n asic99 -d all') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 1 + assert result == intf_invalid_asic_error + + def test_invalid_asic_name(self): + return_code, result = self.get_result_and_return_code('intfutil -c status -n asic99') + print("return_code: {}".format(return_code)) + print("result = {}".format(result)) + assert return_code == 1 + assert result == intf_invalid_asic_error + + def teardown_class(cls): + print("TEARDOWN") + os.environ["PATH"] = os.pathsep.join( + os.environ["PATH"].split(os.pathsep)[:-1]) + os.environ["UTILITIES_UNIT_TESTING"] = "0" diff --git a/utilities_common/cli.py b/utilities_common/cli.py index 912cfeb596..3b45ee92e5 100644 --- a/utilities_common/cli.py +++ b/utilities_common/cli.py @@ -8,8 +8,7 @@ from natsort import natsorted from utilities_common.db import Db - -from swsssdk import ConfigDBConnector +from sonic_py_common import multi_asic VLAN_SUB_INTERFACE_SEPARATOR = '.' @@ -122,13 +121,12 @@ class InterfaceAliasConverter(object): def __init__(self, db=None): if db is None: - self.config_db = ConfigDBConnector() - self.config_db.connect() + self.port_dict = multi_asic.get_port_table() else: self.config_db = db.cfgdb - + self.port_dict = self.config_db.get_table('PORT') self.alias_max_length = 0 - self.port_dict = self.config_db.get_table('PORT') + if not self.port_dict: click.echo(message="Warning: failed to retrieve PORT table from ConfigDB!", err=True) From d198e6cca8838298db73ce9ee0cc434ba00fc7c9 Mon Sep 17 00:00:00 2001 From: Olivier Singla Date: Sat, 11 Jul 2020 19:58:59 -0400 Subject: [PATCH 48/48] Use /var/tmp instead of /tmp for sonic_installer and generate_dump sonic_installer and generate_dump utilities use currently /tmp directory to store temporary files. Since the amount of data stored can be rather large (especially in the case of the sonic_installer), we are switching to use /var/tmp instead. We are doing these changes in anticipation that at some point /tmp will being mounted over TMPFS /tmp is typically mounted on TMPFS, which means it's fast and does not produce wear on SSD or Hard-Drive. /var/tmp is mounted on the rootfs partition, therefore not in the TMPFS partition. We also noticed that both sonic_installer and generate_dump produce temporary files which are not deleted once thse programes are done. We are then making changes to peform a clean-up and remove the temporary files not needed anymore. --- scripts/generate_dump | 16 ++++++++++------ sonic_installer/bootloader/aboot.py | 7 ++++--- sonic_installer/bootloader/grub.py | 1 + sonic_installer/bootloader/onie.py | 3 ++- sonic_installer/bootloader/uboot.py | 1 + sonic_installer/common.py | 7 ++++++- sonic_installer/main.py | 27 +++++++++++++++++++++++---- 7 files changed, 47 insertions(+), 15 deletions(-) diff --git a/scripts/generate_dump b/scripts/generate_dump index 01e45f801a..579c52cb7b 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -24,7 +24,8 @@ NOOP=false DO_COMPRESS=true CMD_PREFIX= SINCE_DATE="@0" # default is set to January 1, 1970 at 00:00:00 GMT -REFERENCE_FILE=/tmp/reference +TMP_PREFIX=/var/tmp +REFERENCE_FILE=${TMP_PREFIX}/reference BASE=sonic_dump_`hostname`_`date +%Y%m%d_%H%M%S` DUMPDIR=/var/dump TARDIR=$DUMPDIR/$BASE @@ -423,12 +424,12 @@ main() { local asic="$(/usr/local/bin/sonic-cfggen -y /etc/sonic/sonic_version.yml -v asic_type)" if [[ "$asic" = "mellanox" ]]; then - local sai_dump_filename="/tmp/sai_sdk_dump_$(date +"%m_%d_%Y_%I_%M_%p")" + local sai_dump_filename="${TMP_PREFIX}/sai_sdk_dump_$(date +"%m_%d_%Y_%I_%M_%p")" ${CMD_PREFIX}docker exec -it syncd saisdkdump -f $sai_dump_filename - ${CMD_PREFIX}docker exec syncd tar Ccf $(dirname $sai_dump_filename) - $(basename $sai_dump_filename) | tar Cxf /tmp/ - + ${CMD_PREFIX}docker exec syncd tar Ccf $(dirname $sai_dump_filename) - $(basename $sai_dump_filename) | tar Cxf ${TMP_PREFIX}/ - save_file $sai_dump_filename sai_sdk_dump true - local mst_dump_filename="/tmp/mstdump" + local mst_dump_filename="${TMP_PREFIX}/mstdump" local max_dump_count="3" for i in $(seq 1 $max_dump_count); do ${CMD_PREFIX}/usr/bin/mstdump /dev/mst/mt*conf0 > "${mst_dump_filename}${i}" @@ -519,8 +520,8 @@ main() { # run 'hw-management-generate-dump.sh' script and save the result file /usr/bin/hw-management-generate-dump.sh - save_file "/tmp/hw-mgmt-dump*" "hw-mgmt" false - rm -f /tmp/hw-mgmt-dump* + save_file "${TMP_PREFIX}/hw-mgmt-dump*" "hw-mgmt" false + rm -f ${TMP_PREFIX}/hw-mgmt-dump* # clean up working tar dir before compressing $RM $V -rf $TARDIR @@ -535,6 +536,9 @@ main() { fi echo ${TARFILE} + + # Cleanup + rm -f ${REFERENCE_FILE} } ############################################################################### diff --git a/sonic_installer/bootloader/aboot.py b/sonic_installer/bootloader/aboot.py index 3cc43a457f..becda5436b 100644 --- a/sonic_installer/bootloader/aboot.py +++ b/sonic_installer/bootloader/aboot.py @@ -18,6 +18,7 @@ HOST_PATH, IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, run_command, ) from .bootloader import Bootloader @@ -42,7 +43,7 @@ class AbootBootloader(Bootloader): NAME = 'aboot' BOOT_CONFIG_PATH = os.path.join(HOST_PATH, 'boot-config') - DEFAULT_IMAGE_PATH = '/tmp/sonic_image.swi' + DEFAULT_IMAGE_PATH = TMP_PREFIX+'/sonic_image.swi' def _boot_config_read(self, path=BOOT_CONFIG_PATH): config = collections.OrderedDict() @@ -100,8 +101,8 @@ def set_next_image(self, image): return True def install_image(self, image_path): - run_command("/usr/bin/unzip -od /tmp %s boot0" % image_path) - run_command("swipath=%s target_path=/host sonic_upgrade=1 . /tmp/boot0" % image_path) + run_command("/usr/bin/unzip -od %s %s boot0" % (TMP_PREFIX, image_path)) + run_command("swipath=%s target_path=/host sonic_upgrade=1 . %s/boot0" % (image_path, TMP_PREFIX)) def remove_image(self, image): nextimage = self.get_next_image() diff --git a/sonic_installer/bootloader/grub.py b/sonic_installer/bootloader/grub.py index 1d111f4191..f3e2834c5a 100644 --- a/sonic_installer/bootloader/grub.py +++ b/sonic_installer/bootloader/grub.py @@ -12,6 +12,7 @@ HOST_PATH, IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, run_command, ) from .onie import OnieInstallerBootloader diff --git a/sonic_installer/bootloader/onie.py b/sonic_installer/bootloader/onie.py index ca16172efa..eedfb1c912 100644 --- a/sonic_installer/bootloader/onie.py +++ b/sonic_installer/bootloader/onie.py @@ -10,6 +10,7 @@ from ..common import ( IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, ) from .bootloader import Bootloader @@ -20,7 +21,7 @@ def default_sigpipe(): class OnieInstallerBootloader(Bootloader): # pylint: disable=abstract-method - DEFAULT_IMAGE_PATH = '/tmp/sonic_image' + DEFAULT_IMAGE_PATH = TMP_PREFIX+'/sonic_image' def get_current_image(self): cmdline = open('/proc/cmdline', 'r') diff --git a/sonic_installer/bootloader/uboot.py b/sonic_installer/bootloader/uboot.py index 47252dd6af..5bc10c6fa2 100644 --- a/sonic_installer/bootloader/uboot.py +++ b/sonic_installer/bootloader/uboot.py @@ -11,6 +11,7 @@ HOST_PATH, IMAGE_DIR_PREFIX, IMAGE_PREFIX, + TMP_PREFIX, run_command, ) from .onie import OnieInstallerBootloader diff --git a/sonic_installer/common.py b/sonic_installer/common.py index a9df312a85..f95a54a914 100644 --- a/sonic_installer/common.py +++ b/sonic_installer/common.py @@ -5,6 +5,7 @@ import subprocess import sys +import os import click @@ -13,9 +14,10 @@ HOST_PATH = '/host' IMAGE_PREFIX = 'SONiC-OS-' IMAGE_DIR_PREFIX = 'image-' +TMP_PREFIX = '/var/tmp' # Run bash command and print output to stdout -def run_command(command): +def run_command(command, img_to_delete=None): click.echo(click.style("Command: ", fg='cyan') + click.style(command, fg='green')) proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) @@ -24,6 +26,9 @@ def run_command(command): click.echo(out) if proc.returncode != 0: + if img_to_delete: + if os.path.exists(img_to_delete): + os.remove(img_to_delete) sys.exit(proc.returncode) # Run bash command and return output, raise if it fails diff --git a/sonic_installer/main.py b/sonic_installer/main.py index ca843c394b..c35c690663 100644 --- a/sonic_installer/main.py +++ b/sonic_installer/main.py @@ -17,7 +17,11 @@ from swsssdk import SonicV2Connector from .bootloader import get_bootloader -from .common import run_command, run_command_or_raise +from .common import ( + TMP_PREFIX, + run_command, + run_command_or_raise +) from .exception import SonicRuntimeException SYSLOG_IDENTIFIER = "sonic-installer" @@ -84,7 +88,6 @@ def get_command(self, ctx, cmd_name): return click.Group.get_command(self, ctx, matches[0]) ctx.fail('Too many matches: %s' % ', '.join(sorted(matches))) - # # Helper functions # @@ -303,6 +306,8 @@ def install(url, force, skip_migration=False): binary_image_version = bootloader.get_binary_image_version(image_path) if not binary_image_version: click.echo("Image file does not exist or is not a valid SONiC image file") + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() # Is this version already installed? @@ -310,6 +315,8 @@ def install(url, force, skip_migration=False): click.echo("Image {} is already installed. Setting it as default...".format(binary_image_version)) if not bootloader.set_default_image(binary_image_version): click.echo('Error: Failed to set image as default') + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() else: # Verify that the binary image is of the same type as the running image @@ -317,6 +324,8 @@ def install(url, force, skip_migration=False): click.echo("Image file '{}' is of a different type than running image.\n" "If you are sure you want to install this image, use -f|--force.\n" "Aborting...".format(image_path)) + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() click.echo("Installing image {} and setting it as default...".format(binary_image_version)) @@ -329,6 +338,10 @@ def install(url, force, skip_migration=False): update_sonic_environment(click, binary_image_version) + # Clean-up by deleting downloaded file + if os.path.exists(image_path): + os.remove(image_path) + # Finally, sync filesystem run_command("sync;sync;sync") run_command("sleep 3") # wait 3 seconds after sync @@ -478,7 +491,7 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): image_latest = image_name + ":latest" image_id_previous = get_container_image_id(image_latest) - DEFAULT_IMAGE_PATH = os.path.join("/tmp/", image_name) + DEFAULT_IMAGE_PATH = os.path.join(TMP_PREFIX+"/", image_name) if url.startswith('http://') or url.startswith('https://'): click.echo('Downloading image...') validate_url_or_abort(url) @@ -495,6 +508,8 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): # TODO: Verify the file is a *proper Docker image file* if not os.path.isfile(image_path): click.echo("Image file '{}' does not exist or is not a regular file. Aborting...".format(image_path)) + if os.path.exists(image_path): + os.remove(image_path) raise click.Abort() warm_configured = False @@ -515,7 +530,7 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): # Fetch tag of current running image tag_previous = get_docker_tag_name(image_latest) # Load the new image beforehand to shorten disruption time - run_command("docker load < %s" % image_path) + run_command("docker load < %s" % image_path, image_path) warm_app_names = [] # warm restart specific procssing for swss, bgp and teamd dockers. if warm_configured is True or warm: @@ -618,6 +633,10 @@ def upgrade_docker(container_name, url, cleanup_image, skip_check, tag, warm): if container_name == "swss" or container_name == "bgp" or container_name == "teamd": run_command("config warm_restart disable %s" % container_name) + # Clean-up by deleting downloaded file + if os.path.exists(image_path): + os.remove(image_path) + if state == exp_state: click.echo('Done') else: